针对 CoordinatorLayout 及 Behavior 的一次细节较真

本文详细探讨了Android的CoordinatorLayout及其Behavior的使用,包括Behavior如何设置、响应滑动事件,以及自定义Behavior的总结。通过实例展示了Behavior如何处理View间的依赖关系和滑动事件,揭示了嵌套滑动的原理。最后,总结了CoordinatorLayout的测量、布局流程,强调了Behavior在交互设计中的关键作用。
摘要由CSDN通过智能技术生成

我认真不是为了输赢,我就是认真。– 罗永浩

我一直对 Material Design 很感兴趣,每次在官网上阅读它的相关文档时,我总会有更进一步的体会。当然,Material Design 并不是仅仅针对 Android 而言的,它其实是一套普遍性的设计规范。而对于 Android 开发人员而言,我们涉及的往往是它的实现。也就是一个个个性鲜明的类。比如 RecyclerView 、CardView、Palette 等等。并且为了让开发者更轻松地开发出符合 Material Design 设计规范的界面,Google 开发人员直接提供了一个兼容包,它就是 Android Support Design Library。

引用这个包需要在 build.gradle 中添加依赖。

compile 'com.android.support:design:25.0.1'

在这个包中,最核心的一个类就是 CoordinatorLayout。因为其它的类都需要与它进行相关联才能互动。而今天的主题就是讨论这个类的一些细节。
这里写图片描述

上图中这种高大上的视觉和交互效果,第一次看的时候我心头就痒痒的,恨不得立马就去实现它。然后,就百度查找相关的博文,但是风格我都不是很喜欢。我不喜欢文章中放一个 xml 布局文件,然后配置一些属性,然后就没有了。

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context="com.frank.supportdesigndemo.ScrollingActivity">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/app_bar"
        android:layout_width="match_parent"
        android:layout_height="@dimen/app_bar_height"
        android:layout_marginTop="-28dp"
        android:fitsSystemWindows="true"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/toolbar_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"
            app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">
            <ImageView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:scaleType="centerCrop"
                android:src="@drawable/test"
                app:layout_collapseMode="parallax"
                app:layout_collapseParallaxMultiplier="0.7"
                />
            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin"
                app:popupTheme="@style/AppTheme.PopupOverlay" />

        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>

    <include layout="@layout/content_scrolling" />

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="@dimen/fab_margin"
        app:layout_anchor="@id/app_bar"
        app:layout_anchorGravity="bottom|end"
        app:srcCompat="@android:drawable/ic_dialog_email" />

</android.support.design.widget.CoordinatorLayout>

我照着完成了,效果也达到了,但是感觉有些虚。我不得劲,或者说我内心纠结吧。内心千头万绪,上面的布局文件中,除了 Toolbar 我认识外,其它的控件,我一个都不熟悉。

我不喜欢这种感觉。因为我有许许多多的疑惑。

CoordinatorLayout 是什么?
CoordinatorLayout 有什么作用?
AppBarLayout 是什么?
AppBarLayout 有什么作用?
……

接下来的文章篇幅会比较长,大家仔细阅读就好。如果时间不够,可以直接拖动到文章最后总结的那一节。不明白的地方再到文章中间部分阅读相关内容就可以了。但我希望读者还是顺序方式阅读,因为我相信如果你有许多疑惑,我的学习过程也许可以给你一些提示或者启迪。

更多的真相

在编程领域,学习一个陌生的事物,最好的途径可能就是阅读它的官方文档或者是源代码。带着心中的困惑,我前往 Android 官网,直接挑最显眼的 CoordinatorLayout 来进行研究。所以这篇文章我主讲 CoordinatorLayout。
这里写图片描述

官网解释 CoordinatorLayout 是一个超级 FrameLayout,然后可以作为一个容器指定与 child 的一些交互规则。

public class CoordinatorLayout extends ViewGroup implements NestedScrollingParent {
   }

这是 CoordinatorLayout 的声明。它本质上就是一个 ViewGroup,注意的是它并没有继承自 FrameLayout,然后实现了 NestedScrollingParent 接口,我们先不管这个 NestedScrollingParent,NestedScrolliingParent 在文章后面适当的地方我会给出解释。

官网又说通过给CoordinaotrLayout 中的 child 指定 Behavior,就可以和 child 进行交互,或者是 child 之间互相进行相关的交互。并且自定义 View 时,可以通过 DefaultBehavior 这个注解来指定它关联的 Behavior。这里出现了新的名词:Behavior。于是,中断对 CoordinatorLayout 的跟踪,转到 Behavior 细节上来。
这里写图片描述
Behavior 其实是 CoordinatorLayout 中的一个静态内部类,并且是个泛型,接受任何 View 类型。官方文档真是惜字如金,更多的细节需要去阅读代码,也就是要靠猜测。这点很不爽的。好吧,官方文档说 Behavior 是针对 CoordinatorLayout 中 child 的交互插件。记住这个词:插件。插件也就代表如果一个 child 需要某种交互,它就需要加载对应的 Behavior,否则它就是不具备这种交互能力的。而 Behavior 本身是一个抽象类,它的实现类都是为了能够让用户作用在一个 View 上进行拖拽、滑动、快速滑动等手势。如果自己要定制某个交互动作,就需要自己实现一个 Behavior。

但是,对于我们而言,我们要实现一个 Behavior,我们用来干嘛呢?

是的,问问自己吧,我们如果自定义一个 Behavior,我们想干嘛?

前面内容有讲过,CoordinatorLayout 可以定义与它 child 的交互或者是某些 child 之间的交互。

我们先看看 Behavior 的代码细节,代码有精简。

public static abstract class Behavior<V extends View> {
   

    public Behavior() { }

    public Behavior(Context context, AttributeSet attrs) {}


    public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) { return false; }

    public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) {  return false; }

    public void onDependentViewRemoved(CoordinatorLayout parent, V child, View dependency) {}


    public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout,
            V child, View directTargetChild, View target, int nestedScrollAxes) {
        return false;
    }

    public void onNestedScrollAccepted(CoordinatorLayout coordinatorLayout, V child,
            View directTargetChild, View target, int nestedScrollAxes) {
        // Do nothing
    }

    public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target) {
        // Do nothing
    }

    public void onNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target,
            int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
        // Do nothing
    }

    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target,
            int dx, int dy, int[] consumed) {
        // Do nothing
    }

    public boolean onNestedFling(CoordinatorLayout coordinatorLayout, V child, View target,
            float velocityX, float velocityY, boolean consumed) {
        return false;
    }

    public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, V child, View target,
            float velocityX, float velocityY) {
        return false;
    }
}

一般我们自定义一个 Behavior,目的有两个,一个是根据某些依赖的 View 的位置进行相应的操作。另外一个就是响应 CoordinatorLayout 中某些组件的滑动事件。
我们先看第一种情况。

两个 View 之间的依赖关系

如果一个 View 依赖于另外一个 View。那么它可能需要操作下面 3 个 API:

public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) { return false; }

public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) {  return false; }

public void onDependentViewRemoved(CoordinatorLayout parent, V child, View dependency) {}

确定一个 View 对另外一个 View 是否依赖的时候,是通过 layoutDependsOn() 这个方法。注意参数,child 是要判断的主角,而 dependency 是宾角,如果 return true,表示依赖成立,反之不成立。当然,你可以复写这个方法对 dependency 进行类型判断否则是其它条件判断,然后再决定是否依赖。只有在 layoutDependsOn() 返回为 true 时,后面的 onDependentViewChanged() 和 onDependentViewRemoved() 才会被调用。

当依赖的那个 View 发生变化时,这个变化代码注释有解释,指的是 dependency 的尺寸和位置发生的变化,当有变化时 Behavior 的 onDependentViewChanged() 方法会被调用。如果复写这个方法时,改变了 child 的尺寸和位置参数,则需要返回 true,默认情况是返回 false。

onDependentView() 被调用时一般是指 dependency 被它的 parent 移除,或者是 child 设定了新的 anchor。

有了上面 3 个 API,我们就能应付在 CoordinatorLayout 中一个子 View 对别个一个子 View 的依赖情景了。

可能会有同学不明白,依赖是为何?或者说是何种依赖。为了避免概念过于空洞抽象。下面,我们用一个简单的例子来让大家感受一下,加深理解。

为了演示效果,我首先在屏幕上定义一个能够响应拖动的自定义 View,我叫它 DependencyView 好了。
这里写图片描述
它的代码很简单,主要是继承一个 TextView,然后在触摸事件中对自身位置进行位移。

public class DependencyView extends TextView {
   

    private final int mSlop;
    private float mLastX;
    private float mLastY;

    public DependencyView(Context context) {
        this(context,null);
    }

    public DependencyView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public DependencyView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        setClickable(true);

        mSlop = ViewConfiguration.getTouchSlop();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
       // return super.onTouchEvent(event);
        int action = event.getAction();

        switch (action)
        {
            case MotionEvent.ACTION_DOWN:
                mLastX = event.getX();
                mLastY = event.getY();
                break;

            case MotionEvent.ACTION_MOVE:
                int deltax = (int) (event.getX() - mLastX);
                int deltay = (int) (event.getY() - mLastY);
                if (Math.abs(deltax) > mSlop || Math.abs(deltay) > mSlop) {
                    ViewCompat.offsetTopAndBottom(this,deltay);
                    ViewCompat.offsetLeftAndRight(this,deltax);
                    mLastX = event.getX();
                    mLastY = event.getY();
                }

                break;

            case MotionEvent.ACTION_UP:
                mLastX = event.getX();
                mLastY = event.getY();
                break;

            default:
                break;

        }

        return true;
    }
}

前置条件已经确定好了,现在我们要向目标 Behavior 出发了。

做一个跟屁虫

实现一个 Behavior,让它支配一个 View 去紧紧跟随

CoordinatorLayoutAndroid Design Support Library 提供的一个特殊的布局容器,它可以协调其内部的子 View 之间的交互行为,实现各种复杂的交互效果,比如 AppBarLayout 和 FloatingActionButton 的联动效果。 CoordinatorLayout 的主要作用是让子 View 之间可以通过 Behavior 进行交互。Behavior 是指子 View 在 CoordinatorLayout 中的交互行为的定义,可以让子 View 之间实现联动效果,如 FloatingActionButton 随着 Snackbar 的出现和消失而改变位置,子 View 之间的交互行为通过 Behavior 实现。 使用 CoordinatorLayout 需要注意以下几点: 1. CoordinatorLayout 必须作为根布局。 2. 子 View 需要设置 app:layout_behavior 属性,指定其交互行为的 Behavior。 3. 子 View 的交互行为需要在 Behavior 中定义。 下面是一个简单的 CoordinatorLayout 的示例: ``` <androidx.coordinatorlayout.widget.CoordinatorLayout android:layout_width="match_parent" android:layout_height="match_parent"> <com.google.android.material.appbar.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <com.google.android.material.appbar.CollapsingToolbarLayout android:layout_width="match_parent" android:layout_height="200dp" app:layout_scrollFlags="scroll|exitUntilCollapsed"> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop" android:src="@drawable/image"/> <androidx.appcompat.widget.Toolbar android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:layout_collapseMode="pin"/> </com.google.android.material.appbar.CollapsingToolbarLayout> </com.google.android.material.appbar.AppBarLayout> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"/> </androidx.coordinatorlayout.widget.CoordinatorLayout> ``` 在上面的示例中,AppBarLayout 和 CollapsingToolbarLayout 实现了一个可折叠的 Toolbar,RecyclerView 使用了 appbar_scrolling_view_behavior Behavior,实现了和可折叠的 Toolbar 的联动效果。
评论 23
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

frank909

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值