前言
ViewPager
咱们在之前的文章也现已提到了,它是Android平台上的一个布局容器,用于完结多个页面的滑动切换。它通常用于构建用户界面中的多页内容,例如轮播图、图片浏览器、引导页等。ViewPager
能够滑动切换不同的页面,而且支撑左右/上下滑动手势。
TabLayout
是一个用于创立标签式导航栏的UI组件。它通常与ViewPager
结合运用,用于展现ViewPager
中不同页面的标题或图标,并供给切换页面的导航功能。TabLayout
能够以标签的形式展现页面,运用户能够快速切换到所需的页面。
结合运用ViewPager
和TabLayout
能够为运用程序供给更好的用户体验和导航办法。ViewPager
能够让用户经过滑动来浏览不同的页面,而TabLayout
则供给了清晰的标签导航,运用户能够快速找到并切换到所需的页面。这种结合运用的形式在许多运用中被广泛采用。
TabLayout与ViewPager2的结合运用
要将 ViewPager2
和 TabLayout
结合运用,能够依照以下过程进行操作:
- 在 XML 布局文件中增加
TabLayout
和ViewPager2
:
<?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>
- 在代码中获取
TabLayout
和ViewPager2
的实例:
val viewPager = binding.viewPager
val tabLayout = binding.tabLayout
- 创立
Fragment
列表和相应的标签标题:
val fragments = listOf(FirstFragment(), SecondFragment(), ThirdFragment())
val titles = listOf("Tab 1", "Tab 2", "Tab 3")
- 创立
FragmentStateAdapter
并设置给ViewPager2
:
val adapter = MyFragmentStateAdapter(this, fragments)
viewPager.adapter = adapter
- 将
ViewPager2
和TabLayout
绑定在一起:
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
,所以运转前需要注意:
- 运用的
Theme
必须是Theme.AppCompat
及其子主题; -
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
)则不会犯错,所以抱着猎奇的心态去了解了一下,定论有两点:
-
LayoutInflater.from()
中传入this
和baseContext/applicationContext
会得到不同的LayoutInflater
对象,是因为Activity
继承自ContextThemeWrapper
,而ContextThemeWrapper
中重写了getSystemService
办法; 具体能够看blog.csdn.net/cj_286/arti… -
ContextThemeWrapper
和它的mBase
成员在Resource
以及Theme
相关的行为上是不同的 概况能够检查/post/684490…
回到本文的主题,将工程运转起来,作用如下:
此刻,简略的与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款式来完结,比方:
为了完结这个需求,咱们需要做以下几件事:
- 将
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"/>
- 自界说
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>
- 界说
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)
- 设置
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()
- 增加
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款式的需求了