自定義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進去。
優(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ù)賦值時,默認為0趾断;

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

onDraw與onDrawOver

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

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;//上次獲取數(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的原點在左上角藍色圓的位置峭判,文字要想垂直居中开缎,原點應(yīng)該在紫色圓的位置。
找到紫圓的Y軸坐標(biāo)就可以了林螃。
((bottom + top)/ 2) + (文字所占的高度 / 2)

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

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

跟上文說的一樣伏钠,就是矩形,這里傳進去的textBounds就是Rect谨设,穿進去之后可以獲取到當(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ù)進去接受這些東西蕾额。給個回調(diào)接口也好啊!

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末商玫,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子牡借,更是在濱河造成了極大的恐慌拳昌,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蓖捶,死亡現(xiàn)場離奇詭異地回,居然都是意外死亡,警方通過查閱死者的電腦和手機俊鱼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進店門刻像,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人并闲,你說我怎么就攤上這事细睡。” “怎么了帝火?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵溜徙,是天一觀的道長。 經(jīng)常有香客問我犀填,道長蠢壹,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任九巡,我火速辦了婚禮图贸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己疏日,他們只是感情好偿洁,可當(dāng)我...
    茶點故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著沟优,像睡著了一般涕滋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上挠阁,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天宾肺,我揣著相機與錄音,去河邊找鬼侵俗。 笑死爱榕,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的坡慌。 我是一名探鬼主播,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼藻三,長吁一口氣:“原來是場噩夢啊……” “哼洪橘!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起棵帽,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤熄求,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后逗概,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體弟晚,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年逾苫,在試婚紗的時候發(fā)現(xiàn)自己被綠了卿城。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡铅搓,死狀恐怖瑟押,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情星掰,我是刑警寧澤多望,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站氢烘,受9級特大地震影響怀偷,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜播玖,卻給世界環(huán)境...
    茶點故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一椎工、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦晋渺、人聲如沸镰绎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽畴栖。三九已至,卻和暖如春八千,著一層夾襖步出監(jiān)牢的瞬間吗讶,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工恋捆, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留照皆,地道東北人。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓沸停,卻偏偏與公主長得像膜毁,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子愤钾,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,086評論 2 355

推薦閱讀更多精彩內(nèi)容