轉載注明出處:http://blog.csdn.net/xiaohanluo/article/details/52251509
1. 概述
上一篇講解了RecyclerView的基本用法,回顧下上一篇文章講解内容。
- 水準清單展示,設定LayoutManager的方向性
- 豎直清單展示,設定LayoutManager的方向性
- 自定義間隔,RecyclerView.addItemDecoration()
- Item添加和删除動畫,RecyclerView.setItemAnimator()
關于網格樣式和瀑布流樣式在本篇會仔細的介紹,細心的同學會發現,自定義間隔在上一篇文章中并沒有太過深入,隻是介紹了方法的調用時機,但是關于更換間隔樣式沒有太詳細的介紹,是因為清單樣式的RecyclerView自定義間隔比較簡單,統一放到複雜一點的網格中來講解。直接進入主題,看看期待已久的網格模式和瀑布流模式的使用吧。
2. 網格樣式
上篇文章中已經了解到,RecyclerView展示的樣式是有布局管理器LayoutManager來控制。網格樣式的管理器是
GridLayoutManager
,看一下它最常用的兩個構造函數以及參數含義。
- GridLayoutManager(Context context, int spanCount)
- spanCount,每列或者每行的item個數,設定為1,就是清單樣式
- 該構造函數預設是豎直方向的網格樣式
- GridLayoutManager(Context context, int spanCount, int orientation,boolean reverseLayout)
- spanCount,每列或者每行的item個數,設定為1,就是清單樣式
- 網格樣式的方向,水準(OrientationHelper.HORIZONTAL)或者豎直(OrientationHelper.VERTICAL)
- reverseLayout,是否逆向,true:布局逆向展示,false:布局正向顯示
看一下使用。
// 豎直方向的網格樣式,每行四個Item
mLayoutManager = new GridLayoutManager(this, , OrientationHelper.VERTICAL, false);
mRecyclerView.setLayoutManager(mLayoutManager);
看一下運作效果。
圖-1 RecyclerView網格無間隔
網格樣式已經顯示出來了,和之前遇見的問題一樣,沒有間隔線,非常醜,間隔線必須加,而且要使用自定義,不使用系統自帶的。
建立檔案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();
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 == ? spanCount : extra;
for(int i = ; i < childCount; i++) {
isDrawVerticalDivider = true;
isDrawHorizontalDivider = true;
// 如果是豎直方向,最右邊一列不繪制豎直方向的間隔
if(orientation == OrientationHelper.VERTICAL && (i + ) % spanCount == ) {
isDrawVerticalDivider = false;
}
// 如果是豎直方向,最後一行不繪制水準方向間隔
if(orientation == OrientationHelper.VERTICAL && i >= childCount - extra) {
isDrawHorizontalDivider = false;
}
// 如果是水準方向,最下面一行不繪制水準方向的間隔
if(orientation == OrientationHelper.HORIZONTAL && (i + ) % spanCount == ) {
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 + ) % spanCount == ) {
outRect.set(, , , mDivider.getIntrinsicHeight());
return;
}
if(orientation == OrientationHelper.HORIZONTAL && (position + ) % spanCount == ) {
outRect.set(, , mDivider.getIntrinsicWidth(), );
return;
}
outRect.set(, , 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));
運作效果如下圖。
圖-2 RecyclerView網格有間隔
關于網格樣式的RecyclerView使用大體和清單樣式相同,主要在于間隔線的實作上有些不同,來看一下如果真正的使用自定義的間隔線需要做些什麼。
- 實作間隔線樣式,可以是xml檔案也可以是圖檔
- 覆寫應用主題的listdivider屬性,使用自定義的間隔線樣式
- 繼承
類,并實作其中的繪制間隔線方法RecyclerView.ItemDecoration
- 設定RecyclerView間隔線樣式
關于第三步,實作繪制線的方法,上面的代碼提供了一種大體的思路,可以供大家借鑒,下面就讓我們看看期待已久的瀑布流樣式的清單。
3. 瀑布流樣式
RecyclerView的瀑布流布局管理器是taggeredGridLayoutManager,它最常用的構造函數就一個,StaggeredGridLayoutManager(int spanCount, int orientation),spanCount代表每行或每列的Item個數,orientation代表清單的方向,豎直或者水準。
看在代碼中的使用。
// 初始化布局管理器
mLayoutManager = new StaggeredGridLayoutManager(, OrientationHelper.VERTICAL);
// 設定布局管理器
mRecyclerView.setLayoutManager(mLayoutManager);
// 設定adapter
mRecyclerView.setAdapter(mAdapter);
// 設定間隔樣式
mRecyclerView.addItemDecoration(new MDStaggeredRvDividerDecotation(this));
要實作瀑布流效果(僅讨論豎直方向的瀑布流樣式),每一個Item的高度要有所差别,如果所有的item的高度相同,就和網格樣式是一樣的展示效果。示例中就實作兩中不同高度的Item,一個高度為80dp,一個高度為100dp。
view_rv_staggered_item.xml布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="80dp">
<TextView
android:id="@+id/item_tv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
tools:text="item"/>
</LinearLayout>
view_rv_staggered_item_two.xml布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="100dp">
<TextView
android:id="@+id/item_tv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
tools:text="item"/>
</LinearLayout>
Item不同的布局是在Adapter裡面綁定的,看一下Adapter的實作。
public class MDStaggeredRvAdapter extends RecyclerView.Adapter<MDStaggeredRvAdapter.ViewHolder> {
/**
* 展示資料
*/
private ArrayList<String> mData;
public MDStaggeredRvAdapter(ArrayList<String> data) {
this.mData = data;
}
public void updateData(ArrayList<String> data) {
this.mData = data;
notifyDataSetChanged();
}
@Override
public int getItemViewType(int position) {
// 瀑布流樣式外部設定spanCount為2,在這列設定兩個不同的item type,以區分不同的布局
return position % ;
}
@Override
public MDStaggeredRvAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// 執行個體化展示的view
View v;
if(viewType == ) {
v = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_rv_staggered_item, parent, false);
} else {
v = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_rv_staggered_item_two, parent, false);
}
// 執行個體化viewholder
ViewHolder viewHolder = new ViewHolder(v);
return viewHolder;
}
@Override
public void onBindViewHolder(MDStaggeredRvAdapter.ViewHolder holder, int position) {
// 綁定資料
holder.mTv.setText(mData.get(position));
}
@Override
public int getItemCount() {
return mData == null ? : mData.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
TextView mTv;
public ViewHolder(View itemView) {
super(itemView);
mTv = (TextView) itemView.findViewById(R.id.item_tv);
}
}
}
接下來是設定瀑布流樣式的間隔線樣式的,上面代碼中使用的是
MDStaggeredRvDividerDecotation
類,其實是直接拷貝的網格樣式的間隔線繪制類。看一下運作效果。
圖-3 RecyclerView瀑布流2列
很奇怪,間隔線并沒有按照我們想象中的方式繪制,仔細看瀑布流中Item的分布,發現瀑布流樣式的Item分布和網格樣式的Item分布有些不同。對比一下兩者Item的分布,如下圖。
圖-4 RecyclerView對比
網格樣式的Item分布規律很明顯,豎直方向的網格,Item是從左向右從上到下依次按順序排列分布。
瀑布流樣式的Item分布也是從上到下,從左到右的順序排列,但是有一個高度的優先級,如果某一列中有一個高度最低的位置為空,最優先在此處添加Item。看第三張圖的
3 item
,因為該位置最低,優先在此處添加Item。
分析出了瀑布流樣式的Item的分布規律,就會發現,按照以往清單樣式或者網格樣式去設定間隔線是有問題的,因為不知道Item具體的位置,上下左右間隔線是否需要繪制不确定,參考第二張圖,其實第三張圖的間隔線也有問題,向上滑動就會展示出來。
目前能考慮到的瀑布流添加間隔線的思路:
- Item布局中設定四周間隔padding/margin
- 代碼中動态修改ItemView的間隔padding/margin
設定間隔有兩個方法:
- 上下左右都設定間隔
- 相鄰兩邊設定間隔(左上/左下/右上/右下)
第一種設定間隔的方法會導緻相鄰的Item間距是間隔的兩倍,第二種設定間隔的方法會導緻Item某一個方向上的與父布局邊緣無間隔,但是另一個方向與父布局邊緣有間隔,例如左上相鄰兩邊設定了間隔,最左邊一列的Item左邊與父布局邊緣有間隔,但是最右邊一列Item右邊與父布局無間隔,第一行和最後一行的Item也會出現這種情況。
要解決上面的問題,父布局RecyclerView也需要根據相應的情況設定padding讓整個布局的間隔都一緻。下面的例子是選擇在Item布局中設定間隔,因為可以自己在布局檔案中控制顔色比較友善,選擇右下兩邊設定間隔。
首先修改兩個Item的布局檔案。
view_rv_staggered_item.xml修改背景色和外層間距背景色。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="@dimen/md_common_view_height"
android:background="@color/md_divider"
android:paddingBottom="5dp"
android:paddingRight="5dp">
<TextView
android:id="@+id/item_tv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:background="@color/md_white"
tools:text="item"/>
</LinearLayout>
同樣修改view_rv_staggered_item_two.xml。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="100dp"
android:paddingBottom="5dp"
android:paddingRight="5dp"
android:background="@color/md_divider">
<TextView
android:id="@+id/item_tv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:background="@color/md_white"
tools:text="item"/>
</LinearLayout>
最後修改RecyclerView的一些屬性。
<android.support.v7.widget.RecyclerView
android:id="@+id/my_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/md_divider"
android:paddingLeft="5dp"
android:paddingTop="5dp"
android:fadeScrollbars="true"/>
運作一下,看看最後的效果。
圖-5 RecyclerView瀑布流
差不多完美的解決了間隔線的問題,有細心的同學可能發現,在RecyclerView滑動的時候上面一直有一條灰色的間隔線,這個可以通過取消xml布局檔案中RecyclerView的paddingTop屬性去掉頂部灰色的間隔線。
4. 總結
本篇文章主要介紹網格樣式和瀑布流樣式的RecyclerView,清單樣式、網格樣式和瀑布流樣式在某種程度上是可以轉換的。
- 網格樣式的布局管理器的spanCount設定為1,就是清單樣式
- 瀑布流樣式如果Item的布局檔案是等高,豎直方向,就是豎直方向的網格樣式;如果Item是等寬,水準方向,那就是水準方向的網絡樣式
- 如果瀑布流樣式的布局管理器spanCount設定為1,豎直方向,是豎直方向的清單;水準方向,就是水準方向的清單
Demo工程位址:https://github.com/Kyogirante/MaterialDesignDemo
目前為止關于RecyclerView的基本使用的介紹可以告一段落了,但其實關于RecyclerView深入使用可不止着一些,比如說單個Item橫滑,拖動Item之間轉換位置等等,官方都有提供,當然這些使用會在後面依次介紹。