Android进阶之路 - ViewPager2 比 ViewPager 强在哪?

我记得前年(2022)面试的时候有被问到 ViewPager 和 ViewPager2 有什么区别?当时因为之前工作一直在开发售货机相关的项目,使用的技术要求并不高,所以一直没去了解过 ViewPager2~ 去年的时候正好有相关的功能需求,索性直接用 ViewPager2 进行了

Tip:很多人可能比较关注俩者区别、变更,那么我们结论先行,然后再接着验证

结论先行

关于它们的区别,我仅从我个人理解的角度来讲(不知不觉用了好几天…)

实现方面

  • ViewPager 继承自ViewGroup,内部并未使用已有的成熟控件,更多的是自定义的操作
  • ViewPager2 也继承自ViewGroup,但其内部可以明显的看到 RecyclerView 影子,所以可以说是基于 RecyclerView实现,那么这也意味着性能的提升,毕竟ViewHodelr减少了内存开销,同时RecyclerView相关方法在ViewPager2也可以看到类似封装

功能方面

TipViewPager2 新增功能是建立在 ViewPager 已有功能的基础上的扩展,例如ViewPager2支持垂直滑动,同时也是支持水平滑动的

  • ViewPager2 支持垂直方向滑动,而 ViewPager 仅支持水平方向滑动(扩展组件功能)

关于ViewPager2新支持的RTL方向布局简单概述一下:国内一般都是默认的LTR方向布局,但是针对国际用户会根据语言环境(阿拉伯语等)自动启动从右到左(RTL)页面布局,如果想要设置ViewPager2布局方向可以通过设置android:layoutDirection属性或setLayoutDirection()方式

  • ViewPager2 支持RTL方向布局,而 ViewPager 支持LTR方向布局(扩展组件功能)
  • ViewPager2 DiffUtil 支持,减少页面刷新频率,当数据未发生变更时不必重新绘制(提升用户体验)

Adapter(适配器)方面

  • ViewPager2 使用的Adapter,一般为PagerAdapter、及其子类 FragmentPagerAdapterFragmentStatePagerAdapter
  • ViewPager2 既然基于RecyclerView实现,那么它所使用的Adapter同理也应该基于RecyclerView.Adapter,所以新增了 FragmentStateAdapter

加载方面

  • ViewPager 默认执行预加载,如果需要懒加载的话,需要自行封装
  • ViewPager2 默认执行懒加载,但是依旧可以设置预加载

API方面

  • 监听:ViewPager2registerOnPageChangeCallback 取代了 ViewPageraddPageChangeListener
  • 关联TabLayout :ViewPagerTabLayout 关联用的是 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效果

Android进阶之路 - ViewPager2 比 ViewPager 强在哪?_第1张图片

项目效果

Android进阶之路 - ViewPager2 比 ViewPager 强在哪?_第2张图片

新增功能

如果想了解 ViewPager2,我觉得最好的方式可能就是跟着 ViewPager2官方文档 简单的过一下版本的更新情况

从更新记录可以看出部分 ViewPager、ViewPager2 区别

  • ViewPager2 支持 RTL布局(Right To Left?),ViewPager 仅支持LTR布局
  • ViewPager2 支持 水平方向、垂直方向(类似抖音、快手垂直切换视频的场景),ViewPager 仅支持横向滑动(水平方向)
  • DiffUtil 支持,减少页面刷新频率,当数据未发生变更时不必重新绘制 (记得好早以前就有类似工具,不过现在很多框架内部都做了Diff操作)

2019年2月7日 ViewPager2 应运而生(首个测试版本)

Android进阶之路 - ViewPager2 比 ViewPager 强在哪?_第3张图片

2019年11月20日 ViewPager2 出了首个稳定的正式版本
Android进阶之路 - ViewPager2 比 ViewPager 强在哪?_第4张图片

源码解析

查看 ViewPager 可以看到其继承自 ViewGroup ,同时 内部貌似并未使用现有的View控件

Android进阶之路 - ViewPager2 比 ViewPager 强在哪?_第5张图片

查看 ViewPager2 发现它虽然同样继承自ViewGroup ,但其内部却是基于 RecyclerView 实现的,故 RecyclerView 具备的方法 ViewPager2 也可以尝试调用,常见的类似于LayoutManagerItemDecorator等类似方法

Android进阶之路 - ViewPager2 比 ViewPager 强在哪?_第6张图片

例如 ViewPager2 支持垂直方向滑动就提供了setOrientation 方法(默认水平方向),那么通过源码可以看出这种方式其实类似RecyclerView设置 LayoutManager 方式

Android进阶之路 - ViewPager2 比 ViewPager 强在哪?_第7张图片

setOrientation 方向提供了 ORIENTATION_HORIZONTALORIENTATION_VERTICAL;在动态设置中伪代码 viewPager2.orientation = ORIENTATION_VERTICAL

Android进阶之路 - ViewPager2 比 ViewPager 强在哪?_第8张图片


变更场景

ViewPager、ViewPager2 中 引用对比

记得以前调用的是Suppor V4包下的 ViewPager,但现在不论是 ViewPagerViewPager2 都直接在 androidx 内了,基本创建项目后就可以直接引用了

Android进阶之路 - ViewPager2 比 ViewPager 强在哪?_第9张图片

ViewPager、ViewPager2 中 Adapter区别对比

ViewPager 源码注释中其实已经解释了Adapter相关内容

Android进阶之路 - ViewPager2 比 ViewPager 强在哪?_第10张图片

ViewPager 使用的 Adapter 主要是 PagerAdapter 及其子类FragmentPagerAdapterFragmentStatePagerAdapter,俩者的主要区别如下

  • FragmentPagerAdapter 支持缓存
  • FragmentStatePagerAdapter 不支持缓存

PagerAdapter

Android进阶之路 - ViewPager2 比 ViewPager 强在哪?_第11张图片

PagerAdapter 子类

Android进阶之路 - ViewPager2 比 ViewPager 强在哪?_第12张图片

ViewPager2 - API变更

Android进阶之路 - ViewPager2 比 ViewPager 强在哪?_第13张图片

ViewPager2 使用的 Adapter 主要是 RecyclerView.Adapter 的子类 FragmentStateAdapter,它也继承了RecyclerView的优点,内置了FragmentViewHolder提升了性能

Android进阶之路 - ViewPager2 比 ViewPager 强在哪?_第14张图片

FragmentStateAdapter

Android进阶之路 - ViewPager2 比 ViewPager 强在哪?_第15张图片

FragmentStateAdapter 提供了三种构造参数,支持绑定组件的生命周期

Android进阶之路 - ViewPager2 比 ViewPager 强在哪?_第16张图片

FragmentViewHolder

Android进阶之路 - ViewPager2 比 ViewPager 强在哪?_第17张图片


ViewPager、ViewPager2 中 Adapter使用对比

以前我在别的知识结构 也曾引用过ViewPager,如有需要也可以借鉴下

对比后发现继承的 Adapter 和重写方法命名发生变更,此处主要说 API变更

  • ViewPager2 getItemCount 等于(=) ViewPager 中的 getCount
  • ViewPager2 createFragment 等于(=) ViewPager 中的 getItem

ViewPager

PagerAdapter的子类Adapter已经被标记过时了,最好还是开始用ViewPager2

Android进阶之路 - ViewPager2 比 ViewPager 强在哪?_第18张图片

示例

    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 中 监听对比

ViewPagerViewPager2 监听的内容相同,只有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 中 预加载、懒加载对比

首先要明确一个概念:不论是 ViewPagerViewPager2都支持预加载!

预加载的意义在于不必让用户每次都等切换页面后才执行接口请求、页面绘制等操作,而是直接显示效果,不过这样做性能开销方面会大一些,用户体验有时候需要看加载元素的多少而决定

懒加载顾名思义只有在用户需要的时候才去执行,其实现的核心意义在于

  • 是否为当前页面(是否可见)
  • 是否已经加载过了
  • 视图是否初始化完成(setUserVisibleHint()的调用在onCreateView之前)

预加载区别

  • ViewPager 默认调用了预加载方法,且默认预加载值为1(一般加载当前视图的左右相邻视图)

相关源码

Android进阶之路 - ViewPager2 比 ViewPager 强在哪?_第19张图片

Android进阶之路 - ViewPager2 比 ViewPager 强在哪?_第20张图片

Android进阶之路 - ViewPager2 比 ViewPager 强在哪?_第21张图片

  • ViewPager2 默认不进行预加载,相对应的预加载方法值为-1

相关源码

Android进阶之路 - ViewPager2 比 ViewPager 强在哪?_第22张图片
Android进阶之路 - ViewPager2 比 ViewPager 强在哪?_第23张图片
Android进阶之路 - ViewPager2 比 ViewPager 强在哪?_第24张图片

懒加载区别

  • ViewPager 并未实现懒加载,如果需要懒加载的话需要自行实现,通常在 Fragment 场景下就是重写 setUserVisibleHint方法,然后继承于该基类
  • ViewPager2 默认实现了懒加载, 主要是通过 LifecycleFragment 的生命周期进行管理

ViewPager、ViewPager2 关联TabLayout

因为我在项目中的 TabLayout 部分布局需要自定义一下,所以并未直接使用 TabLayout,有机会在详细说吧

  • ViewPagerTabLayout 的代码关联用的是 TabLayout 的方法 setupWithViewPager()
  • ViewPager2TabLayout 的代码关联是通过 TabLayoutMediator 类来做了关联

从ViewPager 迁移到 ViewPager2

具体迁移操作,可参考 从 ViewPager 迁移到 ViewPager2

Android进阶之路 - ViewPager2 比 ViewPager 强在哪?_第25张图片


实战演练

关于ViewPager2的使用也可以看 官网代码示例:views-widgets-samples/ViewPager2

因为我Demo中并未结合TabLayout一起使用,如有需求也可以前往 用户指南:使用 ViewPager2 创建包含标签的滑动视图

Demo版本

同个页面使用ViewPagerViewPager2 遇到一个有趣的场景,最后有讲到原因与处理方式

我写Demo时为了更直观的对比,所以将 ViewPagerViewPager2 一起使用;但是在使用时我分别简单讲解了不同的使用方式和综合方式

前置配件

activity_main


<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.ViewPager
        android:id="@+id/view_pager1"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:background="#f78744" />

    <androidx.viewpager2.widget.ViewPager2
        android: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内的AFragmentBFragment基本一致,直接copy修改下想要布局就好

AFragment

package com.example.viewpager2

import 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


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="A-Fragment"
        android:gravity="center"
        android:textStyle="bold"
        />
LinearLayout>
ViewPager 使用方式
package com.example.viewpager2

import 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


class 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: FragmentManager
        var fragmentList: MutableList<Fragment> = mutableListOf()

        init {
            this.manager = manager
            this.fragmentList = fragmentList
        }

        override fun getCount(): Int {
            return fragmentList.size
        }

        override fun getItem(position: Int): Fragment {
            return fragmentList[position]
        }
    }
}
ViewPager2 使用方式
package com.example.viewpager2

import 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.OnPageChangeCallback

class 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_VERTICAL
        viewPager2.adapter = viewPager2Adapter
        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)
            }
        })

    }

    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.viewpager2

import 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.OnPageChangeCallback

class 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_VERTICAL
        viewPager2.adapter = viewPager2Adapter
        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)
            }
        })

    }

    inner class ViewPager1Adapter(manager: FragmentManager, fragmentList: MutableList<Fragment>) : FragmentStatePagerAdapter(manager) {
        var manager: FragmentManager
        var fragmentList: MutableList<Fragment> = mutableListOf()

        init {
            this.manager = manager
            this.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的效果即可

ActivityFragment数据共享采用了ViewModel;接口返回数据监听采用了LiveData;有兴趣的可以前往进阶

  • 组件化之路 - ViewModel一知半解
  • 组件化之路 - LiveData一知半解
  • 组件化之路 - LiveData + ViewModel一知半解

组件中可以有多个ViewModel,在这里为了区分功能一个 FundSmileViewModel 用于网络请求等逻辑操作,一个 FundCodeViewModel 用于存放共享数据(当然也可以只用一个)

package cn.com.xx

import 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: String

    private 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 = it
            fragmentList.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 = pagerAdapter
        binding.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 = false
            SPUtils.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:部分自定义控件,自行取舍、更换即可


<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">

    <LinearLayout
        android: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">

        <View
            android:id="@+id/status_bar"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            tools:layout_height="28dp" />

        <cn.com.ui.widgets.TitleBar
            android:id="@+id/title_bar"
            android:layout_width="match_parent"
            android:layout_height="?actionBarSize"
            tools:layout_height="48dp" />

    LinearLayout>

    <LinearLayout
        android: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">

        <LinearLayout
            android: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.MediumTextView
                android: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" />

            <TextView
                android: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" />

            <View
                android:id="@+id/view_tab1"
                android:layout_width="match_parent"
                android:layout_height="2dp"
                android:layout_marginTop="4dp"
                android:background="#1760EA" />
        LinearLayout>

        <LinearLayout
            android: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">

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:orientation="horizontal">

                <cn.com.acts.ui.widgets.MediumTextView
                    android: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" />

                <ImageView
                    android: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>

            <TextView
                android: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" />

            <View
                android: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.ViewPager2
        android: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中同时设置ViewPagerViewPager2的Adapter时(引用同一份数据源),报了以下错误

Tip:从错误来看标明Fragment已经被添加过了;具体原因是当我们添加一个Fragment到Activity中,FragmentManager会负责管理Fragment生命周期和状态,如果尝试多次添加同一个Fragment实例,就会报以下错误

Android进阶之路 - ViewPager2 比 ViewPager 强在哪?_第26张图片

知道报错原因,那么解决方案就由之而来,故最终我在 ViewPager、ViewPager2 一起使用 中分别使用了不同的数据源(曲线救国,非最优解)~

以下个人瞎捉摸,可忽略:我扭头看了一下 fragmentList 仅添加了一次数据,为什么会有重复实例? 然后我就看到了ViewPagerViewPager2所使用 Adapter构造的不同,获取FragmentManager后涉及到了组件 lifecycle

FragmentStatePagerAdapter

Android进阶之路 - ViewPager2 比 ViewPager 强在哪?_第27张图片

FragmentStateAdapter

Android进阶之路 - ViewPager2 比 ViewPager 强在哪?_第28张图片

你可能感兴趣的:(Android进阶之路,Android,ViewPager2,ViewPager,ViewPager1,2区别)