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

Android实现CNN风格TabLayout:仅显示选中项前后半裁切标签

实现CNN风格的TabLayout:居中选中项+半裁切相邻标签

嘿,我之前刚好做过一模一样的需求!原生TabLayout的属性确实搞不定这种半裁切+居中的效果,得结合自定义TabView和手动控制滚动来实现,下面是我踩坑后总结的完整方案:

核心思路

要达到「选中Tab居中,前后各露一半」的效果,关键是:

  1. 让每个Tab的宽度固定为屏幕宽度的1/3——这样选中Tab占满中间1/3,左右Tab各占1/3,滚动后刚好露出一半
  2. 手动控制TabLayout的滚动位置,确保选中Tab始终居中
  3. 用自定义TabView替代原生Tab,自己控制内边距和测量逻辑(解决原生tabPadding不生效的问题)

步骤1:自定义ClippedTabView

这个View用来控制每个Tab的宽度,同时处理内边距:

class ClippedTabView(context: Context, attrs: AttributeSet?) : TextView(context, attrs) {
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        // 固定宽度为屏幕1/3,确保前后Tab刚好露出一半
        val screenWidth = Resources.getSystem().displayMetrics.widthPixels
        val targetWidth = screenWidth / 3
        super.onMeasure(
            MeasureSpec.makeMeasureSpec(targetWidth, MeasureSpec.EXACTLY),
            heightMeasureSpec
        )
    }
}

步骤2:布局文件配置TabLayout

注意要设置tabMode="scrollable",因为我们需要滚动效果:

<com.google.android.material.tabs.TabLayout
    android:id="@+id/tabLayout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:tabIndicatorColor="@color/your_primary_color"
    app:tabIndicatorHeight="3dp"
    app:tabMode="scrollable"
    app:tabTextColor="@color/gray"
    app:tabSelectedTextColor="@color/your_primary_color">

    <!-- 这里可以手动加Tab,也可以在代码中动态添加 -->
    <com.google.android.material.tabs.TabItem
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="热点新闻" />

    <com.google.android.material.tabs.TabItem
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="财经频道" />

    <com.google.android.material.tabs.TabItem
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="科技前沿" />

    <!-- 更多Tab... -->
</com.google.android.material.tabs.TabLayout>

步骤3:代码中绑定自定义TabView+控制滚动

在Activity/Fragment中初始化TabLayout,替换原生Tab为我们的自定义View,并处理选中后的居中逻辑:

val tabLayout = findViewById<TabLayout>(R.id.tabLayout)

// 给每个Tab绑定自定义ClippedTabView
for (i in 0 until tabLayout.tabCount) {
    val tab = tabLayout.getTabAt(i) ?: continue
    val customTab = ClippedTabView(this, null).apply {
        text = tab.text
        gravity = Gravity.CENTER
        // 这里设置上下内边距,替代原生tabPadding(原生属性对自定义View不生效)
        setPadding(0, 16.dpToPx(), 0, 16.dpToPx())
        textSize = 14f
    }
    tab.customView = customTab
}

// 监听Tab选中,手动滚动到居中位置
tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
    override fun onTabSelected(tab: TabLayout.Tab) {
        val screenWidth = Resources.getSystem().displayMetrics.widthPixels
        val tabWidth = screenWidth / 3
        // 计算滚动位置:让选中Tab的中心与屏幕中心对齐
        val scrollX = tab.position * tabWidth - (screenWidth - tabWidth) / 2
        tabLayout.scrollTo(scrollX, 0)
    }

    override fun onTabUnselected(tab: TabLayout.Tab) {}
    override fun onTabReselected(tab: TabLayout.Tab) {}
})

// 初始化时让第一个Tab居中显示
tabLayout.post {
    val screenWidth = Resources.getSystem().displayMetrics.widthPixels
    val tabWidth = screenWidth / 3
    val initScrollX = 0 * tabWidth - (screenWidth - tabWidth) / 2
    tabLayout.scrollTo(initScrollX, 0)
    tabLayout.getTabAt(0)?.select()
}

// 扩展函数:dp转px
fun Int.dpToPx(): Int {
    return (this * Resources.getSystem().displayMetrics.density).toInt()
}

步骤4:适配屏幕旋转(横竖屏切换)

在Activity中重写onConfigurationChanged,重新计算Tab宽度并调整滚动位置:

override fun onConfigurationChanged(newConfig: Configuration) {
    super.onConfigurationChanged(newConfig)
    val screenWidth = Resources.getSystem().displayMetrics.widthPixels
    val tabWidth = screenWidth / 3

    // 更新每个Tab的宽度
    for (i in 0 until tabLayout.tabCount) {
        val tab = tabLayout.getTabAt(i) ?: continue
        val customTab = tab.customView as? ClippedTabView
        customTab?.layoutParams?.width = tabWidth
        customTab?.requestLayout()
    }

    // 重新滚动到当前选中Tab的居中位置
    val selectedPos = tabLayout.selectedTabPosition
    val scrollX = selectedPos * tabWidth - (screenWidth - tabWidth) / 2
    tabLayout.scrollTo(scrollX, 0)
}

别忘了在AndroidManifest.xml中给Activity添加configChanges属性:

<activity
    android:name=".YourActivity"
    android:configChanges="orientation|screenSize|smallestScreenSize" />

关键问题解决

  • tabPadding不生效:原生TabLayout的tabPadding是作用在Tab容器上,自定义View需要自己设置padding(上面代码中已经用setPadding处理)
  • 半裁切效果:通过固定Tab宽度为屏幕1/3,配合滚动位置计算,自然实现前后Tab只显示一半的效果
  • 屏幕适配:所有尺寸计算都基于屏幕宽度,不管是手机还是平板、横竖屏都能自动适配

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 08:54:49