您好,登錄后才能下訂單哦!
本文介紹了Android RecyclerView懸浮效果,分享給大家,具體如下:
先看個效果
這是一個City列表,每個City都有所屬的Province,需要在滑動的時候,將對應的Province懸浮在頂部。懸浮頂部的Province需要根據列表的滑動而適當改變位置,實現“頂上去”的效果。
實現思路:
ItemDecoration
既然是利用RecyclerView.ItemDecoration實現的懸浮效果,那么有必要了解下它。
ItemDecoration字面意思:Item的裝飾。是的!是裝飾!不只是畫分割線。
其實ItemDecoration的功能非常強大,而我們平時只是用它來實現分割線的效果(至少我是這樣)。因此,可能很多同學認為ItemDecoration就是用來繪制分割線的。其實不然,ItemDecoration的功能遠不止是分割線的繪制。
先看下RecyclerView.ItemDecoration的源碼(部分):
public static abstract class ItemDecoration { ... public void onDraw(Canvas c, RecyclerView parent, State state) { onDraw(c, parent); } public void onDrawOver(Canvas c, RecyclerView parent, State state) { onDrawOver(c, parent); } public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) { getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(), parent); } }
里面是我們常用的三個方法:
RecyclerView 的背景、onDraw繪制的內容、Item、onDrawOver繪制的內容,各層級關系如下:
層級關系
繪制分割線
先看看一般的分割線繪制。
定義分割線高度
private int mHeight = 5; //分割線高度
通過getItemOffsets預留空間
@Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { super.getItemOffsets(outRect, view, parent, state); int position = parent.getChildAdapterPosition(view); if (position != 0) { //第一個item預留空間 outRect.top = mHeight; } }
然后在onDraw中繪制
@Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { super.onDraw(c, parent, state); final int left = parent.getLeft(); final int right = parent.getRight(); final int childCount = parent.getChildCount(); for (int i = 0; i < childCount; i++) { final View childView = parent.getChildAt(i); final int bottom = childView.getTop(); final int top = bottom - mHeight; c.drawRect(left, top, right, bottom, mPaint); } }
獲取當前RecyclerView的Item數量,遍歷每個Item。在對應的位置繪制一個高度為mHeight的矩形 ,從而實現分割線的效果。
(詳情代碼見底部鏈接)
打造懸浮效果
這是一個城市列表,根據省份分組,相同的城市只會顯示一個省份。滾動城市列表時,省份會懸浮在頂部。效果如下:
實現
由于需要懸浮效果,所以需要在onDrawOver中繪制分組。
定義一個interface,根據position通過接口方法getGroupName獲取當前省名(由Activity實現)
public interface GroupListener { String getGroupName(int position); }
創建StickyDecoration繼承RecyclerView.ItemDecoration
定義isFirstInGroup方法。根據前一個省份,判斷當前是否為新的省份
//判斷是不是組中的第一個位置 //根據前一個組名,判斷當前是否為新的組 private boolean isFirstInGroup(int pos) { if (pos == 0) { return true; } else { String prevGroupId = getGroupName(pos - 1); String groupId = getGroupName(pos); return !TextUtils.equals(prevGroupId, groupId); } }
通過position,對比上一個省份名稱,判斷當前省是否為第一個
重寫getItemOffsets方法,為懸浮欄預留空間
@Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { super.getItemOffsets(outRect, view, parent, state); int pos = parent.getChildAdapterPosition(view); String groupId = getGroupName(pos); if (groupId == null) return; //只有是同一組的第一個才顯示懸浮欄 if (pos == 0 || isFirstInGroup(pos)) { outRect.top = mGroupHeight; } }
只有第一個Item或者新的省份才為懸浮欄預留空間
重寫onDrawOver方法(重點)
@Override public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { super.onDrawOver(c, parent, state); final int itemCount = state.getItemCount(); final int childCount = parent.getChildCount(); final int left = parent.getLeft() + parent.getPaddingLeft(); final int right = parent.getRight() - parent.getPaddingRight(); String preGroupName; //標記上一個item對應的Group String currentGroupName = null; //當前item對應的Group for (int i = 0; i < childCount; i++) { View view = parent.getChildAt(i); int position = parent.getChildAdapterPosition(view); preGroupName = currentGroupName; currentGroupName = getGroupName(position); if (currentGroupName == null || TextUtils.equals(currentGroupName, preGroupName)) continue; int viewBottom = view.getBottom(); float top = Math.max(mGroupHeight, view.getTop());//top 決定當前頂部第一個懸浮Group的位置 if (position + 1 < itemCount) { //獲取下個GroupName String nextGroupName = getGroupName(position + 1); //下一組的第一個View接近頭部 if (!currentGroupName.equals(nextGroupName) && viewBottom < top) { top = viewBottom; } } //根據top繪制group c.drawRect(left, top - mGroupHeight, right, top, mGroutPaint); Paint.FontMetrics fm = mTextPaint.getFontMetrics(); //文字豎直居中顯示 float baseLine = top - (mGroupHeight - (fm.bottom - fm.top)) / 2 - fm.bottom; c.drawText(currentGroupName, left + mLeftMargin, baseLine, mTextPaint); } }
通過變量preGroupId和currentGroupId來保存當前分組名和上一個分組名。當前Item與上一個Item為同一個分組時,跳過該Item的繪制。
其中代碼:
float top = Math.max(mGroupHeight, view.getTop());
根據當前Item的位置確定繪制分組的位置。top將在mGroupHeight和view.getTop()中取最大值,也就是說top將不會小于mGroupHeight,這樣就能實現吸頂效果。
其中代碼:
if (!currentGroupName.equals(nextGroupName) && viewBottom < top) { top = viewBottom; }
當下個分組的頂部(當前Item的底部viewBottom可近似認為下個Item的頂部)距離RecyclerView頂部小于top時,偏移當前分組位置。實現下一組上滑時候,當前分組上移;上一組下滑的時候,當前分組下移。
最后計算baseLine,并繪制背景和文字。
到目前為止,一個帶有懸浮功能的列表就實現了。
(詳細代碼見底部鏈接)
進階
當我們利用ItemDecoration實現文字的懸浮的時候,是不是還可以搞點事情~ ~我有個大膽的想法
只有文字的懸浮怎么行!我還希望可以再來個icon?再來幾個TextView?來個自定義布局?那就來個自定義布局吧。
實現
實現的原理跟上面一樣,由于需要自定義布局,所以需要在接口中添加一個獲取View的方法。
定義PowerGroupListener
public interface PowerGroupListener { String getGroupName(int position); View getGroupView(int position); }
相比之前,多了個getGroupView方法,用來獲取View。
在onDrawOver中繪制
@Override public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { ... for (int i = 0; i < childCount; i++) { ... //根據position獲取View View groupView = mGroupListener.getGroupView(position); if (groupView == null) return; ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, mGroupHeight); groupView.setLayoutParams(layoutParams); groupView.setDrawingCacheEnabled(true); groupView.measure( View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); //指定高度、寬度的groupView groupView.layout(0, 0, right, mGroupHeight); groupView.buildDrawingCache(); Bitmap bitmap = groupView.getDrawingCache(); c.drawBitmap(bitmap, left, top - mGroupHeight, null); } }
在原來的基礎上做了點修改,通過接口的getGroupView方法獲取需要繪制的分組View,將得到的View繪制到指定位置。
效果:
(詳細代碼見底部鏈接)
源碼鏈接
已封裝成庫,歡迎來提Issues
repositories { jcenter()// If not already there } dependencies { compile 'com.gavin.com.library:stickyDecoration:1.0.2' }
詳細用法級源碼請看Github
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。