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")
    }
  }
}
相关推荐
37方寸2 小时前
前端基础知识(HTML、CSS)
前端·css·html
吴声子夜歌2 小时前
RxJava——概述
android·rxjava
u1301302 小时前
深入解析二维码技术与前端生成方案
前端·二维码
小范馆2 小时前
STM32F03C8T6通过AT指令获取天气API-下篇
前端·stm32·esp8266-01s
开开心心_Every2 小时前
无广告输入法推荐:内置丰富词库免费皮肤
服务器·前端·学习·决策树·edge·powerpoint·动态规划
卓怡学长3 小时前
m111基于MVC的舞蹈网站的设计与实现
java·前端·数据库·spring boot·spring·mvc
C_心欲无痕8 小时前
前端实现水印的两种方式:SVG 与 Canvas
前端·安全·水印
尾善爱看海11 小时前
不常用的浏览器 API —— Web Speech
前端