viewpager2的使用与相关踩坑
·
一、使用
1. adapter
- adapter说明: ViewPager2的适配器通常继承于FragmentStateAdapter,而他的父类是RecyclerView.Adapter,所以直接继承recyclerView的适配器也可以使用。
- 具体使用场景的选择:
1.RecyclerView.Adapter:
该适配器中每个页面是一个View,写法跟RecyclerView的完全相同,适合UI跟点击等逻辑相同的简单页面。
/**
* ViewPager2使用RecyclerView.Adapter实现不同页面的点击事件
*/
class ViewPagerRecyclerAdapter(
private val pageDataList: List<PageData>,
private val onButtonClickListener: (position: Int) -> Unit,
private val onItemClickListener: (position: Int, item: String) -> Unit
) : RecyclerView.Adapter<ViewPagerRecyclerAdapter.PageViewHolder>() {
// 页面数据列表(每个页面可以有多个列表项)
private val pageItems = listOf(
listOf("项目1-1", "项目1-2", "项目1-3"),
listOf("项目2-1", "项目2-2", "项目2-3", "项目2-4"),
listOf("项目3-1", "项目3-2")
)
class PageViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val titleTextView: TextView = itemView.findViewById(R.id.titleTextView)
val contentTextView: TextView = itemView.findViewById(R.id.contentTextView)
val actionButton: Button = itemView.findViewById(R.id.actionButton)
val item1Button: Button = itemView.findViewById(R.id.item1Button)
val item2Button: Button = itemView.findViewById(R.id.item2Button)
val item3Button: Button = itemView.findViewById(R.id.item3Button)
val item4Button: Button = itemView.findViewById(R.id.item4Button)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PageViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_viewpager_page, parent, false)
return PageViewHolder(view)
}
override fun onBindViewHolder(holder: PageViewHolder, position: Int) {
val pageData = pageDataList[position]
val items = pageItems.getOrElse(position) { emptyList() }
// 设置页面基本信息
holder.titleTextView.text = pageData.title
holder.contentTextView.text = pageData.content
holder.actionButton.text = pageData.buttonText
holder.actionButton.setBackgroundColor(Color.parseColor(pageData.buttonColor))
holder.itemView.setBackgroundColor(Color.parseColor(pageData.backgroundColor))
// 设置按钮点击事件
holder.actionButton.setOnClickListener {
onButtonClickListener(position)
}
// 设置列表项点击事件
setupItemButtons(holder, position, items)
}
private fun setupItemButtons(holder: PageViewHolder, position: Int, items: List<String>) {
// 隐藏所有列表项按钮
holder.item1Button.visibility = View.GONE
holder.item2Button.visibility = View.GONE
holder.item3Button.visibility = View.GONE
holder.item4Button.visibility = View.GONE
// 根据页面的列表项数量显示对应按钮
items.forEachIndexed { index, item ->
when (index) {
0 -> {
holder.item1Button.text = item
holder.item1Button.visibility = View.VISIBLE
holder.item1Button.setOnClickListener {
onItemClickListener(position, item)
}
}
1 -> {
holder.item2Button.text = item
holder.item2Button.visibility = View.VISIBLE
holder.item2Button.setOnClickListener {
onItemClickListener(position, item)
}
}
2 -> {
holder.item3Button.text = item
holder.item3Button.visibility = View.VISIBLE
holder.item3Button.setOnClickListener {
onItemClickListener(position, item)
}
}
3 -> {
holder.item4Button.text = item
holder.item4Button.visibility = View.VISIBLE
holder.item4Button.setOnClickListener {
onItemClickListener(position, item)
}
}
}
}
}
override fun getItemCount(): Int = pageDataList.size
}
2.FragmentStateAdapter:适用于每页是独立的模块且需要生命周期,不同的页面需要对应的fragment来展示不同的逻辑。
1.相同展示内容的FragmentStateAdapter
class ViewpagerAdapter(val mvList: List<Mv>, fragment: Fragment) :
FragmentStateAdapter(fragment) {
/**
* 用于返回指定的frgment界面,这里建议将Fragment的创建通过共同类自定义的静态方法进行创建,具体设置可参考下面的实践代码中
*/
override fun createFragment(position: Int): Fragment =
MvViewPagerFragment.newInstance(mvList[position].rankid)
/**
* 用于加载容器大小
*/
override fun getItemCount(): Int = mvList.size
}
2.不同的页面展示时的FragmentStateAdapter设置
class ViewPagerAdapter(
private val data: List<String>,
fragmentActivity: FragmentActivity
) : FragmentStateAdapter(fragmentActivity) {
override fun createFragment(position: Int): Fragment = when (position) {
0 -> { //个数必须跟data的数量相对应
Fragment1()
}
1 -> {
Fragment2()
}
else -> {
MyFragment()
}
}
override fun getItemCount(): Int = data.size
}
三种构造器区别:
| FragmentStateAdapter(Fragment fragment) | FragmentStateAdapter(FragmentActivity activity) | FragmentStateAdapter(FragmentManager fragmentManager, Lifecycle lifecycle) | |
|---|---|---|---|
| 使用 | ViewPager2位于 Fragment中使用 | ViewPager2位于activity中使用 | 特殊场景中自定义生命周期或管理器时使用 |
| FragmentManager | 来自Fragment的childFragmentManager | 来自activity的supportFragmentManager | 来自于手动传入的 fragmentManager |
| Lifecycle | 来自Fragment的viewLifecycleOwner.lifecycle | 来自Activity 的 lifecycle | 来自手动传入的lifecycle |
| 生命周期 | 与Fragment视图生命周期绑定 | 与 Activity 生命周期绑定 | 自定义管理 |
2.viewpager具体的使用
适配器设置参考如下,下面两者的代码相同:
class ViewpagerAdapter(val data: List<String>, fragment: Fragment) :
FragmentStateAdapter(fragment) {
/**
* 用于返回指定的frgment界面,这里建议将Fragment的创建通过共同类自定义的静态方法进行创建,具体设置可参考下面的实践代码中
*/
override fun createFragment(position: Int): Fragment =
ViewPagerFragment.newInstance(data[position])
/**
* 用于加载容器大小
*/
override fun getItemCount(): Int = data.size
}
- 单独ViewPager2:
1.xml页面放入控件
<LinearLayout 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:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<!--android:orientation="vertical"可以通过该设置来支持垂直分页方向上的翻页,默认是水平 -->
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/mian_viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</LinearLayout>
2.创建存放滑动内容的容器fragment
class ViewPagerFragment : Fragment() {
private lateinit var text: TextView
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = LayoutInflater.from(context).inflate(R.layout.fragment_viewpager, null)
initView(view)
return view
}
private fun initView(view: View) {
text = view.findViewById(R.id.content)
}
override fun onResume() {
super.onResume()
val data = arguments?.getString(DATA)
text.text = data
}
companion object {
const val DATA = "DataBean"
fun newInstance(data: String): ViewPagerFragment = ViewPagerFragment().apply {
arguments = Bundle().apply {
putString(DATA,data)
}
}
}
}
3.进行初始化与适配器设置
class MainActivity : AppCompatActivity() {
private lateinit var viewPager: ViewPager2
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//1.控件初始化
viewPager = findViewById(R.id.mian_viewpager)
//2.设置需要的数据,并将展示的容器fragment传递
val data = mutableListOf<String>()
for (i in 1 until 11) {
data.add("页面"+i)
}
//3.设置适配器,并传递展示的fragment
viewPager.adapter = ViewPagerAdapter(data,this)
//通常可以设置较多的预加载,然后在onResume()中控制数据显示时再加载,避免了过多fragment的频繁销毁与创建
// mvViewPager.setOffscreenPageLimit(result.size)
}
}
2.tabLayout+ViewPager2时:
1.xml页面
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<!--app:tabIndicatorColor="@color/black" 设置指示器颜色-->
<!--app:tabIndicatorHeight="5dp"设置指示器高度-->
<!--app:tabIndicator=""设置自定义指示器图形-->
<!--app:tabIndicatorFullWidth="false"设置指示器宽度是否填满tab,true时充满,false时与文字等宽-->
<!--app:tabIndicatorGravity="top"设置指示器位置,顶部 底部等-->
<!--app:tabMode="auto"设置标签模式,fixed固定 scrollable滚动 auto自适应-->
<!--app:tabSelectedTextColor="@color/black"选中tab时的字体颜色-->
<!--app:tabTextColor="@color/black" 未选中tab时的字体颜色-->
<!-- app:tabContentStart="5dp"标签整体距离起始位置向右偏移了5dp-->
<!-- app:tabGravity="center"tab的对齐方式,tabMode="fixed"时会更有效-->
<com.google.android.material.tabs.TabLayout
android:id="@+id/mian_tablayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabMode="auto"
app:tabIndicatorColor="@color/black"
app:tabContentStart="50dp"
app:tabIndicatorHeight="5dp"
app:tabIndicatorFullWidth="true"
app:tabGravity="center"
app:tabTextColor="@color/black" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/mian_viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
2.在前面的基础上添加tablayout的初始化与联动设置
class MainActivity : AppCompatActivity() {
private lateinit var viewPager: ViewPager2
private lateinit var tabLayout: TabLayout
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//1.控件初始化
viewPager = findViewById(R.id.mian_viewpager)
//a1.tablayout初始化
tabLayout = findViewById(R.id.mian_tablayout)
//2.设置需要的数据,并将展示的容器fragment传递
val data = mutableListOf<String>()
for (i in 1 until 11) {
data.add("页面"+i)
}
//3.设置适配器,并传递展示的fragment
viewPager.adapter = ViewPagerAdapter(data,this)
//a2.将tablayout跟viewPager进行联动设置
TabLayoutMediator(tabLayout,viewPager,object : TabLayoutMediator.TabConfigurationStrategy{
override fun onConfigureTab(
tab: TabLayout.Tab,
position: Int
) {
//a3.展示标签内容
tab.text = data[position]
}
}).attach() //启动
}
}
二、相关踩坑记录
1.TabLayout联动时会强制ViewPager2预加载的问题
ViewPager2相比上一代最主要的问题是解决预加载问题,现版本只要不设置viewPager.setOffscreenPageLimit(result.size),默认的就是-1,因为不会预加载,但是,如果跟TabLayout联动时没有禁止平滑滚动,即便你没有设置预加载也会导致ViewPager预加载。
//解决办法如下:smoothScroll参数设置为false
TabLayoutMediator(
mvTabLayout,
mvViewPager, true,false, //这里false为了让tablayout点击时view pager不会是平滑滚动,从而出现禁止预加载失效的问题
object : TabLayoutMediator.TabConfigurationStrategy {
override fun onConfigureTab(
tab: TabLayout.Tab,
position: Int
) {
tab.text = data.rank.list[position].rankname
}
}).attach() //开始启动联动启动
2.数据的加载过早导致页面显示空白
当viewpager2+Tablayoutl联动时,如果没有进行上面记录1中禁止平滑滚动,那么即便没有设置预加载,也会有预加载出现,而且滑动页数超过了初始化预加载之后会产生空白页面现象,此时在viewpager里面的fragment中进行数据的设置时,最好将数据展示放在onResume()中,否则会因为数据加载完毕展示时点击后的页面却没有测量完毕的情况,从而导致页面空白,特别是fragment页面中有一个recyclerView进行数据更新时,或者用主线程的post{}确保布局绘制完成后再加载数据
/**
* 当view pager加载过多页面时,预加载数据太少会造成更多的fragment创建与销毁,
* 所以将预加载数据设置为页面数量,但是为了避免创建时多个网络请求的发起,所以将数据初始化放在onResume中只有显示的页面才发起网络请求
*/
override fun onResume() {
super.onResume()
Log.i("TAG", "onResume: isLaidOut=${recyclerView.isLaidOut}, 添加到窗口是否经过了layout" +
"recyclerView.width=${recyclerView.width}")
if (isFitrstLoad) {
isFitrstLoad = false
mvSongsPresenterImpl?.loadDatas(AppConstants.TYPE_INIT_REFRESH)
}
Log.i("MvViewPagerFragment", "onResume: ,isFitrstLoad =$isFitrstLoad ")
}
3.viewpager切换时出异常
如java.lang.IllegalStateException: Expected the adapter to be ‘fresh’ while restoring state.
解决:
//方案1.不保存状态,防止异常退出时恢复中出现bug,属于快捷粗暴的方法
mvViewPager.isSaveEnabled = false
方案2:根据异常代码具体的问题分别解决,具体可参考改内容:嵌套fragment使用ViewPager2在activity异常退出后重新创建报错解决
更多推荐



所有评论(0)