自定義View 之 RecyclerView.ItemDecoration

是這么一回事

年底了,趕項目,于是忙了一個月業(yè)務(wù),忙了一個月沒有營養(yǎng)的東西。為啥說沒營養(yǎng),因為就是很簡簡單單的展示,沒有啥東西可寫。我差點要搬出11月份的騰訊面試經(jīng)歷了,就在這時我給自己挖了個坑。
我本人的自定義View的能力是很差的,之前也沒有寫過,一直都用android自帶或者github上寫好的東西。所以這個坑挖的還是值。

坑的來源

之前我們有一個報警消息展示界面,是這樣的;

有個功能是這樣的,紅點顯示未讀,點擊一下就能消滅紅點。
問題就來了:后臺表示不能提供是否已讀的狀態(tài),我表示我這邊本地存儲報警消息狀態(tài)并不合理。然后我就騷了一波,說接口不用改,我自己這邊處理。其實我想的就是仿微信朋友圈里面的文字分割線“以下是已讀內(nèi)容”,這樣就不用處理每一條消息了,哈哈哈哈哈哈哈。

兩種方案與思路

一開始我想到了兩種方案:
A :類似于添加head,footer,寫個新的viewholder進(jìn)去。
優(yōu)點:網(wǎng)文較多;布局復(fù)雜的情況下比較好管理修改;
缺點:修改的東西比較多。
B:自定義RecyclerView.ItemDecoration
優(yōu)點:修改東西較少;自定義的優(yōu)點;
缺點:自定義的缺點;

思路:無論是A方案還是B方案,我都需要知道這個分割線的position,在這里我是將上一次請求到的數(shù)據(jù)中最新一條的createTime存入SP中,我將通過這個值去對比每一次請求下來的數(shù)據(jù)集的createTime,當(dāng)他相等時,這個item的position,就應(yīng)該是分割線的position。(這里選擇對比條件是一定要選擇一個唯一,不重復(fù)的)。

在A方案中,adapter得到list后,可以找到分割線的position,然后在此position返回TextDivider的Viewholder。麻煩在于position之后的數(shù)據(jù),TextDivider之后的每一個數(shù)據(jù)的position都必須+1。每一次都得重新去算。每次滑動都會算,這里處理起來可能不是很方便,而且會增加許多屬性幫助確定真正的position。棄之

所以我選擇了B方案。也是對自己個機會去學(xué)習(xí)自定義view。

“懶惰是第一生產(chǎn)力” —— 沃·茲基朔德

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){}
}

創(chuàng)建一個類去繼承RecyclerView.ItemDecoration,有三個方法需要重寫;
執(zhí)行順序是getItemOffsets(),onDraw(),onDrawOver();

看名字,ItemDecoration是一個裝飾者,并且是給每一個item加一個裝飾。我們常用場景是寫個分割線,各種分割線,希望大家能通過我這篇文章,對ItemDecoration有更多新的騷操作。

我們先來說關(guān)于這三個方法的用法。

getItemOffsets

第一個參數(shù)Rect,看名字不是不太容易知道有啥用。其實它就是我們當(dāng)前item的矩形。我們可以通過這個參數(shù)獲取到他的top、bottom、left、right。也可以給這幾個屬性賦值。當(dāng)我們不給這幾個參數(shù)賦值時,默認(rèn)為0;

當(dāng)我們設(shè)置了rect的參數(shù)之后,就有了上圖左邊的效果,如果不賦值,默認(rèn)就是右邊這個樣子。

onDraw與onDrawOver

這就是當(dāng)靈魂畫家的部分了,用canvas可以畫你想畫的東西。
parent幫助你獲取當(dāng)前item的屬性。
state獲取當(dāng)前recycleView的狀態(tài)。
這兩個方法的區(qū)別在于先后順序。

onDraw畫的東西會被item布局擋?。?br> 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;//上次獲取數(shù)據(jù)集的最新數(shù)據(jù)的createtime

除了textBounds ,其他都很容易理解是干嘛的。

    public TextDivider(Context context) {
        dividerPaint = new Paint();
        textPaint = new Paint();
        //設(shè)置分割線顏色
        dividerPaint.setColor(context.getResources().getColor(R.color.whitesmoke));
        textPaint.setColor(context.getResources().getColor(R.color.vpi__bright_foreground_disabled_holo_dark));
        textString = "--------------以-下-是-已-閱-讀-內(nèi)-容--------------";
        textPaint.setTextSize(32);
        textPaint.setTextAlign(Paint.Align.CENTER);
        //設(shè)置分割線寬度
        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);
    }

這是接口用來從外部獲取當(dāng)前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畫東西的時候的原點,在左上角。

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

文字垂直居中

關(guān)于點先生有多帥就不多講了。這里說一說文字居中的問題。
本帥了解也不是很深, 就只找到了一種方法讓它居中。
水平居中很簡單,上面已經(jīng)說到過了。

item的原點在左上角藍(lán)色圓的位置,文字要想垂直居中,原點應(yīng)該在紫色圓的位置。
找到紫圓的Y軸坐標(biāo)就可以了。
((bottom + top)/ 2) + (文字所占的高度 / 2)

文字所占高度,就是最后的難點了。
各種get方法都找不到文字高度,最后在畫文字時候傳的一個參數(shù)Rect給找到方法了。

  textPaint.getTextBounds(textString, 0, textString.length(), textBounds);

跟上文說的一樣,就是矩形,這里傳進(jìn)去的textBounds就是Rect,穿進(jìn)去之后可以獲取到當(dāng)前文字的一些屬性,問題迎刃而解。

在recycleView使用處調(diào)用也很簡單。

        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);

嘻嘻!


后續(xù)

做完之后有個疑問。為啥獲取文字屬性的沒有一個叫g(shù)et***()的方法!
還要我親自傳一個參數(shù)進(jìn)去接受這些東西。給個回調(diào)接口也好??!

打臉也挺快,自己親手寫過的接口隔離原則都差點忘了。
Rect里面這么多屬性,它又不知道我要什么東西,全都回調(diào)給我,也太傻逼了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容