帶可摺疊的headView的RecyclerView
阿新 • • 發佈:2018-12-14
摘要:整體思路就是通過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; } }