自定義View 之 RecyclerView.ItemDecoration

做者:點先生 時間:2019.1.26android

是這麼一回事

年末了,趕項目,因而忙了一個月業務,忙了一個月沒有養分的東西。爲啥說沒養分,由於就是很簡簡單單的展現,沒有啥東西可寫。我差點要搬出11月份的騰訊面試經歷了,就在這時我給本身挖了個坑。 我本人的自定義View的能力是不好的,以前也沒有寫過,一直都用android自帶或者github上寫好的東西。因此這個坑挖的仍是值。git

坑的來源

以前咱們有一個報警消息展現界面,是這樣的;github

有個功能是這樣的,紅點顯示未讀,點擊一下就能消滅紅點。
問題就來了:後臺表示不能提供是否已讀的狀態,我表示我這邊本地存儲報警消息狀態並不合理。而後我就騷了一波,說接口不用改,我本身這邊處理。其實我想的就是仿微信朋友圈裏面的文字分割線「如下是已讀內容」,這樣就不用處理每一條消息了,哈哈哈哈哈哈哈。面試

兩種方案與思路

一開始我想到了兩種方案:
A :相似於添加head,footer,寫個新的viewholder進去。
優勢:網文較多;佈局複雜的狀況下比較好管理修改;
缺點:修改的東西比較多。
B:自定義RecyclerView.ItemDecoration
優勢:修改東西較少;自定義的優勢;
缺點:自定義的缺點;\canvas

思路:不管是A方案仍是B方案,我都須要知道這個分割線的position,在這裏我是將上一次請求到的數據中最新一條的createTime存入SP中,我將經過這個值去對比每一次請求下來的數據集的createTime,當他相等時,這個item的position,就應該是分割線的position。(這裏選擇對比條件是必定要選擇一個惟一,不重複的)。bash

在A方案中,adapter獲得list後,能夠找到分割線的position,而後在此position返回TextDivider的Viewholder。麻煩在於position以後的數據,TextDivider以後的每個數據的position都必須+1。每一次都得從新去算。每次滑動都會算,這裏處理起來可能不是很方便,並且會增長許多屬性幫助肯定真正的position。棄之微信

因此我選擇了B方案。也是對本身個機會去學習自定義view。ide

「懶惰是第一輩子產力」 —— 沃·茲基朔德佈局

RecyclerView.ItemDecoration

public class TextDivider extends RecyclerView.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){}
}
複製代碼

建立一個類去繼承RecyclerView.ItemDecoration,有三個方法須要重寫;
執行順序是getItemOffsets(),onDraw(),onDrawOver();學習

看名字,ItemDecoration是一個裝飾者,而且是給每個item加一個裝飾。咱們經常使用場景是寫個分割線,各類分割線,但願你們能經過我這篇文章,對ItemDecoration有更多新的騷操做。

咱們先來講關於這三個方法的用法。

getItemOffsets

第一個參數Rect,看名字不是不太容易知道有啥用。其實它就是咱們當前item的矩形。咱們能夠經過這個參數獲取到他的top、bottom、left、right。也能夠給這幾個屬性賦值。當咱們不給這幾個參數賦值時,默認爲0;

當咱們設置了rect的參數以後,就有了上圖左邊的效果,若是不賦值,默認就是右邊這個樣子。

onDraw與onDrawOver

這就是當靈魂畫家的部分了,用canvas能夠畫你想畫的東西。
parent幫助你獲取當前item的屬性。
state獲取當前recycleView的狀態。
這兩個方法的區別在於前後順序。

onDraw畫的東西會被item佈局擋住;
item佈局裏的東西會被onDrawOver擋住;
明白了吧?

左邊的圓就是onDraw畫的,右邊的圓就是onDrawOver畫的
tips!!! 上一個的item可能會被下一個item的onDraw東西給擋住,因此在畫的時候必定要控制好你的範圍。

代碼!安排!

private int bottomDevider;//分割線寬度
    private int topDevider;//文字分割寬度
    private String textString;//分割線的文字

    Rect textBounds = new Rect();

    private Paint dividerPaint;
    private Paint textPaint;

    private Long lastReadMsgDate;//上次獲取數據集的最新數據的createtime
複製代碼

除了textBounds ,其餘都很容易理解是幹嗎的。

public TextDivider(Context context) {
        dividerPaint = new Paint();
        textPaint = new Paint();
        //設置分割線顏色
        dividerPaint.setColor(context.getResources().getColor(R.color.whitesmoke));
        textPaint.setColor(context.getResources().getColor(R.color.vpi__bright_foreground_disabled_holo_dark));
        textString = "--------------以-下-是-已-閱-讀-內-容--------------";
        textPaint.setTextSize(32);
        textPaint.setTextAlign(Paint.Align.CENTER);
        //設置分割線寬度
        bottomDevider = context.getResources().getDimensionPixelSize(R.dimen.space_2);
        topDevider = 100;
        lastReadMsgDate = Long.parseLong(SPM.getStr(BaseApp.getContext(), LC.CONSTANT, LC.LAST_REMIND_MSG_DATA, "0"));
    }
複製代碼

textPaint.setTextAlign(Paint.Align.CENTER); 這句代碼是讓所寫的文字,居於原點水平居中。

private CreateTimeListener mListener;

    public void setCreateTimeListener(CreateTimeListener listener) {
        mListener = listener;
    }
    public interface CreateTimeListener {
        long getCreateTime(int position);
    }
複製代碼

這是接口用來從外部獲取當前item的createTime。

public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        outRect.bottom = bottomDevider;
        if(lastReadMsgDate == mListener.getCreateTime(parent.getChildAdapterPosition(view))){
            outRect.top = topDevider;
        }
    }
複製代碼

給每一個item下方增長一段距離,用於畫普通的分割線。
在須要畫文字分割線的上方增長一段距離,用於畫文字分割線

public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        final int childCount = parent.getChildCount();
        final int left = parent.getLeft() + parent.getPaddingLeft();
        final int right = parent.getRight() - parent.getPaddingRight();
        for (int i = 0; i < childCount; i++) {
            View view = parent.getChildAt(i);
            int position = parent.getChildAdapterPosition(view);
            if(lastReadMsgDate == mListener.getCreateTime(position)){
                float top = view.getBottom();
                float bottom = view.getBottom() + bottomDevider;
                c.drawRect(left, top, right, bottom, dividerPaint);
                top = view.getTop() - bottomDevider;
                bottom = view.getTop();
                c.drawRect(left, top, right, bottom, dividerPaint);

                //文字居中線
                float x = (view.getRight() - view.getLeft())/2;
                //文字所佔用的邊框top,bottom位置
                top = view.getTop() - topDevider;
                bottom = view.getTop() - bottomDevider;
                //獲取文字的Bounds
                textPaint.getTextBounds(textString, 0, textString.length(), textBounds);
                //計算文字的基線
                float y = ((bottom + top)/2) + (textBounds.height()/2);

                c.drawText(textString, x, y, textPaint);
            }else {
                float top = view.getBottom();
                float bottom = view.getBottom() + bottomDevider;
                c.drawRect(left, top, right, bottom, dividerPaint);
            }
        }
    }
複製代碼

在畫文字分割線的時候我以爲比較煩的就是算距離。
一般咱們用canvas畫東西的時候的原點,在左上角。

而文字分割線的原點在第一個字的左下角偏左一點點的距離。

文字垂直居中

關於點先生有多帥就很少講了。這裏說一說文字居中的問題。
本帥瞭解也不是很深, 就只找到了一種方法讓它居中。
水平居中很簡單,上面已經說到過了。

item的原點在左上角藍色圓的位置,文字要想垂直居中,原點應該在紫色圓的位置。 找到紫圓的Y軸座標就能夠了。
((bottom + top)/ 2) + (文字所佔的高度 / 2)

文字所佔高度,就是最後的難點了。 各類get方法都找不到文字高度,最後在畫文字時候傳的一個參數Rect給找到方法了。

textPaint.getTextBounds(textString, 0, textString.length(), textBounds);
複製代碼

跟上文說的同樣,就是矩形,這裏傳進去的textBounds就是Rect,穿進去以後能夠獲取到當前文字的一些屬性,問題迎刃而解。

在recycleView使用處調用也很簡單。

textDivider = new TextDivider(getContext());
        textDivider.setCreateTimeListener(new TextDivider.CreateTimeListener() {
            @Override
            public long getCreateTime(int position) {
                if (cacherRmindMsgList.size()==0) return 0L;
                else return cacherRmindMsgList.get(position).getCreateTime();
            }
        });
        recyclerView.addItemDecoration(textDivider);

複製代碼

嘻嘻!

後續

作完以後有個疑問。爲啥獲取文字屬性的沒有一個叫get***()的方法!
還要我親自傳一個參數進去接受這些東西。給個回調接口也好啊!

打臉也挺快,本身親手寫過的接口隔離原則都差點忘了。 Rect裏面這麼多屬性,它又不知道我要什麼東西,全都回調給我,也太傻逼了。

相關文章
相關標籤/搜索