• Android 使用Kotlin封装RecyclerView


    1.概述

    在一个开源项目上看到了一个Android Kotlin版的RecyclerView封装,个人觉得非常方便,所以就将这个封装摘了出来,记录下,方便以后使用,这个开源的项目叫DanDanPlayForAndroid点击链接可以查看具体的开源项目代码。

    2.运行效果图

    在这里插入图片描述在这里插入图片描述
    在这里插入图片描述在这里插入图片描述

    3.代码实现

    3.1 扩展RecyclerView

    我们可以通过Kotlin的扩展函数扩展RecycleView的布局方式,设置数据等功能,方便我们调用。代码如下:

    fun RecyclerView.vertical(
        reverse: Boolean = false
    ): LinearLayoutManager {
        return LinearLayoutManager(
            context,
            LinearLayoutManager.VERTICAL,
            reverse
        )
    }
    
    fun RecyclerView.horizontal(
        reverse: Boolean = false
    ): LinearLayoutManager {
        return LinearLayoutManager(
            context,
            LinearLayoutManager.HORIZONTAL,
            reverse
        )
    }
    
    fun RecyclerView.grid(
        spanCount: Int
    ): GridLayoutManager {
        return GridLayoutManager(context, spanCount)
    }
    
    fun RecyclerView.gridEmpty(spanCount: Int): GridLayoutManager {
        return GridLayoutManager(context, spanCount).also {
            it.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
                override fun getSpanSize(position: Int): Int {
                    if (position == RecyclerView.NO_POSITION) {
                        return 1
                    }
    
                    val viewType = adapter?.getItemViewType(position)
                    if (viewType != -1) {
                        return 1
                    }
    
                    return spanCount
                }
            }
        }
    }
    
    fun RecyclerView.setData(itemData: List<Any>) {
        (adapter as RVBaseAdapter).setData(itemData)
    }
    
    fun RecyclerView.requestIndexChildFocus(index: Int): Boolean {
        scrollToPosition(index)
        val targetTag = "tag_focusable_item"
        val indexView = layoutManager?.findViewByPosition(index)
        if (indexView != null) {
            indexView.findViewWithTag<View>(targetTag)?.requestFocus()
            return true
        }
    
        post {
            layoutManager?.findViewByPosition(index)
                ?.findViewWithTag<View>(targetTag)
                ?.requestFocus()
        }
    
        return true
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    3.2 扩展Adapter

    在扩展Adapter之前,我们需要先定义一个我们自己的Adapter,然后再基于我们自己的Adapter去做扩展,代码如下:

    class RVBaseAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
        companion object{
            // the data of empty layout
            val EMPTY_ITEM = Any()
    
            // view type of empty layout
            const val VIEW_TYPE_EMPTY = -1
    
            // number of max item
            private const val NUMBER_OF_MAX_VIEW_TYPE = Int.MAX_VALUE -1
        }
    
        val itemData: MutableList<Any> = mutableListOf()
    
        private val typeHolders = 
        SparseArrayCompat<BaseViewHolderCreator<out ViewDataBinding>>()
        override fun onCreateViewHolder(
            parent: ViewGroup,
            viewType: Int
        ): RecyclerView.ViewHolder {
            return BaseViewHolder(
                DataBindingUtil.inflate(
                    LayoutInflater.from(parent.context),
                    getHolderCreator(viewType).getResourceId(),
                    parent,
                    false
                )
            )
        }
    
        private fun getHolderCreator(viewType: Int): 
        BaseViewHolderCreator<out ViewDataBinding> {
            return typeHolders.get(viewType)
                ?: throw java.lang.RuntimeException()
        }
    
        override fun getItemCount(): Int {
            return itemData.size
        }
    
        override fun onBindViewHolder(holder: RecyclerView.ViewHolder,
         position: Int) {
            getHolderCreator(holder.itemViewType).apply {
                initItemBinding(holder.itemView)
                onBindViewHolder(itemData[position],position,this)
            }
        }
    
        fun setData(dataList: List<Any>) {
            itemData.clear()
            itemData.addAll(dataList)
    
            // show the empty layout when data is empty
            if(itemData.isEmpty() && typeHolders.containsKey(VIEW_TYPE_EMPTY)){
                itemData.add(EMPTY_ITEM)
            }
    
            notifyDataSetChanged()
        }
    
        fun register(creator: BaseViewHolderCreator<out ViewDataBinding>, 
        customViewType: Int? = null) {
            apply {
                var viewType = customViewType ?: typeHolders.size()
                while (typeHolders.get(viewType) != null) {
                    viewType++
                    require(viewType < NUMBER_OF_MAX_VIEW_TYPE) {
                        "the number of view type has reached the maximum limit"
                    }
                }
    
                require(viewType < NUMBER_OF_MAX_VIEW_TYPE) {
                    "the number of view type has reached the maximum limit"
                }
    
                typeHolders.put(viewType, creator)
            }
        }
    
        override fun getItemViewType(position: Int): Int {
            if(itemData[position] == EMPTY_ITEM
                && typeHolders.containsKey(VIEW_TYPE_EMPTY)){
                return VIEW_TYPE_EMPTY
            }
    
            // only one viewHolder
            if(typeHolders.size() == 1){
                return typeHolders.keyAt(0)
            }
    
            // more than one viewHolder
            for (i in 0 until typeHolders.size()){
                if(typeHolders.keyAt(i) == VIEW_TYPE_EMPTY){
                    continue
                }
    
                val holder = typeHolders.valueAt(i)
                if(holder.isForViewType(itemData[position],position)){
                    return typeHolders.keyAt(i)
                }
            }
    
            throw java.lang.IllegalStateException(
                "no holder added that matches at position: $position in data source"
            )
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107

    与上面代码相关联的抽象类:

    class BaseViewHolder(binding: ViewDataBinding) :
     RecyclerView.ViewHolder(binding.root) {
    }
    
    • 1
    • 2
    • 3
    abstract class BaseViewHolderCreator<V : ViewDataBinding> {
        abstract fun isForViewType(data: Any?, position: Int): Boolean
        abstract fun getResourceId(): Int
        abstract fun onBindViewHolder(
            data: Any?,
            position: Int,
            creator: BaseViewHolderCreator<out ViewDataBinding>
        )
    
        lateinit var itemDataBinding: V
    
        fun initItemBinding(itemView: View) {
            this.itemDataBinding = DataBindingUtil.getBinding(itemView)!!
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    抽象类的实现:

    class BaseViewHolderDSL<T : Any, V : ViewDataBinding>(
        private val resourceId: Int,
        private val clazz: KClass<T>
    ) : BaseViewHolderCreator<V>() {
        private var checkViewType: ((data: Any, position: Int) -> Boolean)? = null
    
        private var viewHolder: (
            (data: T, position: Int, creator:
             BaseViewHolderCreator<out ViewDataBinding>) -> Unit
        )? = null
    
        private var emptyViewHolder: (() -> Unit)? = null
        override fun isForViewType(data: Any?, position: Int): Boolean {
            if(data == null){
                return false
            }
    
            if(checkViewType != null){
                return checkViewType!!.invoke(data,position)
            }
    
            return clazz.isInstance(data)
        }
    
        /**
         * judge the type of current item data according to position
         */
    
        fun checkType(viewType:(data:Any,position:Int) ->Boolean){
            this.checkViewType = viewType
        }
    
        fun initView(
            holder:(
                data:T,
                position:Int,
                holder:BaseViewHolderCreator<out ViewDataBinding>
            )->Unit
        ){
            this.viewHolder = holder
        }
    
        override fun getResourceId(): Int {
            return resourceId
        }
    
        override fun onBindViewHolder(
            data: Any?,
            position: Int,
            creator: BaseViewHolderCreator<out ViewDataBinding>
        ) {
            // empty layout
            if(data == RVBaseAdapter.EMPTY_ITEM){
                emptyViewHolder?.invoke()
                return
            }
    
            data ?: return
    
            viewHolder?.invoke(data as T,position,creator)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    RVBaseAdapter类的扩展

    fun buildAdapter(init: RVBaseAdapter.() -> Unit): RVBaseAdapter {
        return RVBaseAdapter().apply {
            init()
        }
    }
    
    inline fun <reified T : Any, V : ViewDataBinding> RVBaseAdapter.addItem(
        resourceID: Int,
        init: BaseViewHolderDSL<T, V>.() -> Unit
    ) {
        register(
            BaseViewHolderDSL<T, V>(resourceID, T::class).apply { init() }
        )
    }
    
    inline fun RVBaseAdapter.addEmptyView(
        resourceID: Int,
        init: (BaseViewHolderDSL<Any, LayoutEmptyBinding>.() -> Unit) = {}
    ) {
        register(
            BaseViewHolderDSL<Any, LayoutEmptyBinding>(resourceID, Any::class)
            .apply {
                init()
            },
            customViewType = RVBaseAdapter.VIEW_TYPE_EMPTY
        )
    
        setData(listOf(RVBaseAdapter.EMPTY_ITEM))
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    3.3 RecyclerView装饰绘制

    RecyclerView可以继承自ItemDecoration类绘制自己想要的分割线和装饰,这里做了几个例子,代码如下:

    3.3.1 以图片实现分割线

    /**
     * 分割线(以图片实现)
     */
    class MyItemDecoration(divider: Drawable, dividerSize: Int) : 
    RecyclerView.ItemDecoration() {
        private val mDivider = divider
        private val mDividerSize = dividerSize
    
        override fun onDraw(canvas: Canvas, parent: RecyclerView, state: 
        RecyclerView.State) {
            canvas.save()
    
            //居中显示
            val top = (parent.height - mDividerSize) / 2
            val bottom = top + mDividerSize
    
            val mBounds = Rect()
    
            //只在中间绘制
            for (i in 0 until parent.childCount - 1) {
                val child = parent.getChildAt(i)
                parent.layoutManager!!.getDecoratedBoundsWithMargins(child, mBounds)
    
                val right = mBounds.right + child.translationX.roundToInt()
                val left = right - mDividerSize
                mDivider.setBounds(left, top, right, bottom)
                mDivider.draw(canvas)
            }
            canvas.restore()
        }
    
        override fun getItemOffsets(
            outRect: Rect,
            view: View,
            parent: RecyclerView,
            state: RecyclerView.State
        ) {
            outRect.set(0, 0, mDividerSize, 0)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    3.3.2 画网格线

    class ItemGridDecorationDrawable : ItemDecoration {
        private var leftRight: Int
        private var topBottom: Int
        private var mDivider: Drawable?
    
        constructor(spacePx: Int) {
            leftRight = spacePx
            topBottom = spacePx
            mDivider = ColorDrawable(Color.WHITE)
        }
    
        constructor(leftRight: Int, topBottom: Int) {
            this.leftRight = leftRight
            this.topBottom = topBottom
            mDivider = ColorDrawable(Color.WHITE)
        }
    
        constructor(leftRight: Int, topBottom: Int, mColor: Int) {
            this.leftRight = leftRight
            this.topBottom = topBottom
            mDivider = ColorDrawable(mColor)
        }
    
        override fun onDraw(
            c: Canvas,
            parent: RecyclerView,
            state: RecyclerView.State
        ) {
            val layoutManager = parent.layoutManager 
            as GridLayoutManager? ?: return
            val lookup = layoutManager.spanSizeLookup
            if (mDivider == null || layoutManager.childCount == 0) {
                return
            }
            //判断总的数量是否可以整除
            val spanCount = layoutManager.spanCount
            var left: Int
            var right: Int
            var top: Int
            var bottom: Int
            val childCount = parent.childCount
            if (layoutManager.orientation == GridLayoutManager.VERTICAL) {
                for (i in 0 until childCount) {
                    val child = parent.getChildAt(i)
                    //将带有颜色的分割线处于中间位置
                    val centerLeft =
                        ((layoutManager.getLeftDecorationWidth(child) + layoutManager.getRightDecorationWidth(
                            child
                        )).toFloat()
                                * spanCount / (spanCount + 1) + 1 - leftRight) / 2
                    val centerTop =
                        (layoutManager.getBottomDecorationHeight(child)
                         + 1 - topBottom) / 2f
                    //得到它在总数里面的位置
                    val position = parent.getChildAdapterPosition(child)
                    //获取它所占有的比重
                    val spanSize = lookup.getSpanSize(position)
                    //获取每排的位置
                    val spanIndex = lookup.getSpanIndex(position, 
                    layoutManager.spanCount)
                    //判断是否为第一排
                    val isFirst =
                        layoutManager.spanSizeLookup.getSpanGroupIndex(position,
                         spanCount) == 0
                    //画上边的,第一排不需要上边的,只需要在最左边的那项的时候画一次就好
                    if (!isFirst && spanIndex == 0) {
                        left = layoutManager.getLeftDecorationWidth(child)
                        right = parent.width - 
                        layoutManager.getLeftDecorationWidth(child)
                        top = (child.top - centerTop).toInt() - topBottom
                        bottom = top + topBottom
                        mDivider!!.setBounds(left, top, right, bottom)
                        mDivider!!.draw(c)
                    }
                    //最右边的一排不需要右边的
                    val isRight = spanIndex + spanSize == spanCount
                    if (!isRight) { //计算右边的
                        left = (child.right + centerLeft).toInt()
                        right = left + leftRight
                        top = child.top
                        if (!isFirst) {
                            top -= centerTop.toInt()
                        }
                        bottom = (child.bottom + centerTop).toInt()
                        mDivider!!.setBounds(left, top, right, bottom)
                        mDivider!!.draw(c)
                    }
                }
            } else {
                for (i in 0 until childCount) {
                    val child = parent.getChildAt(i)
                    //将带有颜色的分割线处于中间位置
                    val centerLeft =
                        (layoutManager.getRightDecorationWidth(child) 
                        + 1 - leftRight) / 2f
                    val centerTop =
                        ((layoutManager.getTopDecorationHeight(child) + layoutManager.getBottomDecorationHeight(
                            child
                        )).toFloat()
    * spanCount / (spanCount + 1) - topBottom) / 2
                    //得到它在总数里面的位置
                    val position = parent.getChildAdapterPosition(child)
                    //获取它所占有的比重
                    val spanSize = lookup.getSpanSize(position)
                    //获取每排的位置
                    val spanIndex = lookup
                    .getSpanIndex(position, layoutManager.spanCount)
                    //判断是否为第一列
                    val isFirst =
                        layoutManager.spanSizeLookup
                        .getSpanGroupIndex(position, spanCount) == 0
                    //画左边的,第一排不需要左边的,只需要在最上边的那项的时候画一次就好
                    if (!isFirst && spanIndex == 0) {
                        left = (child.left - centerLeft).toInt() - leftRight
                        right = left + leftRight
                        top = layoutManager.getRightDecorationWidth(child)
                        bottom = parent.height - layoutManager.getTopDecorationHeight(child)
                        mDivider!!.setBounds(left, top, right, bottom)
                        mDivider!!.draw(c)
                    }
                    //最下的一排不需要下边的
                    val isRight = spanIndex + spanSize == spanCount
                    if (!isRight) { //计算右边的
                        left = child.left
                        if (!isFirst) {
                            left -= centerLeft.toInt()
                        }
                        right = (child.right + centerTop).toInt()
                        top = (child.bottom + centerLeft).toInt()
                        bottom = top + leftRight
                        mDivider!!.setBounds(left, top, right, bottom)
                        mDivider!!.draw(c)
                    }
                }
            }
        }
    
        override fun getItemOffsets(
            outRect: Rect,
            view: View,
            parent: RecyclerView,
            state: RecyclerView.State
        ) {
            val layoutManager = parent.layoutManager as GridLayoutManager? ?: return
            
            val lp =
                view.layoutParams as GridLayoutManager.LayoutParams
            val childPosition = parent.getChildAdapterPosition(view)
            val spanCount = layoutManager.spanCount
            if (layoutManager.orientation == GridLayoutManager.VERTICAL) { 
            //判断是否在第一排
                if (layoutManager.spanSizeLookup.getSpanGroupIndex(
                        childPosition,
                        spanCount
                    ) == 0
                ) { //第一排的需要上面
                    outRect.top = topBottom
                }
                outRect.bottom = topBottom
                //这里忽略和合并项的问题,只考虑占满和单一的问题
                if (lp.spanSize == spanCount) { //占满
                    outRect.left = leftRight
                    outRect.right = leftRight
                } else {
                    outRect.left =
                        ((spanCount - lp.spanIndex).toFloat() / spanCount * leftRight).toInt()
                    outRect.right =
                        (leftRight.toFloat() * (spanCount + 1) / spanCount - outRect.left).toInt()
                }
            } else {
                if (layoutManager.spanSizeLookup.getSpanGroupIndex(
                        childPosition,
                        spanCount
                    ) == 0
                ) { //第一排的需要left
                    outRect.left = leftRight
                }
                outRect.right = leftRight
                //这里忽略和合并项的问题,只考虑占满和单一的问题
                if (lp.spanSize == spanCount) { //占满
                    outRect.top = topBottom
                    outRect.bottom = topBottom
                } else {
                    outRect.top =
                        ((spanCount - lp.spanIndex).toFloat() / spanCount * topBottom).toInt()
                    outRect.bottom =
                        (topBottom.toFloat() * (spanCount + 1) / spanCount - outRect.top).toInt()
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191

    3.3.3空白的分割线

    /**
     * 空白的分割线
     *
     */
    class ItemDecorationSpace : ItemDecoration {
        private var top: Int
        private var left: Int
        private var right: Int
        private var bottom: Int
        private var spanCount: Int
    
        constructor(space: Int) : this(space, space, space, space)
    
        constructor(spaceLR: Int, spaceTB: Int) : this(spaceTB, spaceLR, spaceLR,
         spaceTB)
    
        constructor(top: Int, left: Int, right: Int, bottom: Int) {
            this.top = top
            this.left = left
            this.right = right
            this.bottom = bottom
            spanCount = 0
        }
    
        constructor(top: Int, left: Int, right: Int, bottom: Int, spanCount: Int) {
            this.top = top
            this.left = left
            this.right = right
            this.bottom = bottom
            this.spanCount = spanCount
        }
    
        override fun getItemOffsets(
            outRect: Rect, view: View,
            parent: RecyclerView, state: RecyclerView.State
        ) {
            outRect.top = top
            outRect.left = left
            outRect.bottom = bottom
            if (spanCount != 0) {
                val position = parent.getChildLayoutPosition(view)
                if ((position + 1) % spanCount == 0) {
                    outRect.right = 0
                } else {
                    outRect.right = right
                }
            } else {
                outRect.right = right
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    3.3.4 不同方向上的分割线

    /**
     * 不同方向上的分割线
     */
    
    class ItemDecorationOrientation : ItemDecoration {
        private val dividerPx: Int
        private val headerPx: Int
        private val footerPx: Int
        private val orientation: Int
    
        constructor(dividerPx: Int, @RecyclerView.Orientation orientation: Int) 
        : this(
            dividerPx,
            dividerPx,
            orientation
        )
    
        constructor(
            dividerPx: Int,
            headerFooterPx: Int,
            @RecyclerView.Orientation orientation: Int
        ) : this(dividerPx, headerFooterPx, headerFooterPx, orientation)
    
        constructor(
            dividerPx: Int,
            headerPx: Int,
            footerPx: Int,
            @RecyclerView.Orientation orientation: Int
        ) {
            this.dividerPx = dividerPx
            this.headerPx = headerPx
            this.footerPx = footerPx
            this.orientation = orientation
        }
    
        override fun getItemOffsets(
            outRect: Rect,
            view: View,
            parent: RecyclerView,
            state: RecyclerView.State
        ) {
            if (orientation == RecyclerView.VERTICAL) {
                getItemOffsetsVertical(outRect, view, parent)
            } else {
                getItemOffsetsHorizontal(outRect, view, parent)
            }
        }
    
        private fun getItemOffsetsVertical(outRect: Rect, view: View,
         parent: RecyclerView) {
            val itemCount = parent.adapter?.itemCount ?: return
            val position = parent.getChildAdapterPosition(view)
    
            if (position == 0) {
                outRect.top = headerPx
            } else {
                outRect.top = position * dividerPx / itemCount
            }
    
            if (position == itemCount - 1) {
                outRect.bottom = footerPx
            } else {
                outRect.bottom = dividerPx - (position + 1) * dividerPx / itemCount
            }
        }
    
        private fun getItemOffsetsHorizontal(outRect: Rect, view: View, parent:
         RecyclerView) {
            val itemCount = parent.adapter?.itemCount ?: return
            val position = parent.getChildAdapterPosition(view)
    
            if (position == 0) {
                outRect.left = headerPx
            } else {
                outRect.left = position * dividerPx / itemCount
            }
    
            if (position == itemCount - 1) {
                outRect.right = footerPx
            } else {
                outRect.right = dividerPx - (position + 1) * dividerPx / itemCount
                
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85

    3.4 使用方法

    使用的时候去掉代码中对应的注释,体验各种风格

    class RecyclerViewActivity : AppCompatActivity() {
        private lateinit var dataBinding: ActivityRecyclerViewBinding
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            initDataBinding()
            initRV()
            val dataList = listOf<UserData>(
                UserData("walt zhong", 21),
                UserData("walt xian", 22),
                UserData("walt jian", 31),
                UserData("walt x", 22),
                UserData("walt y", 41),
                UserData("walt z", 26),
                UserData("walt 2", 29),
            )
    
            //   val dataList = emptyList()
    
            dataBinding.rvList.setData(dataList)
        }
    
        private fun initRV() {
            dataBinding.rvList.apply {
               // layoutManager = gridEmpty(3) //网格布局
                // layoutManager = vertical(false) // 垂直布局
                 layoutManager = horizontal(false) // 水平布局
                adapter = buildAdapter {
                    addEmptyView(R.layout.layout_empty)
                    addItem<UserData, RvItemBinding>(R.layout.rv_item) {
                        initView { data, position, _ ->
                            itemDataBinding.apply {
                                tvName.text = data.name
                                tvAge.text = data.age.toString()
    
                                itemLayout.setOnClickListener {
                                    Log.d("zhongxj", "click item: $position")
                                }
                            }
                        }
                    }
                }
    
    //            val pxValue = dp2px(5)
    //
    //            addItemDecoration(
    //                ItemGridDecorationDrawable(
    //                    pxValue,
    //                    pxValue,
    //                    R.color.purple_200
    //                )
    //            )
    
    //            addItemDecoration(
    //                ItemDecorationSpace(
    //                    pxValue
    //                )
    //            )
    
    //            addItemDecoration(
    //                ItemDecorationOrientation(
    //                   dividerPx = pxValue,
    //                    headerFooterPx = 0,
    //                    orientation = RecyclerView.HORIZONTAL
    //                )
    //            )
    
                val dividerSize = dp2px(16)
                val divider =  ContextCompat.getDrawable(context, R.drawable.ic_arrow)
                if(divider != null){
                    addItemDecoration(
                        MyItemDecoration(
                            divider,
                            dividerSize
                        )
                    )
                }
            }
        }
    
        private fun initDataBinding() {
            dataBinding = DataBindingUtil.setContentView(
                this,
                R.layout.activity_recycler_view
            )
    
            dataBinding.lifecycleOwner = this@RecyclerViewActivity
        }
    
        /**
         * 单位转换,将DP转为PX
         */
        fun dp2px(dpValue: Int): Int {
            val scale = Resources.getSystem().displayMetrics.density
            return (dpValue * scale + 0.5f).toInt()
        }
    }
    
    data class UserData(var name:String,var age:Int)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98

    布局文件:
    RcyclerViewActivity布局

    
    <layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools">
    
        <data>
    
        data>
        <LinearLayout
            android:background="#eeeeee"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"
            tools:context=".RecyclerViewActivity">
    
            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/rv_list"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />
    
        LinearLayout>
    layout>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    RecyclerView item布局

    
    <layout xmlns:android="http://schemas.android.com/apk/res/android">
    
        <data>
    
        data>
    
        <LinearLayout
            android:background="@color/white"
            android:padding="10dp"
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:id="@+id/item_layout"
            >
            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:textSize="20sp"
                android:text="walt"
                android:id="@+id/tv_name"/>
    
            <TextView
                android:layout_marginTop="10dp"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:textSize="20sp"
                android:text="24"
                android:id="@+id/tv_age"/>
    
        LinearLayout>
    layout>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    没有数据时的空布局

    
    <layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto">
    
        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
            <ImageView
                android:id="@+id/empty_iv"
                android:layout_width="200dp"
                android:layout_height="200dp"
                android:src="@mipmap/ic_empty_data"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintVertical_bias="0.382" />
    
            <TextView
                android:id="@+id/empty_tv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="5dp"
                android:text="没有数据"
                android:textColor="@color/black"
                android:textSize="16sp"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/empty_iv" />
    
        androidx.constraintlayout.widget.ConstraintLayout>
    layout>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    里面对应的图片读者自己找喜欢的替换上就可以啦,本文主要是记录,代码也不难,读者可以自行跟着敲一遍,加深映像,熟悉这种封装方法,后面可以使用在项目的其他部分的封装。

  • 相关阅读:
    现代循环神经网络 - 编码器-解码器架构
    SD-WAN NFV uCPE VNF
    14种主流的RTOS 单片机操作系统~来学!
    利用一段字节序列构建一个数组对象
    Elasticsearch语法知多少之Term query
    计算机毕业设计java毕业设计项目基于ssm maven健身房俱乐部管理系统
    Error checking compiler version for cl: [WinError 2] 系统找 不到指定的文件[已解决]
    浅谈Python异常处理
    单个数据盘分区如何配置LVM
    AbstractHandlerMethodAdapter类简介说明
  • 原文地址:https://blog.csdn.net/zxj2589/article/details/133442955