很久没有写博客了,主要仍是任务过多哈。在开发的过程中,也记录了不少东西,可是技术这个事吧,其实,时效性真的事很是强……就好比说,你昨天还津津乐道的一个难点解决方案,你过个几天再回过头去看它,就会有一种莫名的“轻视感”(不知道有没有这个说法,反正你们本身体会吧……),以为它也很少如此嘛。而后慢慢的就不知道分享什么了。也所以,趁着热度还在,赶忙跟你们分享一下刚刚完成一个基于CoordinatorLayout的比较复杂的交互逻辑。html
刚看到这个交互的时候,我也尝试了蛮多搜索的,可是很遗憾,结果就是只能本身搞啦~我们先看一下效果图哈,先一饱眼福,再进行深究。另外也附上Github代码仓库,方便你们实际操做:android
https://github.com/wytings/SpecialTabIndicatorgit
看着效果仍是蛮炫的,可是怎么作呢?下面慢慢给你们分析一下,我在开发这个交互的整个过程。主要分为两大部分:1、理论分析;2、技术点概况;3、技术实现细节github
首先,一看到这个交互图。我首先想到是以前写的一篇:功能分解——Android下画分时图与k线图有感。咱们要进行的第一步就是分解这个交互。缓存
一、顶部的TitleBar —— 背景从透明变黑、左边返回按钮白变蓝、右边loading progress 透明度0到1以及旋转;app
二、头部底层布局——能够切换的图片,有渐变色、有切换动画;less
三、头部下面的指示器——相似于普通的Indicator,可是selected和unselected的颜色会变化;ide
四、底部ViewPager——ViewPager就没有什么说的了。布局
以上就是咱们分拆的4个大布局类,咱们姑且把其序号当编号用,方便描述。分拆完了之后,咱们再回过头去看看,他们各自的Layout行为表现:post
1号布局位置固定不变,颜色变化;
2号布局位置固定不变,颜色和高度变化;
3号布局位置、颜色变化;
4号布局位置变化;
而后就会发现、全部的变化都在回绕着2号布局的高度而发生改变。咱们只须要让其余布局都去监听2号布局的高度变化,便可进行响应的改变了(固然这里面还有滑动冲突的解决)。有了这个思路后,即使本身写一套是否是感受也明朗好多了?可是,使人更加雀跃的是,Android里面有一个布局是彻底能够胜任这个事的,就是CoordinatorLayout。
尽管咱们知道总体上是使用CoordinatorLayout来处理布局关系,但其实仍是涉及了不少动画的改变,图标的变化等。咱们要抽出一个个技术点来,才好进行具体的实施。接下来,咱们要作的事就是这个。
2.一、背景设置渐变色
纯色背景的设置仍是比较简单,渐变色的设置则须要进行的必定的处理。这里我想到的是 GradientDrawable。经过这个类,咱们能够进行渐变背景的设置。这里比较建议代码操做而不是经过XML去操做,由于他们渐变逻辑是同样的,若是经过XM来进行,则需创建的XML文件较多。
2.二、颜色渐变切换
这里涉及到颜色切换的渐变,A颜色->B 颜色的过程得是逐渐改变而不是突变。一般这种逻辑主要涉及在动画处理。若是要单独处理则能够抽出其中的一个关键类:ArgbEvaluator。经过这个类,咱们能够计算出两种颜色发生改变的中间值这个过程可调节的参数为0到1。
2.三、PNG图标颜色渐变
咱们看到返回箭头实际上是一个PNG图标,代码里面读取后就是一个Drawable,因此咱们要对Drawable进行渐变处理。这里就涉及到一个叫TintColor的东西,经过设置它能够改变在绘制Drawable的时候的颜色。好比上面的返回图标实际上是白色的,可是在绘制这个图标的时候,是能够改变颜色的。另外须要注意的是Drawable默认是系统复用的,因此须要进行mutate()一下,避免你在修改他属性的时候影响其余场景的使用。
2.四、顶部图片切换动画
这里没有使用ViewPager,是由于ViewPager切换动画比较局限。虽然ViewPager能够自定义PageTransformer进行自定义动画切换界面,可是依然很难知足上图的交互需求,尤为是要进行跨Tab切换的时候。那咱们使用什么呢?我选择了ViewAnimationor。由于它能够自定义画面的进入仍是出去,可是因为它是单纯为了显示,因此要本身添加手势的监听来支持左右切换。
2.五、整个布局的摆放
这个就涉及到了CoordinatorLayout的用法问题了。这个控件对布局的操做都依赖于CoordinatorLayout.Behavior,这个类是咱们这个交互最核心的类。经过对子布局设置Behavior,能够决定其怎么展现。
上面咱们已经大概说明了一些依赖的技术点。若是你们对以上所说的点有不熟悉的话,最好仍是先去看看相关资料。这样对于接下来要说的,会更省心一些。另外,主要是挑几个要点讲不可能无脑贴代码。具体的代码实现,你们能够把Github上clone下来,本身慢慢看。
首先,咱们来看一下 GradientDrawable 这个类。
/** * Create a new gradient drawable given an orientation and an array * of colors for the gradient. */
public GradientDrawable(Orientation orientation, @ColorInt int[] colors) { this(new GradientState(orientation, colors), null); }
经过构造方法,咱们能够看出来,渐变色的实现仍是比较简单的。
GradientDrawable gradient = new GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT, new int[]{startColor, endColor}); view.setBackground(gradient);
图标的颜色渐变切换就是支持取出Drawable,而后进行tint操做。
Drawable wrappedDrawable = DrawableCompat.wrap(imageBack.getDrawable()).mutate(); ColorStateList tint = ColorStateList.valueOf(currentColor); DrawableCompat.setTintList(wrappedDrawable, tint);
咱们来看一下上面的操做,首先取出返回按钮的Drawable,记得要mutate(),显性声明这个Drawable再也不与其余地方进行分享,单独持有。
第二步就是进行ColorStateList的建立,你们别看每次valueOf一下就会建立一个新的,里面是有缓存机制的。而后直接更新其TintList。
最后咱们再来看看大头的 CoordinatorLayout.Behavior。有两个方法 layoutDependsOn、onDependentViewChanged是影响其布局位置的。经过注释,咱们也能够发现,第一个就是要声明,该Behavior附属的View依赖于哪个View,在CoordinatorLayout遍历整个View节点的时候会不断回调,直到return true,告诉它这个view就是个人依赖。那么日后,这个Behavior里面全部方法中的但凡是有dependency参数的都是这个return true声明的View。有点绕是吧…
另一个方法onDependentViewChanged就是在你声明依赖后,若是依赖的View发生位置或大小的变化时,就会经过这个回调进行通知,而后就能够进行相关View改变了。
/** * Determine whether the supplied child view has another specific sibling view as a * layout dependency. * * <p>This method will be called at least once in response to a layout request. If it * returns true for a given child and dependency view pair, the parent CoordinatorLayout * will:</p> * <ol> * <li>Always lay out this child after the dependent child is laid out, regardless * of child order.</li> * <li>Call {@link #onDependentViewChanged} when the dependency view's layout or * position changes.</li> * </ol> * * @param parent the parent view of the given child * @param child the child view to test * @param dependency the proposed dependency of child * @return true if child's layout depends on the proposed dependency's layout, * false otherwise * * @see #onDependentViewChanged(CoordinatorLayout, android.view.View, android.view.View) */
public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) { return false; } /** * Respond to a change in a child's dependent view * * <p>This method is called whenever a dependent view changes in size or position outside * of the standard layout flow. A Behavior may use this method to appropriately update * the child view in response.</p> * * <p>A view's dependency is determined by * {@link #layoutDependsOn(CoordinatorLayout, android.view.View, android.view.View)} or * if {@code child} has set another view as it's anchor.</p> * * <p>Note that if a Behavior changes the layout of a child via this method, it should * also be able to reconstruct the correct position in * {@link #onLayoutChild(CoordinatorLayout, android.view.View, int) onLayoutChild}. * <code>onDependentViewChanged</code> will not be called during normal layout since * the layout of each child view will always happen in dependency order.</p> * * <p>If the Behavior changes the child view's size or position, it should return true. * The default implementation returns false.</p> * * @param parent the parent view of the given child * @param child the child view to manipulate * @param dependency the dependent view that changed * @return true if the Behavior changed the child view's size or position, false otherwise */
public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) { return false; }
最后一个就是,滑动冲突的处理,固然也是在Behavior里面操做的。我就不截图了。咱们直接看具体的方法名:onStartNestedScroll、onNestedScrollAccepted、onNestedPreScroll、onNestedScroll、onNestedPreFling、onStopNestedScroll
。是否是看着有点恐怖…是的,刚开始不熟悉的话会很痛苦,理解了就行了。注意这个方法名的循序就是一个滑动操做的通用过程。
看看方法名,基本就知道是干吗的了吧。须要注意的两点是:
一、在onStarrtNestedScroll必须返回true,后续的方法才会收到剩余的Touch事件。而后就能够慢慢处理了。
二、若是在嵌套滑动的时候,打算提早处理,若是还有没消耗完的距离,要记得告诉外面的View,避免生硬的滑动。就是onNestedPreScroll中的consumed.
public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull ViewPager child, @NonNull View target, int dx, int dy, @NonNull int[] consumed, int type)
其实,整个难点基本就没了…剩下的都是实打实的细节调整。另外,这篇不是入门级解说哈,像里面涉及的动画切换就不可能再分拆出来讲了。不然,就光光那个ViewPager的Indicator均可以分拆出一个项目来进行说明了……
这个还请那些对CoordinatorLayout不太了解的同窗多多包含,本身多看点源码,跑一遍。看到手机上真实的效果后,会激起很高的学习热情的。
你们加油!