仿小紅書嵌套滑動效果

小紅書效果

分析

  • 思路一:能夠經過自定義view來實現相似 (比較麻煩,參考這裏
  • 思路二:藉助 NestScrolling 機制實現 (比較簡單,關於嵌套滑動概述參考這裏

下面就用思路二來分析一下如何實現php

滑動效果分析

  • recyclerView 上滑時,若是手指沒有滑過 recyclerView 的頂部,那麼 recyclerView 本身消費滑動事件;不然開始嵌套滑動(總體上滑)。當手指擡起時候若是滑動距離大於 50dp ,經過慣性滑動滑到頂部,不然滑回到初始位置
  • recyclerView 下滑時,若是recyclerView的第一個 item 不是徹底可見,那麼 recyclerView 本身消費滑動事件;不然開始嵌套滑動(總體下滑)。當手指擡起時候若是滑動距離大於 50dp ,經過慣性滑動滑到底部,不然滑回到初始位置

實現

NestedRecyclerView(NestScrollChild)

public class NestedRecyclerView extends RecyclerView {


    private float topPadding = Utils.dp2Px(50);
    private float scrollSlop = Utils.dp2Px(50); //滑動超過這個距離鬆手後自動自動滑動到頂部
    private float downY;
    private float moveY;
    private float deltaMoveY;
    private float deltaDownMoveY;
    private float lastMoveY;
    public OverScroller mScroller;
    private int firstCompletelyVisiblePosition;

    public NestedRecyclerView(@NonNull Context context) {
        this(context, null);
    }

    public NestedRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    private void init(Context context) {
        mScroller = new OverScroller(context);
    }


    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                Log.d("NestedRecyclerView", "ACTION_DOWN");
                downY = event.getRawY();
                lastMoveY = event.getRawY();
                break;
        }
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                Log.d("NestedRecyclerView", "ACTION_DOWN");
                downY = event.getRawY();
                break;
            case MotionEvent.ACTION_MOVE:
                moveY = event.getRawY();

                deltaMoveY = moveY - lastMoveY;
                deltaDownMoveY = moveY - downY;

                //向上滑動 手指滑動到 recyclerview 頂部或者以上 觸發嵌套滑動
                //event.getY() <= 0 且 getTop() - ((View) getParent()).getScrollY() > topPadding
                if (deltaMoveY < 0) {
                    if (event.getY() <= 0) {

                        if (getTop() - ((View) getParent()).getScrollY() > topPadding) {
                            setNestedScrollingEnabled(true);
                            startNestedScroll(SCROLL_AXIS_VERTICAL);
                        } else {
                            setNestedScrollingEnabled(false);
                        }
                    } else {
                        setNestedScrollingEnabled(false);
                    }
                } else {
                    //向下滑動
                    //先向下滑動 recyclerview,等待 recyclerview 所有展示後觸發嵌套滑動
                    GridLayoutManager layoutManager = ((GridLayoutManager) getLayoutManager());
                    firstCompletelyVisiblePosition = layoutManager.findFirstCompletelyVisibleItemPosition();
                    if (firstCompletelyVisiblePosition == 0 && ((View) getParent()).getScrollY() > 0) {
                        setNestedScrollingEnabled(true);
                        startNestedScroll(SCROLL_AXIS_VERTICAL);
                    } else {
                        setNestedScrollingEnabled(false);
                    }
                }

                lastMoveY = moveY;
                break;
            case MotionEvent.ACTION_UP:
                int parentScrollY = ((View) getParent()).getScrollY();
                int startY = parentScrollY;
                int dy;
                if (deltaDownMoveY < 0) {
                    //向上滑動切滑動距離超過 scrollSlop 滑動到頂部
                    //向上滑動切滑動距離不超過 scrollSlop 滑回到底部

                    if (parentScrollY >= scrollSlop) {
                        dy = (int) (getTop() - ((View) getParent()).getScrollY() - topPadding);
                    } else {
                        dy = -parentScrollY;
                    }
                } else {
                    //向下滑動切滑動距離超過 scrollSlop 滑動到底部
                    //向下滑動切滑動距離不超過 scrollSlop 滑回到頂部
                    if (getTop() - ((View) getParent()).getScrollY() > scrollSlop + topPadding) {
                        dy = -parentScrollY;
                    } else {
                        dy = (int) (getTop() - ((View) getParent()).getScrollY() - topPadding);
                    }
                }
                mScroller.startScroll(0, startY, 0
                        , dy, 300);
                ((View) getParent()).invalidate();
                break;
        }
        return super.onTouchEvent(event);
    }

    public OverScroller getScroller() {
        return mScroller;
    }
}

複製代碼

因爲RecyclerView默認實現了NestedScrollingChild2,不須要咱們本身實現NestedScrollingChild。java

重寫RecyclerView的onTouchEvent方法,在ACTION_MOVE裏面合適的時候開啓/禁止彈性滑動,具體邏輯參考前面流程圖和代碼註釋。android

在ACTION_UP的時候經過 Scroller 實現彈性滑動的效果,關鍵是計算出 dy,代碼註釋裏面寫的很清楚。app

關於 Scroller 的使用參考這裏ide

LinearLayoutNestScrollParent(NestScrollParent)

public class LinearLayoutNestScrollParent extends LinearLayout implements NestedScrollingParent {
    private NestedScrollingParentHelper mParentHelper;
    private View imgView;
    private NestedRecyclerView recyclerview;

    public LinearLayoutNestScrollParent(Context context) {
        this(context, null);
    }

    public LinearLayoutNestScrollParent(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        mParentHelper = new NestedScrollingParentHelper(this);
    }


    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        imgView = findViewById(R.id.img);
        recyclerview = findViewById(R.id.rv_photos);

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int imgHeight = imgView.getMeasuredHeight();
        //從新測量高度
        int newHeightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight() + imgHeight, MeasureSpec.EXACTLY);
        super.onMeasure(widthMeasureSpec, newHeightMeasureSpec);
    }


    @Override
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
        if (target instanceof RecyclerView) {
            return true;
        }
        return false;
    }


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

    @Override
    public void onStopNestedScroll(View target) {
        mParentHelper.onStopNestedScroll(target);
    }

    //先於 child 滾動
    @Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
        scrollBy(0, dy);//滾動
        consumed[1] = dy;//告訴child我消費了多少
    }

    //後於child滾動
    @Override
    public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {

    }

    //返回值:是否消費了fling
    @Override
    public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
        return false;
    }

    //返回值:是否消費了fling
    @Override
    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
        return false;
    }

    @Override
    public int getNestedScrollAxes() {
        return mParentHelper.getNestedScrollAxes();
    }


    @Override
    public void computeScroll() {
        OverScroller scroller = recyclerview.getScroller();
        if (scroller.computeScrollOffset()) {
            scrollTo(scroller.getCurrX(), scroller.getCurrY());
            invalidate();
        }
    }
}

複製代碼

實現NestedScrollingParent接口,只要實現onNestedScroll這個方法,所有消費 NestedScrollingChild傳來的 dy 。佈局

另外須要注意的onMeasure方法,從新測量高度,不然上滑時候RecyclerView下面部分會出現空白。this

佈局文件

<?xml version="1.0" encoding="utf-8"?>
<cn.feng.xhsimageview.views.LinearLayoutNestScrollParent 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" android:orientation="vertical">

    <ImageView android:id="@+id/img" android:layout_width="match_parent" android:layout_height="wrap_content"/>

    <cn.feng.xhsimageview.views.NestedRecyclerView android:id="@+id/rv_photos" android:layout_width="match_parent" android:layout_height="0dp" android:layout_marginTop="2dp" android:layout_weight="1" />

</cn.feng.xhsimageview.views.LinearLayoutNestScrollParent>
複製代碼
相關文章
相關標籤/搜索