Android NestedScrolling帮你实现一个简单的嵌套滑动

来源:互联网 发布:中医药大数据 编辑:程序博客网 时间:2024/04/27 13:20

1. 仅仅有图片可以参考.

1. 首先你得知道正常的事件分发机制,即当一个事件被某个拦截时,当前手势接下来的事件都会交给View进行处理。

2. 这也是为什么ScrollView嵌套ListView 产生滑动冲突,体验不佳的原因。

3. 使用NestedScrollParent组合NestedScrollChild并不是改变这种机制,而正是利用这种机制进行恰当的回调。

4. NestedScrollParent和NestedScrollChild是2个接口,实现NestedScrollParent的View不再需要拦截scroll事件。

5.也就是说全程处理滑动事件的仅仅是子View 但是子View能滑动多少,实现NestedScrollParent的父View说了算。

5. 实现NestedScrollChild接口的子View 需要在touch事件里恰当的处理好回调方法,及时准确的通知父View 自己准备要进行的动作。

6. 子View 通过把scroll的动作告知父View ,父View可以通过实现回调的方法来响应这些回调 1.处理自身的位置 2.告知子View要处理的位置。

7. 举个栗子:RecyclerView接到滑动事件,要向上滑动10,再没滑动之前 把要滑动的情况优先告知父View(实现NestedScrollParent)。

8. 父View再接到RecyclerView可以产生向上滑动10的时候优先做出处理,先让自己身滑动5,并告知RecyclerView “我已经消费了5”。

9. RecyclerView 把本来要消费的10 和已经被父View消费的5 综合起来判断自己要消费多少。即RecyclerView仅仅向上滑动了5.

10.      所以关键点就在于你什么时候想消费、并且消费多少 子View的滑动,这影响到父View和子View的具体展示。


布局:仅仅是一个父容器(实现NestedScrollParent) 内嵌入 TopView 和实现了NestedScrollChild的RecyclerView。


<?xml version="1.0" encoding="utf-8"?><com.yushilei.nestedscrolling.NestedScrollLayout    android:id="@+id/nested"    xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:background="#d8daa2"    android:orientation="vertical"    tools:context=".MainActivity">    <RelativeLayout        android:id="@+id/text_top"        android:layout_width="match_parent"        android:layout_height="300dp"        android:background="#4400ff00">        <TextView            android:layout_width="match_parent"            android:layout_height="match_parent"            android:gravity="center"            android:text="topView"            android:textSize="30sp"            android:textStyle="bold"/>    </RelativeLayout>    <android.support.v7.widget.RecyclerView        android:id="@+id/recycler"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:background="#00ccff"        android:padding="10dp"/></com.yushilei.nestedscrolling.NestedScrollLayout>

代码:
package com.yushilei.nestedscrolling;import android.content.Context;import android.support.v4.view.NestedScrollingParent;import android.support.v4.view.ViewCompat;import android.support.v7.widget.RecyclerView;import android.util.AttributeSet;import android.util.Log;import android.view.View;import android.view.ViewGroup;import android.widget.LinearLayout;import android.widget.OverScroller;/** * @author by  yushilei. * @time 2016/9/1 -10:57. * @Desc */public class NestedScrollLayout extends LinearLayout implements NestedScrollingParent {    private View topV;    private RecyclerView recycler;    private OverScroller mScroller;    public NestedScrollLayout(Context context, AttributeSet attrs) {        super(context, attrs);    }    String TAG = "NestedScrollLayout";    /**     * 当实现NestedScrollingChild的子view将要滑动时,回调实现NestedScrollingParent的父View     * nestedScrollAxes代表方向 父View可以根据自己的意愿来响应子view在某个方向上的滑动     */    @Override    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {        Log.d(TAG, "onStartNestedScroll nestedScrollAxes=" + nestedScrollAxes);                //1、响应纵向滑动 即上下滑动时,父View可以接受响应                return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;    }    @Override    public void onNestedScrollAccepted(View child, View target, int axes) {        Log.d(TAG, "onNestedScrollAccepted axes=" + axes);    }    @Override    public void onStopNestedScroll(View child) {        Log.d(TAG, "onStopNestedScroll" + child);    }    @Override    public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {        Log.d(TAG, "onNestedScroll dxConsumed=" + dxConsumed + ";dyConsumed=" + dyConsumed + ";dxUnconsumed=" + dxUnconsumed + ";dyUnconsumed=" + dyUnconsumed);    }    /**     * 如果父View 在onStartNestedScroll 响应子view,那么子View在滑动即将滑动时     * 会产生子View要滑动的距离 dx 和dy     * onNestedPreScroll 可以通过 dx dy consumed[0] consumed[1] 来影响父View 和子View的变化     * 即 父View可以消耗掉部分 或者全部子View的滑动距离     */    @Override    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {        //2、dy>0 代表向上滑动 dy<0 代表向下滑动        //3、 如果向上滑动 判断TopView是否需要进行隐藏                boolean hiddenTop = dy > 0 && getScrollY() < topV.getHeight();                //4、 如果向下滑动 判断是否可以展示TopView                boolean showTop = dy < 0 && getScrollY() > 0 && !ViewCompat.canScrollVertically(target, -1);        if (hiddenTop || showTop) {                    //5、如果 需要隐藏TopView 或者需要展示TopView 都应该消费掉 dy            //5.1、并且把消费掉的距离转换成父View Scroll的距离,已完成对TopView的展示或隐藏                        //scrollBy 内部调用scrollTo 所以需要考虑scroll过量的情况,所以从写scrollTo            scrollBy(0, dy);                        // consumed[1]用来告诉 子View 父View 对dy的消费程度,            // 子View 就是根据dy 和consumed[1] 来判断自己还能滑动多少距离的                        consumed[1] = dy;        }        Log.d(TAG, "onNestedPreScroll dx=" + dx + ";dy=" + dy + ";consumed" + consumed);    }    public void fling(int velocityY) {        mScroller.fling(0, getScrollY(), 0, velocityY, 0, 0, 0, topV.getHeight());        invalidate();    }    /**     * 就是当快速滑动手指Up的瞬间 产生的Fling动作,这个时候需要根据Fling的速度     * 来计算View停下来的位置     */    @Override    public boolean onNestedPreFling(View target, float velocityX, float velocityY) {        Log.d(TAG, "onNestedPreFling velocityX" + velocityX + ";velocityY=" + velocityY);        if (getScrollY() >= topV.getHeight()) {            return true;        }        fling((int) velocityY);        return true;    }    @Override    protected void onFinishInflate() {        super.onFinishInflate();        topV = findViewById(R.id.text_top);        recycler = (RecyclerView) findViewById(R.id.recycler);        mScroller = new OverScroller(getContext());    }    /**     * 重写scrollTo 需要根据 x ,y 的坐标来判断是否还能继续滑动了     */    @Override    public void scrollTo(int x, int y) {        Log.d(TAG, "scrollTo x=" + x + ";y=" + y);                //当y的值=0时 就是TopView全部露出的时候,不能继续向下滑动了                if (y < 0) {            y = 0;        }        //如果y的值大于 TopView的高度,那么就是父View 始终保持在向上滑动topV.getHeight()的距离上                if (y > topV.getHeight()) {            y = topV.getHeight();        }        if (y != getScrollY()) {            super.scrollTo(x, y);        }    }    /**     * 必须重写onMeasure 不然 RecyclerView会始终占据部分屏蔽     */    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);                //1、在这里 我们实际上是让RecyclerView 的高度为 整个父布局的高度        ViewGroup.LayoutParams layoutParams = recycler.getLayoutParams();        layoutParams.height = getMeasuredHeight();        recycler.setLayoutParams(layoutParams);        //2、改变父布局的高度 即让父布局的高度为 topView的高度+ RecyclerView的高度        //3、按本例子来看,打印的结果 即 RecyclerView的高度为屏幕高度 ,父布局高度为 屏幕高度+ TopView的高度        //4、也就是说 父布局不仅仅是初始化看到的屏幕那边大,如果你给RecyclerView 的Adapter 方法加打印的话        //你就可以看到 RecyclerView实际加载的布局 要比初始显示的多很多,就是这个原因了        setMeasuredDimension(getMeasuredWidth(), topV.getMeasuredHeight() + recycler.getMeasuredHeight());        Log.d(TAG, "Height=" + getMeasuredHeight() + " TopV height=" + topV.getHeight() + ";recycler =" + recycler.getMeasuredHeight());    }}




0 0
原创粉丝点击