`
renyuan_1991
  • 浏览: 70591 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

NestedScrolling的使用及ScrollView的惯性滑动

阅读更多
NestedScrolling的使用及ScrollView的惯性滑动

转载请注明出处:http://renyuan-1991.iteye.com/blogs/2262643

NestedScrolling介绍
    Lollipop之后增加了NesteScrolling,可以通过这个方法在滚动当前控件的时候改变其他控件的样式,嵌套滑动就是最好的例子。以前的思路是在滑动之前判断父控件剩余滑动空间,如果有滑动空间就把touch事件交个父控件,如果父控件不需要滑动就直接把touch事件交给子控件。这样的处理有一个弊端,每次事件只能被一个对象处理。
    在使用的时候会用到以下四个接口:
    NestedScrollingChild,
    NestedScrollingChildHelper,
    NestedScrollingParent,
    NestedScrollingParentHelper
    子view实现NestedScrollingChild接口并重写
    setNestedScrollingEnabled,
    isNestedScrollingEnabled,
    startNestedScroll,
    stopNestedScroll,
    hasNestedScrollingParent,
    dispatchNestedScroll,
    dispatchNestedPreScroll,
    dispatchNestedFling,
    dispatchNestedPreFling这几个方法。
    父view实现NestedScrollingParent借口并重写
    onNestedScrollAccepted,
    onStartNestedScroll,
    onNestedPreScroll,
    onNestedScroll方法即可。他们之间详细的关系如下:
    在子view需要滑动的时候例如ACTION_DOWN的时候就要调用startNestedScroll(ViewCompat.SCROLL_AXIS_HORIZONTAL | ViewCompat.SCROLL_AXIS_VERTICAL);方法来告诉父view自己要开始滑动了,父view会收到onStartNestedScroll这个方法的回调从而决定是不是要配合子view做出响应。如果需要配合,还会回调onNestedScrollAccepted()。在滑动事件产生但是子view还没处理前可以调用dispatchNestedPreScroll(0,dy,consumed,offsetInWindow)这个方法把事件传给父view这样父view就能在onNestedPreScroll方法里面收到子view的滑动信息,然后做出相应的处理把处理完后的结果通过consumed传给子view。同样的道理,如果父view需要在子view滑动后处理相关事件的话可以在子view的事件处理完成之后调用dispatchNestedScroll然后父view会在onNestedScroll收到回调。最后,滑动结束,调用onStopNestedScroll()表示本次处理结束。就这么简单,下面总结一下流程:
第一步:
      在Childe中创建NestedScrollingChildHelper一般实现一下几个方法即可完成需求:
setNestedScrollingEnabled
isNestedScrollingEnabled
startNestedScroll
stopNestedScroll
hasNestedScrollingParent
dispatchNestedScroll
dispatchNestedPreScroll
dispatchNestedFling
dispatchNestedPreFling


第二步:
      在构造函数里面实例化helper并设置setnestedScrollingEnabled(true)
public MyChildScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
        childHelper = new NestedScrollingChildHelper(this);
        setNestedScrollingEnabled(true);
    }


第三步:
     在onTouchEvent中处理事件
     Activon_down中调用startNestedScroll方法,告诉父类我马上要开始滑动了 startNestedScroll方法接受的参数为ViewCompat.SCROLL_AXIS_HORIZONTAL | ViewCompat.SCROLL_AXIS_VERTICAL
    Activon_move中处理自己的移动,在自己移动之前可以调用dispatchNestedPreScroll让父view通过回调onNestedPreScroll方法 先滑动。父view滑动之后会把结果回调到这个方法的里面,我们可以通过这个方法的参数得知父view滑动了多少距离如下代码
/**
     * @param dx       水平滑动距离
     * @param dy       垂直滑动距离
     * @param consumed 父类消耗掉的距离
     * @return
     */
    @Override
    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
        return childHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
    }

另外,如果我们滑动之后仍然需要父view做行对应的处理就可以通过调用dispatchNestedScroll方法实现,这个方法会回调到父view的onNestedScroll
     Action_up中 调用stopNestedScroll();结束滑动
第四步:
     父view也需要实helper
public MyScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
        parentHelper = new NestedScrollingParentHelper(this);
    }

子view中调用了startNestedScroll 那么父类就需要做相对应的响应,如果父类需要配合滑动就会调用
     父view在onNestedPreScroll先于子view处理滑动事件,这里也可以拦截子view的滑动事件
     如果父view在子view滑动之后也要做相对应的处理那么在onNestedScroll中也可以中相对应的处理
一般情况下父类只需要实现下面几个方法即可:
   
//~~~~~~~ 嵌套滑动 ~~~~~~~
    @Override
    public void onNestedScrollAccepted(View child, View target, int axes) {
        super.onNestedScrollAccepted(child, target, axes);
    }

    @Override
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
        return super.onStartNestedScroll(child, target, nestedScrollAxes);
    }

    @Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
//        super.onNestedPreScroll(target, dx, dy, consumed);
        //切记,把父类滑剩下的给子类
        //~~~~~~~ 正向滑动 ~~~~~~~
        if((sumScroll+dy)>sumScroll){
            //超过边界
            if((Math.abs(sumScroll+dy))>myScrollDis){
                MyScrollView.this.scrollBy(dx, myScrollDis - sumScroll);//最多滑动myScrollDis,当超出边界的时候父类只需要滑动剩下的距离
                consumed[1] = dy - (myScrollDis - sumScroll);//将没滑动的距离给子类,让子类滑动
                sumScroll = myScrollDis;
            }else{//未超过边界
                MyScrollView.this.scrollBy(dx, dy);
                sumScroll += dy;//记录这个view滑动的真实距离
                consumed[1] = 0;
            }
        }
        //~~~~~~~ 反向互动 ~~~~~~~
        else if((sumScroll+dy)<sumScroll){
            //超过边界
            if((sumScroll+dy)<0){
                MyScrollView.this.scrollBy(dx, -sumScroll);//
                consumed[1] = sumScroll+dy;
                sumScroll = 0;
            }else{//未超过边界
                MyScrollView.this.scrollBy(dx, dy);
                sumScroll += dy;//记录这个view滑动的真实距离
                consumed[1] = 0;
            }
        }else {

        }
    }

    @Override
    public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
        super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
    }

    最后提醒一下,在父类的onNestedPreScroll中如果返回0,在子类中的dispatchNestedPreScroll就会返回false。官网上说“true if the parent consumed some or all of the scroll delta”。可以理解成当我们给consumed赋值的时候如果指定它的值是0,就会认为父类没有消耗。所以在这里强调一下consumed一定要统一返回父类消耗的距离。

子view:
public class MyChildScrollView extends ScrollView implements NestedScrollingChild{
    private NestedScrollingChildHelper childHelper;
    private int[] consumed = new int[2];
    private int[] offsetInWindow = new int[2];
    private int downX;
    private int downY;
    private VelocityTracker mVelocityTracker = null;
    private boolean allowFly = false;
    public MyChildScrollView(Context context) {
        super(context);
    }

    public MyChildScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
        childHelper = new NestedScrollingChildHelper(this);
        setNestedScrollingEnabled(true);
    }
    

    public MyChildScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if(mVelocityTracker == null){
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(ev);

        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                allowFly = false;
                downX = (int)ev.getRawX();
                downY = (int)ev.getRawY();
                startNestedScroll(ViewCompat.SCROLL_AXIS_HORIZONTAL | ViewCompat.SCROLL_AXIS_VERTICAL);
                break;
            case MotionEvent.ACTION_MOVE:
                int moveX = (int)ev.getRawX();
                int moveY = (int)ev.getRawY();
                int dx = moveX - downX;
                int dy = -(moveY - downY);//滚动方法的方向跟坐标是相反的,所以这里要加一个负号
                downX = moveX;
                downY = moveY;
                //在consumed中就是父类滑动后剩下的距离,
                if(dispatchNestedPreScroll(0,dy,consumed,offsetInWindow)){
                    dy = consumed[1];
                    MyChildScrollView.this.scrollBy(0, dy);
                    allowFly = true;
                }else {

                }
                break;
            case MotionEvent.ACTION_UP:
                stopNestedScroll();
                if(allowFly){
                    mVelocityTracker.computeCurrentVelocity(1000);//该参数指定的是1S内滑动的像素。也可以指定最大速率。
                    int myScrollFly = (int)mVelocityTracker.getYVelocity();
                    fling(-myScrollFly);//惯性滑动的方法
                }
                break;
        }
        return true;
    }
    //~~~~~~~ 嵌套滑动的处理方法 ~~~~~~~
    @Override
    public void setNestedScrollingEnabled(boolean enabled) {
        childHelper.setNestedScrollingEnabled(enabled);
    }

    @Override
    public boolean isNestedScrollingEnabled() {
        return childHelper.isNestedScrollingEnabled();

    }

    @Override
    public boolean startNestedScroll(int axes) {
        return childHelper.startNestedScroll(axes);
    }

    @Override
    public void stopNestedScroll() {
        childHelper.stopNestedScroll();

    }

    @Override
    public boolean hasNestedScrollingParent() {
        return childHelper.hasNestedScrollingParent();
    }

    @Override
    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) {
        return childHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow);
    }

    /**
     * @param dx       水平滑动距离
     * @param dy       垂直滑动距离
     * @param consumed 父类消耗掉的距离
     * @return
     */
    @Override
    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
        return childHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
    }

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

    @Override
    public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
        return childHelper.dispatchNestedPreFling(velocityX, velocityY);
    }
}

中间view:
public class MyViewPager extends ViewPager implements NestedScrollingChild,NestedScrollingParent{
    private NestedScrollingChildHelper childHelper;
    private NestedScrollingParentHelper parentHelper;
    private int[] consumed = new int[2];
    private int[] offsetInWindow = new int[2];
    private float downX;
    private float downY;
    public MyViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
        childHelper = new NestedScrollingChildHelper(this);
        parentHelper = new NestedScrollingParentHelper(this);
        setNestedScrollingEnabled(true);
    }
    public MyViewPager(Context context) {
        super(context);
    }

    //~~~~~~~ 作为父类滑动的四种处理方法 ~~~~~~~

    @Override
    public void onNestedScrollAccepted(View child, View target, int axes) {
        parentHelper.onNestedScrollAccepted(child,target,axes);
    }

    @Override
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
        startNestedScroll(nestedScrollAxes);
        return true;
    }

    @Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
        dispatchNestedPreScroll(dx, dy, consumed, null);
    }

    @Override
    public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
        super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
    }

    //~~~~~~~ 作为子类滑动的处理方法 ~~~~~~~
    @Override
    public void setNestedScrollingEnabled(boolean enabled) {
        childHelper.setNestedScrollingEnabled(enabled);
    }

    @Override
    public boolean isNestedScrollingEnabled() {
        return childHelper.isNestedScrollingEnabled();

    }

    @Override
    public boolean startNestedScroll(int axes) {
        return childHelper.startNestedScroll(axes);
    }

    @Override
    public void stopNestedScroll() {
        childHelper.stopNestedScroll();

    }

    @Override
    public boolean hasNestedScrollingParent() {
        return childHelper.hasNestedScrollingParent();
    }

    @Override
    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) {
        return childHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow);
    }

    /**
     * @param dx       水平滑动距离
     * @param dy       垂直滑动距离
     * @param consumed 父类消耗掉的距离
     * @return
     */
    @Override
    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
        return childHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
    }

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

    @Override
    public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
        return childHelper.dispatchNestedPreFling(velocityX, velocityY);
    }

}

父view:
public class MyScrollView extends ScrollView implements NestedScrollingParent {
    private int myScrollDis = 0;
    private int sumScroll = 0;
    private NestedScrollingParentHelper parentHelper;
    public MyScrollView(Context context) {
        super(context);
    }

    public MyScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
        parentHelper = new NestedScrollingParentHelper(this);
    }

    public MyScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        System.out.println("MyScrollView.onTouchEvent");
        return super.onTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return false;
//        return super.onInterceptTouchEvent(ev);
    }

    //~~~~~~~ 添加一个方法,在代码中设置这个父类滚动的限度 ~~~~~~~
    public void setMyScrollDis(int myScrollDis){
        this.myScrollDis = myScrollDis;
    }

    //~~~~~~~ 嵌套滑动 ~~~~~~~
    @Override
    public void onNestedScrollAccepted(View child, View target, int axes) {
        super.onNestedScrollAccepted(child, target, axes);
    }

    @Override
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
        return super.onStartNestedScroll(child, target, nestedScrollAxes);
    }

    @Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
//        super.onNestedPreScroll(target, dx, dy, consumed);
        //切记,把父类滑剩下的给子类
        //~~~~~~~ 正向滑动 ~~~~~~~
        if((sumScroll+dy)>sumScroll){
            //超过边界
            if((Math.abs(sumScroll+dy))>myScrollDis){
                MyScrollView.this.scrollBy(dx, myScrollDis - sumScroll);//最多滑动myScrollDis,当超出边界的时候父类只需要滑动剩下的距离
                consumed[1] = dy - (myScrollDis - sumScroll);//将没滑动的距离给子类,让子类滑动
                sumScroll = myScrollDis;
            }else{//未超过边界
                MyScrollView.this.scrollBy(dx, dy);
                sumScroll += dy;//记录这个view滑动的真实距离
                consumed[1] = 0;
            }
        }
        //~~~~~~~ 反向互动 ~~~~~~~
        else if((sumScroll+dy)<sumScroll){
            //超过边界
            if((sumScroll+dy)<0){
                MyScrollView.this.scrollBy(dx, -sumScroll);//
                consumed[1] = sumScroll+dy;
                sumScroll = 0;
            }else{//未超过边界
                MyScrollView.this.scrollBy(dx, dy);
                sumScroll += dy;//记录这个view滑动的真实距离
                consumed[1] = 0;
            }
        }else {

        }
    }

    @Override
    public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
        super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
    }
}


    对了,好像忘了说ScrollView的惯性滑动了,其实代码已经在上面的子view中了,先用VelocityTracker得到滑动的速率首先要创建mVelocityTracker,并把event添加到VelocityTracker中
if(mVelocityTracker == null){
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(ev);

然后获得滑动速率(这里设置的时间是一秒)
mVelocityTracker.computeCurrentVelocity(1000);//该参数指定的是1S内滑动的像素。也可以指定最大速率。
                    int myScrollFly = (int)mVelocityTracker.getYVelocity();//获得y轴上的速率

最后用fling滑动这个速率即可。   
fling(-myScrollFly);//惯性滑动的方法



    github地址:https://github.com/renyuan1991/MyFistNesteD/tree/renyuan_ns
   
    转载请注明出处:http://renyuan-1991.iteye.com/blogs/2262643
    希望爱好编程的小伙伴能加这个群,互相帮助,共同学习。群号: 141877583
   
   
   
   
   
   
   
   
分享到:
评论

相关推荐

    安卓 惯性 滑动 回弹 ScrollView

    在Android开发中,惯性滑动和回弹效果是用户界面设计中常见且重要的功能,尤其是在ScrollView这样的滚动视图组件中。惯性滑动是指当用户快速滑动屏幕后,内容会继续保持一段时间的滑动状态,模拟真实世界的物理惯性...

    Android ScrollView自动滑动

    本文将深入探讨如何实现ScrollView的自动滑动功能,特别是在创建"关于我们"页面时,如何让ScrollView自动滑动到底部,以及相关的编程技术和注意事项。 一、ScrollView基础知识 ScrollView是Android提供的一个可滚动...

    webview随scrollview一起滑动

    当一个WebView被放入ScrollView中时,可能会遇到一些问题,比如“webView与scrollView结合一起滑动时,webview显示为空白”。这个问题主要是由于滚动机制冲突导致的。下面我们将详细探讨这个问题的原因、解决方案...

    判断ScrollView是否滑动到最下边或者最上边

    这个代码可以判断ScrollView是否滑动到了最下边或者最上边,同理,HorizontalScrollView也可以判断是否滑动到最右边或者最左边。使用方法就是直接用这个自定义控件并实现里面的OnScrollListener就可以了,会自动复写...

    scrollview的滑动监听底部以及横向滑动

    此外,我们还可以使用ScrollView的OnTouchListener来监听更复杂的滑动行为,如滑动速度、滑动方向等。通过GestureDetector和Scroller类,我们可以实现更精细的滑动手势控制,例如快速滑动时的平滑滚动效果。 总结...

    Android ScrollView向上滑动控件顶部悬浮效果实现

    本文将详细讲解如何实现ScrollView向上滑动时,控件顶部悬浮的效果,这种效果通常被称为“头部固定”或“吸顶”效果,常见于各种应用的导航栏或者工具栏。 首先,我们需要理解这个效果的基本原理。当用户在...

    监听Scrollview滑动到最左边与最右边

    接下来,为了监听ScrollView的滑动事件,我们需要使用ScrollView的滚动回调。在Java代码中,可以通过设置OnScrollChangeListener来实现: ```java ScrollView scrollView = findViewById(R.id.scrollView); ...

    Android ScrollView嵌套横向滑动控件时冲突问题

    前言:今天在开发的时候遇到这样的问题,最外层是ScrollView,里面嵌套了一个横向滑动的日历控件,在滑动日历的时候很卡顿。看到这种问题,自然而然的就会想到scrollview和其他可滑动控件的冲突问题。 解决思路 用户...

    Android开发控制ScrollView滑动速度的方法

    值得注意的是,`fling`方法仅处理快速滑动的情况,当用户缓慢滑动时,仍然会使用ScrollView的默认行为。如果你希望同时控制慢速滑动的速度,你需要监听滑动事件,例如使用`OnScrollChangeListener`,并在其中调整...

    Unity实现ScrollView滑动吸附功能

    本文实例为大家分享了Unity实现ScrollView滑动吸附的具体代码,供大家参考,具体内容如下 最近在做一个展示模块的时候遇到了一个需要实现滑动窗口并且能固定吸附距离的需求,借助UGUI的ScrollView的API以及Dotween...

    ScrollView嵌套ScrollView滑动

    当一个ScrollView内嵌套另一个ScrollView时,可能会遇到一些滑动事件处理的问题。这种情况下,我们需要理解Android事件分发机制以及如何解决嵌套滚动冲突。 首先,我们要明白Android的事件分发机制,它主要包括三个...

    新版闪记ScrollView垂直滑动框架

    "新版闪记ScrollView垂直滑动框架"就是这样一个专为实现流畅、美观的滚动效果而设计的UI框架,尤其适用于记事本类应用或者任何需要上下滚动内容的场景。这个框架优化了下拉滑动操作,使得用户在浏览长内容时能够享受...

    Android-滑动响应工具类目前支持RecyclerView和ScrollView的滑动响应

    在Android应用开发中,滑动操作是用户交互的重要组成部分,特别是在滚动视图如RecyclerView和ScrollView中。为了提高用户体验,开发者通常需要对这些视图的滑动事件进行定制处理,例如实现下拉刷新、上拉加载更多等...

    UGUI ScrollView 分页滑动

    【Unity】UGUI ScrollView 分页 单次拖拽滑动一页,主要使用了ScrollRect.horizontalNormalizedPosition来实现。

    Super ScrollView无线滑动列表.rar

    在Unity游戏开发中,"Super ScrollView无线滑动列表"是一个高效且易用的插件,专为实现无限滚动效果而设计。它适用于那些需要展示大量数据或者内容的场景,如商品列表、用户评论等,可以极大地提升用户体验。该插件...

    弹性拉伸Scrollview、scrollview嵌套listview和scrollview滑动监听demo

    本文将深入探讨如何实现“弹性拉伸”的ScrollView、在ScrollView中嵌套ListView以及添加ScrollView的滑动监听。这些技巧在创建复杂的用户界面时非常实用。 首先,让我们谈谈“弹性拉伸”效果的ScrollView。这种效果...

    UnityUGUI实现ScrollView 滑动居中的放大、其他的缩小,简单实现方法

    UnityUGUI实现ScrollView 滑动居中的放大、其他的缩小,简单实现方法 配套资源文章:https://blog.csdn.net/lq1340817945/article/details/121001075

    android完美解决listView与ScrollView滑动冲突

    5. **使用 NestedScrolling** API:ListView和ScrollView可以利用Android的NestedScrolling API来协调它们的滚动行为。ListView需要实现NestedScrollingChild接口,ScrollView需要实现NestedScrollingParent接口,...

    惯性滑动切换(Fling操作)源码

    惯性滑动切换,也称为Fling操作,是Android用户界面设计中常见的一种手势交互方式。它主要用于ListView、ViewPager、ScrollView等控件,允许用户快速滑动内容并以平滑的惯性效果滚动到目标位置。Fling操作的实现原理...

Global site tag (gtag.js) - Google Analytics