NestedScrolling 嵌套滑动机制:简单实践(一)

一、前言

NestedScrollView 机制是周三听其他组的同学分享才知道的。谈起技术分享,让我学习很多:

  1. 关于视野:学习这件事情,从来不是孤军奋战,应该多多交流和分析、讨论。正如爱尔兰作家说:你有一个苹果,我有一个苹果,我们交换一下,一人还是一个苹果;但是,你有一个思想,我有一个思想,我们交换一下,一人就有两个思想。交流的重要性。
  2. 关于表达:分享的同学表达能力很强,能清楚的明白参数方法的概念,这是需要基本功的。
  3. 关于技术进阶:技术的研究通常是建立在已知问题上的,技术是一种工具,不是为了技术而技术,应该是为了快乐而快乐。

关于NestedScrollView 的学习,想在前人的研究基础上,总结两篇文章,一篇文章讲基础应用,一篇简单的深入分析一下源码,所谓知其然,还要知其背后的思想。

本文的写作逻辑如下:

  1. 前言
  2. NestedScrollIng 相关类简介
  3. NestedScrollingChild 与 NestedScrollingParent 方法详解
  4. DEMO介绍
  5. 小结

二、NestedScrollIng 相关类简介

2.1、ScrollingView 上层滑动接口

继承该接口的类有:

NestedScrolling 嵌套滑动机制:简单实践(一)_第1张图片

该接口主要作用是:

An interface that can be implemented by Views to provide scroll related APIs.

2.2、NestedScrollView 嵌套滑动视图

传统的ScrollView 只是继承了帧布局,与该类相关的接口有 :

NestedScrolling 嵌套滑动机制:简单实践(一)_第2张图片

该类的作用是:

NestedScrollView is just like ScrollView, but it supports acting as both a nested scrolling parent and child on both new and old versions of Android. Nested scrolling is enabled by default.

不仅支持传统的 ScrollView功能,还可以支付嵌套的父View 与子View 的滑动,系统默认开启嵌套滑动支持。

2.3、NestedScrollingChild 子视图嵌套滑动接口

与该接口相关的类有 :

NestedScrolling 嵌套滑动机制:简单实践(一)_第3张图片

该接口的作用:

This interface should be implemented by View subclasses that wish to support dispatching nested scrolling operations to a cooperating parent ViewGroup.

Classes implementing this interface should create a final instance of a NestedScrollingChildHelper as a field and delegate any View methods to the NestedScrollingChildHelper methods of the same signature.

2.4、NestedScrollingParent 父视图嵌套滑动接口

与该接口相关的类有 :

NestedScrolling 嵌套滑动机制:简单实践(一)_第4张图片

该接口的作用:

This interface should be implemented by ViewGroup subclasses that wish to support scrolling operations delegated by a nested child view.

Classes implementing this interface should create a final instance of a NestedScrollingParentHelper as a field and delegate any View or ViewGroup methods to the NestedScrollingParentHelper methods of the same signature.

三、NestedScrollingChild 与 NestedScrollingParent 方法详解

官方NestedScrolling机制涉及关键的类(接口),有:

NestedScrollingChild
NestedScrollingParent
NestedScrollingChildHelper
NestedScrollingParentHelper

3.1、NestedScrollingChild相关方法解析

1、publiv void setNestedScrollingEnabled(boolean enabled);

NestedScrollingChild中的方法,设置该View是否开启Nested Scrolling机制,一个相关联方法为isNestedScrollingEnabled()

2、public boolean startNestedScroll(int axes);

在给定的方向(axes)上开始一个Nested Scroll,只有返回值是true的情况下才会执行之后的方法,否则NestedScrolling机制无效. 默认实现(NestedScrollingChildHelper中的实现)会找到该View的parent,调用parent的onStartNestedScroll方法,如果parent为null或者onStartNestedScroll()返回false,则流程结束.否则调用NestedScrollingParent.onNestedScrollAccepted给其一个初始化配置的机会.

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

传统的事件分发机制有个onInterceptTouchEvent()方法来拦截事件,让ViewGroup可以优先于子View处理触摸事件,这个机制而Nested Scrolling机制也提供了类似的机制.这个机制的实现就是依靠dispatchNestedPreScroll方法.这个方法会在child消耗掉滑动事件之前调用,给parent一个率先处理滑动事件的机会.
offsetInWindow是自己本身的窗体变化,这个方法需要计算这个值做处理来修正touch事件的做标志。

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

分发nested scroll,这个方法会在该child处理完滑动事件之后调用.无论是否有未消耗的值都会调用. offsetInWindow 同dispatchNestedPreScroll.

5、fling相关
fling机制和scroll类似

6、public void stopNestedScroll();
停止nested scroll

3.2、NestedScrollingChild相关方法解析

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

被NestedScrollingChild的startNestedScroll调用,实现者根据具体情况决定是否使用Nested Scroll机制来返回true或false.

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

onStartNestedScroll方法返回true之后被调用,他提供了一个初始化nested scroll相关配置的机会,之所以有这个方法可能是因为onStartNestedScroll无论是否接收nested scroll都回被调用,而该方法只有在确定要执行nested scroll时才会被调用.

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

响应dispatchNestedPreScroll,在child消耗滑动事件之前调用,实现滑动处理的地方.

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

响应dispatchNestedScroll,在child消耗滑动事件之后调用.实现滑动处理.

5、public void onStopNestedScroll(View target);
响应stopNestedScroll,可以做清理工作;

3.3、方法调用关系

NestedScrolling 嵌套滑动机制:简单实践(一)_第5张图片

比较形象的可以聊天图:

NestedScrolling 嵌套滑动机制:简单实践(一)_第6张图片

DEMO介绍

DEMO主要涉及三个类,NestedScrollingActivity、MyNestedScrollParent、MyNestedScrollChildL;

NestedScrollingActivity:

 // 隐藏actionBar
 getSupportActionBar().hide();

布局文件:顶层布局是相对布局,依次是MyNestedScrollParentL、MyNestedScrollChildL

NestedScrolling 嵌套滑动机制:简单实践(一)_第7张图片

MyNestedScrollParentL.java

public class MyNestedScrollParentL extends LinearLayout implements NestedScrollingParent {

    private ImageView mImage;
    private TextView mTvTitle;
    private MyNestedScrollChildL mMyNestedScrollChildL;

    private int mImageHeight;
    private int mTvTitleHeight;

    private NestedScrollingParentHelper mNestedScrollingParentHelper;

    public MyNestedScrollParentL(Context context) {
        super(context);
        init();
    }

    public MyNestedScrollParentL(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init(){
        mNestedScrollingParentHelper = new NestedScrollingParentHelper(this);  //相关事情都交给NestedScrollingParentHelper做
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        mImage = (ImageView)getChildAt(0);//获取第一个视图
        mTvTitle = (TextView)getChildAt(1);
        mMyNestedScrollChildL = (MyNestedScrollChildL)getChildAt(2);

//OnGlobalLayoutListener 是ViewTreeObserver的内部类,当一个视图树的布局发生改变时,可以被ViewTreeObserver监听到

//获取图片的高度
        mImage.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                if(mImageHeight <= 0){
                    mImageHeight = mImage.getMeasuredHeight();
                    Log.i("TEST", "mImageHeight:" + mImageHeight);
                }
            }
        });

 //获取标题的高度     
   mTvTitle.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                if(mTvTitleHeight <= 0){
                    mTvTitleHeight = mTvTitle.getMeasuredHeight();
                    Log.i("TEST", "mTvTitleHeight:" + mTvTitleHeight);
                }
            }
        });

    }

    @Override
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
        Log.i("TEST", "onStartNestedScroll()");
        return true;
    }

    @Override
    public void onNestedScrollAccepted(View child, View target, int axes) {
        Log.i("TEST", "onNestedScrollAccepted()");
        mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, axes);
    }

    @Override
    public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
        Log.i("TEST", "onNestedScrollAccepted()");
    }

    @Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
        Log.i("TEST", "onNestedPreScroll()");
//父布局中除去child部分之外的视图
        if(isShowImage(dy) || isHideImage(dy)){
            consumed[1] = dy/2;
            scrollBy(0, dy);     // scrollBy 其实会调用我们重写的scrollTo 方法;
        }
    }

    @Override
    public void onStopNestedScroll(View child) {
        Log.i("TEST", "onStopNestedScroll()");
        mNestedScrollingParentHelper.onStopNestedScroll(child);
    }

    @Override
    public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
        Log.i("TEST", "onNestedPreFling()");
        return false;
    }

    @Override
    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
        Log.i("TEST", "onNestedFling()");
        return false;
    }

    @Override
    public int getNestedScrollAxes() {

        Log.i("TEST", "getNestedScrollAxes()");

        return 0;
    }

    @Override
    public void scrollTo(int x, int y) {

        Log.i("TEST", "scrollTo():" + "x=" + x + "__y=" + y);

        if( y < 0){ //父布局中除去child部分之外的视图保持不动
            y = 0;
        }
        if(y >= mImageHeight){//父布局中除去child部分之外的视图保持图片的高度
            y = mImageHeight;
        }
        super.scrollTo(x, y);
    }

    /**
     * 判断下拉的时候是否需要向下滑动显示图片
     * 下拉的时候 dy < 0
     */
    public boolean isShowImage(int dy){

        Log.i("TEST", "getScrollY():" + getScrollY());
        Log.i("TEST", "mMyNestedScrollChildL.getScrollY():" + mMyNestedScrollChildL.getScrollY());

        // dy< 0 表示在下拉;
        if(dy < 0){
            if(getScrollY() > 0 && mMyNestedScrollChildL.getScrollY() == 0){
                return true;
            }
        }
        return false;
    }

    /**
     * 上滑的时候;
     * @param dy> 0 表示在上滑
     * @return
     */
   public boolean isHideImage(int dy){

       Log.i("TEST", "getScrollY():" + getScrollY());
       Log.i("TEST", "mMyNestedScrollChildL.getScrollY():" + mMyNestedScrollChildL.getScrollY());

       if(dy > 0){
           if(getScrollY() < mImageHeight){
               return true;
           }
       }
       return false;
   }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i("TEST", "onTouchEvent():" + event.getAction());
        return super.onTouchEvent(event);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.i("TEST","dispatchTouchEvent _  getY():getRawY:"+event.getRawY());
        return super.dispatchTouchEvent(event);
    }
}

MyNestedScrollChildL .java

public class MyNestedScrollChildL extends LinearLayout implements NestedScrollingChild {

    private NestedScrollingChildHelper mScrollingChildHelper;

    public static final String Tag = "MyNestedScrollChildL";

    private final int[] mScrollOffset = new int[2];
    private final int[] mScrollConsumed = new int[2];
    private final int[] mNestedOffsets = new int[2];

    private int mLastTouchX;
    private int mLastTouchY;
    private int showHeight;

    public MyNestedScrollChildL(Context context) {
        super(context);
        init();
    }

    public MyNestedScrollChildL(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init(){
        if (mScrollingChildHelper == null) {
            mScrollingChildHelper = new NestedScrollingChildHelper(this);
            mScrollingChildHelper.setNestedScrollingEnabled(true);
        }
    }


    @Override
    public void setNestedScrollingEnabled(boolean enabled) {
        Log.i(Tag, "setNestedScrollingEnabled:" + enabled);
        mScrollingChildHelper.setNestedScrollingEnabled(enabled);
    }

    @Override
    public boolean isNestedScrollingEnabled() {
        Log.i(Tag, "isNestedScrollingEnabled");
        return mScrollingChildHelper.isNestedScrollingEnabled();
    }

    @Override
    public boolean startNestedScroll(int axes) {

        return mScrollingChildHelper.startNestedScroll(axes);
    }

    @Override
    public void stopNestedScroll() {
        Log.i(Tag, "stopNestedScroll");
        mScrollingChildHelper.stopNestedScroll();
    }

    @Override
    public boolean hasNestedScrollingParent() {
        Log.i(Tag, "hasNestedScrollingParent");
        return mScrollingChildHelper.hasNestedScrollingParent();
    }

    @Override
    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
                                        int dyUnconsumed, int[] offsetInWindow) {
        Log.i(Tag, "dispatchNestedScroll()");
        return mScrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed,
                dxUnconsumed, dyUnconsumed, offsetInWindow);
    }

    @Override
    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
        Log.i(Tag, "dispatchNestedPreScroll()");
        return mScrollingChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
    }

    @Override
    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
        return mScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
    }

    @Override
    public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
        Log.i(Tag, "dispatchNestedPreFling()");
        return mScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:

                mLastTouchY = (int)(event.getRawY() + 0.5f);
                int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE;
                nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL;  //按位或运算

                Log.i(Tag, "nestedScrollAxis:" + nestedScrollAxis);
                startNestedScroll(nestedScrollAxis);
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i(Tag, "Child--getRawY:" + event.getRawY());

                int x = (int) (event.getX() + 0.5f);
                int y = (int) (event.getRawY() + 0.5f);
                int dx = mLastTouchX - x;
                int dy = mLastTouchY - y;

                Log.i(Tag, "child:dy:" + dy + ",mLastTouchY:" + mLastTouchY + ",y;" + y);

                mLastTouchY = y;
                mLastTouchX = x;
                if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset)) {
                    dy -= mScrollConsumed[1];
                    if (dy == 0) {
                        return true;
                    }
                } else {
                    scrollBy(0, dy);

                }
                break;
        }
        return true;
    }

    @Override
    public void scrollTo(int x, int y) {

        int MaxY = getMeasuredHeight() - showHeight;
        if (y > MaxY) {
            y = MaxY;
        }
        if (y < 0) {
            y = 0;
        }
        super.scrollTo(x, y);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (showHeight <= 0) {
            showHeight = getMeasuredHeight();
        } else {
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(1000000, MeasureSpec.UNSPECIFIED);
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

}

执行效果:

向上滑动时,标题可以固定:

NestedScrolling 嵌套滑动机制:简单实践(一)_第8张图片

参考:
1、NestedScrollingChild
2、NestedScrollingParent
3、NestedScrolling

你可能感兴趣的:(Android,进阶)