ItemDecoration

整理一下,每次用的时候还得回忆,以后就复制了。
主要就是用来处理item之间的间隔

使用的时候rv.addItemDecoration即可

需要注意的是,这方法是add,不是set。也就是它是放到一个集合里的,你多次调用这个add方法,那么就添加了N个,到时候你会发现间距不停的变大。这种情况很多时候出现在刷新数据以后,又调用了这个方法。需要注意。

简单分析下这个类的几个方法


image.png

主要就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)

这个看名字,就是画在最顶层的,你也可以理解成最后画

上边就简单分析下,太久了,都不记得具体的谁在谁上边了。有空再看下再修改。

联系人首字母索引

今天主要弄下下边这种效果


image.png

上代码,也不算太难

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<T>:RecyclerView.ItemDecoration(){

    var datas= ArrayList<T>()
    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<indexHeight*2){
                moveY=holder.itemView.top-indexHeight*2;
            }
        }
        floatRect.offset(0,moveY)
        drawFloatingBg(c,floatRect,paintFloatBg)
        drawFloatingBgText(c,getDrawText(datas.get(first)),floatingTextLeft,floatRect.bottom-(indexHeight-textHeight)/2f,paintFloatBgText,floatRect)
    }

    //找到下一个带索引的item的position
    fun findNextIndex(position: Int,parent: RecyclerView):Int{
        for(i in position+1 until parent.adapter.itemCount){
            if(!indexEqual(datas.get(i-1),datas.get(i))){
                return i;
            }
        }
        return position
    }
}

上边是抽象类,因为不知道实体对象到底要的索引是啥。
下边来个联系人的简单实现,抽闲类公开了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<Contact>(){
    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<indexHeight*2){
                moveY=holder.itemView.top-indexHeight*2;
            }
image.png

可能有人说这些玩意不能设置点击事件

那个你真需要可以通过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的背景。


image.png

如果研究过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待研究

哎,竟然不知道系统提供了一个默认的实现。
使用起来也比较简单,如下代码,下图这种默认使用的是主题里系统的图片
可以自定义,如下

<item name="android:listDivider">@drawable/divider_normal_gray</item>
addItemDecoration(DividerItemDecoration(activity,LinearLayout.VERTICAL))

或者你也可以直接setDrawable给DividerItemDecoration弄个线条

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容