1. 程式人生 > >帶可摺疊的headView的RecyclerView

帶可摺疊的headView的RecyclerView

摘要:整體思路就是通過CoordinatorLayout來實現頭部view和RecyclerView的一個聯動效果,其中重要的一部分是利用了佈局的behavior屬性來控制一系列的聯動效果,先來了gif圖效果:

Activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    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:background="#0f3"
    tools:context="com.rivers.headFoldingRecyclerView.ui.MainActivity">

    <RelativeLayout
        android:id="@+id/card"
        android:layout_width="match_parent"
        android:layout_height="@dimen/main_topview_max_height"
        android:layout_marginEnd="10dp"
        android:layout_marginStart="10dp"
        android:minHeight="@dimen/main_topview_min_height"
        app:layout_behavior="com.rivers.headFoldingRecyclerView.view.HeadViewBehavior">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_alignParentBottom="true"
            android:layout_marginTop="24dp"
            android:background="@drawable/card_white_bg"
            android:orientation="vertical"
            android:paddingStart="18dp">
            <!-- 標題 -->
            <TextView
                android:id="@+id/withdraw_tv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="36dp"
                android:fontFamily="sans-serif"
                android:maxWidth="160dp"
                android:text="檢視刪除資訊"
                android:textColor="#00ae63"
                android:textSize="18sp"
                android:textStyle="bold"/>

            <!-- 許可權狀態 -->
            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="9dp"
                android:gravity="center_vertical"
                android:orientation="horizontal">

                <TextView
                    android:id="@+id/withdraw_status_text"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginStart="4dp"
                    android:text="推薦使用"
                    android:textColor="#fe4728"
                    android:textSize="12sp"/>
            </LinearLayout>

        </LinearLayout>
    </RelativeLayout>

    <RelativeLayout
        android:id="@+id/ic_top_card_right"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginEnd="10dp"
        android:layout_marginStart="10dp">

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="139dp"
            android:layout_alignParentEnd="true"
            android:layout_alignParentTop="true"
            android:layout_marginEnd="23dp"
            android:layout_marginTop="2dp"
            android:scaleType="centerInside"
            android:src="@drawable/withdraw_area_icon"/>
    </RelativeLayout>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginEnd="10dp"
        android:layout_marginStart="10dp"
        android:background="@drawable/card_white_bg"
        android:scrollbarFadeDuration="500"
        android:scrollbars="vertical"
        app:layout_behavior="com.rivers.headFoldingRecyclerView.view.RecycleBehavior"/>
</android.support.design.widget.CoordinatorLayout>

HeadViewBehavior.java:這個類主要是控制headview的縮放效果

public class HeadViewBehavior extends CoordinatorLayout.Behavior<RelativeLayout> {

    private String TAG = this.getClass().getSimpleName();
    private int maxHeight;
    private int minHeight;
    private final OverScroller mOverScroller;
    private boolean isAbleFold = true;  //是否可以摺疊
    private FlingRunnable mFlingRunnable;
    private int duration = 400;
    private OnUnfoldFinishLister mOnUnfoldFinishLister;
    private WeakReference<CoordinatorLayout> coordinatorLayoutWeakReference;
    private WeakReference<RelativeLayout> cardViewWeakReference;
    private UnfoldRunnable unfoldRunnable;
    private int maxWidth;
    private int minWidth;

    public HeadViewBehavior(Context context, AttributeSet attr) {
        this.minHeight = (int) context.getResources().getDimension(R.dimen.main_topview_min_height);
        this.maxHeight = (int) context.getResources().getDimension(R.dimen.main_topview_max_height);
        mOverScroller = new OverScroller(context);
    }


    @Override
    public boolean onLayoutChild(CoordinatorLayout parent, RelativeLayout child, int layoutDirection) {
        Log.d(TAG, "onLayoutChild");
        if (coordinatorLayoutWeakReference == null) {
            coordinatorLayoutWeakReference = new WeakReference<CoordinatorLayout>(parent);
        }
        if (cardViewWeakReference == null) {
            cardViewWeakReference = new WeakReference<RelativeLayout>(child);
        }
        return super.onLayoutChild(parent, child, layoutDirection);
    }

    @Override
    public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull RelativeLayout child, @NonNull View directTargetChild, @NonNull View target, int axes, int type) {
        if (target instanceof RecyclerView) {
            LinearLayoutManager manager = (LinearLayoutManager) ((RecyclerView) target).getLayoutManager();
            int position = manager.findFirstCompletelyVisibleItemPosition();
            if (!mOverScroller.isFinished()) {
                mOverScroller.forceFinished(true);
            }
            return position == 0 && isAbleFold;
        }
        return false;
    }

    @Override
    public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull RelativeLayout child, @NonNull View target, int type) {
        super.onStopNestedScroll(coordinatorLayout, child, target, type);
        if (!mOverScroller.isFinished()) {
            mOverScroller.forceFinished(true);
        }
        CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
        snapToChildIfNeeded(coordinatorLayout, child, params.height, params);
    }

    @Override
    public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull RelativeLayout child, @NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) {
        if (!isAbleFold) {
            return;
        }
        Log.d(TAG, "onNestedPreScroll");
        if (!mOverScroller.isFinished()) {
            mOverScroller.forceFinished(true);
        }

        if (target instanceof RecyclerView) {
            RecyclerView recyclerView = (RecyclerView) target;
            LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
            int firstCompletelyVisibleItemPosition = linearLayoutManager.findFirstCompletelyVisibleItemPosition();
            CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
            int oldheight = layoutParams.height;
            int finalHeight = oldheight - dy;
            if (dy > 0 && oldheight >= minHeight /*&& firstCompletelyVisibleItemPosition == 0*/) {//up
                finalHeight = finalHeight > minHeight ? finalHeight : minHeight;
                refresh(coordinatorLayout, child, layoutParams, finalHeight);
                consumed[1] = /*dy*/oldheight - finalHeight;
            } else if (dy < 0 && oldheight <= maxHeight && firstCompletelyVisibleItemPosition == 0) {//down
                finalHeight = finalHeight > maxHeight ? maxHeight : finalHeight;
                refresh(coordinatorLayout, child, layoutParams, finalHeight);
                consumed[1] = dy/*oldheight - finalHeight*/;
            } else {
//                CoordinatorLayout.LayoutParams layoutParams1 = (CoordinatorLayout.LayoutParams) recyclerView.getLayoutParams();
//                CoordinatorLayout.Behavior behavior = layoutParams1.getBehavior();
//                behavior.onNestedPreScroll(coordinatorLayout, recyclerView, recyclerView, dx, dy, consumed, type);
//                refresh(child, layoutParams, finalHeight);
            }
        }
    }

    @Override
    public boolean onNestedFling(@NonNull CoordinatorLayout coordinatorLayout, @NonNull RelativeLayout child, @NonNull View target, float velocityX, float velocityY, boolean consumed) {
        return false;
    }

    @Override
    public boolean onNestedPreFling(@NonNull CoordinatorLayout coordinatorLayout, @NonNull RelativeLayout child, @NonNull View target, float velocityX, float velocityY) {
        Log.d(TAG, "onNestedPreScroll");
        if (!isAbleFold) {
            return false;
        }
        RecyclerView target1 = (RecyclerView) target;
        LinearLayoutManager layoutManager = (LinearLayoutManager) target1.getLayoutManager();
        if (layoutManager.findFirstCompletelyVisibleItemPosition() != 0) {
            return false;
        }
        if (!mOverScroller.isFinished()) {
            mOverScroller.forceFinished(true);
        }
        CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
        if (mFlingRunnable == null) {
            mFlingRunnable = new FlingRunnable(coordinatorLayout, child, layoutParams);
        }
        int height = layoutParams.height;
        if (velocityY > 0 && height != minHeight) {
            mOverScroller.startScroll(0, height, 0, height - minHeight, duration);
            ViewCompat.postOnAnimation(child, mFlingRunnable);
            return true;
        } else if (velocityY < 0 && height != maxHeight) {
            mOverScroller.startScroll(0, height, 0, maxHeight - height, duration);
            ViewCompat.postOnAnimation(child, mFlingRunnable);
            return true;
        }
        return false;
    }

    private void snapToChildIfNeeded(CoordinatorLayout coordinatorLayout, RelativeLayout child, int finalHeight,
                                     CoordinatorLayout.LayoutParams layoutParams) {
        if (!isAbleFold) {
            return;
        }
        if (!mOverScroller.isFinished()) {
            mOverScroller.forceFinished(true);
        }
        if (finalHeight > (maxHeight - minHeight) / 2 + minHeight) {//fold
            mOverScroller.startScroll(0, finalHeight, 0, maxHeight - finalHeight, duration);
        } else {//unfold
            mOverScroller.startScroll(0, finalHeight, 0, minHeight - finalHeight, duration);
        }
        if (mFlingRunnable == null) {
            mFlingRunnable = new FlingRunnable(coordinatorLayout, child, layoutParams);
        }
        ViewCompat.postOnAnimation(child, mFlingRunnable);

    }

    private boolean isDependsOn(View dependency) {
        return dependency != null && dependency.getId() == R.id.recyclerview;
    }

    private class FlingRunnable implements Runnable {
        private final CoordinatorLayout mParent;
        private final RelativeLayout mCardView;
        CoordinatorLayout.LayoutParams params;

        FlingRunnable(CoordinatorLayout parent, RelativeLayout layout, CoordinatorLayout.LayoutParams params) {
            mParent = parent;
            mCardView = layout;
            this.params = params;
        }

        @Override
        public void run() {
            if (mCardView != null && mOverScroller != null) {
                if (mOverScroller.computeScrollOffset()) {
                    int abs = Math.abs(mOverScroller.getCurrY());
                    refresh(mParent,mCardView, params, abs);
                    ViewCompat.postOnAnimation(mCardView, this);
                } else {
//                    onFlingFinished(mParent, mCardView, params);
                }
            }
        }
    }

    private void refresh(CoordinatorLayout parent, @NonNull RelativeLayout child, CoordinatorLayout.LayoutParams layoutParams, int finalHeight) {
        layoutParams.height = finalHeight;
        child.setLayoutParams(layoutParams);
        float factor = (float) finalHeight / maxHeight;
        float alpha = 0.5f * (1 + factor);
        float scale = 0.8f + 0.2f * factor;
        child.setAlpha(alpha);
        child.setScaleX(scale);
        child.setScaleY(scale);

        /*頂部右圖固定方案*/
        RelativeLayout rightImg = parent.findViewById(R.id.ic_top_card_right);
        alpha = 0.9f + 0.1f * factor;
        rightImg.setAlpha(alpha);
        rightImg.setScaleY(scale);
        rightImg.setScaleX(scale);
    }

    public void setAbleFold(boolean ableFold) {
        isAbleFold = ableFold;
    }

    public void unfold() {
        if (coordinatorLayoutWeakReference != null && coordinatorLayoutWeakReference.get() != null
                && cardViewWeakReference != null && cardViewWeakReference.get() != null) {
            CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams)
                    cardViewWeakReference.get().getLayoutParams();
            int height = layoutParams.height;
            //add--start--by--kezq--for--ncd#14558--2018/1/5
            if (height >= maxHeight) {
                if (mOnUnfoldFinishLister != null) {
                    mOnUnfoldFinishLister.onUnfoldFinished();
                }
                return;
            }//add--end--by--kezq--for--ncd#14558--2018/1/5
            mOverScroller.startScroll(0, height, 0, maxHeight - height, 200);
            unfoldRunnable = new UnfoldRunnable(coordinatorLayoutWeakReference.get(), cardViewWeakReference.get(), layoutParams);
            ViewCompat.postOnAnimation(cardViewWeakReference.get(), unfoldRunnable);
        }
    }

    private class UnfoldRunnable implements Runnable {
        private final CoordinatorLayout mParent;
        private final RelativeLayout mCardView;
        CoordinatorLayout.LayoutParams params;

        UnfoldRunnable(CoordinatorLayout parent, RelativeLayout layout, CoordinatorLayout.LayoutParams params) {
            mParent = parent;
            mCardView = layout;
            this.params = params;
        }

        @Override
        public void run() {
            if (mCardView != null && mOverScroller != null) {
                if (mOverScroller.computeScrollOffset()) {
                    refresh(mParent,mCardView, params, Math.abs(mOverScroller.getCurrY()));
                    ViewCompat.postOnAnimation(mCardView, this);
                } else {
                    if (mOnUnfoldFinishLister != null) {
                        mOnUnfoldFinishLister.onUnfoldFinished();
                    }
                }
            }
        }
    }

    public void onDestroy() {
        if (!mOverScroller.isFinished()) {
            mOverScroller.forceFinished(true);
        }
        if (cardViewWeakReference == null) {
            return;
        }
        final RelativeLayout mCardView = cardViewWeakReference.get();
        if (mCardView != null) {
            if (unfoldRunnable != null) {
                mCardView.removeCallbacks(unfoldRunnable);
            }
            if (mFlingRunnable != null) {
                mCardView.removeCallbacks(mFlingRunnable);
            }
        }
    }

    public interface OnUnfoldFinishLister {
        void onUnfoldFinished();
    }
}

RecycleBehavior.java:這個類是用來控制recyclerview依據headview的縮放高度來聯動自己的Y軸的座標也就是在佈局中的高度位置

public class RecycleBehavior extends CoordinatorLayout.Behavior<RecyclerView> {
    private int gapMaxHeight;
    private int maxHeight;
    private int minHeight;
    private String TAG=this.getClass().getSimpleName();

    public RecycleBehavior(Context context, AttributeSet attr) {
        this.minHeight= (int) context.getResources().getDimension(R.dimen.main_topview_min_height);
        this.maxHeight= (int) context.getResources().getDimension(R.dimen.main_topview_max_height);
        this.gapMaxHeight = context.getResources().getDimensionPixelOffset(R.dimen.divider_header_gap);
    }

    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, RecyclerView child, View dependency) {
        Log.d(TAG,"layoutDependsOn");
        return isDependsOn(dependency);
    }

    private boolean isDependsOn(View dependency) {
        return dependency != null && dependency.getId() == R.id.card;
    }

    private View findFirstDependency(List<View> views) {
        for (int i = 0, z = views.size(); i < z; i++) {
            View view = views.get(i);
            if (isDependsOn(view))
                return view;
        }
        return null;
    }

    @Override
    public boolean onMeasureChild(CoordinatorLayout parent, RecyclerView child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
        Log.d(TAG,"onMeasureChild");
        List<View> dependencies = parent.getDependencies(child);
        View dependency = findFirstDependency(dependencies);
        if (dependency!=null){
            int mode = View.MeasureSpec.getMode(parentHeightMeasureSpec);
            int size = View.MeasureSpec.getSize(parentHeightMeasureSpec);
            CoordinatorLayout.LayoutParams layoutParams= (CoordinatorLayout.LayoutParams) dependency.getLayoutParams();
            int i=size-minHeight-layoutParams.topMargin+gapMaxHeight;
            int measureHeight = View.MeasureSpec.makeMeasureSpec(i, mode);
            parent.onMeasureChild(child,parentWidthMeasureSpec,widthUsed,measureHeight,heightUsed);
            return true;
        }
        return false;
    }

    @Override
    public boolean onLayoutChild(CoordinatorLayout parent, RecyclerView child, int layoutDirection) {
        return super.onLayoutChild(parent, child, layoutDirection);
    }

    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, RecyclerView child, View dependency) {
        Log.d(TAG,"onDependentViewChanged");
        CoordinatorLayout.LayoutParams params= (CoordinatorLayout.LayoutParams) dependency.getLayoutParams();
        float factor=(float)params.height/maxHeight;
        int gapHeight = (int) (factor* gapMaxHeight); //中間縫隙高度
        int y= (int) (factor*params.height+params.topMargin+gapHeight);  //child距離頂部的高度
        child.setY(y);
        return true;
    }

    @Override
    public boolean onStartNestedScroll(@NonNull CoordinatorLayout parent, @NonNull RecyclerView child, @NonNull View directTargetChild, @NonNull View target, int axes, int type) {
        Log.d(TAG,"onStartNestedScroll");
        List<View> dependencies = parent.getDependencies(child);
        View dependency = findFirstDependency(dependencies);
        if (isDependsOn(target)) {
            int dependencyHeight = target.getHeight();
            LinearLayoutManager manager = (LinearLayoutManager) child.getLayoutManager();
            int position = manager.findFirstCompletelyVisibleItemPosition();
            if (dependencyHeight == minHeight && position != 0) {
                return true;
            }
        }
        return false;
    }
}