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效果

请添加图片描述

项目效果

请添加图片描述

新增功能

如果想了解 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 也可以尝试调用,常见的类似于LayoutManagerItemDecorator等类似方法

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述


变更场景

ViewPager、ViewPager2 中 引用对比

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

在这里插入图片描述

ViewPager、ViewPager2 中 Adapter区别对比

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

在这里插入图片描述

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

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

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(一般加载当前视图的左右相邻视图)

相关源码

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

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

相关源码

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

懒加载区别

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

ViewPager、ViewPager2 关联TabLayout

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

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

从ViewPager 迁移到 ViewPager2

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

在这里插入图片描述


实战演练

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

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

Demo版本

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

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

前置配件

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

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

    <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:部分自定义控件,自行取舍、更换即可

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

    <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实例,就会报以下错误

在这里插入图片描述

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

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

FragmentStatePagerAdapter

在这里插入图片描述

FragmentStateAdapter

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/362065.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[Java 并发基础]多线程编程

文章参考&#xff1a; https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Future.html https://juejin.cn/post/6970558076642394142 文章目录 线程的创建方式继承 Thread实现 Runnable 接口实现 Callable 接口使用 Lambda使用线程池 线程创建相关的 jdk源码Thr…

TCP四次握手

TCP 协议在关闭连接时&#xff0c;需要进行四次挥手的过程&#xff0c;主要是为了确保客户端和服务器都能正确地关闭连接。 # 执行流程 四次挥手的具体流程如下&#xff1a; 客户端发送 FIN 包&#xff1a;客户端发送一个 FIN 包&#xff0c;其中 FIN 标识位为 1&#xff0c…

【项目管理】立项管理

一、前言 对于甲方的立项&#xff1a;需求调研二编写项目申请书一可行性研究&#xff08;机会、初步、详细&#xff09;一项目论证一项目评估一评审获得批准一发布招标文件&#xff01;对于乙方的立项&#xff1a;看到招标文件一进行项目识别一可行性研究&#xff08;机会、初…

【Java 数据结构】优先级队列(堆)

优先级队列&#xff08;堆&#xff09; 1. 优先级队列1.1 概念 2. 优先级队列的模拟实现2.1 堆的概念2.2 堆的存储方式2.3 堆的创建2.3.1 堆向下调整2.3.2 堆的创建2.3.3 建堆的时间复杂度 2.4 堆的插入与删除2.4.1 堆的插入2.4.2 堆的删除 2.5 用堆模拟实现优先级队列 3.常用…

JDBC - 结构优化1

JDBC - 结构优化1 文章目录 JDBC - 结构优化1三层架构1 什么是三层架构2 三层架构项目搭建 结构优化1 - 学生信息管理1 封装工具类2 ORM3 DAO 三层架构 1 什么是三层架构 **三层架构&#xff1a;**将程序划分为表示层, 业务逻辑层, 数据访问层三层&#xff0c;各层之间采用接…

Redis应用-哨兵模式以及缓存穿透雪崩解决方案

文章目录 Redis应用-哨兵模式以及缓存穿透雪崩哨兵模式Redis缓存穿透和雪崩缓存穿透布隆过滤器缓存空对象 缓存击穿设置热点数据永不过期加互斥锁 缓存雪崩Redis高可用限流降级数据预热 Redis应用-哨兵模式以及缓存穿透雪崩 哨兵模式 概述 主从切换技术的方法是&#xff1a;当…

RHCE DNS域名解析服务器

目录 1. 正向解析 1.1 安装必要软件 1.2 配置静态ip 1.3 DNS配置 1.4 测试 2. 反向解析 2.1 关闭安全软件&#xff0c;安装必要软件 2.2 配置静态ip 2.3 DNS配置 2.4 测试 1. 正向解析 1.1 安装必要软件 1.2 配置静态ip 服务器配置 nmcli c modify ens32 ipv4.method man…

基于simulink的模糊PID控制器建模与仿真,并对比PID控制器

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 4.1PID控制器原理 4.2 模糊PID控制器原理 5.完整工程文件 1.课题概述 在simulink&#xff0c;分别建模实现一个模糊PID控制器和一个PID控制器&#xff0c;然后将PID控制器的控制输出和模糊PID的控制输出…

[工具探索]Safari 和 Google Chrome 浏览器内核差异

最近有些Vue3的项目&#xff0c;使用了safari进行测试环境搞开发&#xff0c;发现页面存在不同程序的页面乱码情况&#xff0c;反而google浏览器没问题&#xff0c;下面我们就对比下他们之间的差异点&#xff1a; 日常开发google chrome占多数&#xff1b;现在主流浏览器 Goog…

双目模组 - IMSEE SDK的配置实践:含Opencv的详细编译配置

IMSEE 的环境要求: CMake(3.0以上)(需要支持vs2019) Visual Studio 2019 opencv3.3.1 IMSEE-SDK 官网参考: Windows 源码安装 — IMSEE SDK 1.4.2 文档 (imsee-sdk-docs.readthedocs.io) 【案】按照IMSEE的建议进行安装: 1 Windows 安装: 1.1 环境准备: 1.1.1 CMake:in…

当阿里云偶遇个人用户——谈幻兽帕鲁自建服

1. 快乐地闲谈 我擅长分析的云计算领域是“以工程师为操作用户的企业级IT服务”&#xff0c;但这篇文章讨论的对象甚至不是开发者用户&#xff0c;而是我从未设想过的“非IT用户”。 看到朋友转发《阿里云60秒部署幻兽帕鲁》的文章&#xff0c;我还想就是这能分析什么哪&#x…

结构体与共用体——C语言——day15

在C语言中&#xff0c;C语言允许用户自己指定这样一种数据结构&#xff0c;它称为结构体(structure) 。它相当于其他高级语言中的“记录”。 假设程序中要用到图所表示的数据结构&#xff0c;但是C语言没有提供这种现成的数据类型&#xff0c;因此用户必须要在程序中建立所需的…

C#——三角形面积公式

已知三角形的三个边&#xff0c;求面积&#xff0c;可以使用海伦公式。 因此&#xff0c;可以执行得到三角形面积公式的计算方法代码如下&#xff1a; /** / <summary>* / 三角形面积公式* / </summary>* / <param name"a">边长a</param>*…

[word] word艺术字体如何设置? #知识分享#职场发展#媒体

word艺术字体如何设置&#xff1f; 在工作中有些技巧&#xff0c;可以快速提高工作效率&#xff0c;解决大部分工作&#xff0c;今天给大家分享word艺术字体如何设置的技巧&#xff0c;希望可以帮助到你。 1、设置艺术字 选中文字&#xff0c;然后点击菜单栏的【插入】按钮一一…

版本管理工具git: 谨慎使用git中的撤回操作

文章目录 一、背景二、解决方案1、步骤一2、步骤二 三、参考 一、背景 昨天代码分支提交错了&#xff0c;idea中使用了如下操作&#xff0c;结果代码不见了 二、解决方案 1、步骤一 使用git reflog命令&#xff0c;查看提交记录&#xff0c;找到之前commit操作的哈希值 …

启动盘重装ubuntu22系统

win+R msinfo32查看 插入制作好的u盘电脑开机 进入BIOS界面的方法有多种,以下是一些常见的方法: 进入BIOS界面的最常见按键有: Del键:大多数台式机通过在启动时按下Del键来进入BIOS。Esc键:在AMI BIOS和某些品牌电脑中,进入BIOS系统需要按“Esc”键,一般在开机画面…

确认项目范围基准 常见的5大问题

确认项目范围基准的过程中&#xff0c;经常会遇到一些问题&#xff0c;如经常出现项目范围不明确、范围变更频繁等问题&#xff0c;往往会导致项目延期、超预算、质量下降等问题&#xff0c;严重的话可能会导致项目失败。 因此&#xff0c;我们在进行项目范围基准确认时&#x…

Centos慢慢长大(一)

1、写在前面 这将是一个系列性的文章。可能更多的是记录我在学习的过程中的一些感悟吧。我想强调的是在这一系列文章里我会从最小化的安装开始&#xff0c;然后逐渐的增加需要安装的软件。就象一个婴儿的诞生&#xff0c;慢慢的学走路、学说话、学使用筷子。。。。。。 这将是一…

不同生态系统蒸散发研究进展_刘超_2023

不同生态系统蒸散发研究进展_刘超_2023 摘要关键词 1 研究方法1.1 实测法1.1.1 蒸渗仪1.1.2 气孔计法1.1.3 化学示踪法1.1.4 大孔径闪烁仪1.1.5 涡动相关法 1.2 模型法1.2.1 水量平衡法1.2.2 波文比-能量平衡法1.2.3 遥感技术1.2.4 综合法和辐射法 2 研究展望2.1 研究进展2.2 存…

zookeeper(2) 服务器动态上下线监听和分布式锁案例

案例一&#xff1a;服务器动态上下线监听 某分布式系统中&#xff0c;主节点可以有多台&#xff0c;可以动态上下线&#xff0c;任意一台客户端都能实时感知 到主节点服务器的上下线。 1.服务端代码 package com.atguigu.case1;import org.apache.zookeeper.*;import java.io…