由潜入深使用viewPage2

概述

viewPage2是由Google在2011发布用于替代viewPage的新控件.该控件运行在水平或者垂直滑动在当前屏幕进行导航,大多用于轮播图或者导航切换

简单的图片切换

  1. 新建项目MysViewPage2
    博客新建MysViewPage2项目
  2. 下载资源,移动到res/drawable文件夹下.
    博客新建MysViewPage2复制资源文件
  3. 修改activity_main.xml文件,代码如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <?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">
    <androidx.viewpager2.widget.ViewPager2
    android:id="@+id/viewpager2"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
    </LinearLayout>
    activity_main.xml文件中,只创建了一个ViewPager2控件用于显示切换的视图且为其id名为viewpager2.
  4. 新建需要被切换的视图(轮播图)的布局item.xml
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <?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">
    <ImageView
    android:id="@+id/image"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"/>
    </LinearLayout>
    这里仅仅创建了一个ImageView用于代表一个被切换的视图.
  5. 新建适配器文件MyViewPage2Adapter

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // 可以继承的适配器不止RecyclerView
    class MyViewPage2Adapter(private val imageList: ArrayList<Int>) : RecyclerView.Adapter<MyViewPage2Adapter.ViewHolder>(){
    inner class ViewHolder(val view:View) : RecyclerView.ViewHolder(view) {
    // 将imageView放入Holder
    val imageView: ImageView = view.findViewById(R.id.image)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
    // 加载被切换的布局
    val view = LayoutInflater.from(parent.context).inflate(R.layout.item,parent,false)
    return ViewHolder(view)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    // 设置当前视图图片ID
    holder.imageView.setImageResource(imageList[position])
    }

    override fun getItemCount(): Int = imageList.size
    }

    viewPage2不能继承viewPage,除了继承RecyclerView,FragmentStateAdapter等.可以前往官方文档查看更多

  6. 连接适配器,修改MainActivity.kt,代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class MainActivity : AppCompatActivity() {
    private val datas = ArrayList<Int>().apply {
    add(R.drawable.tx1)
    add(R.drawable.tx2)
    add(R.drawable.tx3)
    add(R.drawable.tx4)
    }
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    val viewPage2 = findViewById<ViewPager2>(R.id.viewpager2)
    val myViewPage2Adapter = MyViewPage2Adapter(datas)
    viewPage2.adapter = myViewPage2Adapter
    }
    }

    代码非常简单,就是找到控件,加载适配器再载入数据.

  7. 效果图
    博客新建MysViewPage2效果图1

给ViewPage2加上循环滚动

  1. 在上面的基础上修改MyViewPage2Adapter.kt,代码如下:\
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    class MyViewPage2Adapter(private val imageList: ArrayList<Int>) : RecyclerView.Adapter<MyViewPage2Adapter.ViewHolder>(){
    inner class ViewHolder(val view:View) : RecyclerView.ViewHolder(view) {
    val imageView: ImageView = view.findViewById(R.id.image)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
    val view = LayoutInflater.from(parent.context).inflate(R.layout.item,parent,false)
    return ViewHolder(view)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    // 通过取余,一直循环
    val i = position % imageList.size
    holder.imageView.setImageResource(imageList[i])
    }

    // 设置一个超级大的数(2147483647),达成理论无限滚动
    override fun getItemCount(): Int = Int.MAX_VALUE
    }
  2. 效果图
    博客新建MysViewPage2效果图2

给ViewPage2加上自动滚动

前面的代码都只能手动滚动,为了实现自动滚动需要借助循环器Handler来在后台自动切换下一张.不建议使用协程,因为协程不被允许修改主线程ui.

  1. 循环器,修改MainActivity.kt文件,代码如下
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    class MainActivity : AppCompatActivity() {
    // 在主线程创建循环器
    private var mHandler = Handler(Looper.getMainLooper())
    private lateinit var viewPage2:ViewPager2
    private val datas = ArrayList<Int>().apply {
    add(R.drawable.tx1)
    add(R.drawable.tx2)
    add(R.drawable.tx3)
    add(R.drawable.tx4)
    }
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    viewPage2 = findViewById<ViewPager2>(R.id.viewpager2)
    val myViewPage2Adapter = MyViewPage2Adapter(datas)
    viewPage2.adapter = myViewPage2Adapter
    }

    // 软件启动时回调
    override fun onResume(){
    super.onResume()
    mHandler.postDelayed(runnable,500)
    }
    // 退出时移除循环器
    override fun onPause() {
    super.onPause()
    mHandler.removeCallbacks(runnable)
    }

    private val runnable: java.lang.Runnable = object : java.lang.Runnable {
    override fun run() {
    //获得轮播图当前的位置
    var currentPosition = viewPage2.currentItem
    currentPosition++
    viewPage2.setCurrentItem(currentPosition, true)
    mHandler.postDelayed(this, 5000)
    }
    }
    }

给轮播图加上指示器(小白点)

  1. res/drawable/文件夹下面新增文件shape_dot.xml文件,代码如下
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <?xml version="1.0" encoding="utf-8"?>
    <shape xmlns:android="http://schemas.android.com/apk/res/android">
    <size
    android:width="8dp"
    android:height="8dp"/>
    <corners
    android:radius="8dp"/>
    <solid
    android:color="#00ccff"/>
    </shape>
  2. res/drawable/文件夹下面新增文件shape_dot_selected.xml文件,代码如下
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <?xml version="1.0" encoding="utf-8"?>
    <shape xmlns:android="http://schemas.android.com/apk/res/android">
    <size
    android:width="8dp"
    android:height="8dp"/>
    <corners
    android:radius="8dp"/>
    <solid
    android:color="#ffffff"/>
    </shape>
  3. activity_main.xml新增放置指示器的布局,重写布局.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <?xml version="1.0" encoding="utf-8"?>
    <!--改为帧布局,方便将指示器重合-->
    <FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <androidx.viewpager2.widget.ViewPager2
    android:id="@+id/viewpager2"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
    <!-- 为指示器准备的布局-->
    <LinearLayout
    android:gravity="center"
    android:id="@+id/container_indicator"
    android:layout_gravity="bottom"
    android:layout_width="match_parent"
    android:layout_height="20dp"
    android:orientation="horizontal" />
    </FrameLayout>
  4. 修改MainActivity.kt,代码如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    class MainActivity : AppCompatActivity() {
    private val TAG = "MainActivity"
    private lateinit var initIndicatorDots: LinearLayout
    // 在主线程创建循环器
    private var mHandler = Handler(Looper.getMainLooper())
    private lateinit var viewPage2: ViewPager2
    private val datas = ArrayList<Int>().apply {
    add(R.drawable.tx1)
    add(R.drawable.tx2)
    add(R.drawable.tx3)
    add(R.drawable.tx4)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    viewPage2 = findViewById(R.id.viewpager2)
    val myViewPage2Adapter = MyViewPage2Adapter(datas)
    viewPage2.adapter = myViewPage2Adapter

    // 初始化指示器父布局
    initIndicatorDots = findViewById(R.id.container_indicator)
    viewPage2.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
    // 上一个图片索引
    private var lastPosition = 0
    override fun onPageSelected(position: Int) {
    super.onPageSelected(position)
    //轮播时,改变指示点
    val current = position % 4
    val last = lastPosition % 4
    initIndicatorDots.getChildAt(current).setBackgroundResource(R.drawable.shape_dot_selected)
    initIndicatorDots.getChildAt(last).setBackgroundResource(R.drawable.shape_dot)
    // 将本次图片索引更新到上一个图片索引
    lastPosition = position
    }
    })
    initIndicatorDots()
    }

    // 软件启动时回调
    override fun onResume() {
    super.onResume()
    mHandler.postDelayed(runnable, 500)
    }

    // 退出时移除循环器
    override fun onPause() {
    super.onPause()
    mHandler.removeCallbacks(runnable)
    }

    private fun initIndicatorDots() {
    Log.d(TAG, "初始化完成")
    // 循环数据长度
    for (i in datas.indices) {
    // 实例化一个viewImage控件对象
    val imageView = ImageView(this)
    // 初始启动,除了第0个,其它都是未选择状态
    if (i == 0) imageView.setBackgroundResource(R.drawable.shape_dot_selected)
    else imageView.setBackgroundResource(R.drawable.shape_dot)
    // 为指示器设置宽高
    val layoutParams = LinearLayout.LayoutParams(
    LinearLayout.LayoutParams.WRAP_CONTENT,
    LinearLayout.LayoutParams.WRAP_CONTENT
    )

    // 指示器间距
    layoutParams.marginEnd = 20
    imageView.layoutParams = layoutParams
    initIndicatorDots.addView(imageView)
    }
    }

    private val runnable: java.lang.Runnable = object : java.lang.Runnable {
    override fun run() {
    //获得轮播图当前的位置
    var currentPosition = viewPage2.currentItem
    currentPosition++
    viewPage2.setCurrentItem(currentPosition, true)
    mHandler.postDelayed(this, 5000)
    }
    }
    }
    在这里主要新建了选中和未选中两个指示器的xml,文件.然后在MainActivity中在onCreate生命周期时将指示器的初始图片赋值,然后注册了一个Page切换的回调函数,用于修改指示器的背景图片.
  5. 效果图
    博客新建MysViewPage2效果图3

本篇代码

Github


由潜入深使用viewPage2
https://007666.xyz/2022/10/17/由潜入深使用viewPage2/
作者
梦无念
发布于
2022年10月17日
许可协议
CC BY-NC-SA 4.0