整理一下,每次用的时候还得回忆,以后就复制了。
主要就是用来处理item之间的间隔
使用的时候rv.addItemDecoration即可
需要注意的是,这方法是add,不是set。也就是它是放到一个集合里的,你多次调用这个add方法,那么就添加了N个,到时候你会发现间距不停的变大。这种情况很多时候出现在刷新数据以后,又调用了这个方法。需要注意。
简单分析下这个类的几个方法
主要就3个方法
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state)
设置间隔的大小的,修改ourRect这个参数即可,里边有left,right,top,bottom属性
举例,如下,第一个item,有个top 30,那么你看到的效果就是第一个item距离上边有30个间隔,就是这里设置的。
val i=parent.getChildAdapterPosition(view)
if(i==0)
outRect.top=30
public void onDraw(Canvas c, RecyclerView parent, State state)
这里就是来操作上边弄的间隔的,默认间隔就是个空白,这里你可以随便画点啥。
public void onDrawOver(Canvas c, RecyclerView parent, State state)
这个看名字,就是画在最顶层的,你也可以理解成最后画
上边就简单分析下,太久了,都不记得具体的谁在谁上边了。有空再看下再修改。
联系人首字母索引
今天主要弄下下边这种效果
上代码,也不算太难
import android.graphics.*
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.view.View
import java.util.ArrayList
/**
* Created by charlie.song on 2018/4/3.
* 此类只支持垂直方向的layoutmanager,不做验证,非此LayoutManager会挂掉。
*/
abstract class ItemDecorationContact:RecyclerView.ItemDecoration(){
var datas= ArrayList()
abstract fun getDrawText(t:T):CharSequence//要画什么东西
abstract fun indexEqual(pre:T,t:T):Boolean//返回两者的索引是否一样
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
super.getItemOffsets(outRect, view, parent, state)
val i=parent.getChildAdapterPosition(view)
outRect.top=if(needDraw(i)) indexHeight else 0
}
var indexHeight=60;//要画的分组索引的高度
var paintFloatBg=Paint()//背景
val paintFloatBgText=Paint()//文字
var floatingTextLeft=20f;//要画的文字距离左边的间距
var textHeight=0;//测量的索引字母的高度
var floatRect=Rect()//用来画索引的布局方位
open fun initSomeThing(){
paintFloatBgText.textSize=30f
val bounds=Rect()
paintFloatBgText.getTextBounds("G",0,1,bounds)
textHeight=bounds.height();
paintFloatBgText.color=Color.RED
paintFloatBg.color=Color.parseColor("#888888")
}
override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
super.onDraw(c, parent, state)
if(datas.size==0){return}
if(textHeight==0){
initSomeThing()
}
for(i in 0 until parent.childCount){
val child=parent.getChildAt(i);
if(child!=null){
val position=parent.getChildAdapterPosition(child)
if(needDraw(position) ){
val rect=Rect(child.left, (child.top-indexHeight), child.right, child.top)
drawFloatingBg(c, rect,paintFloatBg)
drawFloatingBgText(c,getDrawText(datas.get(position)),child.left+floatingTextLeft,child.top-(indexHeight-textHeight)/2f,paintFloatBgText,rect)
}
}
}
}
//画分组背景颜色
open fun drawFloatingBg(c:Canvas,rect: Rect,paint: Paint){
c.drawRect(rect,paint)
}
//话分组条上的文字,rect是分组背景的范围
open fun drawFloatingBgText(c:Canvas,text:CharSequence,x:Float,y:Float,paint: Paint,rect: Rect){
c.drawText(text,0,1,x,y,paintFloatBgText)
}
//第一条or和自己上一条的首字母不一样,那么肯定是个新的首字母,就画出来
private fun needDraw(position:Int):Boolean{
if(datas.size==0){
return false
}
if(position==0){
return true
}
return !indexEqual(datas.get(position-1),datas.get(position))
}
override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
super.onDrawOver(c, parent, state)
if(datas.size==0){return}
floatRect.set(0,0,parent.width,indexHeight)
val first=(parent.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
if(first<0){
return
}
var moveY=0;
val next=findNextIndex(first,parent)
if(next!=first){
val holder=parent.findViewHolderForAdapterPosition(next)
if(holder.itemView.top
上边是抽象类,因为不知道实体对象到底要的索引是啥。
下边来个联系人的简单实现,抽闲类公开了initSomething方法,可以修改paint的属性。
drawBG,drawBgText也公开了,可以自己修改。
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.text.TextUtils
class ItemDecorationC:ItemDecorationContact(){
override fun getDrawText(t: Contact): CharSequence {
return t.index
}
override fun indexEqual(t: Contact, t1: Contact): Boolean {
return TextUtils.equals(t.index.substring(0,1),t1.index.substring(0,1))
}
override fun drawFloatingBgText(c: Canvas, text: CharSequence, x: Float, y: Float, paint: Paint, rect: Rect) {
val radius=indexHeight/2-6f
//重写以下,原本的文字是从左边开始的,这里要添加的一个圆圈,所以从圆的中心开始画,因此在init方法里重新设置了paint的textAlign属性
c.drawText(text,0,1,x+radius,y,paintFloatBgText)
c.drawCircle(x+radius,rect.centerY().toFloat(),radius,paintCircle)//文字的y值是修正过的,在中心点下边一点,所以这里不能用那个y
}
val paintCircle=Paint(Paint.ANTI_ALIAS_FLAG)
override fun initSomeThing() {
super.initSomeThing()
paintFloatBgText.textAlign=Paint.Align.CENTER
paintCircle.color=Color.WHITE
paintCircle.style=Paint.Style.STROKE
paintCircle.strokeWidth=2f
}
}
这里简单分析下drawOver那里的偏移量。
val holder=parent.findViewHolderForAdapterPosition(next)
if(holder.itemView.top
可能有人说这些玩意不能设置点击事件
那个你真需要可以通过SimpleOnItemTouchListener 来处理啊,坐标都给你了。自然可以算出来到底点的是哪里。
也就能做对应的处理拉。
看下itemDecoration啥时候画的
在recyclerview里,我们知道onDraw是通过draw方法调用的,也就是下边的super.draw(c)调用的,所以
代码1先执行的,之后大家知道会执行dispatchDraw绘制子view,也就是我们的item了。这下super.draw(c)执行完了
最后执行代码2。
所以现在知道了ItemDecoration的ondraw是画在最底层的,上边是itemview,然后最上边是drawOver画的东西
public void draw(Canvas c) {
super.draw(c);
final int count = mItemDecorations.size();
for (int i = 0; i < count; i++) {
mItemDecorations.get(i).onDrawOver(c, this, mState);//代码2
}
public void onDraw(Canvas c) {
super.onDraw(c);
final int count = mItemDecorations.size();
for (int i = 0; i < count; i++) {
mItemDecorations.get(i).onDraw(c, this, mState);//代码1
}
}
看下效果图,可以看到带图片的itemview明显在ItemDecoration的ondraw上边,图上的itemview是没有背景的,白色是activity的背景。
如果研究过draw,onDraw啥时候调用的人可能会有疑问的,recyclerview并没有设置背景,为啥onDraw会执行,我记得我们以前学的时候是 如果viewgroup没有设置背景的话,是不会走onDraw方法的,可这里走了。
网上找了篇帖子,可以看下http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2014/1014/1765.html
然后我们在recyclerview的构造方法里可以找到这样代码
setWillNotDraw(getOverScrollMode() == View.OVER_SCROLL_NEVER);
括号里的值是个false的,所以会执行onDraw的》 OVER_SCROLL_NEVER==2,而前者没有初始化,就是个0,所以不等
DividerItemDecoration待研究
哎,竟然不知道系统提供了一个默认的实现。
使用起来也比较简单,如下代码,下图这种默认使用的是主题里系统的图片
可以自定义,如下
- @drawable/divider_normal_gray
addItemDecoration(DividerItemDecoration(activity,LinearLayout.VERTICAL))
或者你也可以直接setDrawable给DividerItemDecoration弄个线条