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

ViewPager迁移至ViewPager2后,如何实现PagerTitleStrip功能?

如何在ViewPager2中实现类似PagerTitleStrip的效果

嘿,我懂你的困扰——ViewPager2是基于RecyclerView完全重构的,原ViewPager里的PagerTitleStrip确实没法直接适配。不过我们有两种方式可以复刻它的核心效果(就是那种跟随页面滑动时标题条位移、选中标题高亮的交互),下面给你详细拆解:

方案一:自定义View实现核心效果

这种方式可以1:1还原PagerTitleStrip的行为,完全由你掌控样式和交互逻辑,步骤如下:

1. 自定义TitleStrip组件

创建一个继承自LinearLayout的自定义View,用来管理标题的展示和滑动位移:

class ViewPager2TitleStrip @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {

    private var viewPager2: ViewPager2? = null
    private val titleViews = mutableListOf<TextView>()
    private var currentPage = 0
    private var scrollOffset = 0f

    init {
        orientation = HORIZONTAL
        gravity = CENTER_VERTICAL
        setPadding(dp2px(16), 0, dp2px(16), 0)
    }

    // 绑定ViewPager2并监听滑动事件
    fun attachToViewPager(pager: ViewPager2) {
        viewPager2 = pager
        pager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
            override fun onPageScrolled(
                position: Int,
                positionOffset: Float,
                positionOffsetPixels: Int
            ) {
                currentPage = position
                scrollOffset = positionOffset
                updateTitleScrollPosition()
            }

            override fun onPageSelected(position: Int) {
                updateTitleStyle()
            }
        })
        initTitles()
    }

    // 从Adapter获取标题并生成TextView
    private fun initTitles() {
        removeAllViews()
        titleViews.clear()
        val adapter = viewPager2?.adapter ?: return
        for (i in 0 until adapter.itemCount) {
            val title = (adapter as? PageTitleProvider)?.getPageTitle(i) ?: ""
            val tv = TextView(context).apply {
                text = title
                textSize = 16f
                setTextColor(Color.GRAY)
                layoutParams = LayoutParams(
                    LayoutParams.WRAP_CONTENT,
                    LayoutParams.MATCH_PARENT
                ).apply {
                    marginStart = dp2px(16)
                    marginEnd = dp2px(16)
                }
            }
            addView(tv)
            titleViews.add(tv)
        }
        updateTitleStyle()
    }

    // 跟随页面滑动,调整标题条的滚动位置
    private fun updateTitleScrollPosition() {
        if (titleViews.size <= 1) return
        // 计算偏移量,让选中标题始终靠近屏幕中心
        val scrollX = (width / 2 - titleViews[currentPage].width / 2) * scrollOffset
        scrollTo(scrollX.toInt(), 0)
    }

    // 更新选中/未选中标题的样式(颜色、大小)
    private fun updateTitleStyle() {
        titleViews.forEachIndexed { index, tv ->
            if (index == currentPage) {
                tv.setTextColor(Color.BLACK)
                tv.textSize = 20f
            } else {
                tv.setTextColor(Color.GRAY)
                tv.textSize = 16f
            }
        }
    }

    private fun dp2px(dp: Int): Int {
        return (dp * resources.displayMetrics.density).toInt()
    }

    // 定义接口,让Adapter提供页面标题
    interface PageTitleProvider {
        fun getPageTitle(position: Int): CharSequence
    }
}

2. 让PagerAdapter实现标题提供接口

你的ViewPager2适配器需要实现上面的PageTitleProvider接口,用来给标题条提供每个页面的标题:

class MyPagerAdapter : RecyclerView.Adapter<MyPagerAdapter.ViewHolder>(), ViewPager2TitleStrip.PageTitleProvider {

    private val pageTitles = listOf("首页", "发现", "我的", "设置")

    override fun getPageTitle(position: Int): CharSequence {
        return pageTitles[position]
    }

    // 以下是Adapter常规实现
    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.item_page, parent, false)
        return ViewHolder(view)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        // 绑定页面内容
    }

    override fun getItemCount(): Int = pageTitles.size
}

3. 在布局中使用自定义标题条

把ViewPager2TitleStrip放在ViewPager2的上方/下方,和原PagerTitleStrip的布局方式一致:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.yourpackage.ViewPager2TitleStrip
        android:id="@+id/titleStrip"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#ffffff"/>

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/viewPager2"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>

</LinearLayout>

4. 在代码中绑定组件

在Activity/Fragment里完成标题条和ViewPager2的绑定:

val viewPager2 = findViewById<ViewPager2>(R.id.viewPager2)
val titleStrip = findViewById<ViewPager2TitleStrip>(R.id.titleStrip)

viewPager2.adapter = MyPagerAdapter()
titleStrip.attachToViewPager(viewPager2)

方案二:使用第三方适配库

有些第三方库已经封装好了ViewPager2的TitleStrip实现,比如ViewPager2Extensions这类库,它们直接提供了和原PagerTitleStrip用法几乎一致的组件,省去自定义的麻烦。不过要注意选择维护活跃的库,避免版本兼容问题。

关键注意点

  • 原PagerTitleStrip的核心是跟随页面滑动的位移效果,自定义实现时重点要处理onPageScrolled回调中的偏移量计算,让标题条整体滚动来匹配页面位置。
  • 如果只是需要固定顶部的标签切换,TabLayout配合TabLayoutMediator确实更简单,但这和PagerTitleStrip的交互逻辑完全不同——所以如果你要的是滑动时标题跟着移动的效果,还是自定义或用第三方库更合适。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 08:52:48