CoordinatorLayout 和 AppbarLayout 联动原理解析

下图是CoordinatorLayout布局中很常见的一种效果,很多人应该都见过,当我们用手指滑动RecyclerView的时候,不单止RecyclerView会上下滑动,顶部的Toolbar也会随着RecyclerView的滑动隐藏或显现,实现代码的布局如下:

具体代码:

<?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"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|enterAlways|snap"
            android:theme=
              "@style/ThemeOverlay.AppCompat.Dark.ActionBar"
            app:popupTheme=
            "@style/ThemeOverlay.AppCompat.Light" />
    </android.support.design.widget.AppBarLayout>

    <android.support.v7.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" />

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

只要父布局是CoordinatorLayout,然后在Toolbar的外层包上一个AppBarLayout,在Toolbar上添加属性layout_scrollFlags=”scroll|enterAlways|snap”,在RecyclerView上添加属性layout_behavior=”@string/appbar_scrolling_view_behavior”,并把AppBarLayout与RecyclerView作为CoordinatorLayout的子控件,就能实现。

实现的方法知道了,但是我们不能单纯满足于此,接下来我们对原理进行分析
 

实现以上效果主要是涉及了嵌套滑动机制和Behavior两个知识点。

1、嵌套滑动机制(NestedScrolling)

根据事件分发机制,我们知道触摸事件最终只会由一个控件进行处理,当我们滑动RecyclerView时,事件最终肯定是传给了RecyclerView,并交给它进行处理,Toolbar是不应该能够接收到事件并响应的。我们无法依靠默认的事件分发机制完成gif图上的效果的(当然,我们通过自定义View,修改事件分发是可以实现这个效果)。 

因此Google给我们提供了嵌套滑动机制。通过嵌套滑动机制,RecyclerView能够把自身接受到的点击滑动事件传递给父布局CoordinatorLayout,然后CoordinatorLayout把接收到的事件传递给子布局AppBarLayout(Toolbar的父布局),最终滑动事件交给了AppBarLayout进行处理,完成使Toolbar滚出滚进界面等效果。

这里 NestedScrolling 两个重要的概念提及一下

  • NestedScrollingParent NestedScrollingParentHelper
  • NestedScrollingChild NestedScrollingChildHelper

巧合的是 CoordinatorLayout 已经实现了 NestedScrollingParent 接口,所以我们配合一个实现了 NestedScrollingChild 接口的 View 就可以轻松的实现以上效果

一般而言,父布局会实现NestedScrollingParent,而滑动列表作为子控件实现NestedScrollingChild,并把事件传给父布局,父布局再根据情况把事件分发到其它子View。而NestedScrollingParentHelper和NestedScrollingChildHelper分别是NestedScrollingParent和NestedScrollingChild的辅助类,具体的逻辑会委托给它们执行。

接下来我们看一下CoordinatorLayout和RecyclerView的源码:

public class CoordinatorLayout extends ViewGroup implements NestedScrollingParent {

    ......

}
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild {

    ......

}

通过源码可发现CoordinatorLayout实现了NestedScrollingParent,而RecyclerView实现了NestedScrollingChild。毫无疑问,RecyclerView就是通过嵌套滑动机制把滑动事件传给了CoordinatorLayout,然后CoordinatorLayout把事件传递到AppBarLayout中。
那么实现这些接口需要实现哪些方法呢?我们通过源码来了解下:

public interface NestedScrollingChild {

    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed);

    public boolean dispatchNestedPreFling(float velocityX, float velocityY);

    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow);

    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
            int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow);

    public boolean hasNestedScrollingParent();

    public boolean isNestedScrollingEnabled();

    public void setNestedScrollingEnabled(boolean enabled);

    public boolean startNestedScroll(int axes);

    public void stopNestedScroll();

}

public interface NestedScrollingParent {

    public int getNestedScrollAxes();

    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed);

    public boolean onNestedPreFling(View target, float velocityX, float velocityY);

    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed);

    public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
            int dxUnconsumed, int dyUnconsumed);

    public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes);

    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes);

    public void onStopNestedScroll(View target);

}

我整理的是NestedScrollingChild&NestedScrollingParent接口,26版本又添加了新的方法分别继承自NestedScrollingChild&NestedScrollingParent接口 ,看起来要实现的方法很多,也很复杂的样子,但是实质上通过辅助类NestedScrollingChildH

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值