目录1. 前言 2. 回顾RecyclerView缓存机制 3. offscreenPageLimit原理 4. FragmentStateAdapter原理以及缓存机制 4.1 简
众所周知ViewPager2是ViewPager的替代版本。它解决了ViewPager的一些痛点,包括支持right-to-left布局,支持垂直方向滑动,支持可修改的Fragment集合等。ViewPager2内部是使用RecyclerView来实现的。
所以它继承了RecyclerView的优势,包含但不限于以下:
ViewPager2相对于RecyclerView,它又扩展出了以下功能
如果熟悉RecyclerView,那么上手ViewPager2将会非常简单。可以简单把ViewPager2想象成每个ItemView都是全屏的RecyclerView。本文将重点讲解ViewPager2的离屏显示功能和基于FragmentStateAdapter的缓存机制。
本章节,简单回顾下RecyclerView缓存机制。RecyclerView有三级缓存,简单起见,这里只介绍mViewCaches和mRecyclerPool两种缓存池。更多关于RecyclerView的缓存原理,请移步公众号相关文章。
//Androidx.viewpager2:ViewPager2:1.0.0@aar
//ViewPager2.java
public void setOffscreenPageLimit(@OffscreenPageLimit int limit) {
if (limit < 1 && limit != OFFSCREEN_PAGE_LIMIT_DEFAULT) {
throw new IllegalArgumentException(
"Offscreen page limit must be OFFSCREEN_PAGE_LIMIT_DEFAULT or a number > 0");
}
mOffscreenPageLimit = limit;
mRecyclerView.requestLayout();
}
调用setOffscreenPageLimit方法就可以为ViewPager2设置离屏显示的个数,默认值为-1。如果设置不当,会抛异常。我们看到该方法,只是给mOffscreenPageLimit赋值。为什么就能实现离屏显示功能呢?如下代码
//androidx.viewpager2:ViewPager2:1.0.0@aar
//ViewPager2$LinearLayoutManagerImpl
@Override
protected void calculateExtraLayoutSpace(@NonNull RecyclerView.State state,
@NonNull int[] extraLayoutSpace) {
int pageLimit = getOffscreenPageLimit();
if (pageLimit == OFFSCREEN_PAGE_LIMIT_DEFAULT) {
super.calculateExtraLayoutSpace(state, extraLayoutSpace);
return;
}
final int offscreenSpace = getPageSize() * pageLimit;
extraLayoutSpace[0] = offscreenSpace;
extraLayoutSpace[1] = offscreenSpace;
}
以水平滑动ViewPager2为例:getPageSize()表示ViewPager2的宽度,离屏的空间大小为getPageSize() * pageLimit。extraLayoutSpace[0]表示左边的大小,extraLayoutSpace[1]表示右边的大小。
假设设置offscreenPageLimit为1,简单讲,Android系统会默认把画布宽度增加到3倍。左右两边各有一个离屏ViewPager2的宽度。
FragmentStateAdapter继承自RecyclerView.Adapter。它有一个抽象方法,createFragment()。它能将Fragment与ViewPager2完美结合。
public abstract class FragmentStateAdapter extends
RecyclerView.Adapter<FragmentViewHolder> implements StatefulAdapter {
public abstract Fragment createFragment(int position);
}
使用FragmentStateAdapter非常简单,Demo如下
class ViewPager2WithFragmentsActivity : AppCompatActivity() {
private lateinit var mViewPager2: ViewPager2
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_recycler_view_view_pager2)
mViewPager2 = findViewById(R.id.viewPager2)
(mViewPager2.getChildAt(0) as RecyclerView).layoutManager?.apply {
// isItemPrefetchEnabled = false
}
mViewPager2.orientation = ViewPager2.ORIENTATION_VERTICAL
mViewPager2.adapter = MyAdapter(this)
// mViewPager2.offscreenPageLimit = 1
}
inner class MyAdapter(fragmentActivity: FragmentActivity) :
FragmentStateAdapter(fragmentActivity) {
override fun getItemCount(): Int {
return 100
}
override fun createFragment(position: Int): Fragment {
return MyFragment("Item $position")
}
}
class MyFragment(val text: String) : Fragment() {
init {
println("MyFragment $text")
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
var view = layoutInflater.inflate(R.layout.view_item_view_pager_snap, container)
view.findViewById<TextView>(R.id.text_view).text = text
return view;
}
}
}
首先FragmentStateAdapter对应的ViewHolder定义如下,它只是返回一个简单的带有id的FrameLayout。由此可以看出,FragmentStateAdapter并不复用Fragment,它仅仅是复用FrameLayout而已。
public final class FragmentViewHolder extends ViewHolder {
private FragmentViewHolder(@NonNull FrameLayout container) {
super(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);
}
@NonNull FrameLayout getContainer() {
return (FrameLayout) itemView;
}
}
然后介绍FragmentStateAdapter中两个非常重要的数据结构:
final LongSparseArray<Fragment> mFragments = new LongSparseArray<>();
private final LongSparseArray<Integer> mItemIdToViewHolder = new LongSparseArray<>();
mFragments:是position与Fragment的映射表。随着position的增长,Fragment是会不断的新建出来的。 Fragment可以被缓存起来,当它被回收后无法重复使用。
Fragment什么时候会被回收掉呢?
mItemIdToViewHolder:是position与ViewHolder的Id的映射表。由于ViewHolder是RecyclerView缓存机制的载体。所以随着position的增长,ViewHolder并不会像Fragment那样不断的新建出来,而是会充分利用RecyclerView的复用机制。所以如下图,position 4处打上了一个大大的问号,具体的值是不确定的,它由缓存的大小以及离屏个数共同决定的。
接下来我们讲解onViewRecycled()。当ViewHolder从mViewCaches缓存中移出到mRecyclerPool缓存中时会调用该方法
@Override
public final void onViewRecycled(@NonNull FragmentViewHolder holder) {
final int viewHolderId = holder.getContainer().getId();
final Long boundItemId = itemForViewHolder(viewHolderId); // item currently bound to the VH
if (boundItemId != null) {
removeFragment(boundItemId);
mItemIdToViewHolder.remove(boundItemId);
}
}
该方法的作用是,当ViewHolder回收到RecyclerPool中时,将ViewHolder相关的信息从上面两张表中移除。
举例 当ViewHolder1发生回收时,position 0对应的信息从两张表中删除
最后讲解onBindViewHolder方法
@Override
public final void onBindViewHolder(final @NonNull FragmentViewHolder holder, int position) {
final long itemId = holder.getItemId();
final int viewHolderId = holder.getContainer().getId();
final Long boundItemId = itemForViewHolder(viewHolderId); // item currently bound to the VH
if (boundItemId != null && boundItemId != itemId) {
removeFragment(boundItemId);
mItemIdToViewHolder.remove(boundItemId);
}
mItemIdToViewHolder.put(itemId, viewHolderId); // this might overwrite an existing entry
ensureFragment(position);
final FrameLayout container = holder.getContainer();
if (ViewCompat.isAttachedToWindow(container)) {
if (container.getParent() != null) {
throw new IllegalStateException("Design assumption violated.");
}
container.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
if (container.getParent() != null) {
container.removeOnLayoutChangeListener(this);
placeFragmentInViewHolder(holder);
}
}
});
}
GCFragments();
}
该方法可以分成3个部分:
大概的脉络就是这样,为了避免文章冗余,其它的细支且也蛮重要的方法就没有列出来
默认情况:offscreenPageLimit = -1,开启预抓取功能
因为开启了预抓取,所以mViewCaches大小为3。
offscreenPageLimit=1,所以ViewPager2一下子能展示3屏Fragment,左右各显示一屏
到此这篇关于Android ViewPager2中缓存和复用机制的文章就介绍到这了,更多相关ViewPager2缓存和复用机制内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!
--结束END--
本文标题: 详解Android ViewPager2中的缓存和复用机制
本文链接: https://lsjlt.com/news/156122.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
2024-01-21
2023-10-28
2023-10-28
2023-10-27
2023-10-27
2023-10-27
2023-10-27
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0