RecyclerView之萬能分割線(二)

概述

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)
  1. spanCount,每列或者每行的item個數,設置為1,就是列表樣式

  2. 網格樣式的方向,水平(OrientationHelper.HORIZONTAL[h?r?'z?nt(?)l])或者豎直(OrientationHelper.VERTICAL['v??t?k(?)l])

  3. reverseLayout,是否逆向,true:布局逆向展示,false:布局正向顯示

看一下使用。

// 豎直方向的網格樣式,每行四個Item
mLayoutManager = new GridLayoutManager(this, 4, OrientationHelper.VERTICAL, false);
mRecyclerView.setLayoutManager(mLayoutManager);
RecyclerView-網格無間隔.jpg

網格樣式已經顯示出來了,和之前遇見的問題一樣,沒有間隔線,非常丑,間隔線必須加,而且要使用自定義,不使用系統自帶的。

新建文件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;
    }
}

根據王三的貓阿德加以改編

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容