RecyclerView新增摺疊式headView
阿新 • • 發佈:2018-10-31
摘要:整體思路就是通過CoordinatorLayout來實現頭部view和RecyclerView聯動,其中重要的一部分是利用了佈局的behavior屬性來控制頭部view摺疊效果,先來個gif圖效果:
github完整專案地址:https://github.com/Bellriver/HeadFoldingRecyclerView
下面來講解下部分類的核心程式碼理解:
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>
理解:根據CoordinatorLayout的屬性要求,要求新增Behavior屬性的子佈局必須是在CorrdinatorLayout的第一級子佈局下。
HeadViewBehavior.java:這個類主要是控制headview的縮放效果。
程式碼1:這部分主要是實現當CoordinatorLayout滑動停止時HeadView自動實現立體層疊式縮放效果。
@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); } 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 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); }
程式碼2:這個方法主要是實現在CoordinatorLayout滾動過程中實現HeadView的收縮變化效果,dy代表CoordinatorLlayout上下滾動的距離,dy<0這是向下滾動,dy>0則是向上滾動。
@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); } } }
RecycleBehavior.java:這個類是用來控制recyclerview依據headview的縮放高度來聯動自己的Y軸的座標也就是在佈局中的高度位置。
程式碼1:這個方法是根據headView的高度計算RecyclerView的高度。
@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;
}
程式碼2:這個方法主要是根據HeadView的高度設定RecyclerView的y座標也就是頂部位置。
@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;
}