ViewPager2
本来想继续Navigation组件了,这是我之前学习jetpack时,最先接触的组件。但是感觉到Navigation,差不多可以实战了。而且网站里面已经有了Navigation组件的详细教程:https://www.sunofbeach.net/a/1302274897367535616
那我们还是先使用kotlin把常用jetpack库过一遍。
继续吧!
定义
class ViewPager2 : ViewGroup
ViewPager2作为ViewPager的升级替代,除了具体ViewPager的屏幕滑动功能外,还解决了ViewPager存在的大部问题,如支持:从右到左的布局(RTL)、垂直方向、处理 Fragment 的数据集等。
截止2020年10月,稳定版本为:版本 1.0.0
//添加依赖
implementation "androidx.viewpager2:viewpager2:1.0.0"
//1.0.0 的主要功能
/*
-- 对之前的 ViewPager 实现的改进:
RTL(从右向左)布局支持
垂直方向支持
可靠的 Fragment 支持(包括处理底层 Fragment 集合的更改)
数据集更改动画(包括 DiffUtil 支持)
从之前的 ViewPager 实现中轻松迁移(API 尽可能一致)。
*/
ViewPager2 内部使用了Recycleview,因此如果你熟悉Recycleview,很容易上手。
如果你之前使用了viewpager,请忘记那些让人迷惑的PagerAdapter、 FragmentPagerAdapter、 FragmentStatePagerAdapter,只需要记住 RecyclerView.Adapter即可。
从官方示例开始
官方文档:使用 ViewPager2 在 Fragment 之间滑动
接下来,我们根据官方的介绍,一步步改造,深入了解ViewPager2的基本用法:
新建一个androidX项目
添加依赖
implementation "androidx.viewpager2:viewpager2:1.0.0"
XML中局部ViewPager2
默认的activity_main.xml 修改如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
tools:context=".MainActivity">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
现在运行项目,一片空白,什么也没有!
自定义FragmentStateAdapter
前面提到,ViewPager2内部使用了Recyclerview, 因为必须设置Adapter才可以使用。
class SlideAdapter (activity: AppCompatActivity):FragmentStateAdapter(activity) {
// 数据也可以外部传入,本实例为了简化提前初始化好了
companion object {
val datas = arrayOf(
R.string.page1_text_1,
R.string.page2_text_2,
R.string.page2_text_3
)
}
// 返回共几个页面
override fun getItemCount(): Int {
return datas.size
}
// 返回每个页面的Fragment
override fun createFragment(position: Int): Fragment {
return SlidePageFragment.getInstance(position)
}
}
创建fragments for ViewPager2
即一个居中的TextView, fragment_slide_page.xml文件如下:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView style="?android:textAppearanceMedium"
android:id="@+id/fragmentTv"
android:padding="16dp"
android:lineSpacingMultiplier="1.2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textSize="30sp"
android:gravity="center"
android:text="@string/lorem_ipsum" />
</FrameLayout>
对应的Fragment类如下SlidePageFragment.kt:
class SlidePageFragment():Fragment() {
companion object {
const val ARG_POSITION = "position"
// 创建一个Fragment实例,并通过arguments传参
fun getInstance(position: Int): Fragment {
val slidePageFragment = SlidePageFragment()
val bundle = Bundle()
bundle.putInt(ARG_POSITION, position)
slidePageFragment.arguments = bundle
return slidePageFragment
}
}
// Fragment 加载布局
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_slide_page, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val position = requireArguments().getInt(ARG_POSITION)
// 设置fragment 中的TextView
fragmentTv.setText(SlideAdapter.datas[position])
}
}
MainActivity中使用
即setAdapter设置ViewPager2的适配器
class MainActivity : AppCompatActivity() {
private lateinit var viewPager: ViewPager2
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewPager = findViewById(R.id.pager)
val pagerAdapter = SlideAdapter(this)
viewPager.adapter = pagerAdapter
}
}
好了,一个简单的页面滑动就完成了。
具体效果就不截图了,可以gitee上下载上述实例代码自己运行或敲一遍。
设置垂直方向滑动
class MainActivity : AppCompatActivity() {
private lateinit var viewPager: ViewPager2
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// …………
// 设置垂直方向,添加一行代码即可
viewPager.orientation = ViewPager2.ORIENTATION_VERTICAL
}
}
查看setOrientation的源码,可知,实际上就是调用了LinearLayoutManager的setOrientation方法
//LinearLayoutManager
public void setOrientation(@Orientation int orientation) {
mLayoutManager.setOrientation(orientation);
mAccessibilityProvider.onSetOrientation();
}
RTL支持
//调用setLayoutDirection方法
viewPager.layoutDirection = ViewPager2.LAYOUT_DIRECTION_RTL
监听滑动
同样也是很简单:
class MainActivity : AppCompatActivity() {
private lateinit var viewPager: ViewPager2
private lateinit var pageChangeCallback:ViewPager2.OnPageChangeCallback
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// …………
// 监听滑动
pageChangeCallback = object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
Toast.makeText(this@MainActivity, "Page: ${position+1}",
Toast.LENGTH_SHORT).show()
}
}
viewPager.registerOnPageChangeCallback(pageChangeCallback)
}
}
关联Tablayout
添加依赖
implementation 'com.google.android.material:material:1.3.0-alpha03'
XML 中添加tablayout
activity_main.xml 中添加布局
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
tools:context=".MainActivity">
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
app:tabMode="scrollable"
app:tabTextColor="@android:color/white" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
使用TabsLayoutMediator
class MainActivity : AppCompatActivity() {
private lateinit var viewPager: ViewPager2
private lateinit var pageChangeCallback:ViewPager2.OnPageChangeCallback
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// …………
// 关联tabLayout
TabLayoutMediator(tabLayout, viewPager) { tab, position ->
tab.text = pagerAdapter.getPageTitle(position)
}.attach()
}
}
适当修改SlideAdapter.kt, 增加getPageTitle方法:
class SlideAdapter (private val activity: AppCompatActivity):FragmentStateAdapter(activity) {
companion object {
val datas = arrayOf(
R.string.page1_text_1,
R.string.page2_text_2,
R.string.page2_text_3
)
}
override fun getItemCount(): Int {
return datas.size
}
override fun createFragment(position: Int): Fragment {
return SlidePageFragment.getInstance(position)
}
// 获取页面标题
fun getPageTitle(position: Int): CharSequence? = activity.getString(datas[position])
}
总结ViewPager2使用套路
上面的实例虽然简单,但是也可以看出ViewPager2的使用套路:
- build.grade 中添加androidx.viewpager2:viewpager2 依赖
- 在XML中添加ViewPager2布局
- 自定义Adapter继承自FragmentStateAdapter 或 RecyclerView.Adapter
- 设置ViewPager2的adapter
- 根据需要和设置监听或和TabLayout、BottomNavigationView、或Navigation配合使用
什么时候需要自定义FragmentStateAdapter而不是继承RecyclerView.Adapter呢?
//FragmentStateAdapter 也是继承自 RecyclerView.Adapter
public abstract class FragmentStateAdapter extends
RecyclerView.Adapter<FragmentViewHolder> implements StatefulAdapter {
}
自定义FragmentStateAdapter主要场景是页面中使用了fragment,FragmentStateAdapter自动处理了很多fragment相关的事情,如FragmentManager、Lifecycle等。
更多参考实例
官方提供了更多的实例,如添加自定义动画,使用DiffUtil,支持嵌套滚动等,具体可以参考:
https://github.com/android/views-widgets-samples/tree/master/ViewPager2
本系列学习笔记的示例代码也上传到了gitee上:https://gitee.com/itshizhan/android-jetpack-demos