我记得前年(2022)面试的时候有被问到 ViewPager 和 ViewPager2 有什么区别?当时因为之前工作一直在开发售货机相关的项目,使用的技术要求并不高,所以一直没去了解过 ViewPager2~ 去年的时候正好有相关的功能需求,索性直接用 ViewPager2 进行了
Tip
:很多人可能比较关注俩者区别、变更,那么我们结论先行,然后再接着验证
结论先行
关于它们的区别,我仅从我个人理解的角度来讲(不知不觉用了好几天…)
实现方面
ViewPager
继承自ViewGroup
,内部并未使用已有的成熟控件,更多的是自定义的操作ViewPager2
也继承自ViewGroup
,但其内部可以明显的看到RecyclerView
影子,所以可以说是基于RecyclerView
实现,那么这也意味着性能的提升,毕竟ViewHodelr
减少了内存开销,同时RecyclerView
相关方法在ViewPager2
也可以看到类似封装
功能方面
Tip
:ViewPager2
新增功能是建立在 ViewPager
已有功能的基础上的扩展,例如ViewPager2
支持垂直滑动,同时也是支持水平滑动的
ViewPager2
支持垂直方向滑动,而ViewPager
仅支持水平方向滑动(扩展组件功能)
关于ViewPager2
新支持的RTL方向布局
简单概述一下:国内一般都是默认的LTR方向布局
,但是针对国际用户会根据语言环境(阿拉伯语等)自动启动从右到左(RTL)页面布局,如果想要设置ViewPager2
布局方向可以通过设置android:layoutDirection
属性或setLayoutDirection()
方式
ViewPager2
支持RTL方向布局,而ViewPager
支持LTR方向布局(扩展组件功能)ViewPager2
DiffUtil
支持,减少页面刷新频率,当数据未发生变更时不必重新绘制(提升用户体验)
Adapter(适配器)方面
ViewPager2
使用的Adapter,一般为PagerAdapter
、及其子类FragmentPagerAdapter
、FragmentStatePagerAdapter
ViewPager2
既然基于RecyclerView
实现,那么它所使用的Adapter
同理也应该基于RecyclerView.Adapter
,所以新增了FragmentStateAdapter
加载方面
ViewPager
默认执行预加载
,如果需要懒加载的话,需要自行封装ViewPager2
默认执行懒加载
,但是依旧可以设置预加载
API方面
- 监听:
ViewPager2
的registerOnPageChangeCallback
取代了ViewPager
的addPageChangeListener
- 关联TabLayout :
ViewPager
与TabLayout
关联用的是TabLayout
的方法setupWithViewPager()
,ViewPager2
是通过TabLayoutMediator
类来做了个关联
年关将至
- 基础了解
- 实践效果
- 新增功能
- 源码解析
- 变更场景
- ViewPager、ViewPager2 中 引用对比
- ViewPager、ViewPager2 中 Adapter区别对比
- ViewPager、ViewPager2 中 Adapter使用对比
- ViewPager、ViewPager2 中 监听对比
- ViewPager、ViewPager2 中 预加载、懒加载对比
- ViewPager、ViewPager2 关联TabLayout
- 从ViewPager 迁移到 ViewPager2
- 实战演练
- Demo版本
- 前置配件
- ViewPager 使用方式
- ViewPager2 使用方式
- ViewPager、ViewPager2 一起使用
- 项目版本
- 有趣的小问题:java.lang.IllegalStateException: Fragment already added
基础了解
实践效果
Demo效果
项目效果
新增功能
如果想了解
ViewPager2
,我觉得最好的方式可能就是跟着 ViewPager2官方文档 简单的过一下版本的更新情况
从更新记录可以看出部分 ViewPager、ViewPager2 区别
ViewPager2
支持 RTL布局
(Right To Left?),ViewPager
仅支持LTR布局ViewPager2
支持 水平方向、垂直方向
(类似抖音、快手垂直切换视频的场景),ViewPager
仅支持横向滑动(水平方向)DiffUtil 支持,减少页面刷新频率,当数据未发生变更时不必重新绘制
(记得好早以前就有类似工具,不过现在很多框架内部都做了Diff操作)
2019年2月7日 ViewPager2
应运而生(首个测试版本)
2019年11月20日 ViewPager2
出了首个稳定的正式版本
源码解析
查看 ViewPager
可以看到其继承自 ViewGroup
,同时 内部貌似并未使用现有的View控件
查看 ViewPager2
发现它虽然同样继承自ViewGroup
,但其内部却是基于 RecyclerView
实现的,故 RecyclerView
具备的方法 ViewPager2
也可以尝试调用,常见的类似于LayoutManager
、ItemDecorator
等类似方法
例如 ViewPager2
支持垂直方向滑动就提供了setOrientation
方法(默认水平方向),那么通过源码可以看出这种方式其实类似RecyclerView
设置 LayoutManager
方式
setOrientation
方向提供了 ORIENTATION_HORIZONTAL
、ORIENTATION_VERTICAL
;在动态设置中伪代码 viewPager2.orientation = ORIENTATION_VERTICAL
变更场景
ViewPager、ViewPager2 中 引用对比
记得以前调用的是Suppor V4
包下的 ViewPager
,但现在不论是 ViewPager
、ViewPager2
都直接在 androidx
内了,基本创建项目后就可以直接引用了
ViewPager、ViewPager2 中 Adapter区别对比
ViewPager
源码注释中其实已经解释了Adapter
相关内容
ViewPager
使用的 Adapter
主要是 PagerAdapter
及其子类FragmentPagerAdapter
、FragmentStatePagerAdapter
,俩者的主要区别如下
FragmentPagerAdapter
支持缓存FragmentStatePagerAdapter
不支持缓存
PagerAdapter
PagerAdapter 子类
ViewPager2
- API变更
ViewPager2
使用的 Adapter
主要是 RecyclerView.Adapter
的子类 FragmentStateAdapter
,它也继承了RecyclerView
的优点,内置了FragmentViewHolder
提升了性能
FragmentStateAdapter
FragmentStateAdapter
提供了三种构造参数,支持绑定组件的生命周期
FragmentViewHolder
ViewPager、ViewPager2 中 Adapter使用对比
以前我在别的知识结构 也曾引用过ViewPager,如有需要也可以借鉴下
对比后发现继承的 Adapter
和重写方法命名发生变更,此处主要说 API变更
ViewPager2 getItemCount 等于(=) ViewPager 中的 getCount
ViewPager2 createFragment 等于(=) ViewPager 中的 getItem
ViewPager
PagerAdapter的子类Adapter已经被标记过时了,最好还是开始用ViewPager2
吧
示例
class viewPager1Adapter(manager: FragmentManager):FragmentStatePagerAdapter(manager){override fun getCount(): Int {TODO("Not yet implemented")}override fun getItem(position: Int): Fragment {TODO("Not yet implemented")}}
ViewPager2
class viewPager2Adapter(fragment: FragmentActivity):FragmentStateAdapter(fragment){override fun getItemCount(): Int {TODO("Not yet implemented")}override fun createFragment(position: Int): Fragment {TODO("Not yet implemented")}}
ViewPager、ViewPager2 中 监听对比
ViewPager
、ViewPager2
监听的内容相同,只有API简单变更了一下
ViewPager 通过 addOnPageChangeListener
添加监听
viewPager1.addOnPageChangeListener(object :OnPageChangeListener{override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {TODO("Not yet implemented")}override fun onPageSelected(position: Int) {TODO("Not yet implemented")}override fun onPageScrollStateChanged(state: Int) {TODO("Not yet implemented")}})
ViewPager2 通过 registerOnPageChangeCallback
添加监听,注册函数一般都有unregister
函数,不用的时候可以顺手注销一下,防止内存泄露
viewPager2.registerOnPageChangeCallback(object : OnPageChangeCallback() {override fun onPageScrollStateChanged(state: Int) {super.onPageScrollStateChanged(state)}override fun onPageSelected(position: Int) {super.onPageSelected(position)Log.e("tag",position.toString())}override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {super.onPageScrolled(position, positionOffset, positionOffsetPixels)}})
ViewPager、ViewPager2 中 预加载、懒加载对比
首先要明确一个概念:不论是
ViewPager
、ViewPager2
都支持预加载!
预加载的意义在于不必让用户每次都等切换页面后才执行接口请求、页面绘制等操作,而是直接显示效果,不过这样做性能开销方面会大一些,用户体验有时候需要看加载元素的多少而决定
懒加载顾名思义只有在用户需要的时候才去执行,其实现的核心意义在于
- 是否为当前页面(是否可见)
- 是否已经加载过了
- 视图是否初始化完成(
setUserVisibleHint()
的调用在onCreateView
之前)
预加载区别
ViewPager
默认调用了预加载方法,且默认预加载值为1(一般加载当前视图的左右相邻视图)
相关源码
ViewPager2
默认不进行预加载,相对应的预加载方法值为-1
相关源码
懒加载区别
ViewPager
并未实现懒加载,如果需要懒加载的话需要自行实现,通常在Fragment
场景下就是重写setUserVisibleHint
方法,然后继承于该基类ViewPager2
默认实现了懒加载, 主要是通过Lifecycle
对Fragment
的生命周期进行管理
ViewPager、ViewPager2 关联TabLayout
因为我在项目中的 TabLayout
部分布局需要自定义一下,所以并未直接使用 TabLayout
,有机会在详细说吧
ViewPager
和TabLayout
的代码关联用的是TabLayout
的方法setupWithViewPager()
ViewPager2
和TabLayout
的代码关联是通过TabLayoutMediator
类来做了关联
从ViewPager 迁移到 ViewPager2
具体迁移操作,可参考 从 ViewPager 迁移到 ViewPager2
实战演练
关于ViewPager2
的使用也可以看 官网代码示例:views-widgets-samples/ViewPager2
因为我Demo中并未结合TabLayout一起使用,如有需求也可以前往 用户指南:使用 ViewPager2 创建包含标签的滑动视图
Demo版本
同个页面使用
ViewPager
、ViewPager2
遇到一个有趣的场景,最后有讲到原因与处理方式
我写Demo时为了更直观的对比,所以将 ViewPager
、ViewPager2
一起使用;但是在使用时我分别简单讲解了不同的使用方式和综合方式
前置配件
activity_main
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat 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"android:orientation="vertical"tools:context=".MainActivity"><androidx.viewpager.widget.ViewPagerandroid:id="@+id/view_pager1"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1"android:background="#f78744" /><androidx.viewpager2.widget.ViewPager2android:id="@+id/view_pager2"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1"android:background="#d589" /></androidx.appcompat.widget.LinearLayoutCompat>
Tip
:Demo内的AFragment
、BFragment
基本一致,直接copy修改下想要布局就好
AFragment
package com.example.viewpager2import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
class AFragment: Fragment() {override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {return LayoutInflater.from(this.requireContext()).inflate(R.layout.fragment_a,null)}
}
fragment_a
<?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"><TextViewandroid:layout_width="match_parent"android:layout_height="match_parent"android:text="A-Fragment"android:gravity="center"android:textStyle="bold"/>
</LinearLayout>
ViewPager 使用方式
package com.example.viewpager2import android.annotation.SuppressLint
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentStatePagerAdapter
import androidx.viewpager.widget.ViewPager
import androidx.viewpager.widget.ViewPager.OnPageChangeListenerclass MainActivity : AppCompatActivity() {@SuppressLint("MissingInflatedId")private var fragmentList: MutableList<Fragment> = mutableListOf()private var fragmentListA: MutableList<Fragment> = mutableListOf()override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)var viewPager1 = findViewById<ViewPager>(R.id.view_pager1)//初始化数据fragmentList.add(AFragment())fragmentList.add(BFragment())//ViewPager使用val viewPager1Adapter = ViewPager1Adapter(supportFragmentManager,fragmentList)viewPager1.setOffscreenPageLimit(0);viewPager1.adapter = viewPager1Adapter
//viewPager1.addOnPageChangeListener(object : OnPageChangeListener {override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}override fun onPageSelected(position: Int) {}override fun onPageScrollStateChanged(state: Int) {}})}inner class ViewPager1Adapter(manager: FragmentManager, fragmentList: MutableList<Fragment>) : FragmentStatePagerAdapter(manager) {var manager: FragmentManagervar fragmentList: MutableList<Fragment> = mutableListOf()init {this.manager = managerthis.fragmentList = fragmentList}override fun getCount(): Int {return fragmentList.size}override fun getItem(position: Int): Fragment {return fragmentList[position]}}
}
ViewPager2 使用方式
package com.example.viewpager2import android.annotation.SuppressLint
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentStatePagerAdapter
import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2
import androidx.viewpager2.widget.ViewPager2.ORIENTATION_VERTICAL
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallbackclass MainActivity : AppCompatActivity() {@SuppressLint("MissingInflatedId")private var fragmentList: MutableList<Fragment> = mutableListOf()private var fragmentListA: MutableList<Fragment> = mutableListOf()override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)var viewPager2 = findViewById<ViewPager2>(R.id.view_pager2)//初始化数据fragmentList.add(AFragment())fragmentList.add(BFragment())//ViewPager2使用val viewPager2Adapter = ViewPager2Adapter(this)viewPager2.orientation = ORIENTATION_VERTICALviewPager2.adapter = viewPager2AdapterviewPager2.registerOnPageChangeCallback(object : OnPageChangeCallback() {override fun onPageScrollStateChanged(state: Int) {super.onPageScrollStateChanged(state)}override fun onPageSelected(position: Int) {super.onPageSelected(position)Log.e("tag", position.toString())}override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {super.onPageScrolled(position, positionOffset, positionOffsetPixels)}})}inner class ViewPager2Adapter(fragment: FragmentActivity) : FragmentStateAdapter(fragment) {override fun getItemCount(): Int {return fragmentListA.size}override fun createFragment(position: Int): Fragment {return fragmentListA[position]}}
}
ViewPager、ViewPager2 一起使用
为什么我会用到俩个List?最后告诉你...
package com.example.viewpager2import android.annotation.SuppressLint
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentStatePagerAdapter
import androidx.viewpager.widget.ViewPager
import androidx.viewpager.widget.ViewPager.OnPageChangeListener
import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2
import androidx.viewpager2.widget.ViewPager2.ORIENTATION_VERTICAL
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallbackclass MainActivity : AppCompatActivity() {@SuppressLint("MissingInflatedId")private var fragmentList: MutableList<Fragment> = mutableListOf()private var fragmentListA: MutableList<Fragment> = mutableListOf()override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)var viewPager1 = findViewById<ViewPager>(R.id.view_pager1)var viewPager2 = findViewById<ViewPager2>(R.id.view_pager2)//初始化数据fragmentList.add(AFragment())fragmentList.add(BFragment())fragmentListA.add(AFragment())fragmentListA.add(BFragment())//ViewPager使用val viewPager1Adapter = ViewPager1Adapter(supportFragmentManager,fragmentList)viewPager1.setOffscreenPageLimit(0);viewPager1.adapter = viewPager1Adapter
//viewPager1.addOnPageChangeListener(object : OnPageChangeListener {override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}override fun onPageSelected(position: Int) {}override fun onPageScrollStateChanged(state: Int) {}})//ViewPager2使用val viewPager2Adapter = ViewPager2Adapter(this)viewPager2.orientation = ORIENTATION_VERTICALviewPager2.adapter = viewPager2AdapterviewPager2.registerOnPageChangeCallback(object : OnPageChangeCallback() {override fun onPageScrollStateChanged(state: Int) {super.onPageScrollStateChanged(state)}override fun onPageSelected(position: Int) {super.onPageSelected(position)Log.e("tag", position.toString())}override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {super.onPageScrolled(position, positionOffset, positionOffsetPixels)}})}inner class ViewPager1Adapter(manager: FragmentManager, fragmentList: MutableList<Fragment>) : FragmentStatePagerAdapter(manager) {var manager: FragmentManagervar fragmentList: MutableList<Fragment> = mutableListOf()init {this.manager = managerthis.fragmentList = fragmentList}override fun getCount(): Int {return fragmentList.size}override fun getItem(position: Int): Fragment {return fragmentList[position]}}inner class ViewPager2Adapter(fragment: FragmentActivity) : FragmentStateAdapter(fragment) {override fun getItemCount(): Int {return fragmentListA.size}override fun createFragment(position: Int): Fragment {return fragmentListA[position]}}
}
项目版本
因为是在项目中使用的实践场景,所以只留伪代码做个记录;部分框架特有的方法可先行忽略、部分业务逻辑亦可忽略,只看类似 TabLayout + ViewPager2的效果即可
Activity
→ Fragment
数据共享采用了ViewModel
;接口返回数据监听采用了LiveData
;有兴趣的可以前往进阶
- 组件化之路 - ViewModel一知半解
- 组件化之路 - LiveData一知半解
- 组件化之路 - LiveData + ViewModel一知半解
组件中可以有多个ViewModel
,在这里为了区分功能一个 FundSmileViewModel
用于网络请求等逻辑操作,一个 FundCodeViewModel
用于存放共享数据(当然也可以只用一个)
package cn.com.xximport android.graphics.Color
import android.os.Bundle
import androidx.activity.viewModels
import androidx.core.graphics.toColorInt
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2
import dagger.hilt.android.AndroidEntryPoint@AndroidEntryPoint
class FundAndSmileActivity : BaseActivity() {private val binding by lazy { ActivityPurchaseAndSmileBinding.inflate(layoutInflater) }private val fundCodeViewModel by viewModels<FundCodeViewModel>()private val viewModel by viewModels<FundSmileViewModel>()private lateinit var fundCode: Stringprivate var fragmentList: MutableList<Fragment> = mutableListOf()override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(binding.root)ActivityTaskManager.getManager(TaskTag.FIXED_SMILE).addActivity(this)statusBar()setData()loadingState(LoadingState.LoadingStart)viewModel.requestFundControl5010(fundCode)binding.ivImgTag.isVisible = SPUtils.AppSP().get("fundSmileTag", "0") == "0"viewModel.smileState.observe(this) {loadingState(LoadingState.LoadingEnd)binding.llTab.isVisible = itfragmentList.clear()fragmentList.add(FundFixedCreateFragment())if (it) fragmentList.add(SmileInvestmentCreateFragment())initView()}}fun statusBar() {statusBarDarkView(binding.statusBar)binding.titleBarBg.setBackgroundColor(Color.WHITE)binding.titleBar.setLeftImageAction(R.drawable.icon_base_nav_back_black) { onBackPressedDispatcher.onBackPressed() }binding.titleBar.setTitle(text = "定投设置", textColor = "#484848".toColorInt(), medium = true)binding.titleBar.setDividerState(color = "#eeeeee".toColorInt())}fun setData() {fundCode = intent.getStringExtra("fundCode") ?: ""val pickerParams1 = intent?.getParcelable("pickerParams1", PickerParams::class)val pickerParams2 = intent?.getParcelable("pickerParams2", PickerParams::class)fundCodeViewModel.putFundCode(fundCode)pickerParams1?.let { fundCodeViewModel.putPickerParams1(it) }pickerParams2?.let { fundCodeViewModel.putPickerParams2(it) }}fun initView() {val pagerAdapter = PagerAdapter(this)binding.viewPage.isUserInputEnabled = false;//禁止滑动binding.viewPage.adapter = pagerAdapterbinding.viewPage.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {override fun onPageSelected(position: Int) {tabState(position)}})binding.llTab1.setOnClickListener {tabState(0)binding.viewPage.currentItem = 0}binding.llTab2.setOnClickListener {tabState(1)binding.viewPage.currentItem = 1}}fun tabState(state: Int) {binding.tvTabTitle1.setTextColor("#333333".toColorInt())binding.tvTabDesc1.setTextColor("#999999".toColorInt())binding.viewTab1.setBackgroundColor("#F9F9FA".toColorInt())binding.tvTabTitle2.setTextColor("#333333".toColorInt())binding.tvTabDesc2.setTextColor("#999999".toColorInt())binding.viewTab2.setBackgroundColor("#F9F9FA".toColorInt())if (state == 0) {binding.tvTabTitle1.setTextColor("#1760EA".toColorInt())binding.tvTabDesc1.setTextColor("#801760EA".toColorInt())binding.viewTab1.setBackgroundColor("#1760EA".toColorInt())} else {binding.tvTabTitle2.setTextColor("#1760EA".toColorInt())binding.tvTabDesc2.setTextColor("#801760EA".toColorInt())binding.viewTab2.setBackgroundColor("#1760EA".toColorInt())binding.ivImgTag.isVisible = falseSPUtils.AppSP().put("fundSmileTag", "1")}}inner class PagerAdapter(fragment: FragmentActivity) : FragmentStateAdapter(fragment) {override fun getItemCount(): Int {return fragmentList.size}override fun createFragment(position: Int): Fragment {return fragmentList[position]}}
}
activity_purchase_and_smile(ActivityPurchaseAndSmileBinding)
Tip
:部分自定义控件,自行取舍、更换即可
<?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:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"tools:ignore="MissingDefaultResource"><LinearLayoutandroid:id="@+id/title_bar_bg"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="@drawable/drawable_title_bar_blue"android:orientation="vertical"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent"><Viewandroid:id="@+id/status_bar"android:layout_width="match_parent"android:layout_height="0dp"tools:layout_height="28dp" /><cn.com.ui.widgets.TitleBarandroid:id="@+id/title_bar"android:layout_width="match_parent"android:layout_height="?actionBarSize"tools:layout_height="48dp" /></LinearLayout><LinearLayoutandroid:id="@+id/ll_tab"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="#F9F9FA"android:orientation="horizontal"android:visibility="gone"tools:ignore="MissingConstraints"tools:visibility="visible"><LinearLayoutandroid:layout_marginStart="12dp"android:id="@+id/ll_tab1"android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:gravity="center"android:orientation="vertical"><cn.com.acts.ui.widgets.MediumTextViewandroid:id="@+id/tv_tab_title1"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="@dimen/mp_8"android:text="普通定投"android:textColor="#1760EA"android:textSize="16dp"app:mediumText="true" /><TextViewandroid:id="@+id/tv_tab_desc1"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="1dp"android:text="定期定额"android:textColor="#801760EA"android:textSize="12dp" /><Viewandroid:id="@+id/view_tab1"android:layout_width="match_parent"android:layout_height="2dp"android:layout_marginTop="4dp"android:background="#1760EA" /></LinearLayout><LinearLayoutandroid:id="@+id/ll_tab2"android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:gravity="center"android:layout_marginEnd="12dp"android:orientation="vertical"><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:orientation="horizontal"><cn.com.acts.ui.widgets.MediumTextViewandroid:id="@+id/tv_tab_title2"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="@dimen/mp_8"android:text="微笑智能定投"android:textColor="#1760EA"android:textSize="16dp"app:mediumText="true" /><ImageViewandroid:id="@+id/iv_img_tag"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="@dimen/mp_5"android:src="@drawable/icon_fund_smile_top_right_tag"android:visibility="gone"tools:visibility="visible" /></LinearLayout><TextViewandroid:id="@+id/tv_tab_desc2"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="1dp"android:text="定期不定额 全新智能模型"android:textColor="#801760EA"android:textSize="12dp" /><Viewandroid:id="@+id/view_tab2"android:layout_width="match_parent"android:layout_height="2dp"android:layout_marginTop="4dp"android:background="#1760EA" /></LinearLayout></LinearLayout><androidx.viewpager2.widget.ViewPager2android:id="@+id/view_page"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1" /></LinearLayout>
有趣的小问题:java.lang.IllegalStateException: Fragment already added
当我在Activity
中同时设置ViewPager
、ViewPager2
的Adapter时(引用同一份数据源
),报了以下错误
Tip
:从错误来看标明Fragment已经被添加过了;具体原因是当我们添加一个Fragment到Activity中,FragmentManager会负责管理Fragment生命周期和状态,如果尝试多次添加同一个Fragment实例
,就会报以下错误
知道报错原因,那么解决方案就由之而来,故最终我在 ViewPager、ViewPager2 一起使用
中分别使用了不同的数据源(曲线救国,非最优解)~
以下个人瞎捉摸,可忽略:我扭头看了一下 fragmentList
仅添加了一次数据,为什么会有重复实例? 然后我就看到了ViewPager
、ViewPager2
所使用 Adapter构造
的不同,获取FragmentManager
后涉及到了组件 lifecycle
;
FragmentStatePagerAdapter
FragmentStateAdapter