RecyclerView简单的粘性头部实现--选择汽车品牌为例

1、使用ItemDecoration

class CarBrandStickItemDecoration(var context: Context) : RecyclerView.ItemDecoration() {

    private var mItemHeaderHeight = 50
    private var mItemHeaderPaint = Paint()
    private var mTextPaint = Paint()
    private var mLinePaint = Paint()
    private var hotCityText = "热门品牌"

    init {
        mItemHeaderHeight = context.dp2px(30)
        mItemHeaderPaint.color = context.resources.getColor(R.color.color_gray_f0)
        mTextPaint.color = context.resources.getColor(R.color.color_gray_ae)
        mTextPaint.textSize = context.sp2px(14).toFloat()
        mLinePaint.color = context.resources.getColor(R.color.color_gray_eb)
    }

    override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
        if (parent.adapter is CarBrandAdapter) {
            val adapter = parent.adapter as CarBrandAdapter
            if (adapter.itemCount == 0) {
                return
            }
            val position = parent.getChildAdapterPosition(view)
            if (adapter.isHeaderItem(position)) {
                outRect.top = mItemHeaderHeight
            } else {
                outRect.top = 1
            }
        }
    }

    override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
        if (parent.adapter is CarBrandAdapter) {
            val adapter = parent.adapter as CarBrandAdapter
            if (adapter.itemCount == 0) {
                return
            }
            for (i in 0 until parent.childCount) {
                val view = parent.getChildAt(i)
                val position = parent.getChildAdapterPosition(view)
                if (adapter.isHeaderItem(position)) {
                    c.drawRect(0f, (view.top - mItemHeaderHeight).toFloat(), parent.width.toFloat(), view.top.toFloat(), mItemHeaderPaint)
                    val textRect = Rect()
                    if (adapter.hasHotCity() && position == 0) {
                        mTextPaint.getTextBounds(hotCityText, 0, hotCityText.length, textRect)
                        c.drawText(hotCityText, context.dp2px(15).toFloat(),
                                ((view.top - mItemHeaderHeight) + mItemHeaderHeight / 2 + textRect.height() / 2).toFloat(), mTextPaint)
                    } else {
                        mTextPaint.getTextBounds(adapter.getGroupName(position), 0, adapter.getGroupName(position).length, textRect)
                        c.drawText(adapter.getGroupName(position), context.dp2px(15).toFloat(),
                                ((view.top - mItemHeaderHeight) + mItemHeaderHeight / 2 + textRect.height() / 2).toFloat(), mTextPaint)
                    }
                } else {
                    c.drawRect(0f, view.top - 1f, parent.width.toFloat(), view.top.toFloat(), mLinePaint)
                }
            }
        }
    }

    override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
        if (parent.adapter is CarBrandAdapter) {
            val adapter = parent.adapter as CarBrandAdapter
            if (adapter.itemCount == 0) {
                return
            }
            val position = (parent.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
            val view = parent.findViewHolderForAdapterPosition(position)?.itemView!!
            if (adapter.isHeaderItem(position + 1)) {
                val bottom = Math.min(mItemHeaderHeight, view.bottom)
                c.drawRect(0f, view.top - mItemHeaderHeight.toFloat(), parent.width.toFloat(), bottom.toFloat(), mItemHeaderPaint)
                val textRect = Rect()
                if (adapter.hasHotCity() && position == 0) {
                    mTextPaint.getTextBounds(hotCityText, 0, hotCityText.length, textRect)
                    c.drawText(hotCityText, context.dp2px(15).toFloat(), mItemHeaderHeight.toFloat() / 2 + textRect.height() / 2 - (mItemHeaderHeight - bottom), mTextPaint)
                } else {
                    mTextPaint.getTextBounds(adapter.getGroupName(position), 0, adapter.getGroupName(position).length, textRect)
                    c.drawText(adapter.getGroupName(position), context.dp2px(15).toFloat(), mItemHeaderHeight.toFloat() / 2 + textRect.height() / 2 - (mItemHeaderHeight - bottom), mTextPaint)
                }
            } else {
                c.drawRect(0f, 0f, parent.width.toFloat(), mItemHeaderHeight.toFloat(), mItemHeaderPaint)
                val textRect = Rect()
                if (adapter.hasHotCity() && position == 0) {
                    mTextPaint.getTextBounds(hotCityText, 0, hotCityText.length, textRect)
                    c.drawText(hotCityText, context.dp2px(15).toFloat(), mItemHeaderHeight.toFloat() / 2 + textRect.height() / 2, mTextPaint)
                } else {
                    mTextPaint.getTextBounds(adapter.getGroupName(position), 0, adapter.getGroupName(position).length, textRect)
                    c.drawText(adapter.getGroupName(position), context.dp2px(15).toFloat(), mItemHeaderHeight.toFloat() / 2 + textRect.height() / 2, mTextPaint)
                }
            }
        }
    }
}

2、adapter

class CarBrandAdapter(var context: Context, var mList: MutableList<CarBrand>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    private var hotList = mutableListOf<CarBrand>()
    var layoutManager: LinearLayoutManager? = null
    private var onCarBrandSelectedListener: OnCarBrandSelectedListener?= null

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        if (viewType == 0)
            return BrandViewHolder(LayoutInflater.from(context).inflate(R.layout.layout_item_car_brand, parent, false))
        return HotBrandViewHolder(LayoutInflater.from(context).inflate(R.layout.layout_hot_brand, parent, false))
    }

    override fun getItemCount(): Int {
        if (hotList.isEmpty()) {
            return mList.size
        }
        return mList.size + 1
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        if (holder is BrandViewHolder) {
            val realPosition = getRealPosition(position)
            if (mList[realPosition].carIcon.isNotNil()) {
                holder.ivLogo.display(mList[realPosition].carIcon)
            } else {
                holder.ivLogo.setImageResource(R.mipmap.default_product)
            }
            holder.tvBrand.text = mList[realPosition].brandName
            holder.clContent.onClick {
                onCarBrandSelectedListener?.apply {
                    this.onCarBrandSelected(mList[realPosition])
                }
            }
        } else if (holder is HotBrandViewHolder) {
            holder.rvHotBrand.layoutManager = GridLayoutManager(context, 5)
            val adapter = BaseRecyclerViewAdapter(context, hotList, R.layout.layout_tab_item_5_1) { view, carBrand, position1 ->
                if (carBrand.carIcon.isNotNil()) {
                    view.findViewById<ImageView>(R.id.iv_icon).display(carBrand.carIcon)
                } else {
                    view.findViewById<ImageView>(R.id.iv_icon).setImageResource(R.mipmap.default_product)
                }
                view.findViewById<TextView>(R.id.tv_text).text = carBrand.brandName
            }
            adapter.setOnItemClickListener(object : BaseRecyclerViewAdapter.OnItemClickListener {
                override fun onItemClick(v: View, position: Int) {
                    onCarBrandSelectedListener?.apply {
                        this.onCarBrandSelected(hotList[position])
                    }
                }
            })
            holder.rvHotBrand.adapter = adapter
        }
    }

    override fun getItemViewType(position: Int): Int {
        if (position == 0 && hotList.isNotEmpty()) {
            return 1
        }
        return 0
    }

    fun scrollToSection(index: String) {
        if (index == "#" && hotList.isNotEmpty() && layoutManager != null) {
            layoutManager?.scrollToPositionWithOffset(0, 0)
            return
        }
        if (mList.isEmpty()) return
        if (index.isEmpty()) return
        for (i in 0 until mList.size) {
            if (index == mList[i].headChar.substring(0, 1)) {
                if (layoutManager != null) {
                    layoutManager?.scrollToPositionWithOffset(i, 0)
                    return
                }
            }
        }
    }

    fun isHeaderItem(position: Int): Boolean {
        if (position == 0) {
            return true
        } else {
            val realPosition = getRealPosition(position)

            val lastGroupName = if (realPosition == 0) "#" else mList[realPosition - 1].headChar.substring(0, 1)
            val currGroupName = mList[realPosition].headChar.substring(0, 1)
            return lastGroupName != currGroupName
        }
    }

    fun getGroupName(position: Int): String {
        if (position == 0 && hotList.isNotEmpty()) {
            return hotList[0].headChar.substring(0, 1)
        }
        return mList[getRealPosition(position)].headChar.substring(0, 1)
    }

    fun getRealPosition(position: Int): Int {
        return if (itemCount == mList.size) position else position - 1
    }

    fun hasHotCity(): Boolean {
        return hotList.isNotEmpty()
    }

    fun setData(data: List<CarBrand>, hotList: List<CarBrand>) {
        mList.clear()
        mList.addAll(data)
        this.hotList.clear()
        this.hotList.addAll(hotList)
        notifyDataSetChanged()
    }

    fun setOnCarBrandSelectedLsitener(onCarBrandSelectedListener: OnCarBrandSelectedListener) {
        this.onCarBrandSelectedListener = onCarBrandSelectedListener
    }

    class BrandViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        var clContent = itemView.findViewById<ConstraintLayout>(R.id.cl_content)
        var ivLogo = itemView.findViewById<ImageView>(R.id.iv_logo)
        var tvBrand = itemView.findViewById<TextView>(R.id.tv_brand)
    }

    class HotBrandViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        var rvHotBrand = itemView.findViewById<RecyclerView>(R.id.rv_hot)
    }

    interface OnCarBrandSelectedListener {
        fun onCarBrandSelected(brand: CarBrand)
    }
}

3、数据结构

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

推荐阅读更多精彩内容