NestedScrolling机制学习(一)

自从5.0推出NestedScrolling这个机制一直没有去了解,之前项目中要求做这种效果后觉得还是有必要学习一下,掌握这个机制是可以做出现在很多App流行的联动滚动效果的。
比如饿了么这种效果:

20161226202655700.gif

原谅我随便盗个效果图~~

这个机制也是挺复杂的,需要对于view知识有一定的了解,自己刚开始学习这个,也算对于自己学习的一个总结.

贴下布局

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.boboyuwu.nestedscrolling.MainActivity">
    <com.example.boboyuwu.nestedscrolling.LinearNestedScrollParentLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:background="@android:color/holo_blue_light"
            android:gravity="center"
            android:text="这是我的ActionBar"
            android:textColor="@android:color/white"/>
        <TextView
            android:layout_width="match_parent"
            android:layout_height="30dp"
            android:background="@android:color/holo_red_light"
            android:gravity="center"
            android:text="这是中间隔的一个view哦"/>

        <com.example.boboyuwu.nestedscrolling.LinearNestedScrollChildLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <TextView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:text="@string/content"/>
            
        </com.example.boboyuwu.nestedscrolling.LinearNestedScrollChildLayout>
    </com.example.boboyuwu.nestedscrolling.LinearNestedScrollParentLayout>

</FrameLayout>

首先我们定义一个

 LinearNestedScrollParentLayout extends LinearLayout implements NestedScrollingParent

实现NestedScrollingParent接口

    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes);
    public void onStopNestedScroll(View target);
    public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
            int dxUnconsumed, int dyUnconsumed);
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed);
    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed);
    public boolean onNestedPreFling(View target, float velocityX, float velocityY);

    public int getNestedScrollAxes();

做为一个接口特么居然这么多实现方法,额滴天啊是不是一眼就想放弃的感觉,我也是这样想的,不过还好系统给我们提供了一个helper类自动帮我们处理这些逻辑.
我们在init初始化的时候new一个helper对象

mNestedScrollingParentHelper = new NestedScrollingParentHelper(this);

然后在每个实现方法里我们这样调用

  mNestedScrollingParentHelper.onNestedScrollAccepted(child,target,nestedScrollAxes);

把方法中参数传递到helper类相同方法的参数中,copy copy就行了~

ok我们复制粘贴一通终于"实现了"所有的方法.

接下来以同样的方式定义一个LinearNestedScrollChildLayout类

public class LinearNestedScrollChildLayout extends LinearLayout implements NestedScrollingChild

它实现NestedScrollingChild接口,同样我们要实现一堆的方法,这个类也给我们提供了一个helper帮助类简化我们的工作
在init的时候我们创建一个helper对象

    mNestedScrollingChildHelper = new NestedScrollingChildHelper(this);

跟parent同样的方式我们使用这个helper类去实现接口中的所有方法,这里就不贴代码了跟上面是一样的.

好了,基本工作做完接下来我们就去实现一个类似的效果。
我们重写child触摸事件

   @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mStartY = event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                float y = event.getY();
                int diffY = (int) (mStartY - y);
                if (startNestedScroll(View.SCROLL_AXIS_VERTICAL) && dispatchNestedPreScroll(0, diffY, consume, offset)) {
                    mOffsetY += offset[1];
                    Log.e("wwwconsume", " offset:" + mOffsetY);
                } else {
                    scrollBy(0, diffY);
                }
                Log.e("wwww", diffY + "     startY:" + mStartY + "   y:" + y + "  top:" + getTop());
                mStartY = y;
                break;
            case MotionEvent.ACTION_UP:

                break;
        }
        return true;
    }

当我们移动的时候调用startNestedScroll(View.SCROLL_AXIS_VERTICAL)方法通知我们parent我要开始滑动的,里面传入一个参数,这里我传入一个垂直标记,parent可以根据这个判断返回boolean值,如果返回true就优先于child滑动,如果返回false,那么我们的if判断就不成立走else,自然parent也就不接受这次滚动事件,同时我们还要调用dispatchNestedPreScroll(0, diffY, consume, offset)这个方法里面参数分别是
params:
int dx -> 自己当前移动位置的x坐标
int dy ->自己当前移动位置的y坐标
int[] consumed -> 一个int[]数组 用于接收parent优先滑动消费的x,y距离,这个由parent滑动消费后指定
int[] offsetInWindow -> 一个int[]数组 用于记录parent每次事件的偏移量,如果每次child滑动距离parent全部消费了,那么consumed 里面的值是等同于
offsetInWindow 偏移量的我们在if()判断里打印下看看是不是这样

06-07 14:36:50.619 25570-25570/com.example.boboyuwu.nestedscrolling E/wwwconsume:  offset:-3  consume:3
06-07 14:36:50.632 25570-25570/com.example.boboyuwu.nestedscrolling E/wwwconsume:  offset:-18  consume:18
06-07 14:36:50.649 25570-25570/com.example.boboyuwu.nestedscrolling E/wwwconsume:  offset:-1  consume:1
06-07 14:36:50.666 25570-25570/com.example.boboyuwu.nestedscrolling E/wwwconsume:  offset:-15  consume:15
06-07 14:36:50.682 25570-25570/com.example.boboyuwu.nestedscrolling E/wwwconsume:  offset:2  consume:-2
06-07 14:36:50.699 25570-25570/com.example.boboyuwu.nestedscrolling E/wwwconsume:  offset:-9  consume:9
``
忽略helper帮我处理后的符号问题可以看到值都是一模一样的因为我在parent消费完了child的滚动距离

ok现在在parent中我们去实现一些细节

@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
    Log.e("wwww","onStartNestedScroll");
    return child instanceof NestedScrollingChild && nestedScrollAxes==View.SCROLL_AXIS_VERTICAL;
}

在onStartNestedScroll中我简单的判断了一下直接返回true代表parent接受这次事件在child之前滚动,这个方法调用完毕后会接着调用onNestedScrollAccepted方法我们可以在开始滚动之前进行一些设置操作,
接着child的dispatchNestedPreScroll会调用parent的
onNestedPreScroll(View target, int dx, int dy, int[] consumed) 方法,在这里
设置了2个方法


    @Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
        //滚动之前消费
        boolean b = hasHide(dy);
        boolean b1 = hasShow(dy);
        Log.e("aaaaaaaaaaa","b:"+b+"  b1:"+b1);
        if(hasHide(dy) || hasShow(dy)){
            scrollBy(0,dy);
            consumed[1]=dy;
        }
    }

    private boolean hasShow(int dy) {

        if(dy<0){
                if(getScrollY()>0 && mChildAt2.getScrollY()==0){
                    Log.e("wwwhasShow", getScrollY()+ "   "  +mChildAt2.getScrollY()+"  "+mChildAt2.getScrollY());
                    return true;
                }

        }
        return false;
    }

    public boolean hasHide(int dy){

        if(dy>0){
                if(getScrollY()<mChildAt0MeasuredHeight && mChildAt2.getScrollY()==0){
                    Log.e("wwwhasHide", getScrollY()+"   "  +mChildAt2.getScrollY());
                    return true;
                }
        }
        return false;
    }

当接收到child传过来的dy时,根据scrollTo特性如果是-的认为是向下滚动,如果是+的则向上滚动,所以这里dy<0的时候我们判断向下滚动,这里还需要判断一下mChildAt2.getScrollY()==0因为少了这个判断的话,我们向上滚动一段距离,后再向下滚动这时应该child优先滚动,但是由于 getScrollY()始终>0那么这个方法永远返回true, 如果我们增加判断,由于我们之前child向上滚动过一段距离那么getScrollY()!=0的所以这个时候判断不成立滚动就交给我们child了。

同样当dy>0则向上滚动,同样逻辑增加一个判断.

我们滚动自己内容的时候是要限制一下滚动区域的,本来我期望向上滚动的时候滚动actionbar title高度parent就停止滚动,如果不限制它会一直向上滚,所以我们重写一下scrollTo方法因为scrollBy内部也是调用scorllTo的,

  @Override
    public void scrollTo(@Px int x, @Px int y) {
        Log.e("wwwwscrollTo","x:"+x+"   y:"+y+" mChildAt0MeasuredHeight"+mChildAt0MeasuredHeight);
        if(y<0){
            y=0;
        }
        if(y>mChildAt0MeasuredHeight && mChildAt0MeasuredHeight!=0){
            y=mChildAt0MeasuredHeight;
        }
        super.scrollTo(x, y);
    }

这里限制一下如果如果y<0说明向下已经滚动到actionbar title高度,我们限制y=0
如果向上滚动dy>actionbar title 的height高度那么则限制它就为height

同样child里面也应该限制下滚动范围原理跟parent一样就不贴代码了,好了,大概思路已经实现了,这里实现方式比较简单主要是熟悉一下NestedScrolling滚动机制,我们看一下代码运行效果,模拟器有点卡真机流畅很多

FN6BxE4Rwt.gif
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353

推荐阅读更多精彩内容