概述
RecyclerView(一)使用完全指南
RecyclerView(二)之萬能分割線
RecyclerView之瀑布流(三)
上一篇講解了RecyclerView的基本用法,回顧下上一篇文章講解內容
- 水平列表展示,設置LayoutManager的方向性
- 豎直列表展示,設置LayoutManager的方向性
- 自定義間隔,RecyclerView.addItemDecoration()
- Item添加和刪除動畫,RecyclerView.setItemAnimator()
關于網格樣式和瀑布流樣式在本篇會仔細的介紹,細心的同學會發現,自定義間隔在上一篇文章中并沒有太過深入,只是介紹了方法的調用時機,但是關于更換間隔樣式沒有太詳細的介紹,是因為列表樣式的RecyclerView自定義間隔比較簡單,統一放到復雜一點的網格中來講解。直接進入主題,看看期待已久的網格模式和萬能分割線
網格樣式
上篇文章中已經了解到,RecyclerView展示的樣式是有布局管理器LayoutManager來控制。網格樣式的管理器是GridLayoutManager,看一下它最常用的兩個構造函數以及參數含義。
GridLayoutManager(Context context, int spanCount)
1.spanCount ,每列或者每行的item個數,設置為1,就是列表樣式
2.該構造函數默認是豎直方向的網格樣式
GridLayoutManager(Context context, int spanCount, int orientation,boolean reverseLayout)
spanCount,每列或者每行的item個數,設置為1,就是列表樣式
網格樣式的方向,水平(OrientationHelper.HORIZONTAL[h?r?'z?nt(?)l])或者豎直(OrientationHelper.VERTICAL['v??t?k(?)l])
reverseLayout,是否逆向,true:布局逆向展示,false:布局正向顯示
看一下使用。
// 豎直方向的網格樣式,每行四個Item
mLayoutManager = new GridLayoutManager(this, 4, OrientationHelper.VERTICAL, false);
mRecyclerView.setLayoutManager(mLayoutManager);
網格樣式已經顯示出來了,和之前遇見的問題一樣,沒有間隔線,非常丑,間隔線必須加,而且要使用自定義,不使用系統自帶的。
新建文件md_divider.xml,是一個灰色的矩形。
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<solid android:color="@android:color/darker_gray"/>
<size android:height="4dp" android:width="4dp"/>
</shape>
在styles.xml中的自定義的應用主題里替換掉listdivider屬性。
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:listDivider">@drawable/md_divider</item>
</style>
然后繼承RecyclerView.ItemDecoration
類,在構造函數里獲取自定義的間隔線,復寫繪制間隔線的方法。
public class MDGridRvDividerDecoration extends RecyclerView.ItemDecoration {
private static final int[] ATTRS = new int[]{
android.R.attr.listDivider
};
/**
* 用于繪制間隔樣式
*/
private Drawable mDivider;
public MDGridRvDividerDecoration(Context context) {
// 獲取默認主題的屬性
final TypedArray a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable(0);
a.recycle();
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
// 繪制間隔,每一個item,繪制右邊和下方間隔樣式
int childCount = parent.getChildCount();
int spanCount = ((GridLayoutManager)parent.getLayoutManager()).getSpanCount();
int orientation = ((GridLayoutManager)parent.getLayoutManager()).getOrientation();
boolean isDrawHorizontalDivider = true;
boolean isDrawVerticalDivider = true;
int extra = childCount % spanCount;
extra = extra == 0 ? spanCount : extra;
for(int i = 0; i < childCount; i++) {
isDrawVerticalDivider = true;
isDrawHorizontalDivider = true;
// 如果是豎直方向,最右邊一列不繪制豎直方向的間隔
if(orientation == OrientationHelper.VERTICAL && (i + 1) % spanCount == 0) {
isDrawVerticalDivider = false;
}
// 如果是豎直方向,最后一行不繪制水平方向間隔
if(orientation == OrientationHelper.VERTICAL && i >= childCount - extra) {
isDrawHorizontalDivider = false;
}
// 如果是水平方向,最下面一行不繪制水平方向的間隔
if(orientation == OrientationHelper.HORIZONTAL && (i + 1) % spanCount == 0) {
isDrawHorizontalDivider = false;
}
// 如果是水平方向,最后一列不繪制豎直方向間隔
if(orientation == OrientationHelper.HORIZONTAL && i >= childCount - extra) {
isDrawVerticalDivider = false;
}
if(isDrawHorizontalDivider) {
drawHorizontalDivider(c, parent, i);
}
if(isDrawVerticalDivider) {
drawVerticalDivider(c, parent, i);
}
}
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
int spanCount = ((GridLayoutManager) parent.getLayoutManager()).getSpanCount();
int orientation = ((GridLayoutManager)parent.getLayoutManager()).getOrientation();
int position = parent.getChildLayoutPosition(view);
if(orientation == OrientationHelper.VERTICAL && (position + 1) % spanCount == 0) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
return;
}
if(orientation == OrientationHelper.HORIZONTAL && (position + 1) % spanCount == 0) {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
return;
}
outRect.set(0, 0, mDivider.getIntrinsicWidth(), mDivider.getIntrinsicHeight());
}
/**
* 繪制豎直間隔線
*
* @param canvas
* @param parent
* 父布局,RecyclerView
* @param position
* irem在父布局中所在的位置
*/
private void drawVerticalDivider(Canvas canvas, RecyclerView parent, int position) {
final View child = parent.getChildAt(position);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int top = child.getTop() - params.topMargin;
final int bottom = child.getBottom() + params.bottomMargin + mDivider.getIntrinsicHeight();
final int left = child.getRight() + params.rightMargin;
final int right = left + mDivider.getIntrinsicWidth();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(canvas);
}
/**
* 繪制水平間隔線
*
* @param canvas
* @param parent
* 父布局,RecyclerView
* @param position
* item在父布局中所在的位置
*/
private void drawHorizontalDivider(Canvas canvas, RecyclerView parent, int position) {
final View child = parent.getChildAt(position);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int top = child.getBottom() + params.bottomMargin;
final int bottom = top + mDivider.getIntrinsicHeight();
final int left = child.getLeft() - params.leftMargin;
final int right = child.getRight() + params.rightMargin + mDivider.getIntrinsicWidth();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(canvas);
}
}
設置RecyclerView的間隔線。
mRecyclerView.addItemDecoration(new MDGridRvDividerDecoration(this));
運行效果如下圖。
關于網格樣式的RecyclerView使用大體和列表樣式相同,主要在于間隔線的實現上有些不同,來看一下如果真正的使用自定義的間隔線需要做些什么。
實現間隔線樣式,可以是xml文件也可以是圖片
覆蓋應用主題的listdivider屬性,使用自定義的間隔線樣式
繼承RecyclerView.ItemDecoration
類,并實現其中的繪制間隔線方法
設置RecyclerView間隔線樣式
關于第三步,實現繪制線的方法,上面的代碼提供了一種大體的思路,可以供大家借鑒,下面就讓我們看看期待已久的瀑布流樣式的列表。
瀑布流樣式
RecyclerView的瀑布流布局管理器是taggeredGridLayoutManager,它最常用的構造函數就一個,StaggeredGridLayoutManager(int spanCount, int orientation),spanCount代表每行或每列的Item個數,orientation代表列表的方向,豎直或者水平。
看在代碼中的使用。
其實我們發現這樣設置分割線,如果換樣式的話就太麻煩了,所以給大家找了萬能分割線的代碼,用的時候直接復制黏貼就可以了,如果技術過硬的話可以分析一下源碼,畢竟也不是很難
代碼使用方法:
添加水平分割線:高度為2px,顏色為灰色
//水平分割線
recyclerView.addItemDecoration(new DividerItemDecoration(
ListRecycle.this, DividerItemDecoration.HORIZONTAL_LIST));
添加垂直分割線:高度為2px,顏色為灰色
//垂直分割線
recyclerView.addItemDecoration(new DividerItemDecoration(
ListRecycle.this, DividerItemDecoration.VERTICAL_LIST));
平+垂直分割線:高度為2px,顏色為灰色
//垂直+水平分割線
recyclerView.addItemDecoration(new DividerItemDecoration(
ListRecycle.this, DividerItemDecoration.BOTH_SET));
帶顏色的分割線
//添加帶顏色分割線
recyclerView.addItemDecoration(new DividerItemDecoration(
ListRecycle.this, DividerItemDecoration.BOTH_SET,5,Color.BLUE));
自定義圖片的分割線(圖片必須有高度,比如說Xml文件,就可能沒設置高度):
//添加圖片分割線
recyclerView.addItemDecoration(new DividerItemDecoration(
ListRecycle.this, DividerItemDecoration.HORIZONTAL_LIST,R.mipmap.ic_launcher));
萬能分割線
/**
* 萬能分割線
* Created by ChenSS on 2016/9/21.
*/
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
private Paint mPaint;
//取名mDivider似乎更恰當
private Drawable mDrawable;
//分割線高度,默認為1px
private int mDividerHeight = 2;
//列表的方向
private int mOrientation;
//系統自帶的參數
private static final int[] ATTRS = new int[]{android.R.attr.listDivider};
//水平
public static final int HORIZONTAL_LIST = RecyclerView.HORIZONTAL;
//垂直
public static final int VERTICAL_LIST = RecyclerView.VERTICAL;
//水平+垂直
public static final int BOTH_SET = 2;
/**
* 默認分割線:高度為2px,顏色為灰色
*
* @param context 上下文
* @param orientation 列表方向
*/
public DividerItemDecoration(Context context, int orientation) {
this.setOrientation(orientation);
//獲取xml配置的參數
final TypedArray a = context.obtainStyledAttributes(ATTRS);
//typedArray.getDrawable(attr)這句是說我們可以通過我們的資源獲得資源,使用我們的資源名attr去獲得資源id
//看不懂就用自己寫一個分割線的圖片吧,方法:ContextCompat.getDrawable(context, drawableId);
mDrawable = a.getDrawable(0);
//官方的解釋是:回收TypedArray,以便后面重用。在調用這個函數后,你就不能再使用這個TypedArray。
//在TypedArray后調用recycle主要是為了緩存。
a.recycle();
}
/**
* 自定義分割線
*
* @param context 上下文
* @param orientation 列表方向
* @param drawableId 分割線圖片
*/
public DividerItemDecoration(Context context, int orientation, int drawableId) {
this.setOrientation(orientation);
//舊的getDrawable方法棄用了,這個是新的
mDrawable = ContextCompat.getDrawable(context, drawableId);
mDividerHeight = mDrawable.getIntrinsicHeight();
}
/**
* 自定義分割線
*
* @param context 上下文
* @param orientation 列表方向
* @param dividerHeight 分割線高度
* @param dividerColor 分割線顏色
*/
public DividerItemDecoration(Context context, int orientation,
int dividerHeight, int dividerColor) {
this.setOrientation(orientation);
mDividerHeight = dividerHeight;
Log.e("mDividerHeight", mDividerHeight + "===================");
//抗鋸齒畫筆
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(dividerColor);
//填滿顏色
mPaint.setStyle(Paint.Style.FILL);
}
/**
* 設置方向
*
* @param orientation
*/
public void setOrientation(int orientation) {
if (orientation < 0 || orientation > 2)
throw new IllegalArgumentException("invalid orientation");
mOrientation = orientation;
}
/**
* 繪制分割線之后,需要留出一個外邊框,就是說item之間的間距要換一下
*
* @param outRect outRect.set(0, 0, 0, 0);的四個參數理解成margin就好了
* @param view 視圖
* @param parent 父級view
* @param state
*/
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
//下面super...代碼其實調用的就是那個過時的getItemOffsets,也就是說這個方法體內容也可以通通移到那個過時的getItemOffsets中
super.getItemOffsets(outRect, view, parent, state);
//獲取layoutParams參數
RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) view.getLayoutParams();
//當前位置
int itemPosition = layoutParams.getViewLayoutPosition();
//ItemView數量
int childCount = parent.getAdapter().getItemCount();
switch (mOrientation) {
case BOTH_SET:
//獲取Layout的相關參數
int spanCount = this.getSpanCount(parent);
if (isLastRaw(parent, itemPosition, spanCount, childCount)) {
// 如果是最后一行,則不需要繪制底部
outRect.set(0, 0, mDividerHeight, 0);
} else if (isLastColum(parent, itemPosition, spanCount, childCount)) {
// 如果是最后一列,則不需要繪制右邊
outRect.set(0, 0, 0, mDividerHeight);
} else {
outRect.set(0, 0, mDividerHeight, mDividerHeight);
}
break;
case VERTICAL_LIST:
childCount -= 1;
//水平布局右側留Margin,如果是最后一列,就不要留Margin了
outRect.set(0, 0, (itemPosition != childCount) ? mDividerHeight : 0, 0);
break;
case HORIZONTAL_LIST:
childCount -= 1;
//垂直布局底部留邊,最后一行不留
outRect.set(0, 0, 0, (itemPosition != childCount) ? mDividerHeight : 0);
break;
}
}
/**
* 繪制分割線
*
* @param c
* @param parent
* @param state
*/
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
if (mOrientation == VERTICAL_LIST) {
drawVertical(c, parent);
} else if (mOrientation == HORIZONTAL_LIST) {
drawHorizontal(c, parent);
} else {
drawHorizontal(c, parent);
drawVertical(c, parent);
}
}
/**
* 繪制橫向 item 分割線
*
* @param canvas 畫布
* @param parent 父容器
*/
private void drawHorizontal(Canvas canvas, RecyclerView parent) {
final int x = parent.getPaddingLeft();
final int width = parent.getMeasuredWidth() - parent.getPaddingRight();
//getChildCount()(ViewGroup.getChildCount) 返回的是顯示層面上的“所包含的子 View 個數”。
final int childSize = parent.getChildCount();
for (int i = 0; i < childSize; i++) {
final View child = parent.getChildAt(i);
RecyclerView.LayoutParams layoutParams =
(RecyclerView.LayoutParams) child.getLayoutParams();
//item底部的Y軸坐標+margin值
final int y = child.getBottom() + layoutParams.bottomMargin;
final int height = y + mDividerHeight;
Log.e("height", height + "===================");
if (mDrawable != null) {
//setBounds(x,y,width,height); x:組件在容器X軸上的起點 y:組件在容器Y軸上的起點
// width:組件的長度 height:組件的高度
mDrawable.setBounds(x, y, width, height);
mDrawable.draw(canvas);
}
if (mPaint != null) {
canvas.drawRect(x, y, width, height, mPaint);
}
}
}
/**
* 繪制縱向 item 分割線
*
* @param canvas
* @param parent
*/
private void drawVertical(Canvas canvas, RecyclerView parent) {
final int top = parent.getPaddingTop();
final int bottom = parent.getMeasuredHeight() - parent.getPaddingBottom();
final int childSize = parent.getChildCount();
for (int i = 0; i < childSize; i++) {
final View child = parent.getChildAt(i);
RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
final int left = child.getRight() + layoutParams.rightMargin;
final int right = left + mDividerHeight;
if (mDrawable != null) {
mDrawable.setBounds(left, top, right, bottom);
mDrawable.draw(canvas);
}
if (mPaint != null) {
canvas.drawRect(left, top, right, bottom, mPaint);
}
}
}
/**
* 獲取列數
*
* @param parent
* @return
*/
private int getSpanCount(RecyclerView parent) {
int spanCount = -1;
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof GridLayoutManager) {
spanCount = ((GridLayoutManager) layoutManager).getSpanCount();
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
spanCount = ((StaggeredGridLayoutManager) layoutManager)
.getSpanCount();
}
return spanCount;
}
private boolean isLastColum(RecyclerView parent, int pos, int spanCount,
int childCount) {
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof GridLayoutManager) {
int orientation = ((GridLayoutManager) layoutManager)
.getOrientation();
if (orientation == StaggeredGridLayoutManager.VERTICAL) {
// 如果是最后一列,則不需要繪制右邊
if ((pos + 1) % spanCount == 0)
return true;
} else {
childCount = childCount - childCount % spanCount;
// 如果是最后一列,則不需要繪制右邊
if (pos >= childCount)
return true;
}
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
int orientation = ((StaggeredGridLayoutManager) layoutManager)
.getOrientation();
if (orientation == StaggeredGridLayoutManager.VERTICAL) {
// 如果是最后一列,則不需要繪制右邊
if ((pos + 1) % spanCount == 0)
return true;
} else {
childCount = childCount - childCount % spanCount;
// 如果是最后一列,則不需要繪制右邊
if (pos >= childCount)
return true;
}
}
return false;
}
private boolean isLastRaw(RecyclerView parent, int pos, int spanCount,
int childCount) {
int orientation;
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof GridLayoutManager) {
childCount = childCount - childCount % spanCount;
orientation = ((GridLayoutManager) layoutManager)
.getOrientation();
if (orientation == StaggeredGridLayoutManager.VERTICAL) {
// 如果是最后一行,則不需要繪制底部
childCount = childCount - childCount % spanCount;
if (pos >= childCount)
return true;
} else {// StaggeredGridLayoutManager 橫向滾動
// 如果是最后一行,則不需要繪制底部
if ((pos + 1) % spanCount == 0)
return true;
}
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
orientation = ((StaggeredGridLayoutManager) layoutManager)
.getOrientation();
if (orientation == StaggeredGridLayoutManager.VERTICAL) {
// 如果是最后一行,則不需要繪制底部
childCount = childCount - childCount % spanCount;
if (pos >= childCount)
return true;
} else {// StaggeredGridLayoutManager 橫向滾動
// 如果是最后一行,則不需要繪制底部
if ((pos + 1) % spanCount == 0)
return true;
}
}
return false;
}
}
根據王三的貓阿德加以改編