IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android RecycleView 吸顶功能 支持LinearLayoutManager,GridLayoutManager,StaggeredGridLayoutManager -> 正文阅读

[移动开发]Android RecycleView 吸顶功能 支持LinearLayoutManager,GridLayoutManager,StaggeredGridLayoutManager

???????? RecycleView吸顶功能,从网络上随便下载了一张图,类似于下图这种,


具体怎么做呢。其实可以通过 自定义 :RecyclerView.ItemDecoration? 实现

先说 RecyclerView.ItemDecoration? 里面常用的方法:

getItemOffsets() ---  设置 item 的间距,这个方法比较常用
onDrawOver() --- 绘制在item之上的,就是用这个来控制吸顶
onDraw() --- 绘制 item 里面的内容

这么讲不好理解,直接上图,图片也是在网上扣的,

?

其实就是在对应标题的 Item ,绘制在其上面。然后我们不管当前Item是否是标题类型,我们都把它绘制在屏幕顶部,也就是 top = 0 的地方,这样就有一块东西是固定在列表顶部显示。

然后如果新的标题即将推到列表顶部,此时就需要把就旧的标题推出屏幕,把 top = 负数,慢慢推出屏幕顶部。

But..........talk is cheap,show u my code

先上 Activity 和 对应 xml 的代码

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".top.StickyTopActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycleView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
class StickyTopActivity : AppCompatActivity() {

    companion object {
        fun launch(context: Context) {
            context.startActivity<StickyTopActivity>()
        }
    }

    /**
     *  自定义数据类型
     *  @param type 1为标题,2位普通类型
     * */
    data class MyData(val title: String, val type: Int, val typeTitle: String)

    private val items: ArrayList<MyData> = arrayListOf()

    private fun initData() {
        for (index in 0..1000) {
            if (index % 10 == 0) {
                // index 为 10的倍数,为标题
                items.add(MyData("标题 ${index / 10}", 1, "标题 ${index / 10}"))
            } else {
                // 其他的为普通内容
                items.add(MyData("内容${index}", 2, "标题 ${index / 10}"))
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_sticky_top)
        initData()
        recycleView.layoutManager = LinearLayoutManager(this)
        /**
         * 自定义 ItemDecoration,实现RecycleView 吸顶
         * */ 
        recycleView.addItemDecoration(TitleItemDecoration(this, object :
            TitleItemDecoration.TitleDecorationCallback {

            override fun isHeadItem(position: Int) = items[position].type == 1

            override fun getHeadTitle(position: Int) = items[position].typeTitle

        }))
        recycleView.adapter = StickyTopAdapter(items)
    }

}

吸顶功能就是通过自定义 ItemDecoration吸顶实现,其他的,和平常用RecycleView没区别。

当然,我顺便也贴一下Adapter 和 ViewHolder 的代码。

/** 
 * Description: Adapter 代码
 */
class StickyTopAdapter(private val items: ArrayList<StickyTopActivity.MyData>) :
    RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    override fun onCreateViewHolder(
        parent: ViewGroup,
        viewType: Int
    ): RecyclerView.ViewHolder {
        return if (viewType == 1) {
            val view = LayoutInflater.from(parent.context)
                .inflate(R.layout.item_head, parent, false)
            MyHeadViewHolder(
                view
            )
        } else {
            MyViewHolder(
                LayoutInflater.from(parent.context)
                    .inflate(R.layout.item_content, parent, false)
            )
        }
    }

    override fun getItemCount() = items.size

    override fun getItemViewType(position: Int) = items[position].type

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        if (holder is MyViewHolder) {
            holder.bind(items[position])
        } else if (holder is MyHeadViewHolder) {
            holder.bind(items[position])
        }
    }
}


/**
 * Description: HeadViewHolder 标题ViewHolder
 */
class MyHeadViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    private val textView = itemView.findViewById<TextView>(R.id.tvTitle)
    fun bind(data: StickyTopActivity.MyData) {
        textView.text = data.title
    }
}


/**
 * Description: 普通ViewHolder
 */
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    private val textView = itemView.findViewById<TextView>(R.id.textView)
    fun bind(data: StickyTopActivity.MyData) {
        textView.text = data.title
    }
}

item_head.xml  代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="44dp"
    android:background="#afefdd"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/tvTitle"
        android:layout_width="200dp"
        android:layout_height="44dp"
        android:gravity="center_vertical|left"
        android:textSize="14sp"
        android:textColor="#000000"
        android:paddingLeft="25dp"
        android:text="123"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>
item_content.xml 代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <TextView
        android:id="@+id/textView"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="#80475961"
        android:gravity="center"
        android:layout_margin="10dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

上面贴了这么多代码,都是平常用到的平平无奇的。

而实现吸顶的关键,就是自定义 RecyclerView.ItemDecoration 的实现,来,我们看下里面怎么实现。

class TitleItemDecoration(
    private val context: Context,
    private val callback: TitleDecorationCallback
) : RecyclerView.ItemDecoration() {

    private val mTitleHeight: Int

    // 直接引用标题ViewHolder对应的layout -- item_head.xml
    private val titleLayout: View = LayoutInflater.from(context).inflate(R.layout.item_head, null)
    private val tvTitle = titleLayout.findViewById<TextView>(R.id.tvTitle)

    init {
        /**
         * 手动测量头部所需高度,这里提醒一下,item_head.xml 这个layout文件里的控件的宽高,不要用 wrap_content 和 match_parent
         * 要明确标出宽高,才能测量出来,至于为什么,这个就得自己去探究自定义View 测量绘制的原理了,不是此文所介绍的,这里只做提醒
         * */
        titleLayout.measure(View.MeasureSpec.AT_MOST, View.MeasureSpec.UNSPECIFIED)
        mTitleHeight = titleLayout.measuredHeight
    }

    /**
     *      重写 onDrawOver()
     * */
    override fun onDrawOver(
        canvas: Canvas,
        recyclerView: RecyclerView,
        state: RecyclerView.State
    ) {
        super.onDrawOver(canvas, recyclerView, state)
        // 获取第一个可见 Item 对应的 Position
        val firstVisiblePosition = findFirstVisibleItemPosition(recyclerView.layoutManager!!)
        if (firstVisiblePosition <= -1 || firstVisiblePosition >= recyclerView.adapter!!.itemCount - 1) {
            // 安全检测,防止越界
            return
        }
        // 获取第一个可见 Item 对应 View
        val firstVisibleView =
            recyclerView.findViewHolderForAdapterPosition(firstVisiblePosition)!!.itemView

        // 因为我们要绘制在列表顶部,所以先获取RecycleView 左右上 三个坐标
        val left = recyclerView.paddingLeft
        val right = recyclerView.width - recyclerView.paddingRight
        var top = recyclerView.paddingTop
        
        /**
         * 这里要判断,我们下一行是否是标题,如果是,原来绘制在屏幕上的标题,就得推出屏幕顶部
         * 至于推出屏幕顶部距离多少,就得看下一个标题已经推进吸顶区域大多
         * 下面就是获取下一个标题推进吸顶区域的高度是多大
         * */ 
        if (nextLineIsTitle(
                firstVisibleView,
                firstVisiblePosition,
                recyclerView
            ) && firstVisibleView.bottom < mTitleHeight
        ) {
            top = if (mTitleHeight <= firstVisibleView.height) {
                val d = firstVisibleView.height - mTitleHeight
                /**
                 * 通常来说,这里这个d 是等于0的,因为吸顶区域的高度一般都会和列表里面的标题的高度是一模一样的
                 * firstVisibleView.top 就是第一个可见Item 的顶部,这里的top如果是负数,即说明 firstVisibleView已经有一部分
                 * 滑出屏幕了,这时候吸顶绘制的区域,也要跟随它
                 * */
                firstVisibleView.top + d
            } else {
                val d = mTitleHeight - firstVisibleView.height
                firstVisibleView.top - d
            }
        }
        // 去绘制头部
        drawTitle(canvas, top, firstVisiblePosition, left, right)
    }

    private fun drawTitle(canvas: Canvas, top: Int, position: Int, left: Int, right: Int) {
        canvas.save()
        // 设置偏移,dx=0,即代表向左对齐
        canvas.translate(0f, top.toFloat())
        tvTitle.text = callback.getHeadTitle(position)
        titleLayout.layout(left, 0, right, mTitleHeight)
        titleLayout.draw(canvas)
        canvas.restore()
    }

    
    private fun findFirstVisibleItemPosition(layoutManager: RecyclerView.LayoutManager): Int {
        return when (layoutManager) {
            is LinearLayoutManager -> {
                layoutManager.findFirstVisibleItemPosition()
            }
            is GridLayoutManager -> {
                layoutManager.findFirstVisibleItemPosition()
            }
            is StaggeredGridLayoutManager -> {
                layoutManager.findFirstVisibleItemPositions(null)[0]
            }
            else -> {
                throw RuntimeException("咱不支持 类型为:${layoutManager.javaClass.name} 的LayoutManager ,可以自己判断类型,转成自己的LayoutManager,去获取第一个可见Item的position ")
            }
        }
    }

    /**
     *      网格布局应该算下一行是否是Title,而不是算下一个Position
     *      @param 当前Item
     *      @param 当前position
     *      @param parent
     * */
    private fun nextLineIsTitle(
        currentView: View,
        currentPosition: Int,
        parent: RecyclerView
    ): Boolean {
        for (nextLinePosition in currentPosition + 1 until parent.adapter!!.itemCount) {
            val nextItemView = parent.findViewHolderForAdapterPosition(nextLinePosition)!!.itemView
            if (nextItemView.bottom > currentView.bottom) {
                // 找到下一行的 Position
                return callback.isHeadItem(nextLinePosition)
            }
        }
        return false
    }

    interface TitleDecorationCallback {
        /**
         *      当前 position 对应的ViewHolder 是否是标题类型
         * */
        fun isHeadItem(position: Int): Boolean

        /**
         *      当前 position 对应的ViewHolder 是属于哪一种标题类型
         * */
        fun getHeadTitle(position: Int): String
    }
}

具体原理和实现,代码和注释已经很清晰了。

运行起来,看结果

没问题!!!!

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章           查看所有文章
加:2022-01-29 23:11:58  更:2022-01-29 23:14:38 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 12:28:58-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码