android recycleView 簡單使用二---分割線
轉自:https://www.jianshu.com/p/b46a4ff7c10a
RecyclerView沒有像之前ListView提供divider屬性,而是提供了方法
recyclerView.addItemDecoration()
其中ItemDecoration需要我們自己去定制重寫,一開始可能有人會覺得麻煩不好用,最後你會發現這種可插拔設計不僅好用,而且功能強大。
ItemDecoration類主要是三個方法:
public void onDraw(Canvas c, RecyclerView parent, State state) public void onDrawOver(Canvas c, RecyclerView parent, State state)public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state)
官方源碼雖然都寫的很清楚,但還不少小夥伴不知道怎麽理解,怎麽用或用哪個方法,下面我畫個簡單的圖來幫你們理解一下。
首先我們假設綠色區域代表的是我們的內容,紅色區域代表我們自己繪制的裝飾,可以看到:
圖1:代表了getItemOffsets(),可以實現類似padding的效果
圖2:代表了onDraw(),可以實現類似繪制背景的效果,內容在上面
圖3:代表了onDrawOver(),可以繪制在內容的上面,覆蓋內容
註意上面是我個人從應用角度的看法,事實上實現上面的效果可能三個方法每個方法都可以實現。只不過這種方法更好理解。
下面是我們沒有添加任何ItemDecoration的界面
padding
從前面的圖可以看到實現這個效果,需要重寫getItemOffsets方法。
//分割線 public class DividerItemDecoration extends RecyclerView.ItemDecoration { @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { super.getItemOffsets(outRect, view, parent, state);//outRect.bottom、left,right,top設置為int值 outRect.bottom =100 ; outRect.left =50 ; } }
分割線
分割線在app中是經常用到的,用ItemDecoration怎麽實現呢,其實上面padding改成1dp就實現了分割線的效果,但是分割線的顏色只能是背景灰色,所以不能用這種方法。
要實現分割線效果需要 getItemOffsets()和 onDraw()2個方法,首先用 getItemOffsets給item下方空出一定高度的空間(例子中是1dp),然後用onDraw繪制這個空間
//分割線 public class DividerItemDecoration extends RecyclerView.ItemDecoration { private Context mContext; //上下文 private int dividerHeight; //分割線的高度 private Paint mPaint; //畫筆 //自定義構造方法,在構造方法中初始化一些變量 public DividerItemDecoration(Context context){ mContext = context; dividerHeight = 2; //context.getResources().getDimensionPixelSize(R.dimen.divider_height); mPaint = new Paint(); mPaint.setColor(context.getResources().getColor(R.color.colorAccent)); //設置顏色 } //設置padding @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { super.getItemOffsets(outRect, view, parent, state); //outRect.bottom、left,right,top設置為int值,設置每一項的padding outRect.bottom =dividerHeight ; } //畫圖 @Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { super.onDraw(c, parent, state); //獲取item個數 int childCount = parent.getChildCount(); //左右是固定的 int left = parent.getPaddingLeft(); int right = parent.getWidth() - parent.getPaddingRight() ; //高度 for (int i = 0; i < childCount - 1; i++) { View view = parent.getChildAt(i); float top = view.getBottom(); float bottom = view.getBottom() + dividerHeight; //畫圖 c.drawRect(left, top, right, bottom, mPaint); } } }
效果如下:
標簽
現在很多電商app會給商品加上一個標簽,比如“推薦”,“熱賣”,“秒殺”等等,可以看到這些標簽都是覆蓋在內容之上的,這就可以用onDrawOver()來實現,我們這裏簡單實現一個有趣的標簽
public class LeftAndRightTagDecoration extends RecyclerView.ItemDecoration { private int tagWidth; //標簽的寬度 private Paint leftPaint; //左邊的畫筆 private Paint rightPaint; //右邊的畫筆 public LeftAndRightTagDecoration(Context context){ leftPaint =new Paint(); leftPaint.setColor(context.getResources().getColor(R.color.colorPrimary)); rightPaint = new Paint(); rightPaint.setColor(context.getResources().getColor(R.color.colorOrange));
//上面是畫筆的初始化,和設置顏色,下面是標簽寬度的獲取 tagWidth = context.getResources().getDimensionPixelSize(R.dimen.tag_width); } //繪制標簽 @Override public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { super.onDrawOver(c, parent, state); int childCount = parent.getChildCount(); for(int i=0;i<childCount;i++){ View child = parent.getChildAt(i); int pos = parent.getChildAdapterPosition(child); boolean isLeft = pos%2==0; if(isLeft){ float left = child.getLeft(); float right = left+tagWidth; float top = child.getTop(); float bottom =child.getBottom(); c.drawRect(left,top,right,bottom,leftPaint); }else{ float right = child.getRight(); float left = right-tagWidth; float top = child.getTop(); float bottom =child.getBottom(); c.drawRect(left,top,right,bottom,rightPaint); } } } }
效果如下
組合
不要忘記的是ItemDecoration是可以疊加的
//ItemDecoration分割線 mRecycleView.addItemDecoration(new DividerItemDecoration(this)); //標簽 mRecycleView.addItemDecoration(new LeftAndRightTagDecoration(this));
效果如下:
section
這個是什麽呢,先看下我們實現的效果
一看這個就很熟悉吧,手機上面的通訊錄聯系人,知乎日報都是這樣效果,可以叫分組,也可以叫section分塊 先不管它叫什麽。
這個怎麽實現呢? 其實和實現分割線是一樣的道理 ,只是不是所有的item都需要分割線,只有同組的第一個需要。
我們首先定義一個接口給activity進行回調用來進行數據分組和獲取首字母
public interface DecorationCallback { long getGroupId(int position); String getGroupFirstLine(int position); }
然後再來看我們的ItemDecoration
public class SectionDecoration extends RecyclerView.ItemDecoration { private DecorationCallback callback; private TextPaint textPaint; //文字畫筆 private Paint paint; //普通畫筆 private int topGap; //padding_top private Paint.FontMetrics fontMetrics; //自定義構造函數 public SectionDecoration(Context context, DecorationCallback decorationCallback) { Resources res = context.getResources(); this.callback = decorationCallback; //畫筆 paint = new Paint(); paint.setColor(res.getColor(R.color.colorPrimary)); //文字畫筆,樣式設置 textPaint = new TextPaint(); textPaint.setTypeface(Typeface.DEFAULT_BOLD); //加粗 textPaint.setAntiAlias(true); textPaint.setTextSize(70); //字體大小 textPaint.setColor(Color.BLACK); //字體顏色 textPaint.getFontMetrics(fontMetrics); //字體的材質 textPaint.setTextAlign(Paint.Align.LEFT); //字體的向左對齊 fontMetrics = new Paint.FontMetrics(); topGap = res.getDimensionPixelSize(R.dimen.sectioned_top);//32dp Padding——top的值 } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { super.getItemOffsets(outRect, view, parent, state); //視圖的位置 int pos = parent.getChildAdapterPosition(view); long groupId = callback.getGroupId(pos); if (groupId < 0) return; if (pos == 0 || isFirstInGroup(pos)) {//同組的第一個才添加padding outRect.top = topGap; } else { outRect.top = 0; } } @Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { super.onDraw(c, parent, state); int left = parent.getPaddingLeft(); int right = parent.getWidth() - parent.getPaddingRight(); int childCount = parent.getChildCount(); for (int i = 0; i < childCount; i++) { View view = parent.getChildAt(i); int position = parent.getChildAdapterPosition(view); long groupId = callback.getGroupId(position); if (groupId < 0) return; //分組的文本 String textLine = callback.getGroupFirstLine(position).toUpperCase(); if (position == 0 || isFirstInGroup(position)) { float top = view.getTop() - topGap; float bottom = view.getTop(); c.drawRect(left, top, right, bottom, paint);//繪制矩形 c.drawText(textLine, left, bottom, textPaint);//繪制文本 } } } //是否是一個組 private boolean isFirstInGroup(int pos) { if (pos == 0) { return true; } else { long prevGroupId = callback.getGroupId(pos - 1); long groupId = callback.getGroupId(pos); return prevGroupId != groupId; } } //接口實現分組的依據 public interface DecorationCallback { long getGroupId(int position); String getGroupFirstLine(int position); } }
可以看到和divider實現一樣,都是重寫getItemOffsets()和onDraw()2個方法,不同的是根據數據做了處理。
在Activity中使用
mRecycleView.addItemDecoration(new SectionDecoration(this, new SectionDecoration.DecorationCallback() {
@Override
public long getGroupId(int position) {
return Character.toUpperCase(homeAdapter.mTitles[position].charAt(0));
}
@Override
public String getGroupFirstLine(int position) {
return homeAdapter.mTitles[position].substring(0, 1).toUpperCase();
}
}));
幹凈舒服,不少github類似的庫都是去adapter進行處理 侵入性太強 或許ItemDecoration是個更好的選擇,可插拔,可替換。
到這裏細心的人就會發現了,header不會動啊,我手機上的通訊錄可是會隨的滑動而變動呢,這個可以實現麽?
StickyHeader
這個東西怎麽叫我也不知道啊 粘性頭部?英文也有叫 pinned section 取名字真是個麻煩事。
先看下我們簡單實現的效果
stickyheader首先一看到圖,我們就應該想到header不動肯定是要繪制item內容之上的,需要重寫onDrawOver()方法,其他地方和section實現一樣。
public class PinnedSectionDecoration extends RecyclerView.ItemDecoration { private static final String TAG = "PinnedSectionDecoration"; private DecorationCallback callback; private TextPaint textPaint; private Paint paint; private int topGap; private Paint.FontMetrics fontMetrics; public PinnedSectionDecoration(Context context, DecorationCallback decorationCallback) { Resources res = context.getResources(); this.callback = decorationCallback; paint = new Paint(); paint.setColor(res.getColor(R.color.colorAccent)); textPaint = new TextPaint(); textPaint.setTypeface(Typeface.DEFAULT_BOLD); textPaint.setAntiAlias(true); textPaint.setTextSize(80); textPaint.setColor(Color.BLACK); textPaint.getFontMetrics(fontMetrics); textPaint.setTextAlign(Paint.Align.LEFT); fontMetrics = new Paint.FontMetrics(); topGap = res.getDimensionPixelSize(R.dimen.sectioned_top); } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { super.getItemOffsets(outRect, view, parent, state); int pos = parent.getChildAdapterPosition(view); long groupId = callback.getGroupId(pos); if (groupId < 0) return; if (pos == 0 || isFirstInGroup(pos)) { outRect.top = topGap; } else { outRect.top = 0; } } @Override public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { super.onDrawOver(c, parent, state); int itemCount = state.getItemCount(); int childCount = parent.getChildCount(); int left = parent.getPaddingLeft(); int right = parent.getWidth() - parent.getPaddingRight(); float lineHeight = textPaint.getTextSize() + fontMetrics.descent; long preGroupId, groupId = -1; for (int i = 0; i < childCount; i++) { View view = parent.getChildAt(i); int position = parent.getChildAdapterPosition(view); preGroupId = groupId; groupId = callback.getGroupId(position); if (groupId < 0 || groupId == preGroupId) continue; String textLine = callback.getGroupFirstLine(position).toUpperCase(); if (TextUtils.isEmpty(textLine)) continue; int viewBottom = view.getBottom(); float textY = Math.max(topGap, view.getTop()); if (position + 1 < itemCount) { //下一個和當前不一樣移動當前 long nextGroupId = callback.getGroupId(position + 1); if (nextGroupId != groupId && viewBottom < textY ) {//組內最後一個view進入了header textY = viewBottom; } } c.drawRect(left, textY - topGap, right, textY, paint); c.drawText(textLine, left, textY, textPaint); } } }
好了,現在發現ItemDecoration有多強大了吧! 當然還有更多就需要你自己去發現了。
android recycleView 簡單使用二---分割線