通过CoordinatorLayout的Behavior拦截一切

发表于 4年以前  | 总阅读数:2382 次

通过CoordinatorLayout的Behavior拦截一切

如果你不研究 CoordinatorLayout,那你在探索 Android Design Support Library 的路上肯定不会走太远 - 因为 Android Design Support Library 中大多数 View 都需要 CoordinatorLayout。但是为什么呢?CoordinatorLayout 自身不需要完成太多的工作:将它与 Android 标准 UI 框架结合使用,它的作用和 FrameLayout 区别不大,那它为什么能提供那么多酷炫的效果呢?答案是:CoordinatorLayout.Behavior。通过将 CoordinatorLayout.Behavior 绑定到 CoordinatorLayout 中的子元素上,你就可以拦截点击事件,窗口插入,测量,布局,还有嵌套滚动。可以说 Android Design Support Library 大多数酷炫的效果都是通过 Behavior 完成的。

创建 Behavior

自定义 Behavior 很简单,创建 Behavior 子类就可以了:

public class FancyBehavior<V extends View>
    extends CoordinatorLayout.Behavior<V> {
  /**
   * Default constructor for instantiating a FancyBehavior in code.
   */
  public FancyBehavior() {
  }
  /**
   * Default constructor for inflating a FancyBehavior from layout.
   *
   * @param context The {@link Context}.
   * @param attrs The {@link AttributeSet}.
   */
  public FancyBehavior(Context context, AttributeSet attrs) {
    super(context, attrs);
    // Extract any custom attributes out
    // preferably prefixed with behavior_ to denote they
    // belong to a behavior
  }
}

注意类需要绑定的泛型类型,不妨创建可以绑定到任意 View 上的 FancyBehavior;如果你想 Behavior 只能使用到某一种特定的 View 上,可以这样:

public class FancyFrameLayoutBehavior
    extends CoordinatorLayout.Behavior<FancyFrameLayout>

通过 Behavior.setTag()/Behavior.getTag() 可以保存临时数据,onSaveInstanceState()/onRestoreInstanceState() 可以保存 Behavior 相关的实例状态。虽说我建议你创建自己的 Behavior,但这些方法让你能够创建状态性 Behavior。

关联 Behavior

当然,Behavior 不能单干 - 它需要绑定到 CoordinatorLayout 的子元素才能被使用,下面是关联的三种办法。

通过代码绑定

你可能觉得 Behavior 是 CoordinatorLayout 给每个 View 添加的某种额外属性/其他的东西,但如果我告诉你 Behavior 是每个 View 的 LayoutParams 存储的内容,请不要太惊讶 - 这也是 Behavior 必须关联到 CoordinatorLayout 子元素上的原因,因为只有它们才有 LayoutParams 中对应的属性。

FancyBehavior fancyBehavior = new FancyBehavior();
CoordinatorLayout.LayoutParams params =
    (CoordinatorLayout.LayoutParams) yourView.getLayoutParams();
params.setBehavior(fancyBehavior);

在这种情况下,你会使用默认构造方法,当然,你也可以给构造方法设置任何你想要的参数 - 反正代码在手,天下我有嘛。

通过 xml 绑定

不过不得不说,每次都用代码去完成绑定的工作有一丢丢麻烦,就像大多数自定义 LayoutParams,我们也有相应的 layout_ 属性去设置 Behavior:

<FrameLayout
  android:layout_height=”wrap_content”
  android:layout_width=”match_parent”
  app:layout_behavior=”.FancyBehavior” />

此时会调用 FancyBehavior(Context context, AttributeSet attrs) 构造方法,也就意味着可以声明任意自定义属性,然后在代码中获得这些属性,如果你想通过 xml 自定义 Behavior 的功能,这个办法就变得很棒了。

Note: 与使用 layout 类似,你也可以使用 behavior 指定 Behavior 使用的属性。

自动绑定

如果你创建了需要自定义 Behavior 的自定义 View,那你可能想让该 View 绑定默认的 Behavior,而不用在每次使用时在代码或 xml 中声明。为了实现这个特性,代码需要改成下面这样:

@CoordinatorLayout.DefaultBehavior(FancyFrameLayoutBehavior.class)
public class FancyFrameLayout extends FrameLayout {
}

这样就会自动绑定 Behavior 了,相当于 layout_behavior 默认设置为 DefaultBehavior。

拦截点击事件

一旦自定义 Behavior 开发完成,就可以用它完成一些任务了,例如拦截点击事件。

不使用 CoordinatorLayout,总是需要创建 ViewGroup 的子类才能得到点击事件。但有了 CoordinatorLayout,就能在 Behavior 里调用 onInterceptTouchEvent() 以控制 CoordinatorLayout 的 onInterceptTouchEvent(),使 Behavior 能拦截点击事件。通过返回 true,Behavior 就能获取所有点击事件,这就是 SwipeDismissBehavior 的原理。

在 blocksInteractionBelow() 方法中返回 true,就会阻塞所有事件传递的交互,拦截所有事件。当然了,你可能希望得到一些视觉上的信号让你知道交互产生的事件都被阻塞了(最起码要让用户知道 App 崩溃了) - 这也是 blocksInteractionBelow() 的默认功能依赖于 getScrimOpacity() 的值的原因 - getScrimOpacity() 方法返回一个非 0 的值,以在 View 上绘制覆盖颜色(getScrimColor() 方法返回对应的颜色,默认是黑色),而且一下子禁止了所有点击事件。很方便。

拦截窗口插入

我先假设你读过“Why would I want to fitsSystemWindows”,在博文中我们讨论了 fitsSystemWindows 到底干了啥,但说穿了他就是让你得到避免在系统窗口下绘制的窗口插入(例如状态栏和导航栏)。如果 fitsSystemWindows=“true”,那么任何绑定的 Behavior 都会调用 onApplyWindowInsets(),让 Behavior 具有比 View 更高的优先级。

Note: 大多数情况下,Behavior 不会消耗所有的窗口插入,窗口插入应该通过 ViewCompat.dispatchApplyWindowInsets() 传递,以确保所有子 View 都有机会接触窗口插入。

拦截 Measurement 和 Layout

Measurement 和 Layout 是 Android 绘制机制的关键部分,这也意味着 Behavior 作为一切的拦截者,能够通过 onMeasureChild() 和 onLayoutChild() 回调先于 CoordinatorLayout 进行 measurement 和 layout 。

例如,让 Behavior 接受任意类型的 ViewGroup,并添加 maxWidth:

/*
 * Copyright 2015 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

 package com.example.behaviors;

import android.content.Context;
import android.content.res.TypedArray;
import android.support.design.widget.CoordinatorLayout;
import android.util.AttributeSet;
import android.view.ViewGroup;

import static android.view.View.MeasureSpec;

/**
 * Behavior that imposes a maximum width on any ViewGroup.
 *
 * <p />Requires an attrs.xml of something like
 *
 * <pre>
 * &lt;declare-styleable name="MaxWidthBehavior_Params"&gt;
 *     &lt;attr name="behavior_maxWidth" format="dimension"/&gt;
 * &lt;/declare-styleable&gt;
 * </pre>
 */
public class MaxWidthBehavior<V extends ViewGroup> extends CoordinatorLayout.Behavior<V> {
    private int mMaxWidth;

    public MaxWidthBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.MaxWidthBehavior_Params);
        mMaxWidth = a.getDimensionPixelSize(
                R.styleable.MaxWidthBehavior_Params_behavior_maxWidth, 0);
        a.recycle();
    }

    @Override
    public boolean onMeasureChild(CoordinatorLayout parent, V child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        if (mMaxWidth <= 0) {
            // No max width means this Behavior is a no-op
            return false;
        }
        int widthMode = MeasureSpec.getMode(parentWidthMeasureSpec);
        int width = MeasureSpec.getSize(parentWidthMeasureSpec);

        if (widthMode == MeasureSpec.UNSPECIFIED || width > mMaxWidth) {
            // Sorry to impose here, but max width is kind of a big deal
            width = mMaxWidth;
            widthMode = MeasureSpec.AT_MOST;
            parent.onMeasureChild(child,
                    MeasureSpec.makeMeasureSpec(width, widthMode), widthUsed,
                    parentHeightMeasureSpec, heightUsed);
            // We've measured the View, so CoordinatorLayout doesn't have to
            return true;
        }

        // Looks like the default measurement will work great
        return false;
    }
}

泛型 Behavior 固然好用,但你要记住,不是所有 Behavior 都应该是泛型的。

理解 View 间依赖

上述所有功能只需要一个 View,但 Behavior 的魔力真正来源于在 View 间建立以来 - 例如,当另一个 View 发生改变,Behavior 获得回调,基于外部条件改变其功能。

Behavior 可以通过两种方式依赖于 View:当一个 View 被锚定到其他 View 上(被实现的依赖)或在 layoutDependsOn() 方法中返回 true。

使用 CoordinatorLayout 的 layout_anchor 属性就可以完成锚定。此外,再使用 layout_anchorGravity 属性,你可以有效地将两个 View 的位置绑定在一起。例如,将 FAB 锚定到 AppBarLayout 中,此时 FloatingActionButton.Behavior 会通过隐式依赖,在 AppBarLayout 滚动到屏幕可见范围之外时隐藏自身。

不管怎样,Behavior 都会在依赖的 View 被移除时获得 onDependentViewRemoved() 回调,而且只要依赖 View 发生了改变,onDependentViewChanged() 回调也会被触发。(例如改变自身大小,或者改变位置)

将 View 绑定在一起的能力就是 Design Library 实现这么多酷炫效果的秘密 - 举例来说吧,FAB 和 Snackbar 的交互,FAB 的 Behavior 依赖于添加到 CoordinatorLayout 中的 Snackbar,通过调用 onDependentViewChanged() 回调就可以将 FAB 移动到 Snackbar 的上面,避免覆盖。

Note: 当你添加依赖,View 总会在被依赖 View 被放置后被放置,无论其布局关系如何。

嵌套滚动

对于嵌套滚动,下面几件事你需要记住:

  1. 不需要在 NestedScrollView 中声明依赖,因为 CoordinatorLayout 的每一个子元素都能够获得 NestedScrollView 中的滚动事件
  2. NestedScrollView 不仅仅能应用于 CoordinatorLayout 的直接子元素,而能应用到 CoordinatorLayout 中的任意 View 上(CoordinatorLayout 的子布局的子布局的子布局的子布局……)
  3. 虽说我把它叫做嵌套滚动,但实际上它能滚动和挥动

在 onStartNestedScroll() 方法中处理你感兴趣的嵌套滚动事件吧,你能在这得到滚动的坐标轴(例如水平和垂直坐标轴 - 不用在意到底在哪个方向发生了滚动),为了获得该方向上随后的滚动事件,必须返回 true。

onStartNestedScroll() 返回 true 后,嵌套滚动会按如下步骤运行:

  • onNestedPreScroll() 在 ScrollView 获得滚动事件前被调用,允许 Behavior 消耗一部分或所有滚动事件(最后被消耗的滚动事件是 int[] out 参数,也就是你能得到你消耗了什么事件的参数)
  • onNestedScroll() 在滚动 View 滚动时被调用 - 你可以得到 View 已经滚动了多远以及还有多少没有滚动。

此外,还有与挥动操作等价的情况(即使挥动前的回调必须消耗所有非挥动的滚动事件,或不消耗任何一个)。

当嵌套滚动(挥动)结束,获得 onStopNestedScroll() 的回调调用,该回调标记了滚动的结束 - 同时期望下一次滚动发生前得到新的 onStartNestedScroll() 调用。

例如,你想在向下滚动时隐藏 FAB,向上滚动时显示 FAB - 实现这个功能将涉及 onStartNestedScroll() 和 onNestedScroll(),就像 ScrollAwareFABBehavior 中实现的那样。

而这只是开始

Behavior 的每一个部分都很有趣,把它们组合起来就能对界面施行魔法,让界面变得酷炫。我强烈建议大家去看看 Design Library 的源码以发现更多更高级的 Behavior - Android SDK 搜索的 Chrome 拓展一直是我最爱的 Android 开源项目源码。

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:7月以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:7月以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:7月以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:7月以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:7月以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:7月以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:7月以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:7月以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:7月以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:7月以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:7月以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:7月以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:7月以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:7月以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:7月以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:7月以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:7月以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:7月以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:7月以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:7月以前  |  398次阅读  |  详细内容 »
 相关文章
简化Android的UI开发 4年以前  |  520703次阅读
Android 深色模式适配原理分析 3年以前  |  28633次阅读
Android阴影实现的几种方案 1年以前  |  10789次阅读
Android 样式系统 | 主题背景覆盖 3年以前  |  9589次阅读
 目录