通过NestedScrolling实现RecyclerView拖拽回弹效果

简介:

  • Android5.0后Google为Android的滑动机制提供了NestedScrolling特性,可以使我们对嵌套View更简单事件拦截处理。本篇就对NestedScrolling实际应用做一些讲解。

</br>

NestedScrolling与传统事件分发机制作对比:
  • 比如某个外部的ViewGroup拦截掉内部View的事件,那本次事件会被ViewGroup消费并且不会向下传递,如果子view也想处理只能等待下一次手指再按下。
  • NestedScrolling可以很好的解决传统事件拦截的缺点,内部View在滚动的时候通过NestedScrollingChild将dx,dy交给NestedScrollingParent,NestedScrollingParent可对其进行部分消耗,剩余的部分还给内部View。

而NestedScrolling也是support.v4提供的支持类,在老版本也可以很好的兼容。

</br>

描述:

通过NestedScrolling可以实现哪些效果?
  • 菜单悬停效果
  • 下拉刷新、上拉加载更多
  • 拖拽回弹
实现嵌套分发主要通过以下两个接口:

NestedScrollingParent
NestedScrollingChild

要使用 NestedScrolling机制,父View 需要实现NestedScrollingParent接口,而子View需要实现NestedScrollingChild接口。RecyclerView已经默认实现了NestedScrollingChild,如果RecyclerView不满足你的业务需求,那需要去实现NestedScrollingChild,这里我只对NestedScrollingParent实现。

</br>

实现:

public class DZStickyNavLayouts extends LinearLayout implements NestedScrollingParent {

    private View mHeaderView;
    private View mFooterView;
    private static final int MAX_WIDTH = 500;
    private View mChildView;
    // 解决多点触控问题
    private boolean isRunAnim;
    private int mDrag = 2;

    public DZStickyNavLayouts(Context context, AttributeSet attrs) {
        super(context, attrs);
        setOrientation(LinearLayout.HORIZONTAL);
        mHeaderView = new View(context);
        mHeaderView.setBackgroundColor(0xffFFC125);
        mFooterView = new View(context);
        mFooterView.setBackgroundColor(0xffFF3E96);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mChildView = getChildAt(0);
        LayoutParams layoutParams = new LayoutParams(MAX_WIDTH, LayoutParams.MATCH_PARENT);
        addView(mHeaderView, 0, layoutParams);
        addView(mFooterView, getChildCount(), layoutParams);
        // 左移
        scrollBy(MAX_WIDTH, 0);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        ViewGroup.LayoutParams params = mChildView.getLayoutParams();
        params.width = getMeasuredWidth();
    }

    /**
     * 必须要复写 onStartNestedScroll后调用
     */
    @Override
    public void onNestedScrollAccepted(View child, View target, int axes) {

    }

    /**
     * 返回true代表处理本次事件
     */
    @Override
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
        if (target instanceof RecyclerView && !isRunAnim) {
            return true;
        }
        return false;
    }

    /**
     * 复位初始位置
     */
    @Override
    public void onStopNestedScroll(View target) {
        startAnimation(new ProgressAnimation());
    }

    /**
     * 回弹动画
     */
    private class ProgressAnimation extends Animation {
        // 预留
        private float startProgress = 0;
        private float endProgress = 1;

        private ProgressAnimation(){
            isRunAnim = true;
        }

        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            float progress = ((endProgress - startProgress) * interpolatedTime) + startProgress;
            scrollBy((int) ((MAX_WIDTH - getScrollX()) * progress), 0);
            if (progress == 1) {
                isRunAnim = false;
            }
        }

        @Override
        public void initialize(int width, int height, int parentWidth, int parentHeight) {
            super.initialize(width, height, parentWidth, parentHeight);
            setDuration(260);
            setInterpolator(new AccelerateInterpolator());
        }
    }

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

    }

    @Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
        // 如果在自定义ViewGroup之上还有父View交给我来处理
        getParent().requestDisallowInterceptTouchEvent(true);
        // dx>0 往左滑动 dx<0往右滑动
        boolean hiddenLeft = dx > 0 && getScrollX() < MAX_WIDTH && !ViewCompat.canScrollHorizontally(target, -1);
        boolean showLeft = dx < 0 && !ViewCompat.canScrollHorizontally(target, -1);
        boolean hiddenRight = dx < 0 && getScrollX() > MAX_WIDTH && !ViewCompat.canScrollHorizontally(target, 1);
        boolean showRight = dx > 0 && !ViewCompat.canScrollHorizontally(target, 1);
        if (hiddenLeft || showLeft || hiddenRight || showRight) {
            scrollBy(dx / mDrag, 0);
            consumed[0] = dx;
        }

        // 限制错位问题
        if (dx > 0 && getScrollX() > MAX_WIDTH && !ViewCompat.canScrollHorizontally(target, -1)) {
            scrollTo(MAX_WIDTH, 0);
        }
        if (dx < 0 && getScrollX() < MAX_WIDTH && !ViewCompat.canScrollHorizontally(target, 1)) {
            scrollTo(MAX_WIDTH, 0);
        }
    }

    @Override
    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
        return false;
    }


    @Override
    public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
        // 当RecyclerView在界面之内交给它自己惯性滑动
        if (getScrollX() == MAX_WIDTH) {
            return false;
        }
        return true;
    }

    @Override
    public int getNestedScrollAxes() {
        return 0;
    }

    /**
     * 限制滑动 移动x轴不能超出最大范围
     */
    @Override
    public void scrollTo(int x, int y) {
        if (x < 0) {
            x = 0;
        } else if (x > MAX_WIDTH * 2) {
            x = MAX_WIDTH * 2;
        }
        super.scrollTo(x, y);
    }
}

具体实现思路就是给RecyclerView添加头和尾,并控制滑动距离然后通过NestedScrolling进行分发,最后在手指离开界面开启还原动画。

文章参考:
http://blog.csdn.net/lmj623565791/article/details/52204039

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,569评论 25 707
  • Android 事件分发和滑动冲突都是开发中经常遇到的难点问题,遇到问题时可能会通过 Google 或者 Stac...
    任教主来也阅读 2,640评论 0 24
  • 今天为止,李老师的课都听好了。很微妙,很多似乎心里的一些角落里的感觉或者是私人的特点,听完了李老师的课才发现这一切...
    静静的叶子阅读 180评论 0 0
  • 在我们的实际开发过程中会碰到这样的问题,只有当任务1和任务2都完成之后才能执行任务3,这种情况下就需要线程组的出现...
    乐意先生阅读 2,476评论 0 6
  • 努力只为遇见更好的自己! “大山滴子孙哟,爱太阳哟……”儿时听这首歌就格外喜欢,出身平原的我也一直对山里生活充满向...
    沙沙细雨阅读 722评论 2 4