美文网首页
ItemDecoration

ItemDecoration

作者: 有点健忘 | 来源:发表于2018-08-02 16:45 被阅读577次

    整理一下,每次用的时候还得回忆,以后就复制了。
    主要就是用来处理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弄个线条

    相关文章

      网友评论

          本文标题:ItemDecoration

          本文链接:https://www.haomeiwen.com/subject/hmksvftx.html