Android实现CNN风格TabLayout:仅显示选中项前后半裁切标签
实现CNN风格的TabLayout:居中选中项+半裁切相邻标签
嘿,我之前刚好做过一模一样的需求!原生TabLayout的属性确实搞不定这种半裁切+居中的效果,得结合自定义TabView和手动控制滚动来实现,下面是我踩坑后总结的完整方案:
核心思路
要达到「选中Tab居中,前后各露一半」的效果,关键是:
- 让每个Tab的宽度固定为屏幕宽度的1/3——这样选中Tab占满中间1/3,左右Tab各占1/3,滚动后刚好露出一半
- 手动控制TabLayout的滚动位置,确保选中Tab始终居中
- 用自定义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
相关产品推荐
相关产品推荐

