Android 开发之 CoordinatorLayout 使用详解一

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

官网描述为: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 收购注资,并拉拢多家制造商组成开放手机联盟开发改良,逐渐扩展到到平板电脑及其他领域上。

    336 引用 • 324 回帖

相关帖子

欢迎来到这里!

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

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

推荐标签 标签

  • JSON

    JSON (JavaScript Object Notation)是一种轻量级的数据交换格式。易于人类阅读和编写。同时也易于机器解析和生成。

    52 引用 • 190 回帖
  • 30Seconds

    📙 前端知识精选集,包含 HTML、CSS、JavaScript、React、Node、安全等方面,每天仅需 30 秒。

    • 精选常见面试题,帮助您准备下一次面试
    • 精选常见交互,帮助您拥有简洁酷炫的站点
    • 精选有用的 React 片段,帮助你获取最佳实践
    • 精选常见代码集,帮助您提高打码效率
    • 整理前端界的最新资讯,邀您一同探索新世界
    488 引用 • 384 回帖 • 10 关注
  • DevOps

    DevOps(Development 和 Operations 的组合词)是一组过程、方法与系统的统称,用于促进开发(应用程序/软件工程)、技术运营和质量保障(QA)部门之间的沟通、协作与整合。

    58 引用 • 25 回帖 • 3 关注
  • H2

    H2 是一个开源的嵌入式数据库引擎,采用 Java 语言编写,不受平台的限制,同时 H2 提供了一个十分方便的 web 控制台用于操作和管理数据库内容。H2 还提供兼容模式,可以兼容一些主流的数据库,因此采用 H2 作为开发期的数据库非常方便。

    11 引用 • 54 回帖 • 667 关注
  • B3log

    B3log 是一个开源组织,名字来源于“Bulletin Board Blog”缩写,目标是将独立博客与论坛结合,形成一种新的网络社区体验,详细请看 B3log 构思。目前 B3log 已经开源了多款产品:SymSoloVditor思源笔记

    1063 引用 • 3455 回帖 • 158 关注
  • Ant-Design

    Ant Design 是服务于企业级产品的设计体系,基于确定和自然的设计价值观上的模块化解决方案,让设计者和开发者专注于更好的用户体验。

    17 引用 • 23 回帖
  • Hprose

    Hprose 是一款先进的轻量级、跨语言、跨平台、无侵入式、高性能动态远程对象调用引擎库。它不仅简单易用,而且功能强大。你无需专门学习,只需看上几眼,就能用它轻松构建分布式应用系统。

    9 引用 • 17 回帖 • 636 关注
  • 工具

    子曰:“工欲善其事,必先利其器。”

    298 引用 • 763 回帖
  • JavaScript

    JavaScript 一种动态类型、弱类型、基于原型的直译式脚本语言,内置支持类型。它的解释器被称为 JavaScript 引擎,为浏览器的一部分,广泛用于客户端的脚本语言,最早是在 HTML 网页上使用,用来给 HTML 网页增加动态功能。

    730 引用 • 1280 回帖
  • 小说

    小说是以刻画人物形象为中心,通过完整的故事情节和环境描写来反映社会生活的文学体裁。

    32 引用 • 108 回帖
  • NetBeans

    NetBeans 是一个始于 1997 年的 Xelfi 计划,本身是捷克布拉格查理大学的数学及物理学院的学生计划。此计划延伸而成立了一家公司进而发展这个商用版本的 NetBeans IDE,直到 1999 年 Sun 买下此公司。Sun 于次年(2000 年)六月将 NetBeans IDE 开源,直到现在 NetBeans 的社群依然持续增长。

    78 引用 • 102 回帖 • 701 关注
  • Kafka

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

    36 引用 • 35 回帖 • 2 关注
  • Angular

    AngularAngularJS 的新版本。

    26 引用 • 66 回帖 • 550 关注
  • Markdown

    Markdown 是一种轻量级标记语言,用户可使用纯文本编辑器来排版文档,最终通过 Markdown 引擎将文档转换为所需格式(比如 HTML、PDF 等)。

    170 引用 • 1529 回帖
  • IDEA

    IDEA 全称 IntelliJ IDEA,是一款 Java 语言开发的集成环境,在业界被公认为最好的 Java 开发工具之一。IDEA 是 JetBrains 公司的产品,这家公司总部位于捷克共和国的首都布拉格,开发人员以严谨著称的东欧程序员为主。

    181 引用 • 400 回帖
  • 宕机

    宕机,多指一些网站、游戏、网络应用等服务器一种区别于正常运行的状态,也叫“Down 机”、“当机”或“死机”。宕机状态不仅仅是指服务器“挂掉了”、“死机了”状态,也包括服务器假死、停用、关闭等一些原因而导致出现的不能够正常运行的状态。

    13 引用 • 82 回帖 • 78 关注
  • Lute

    Lute 是一款结构化的 Markdown 引擎,支持 Go 和 JavaScript。

    29 引用 • 202 回帖 • 28 关注
  • jsoup

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

    6 引用 • 1 回帖 • 487 关注
  • Webswing

    Webswing 是一个能将任何 Swing 应用通过纯 HTML5 运行在浏览器中的 Web 服务器,详细介绍请看 将 Java Swing 应用变成 Web 应用

    1 引用 • 15 回帖 • 639 关注
  • Sym

    Sym 是一款用 Java 实现的现代化社区(论坛/BBS/社交网络/博客)系统平台。

    下一代的社区系统,为未来而构建

    524 引用 • 4601 回帖 • 706 关注
  • Wide

    Wide 是一款基于 Web 的 Go 语言 IDE。通过浏览器就可以进行 Go 开发,并有代码自动完成、查看表达式、编译反馈、Lint、实时结果输出等功能。

    欢迎访问我们运维的实例: https://wide.b3log.org

    30 引用 • 218 回帖 • 636 关注
  • SpaceVim

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

    3 引用 • 31 回帖 • 113 关注
  • 负能量

    上帝为你关上了一扇门,然后就去睡觉了....努力不一定能成功,但不努力一定很轻松 (° ー °〃)

    89 引用 • 1251 回帖 • 407 关注
  • TextBundle

    TextBundle 文件格式旨在应用程序之间交换 Markdown 或 Fountain 之类的纯文本文件时,提供更无缝的用户体验。

    1 引用 • 2 回帖 • 80 关注
  • Word
    13 引用 • 41 回帖
  • JVM

    JVM(Java Virtual Machine)Java 虚拟机是一个微型操作系统,有自己的硬件构架体系,还有相应的指令系统。能够识别 Java 独特的 .class 文件(字节码),能够将这些文件中的信息读取出来,使得 Java 程序只需要生成 Java 虚拟机上的字节码后就能在不同操作系统平台上进行运行。

    180 引用 • 120 回帖
  • Oracle

    Oracle(甲骨文)公司,全称甲骨文股份有限公司(甲骨文软件系统有限公司),是全球最大的企业级软件公司,总部位于美国加利福尼亚州的红木滩。1989 年正式进入中国市场。2013 年,甲骨文已超越 IBM,成为继 Microsoft 后全球第二大软件公司。

    107 引用 • 127 回帖 • 336 关注