1

处理滑动的基本思路都是基于View事件传递机制,嵌套滑动(NestedScroll)机制也不例外。

大量控件都实现了嵌套滑动机制,有很多源码可以学习。例如RecyclerViewSwipeRefreshLayout,下面提供一个使用嵌套滑动机制来实现仿知乎日报效果Demo,效果如下:

enter image description here

嵌套滑动机制原理(NestedScroll)

假设两个View分别为ParentChild,前者是后者的布局容器。

一般说来,Child处于主动地位,其滑动将带动Parent做跟随滑动。在嵌套滑动中优先对Child的事件做出响应。

Parent虽然处于被动跟随地位,也要有一定自主权,能够决定自己参与Child发起的嵌套滑动事件。Parent常常是Child所处的布局容器,继承ViewGroup类。

嵌套滑动机制的使用十分简单,只需要ParentChild中分别实现两个接口。

1.Child实现NestedScrollingChild接口。
实际上该接口的实现可以完全使用帮助类NestedScrollingChildHelper来代理,唯一要处理的是如何在Child的触摸事件方法中调用该接口的方法。

例如使用下列方法开启/关闭嵌套滑动

  • startNestedScroll(int axes);启动嵌套滑动。

  • stopNestedScroll();停止嵌套滑动。

同时嵌套滑动是一个微小增量过程,使用下列方法在每一步滑动中向Parent分发消息。

  • dispatchNestedPreScroll

  • dispatchNestedScroll()

2.Parent实现NestedScrollingParent接口。

该接口中若干方法的实现与具体业务逻辑有关,应该用户自己实现。如下列方法判断父布局控件是否要参与子控件发起的嵌套滑动。

  • onStartNestedScroll 判断是否参与嵌套滑动。

  • onNestedScrollAccepted 确定参与嵌套滑动后将执行,可用作配置初始化。

在嵌套滑动过程中,要处理Child所分发的步进事件。

  • onNestedPreScroll 响应ChilddispatchNestedPreScroll方法。

  • onNestedScroll 响应ChilddispatchNestedScroll方法。

在一次嵌套滑动事件中,ParentChild处于问答式交互。以用户触摸事件开端,Child控件会询问Parent是否参与滑动;而后在每一次增量滑动前后,二者都会沟通。具体如下图

enter image description here

源码解析

1.如果在触摸事件中调用了startNestedScroll方法,实际是由NestedScrollingChildHelper类实现

public boolean startNestedScroll(int axes) {

    if (isNestedScrollingEnabled()) {
        ViewParent p = mView.getParent();
        View child = mView;
        while (p != null) {
            if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes)) {
                mNestedScrollingParent = p;
                ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes);
                return true;
            }
            if (p instanceof View) {
                child = (View) p;
            }
            p = p.getParent();
        }
    }
    return false;
}

子控件会不断上溯寻找能够响应嵌套滑动事件的父容器,并且会调用父控件的onStartNestedScroll方法和onNestedScrollAccepted方法。

2.再看子控件在滑动前的分发事件方法dispatchNestedPreScroll

  • 参数dxdy应该是子控件中触摸事件滑动距离,可以计算出来。

  • consumed是一个数组,应该以零值传入,表示父控件是否消费了滑动事件。在父控件调用后,如果consumed不为0,整个方法返回true,表示父控件消费了滑动事件。

  • offsetInWindow表示执行前后子控件在屏幕上的偏移量。

public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
    //如果开启嵌套滑动,且父控件存在,且滑动有效
    mView.getLocationInWindow(offsetInWindow);
    int startX = offsetInWindow[0];
    int startY = offsetInWindow[1];
    
    consumed[0] = 0;
    consumed[1] = 0;  
    ViewParentCompat.onNestedPreScroll(
    mNestedScrollingParent, mView, dx, dy, consumed);
    
    mView.getLocationInWindow(offsetInWindow);
    offsetInWindow[0] -= startX;
    offsetInWindow[1] -= startY;
b}

Action Bar的完全透明

Action Bar的布局如下

<android.support.design.widget.AppBarLayout
    android:id="@+id/appbar"
    android:layout_height="wrap_content"
    android:layout_width="match_parent"
    android:theme="@style/AppTheme.AppBarOverlay">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:background="?attr/colorPrimaryDark"//需要更改
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        app:popupTheme="@style/AppTheme.PopupOverlay"/>

要做到完全透明,要更改以下属性
1.AppBarLayout背景的透明度
2.Toolbar背景透明度
3.Toolbar背景度
4.此外Toolbar的背景配置不能使用android:background="?attr/colorPrimary",必须要换一种近似颜色。

mAppBarLayout.getBackground().setAlpha(alpha);
mToolbar.getBackground().setAlpha(alpha);
mToolbar.setAlpha(alpha);
  • 此外为了使得Toolbar不遮挡子控件的内容,应该去掉子控件的behave属性。

  • 为了去掉AppBar遗留的阴影,应使用如下方法

mAppBarLayout.setElevation(0.1f);

该值越大,阴影效果越明显,但不能设置为0,设置一个较小值即可近似无阴影。

参考文献


incredible
187 声望5 粉丝