ViewPager+Fragment

文章目录

  • [一、ViewPager 和 ViewPager2](#一、ViewPager 和 ViewPager2)
  • 二、ViewPager2常用API
    • [2.1 FragmentStateAdapter](#2.1 FragmentStateAdapter)
    • [2.2 setCurrentItem](#2.2 setCurrentItem)
    • [2.3 setPageTransformer](#2.3 setPageTransformer)
    • [2.4 setOffscreenPageLimit](#2.4 setOffscreenPageLimit)
    • [2.5 OnPageChangeCallback](#2.5 OnPageChangeCallback)
  • 三、Fragment作为page时,预加载页面的生命周期
    • [3.1 ViewPager2的页面生命周期](#3.1 ViewPager2的页面生命周期)
    • [3.2 ViewPager的页面生命周期](#3.2 ViewPager的页面生命周期)
  • [四、简易的ViewPager2 + Fragment](#四、简易的ViewPager2 + Fragment)
    • [4.1 Activity实现](#4.1 Activity实现)
    • [4.2 Adapter实现](#4.2 Adapter实现)

一、ViewPager 和 ViewPager2

  • ViewPager2基于RecycleView实现,性能更好
  • ViewPager2支持水平和垂直方向滑动。ViewPager默认只支持水平滑动,如果要做垂直滑动,要做很多的定制操作,比较麻烦。
特性 ViewPager ViewPager2
底层实现 基于 AdapterView(类似 ListView) 基于 RecyclerView
方向支持 仅水平方向 水平 + 垂直方向
Adapter PagerAdapter RecyclerView.Adapter
Fragment支持 FragmentPagerAdapter / FragmentStatePagerAdapter FragmentStateAdapter
更新方式 notifyDataSetChanged() notifyItemChanged() / DiffUtil
性能 较低 高(复用机制好)

二、ViewPager2常用API

2.1 FragmentStateAdapter

  • Fragment作为页面元素时使用的Adapter

2.2 setCurrentItem

  • 设置当前的页面,可以配置smoothScroll
  • 结合handler 定时任务,可以实现页面的轮播。在页面轮播时需要注意用户手动滑动时,需要取消轮播定时器。
  • ViewPager2自动轮播时,平台并没有提供接口用于设置两个Page切换动画的时长,比如想让page间切换的速度慢一些。实现起来比较繁琐

2.3 setPageTransformer

  • 自定义页面切换的动效,主要是手动滑动时的动效。自动轮播不会使用这个动效。

2.4 setOffscreenPageLimit

  • 设置ViewPager2的预加载页面数量,左右各有几个页面预加载或者保存在内存中。至少是1个页面进行预加载。
  • 预加载的页面过多,会导致内存增加。预加载的页面少,流畅性就会降低,需要平衡。

2.5 OnPageChangeCallback

  • onPageScrollStateChanged滑动状态回调。当手指滑动的时候,首先回调的是SCROLL_STATE_DRAGGING,手指抬起后回调SCROLL_STATE_SETTLING,等到页面完全静止后回调SCROLL_STATE_IDLE。
回调状态字段 状态 数值
SCROLL_STATE_IDLE 滑动完全停止 0
SCROLL_STATE_DRAGGING 手指滑动状态,手没有抬起 1
SCROLL_STATE_SETTLING 沉降状态,手指抬起了,自动滑动状态 2
  • onPageScrolled滑动的位置和Position回调
  • onPageSelected回调当前选中的页面
  • 一次手指滑动翻页的回调顺序如下,需要注意onPageSelected回调之后,页面仍然时再滑动的。
java 复制代码
I/ViewPager2Activity: -------- onPageScrollStateChanged: state 1 ----------
I/ViewPager2Activity: onPageScrolled: position: 1, positionOffset: 0.014814815, 16
I/ViewPager2Activity: onPageScrolled: position: 1, positionOffset: 0.06759259, 73
I/ViewPager2Activity: onPageScrolled: position: 1, positionOffset: 0.2574074, 278
I/ViewPager2Activity: onPageScrolled: position: 1, positionOffset: 0.27222222, 294
I/ViewPager2Activity: -------- onPageScrollStateChanged: state 2 ----------
I/ViewPager2Activity: -------- onPageSelected: position 2 -------------
I/ViewPager2Activity: onPageScrolled: position: 1, positionOffset: 0.34814814, 376
I/ViewPager2Activity: onPageScrolled: position: 1, positionOffset: 0.42407408, 458
I/ViewPager2Activity: onPageScrolled: position: 1, positionOffset: 0.49166667, 531
I/ViewPager2Activity: onPageScrolled: position: 1, positionOffset: 0.55925924, 604
I/ViewPager2Activity: onPageScrolled: position: 1, positionOffset: 0.6175926, 667
I/ViewPager2Activity: onPageScrolled: position: 1, positionOffset: 0.675, 729
I/ViewPager2Activity: onPageScrolled: position: 1, positionOffset: 0.7259259, 784
I/ViewPager2Activity: --------- onPageScrollStateChanged: state 0 ----------

三、Fragment作为page时,预加载页面的生命周期

3.1 ViewPager2的页面生命周期

  • 只有当前可见Fragment页面会执行onResume()。预加载页面的生命周期会执行到onStart(),不会执行onResume()。当页面划走不可见后,页面也会执行onPause()。

3.2 ViewPager的页面生命周期

  • VIewPager默认情况下当前页面和预加载的页面都会进入onResume()状态。
  • 造成的问题:Fragment内无法知道当前正在展示的页面,因为当前页面和预加载的页面都会执行到onResume()状态。如果某些操作在Fragment页面真正展示的时候才执行,而在预加载阶段不执行,那么就需要获取当前正在展示的页面。
  • 这种情况只能在外部把VIewPager当前页面index传入到Fragment内部,增加了复杂性。也造成了一些耦合。
  • Android平台配置:FragmentPagerAdapter的构造参数可以设置Fragment在预载时的生命周期,通过设置BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT,预载阶段的Fragment就只会执行到onStart状态,不会执行onResume状态。
java 复制代码
    /**
     * Indicates that {@link Fragment#setUserVisibleHint(boolean)} will be called when the current
     * fragment changes.
     *
     * @deprecated This behavior relies on the deprecated
     * {@link Fragment#setUserVisibleHint(boolean)} API. Use
     * {@link #BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT} to switch to its replacement,
     * {@link FragmentTransaction#setMaxLifecycle}.
     * @see #FragmentStatePagerAdapter(FragmentManager, int)
     */
    @Deprecated
    public static final int BEHAVIOR_SET_USER_VISIBLE_HINT = 0;

    /**
     * Indicates that only the current fragment will be in the {@link Lifecycle.State#RESUMED}
     * state. All other Fragments are capped at {@link Lifecycle.State#STARTED}.
     *
     * @see #FragmentStatePagerAdapter(FragmentManager, int)
     */
    public static final int BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT = 1;

四、简易的ViewPager2 + Fragment

4.1 Activity实现

kotlin 复制代码
package com.example.myapplication2

import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.Trace
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.viewpager2.widget.ViewPager2
import kotlinx.coroutines.Runnable
import kotlin.math.abs

class ViewPager2Activity : AppCompatActivity() {
  companion object {
    const val TAG = "ViewPager2Activity"
  }

  private lateinit var viewPager: ViewPager2
  private val onPageChangeCallback = object : ViewPager2.OnPageChangeCallback() {
    override fun onPageScrollStateChanged(state: Int) {
      Log.i(TAG, "--------- onPageScrollStateChanged: $state ----------")
      Trace.beginSection("onPageScrollStateChanged: $state")
      super.onPageScrollStateChanged(state)
      Trace.endSection()
    }

    override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
      Log.i(TAG, "onPageScrolled: position: $position, positionOffset: $positionOffset, $positionOffsetPixels")
      Trace.beginSection("onPageScrolled: $position")
      super.onPageScrolled(position, positionOffset, positionOffsetPixels)
      Trace.endSection()
    }

    override fun onPageSelected(position: Int) {
      Log.i(TAG, "-------- onPageSelected: $position -------------")
      Trace.beginSection("onPageSelected $position")
      super.onPageSelected(position)

      // 如果滑动到最后一个页面,则跳转到第一个页面。
      if (position == viewPager.adapter?.itemCount?.minus(1)) {
        viewPager.setCurrentItem(0, false)
      }
      Trace.endSection()
    }
  }

  private lateinit var handler: Handler
  private val nextPageRunnable = object : Runnable {
    override fun run() {
      viewPager.setCurrentItem(viewPager.currentItem + 1, true)
      handler.postDelayed(this, 3000)
    }
  }

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

    viewPager = findViewById(R.id.view_pager2)
    viewPager.adapter = ViewPager2Adapter(this)
    viewPager.registerOnPageChangeCallback(onPageChangeCallback)

    viewPager.setPageTransformer { page, position ->
      // 设置页面变换效果
      val scale = 1 - abs(position) * 0.3f
      page.scaleX = scale
      page.scaleY = scale
      page.alpha = 0.5f + (1 - abs(position)) * 0.5f
    }

    Log.i("MainActivity", "onCreate: ${viewPager.offscreenPageLimit}")
    // 设置ViewPager2的预加载页面数量,左右各有2个页面预加载或者保存在内存中
    viewPager.offscreenPageLimit = 1

    // 每一秒切换一个页面
    handler = Handler(Looper.getMainLooper())
    handler.postDelayed(nextPageRunnable, 3000)
  }
}

4.2 Adapter实现

kotlin 复制代码
package com.example.myapplication2

import android.util.Log
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.example.myapplication2.ui.dashboard.DashboardFragment
import com.example.myapplication2.ui.home.HomeFragment
import com.example.myapplication2.ui.notifications.NotificationsFragment

class ViewPager2Adapter(activity: FragmentActivity): FragmentStateAdapter(activity) {
  companion object {
    private const val TAG = "ViewPager2Adapter"
  }
  
  override fun getItemCount(): Int {
    return 8
  }

  override fun createFragment(position: Int): Fragment {
    Log.i(TAG, "----------createFragment: $position -----------------")
    return when (position % 3) {
      0 -> HomeFragment(position)
      1 -> DashboardFragment(position)
      2 -> NotificationsFragment(position)
      else -> throw IllegalStateException("Invalid position: $position")
    }
  }
}
相关推荐
Byron07071 天前
从 0 到 1 搭建 Vue 前端工程化体系:提效、提质、降本实战落地
前端·javascript·vue.js
哆啦code梦1 天前
前端存储三剑客:localStorage、sessionStorage与Cookie解析
前端·前端存储
徐小夕@趣谈前端1 天前
Web文档的“Office时刻“:jitword共建版2.0发布!让浏览器变成本地生产力
前端·数据结构·vue.js·算法·开源·编辑器·es6
Data_Journal1 天前
如何使用 Python 解析 JSON 数据
大数据·开发语言·前端·数据库·人工智能·php
德育处主任Pro1 天前
纯前端网格路径规划:PathFinding.js的使用方法
开发语言·前端·javascript
墨笔.丹青1 天前
基于QtQuick开发界面设计出简易的HarmonyUI界面----下
开发语言·前端·javascript
董世昌411 天前
深度解析浅拷贝与深拷贝:底层逻辑、实现方式及实战避坑
前端·javascript·vue.js
扶苏10021 天前
vue使用event.dataTransfer实现A容器数据拖拽复制到到B容器
前端·vue.js·chrome
David凉宸1 天前
Vue 3 项目的性能优化策略:从原理到实践
前端·vue.js·性能优化
小马_xiaoen1 天前
Proxy 与 Reflect 从入门到实战:ES6 元编程核心特性详解
前端·javascript·ecmascript·es6