一、前言:
Tablayout继承自HorizontalScrollView,用作页面切换指示器,因使用简便功能强大而广泛使用在App中。
先上效果图:分别为设置tab属性、去掉指示线、设置指示线长度、设置图标tab、超出屏幕滚动tab

二、使用:
1、TabLayout的一些基本属性:
app:tabIndicatorColor :指示线的颜色
app:tabIndicatorHeight :指示线的高度
app:tabSelectedTextColor : tab选中时的字体颜色
app:tabTextColor:tab未被选中时的字体颜色
app:tabMode="scrollable" : 默认是fixed,固定的;scrollable:可滚动的
2、高度设置为0:
给tabIndicatorHeight属性设置0dp,或者给tabSelectedTextColor属性设置透明,就不显示指示线了。
app:tabIndicatorHeight ="0dp"
还可以设置成透明色:
app:tabIndicatorColor="@color/transparent"
3、设置默认图标:

still easy,Tablayout自带了setIcon()方法设置图标资源,不过这中效果很别扭,脸被拉长了。不服,就自己造一个啊,造就造!
tabLayout.getTabAt(i).setText(titles[i]).setIcon(pics[i]);
4、自己造tab样式(推荐这种方式):
具体的详细使用查看: 四、自定义tabLayout布局(推荐这个)

1、创建图标和文字布局(下方还可以定义下划线):
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:gravity="center">
<ImageView
android:id="@+id/imageview"
android:layout_gravity="center"
android:layout_width="24dp"
android:layout_height="24dp" />
<TextView
android:id="@+id/textview"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:textSize="14sp"
android:layout_marginLeft="8dp"/>
</LinearLayout>
2、这里是图标居左,那我要改为图标上下左右呢?都自定义view了,你上天都行。
/**
* 设置自定义位置图标
*/
private void setCustomIcon() {
//创建多少个tab
tabLayout2 = (TabLayout) findViewById(R.id.tablayout2);
for(int i=0;i<titles.length;i++){
tabLayout2.addTab(tabLayout2.newTab());
}
for(int i=0;i<titles.length;i++){
//给每个tab设置自定义布局
tabLayout2.getTabAt(i).setCustomView(makeTabView(i));
}
}
/**
* 引入布局设置图标和标题(引入布局的使用)
* @param position
* @return
*/
private View makeTabView(int position){
View tabView = LayoutInflater.from(this).inflate(R.layout.tab_text_icon,null);
TextView textView = tabView.findViewById(R.id.textview);
ImageView imageView = tabView.findViewById(R.id.imageview);
textView.setText(titles[position]);
imageView.setImageResource(pics[position]);
return tabView;
}
5、tab数量太多,超出屏幕,就像今日头条的分类一样,那就挤在一起了啊。不怕,我们有app:tabMode="scrollable" 属性,让Tablayout变得可滚动,可超出屏幕。

布局引入:
<android.support.design.widget.TabLayout
android:id="@+id/tablayout3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabSelectedTextColor="@color/green"
android:layout_marginTop="20dp"
app:tabMode="scrollable"
android:background="@color/white"/>
三、自己写的demo(推荐这个):

注意:下划线颜色设置透明色,就可以使用自定义的下划线颜色
//下划线颜色设置透明色,就会隐藏掉
app:tabIndicatorColor = "@color/transparent"
1、TabLayoutMediator 找不到
TabLayoutMediator 是一个非常新的类,它是在 Android Jetpack 库的 1.2.0 版本中引入的,并且只与 ViewPager2 兼容。
如果您想使用 TabLayoutMediator 类来将 TabLayout 和 ViewPager2 集成在一起,您需要确保使用了最新版本的 Android Jetpack 库。
同时,您需要在项目的构建脚本中添加以下依赖项:
dependencies {
// ...
implementation 'com.google.android.material:material:1.3.0' // 替换为最新版本
}
然后,在代码中,您可以按照以下方式使用 TabLayoutMediator:
2、activity
package com.kana.moonlight
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import com.kana.moonlight.databinding.ActivityMainBinding
import com.kana.moonlight.databinding.SearchTabItemBinding
class MainActivity : AppCompatActivity() {
val tabNames = arrayListOf<String>("测试1", "测试2", "测试3", "测试4", "测试5")
val childFragments = arrayListOf<Fragment>()
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
childFragments.add(FirstFragment("第一个页面"))
childFragments.add(FirstFragment("第二个页面"))
childFragments.add(FirstFragment("第三个页面"))
childFragments.add(FirstFragment("第四个页面"))
childFragments.add(FirstFragment("第五个页面"))
//初始化tab
initTabButton()
}
private fun initTabButton() {
//创建对应的tabLayout
tabNames.forEachIndexed { _index, it ->
binding.tablayout.addTab(binding.tablayout.newTab().setText(it))
}
//设置adapter
binding.viewpager.offscreenPageLimit = 1
binding.viewpager.adapter = fragmentAdapter
//暂时用不到(TabLayoutMediator中已经包含左右滑动状态选中了)
/* binding.viewpager.registerOnPageChangeCallback(object :
ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
}
})*/
/**
* 选中状态监听
*/
binding.tablayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) {
//选中状态
updateTabView(tab, true)
}
override fun onTabUnselected(tab: TabLayout.Tab?) {
//未选中状态
updateTabView(tab, false)
}
override fun onTabReselected(tab: TabLayout.Tab?) {
}
})
/**
* 关联tablayout和viewpager
*/
TabLayoutMediator(binding.tablayout, binding.viewpager, false, true) { tab, position ->
//设置自定义布局
tab.customView = getTabView(tabNames[position])
}.attach()
//选中第一个tab
binding.tablayout.getTabAt(0)?.select()
}
/**
* 创建自定义布局,并设置tabName
*/
private fun getTabView(tab: String): View {
val binding = DataBindingUtil.inflate<SearchTabItemBinding>(
layoutInflater,
R.layout.search_tab_item,
null,
true
)
//设置tabName的值
binding.tabName.text = tab
return binding.root
}
/**
* 更新tabName字体颜色、大小等和下划线是否显示
*/
private fun updateTabView(tab: TabLayout.Tab?, isSelect: Boolean) {
Log.d("lyy", "updateTabView: tab=${tab?.position}, isSelectd=$isSelect")
tab?.customView?.let {
//tab文字
val tabName = it.findViewById<TextView>(R.id.tab_name)
//下划线
val ivIndicator = it.findViewById<ImageView>(R.id.iv_indicator)
if (isSelect) {
//设置字体大小、加粗等
val paint = tabName.paint
paint.isFakeBoldText = true
tabName.textSize = 16f
//设置字体颜色
tabName.setTextColor(ContextCompat.getColor(this, R.color.teal_700))
//设置下划线可见
ivIndicator.visibility = View.VISIBLE
} else {
//设置字体大小、加粗等
val paint = tabName.paint
paint.isFakeBoldText = false
tabName.textSize = 14f
//设置字体颜色
tabName.setTextColor(ContextCompat.getColor(this, R.color.read_bg_8))
//设置下划线不可见
ivIndicator.visibility = View.INVISIBLE
}
}
}
/**
* viewPager adapter
*/
private val fragmentAdapter: FragmentStateAdapter by lazy {
object : FragmentStateAdapter(this) {
override fun getItemCount(): Int {
return tabNames.size
}
override fun createFragment(position: Int): Fragment {
return childFragments[position]
}
}
}
}
3、activity_main
<?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=".MainActivity">
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="tab切换"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:textSize="22sp"
android:textColor="#f00"
/>
<com.google.android.material.tabs.TabLayout
android:id="@+id/tablayout"
android:layout_width="match_parent"
android:layout_height="44dp"
android:layout_marginTop="10dp"
android:shadowColor="#f0000000"
android:shadowDx="0"
android:shadowDy="2"
app:tabGravity="fill"
app:tabIndicatorFullWidth="false"
app:tabIndicatorHeight="0dp"
app:tabMinWidth="30dp"
app:tabMode="scrollable"
app:tabPaddingEnd="18dp"
app:tabPaddingStart="12dp"
app:tabRippleColor="@android:color/transparent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_title"
/>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="2dp"
app:layout_constraintTop_toBottomOf="@+id/tablayout"
app:layout_constraintBottom_toBottomOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
4、FirstFragment
class FirstFragment(var tabName:String?="") : Fragment() {
private var _binding: FragmentFirstBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentFirstBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.textviewFirst.text = tabName
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
5、fragment_first
<?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=".FirstFragment">
<TextView
android:id="@+id/textview_first"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_first_fragment"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="100dp"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
6、自定义 search_tab_item.xml
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<data></data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tab_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="全部"
android:textColor="@color/black_3"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/iv_indicator"
android:layout_width="20dp"
android:layout_height="4dp"
android:layout_marginTop="2dp"
android:background="@drawable/btn_shap_19"
android:visibility="invisible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tab_name" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
7、下划线:btn_shap_19
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient android:startColor="#FA731D"
android:endColor="#FE9D04" android:angle="180" />
<corners android:radius="@dimen/ui_dp_2"/>
</shape>
四、自定义tabLayout布局
效果图:

dependencies {
// 不包含tablayout和viewPage2的绑定方法
implementation 'com.google.android.material:material:1.0.0'
}
使用这种方式的原因:我想升级material的版本号,发现和项目中的很多jar包冲突,资源文件报错等,改了好久,还是不行,就放弃升级material的版本号,采用手动绑定tablayout和viewPage2的关联。
注意:下划线颜色设置透明色,就可以使用自定义的下划线颜色
//下划线颜色设置透明色,就会隐藏掉
app:tabIndicatorColor = "@color/transparent"
特别注意:由于tablayout和viewPage2没有关联,所以点击按钮不会切换,只会显示第一个页面,左右滑动没有问题,所以在此手动切换fragment的跳转。
//获取对应的下标
var position= tab?.position ?: 0
//跳转对应的fragment页面
binding.viewpager.currentItem = position
1、主页面调用:RankingActivity
/**
* 最新榜单页面
*/
class RankingActivity : BaseActivity<NewSearchViewModel, ActivityRankingBinding>() {
val tabNames = arrayListOf<String>("全部", "男频", "女频")
val childFragments = arrayListOf<Fragment>()
override fun initView(savedInstanceState: Bundle?) {
childFragments.add(RankingFragment(1))
childFragments.add(RankingFragment(2))
childFragments.add(RankingFragment(3))
//初始化tab
initTabButton(mViewBind)
}
private fun initTabButton(binding: ActivityRankingBinding) {
tabNames.forEachIndexed { _index, it ->
//创建tab
val tab = binding.tablayout.newTab()
//自定义布局
tab.setCustomView(R.layout.search_tab_item)
tab.customView?.findViewById<TextView>(R.id.tab_name)?.text = it
binding.tablayout.addTab(tab)
}
binding.viewpager.offscreenPageLimit = 1
binding.viewpager.adapter = fragmentAdapter
//这个方法必须注册,否则左右滑动,tab没有选中状态
binding.viewpager.registerOnPageChangeCallback(object :
ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
//左右滑动时调用
binding.tablayout.getTabAt(position)?.select()
}
})
//点击时调用
binding.tablayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) {
//更新按钮选中状态
updateTabView(tab, true)
//注意:由于tablayout和viewPage2没有关联,所以点击按钮不会切换,只会显示第一个页面,左右滑动没有问题,所以在此手动切换fragment的跳转。
//获取对应的下标
var position= tab?.position ?: 0
//跳转对应的fragment页面
binding.viewpager.currentItem = position
}
override fun onTabUnselected(tab: TabLayout.Tab?) {
updateTabView(tab, false)
}
override fun onTabReselected(tab: TabLayout.Tab?) {
}
})
//默认第一个选中
val tabFirst = binding.tablayout.getTabAt(0)
tabFirst?.select()
updateTabView(tabFirst, true)
}
/**
* 更新tab的字体大小、颜色或者下划线显示
*/
private fun updateTabView(tab: TabLayout.Tab?, isSelect: Boolean) {
Log.d("home", "updateTabView: tab=${tab?.position}, isSelectd=$isSelect")
tab?.customView.let {
val tabName: TextView? = it?.findViewById(R.id.tab_name)
val ivIndicator: ImageView? = it?.findViewById(R.id.iv_indicator)
if (isSelect) {
//设置字体类型、大小
val paint = tabName?.paint
paint?.isFakeBoldText = true
tabName?.textSize = 18f
//下划线显示
ivIndicator?.visibility = View.VISIBLE
//设置tab字体颜色
tabName?.setTextColor(ContextCompat.getColor(this, R.color.color_FF9017))
} else {
//设置字体类型、大小
val paint = tabName?.paint
paint?.isFakeBoldText = false
tabName?.textSize = 16f
//下划线隐藏
ivIndicator?.visibility = View.INVISIBLE
//设置tab字体颜色
tabName?.setTextColor(ContextCompat.getColor(this, R.color.black_3))
}
}
}
/**
* viewPager adapter
*/
private val fragmentAdapter: FragmentStateAdapter by lazy {
object : FragmentStateAdapter(this) {
override fun getItemCount(): Int {
return tabNames.size
}
override fun createFragment(position: Int): Fragment {
return childFragments[position]
}
}
}
}
2、主布局:activity_ranking.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:ignore="UseAppTint"
>
<data>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cl_top"
android:layout_width="match_parent"
android:layout_height="@dimen/ui_dp_54"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
>
<ImageView
android:id="@+id/iv_back"
android:layout_width="38dp"
android:layout_height="28dp"
android:layout_centerVertical="true"
android:layout_gravity="center_vertical"
android:src="@mipmap/back_black"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/tablayout"
app:layout_constraintBottom_toBottomOf="@+id/tablayout"
android:layout_marginStart="@dimen/ui_dp_5"
android:paddingLeft="@dimen/ui_dp_10"
android:paddingRight="@dimen/ui_dp_10"
android:paddingVertical="@dimen/ui_dp_5"
/>
<com.google.android.material.tabs.TabLayout
android:id="@+id/tablayout"
android:layout_width="0dp"
android:layout_height="44dp"
android:layout_marginTop="10dp"
android:shadowColor="#f0000000"
android:shadowDx="0"
android:shadowDy="2"
app:tabGravity="fill"
app:tabIndicatorFullWidth="false"
app:tabIndicatorHeight="4dp"
app:tabMinWidth="30dp"
app:tabMode="scrollable"
app:tabPaddingEnd="12dp"
app:tabPaddingStart="12dp"
app:tabRippleColor="@android:color/transparent"
app:layout_constraintStart_toEndOf="@+id/iv_back"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="@dimen/ui_dp_8"
app:tabIndicatorColor = "@color/transparent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@+id/cl_top"
app:layout_constraintBottom_toBottomOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
3、自定义布局:search_tab_item.xml
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<data></data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tab_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="全部"
android:textColor="@color/black_3"
android:textSize="@dimen/ui_sp_16"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/iv_indicator"
android:layout_width="@dimen/ui_dp_20"
android:layout_height="@dimen/ui_dp_4"
android:layout_marginTop="@dimen/ui_dp_2"
android:background="@drawable/btn_shap_19"
android:visibility="invisible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tab_name" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
4、下划线:btn_shap_19
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient android:startColor="#FA731D"
android:endColor="#FE9D04" android:angle="180" />
<corners android:radius="@dimen/ui_dp_2"/>
</shape>
5、Fragment 的页面:RankingFragment
/**
* 排行榜页面
* type : 1、全部;2、男频;3、女频
*/
class RankingFragment constructor(typePage: Int? = 0) :
BaseFragment<MaleFemaleFragmentViewModel, FragmentRankingBinding>() {
/**
* type : 1、全部;2、男频;3、女频
*/
private var typePage = typePage
override fun initView(savedInstanceState: Bundle?) {
if (2== typePage){
mViewBind.tvTitle.text = "男频页面"
}else if (3== typePage){
mViewBind.tvTitle.text = "女频页面"
}else{
mViewBind.tvTitle.text = "全部页面"
}
}
}
五、tabLayout的对齐方式
1、居中对齐

设置以下三条属性即可
app:tabMode="fixed"
app:tabMaxWidth="0dp"
app:tabGravity="fill"
2、左侧开始排,显示不下,左右滑动

app:tabMode="scrollable"
app:tabGravity="fill"
app:tabMode="scrollable"
