Android-高级 UI-09- ViewPager 和 ViewPager2

315 阅读7分钟

ViewPager 和 ViewPager2 对比分析

ViewPagerViewPager2 是 Android 提供的用于切换视图的组件,广泛用于实现滑动界面(如引导页、图片轮播等)。ViewPager2ViewPager 的改进版本,解决了许多功能限制和性能问题。以下是两者的详细对比分析:


1. 架构与实现差异

ViewPager

  • 基于 ViewGroup
  • 使用 PagerAdapter 提供数据。
  • 滑动实现基于 ScrollerVelocityTracker

ViewPager2

  • 基于 RecyclerView,继承自 FrameLayout
  • 使用 RecyclerView.Adapter 提供数据。
  • 滑动行为由 RecyclerView 提供,内部依赖 LinearLayoutManager

2. 功能对比

功能ViewPagerViewPager2
方向支持仅支持 水平滑动支持 水平和垂直滑动setOrientation()
Fragment 支持通过 FragmentPagerAdapterFragmentStatePagerAdapter通过 FragmentStateAdapter,更高效和简单
数据更新更新数据时需要调用 notifyDataSetChanged()更高效的局部刷新机制(基于 RecyclerView.Adapter 的 notifyItemChanged()
嵌套滚动嵌套滚动需要手动处理冲突自动支持嵌套滚动,解决了嵌套 RecyclerView 滚动冲突
RecyclerView 支持不支持直接嵌套 RecyclerView内部就是 RecyclerView,天然支持嵌套使用
RTL 支持手动处理翻转布局原生支持 RTL 方向切换

ViewPager懒加载嵌套分发

image.png

ViewPager2加载Fragment

1. 添加依赖

确保在 build.gradle 中添加了 ViewPager2 的依赖:

groovy
复制代码
implementation 'androidx.viewpager2:viewpager2:1.0.0'

2. 创建布局文件

在主布局文件中添加 ViewPager2

xml
复制代码
<androidx.viewpager2.widget.ViewPager2
    android:id="@+id/viewPager2"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

3. 创建 Fragment 类

创建多个 Fragment 类,比如:

Fragment1.java

java
复制代码
public class Fragment1 extends Fragment {

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_1, container, false);
    }
}

Fragment2.java

java
复制代码
public class Fragment2 extends Fragment {

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_2, container, false);
    }
}

创建对应的 fragment_1.xmlfragment_2.xml 布局文件。


4. 创建 FragmentAdapter

ViewPager2 创建适配器类,用于管理 Fragment

java
复制代码
public class FragmentAdapter extends FragmentStateAdapter {

    private final List<Fragment> fragmentList;

    public FragmentAdapter(@NonNull FragmentActivity fragmentActivity, List<Fragment> fragments) {
        super(fragmentActivity);
        this.fragmentList = fragments;
    }

    @NonNull
    @Override
    public Fragment createFragment(int position) {
        return fragmentList.get(position); // 返回对应位置的 Fragment
    }

    @Override
    public int getItemCount() {
        return fragmentList.size(); // Fragment 数量
    }
}

5. 初始化 ViewPager2

Activity 中初始化 ViewPager2 并设置适配器:

java
复制代码
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ViewPager2 viewPager2 = findViewById(R.id.viewPager2);

        // 准备 Fragment 列表
        List<Fragment> fragmentList = new ArrayList<>();
        fragmentList.add(new Fragment1());
        fragmentList.add(new Fragment2());

        // 设置适配器
        FragmentAdapter adapter = new FragmentAdapter(this, fragmentList);
        viewPager2.setAdapter(adapter);
    }
}

6 更多功能
1. 添加 TabLayout

如果需要添加 TabLayout,可以通过 TabLayoutMediator 实现:

布局文件

xml
复制代码
<com.google.android.material.tabs.TabLayout
    android:id="@+id/tabLayout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="?attr/colorPrimary"
    app:tabIndicatorColor="@android:color/white" />

<androidx.viewpager2.widget.ViewPager2
    android:id="@+id/viewPager2"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

代码实现

java
复制代码
TabLayout tabLayout = findViewById(R.id.tabLayout);
ViewPager2 viewPager2 = findViewById(R.id.viewPager2);

FragmentAdapter adapter = new FragmentAdapter(this, fragmentList);
viewPager2.setAdapter(adapter);

// 将 TabLayout 与 ViewPager2 关联
new TabLayoutMediator(tabLayout, viewPager2, (tab, position) -> {
    tab.setText("Tab " + (position + 1)); // 设置 Tab 标题
}).attach();

2. 垂直滚动

通过设置 ViewPager2 的方向可以实现垂直滚动:

java
复制代码
viewPager2.setOrientation(ViewPager2.ORIENTATION_VERTICAL);

3. 页面切换监听

监听页面滑动或切换事件:

java
复制代码
viewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
    @Override
    public void onPageSelected(int position) {
        super.onPageSelected(position);
        Log.d("ViewPager2", "Page selected: " + position);
    }
});

ViewPager2 的懒加载机制解析与实现方案

ViewPager2 默认不具备自动懒加载功能,但其底层基于 RecyclerView 实现,通过合理的生命周期监听和适配器配置,可有效实现懒加载效果。以下是具体分析及实现方案:


一、ViewPager2 的默认行为
  1. 预加载机制

    • ViewPager2 默认不会像 ViewPager 那样预加载相邻 Fragment15,但其内部 RecyclerView 的缓存机制会保留当前 Fragment 及相邻的 Fragment 实例(数量由 offscreenPageLimit 控制)1
    • 默认的 offscreenPageLimit 值为 1(实际可能动态调整),可通过 setOffscreenPageLimit(0) 强制减少缓存,但最小值仍为 15
  2. 生命周期触发特点

    • 当 Fragment 进入可视范围时,其 onResume() 会被触发;离开时触发 onPause(),但不会立即销毁24
    • 若需仅在用户可见时加载数据,需手动监听生命周期或结合其他标志位。

二、懒加载实现方案
方案 1:基于 Fragment 生命周期方法
kotlin
复制
abstract class LazyFragment : Fragment() {
    private var isViewCreated = false 
    private var isFirstVisible = true 
 
    override fun onResume() {
        super.onResume() 
        if (isViewCreated && isFirstVisible) {
            loadData() // 首次可见时加载数据 
            isFirstVisible = false 
        }
    }
 
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view,  savedInstanceState)
        isViewCreated = true 
    }
 
    abstract fun loadData()
}
  • 原理:通过 onResume() 判断是否为首次可见,避免重复加载24
方案 2:利用 FragmentStateAdapter 回调
kotlin
复制
viewPager2.registerOnPageChangeCallback(object  : ViewPager2.OnPageChangeCallback() {
    override fun onPageSelected(position: Int) {
        val fragment = adapter.getFragmentAt(position) 
        (fragment as? LazyFragment)?.onVisible()
    }
})
  • 原理:监听页面切换事件,触发当前 Fragment 的可见性回调35
方案 3:动态调整预加载数量
kotlin
复制
viewPager2.setOffscreenPageLimit(0)   // 尽可能减少缓存(实际至少保留1个)
  • 注意:即使设置为 0,系统仍会保留至少 1 个相邻 Fragment15。这里是默认的预加载机制,所以该方案不可行

三、常见问题与优化
  1. 生命周期重复触发

    • 避免在 onResume() 中直接加载数据,需结合标志位判断是否首次可见4
  2. 数据销毁与恢复

    • 使用 ViewModel 或 SavedStateHandle 缓存数据,避免重复请求5
  3. 嵌套 Fragment 场景

    • 在多层 ViewPager2 嵌套时,需逐层传递可见性事件25

四、与 ViewPager 的对比
特性ViewPagerViewPager2
默认预加载预加载相邻 1 个 Fragment动态调整,更灵活1
懒加载实现复杂度需手动管理 setUserVisibleHint基于生命周期更直观2
缓存机制固定数量基于 RecyclerView 动态优化5

总结:ViewPager2 本身不提供开箱即用的懒加载,但通过监听 Fragment 生命周期或页面切换事件,可高效实现按需加载。建议优先采用 方案1 结合标志位控制,兼顾代码简洁性与性能24

3. 性能对比

ViewPager

  • 内存占用:一次性加载所有页面,适合少量固定页面,但会消耗较多内存。
  • 页面回收:使用 instantiateItem()destroyItem() 机制,页面的回收与复用较弱。
  • 嵌套滑动性能:嵌套滑动冲突处理复杂。

ViewPager2

  • 内存占用:基于 RecyclerView 的复用机制,仅加载当前页面及相邻页面,内存占用更低。
  • 页面回收:页面的回收与复用由 RecyclerView 完成,性能更高。
  • 嵌套滑动性能:利用 RecyclerView 原生支持嵌套滑动,性能表现更好。

4. 开发简便性

ViewPager

  • 需要使用专门的 Adapter(如 PagerAdapter)。
  • 自定义功能需要额外处理(如垂直滑动、嵌套滑动等)。

ViewPager2

  • 可复用 RecyclerView.Adapter,与 RecyclerView 的使用方式保持一致。
  • 更灵活:例如可以轻松添加动画、分割线等。

5. 特性对比

特性ViewPagerViewPager2
页面缓存可设置缓存页数(setOffscreenPageLimit()同样支持,但基于 RecyclerView 的实现更高效
ItemDecoration不支持支持 RecyclerView 的 ItemDecoration
滑动动画支持默认的页面切换动画,需手动自定义支持 RecyclerView 的 ItemAnimator
事件拦截与处理自定义滑动冲突处理较复杂内置更好的滑动冲突处理机制
多方向滑动仅支持水平滑动支持水平和垂直滑动
DiffUtil 支持不支持支持基于 RecyclerView 的 DiffUtil 更新优化

6. 适用场景对比

场景推荐使用理由
简单的静态页面滑动ViewPager实现简单,无需复杂配置
动态数据更新(如新闻列表)ViewPager2基于 RecyclerView,数据更新更高效
嵌套滑动ViewPager2自动支持嵌套滑动,处理滑动冲突更简单
支持垂直滑动的场景ViewPager2原生支持垂直滑动
页面数量较多(如电商轮播)ViewPager2RecyclerView 提供了更高效的复用机制

7. 示例代码

ViewPager 示例

java
复制代码
ViewPager viewPager = findViewById(R.id.viewPager);
viewPager.setAdapter(new MyPagerAdapter());

ViewPager2 示例

java
复制代码
ViewPager2 viewPager2 = findViewById(R.id.viewPager2);
viewPager2.setAdapter(new MyRecyclerViewAdapter());
viewPager2.setOrientation(ViewPager2.ORIENTATION_VERTICAL);

总结

对比维度ViewPagerViewPager2
性能较弱基于 RecyclerView 更强
灵活性较低更高
功能扩展手动实现较多自带多种新特性
复杂场景适配较弱更好

总体来说,ViewPager2ViewPager 的全面升级版,推荐在新项目中使用 ViewPager2