做者:点先生 时间: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
“懒惰是第一辈子产力” —— 沃·兹基朔德布局
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有更多新的骚操做。
咱们先来讲关于这三个方法的用法。
第一个参数Rect,看名字不是不太容易知道有啥用。其实它就是咱们当前item的矩形。咱们能够经过这个参数获取到他的top、bottom、left、right。也能够给这几个属性赋值。当咱们不给这几个参数赋值时,默认为0;
当咱们设置了rect的参数以后,就有了上图左边的效果,若是不赋值,默认就是右边这个样子。
这就是当灵魂画家的部分了,用canvas能够画你想画的东西。
parent帮助你获取当前item的属性。
state获取当前recycleView的状态。
这两个方法的区别在于前后顺序。
onDraw画的东西会被item布局挡住;
item布局里的东西会被onDrawOver挡住;
明白了吧?
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里面这么多属性,它又不知道我要什么东西,全都回调给我,也太傻逼了。