自定义Behavior之ToolBar上滑TabLayout颜色渐变
来源:互联网 发布:游戏编程图书 编辑:程序博客网 时间:2024/05/21 09:39
本篇文章介绍使用CoordinatorLayout的自定义Behavior来实现如下的效果
分析本例效果
首先我们来分析下整个例子需要实现哪些效果:
- ToolBar的上滑和下滑
- TabLayout跟随ToolBar上移和下移
- TabLayout颜色会跟随距离的变化发生渐变
- 滑动时会有黏性效果
- 滑动距离超过中间值后放开会自动滑向想要的方向
- 滑动距离未超过中间值放开则会自动回弹
本例需要的几个重要方法介绍
我们的例子中重写了Behavior的几个重要方法:
- layoutDependsOn
- onDependentViewChanged
- onLayoutChild
- onStartNestedScroll
- onNestedPreScroll
- onNestedScroll
- onStopNestedScroll
- onNestedScrollAccepted
- onNestedPreFling
- onStartNestedScroll
这些方法具体的说明可以参考:CoordinatorLayout自定义Behavior的简单总结
自定义 Behavior 实现思路
将ToolBar来作为依赖视图,TabLayout所在的父布局作为子视图,TabLayout通过 Nested Scrolling 机制调整ToolBar的位置,进而因ToolBar位置的改变,从而计算出一个百分比值,利用这个百分比值来影响自身的位置以及颜色
实现过程具体分析
有了思路我们就能一步步来实现效果了
首先继承自 Behavior,这是一个范型类,范型类型为被 Behavior 控制的视图类型:
public class ToolBarScrollBehavior extends CoordinatorLayout.Behavior<View> { private static final String TAG = ToolBarScrollBehavior.class.getSimpleName(); private WeakReference<View> mDependencyView; private WeakReference<TabLayout> mTabLayout; private OverScroller mOverScroller; private Handler mHandler; private boolean isScrolling = false; private Context mContext; private ArgbEvaluator evaluator; public ToolBarScrollBehavior(Context context, AttributeSet attrs) { super(context, attrs); mContext = context; mOverScroller = new OverScroller(context); mHandler = new Handler(); evaluator = new ArgbEvaluator(); } ......}
解释一下几个重要变量的作用:
- Scroller
用来实现用户释放手指后的滑动动画 - Handler
用来驱动 Scroller 的运行 - dependentView
是依赖视图的一个弱引用,方便我们后面的操作 - mTabLayout
是子视图里TabLayout的一个弱引用 - ArgbEvaluator
是一个可以通过[0,1]的偏移量来计算两种色彩渐变色的类
@Override public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) { CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) child.getLayoutParams(); if (params != null && params.height == CoordinatorLayout.LayoutParams.MATCH_PARENT) { child.layout(0, 0, parent.getWidth(), parent.getHeight()); return true; } return super.onLayoutChild(parent, child, layoutDirection); }
由于CoodinatorLayout本质上是一个FrameLayout,不会像 LinearLayout 一样能自动分配各个 View 的高度,本例由于ToolBar上滑后会隐藏,子视图就会填满整个屏幕,因此我们将CoodinatorLayout的宽和高填充子视图
@Override public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) { if (dependency != null && dependency.getId() == R.id.toolbar) { mDependencyView = new WeakReference<>(dependency); mTabLayout = new WeakReference<>((TabLayout) ((LinearLayout) child).getChildAt(0)); return true; } return false; }
负责查询该 Behavior 是否依赖于某个视图,这里我们判断依赖视图是否为ToolBar,是的话返回true,之后的其他操作都会围绕ToolBar来执行了,我们可以在这里拿到子视图内的TabLayout,由于CoordinatorLayout 子视图的层级关系,如果想在子视图中使用 Behavior 进行控制,那么这个子视图一定是 CoordinatorLayout 的直接孩子,间接子视图是不具有 behavior 属性的,因此我们要在这里拿到子视图内的TabLayout引用,方便之后的颜色渐变操作
@Override public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) { final float progress = Math.abs(dependency.getTranslationY() / (dependency.getHeight())); child.setTranslationY(dependency.getHeight() + dependency.getTranslationY()); final int colorPrimary = getColor(R.color.colorPrimary); final int evaluate1 = (Integer) evaluator.evaluate(progress, Color.WHITE, colorPrimary); final int evaluate2 = (Integer) evaluator.evaluate(progress, colorPrimary, Color.WHITE); getTabLayoutView().setBackgroundColor(evaluate1); getTabLayoutView().setTabTextColors(evaluate2, evaluate2); getTabLayoutView().setSelectedTabIndicatorColor(evaluate2); return true; }
我们可以在这个方法里做调整子视图的操作,因为当依赖视图发生变化的时候就会回调这个方法
依赖视图发生位移会影响translateY的值,我们主要用到的就是这个translateY
我们可以根据依赖视图的translateY除以依赖视图的高度来计算出一个百分比因数(0-1),通过这个因数配合ArgbEvaluator我们可以用来计算TabLayout颜色渐变的颜色值
最后同样也要通过依赖视图的translateY来让子视图始终紧跟依赖视图下面
@Override public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) { return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0; }
该方法在用户按下手指的时候回调,该方法在返回true的时候才会引发其他一系列的回调,这里我们只需要考虑垂直滑动,因此在垂直滑动条件成立的时候返回true
@Override public void onNestedScrollAccepted(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) { isScrolling = false; mOverScroller.abortAnimation(); super.onNestedScrollAccepted(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes); }
在这个方法里我们可以做一些准备工作,比如让之前的滑动动画结束
@Override public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) { super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed); // 在这个方法里面只处理向上滑动 if (dy < 0) { return; } View dependencyView = getDependencyView(); float transY = dependencyView.getTranslationY() - dy; if (transY < 0 && -transY < getToolbarSpreadHeight()) { dependencyView.setTranslationY(transY); consumed[1] = dy; } } @Override public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed); // 在这个方法里只处理向下滑动 if (dyUnconsumed > 0) { return; } View dependencyView = getDependencyView(); float transY = dependencyView.getTranslationY() - dyUnconsumed; if (transY < 0) { dependencyView.setTranslationY(transY); } }
这两个方法放在一起解释,由于onNestedPreScroll方法会优先于onNestedScroll之前调用,因此我们可以将上滑动作分配到onNestedPreScroll,下滑动作分配到onNestedScroll,我们来分析下这样实现的原理:
- 上滑
当用户上滑时onNestedPreScroll优先调用,我们判断滑动方向,向上滑动才继续执行,通过调整依赖视图的translateY值来进行上移操作,并且消耗相应的consumed值,之后会回调onNestedScroll方法,如果dyUnconsumed还有值的话说明没有上滑操作没有完成,直接中断,然后继续回调onNestedPreScroll方法,重复一遍上面的操作,直到onNestedScroll方法里的dyUnconsumed消耗到0时就表示上滑到头了,整个上滑操作完成 - 下滑
我们在onNestedPreScroll方法中只有上滑时dy>0的情况才继续执行,因此下滑时dy<0的值不会在onNestedPreScroll中消耗掉,会直接传递到onNestedScroll方法中的dyUnconsumed,然后我们可以通过调整依赖视图的translateY值来进行下移操作,并消耗相应的dyUnconsumed值,然后不断重复上面步骤直到依赖视图完全实现完毕,整个下滑操作完成
最后解释下为什么要分别分配到两个方法中,因为如果依赖视图完全折叠了,子视图又可以向下滚动,这时我们就不能决定是让依赖视图位移还是子视图滚动了,只有让子视图向下滚动到头才能保证唯一性
@Override public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, View child, View target, float velocityX, float velocityY) { return onUserStopDragging(velocityY); }
用户松开手指并且会发生惯性滚动之前调用,在这个方法内我们可以实现快速上滑或者快速下滑的操作
@Override public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target) { if (!isScrolling) { onUserStopDragging(800); } }
用户松开手指如果不发生惯性滚动,就会执行该方法,这里我们可以用来黏性滑动的效果
private boolean onUserStopDragging(float velocity) { View dependentView = getDependencyView(); float translateY = dependentView.getTranslationY(); float minHeaderTranslate = -(dependentView.getY() + getToolbarSpreadHeight()); if (translateY == 0 || translateY == -getToolbarSpreadHeight()) { return false; } boolean targetState; // Flag indicates whether to expand the content. if (Math.abs(velocity) <= 800) { if (Math.abs(translateY) < Math.abs(translateY - minHeaderTranslate)) { targetState = false; } else { targetState = true; } velocity = 800; // Limit velocity's minimum value. } else { if (velocity > 0) { targetState = true; } else { targetState = false; } } float targetTranslateY = targetState ? minHeaderTranslate : -dependentView.getY(); mOverScroller.startScroll(0, (int) translateY, 0, (int) (targetTranslateY), (int) (1000000 / Math.abs(velocity))); mHandler.post(flingRunnable); isScrolling = true; return true; } private Runnable flingRunnable = new Runnable() { @Override public void run() { if (mOverScroller.computeScrollOffset()) { getDependencyView().setTranslationY(mOverScroller.getCurrY()); mHandler.post(this); } else { isScrolling = false; } } };
实现黏性滑动的代码,如果提供了速度的话使用速度来滑动,否则使用默认速度来滑动,在计算出需要滑动的剩余距离后,通过Scroller 配合 Handler 来实现该效果
代码示例:
MaterialDesignFeatures
参考:
http://www.jianshu.com/p/7f50faa65622
- 自定义Behavior之ToolBar上滑TabLayout颜色渐变
- toolbar的颜色渐变
- 自定义CoordinatorLayout的Behavior(2):实现淘宝和QQ ToolBar透明渐变效果
- 自定义CoordinatorLayout的Behavior(2):实现淘宝和QQ ToolBar透明渐变效果
- android之自定义渐变颜色(一)
- android之自定义渐变颜色(二)
- android之自定义渐变颜色(二)
- 自定义View之颜色渐变折线图
- 自定义View之颜色渐变折线图
- ToolBar按钮颜色自定义
- Android 监听手指滑动,Toolbar颜色渐变
- 自定义View之GradualView文字渐变-颜色渐变-图像渐变
- 自定义ScrollView实现Toolbar(标题栏)渐变
- Toolbar的标题自定义颜色
- 自定义颜色渐变的TextView
- TabLayout之自定义样式
- 自定义Behavior之Floating头像
- Android开发之自定义Behavior
- Linux学习笔记7 cut,sort,uniq,wc,tr
- 二进制平行算法
- 三道java高级工程师面试题
- Vue 爬坑之路(一)—— 使用 vue-cli 搭建项目
- java反射简单讲解
- 自定义Behavior之ToolBar上滑TabLayout颜色渐变
- 最长上升连续子序列
- 时间序列分析笔记(待整理)
- js向div中追加html代码
- 数据结构与算法分析笔记与总结(java实现)--二叉树4:二叉树的序列化和反序列化练习题
- MongoDB笔记
- ContentProvider(内容提供者)和ContentResolve(内容访问者)
- nginx 参数优化
- hi3519v101 sdk 编译错误