关于CoordinatorLayout的用法——复杂交互的克星
好久没有写博客了,主要还是任务过多哈。在开发的过程当中,也记录了很多东西,但是技术这个事吧,其实,时效性真的事非常强……就比如说,你昨天还津津乐道的一个难点解决方案,你过个几天再回过头去看它,就会有一种莫名的“轻视感”(不知道有没有这个说法,反正大家自己体会吧……),觉得它也不多如此嘛。然后慢慢的就不知道分享什么了。也因此,趁着热度还在,赶紧跟大家分享一下刚刚完成一个基于CoordinatorLayout的比较复杂的交互逻辑。
刚看到这个交互的时候,我也尝试了蛮多搜索的,但是很遗憾,结果就是只能自己搞啦~咱们先看一下效果图哈,先一饱眼福,再进行深究。另外也附上Github代码仓库,方便大家实际操作:
https://github.com/wytings/SpecialTabIndicator
看着效果还是蛮炫的,但是怎么做呢?下面慢慢给大家分析一下,我在开发这个交互的整个过程。主要分为两大部分:一、理论分析;二、技术点概况;三、技术实现细节
一、理论分析
首先,一看到这个交互图。我首先想到是之前写的一篇:功能分解——Android下画分时图与k线图有感。我们要进行的第一步就是分解这个交互。
1、顶部的TitleBar —— 背景从透明变黑、左边返回按钮白变蓝、右边loading progress 透明度0到1以及旋转;
2、头部底层布局——可以切换的图片,有渐变色、有切换动画;
3、头部下面的指示器——类似于普通的Indicator,但是selected和unselected的颜色会变化;
4、底部ViewPager——ViewPager就没有什么说的了。
以上就是我们分拆的4个大布局类,我们姑且把其序号当编号用,方便描述。分拆完了以后,我们再回过头去看看,他们各自的Layout行为表现:
1号布局位置固定不变,颜色变化;
2号布局位置固定不变,颜色和高度变化;
3号布局位置、颜色变化;
4号布局位置变化;
然后就会发现、所有的变化都在回绕着2号布局的高度而发生改变。我们只需要让其他布局都去监听2号布局的高度变化,即可进行响应的改变了(当然这里面还有滑动冲突的解决)。有了这个思路后,即便自己写一套是不是感觉也明朗好多了?但是,令人更加雀跃的是,Android里面有一个布局是完全可以胜任这个事的,就是CoordinatorLayout。
二、技术点概况
尽管我们知道整体上是使用CoordinatorLayout来处理布局关系,但其实还是涉及了很多动画的改变,图标的变化等。我们要抽出一个个技术点来,才好进行具体的实施。接下来,我们要做的事就是这个。
2.1、背景设置渐变色
纯色背景的设置还是比较简单,渐变色的设置则需要进行的一定的处理。这里我想到的是 GradientDrawable。通过这个类,我们可以进行渐变背景的设置。这里比较建议代码操作而不是通过XML去操作,因为他们渐变逻辑是一样的,如果通过XM来进行,则需建立的XML文件较多。
2.2、颜色渐变切换
这里涉及到颜色切换的渐变,A颜色->B 颜色的过程得是逐渐改变而不是突变。通常这种逻辑主要涉及在动画处理。如果要单独处理则可以抽出其中的一个关键类:ArgbEvaluator。通过这个类,我们可以计算出两种颜色发生改变的中间值这个过程可调节的参数为0到1。
2.3、PNG图标颜色渐变
我们看到返回箭头其实是一个PNG图标,代码里面读取后就是一个Drawable,所以我们要对Drawable进行渐变处理。这里就涉及到一个叫TintColor的东西,通过设置它可以改变在绘制Drawable的时候的颜色。比如上面的返回图标其实是白色的,但是在绘制这个图标的时候,是可以改变颜色的。另外需要注意的是Drawable默认是系统复用的,所以需要进行mutate()一下,避免你在修改他属性的时候影响其他场景的使用。
2.4、顶部图片切换动画
这里没有使用ViewPager,是因为ViewPager切换动画比较局限。虽然ViewPager可以自定义PageTransformer进行自定义动画切换界面,但是依然很难满足上图的交互需求,尤其是要进行跨Tab切换的时候。那我们使用什么呢?我选择了ViewAnimationor。因为它可以自定义画面的进入还是出去,但是由于它是单纯为了显示,所以要自己添加手势的监听来支持左右切换。
2.5、整个布局的摆放
这个就涉及到了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
。是不是看着有点恐怖…是的,刚开始不熟悉的话会很痛苦,理解了就好了。注意这个方法名的循序就是一个滑动操作的通用过程。
看看方法名,基本就知道是干嘛的了吧。需要注意的两点是:
1、在onStarrtNestedScroll必须返回true,后续的方法才会收到剩余的Touch事件。然后就可以慢慢处理了。
2、如果在嵌套滑动的时候,打算提前处理,如果还有没消耗完的距离,要记得告诉外面的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不太了解的同学多多包含,自己多看点源码,跑一遍。看到手机上真实的效果后,会激起很高的学习热情的。
大家加油!
关于CoordinatorLayout的用法——复杂交互的克星的更多相关文章
- 一起玩转CoordinatorLayout
作为Material Design风格的重要组件,CoordinatorLayout协调多种组件的联动,实现各种复杂的效果,在实际项目中扮演着越来越重要的角色.本篇博客将由浅到深,带你一起玩转Coor ...
- [置顶]
针对 CoordinatorLayout 及 Behavior 的一次细节较真
我认真不是为了输赢,我就是认真.– 罗永浩 我一直对 Material Design 很感兴趣,每次在官网上阅读它的相关文档时,我总会有更进一步的体会.当然,Material Design 并不是仅仅 ...
- Linux下FTP虚拟账号环境部署总结
vsftp的用户有三种类型:匿名用户.系统用户.虚拟用户.1)匿名登录:在登录FTP时使用默认的用户名,一般是ftp或anonymous.2)本地用户登录:使用系统用户登录,在/etc/passwd中 ...
- Android-Toolbar相关
Android-Toolbar相关 学习自 <Android第一行代码> https://www.jianshu.com/p/79604c3ddcae https://www.jiansh ...
- imperva 非交互式导入导出配置
非交互使用模式full_expimp.sh可以导出/导入手动使用交互式CLI 在root的命令行下执行: 例子:导出:# full_expimp.sh --operation=1 --pwd=密码 - ...
- Ant Design Pro中Transfer穿梭框的实际用法(与后端交互)
Ant Design Pro中Transfer穿梭框的实际用法(与后端交互) 该控件的属性以及属性的作用在ADP的官方文档中都有介绍,但没有讲如何与后端交互,本文旨在讲解该控件与后端的交互. Ant ...
- JSON(五)——同步请求中使用JSON格式字符串进行交互(不太常见的用法)
在同步请求中使用JSON格式进行数据交互的场景并不多,同步请求是浏览器直接与服务器进行数据交互的大多是用jsp的标签jstl和el表达式对请求中的数据进行数据的渲染.我也是在一次开发中要从其它服务器提 ...
- python 全栈开发,Day18(对象之间的交互,类命名空间与对象,实例的命名空间,类的组合用法)
一.对象之间的交互 现在我们已经有一个人类了,通过给人类一些具体的属性我们就可以拿到一个实实在在的人.现在我们要再创建一个狗类,狗就不能打人了,只能咬人,所以我们给狗一个bite方法.有了狗类,我们还 ...
- JavaScript高级用法一之事件响应与网页交互
综述 本篇的主要内容来自慕课网,事件响应与网页交互,主要内容如下 1 什么是事件 2 鼠标单击事件( onclick ) 3 鼠标经过事件(onmouseover) 4 鼠标移开事件(onmouseo ...
随机推荐
- 利用Jsonp实现跨域请求,spring MVC+JQuery
1 什么是Jsonp? JSONP(JSON with Padding)是数据格式JSON的一种"使用模式",可以让网页从别的网域要数据.另一个解决这个问题的新方法是跨来源资源共享 ...
- Selenium+Python ---- 免登录、等待、unittest单元测试框架、PO模型
1.免登录在进行测试的过程中难免会遇到登录的情况,给测试工作添加了工作量,本文仅提供一些思路供参考解决方式:手动请求中添加cookies.火狐的profile文件记录信息实现.人工介入.万能验证码.去 ...
- Sping Boot入门到实战之入门篇(二):第一个Spring Boot应用
该篇为Spring Boot入门到实战系列入门篇的第二篇.介绍创建Spring Boot应用的几种方法. Spring Boot应用可以通过如下三种方法创建: 通过 https://start.spr ...
- mysql4 - 高级操作
一.联结(使用 where(早) 和 join(晚) 都可以完成联结) 1.1 从 Teacher 表和 Profession 表中,查询出老师的名字和所属专业的名称. SELECT t.`l_nam ...
- qt Multimedia 模块类如何使用?
qt 多媒体模块介绍 类名 英文描述 中文描述 QAudioBuffer Represents a collection of audio samples with a specific format ...
- 【BZOJ1565】 植物大战僵尸
Description Input Output 仅包含一个整数,表示可以获得的最大能源收入.注意,你也可以选择不进行任何攻击,这样能源收入为0. Sample Input 3 2 10 0 20 0 ...
- PyQt5多点触控写字板实现及困惑
Qt支持程序多点触控,就想使用PyQt5做一个触控画板,经过几番周折,查阅了TouchEvent官方文档,又参考了一篇QT for Android的例子,采用eventfilter过滤器来识别触屏事件 ...
- 【Thinkphp】入口文件和配置文件
一.入口文件 ThinkPHP采用单一入口模式进行项目部署和访问 入口文件代码 <?php define('APP_DEBUG',TRUE);//打开调试模式 在生产环境中应该关闭 define ...
- PL/SQL NOCOPY限制模式
NOCOPY模式用于限定OUT模式和IN OUT模式在调用时是不是以传引用的方式进行. 默认情况下,OUT模式和IN OUT模式的参数是以传值的方式进行调用的. IN模式总是以传引用的方式,如果用NO ...
- hi3531的h264压缩中修改波特率
typedef struct hiVENC_ATTR_H264_CBR_S { HI_U32 u32Gop; HI_U32 u32StatTime; HI_U32 u32ViFrmRate; HI_F ...