ViewPager2+Fragment操作笔记

2024-02-09 06:58

本文主要是介绍ViewPager2+Fragment操作笔记,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

ViewPager2+Fragment操作笔记

心情好

文章目录

  • ViewPager2+Fragment操作笔记
    • ViewPager2简介
    • 实际操作效果
    • RecycleView和Viewpage2的滑动冲突
    • ViewPager2中Fragment的懒加载
      • 懒加载
      • 预加载
    • FragmentStateAdapter
      • onCreateViewHolder
      • onBindViewHolder
      • onViewAttachedToWindow
      • FragmentStateAdapter使用
      • 获取Fragment实例
      • 异常处理
    • ViewPager2滑动监听
    • TabLayout+TabLayoutMediator
    • DiffUtil 局部更新
    • 总结

ViewPager2简介

ViewPager2官网介绍

ViewPager2官网Samples

距离ViewPager2正式版的发布已经一年多了,目前ViewPager早已停止更新,官方鼓励使用ViewPager2替代。 ViewPager2底层基于RecyclerView实现,因此可以获得RecyclerView带来的诸多收益:

  • 抛弃传统的PagerAdapter,统一了AdapterAPI
  • 横向、竖向布局都可以实现自由滑动;
  • 支持DiffUitl,可以实现局部刷新;
  • 支持RTL(right-to-left),对于一些有出海需求的APP非常有用;
  • 支持ItemDecorator,搭配PageTransformer实现炫酷的跳转动画;

ViewPager2更多的是配合Fragment的使用,这需要借助于FragmentStateAdapter

他们偶尔会搭配TabLayout一起使用,相关代码直接阅读或者运行 ViewPager2官网Samples 即可,这里不做重复的讲解。

下面主要讲一下在使用过程中遇到的问题~!

实际操作效果

上滑吸顶+标题页面左右滑动+横滑和竖滑列表+标题页面数据和数量更新

上滑吸顶
CoordinatorLayout+AppBarLayout+CollapsingToolbarLayout
左右滑动
ViewPager2+TabLayout+Fragment
横滑和竖滑列表
RecycleView+NestedScrollableHost
标题页面数据和数量
TabLayoutMediator+声明周期检测+缓存优化

viewpage2_tabLayout_nestedscroll.gif

RecycleView和Viewpage2的滑动冲突

/*** Created by Tanzhenxing* Date: 2021/4/7 7:04 下午* Description:解决 [RecyclerView] 嵌套到 [androidx.viewpager2.widget.ViewPager2] 左右滑动冲突* 目前只解决了左右滑动冲突*/
class RecyclerViewAtViewPager2 : RecyclerView {constructor(context: Context) : this(context, null)constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)var x1 = 0fvar x2 = 0foverride fun dispatchTouchEvent(event: MotionEvent?): Boolean {if(event!!.action == MotionEvent.ACTION_DOWN) {x1 = event.x} else if(event.action == MotionEvent.ACTION_MOVE) {x2 = event.x} else if (event.action == MotionEvent.ACTION_CANCEL|| event.action == MotionEvent.ACTION_UP) {x2 = 0fx1 = 0f}val xOffset= x2-x1if (layoutManager is LinearLayoutManager) {val linearLayoutManager = layoutManager as LinearLayoutManagerif (linearLayoutManager.orientation == HORIZONTAL) {if ((xOffset <= 0 && canScrollHorizontally(1))|| (xOffset >= 0 && canScrollHorizontally(-1))) {this.parent?.requestDisallowInterceptTouchEvent(true)} else {this.parent?.requestDisallowInterceptTouchEvent(false)}} else {// TODO: 2021/4/8 目前没有实现上下滑动和 [androidx.viewpager2.widget.ViewPager2]上下滑动的冲突}} else {handleDefaultScroll()}return super.dispatchTouchEvent(event)}fun handleDefaultScroll() {val canScrollHorizontally = canScrollHorizontally(-1) || canScrollHorizontally(1)val canScrollVertically = canScrollVertically(-1) || canScrollVertically(1)if (canScrollHorizontally || canScrollVertically) {this.parent?.requestDisallowInterceptTouchEvent(true)} else {this.parent?.requestDisallowInterceptTouchEvent(false)}}
}

ViewPager2中Fragment的懒加载

懒加载

一般我们使用Fragment对页面进行数据懒加载的时候都是通过onHiddenChanged方法判断显示和隐藏,在第一次展现出来的时候再进行接口调用。

 @Override
public final void onHiddenChanged(boolean hidden) {super.onHiddenChanged(hidden);if (!hidden) {onUserVisible();} else {onUserGone();}
}

但在ViewPager2中,FragmentsetUserVisibleHintonHiddenChanged方法都是不执行的。

  • ViewPager展现第一个页面,然后切后台的日志:
04-17 16:45:10.992 D/tanzhenxing:11(22006): onCreate
04-17 16:45:10.992 D/tanzhenxing:11(22006): onCreateView:
04-17 16:45:11.004 D/tanzhenxing:11(22006): onActivityCreated
04-17 16:45:11.004 D/tanzhenxing:11(22006): onViewStateRestored: 184310198
04-17 16:45:11.004 D/tanzhenxing:11(22006): onStart
04-17 16:45:11.004 D/tanzhenxing:11(22006): onResume
04-17 16:45:18.739 D/tanzhenxing:11(22006): onPause
04-17 16:45:18.779 D/tanzhenxing:11(22006): onStop

然后在切回前台的日志:

04-17 16:53:40.749 D/tanzhenxing:11(22006): onStart
04-17 16:53:40.752 D/tanzhenxing:11(22006): onResume
  • ViewPager展现第一个页面,然后手动滑动到第二个页面日志:
04-17 16:54:44.168 D/tanzhenxing:12(22006): onCreate
04-17 16:54:44.168 D/tanzhenxing:12(22006): onCreateView:
04-17 16:54:44.178 D/tanzhenxing:12(22006): onActivityCreated
04-17 16:54:44.178 D/tanzhenxing:12(22006): onViewStateRestored: 47009644
04-17 16:54:44.178 D/tanzhenxing:12(22006): onStart
04-17 16:54:44.553 D/tanzhenxing:11(22006): onPause
04-17 16:54:44.554 D/tanzhenxing:12(22006): onResume

这样看起来我们是可以利用Fragment声明周期中的onStartonResume方法做懒加载。

预加载

只要讲数据请求写在 onCreateView 或者onStart中就能进行接口的离屏请求。

FragmentStateAdapter

ViewPager2继承自RecyclerView,大概率FragmentStateAdapter继承自RecyclerView.Adapter

public abstract class FragmentStateAdapter extends RecyclerView.Adapter<FragmentViewHolder> implements StatefulAdapter {
}

onCreateViewHolder

onCreateViewHolderRecycleVeiw用于创建ViewHolder的方法:

@NonNull
@Override
public final FragmentViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){return FragmentViewHolder.create(parent);
}

FragmentViewHolder的主要作用是通过FrameLayoutFragment提供用作容器的container

@NonNull
static FragmentViewHolder create(@NonNull
ViewGroup parent) {FrameLayout container = new FrameLayout(parent.getContext());container.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT));container.setId(ViewCompat.generateViewId());container.setSaveEnabled(false);return new FragmentViewHolder(container);
}

onBindViewHolder

onBindViewHolderRecycleVeiw用于数据绑定的方法:

@Override
public final void onBindViewHolder(final @NonNull FragmentViewHolder holder, int position) {/**部分代码省略 */ensureFragment(position);/**部分代码省略 */gcFragments();
}

ensureFragment(position),其内部会最终回调用createFragment创建当前Fragment

private void ensureFragment(int position) {long itemId = getItemId(position);if (!mFragments.containsKey(itemId)) {// TODO(133419201): check if a Fragment provided here is a new FragmentFragment newFragment = createFragment(position);newFragment.setInitialSavedState(mSavedStates.get(itemId));mFragments.put(itemId, newFragment);}
}

mFragments缓存创建的Fragment,供后面placeFramentInViewholder使用; gcFragments回收已经不再使用的的Fragment对应的item已经删除),节省内存开销。

onViewAttachedToWindow

onViewAttachedToWindowViewHolder出现在页面中回调。

@Override
public final void onViewAttachedToWindow(@NonNull final FragmentViewHolder holder) {//将FragmentViewHolder的container与当前Fragment绑定placeFragmentInViewHolder(holder);gcFragments();
}

FragmentStateAdapter使用

  • Fragment对象容器;
  • 生产fragment识别的id
class MyFragmentStateAdapter(val data: List<Int>, fragment: Fragment) : FragmentStateAdapter(fragment){private val fragments = mutableMapOf<Int, Fragment>()override fun createFragment(position: Int): Fragment {val value = data[position]val fragment = fragments[value]if (fragment != null) {return fragment}val cardFragment =NestedAllRecyclerViewFragment.create(value)fragments[value] = cardFragmentreturn cardFragment}/*** 根据数据生成唯一id** 如果不重写,那么在调用[notifyDataSetChanged]更新的时候** 会抛出```new IllegalStateException("Fragment already added")```异常*/override fun getItemId(position: Int): Long {return data[position].toLong()}/*** 用来判断当前id对应的fragment是否添加过*/override fun containsItem(itemId: Long): Boolean {data.forEach {if (it.toLong() == itemId) {return true}}return false}override fun getItemCount(): Int {return data.size}
}

在进行

获取Fragment实例

fun getCurrentFragment(position: Int): Fragment? =fragment.childFragmentManager.findFragmentByTag("f$position")

源码分析:

public abstract class FragmentStateAdapter extends RecyclerView.Adapter<FragmentViewHolder>implements StatefulAdapter {/**部分代码省略 */void placeFragmentInViewHolder(@NonNullfinal FragmentViewHolder holder) {/**部分代码省略 */if (!shouldDelayFragmentTransactions()) {scheduleViewAttach(fragment, container);mFragmentManager.beginTransaction().add(fragment, "f" + holder.getItemId()).setMaxLifecycle(fragment, STARTED).commitNow();mFragmentMaxLifecycleEnforcer.updateFragmentMaxLifecycle(false);}/**部分代码省略 */}/**部分代码省略 */@Overridepublic long getItemId(int position) {return position;}
}

Fragment添加的TAG"f" + holder.getItemId()

异常处理

  • 初始化时遇到的崩溃;
Fragment HomeFragment{b793d14 (e67290fe-7ab1-4b2b-b98c-4e08d146644c)} has not been attached yet.
com.xxx.xxx.xxx.adapter.HomeFragmentStateAdapter.<init>(SourceFile:29)

在开发过程中遇到问题,需要在构造FragmentStateAdapter的时候对Fragment的状态做判断isAdded()

  • 更新数据的时候遇到的崩溃:
Fragment already added

重写getItemId方法,该方法返回的值与数据有关而不是与数据在列表中的索引有关。因为它代表着Fragment的唯一性,是否可以复用。

ViewPager2滑动监听

public abstract static class OnPageChangeCallback {//当前页面开始滑动时public void onPageScrolled(int position, float positionOffset,@Px int positionOffsetPixels) {}//当页面被选中时public void onPageSelected(int position) {}//当前页面滑动状态变动时public void onPageScrollStateChanged(@ScrollState int state) {}
}

实现:

var pageChangeCallback = object : ViewPager2.OnPageChangeCallback() {override fun onPageSelected(position: Int) {//需要注意的是postion需要做大于0的判断}
}

TabLayout+TabLayoutMediator

方便实现TABViewPager滑动或跳转的关联。

implementation 'com.google.android.material:material:1.2.0'

建议material的版本号大约1.0.0,否则实现TAB的自定义布局宽度展现些问题。

使用:ViewPager2官网Samples

DiffUtil 局部更新

DiffUtil和它的差量算法

总结

本文主要介绍了ViewPager2配合Fragment的使用方法以及在使用过程中需要注意的问题,顺带提到了TabLayoutOnPageChangeCallbackDiffUtil等。

文章到这里就全部讲述完啦,若有其他需要交流的可以留言哦~!~!

想阅读作者的更多文章,可以查看我 个人博客 和公共号:
振兴书城

这篇关于ViewPager2+Fragment操作笔记的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/693456

相关文章

Java字符串操作技巧之语法、示例与应用场景分析

《Java字符串操作技巧之语法、示例与应用场景分析》在Java算法题和日常开发中,字符串处理是必备的核心技能,本文全面梳理Java中字符串的常用操作语法,结合代码示例、应用场景和避坑指南,可快速掌握字... 目录引言1. 基础操作1.1 创建字符串1.2 获取长度1.3 访问字符2. 字符串处理2.1 子字

Python 中的 with open文件操作的最佳实践

《Python中的withopen文件操作的最佳实践》在Python中,withopen()提供了一个简洁而安全的方式来处理文件操作,它不仅能确保文件在操作完成后自动关闭,还能处理文件操作中的异... 目录什么是 with open()?为什么使用 with open()?使用 with open() 进行

Linux ls命令操作详解

《Linuxls命令操作详解》通过ls命令,我们可以查看指定目录下的文件和子目录,并结合不同的选项获取详细的文件信息,如权限、大小、修改时间等,:本文主要介绍Linuxls命令详解,需要的朋友可... 目录1. 命令简介2. 命令的基本语法和用法2.1 语法格式2.2 使用示例2.2.1 列出当前目录下的文

利用Python快速搭建Markdown笔记发布系统

《利用Python快速搭建Markdown笔记发布系统》这篇文章主要为大家详细介绍了使用Python生态的成熟工具,在30分钟内搭建一个支持Markdown渲染、分类标签、全文搜索的私有化知识发布系统... 目录引言:为什么要自建知识博客一、技术选型:极简主义开发栈二、系统架构设计三、核心代码实现(分步解析

Mysql表的简单操作(基本技能)

《Mysql表的简单操作(基本技能)》在数据库中,表的操作主要包括表的创建、查看、修改、删除等,了解如何操作这些表是数据库管理和开发的基本技能,本文给大家介绍Mysql表的简单操作,感兴趣的朋友一起看... 目录3.1 创建表 3.2 查看表结构3.3 修改表3.4 实践案例:修改表在数据库中,表的操作主要

C# WinForms存储过程操作数据库的实例讲解

《C#WinForms存储过程操作数据库的实例讲解》:本文主要介绍C#WinForms存储过程操作数据库的实例,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、存储过程基础二、C# 调用流程1. 数据库连接配置2. 执行存储过程(增删改)3. 查询数据三、事务处

Java使用Curator进行ZooKeeper操作的详细教程

《Java使用Curator进行ZooKeeper操作的详细教程》ApacheCurator是一个基于ZooKeeper的Java客户端库,它极大地简化了使用ZooKeeper的开发工作,在分布式系统... 目录1、简述2、核心功能2.1 CuratorFramework2.2 Recipes3、示例实践3

Java利用JSONPath操作JSON数据的技术指南

《Java利用JSONPath操作JSON数据的技术指南》JSONPath是一种强大的工具,用于查询和操作JSON数据,类似于SQL的语法,它为处理复杂的JSON数据结构提供了简单且高效... 目录1、简述2、什么是 jsONPath?3、Java 示例3.1 基本查询3.2 过滤查询3.3 递归搜索3.4

Python使用DrissionPage中ChromiumPage进行自动化网页操作

《Python使用DrissionPage中ChromiumPage进行自动化网页操作》DrissionPage作为一款轻量级且功能强大的浏览器自动化库,为开发者提供了丰富的功能支持,本文将使用Dri... 目录前言一、ChromiumPage基础操作1.初始化Drission 和 ChromiumPage

利用Go语言开发文件操作工具轻松处理所有文件

《利用Go语言开发文件操作工具轻松处理所有文件》在后端开发中,文件操作是一个非常常见但又容易出错的场景,本文小编要向大家介绍一个强大的Go语言文件操作工具库,它能帮你轻松处理各种文件操作场景... 目录为什么需要这个工具?核心功能详解1. 文件/目录存javascript在性检查2. 批量创建目录3. 文件