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

来源:互联网 发布:sql varchar转decimal 编辑:程序博客网 时间:2024/05/16 14:00

一、前言

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

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

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

本文的写作逻辑如下:

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

二、NestedScrollIng 相关类简介

2.1、ScrollingView 上层滑动接口

继承该接口的类有:

这里写图片描述

该接口主要作用是:

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

2.2、NestedScrollView 嵌套滑动视图

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

这里写图片描述

该类的作用是:

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 子视图嵌套滑动接口

与该接口相关的类有 :

这里写图片描述

该接口的作用:

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 父视图嵌套滑动接口

与该接口相关的类有 :

这里写图片描述

该接口的作用:

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机制涉及关键的类(接口),有:

NestedScrollingChildNestedScrollingParentNestedScrollingChildHelperNestedScrollingParentHelper

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、方法调用关系

这里写图片描述

比较形象的可以聊天图:

这里写图片描述

DEMO介绍

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

NestedScrollingActivity:

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

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

这里写图片描述

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);    }}

执行效果:

这里写图片描述

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

这里写图片描述

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

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 扑克牌1到13洗后怎么办 南通长牌没钱了怎么办 镇魔曲手游阵营人数已满怎么办 镇魔曲忘记在哪个区怎么办 登录镇魔曲卡在实名验证怎么办 电脑玩联盟花屏怎么办 优盘文件或目录损坏怎么办 苹果下吃鸡设备不兼容怎么办 龙之谷账号忘了怎么办 不花钱的排风除湿怎么办 苹果平板id密码忘了怎么办 苹果平板忘记id及密码怎么办 饥荒抓到的兔子怎么办 苹果手机下载不了王者荣耀怎么办 ipad登录显示验证失败怎么办 苹果手机系统内存太大怎么办 ipad玩游戏没声音怎么办 微信活跃度低怎么办 想开通淘宝直播粉丝不够怎么办 下巴长泡泡还痒怎么办 脚起泡泡很痒怎么办 脚痒还有小泡泡怎么办 外阴长了肉疙瘩怎么办 嘴巴里泡泡破了怎么办 脚上泡泡破了怎么办 脸被自己扣破了怎么办 6s安装不了软件怎么办 苹果6s特别卡怎么办 苹果手机4g网慢怎么办 大王卡玩王者卡怎么办 荣耀7c手机卡顿怎么办 华为6x手机卡顿怎么办 荣耀7c手机老卡怎么办 苹果6打王者卡怎么办 电脑玩游戏显示显卡不行怎么办 笔记本玩游戏显卡不行怎么办 笔记本玩英雄联盟有点卡怎么办 英语考试作文抄了阅读理解怎么办 qq账号被盗怎么办很久了 想玩线上德州没有渠道怎么办 手机玩久了头晕怎么办