是這么一回事
年底了殿漠,趕項目,于是忙了一個月業(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擋住武契;
明白了吧?
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)給我,也太傻逼了鸥诽。