Android 开发之 CoordinatorLayout 使用详解一

本贴最后更新于 2429 天前,其中的信息可能已经沧海桑田

官网描述为:CoordinatorLayout 是一个增强版的 FrameLayout(继承自 ViewGroup)

用途:

1、作为应用的顶层视图。

2、作为一个可以指定子 View 之间相互作用的容器,通过给 CoordinatorLayout 的子 View 指定 CoordinatorLayout.Behavior 来定义子 view 之间的相互作用。(你可以想象成:CoordinatorLayout 相当于在两个 View 之间充当中介,这样子的好处就是两个 view 之间的耦合度降低了,只需要跟 coordinatorLayout 打交到即可,而 CoordinatorLayout.Behavior 相当于两个 view 之间的协议,即通过怎样的规则来约束双方的行为。)

设计概念:

  1. CoordinatorLayout:CoordinatorLayout 作为最顶层视图,将负责管理所有的子 view,使其内部的子 View 彼此间产生一种联系。这个联系通过 Behavior 来实现(包括了滑动状态的处理以及 View 状态的处理)。
  2. AppBarLayout:AppBarLayout 继承自限性布局,作为增强版的线性布局,他增加了对滑动手势的处理。
  3. Behavior:Behavior 是 google 新提出的,能够让你以非侵入式的方式去处理目标 View 和其他 View 的交互行为。Behavior 需要设置在触发事件(比如滚动)的 view 上,且这个 View_必须是 CoordinatorLayout 的第一层级下的子 view_,否则没有效果,因为 Behavior 的初始化是在 CoordinatorLayout 的 LayoutParams 中通过反射完成的。
    Behavior 实例化方式:1、通过 app:layout_behavior 声明 ;2、在你的自定义 View 类上添加 @DefaultBehavior(MyBehavior.class);
  4. Behavior 只是个接口,其调用是由_NestedScrollingParent 与 NestedScrollingChild 接口负责调用。_

接下来我们通过阅读部分源码进行学习:

首先,我们从两个 view 是如何通过 coordinatorlayout 产生关联来入手;看代码

LayoutParams(Context context, AttributeSet attrs) { super(context, attrs); final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CoordinatorLayout_Layout); this.gravity = a.getInteger( R.styleable.CoordinatorLayout_Layout_android_layout_gravity, Gravity.NO_GRAVITY); mAnchorId = a.getResourceId(R.styleable.CoordinatorLayout_Layout_layout_anchor, View.NO_ID); this.anchorGravity = a.getInteger( R.styleable.CoordinatorLayout_Layout_layout_anchorGravity, Gravity.NO_GRAVITY); this.keyline = a.getInteger(R.styleable.CoordinatorLayout_Layout_layout_keyline, -1); insetEdge = a.getInt(R.styleable.CoordinatorLayout_Layout_layout_insetEdge, 0); dodgeInsetEdges = a.getInt( R.styleable.CoordinatorLayout_Layout_layout_dodgeInsetEdges, 0); mBehaviorResolved = a.hasValue( R.styleable.CoordinatorLayout_Layout_layout_behavior); if (mBehaviorResolved) { mBehavior = parseBehavior(context, attrs, a.getString( R.styleable.CoordinatorLayout_Layout_layout_behavior)); } a.recycle(); if (mBehavior != null) { // If we have a Behavior, dispatch that it has been attached mBehavior.onAttachedToLayoutParams(this); } }

mBehaviorResolved = a.hasValue( R.styleable.CoordinatorLayout_Layout_layout_behavior); if (mBehaviorResolved) { mBehavior = parseBehavior(context, attrs, a.getString( R.styleable.CoordinatorLayout_Layout_layout_behavior)); }

这几句我们可以看到。
mBehaviorResolved 是个 boolean 变量,如果

R.styleable.CoordinatorLayout_Layout_layout_behavior CoordinatorLayout 的

layout_behavior 这个字段设置有值,
1、mBehaviorResolved = true -》调用 parseBehavior 方法,将所需参数传入通过 java 的反射技术返回一个 Behavior 实例。

static Behavior parseBehavior(Context context, AttributeSet attrs, String name) { if (TextUtils.isEmpty(name)) { return null; } final String fullName; if (name.startsWith(".")) { // Relative to the app package. Prepend the app package name. fullName = context.getPackageName() + name; } else if (name.indexOf('.') >= 0) { // Fully qualified package name. fullName = name; } else { // Assume stock behavior in this package (if we have one) fullName = !TextUtils.isEmpty(WIDGET_PACKAGE_NAME) ? (WIDGET_PACKAGE_NAME + '.' + name) : name; } try { Map<String, Constructor> constructors = sConstructors.get(); if (constructors == null) { constructors = new HashMap<>(); sConstructors.set(constructors); } Constructor c = constructors.get(fullName); if (c == null) { final Class clazz = (Class) Class.forName(fullName, true, context.getClassLoader()); c = clazz.getConstructor(CONSTRUCTOR_PARAMS); c.setAccessible(true); constructors.put(fullName, c); } return c.newInstance(context, attrs); } catch (Exception e) { throw new RuntimeException("Could not inflate Behavior subclass " + fullName, e); } }

通过这一段我们可以知道,最后是通过调用 Behavior 的参数为(context,attrs)的构造函数进行实例化。
实例化出 Behavior 之后我们会调用 behavior 的 onAttachedToLayoutParams 方法 将 LayoutParams 的实例对象传进去 mBehavior.onAttachedToLayoutParams(this);

mBehavior.onAttachedToLayoutParams 是一个当 LayoutParams 被实例化后的回调方法。

通过这里,我们的

CoordinatorLayout 就能够跟用 layout_behavior 标识的子 View 产生联系。

当子 View 发生变化时,CoordinatorLayout 又是如何处理的的,请看下面代码:

final void onChildViewsChanged(@DispatchChangeEvent final int type) { final int layoutDirection = ViewCompat.getLayoutDirection(this); final int childCount = mDependencySortedChildren.size(); final Rect inset = acquireTempRect(); final Rect drawRect = acquireTempRect(); final Rect lastDrawRect = acquireTempRect(); for (int i = 0; i < childCount; i++) { final View child = mDependencySortedChildren.get(i); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (type == EVENT_PRE_DRAW && child.getVisibility() == View.GONE) { // Do not try to update GONE child views in pre draw updates. continue; } // Check child views before for anchor for (int j = 0; j < i; j++) { final View checkChild = mDependencySortedChildren.get(j); if (lp.mAnchorDirectChild == checkChild) { offsetChildToAnchor(child, layoutDirection); } } // Get the current draw rect of the view getChildRect(child, true, drawRect); // Accumulate inset sizes if (lp.insetEdge != Gravity.NO_GRAVITY && !drawRect.isEmpty()) { final int absInsetEdge = GravityCompat.getAbsoluteGravity( lp.insetEdge, layoutDirection); switch (absInsetEdge & Gravity.VERTICAL_GRAVITY_MASK) { case Gravity.TOP: inset.top = Math.max(inset.top, drawRect.bottom); break; case Gravity.BOTTOM: inset.bottom = Math.max(inset.bottom, getHeight() - drawRect.top); break; } switch (absInsetEdge & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.LEFT: inset.left = Math.max(inset.left, drawRect.right); break; case Gravity.RIGHT: inset.right = Math.max(inset.right, getWidth() - drawRect.left); break; } } // Dodge inset edges if necessary if (lp.dodgeInsetEdges != Gravity.NO_GRAVITY && child.getVisibility() == View.VISIBLE) { offsetChildByInset(child, inset, layoutDirection); } if (type == EVENT_PRE_DRAW) { // Did it change? if not continue getLastChildRect(child, lastDrawRect); if (lastDrawRect.equals(drawRect)) { continue; } recordLastChildRect(child, drawRect); } // Update any behavior-dependent views for the change for (int j = i + 1; j < childCount; j++) { final View checkChild = mDependencySortedChildren.get(j); final LayoutParams checkLp = (LayoutParams) checkChild.getLayoutParams(); final Behavior b = checkLp.getBehavior(); if (b != null && b.layoutDependsOn(this, checkChild, child)) { if (type == EVENT_PRE_DRAW && checkLp.getChangedAfterNestedScroll()) { // If this is from a pre-draw and we have already been changed // from a nested scroll, skip the dispatch and reset the flag checkLp.resetChangedAfterNestedScroll(); continue; } final boolean handled; switch (type) { case EVENT_VIEW_REMOVED: // EVENT_VIEW_REMOVED means that we need to dispatch // onDependentViewRemoved() instead b.onDependentViewRemoved(this, checkChild, child); handled = true; break; default: // Otherwise we dispatch onDependentViewChanged() handled = b.onDependentViewChanged(this, checkChild, child); break; } if (type == EVENT_NESTED_SCROLL) { // If this is from a nested scroll, set the flag so that we may skip // any resulting onPreDraw dispatch (if needed) checkLp.setChangedAfterNestedScroll(handled); } } } } releaseTempRect(inset); releaseTempRect(drawRect); releaseTempRect(lastDrawRect); }

我们可以看到文档说明,大概意思是当子 view 发生变化会调用该方法。该方法会遍历所有的子 view,
然后调用如下代码,layoutDependsOn()这个方法是做什么的呢?我们接下来看下该方法。

if (b != null && b.layoutDependsOn(this, checkChild, child)) { if (type == EVENT_PRE_DRAW && checkLp.getChangedAfterNestedScroll()) { // If this is from a pre-draw and we have already been changed // from a nested scroll, skip the dispatch and reset the flag checkLp.resetChangedAfterNestedScroll(); continue; } final boolean handled; switch (type) { case EVENT_VIEW_REMOVED: // EVENT_VIEW_REMOVED means that we need to dispatch // onDependentViewRemoved() instead b.onDependentViewRemoved(this, checkChild, child); handled = true; break; default: // Otherwise we dispatch onDependentViewChanged() handled = b.onDependentViewChanged(this, checkChild, child); break; } if (type == EVENT_NESTED_SCROLL) { // If this is from a nested scroll, set the flag so that we may skip // any resulting onPreDraw dispatch (if needed) checkLp.setChangedAfterNestedScroll(handled); } }
/** * Determine whether the supplied child view has another specific sibling view as a * layout dependency. * *

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:

*
  1. Always lay out this child after the dependent child is laid out, regardless * of child order.

  2. Call {@link #onDependentViewChanged} when the dependency view's layout or * position changes.

* * @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; }

这个方法,大概意思是如果我们返回 true,说明当前发生变化的子 view 发生变化时。也就是该方法决定我们用

layout_behavior 标识的 view 是否应该做出相应的变化。默认返回 false,该方法需要我们在创建自己的 Behavior 时重写。
当返回 true 的话,可以看到会调用

b.onDependentViewRemoved(this, checkChild, child);

handled = b.onDependentViewChanged(this, checkChild, child);

为了更好理解这两句代码,我们举个例子,假设有 ViewA 和 ViewB ,当 ViewB 发生移动时,ViewA 要向反方向移动。
1、当 ViewB 被移除时会调用

b.onDependentViewRemoved(this, checkChild, child);
2、当 ViewB 发生变化时,会调用

handled = b.onDependentViewChanged(this, checkChild, child);
那么我们就可以在

b.onDependentViewChanged 里面写我们的功能代码了。

通过以上的分析,希望能帮到大家对 CoordinatorLayout 的协作调用过程有一些些的帮助。

  • Android

    Android 是一种以 Linux 为基础的开放源码操作系统,主要使用于便携设备。2005 年由 Google 收购注资,并拉拢多家制造商组成开放手机联盟开发改良,逐渐扩展到到平板电脑及其他领域上。

    337 引用 • 324 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • 以太坊

    以太坊(Ethereum)并不是一个机构,而是一款能够在区块链上实现智能合约、开源的底层系统。以太坊是一个平台和一种编程语言 Solidity,使开发人员能够建立和发布下一代去中心化应用。 以太坊可以用来编程、分散、担保和交易任何事物:投票、域名、金融交易所、众筹、公司管理、合同和知识产权等等。

    34 引用 • 367 回帖 • 1 关注
  • 反馈

    Communication channel for makers and users.

    120 引用 • 906 回帖 • 280 关注
  • Openfire

    Openfire 是开源的、基于可拓展通讯和表示协议 (XMPP)、采用 Java 编程语言开发的实时协作服务器。Openfire 的效率很高,单台服务器可支持上万并发用户。

    6 引用 • 7 回帖 • 119 关注
  • Latke

    Latke 是一款以 JSON 为主的 Java Web 框架。

    71 引用 • 535 回帖 • 831 关注
  • TGIF

    Thank God It's Friday! 感谢老天,总算到星期五啦!

    292 引用 • 4495 回帖 • 664 关注
  • 心情

    心是产生任何想法的源泉,心本体会陷入到对自己本体不能理解的状态中,因为心能产生任何想法,不能分出对错,不能分出自己。

    59 引用 • 369 回帖 • 1 关注
  • OpenShift

    红帽提供的 PaaS 云,支持多种编程语言,为开发人员提供了更为灵活的框架、存储选择。

    14 引用 • 20 回帖 • 665 关注
  • 星云链

    星云链是一个开源公链,业内简单的将其称为区块链上的谷歌。其实它不仅仅是区块链搜索引擎,一个公链的所有功能,它基本都有,比如你可以用它来开发部署你的去中心化的 APP,你可以在上面编写智能合约,发送交易等等。3 分钟快速接入星云链 (NAS) 测试网

    3 引用 • 16 回帖 • 1 关注
  • QQ

    1999 年 2 月腾讯正式推出“腾讯 QQ”,在线用户由 1999 年的 2 人(马化腾和张志东)到现在已经发展到上亿用户了,在线人数超过一亿,是目前使用最广泛的聊天软件之一。

    45 引用 • 557 回帖
  • CSDN

    CSDN (Chinese Software Developer Network) 创立于 1999 年,是中国的 IT 社区和服务平台,为中国的软件开发者和 IT 从业者提供知识传播、职业发展、软件开发等全生命周期服务,满足他们在职业发展中学习及共享知识和信息、建立职业发展社交圈、通过软件开发实现技术商业化等刚性需求。

    14 引用 • 155 回帖 • 1 关注
  • 创业

    你比 99% 的人都优秀么?

    81 引用 • 1395 回帖 • 1 关注
  • SOHO

    为成为自由职业者在家办公而努力吧!

    7 引用 • 55 回帖
  • 强迫症

    强迫症(OCD)属于焦虑障碍的一种类型,是一组以强迫思维和强迫行为为主要临床表现的神经精神疾病,其特点为有意识的强迫和反强迫并存,一些毫无意义、甚至违背自己意愿的想法或冲动反反复复侵入患者的日常生活。

    15 引用 • 161 回帖 • 2 关注
  • Kubernetes

    Kubernetes 是 Google 开源的一个容器编排引擎,它支持自动化部署、大规模可伸缩、应用容器化管理。

    118 引用 • 54 回帖 • 11 关注
  • Gzip

    gzip (GNU zip)是 GNU 自由软件的文件压缩程序。我们在 Linux 中经常会用到后缀为 .gz 的文件,它们就是 Gzip 格式的。现今已经成为互联网上使用非常普遍的一种数据压缩格式,或者说一种文件格式。

    9 引用 • 12 回帖 • 184 关注
  • SpaceVim

    SpaceVim 是一个社区驱动的模块化 vim/neovim 配置集合,以模块的方式组织管理插件以
    及相关配置,为不同的语言开发量身定制了相关的开发模块,该模块提供代码自动补全,
    语法检查、格式化、调试、REPL 等特性。用户仅需载入相关语言的模块即可得到一个开箱
    即用的 Vim-IDE。

    3 引用 • 31 回帖 • 112 关注
  • 正则表达式

    正则表达式(Regular Expression)使用单个字符串来描述、匹配一系列遵循某个句法规则的字符串。

    31 引用 • 94 回帖 • 1 关注
  • Sublime

    Sublime Text 是一款可以用来写代码、写文章的文本编辑器。支持代码高亮、自动完成,还支持通过插件进行扩展。

    10 引用 • 5 回帖 • 4 关注
  • jsoup

    jsoup 是一款 Java 的 HTML 解析器,可直接解析某个 URL 地址、HTML 文本内容。它提供了一套非常省力的 API,可通过 DOM,CSS 以及类似于 jQuery 的操作方法来取出和操作数据。

    6 引用 • 1 回帖 • 500 关注
  • Kafka

    Kafka 是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者规模的网站中的所有动作流数据。 这种动作(网页浏览,搜索和其他用户的行动)是现代系统中许多功能的基础。 这些数据通常是由于吞吐量的要求而通过处理日志和日志聚合来解决。

    36 引用 • 35 回帖
  • 深度学习

    深度学习(Deep Learning)是机器学习的分支,是一种试图使用包含复杂结构或由多重非线性变换构成的多个处理层对数据进行高层抽象的算法。

    43 引用 • 44 回帖
  • BAE

    百度应用引擎(Baidu App Engine)提供了 PHP、Java、Python 的执行环境,以及云存储、消息服务、云数据库等全面的云服务。它可以让开发者实现自动地部署和管理应用,并且提供动态扩容和负载均衡的运行环境,让开发者不用考虑高成本的运维工作,只需专注于业务逻辑,大大降低了开发者学习和迁移的成本。

    19 引用 • 75 回帖 • 683 关注
  • C++

    C++ 是在 C 语言的基础上开发的一种通用编程语言,应用广泛。C++ 支持多种编程范式,面向对象编程、泛型编程和过程化编程。

    108 引用 • 153 回帖
  • Shell

    Shell 脚本与 Windows/Dos 下的批处理相似,也就是用各类命令预先放入到一个文件中,方便一次性执行的一个程序文件,主要是方便管理员进行设置或者管理用的。但是它比 Windows 下的批处理更强大,比用其他编程程序编辑的程序效率更高,因为它使用了 Linux/Unix 下的命令。

    125 引用 • 74 回帖
  • Git

    Git 是 Linux Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。

    211 引用 • 358 回帖
  • Android

    Android 是一种以 Linux 为基础的开放源码操作系统,主要使用于便携设备。2005 年由 Google 收购注资,并拉拢多家制造商组成开放手机联盟开发改良,逐渐扩展到到平板电脑及其他领域上。

    337 引用 • 324 回帖
  • OpenCV
    15 引用 • 36 回帖