前言

ViewPager咱们在之前的文章也现已提到了,它是Android平台上的一个布局容器,用于完结多个页面的滑动切换。它通常用于构建用户界面中的多页内容,例如轮播图、图片浏览器、引导页等。ViewPager能够滑动切换不同的页面,而且支撑左右/上下滑动手势。

TabLayout是一个用于创立标签式导航栏的UI组件。它通常与ViewPager结合运用,用于展现ViewPager中不同页面的标题或图标,并供给切换页面的导航功能。TabLayout能够以标签的形式展现页面,运用户能够快速切换到所需的页面。

结合运用ViewPagerTabLayout能够为运用程序供给更好的用户体验和导航办法。ViewPager能够让用户经过滑动来浏览不同的页面,而TabLayout则供给了清晰的标签导航,运用户能够快速找到并切换到所需的页面。这种结合运用的形式在许多运用中被广泛采用。

TabLayout与ViewPager2的结合运用

要将 ViewPager2TabLayout 结合运用,能够依照以下过程进行操作:

  1. 在 XML 布局文件中增加 TabLayoutViewPager2
<?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">
    <com.google.android.material.tabs.TabLayout
        android:id="@+id/tabLayout"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        app:tabMode="fixed"
        app:tabGravity="fill"
        android:layout_marginBottom="10dp"
        app:layout_constraintBottom_toBottomOf="parent"/>
    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/viewPager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toTopOf="@+id/tabLayout"
        android:orientation="horizontal"/>
</androidx.constraintlayout.widget.ConstraintLayout>
  1. 在代码中获取 TabLayoutViewPager2 的实例:
val viewPager = binding.viewPager
val tabLayout = binding.tabLayout
  1. 创立 Fragment 列表和相应的标签标题:
val fragments = listOf(FirstFragment(), SecondFragment(), ThirdFragment())
val titles = listOf("Tab 1", "Tab 2", "Tab 3")
  1. 创立 FragmentStateAdapter 并设置给 ViewPager2
val adapter = MyFragmentStateAdapter(this, fragments)
viewPager.adapter = adapter
  1. ViewPager2TabLayout 绑定在一起:
TabLayoutMediator(tabLayout, viewPager) { tab, position ->
    tab.text = titles[position]
}.attach()

完好示例代码如下:

class MainActivity : AppCompatActivity() {
    companion object {
        const val TAG = "MainActivity"
    }
    private val binding by lazy {
        ActivityMainBinding.inflate(
            layoutInflater
        )
    }
    private val fragments = listOf(FirstFragment(), SecondFragment(), ThirdFragment())
    private val fragmentAdapter by lazy { MyFragmentStateAdapter(this, fragments) }
    private val titles = listOf("Tab 1", "Tab 2", "Tab 3")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)
        initView()
    }
    private fun initView() {
        val viewPager = binding.viewPager
        viewPager.adapter = fragmentAdapter
        val pageTransformer = CustomPageTransformer()
        viewPager.setPageTransformer(pageTransformer)
        viewPager.offscreenPageLimit = 3
        TabLayoutMediator(binding.tabLayout, viewPager) { tab, position ->
            tab.text = titles[position]
        }.attach()
    }
 }

其间, MyFragmentStateAdapter 是自界说的 FragmentStateAdapter

TabLayout要求运用的theme 必须是Theme.AppCompat,所以运转前需要注意:

  1. 运用的Theme必须是Theme.AppCompat及其子主题;
  2. ActivityMainBinding.inflate( )中的LayoutInflater不能是LayoutInflater.from(baseContext)或许LayoutInflater.from(applicationContext)

假如不满意以上两点,会报如下过错:

android.view.InflateException: Binary XML file line #15 in com.example.viewpager2demo:layout/activity_main: Binary XML file line #15 in com.example.viewpager2demo:layout/activity_main: Error inflating class com.google.android.material.tabs.TabLayout
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:4324)
Caused by: android.view.InflateException: Binary XML file line #15 in com.example.viewpager2demo:layout/activity_main: Binary XML file line #15 in com.example.viewpager2demo:layout/activity_main: Error inflating class com.google.android.material.tabs.TabLayout
Caused by: android.view.InflateException: Binary XML file line #15 in com.example.viewpager2demo:layout/activity_main: Error inflating class com.google.android.material.tabs.TabLayout
Caused by: java.lang.reflect.InvocationTargetException
at java.lang.reflect.Constructor.newInstance0(Native Method)
at android.view.LayoutInflater.inflate(LayoutInflater.java:708)
at android.view.LayoutInflater.inflate(LayoutInflater.java:552)
at com.example.viewpager2demo.databinding.ActivityMainBinding.inflate(ActivityMainBinding.java:50)
at com.example.viewpager2demo.databinding.ActivityMainBinding.inflate(ActivityMainBinding.java:44)
at com.example.viewpager2demo.MainActivity$binding$2.invoke(MainActivity1.kt:18)
at com.example.viewpager2demo.MainActivity$binding$2.invoke(MainActivity1.kt:17)
at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
at com.example.viewpager2demo.MainActivity.getBinding(MainActivity1.kt:17)
at com.example.viewpager2demo.MainActivity.onCreate(MainActivity1.kt:29)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:582)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:968)
Caused by: java.lang.IllegalArgumentException: The style on this component requires your app theme to be Theme.AppCompat (or a descendant).
at com.google.android.material.internal.ThemeEnforcement.checkTheme(ThemeEnforcement.java:241)
at com.google.android.material.internal.ThemeEnforcement.checkAppCompatTheme(ThemeEnforcement.java:211)
at com.google.android.material.internal.ThemeEnforcement.checkCompatibleTheme(ThemeEnforcement.java:146)
at com.google.android.material.internal.ThemeEnforcement.obtainStyledAttributes(ThemeEnforcement.java:75)
at com.google.android.material.tabs.TabLayout.<init>(TabLayout.java:509)
at com.google.android.material.tabs.TabLayout.<init>(TabLayout.java:489)
... 32 more

上面第二点会导致主题过错,是我始料未及的,而假如context填入this(即activity)则不会犯错,所以抱着猎奇的心态去了解了一下,定论有两点:

  1. LayoutInflater.from() 中传入 this baseContext/applicationContext 会得到不同的 LayoutInflater 对象,是因为Activity继承自ContextThemeWrapper,而ContextThemeWrapper中重写了getSystemService办法; 具体能够看blog.csdn.net/cj_286/arti…

  2. ContextThemeWrapper 和它的 mBase 成员在 Resource 以及 Theme 相关的行为上是不同的 概况能够检查/post/684490…

回到本文的主题,将工程运转起来,作用如下:

ViewPager2系列--与TabLayout的结合

此刻,简略的与TabLayout结合运用示例便完结了,接下来咱们学习设置TabLayout的标签和款式

自界说TabLayout的标签和款式

TabLayout特点设置

TabLayout 供给了许多特点,用于自界说标签的外观和行为。以下是一些常用的 TabLayout 特点:

特点 描述
tabMode 设置 Tab 的形式,可选值为 “fixed”(固定形式)、 “scrollable”(可翻滚形式)、”auto”(主动形式,会根据屏幕宽度和Tab个数主动挑选固定形式或许可翻滚形式),可翻滚形式下Tab能够像列表相同翻滚
tabGravity 设置 Tab 的对齐办法,可选值为 “fill”(填充办法)、”center”(居中办法)、”start”(开始对齐办法)
tabIndicatorColor 设置指示器(下划线)的色彩
tabIndicatorHeight 设置指示器的高度
tabBackground 设置标签的布景
tabTextColor 设置标签的文本色彩
tabTextAppearance 设置标签的文本款式
tabSelectedTextColor 设置选中标签的文本色彩
tabRippleColor 设置标签的点击作用色彩
tabIconTint 设置标签图标的着色色彩
tabIconSize 设置标签图标的尺寸
tabContentStart 设置标签内容的开始边距
tabContentEnd 设置标签内容的末尾边距

这些特点能够在 XML 布局文件中经过 app 命名空间来设置,例如:

<com.google.android.material.tabs.TabLayout
    android:id="@+id/tabLayout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:tabMode="scrollable"
    app:tabGravity="center"
    app:tabIndicatorColor="@color/tab_indicator_color"
    app:tabTextAppearance="@style/TabTextAppearance"
    app:tabSelectedTextColor="@color/tab_selected_text_color" />

自界说Tab款式

在大多数运用中,TabLayout自带的特点都不能满意规划的要求,需要咱们自界说Tab款式来完结,比方:

ViewPager2系列--与TabLayout的结合

为了完结这个需求,咱们需要做以下几件事:

  1. Tab指示器高度设为0,从而隐藏下划线指示器
<com.google.android.material.tabs.TabLayout
    android:id="@+id/tabLayout"
    android:layout_width="match_parent"
    android:layout_height="100dp"
    app:tabMode="fixed"
    app:tabGravity="fill"
    app:tabIndicatorHeight="0dp"
    android:layout_marginBottom="10dp"
    app:layout_constraintBottom_toBottomOf="parent"/>
  1. 自界说Tab款式的XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:gravity="center">
    <ImageView
        android:id="@+id/tab_icon"
        android:layout_width="30dp"
        android:layout_height="30dp"
        tools:src="@drawable/ic_instagram_default" />
    <TextView
        android:id="@+id/tab_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:textSize="20sp"
        tools:text="Instagram" />
</LinearLayout>
  1. 界说Tab的text以及选中未选中状况的Icon:
private val tabTitles = listOf("Instagram", "Wechat", "Twitter")
private val tabIcons = listOf(R.drawable.ic_instagram_default, R.drawable.ic_wechat_default, R.drawable.ic_twitter_default)
private val tabSelectedIcons = listOf(R.drawable.ic_instagram_selected, R.drawable.ic_wechat_selected, R.drawable.ic_twitter_selected)
  1. 设置Tab自界说View,并关联ViewPager:
TabLayoutMediator(binding.tabLayout, viewPager) { tab, position ->
    val customTabView = ItemCustomTabBinding.inflate(layoutInflater)
    customTabView.tabText.text = tabTitles[position]
    customTabView.tabIcon.setImageDrawable(getDrawable(tabIcons[position]))
    tab.customView = customTabView.root
}.attach()
  1. 增加Tab选中/未选中监听,请注意:Tab监听要放在Tab初始化(即第4步的设置)的前面,不然初始状况会不符合预期:
binding.tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
    @RequiresApi(Build.VERSION_CODES.M)
    override fun onTabSelected(tab: TabLayout.Tab) {
        // Tab 被选中
        val position = tab.position
        tab.customView?.let {
            val title = it.findViewById<TextView>(R.id.tab_text)
            val icon = it.findViewById<ImageView>(R.id.tab_icon)
            title.setTextColor(getColor(R.color.green))
            icon.setImageDrawable(getDrawable(tabSelectedIcons[position]))
        }
    }
    @RequiresApi(Build.VERSION_CODES.M)
    override fun onTabUnselected(tab: TabLayout.Tab) {
        // Tab 撤销选中
        val position = tab.position
        tab.customView?.let {
            val title = it.findViewById<TextView>(R.id.tab_text)
            val icon = it.findViewById<ImageView>(R.id.tab_icon)
            title.setTextColor(getColor(R.color.black))
            icon.setImageDrawable(getDrawable(tabIcons[position]))
        }
    }
    override fun onTabReselected(tab: TabLayout.Tab) {
        // Tab 被从头选中(点击已选中的 Tab)
    }
})

整体代码如下:


class MainActivity : AppCompatActivity() {
    companion object {
        const val TAG = "MainActivity"
    }
    private val binding by lazy {
        ActivityMainBinding.inflate(
            layoutInflater
        )
    }
    private val fragments = listOf(FirstFragment(), SecondFragment(), ThirdFragment())
    private val fragmentAdapter by lazy { MyFragmentStateAdapter(this, fragments) }
    private val tabTitles = listOf("Instagram", "Wechat", "Twitter")
    private val tabIcons = listOf(R.drawable.ic_instagram_default, R.drawable.ic_wechat_default, R.drawable.ic_twitter_default)
    private val tabSelectedIcons = listOf(R.drawable.ic_instagram_selected, R.drawable.ic_wechat_selected, R.drawable.ic_twitter_selected)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)
        initView()
    }
    private fun initView() {
        val viewPager = binding.viewPager
        viewPager.adapter = fragmentAdapter
        val pageTransformer = CustomPageTransformer()
        viewPager.setPageTransformer(pageTransformer)
        viewPager.offscreenPageLimit = 3
        binding.tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
            @RequiresApi(Build.VERSION_CODES.M)
            override fun onTabSelected(tab: TabLayout.Tab) {
                // Tab 被选中
                val position = tab.position
                tab.customView?.let {
                    val title = it.findViewById<TextView>(R.id.tab_text)
                    val icon = it.findViewById<ImageView>(R.id.tab_icon)
                    title.setTextColor(getColor(R.color.green))
                    icon.setImageDrawable(getDrawable(tabSelectedIcons[position]))
                }
            }
            @RequiresApi(Build.VERSION_CODES.M)
            override fun onTabUnselected(tab: TabLayout.Tab) {
                // Tab 撤销选中
                val position = tab.position
                tab.customView?.let {
                    val title = it.findViewById<TextView>(R.id.tab_text)
                    val icon = it.findViewById<ImageView>(R.id.tab_icon)
                    title.setTextColor(getColor(R.color.black))
                    icon.setImageDrawable(getDrawable(tabIcons[position]))
                }
            }
            override fun onTabReselected(tab: TabLayout.Tab) {
                // Tab 被从头选中(点击已选中的 Tab)
            }
        })
        TabLayoutMediator(binding.tabLayout, viewPager) { tab, position ->
            val customTabView = ItemCustomTabBinding.inflate(layoutInflater)
            customTabView.tabText.text = tabTitles[position]
            customTabView.tabIcon.setImageDrawable(getDrawable(tabIcons[position]))
            tab.customView = customTabView.root
        }.attach()
    }
    private fun printLog(msg: String) {
        Log.d(TAG, msg)
    }
}

至此,就能够完结自定Tab款式的需求了