You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android如何实现滚动时可折叠展开的水平RecyclerView/ViewPager

RecyclerView滚动联动展开/折叠列表项实现方案

实现这个交互不需要引入第三方库,按以下三步做就能跑通:

  • 第一步:适配列表项双状态布局
    给你的列表项数据结构增加一个布尔类型的状态标记,比如isExpanded:

    • 展开态:展示完整内容,包括大图、多行文本、操作按钮等元素
    • 折叠态:只展示核心缩略信息,比如小尺寸头像、单行标题
      两种状态的控件放在同一个item布局文件里即可,通过控制控件的visibility属性切换,不需要写两套独立布局。
      两种状态的效果参考:
      列表项展开模式效果
      列表项折叠模式效果
  • 第二步:监听滚动方向
    给RecyclerView添加滚动监听器,通过滚动的垂直偏移量判断滚动方向,注意加滚动阈值防抖,避免轻微触碰就触发状态切换:

    // 先获取系统默认的触摸滑动阈值,用来做防抖
    val touchSlop = ViewConfiguration.get(context).scaledTouchSlop
    // 标记当前全局列表项的状态,默认展开
    private var isAllItemExpanded = true
    
    recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
        override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
            super.onScrolled(recyclerView, dx, dy)
            // 滑动距离小于阈值不触发状态切换
            if (abs(dy) < touchSlop) return
    
            when {
                // dy>0 代表手指向上滑,屏幕内容向下滚动,此时切折叠态
                dy > 0 && isAllItemExpanded -> {
                    updateItemExpandState(false)
                }
                // dy<0 代表手指向下滑,屏幕内容向上滚动,此时切展开态
                dy < 0 && !isAllItemExpanded -> {
                    updateItemExpandState(true)
                }
            }
        }
    })
    

    Java版本逻辑完全一致,只是语法写法不同。

  • 第三步:高效更新列表状态+过渡动画
    不要每次状态变化都调用notifyDataSetChanged()全量刷新,会造成卡顿和闪烁。只需要更新当前屏幕内可见的列表项即可,屏幕外的item在滑入的时候会在绑定方法里自动读取最新状态。配合TransitionManager可以自动生成顺滑的展开折叠动画:

    // 定义payload标记,用来做局部刷新
    val PAYLOAD_EXPAND_STATE = "expand_state_change"
    
    private fun updateItemExpandState(targetExpanded: Boolean) {
        isAllItemExpanded = targetExpanded
        // 更新所有数据源的状态
        dataList.forEach { it.isExpanded = targetExpanded }
    
        val lm = recyclerView.layoutManager as LinearLayoutManager
        val firstVisiblePos = lm.findFirstVisibleItemPosition()
        val lastVisiblePos = lm.findLastVisibleItemPosition()
        // 只刷新当前可见范围内的item,用payload做局部刷新
        adapter.notifyItemRangeChanged(
            firstVisiblePos,
            lastVisiblePos - firstVisiblePos + 1,
            PAYLOAD_EXPAND_STATE
        )
    }
    

    之后在Adapter里重写带payload参数的onBindViewHolder方法,收到状态更新的payload时,只做控件显隐切换,同时开启动画过渡即可:

    override fun onBindViewHolder(holder: MyViewHolder, position: Int, payloads: MutableList<Any>) {
        if (payloads.contains(PAYLOAD_EXPAND_STATE)) {
            // 开启动画过渡
            TransitionManager.beginDelayedTransition(holder.itemView as ViewGroup)
            // 根据当前状态切换控件显隐
            val item = dataList[position]
            holder.bigCover.visibility = if (item.isExpanded) View.VISIBLE else View.GONE
            holder.descText.visibility = if (item.isExpanded) View.VISIBLE else View.GONE
            holder.actionBtnGroup.visibility = if (item.isExpanded) View.VISIBLE else View.GONE
            return
        }
        // 没有payload走正常的全量绑定逻辑
        super.onBindViewHolder(holder, position, payloads)
    }
    

注意事项

  • 快速滑动时如果频繁触发状态更新,可以加一个时间节流阀,比如两次状态切换间隔不短于300ms,避免动画冲突
  • 如果列表项里有图片,折叠态和展开态尽量用同一张图片资源做缩放,不要切不同的图,避免切换时闪烁
  • 如果用的是GridLayoutManager或者StaggeredGridLayoutManager,找可见位置的方法换成对应LayoutManager的方法即可,逻辑不变

内容的提问来源于stack exchange,提问作者Mehrzad

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.16 23:21:21