1 前言
上一章节【Android Framework系列】第14章 Fragment核心原理(AndroidX版本)我们学习了Fragment的核心原理,本章节学习常用的Fragment+ViewPager
以及Fragment+ViewPager2
的相关使用和一些基本的源码分析。
2 Fragment+ViewPager
我们常用的两个PagerAdapter
的实现类,也就是FragmentStatePagerAdapter
和FragmentPagerAdapter
,今天,我们就来学习一下它们的使用方法,并进行对比。
2.1 FragmentPagerAdapter 和 FragmentStatePagerAdapter 的区别
1. fragments
对象的处理:
FragmentPagerAdapter
:范围外fragments
会保存在内存中(detach
),但是fragment
对应的View
会被销毁
FragmentStatePagerAdapter
:范围外fragments
不会保存在内存中(remove
),View
也会被销毁。
2. 状态的处理:
FragmentPagerAdapter
:范围外fragments
对应的SavedState
会保存
FragmentStatePagerAdapter
:只保存范围内fragments
对应的SavedState
。这个SavedState
在Fragment
的生命周期回调中供外部传参数,和Activity
类似。
3. 适用场景:相同数量的fragments
,FragmentPagerAdapter
内存较大,但页面切换更友好;FragmentStatePagerAdapter
内存占用少,页面切换稍差。
因此FragmentPagerAdapter
适用于Fragment
数量少的情况,FragmentStatePagerAdapter
适用于Fragment数量多的情况。
我们首先来看下FragmentPagerAdapter
:
@Override
public Object instantiateItem(ViewGroup container, int position) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
final long itemId = getItemId(position);
// Do we already have this fragment?
//判断请求的Fragment是否已经被生成过
String name = makeFragmentName(container.getId(), itemId);
Fragment fragment = mFragmentManager.findFragmentByTag(name);
if (fragment != null) {
if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
// 当前缓存有则直接使用
mCurTransaction.attach(fragment);
} else {
fragment = getItem(position);
//调用这个方法来生成新的Fragment
if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
// 将新生成的Fragment存储起来,以便以后再次用到时,直接attach()
mCurTransaction.add(container.getId(), fragment,
makeFragmentName(container.getId(), itemId));
}
if (fragment != mCurrentPrimaryItem) {
fragment.setMenuVisibility(false);
if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
} else {
fragment.setUserVisibleHint(false);
}
}
return fragment;
}
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
Fragment fragment = (Fragment) object;
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
+ " v=" + fragment.getView());
mCurTransaction.detach(fragment);
if (fragment.equals(mCurrentPrimaryItem)) {
mCurrentPrimaryItem = null;
}
}
FragmentPagerAdapter
的destroyItem
方法中调用了detach()
只是改变Fragment状态,说明只有消除整个adapter
时候,才能将生成的Fragment
都消除掉,不然就会直接在内存中。
接下来我们对比FragmentStatePagerAdapter
:
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
// If we already have this item instantiated, there is nothing
// to do. This can happen when we are restoring the entire pager
// from its saved state, where the fragment manager has already
// taken care of restoring the fragments we previously had instantiated.
// mFragments中对应位置有Fragment的情况下直接返回
if (mFragments.size() > position) {
Fragment f = mFragments.get(position);
if (f != null) {
return f;
}
}
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
Fragment fragment = getItem(position);
if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
if (mSavedState.size() > position) {
Fragment.SavedState fss = mSavedState.get(position);
if (fss != null) {
fragment.setInitialSavedState(fss);
}
}
while (mFragments.size() <= position) {
mFragments.add(null);
}
fragment.setMenuVisibility(false);
if (mBehavior == BEHAVIOR_SET_USER_VISIBLE_HINT) {
fragment.setUserVisibleHint(false);
}
mFragments.set(position, fragment);
mCurTransaction.add(container.getId(), fragment);
if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
}
return fragment;
}
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
Fragment fragment = (Fragment) object;
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
+ " v=" + ((Fragment)object).getView());
while (mSavedState.size() <= position) {
mSavedState.add(null);
}
mSavedState.set(position, fragment.isAdded()
? mFragmentManager.saveFragmentInstanceState(fragment) : null);
mFragments.set(position, null);
// 缓存中移除fragment,下次使用得重新创建
mCurTransaction.remove(fragment);
if (fragment.equals(mCurrentPrimaryItem)) {
mCurrentPrimaryItem = null;
}
}
FragmentStatePagerAdapter
在destroyItem
方法调用的时候把Fragment
移除了,因此下次使用需要重新创建Fragment
3 Fragment+ViewPager2
ViewPager2
是Android Jetpack
库中的一个组件,是用于在应用程序中实现页面切换和滑动效果的容器。其实ViewPager2
本身继承自RecyclerView
。可以复习下我们的【Android Framework系列】第12章 RecycleView相关原理及四级缓存策略分析
3.1 ViewPager2的作用和用途
3.1.1 ViewPager2使用场景
是一个功能强大的滑动容器,可以应用于多种场景中,提供了灵活的页面切换和布局定制功能,使得应用程序界面更加丰富和交互性强,可以用于以下场景:
-
实现引导页或欢迎页
ViewPager2
可以用于创建引导页或欢迎页,让用户通过滑动浏览介绍应用程序功能或展示欢迎内容。 -
创建图片浏览器
ViewPager2
可以用于创建图片浏览器,允许用户通过滑动来切换不同的图片,并支持缩放和手势交互。 -
构建轮播图
ViewPager2
非常适合构建轮播图功能,可以通过适配器动态加载不同的轮播项,并提供自动循环滚动的功能。 -
实现选项卡式布局
结合TabLayout
,ViewPager2
可以用于创建选项卡式布局,让用户通过滑动选项卡来切换不同的内容页面。 -
创建垂直滑动页面
与ViewPager
不同,ViewPager2
支持垂直方向的滑动,因此可以用于创建垂直滑动的页面布局,例如垂直滑动的导航菜单或垂直的新闻列表。 -
实现分页数据展示
ViewPager2
可以用于展示分页数据,例如将大量数据按页加载并在每一页中展示一部分内容。 -
嵌套滑动布局
ViewPager2
可以与其他滑动组件(如RecyclerView
)嵌套使用,实现复杂的滑动布局结构。 -
实现自定义的滑动效果
通过使用自定义的转换器(Transformer
),可以实现各种炫酷的页面切换效果,例如渐变、缩放、旋转等。
3.1.2 ViewPager2相较于ViewPager的改进和优势
ViewPager2
是对ViewPager
的改进版本,提供了更好的性能、更灵活的适配器和更丰富的功能。它是构建滑动页面布局的首选组件,可以在应用程序中实现各种滑动页面的需求,并提供更好的用户体验,大致有以下几点改进和优势:
-
支持垂直滑动
ViewPager2
是在ViewPager
的基础上进行改进的,最显著的改进之一是支持垂直滑动
。而在ViewPager
中,只支持水平滑动。这使得ViewPager2
在创建垂直布局或特定场景下的垂直滑动功能更加方便和灵活。 -
更好的性能和稳定性
ViewPager2
内部实现使用了RecyclerView
作为容器,而不再依赖于ViewPager
的实现方式。这使得ViewPager2
具有RecyclerView
的优势,例如更好的性能和内存管理
、更流畅的滑动体验
以及更好的布局回收和复用机制
。同时,ViewPager2
还解决了ViewPager
一些已知的问题和不稳定性,如条目位置错乱、刷新数据的不及时等。 -
支持使用Fragment作为页面
与ViewPager
不同,ViewPager2
直接支持使用Fragment
作为页面,而无需通过FragmentPagerAdapter
或FragmentStatePagerAdapter
进行适配。这简化了页面管理和生命周期处理,并提供了更直观和一致的使用体验。 -
更灵活的适配器
ViewPager2
引入了新的适配器接口,即RecyclerView.Adapter
的子类RecyclerView.Adapter
。这使得适配器的创建和管理更加灵活,同时提供了更多的功能和扩展性。 -
更丰富的功能和接口
ViewPager2
提供了许多新的功能和接口,例如支持页面预加载、更强大的页面切换动画支持、更丰富的回调接口等。这些功能和接口使得开发者能够更好地控制和定制ViewPager2
的行为和外观。
3.2 FragmentStateAdapter
上面我们了解了ViewPager2
对比ViewPager
的一些区别和优化点,下面我们继续看一下ViewPager2
对应的Adapter
。
我们知道ViewPager2
继承自RecyclerView
,那么它所对应的FragmentStateAdapter
必定继承自RecyclerView.Adapter
,这点小伙伴们应该能理解。那么我们在RecyclerView
中的每个Item
加入Fragment
作为容器展示。
RecyclerView.Adapter
关注的是ViewHolder
的复用,但是在FragmentStateAdapter
中的Framgent
是不会复用的,即有多少个item
就应该创建多少个Fragment
,那么这其中是如何转换的呢?
我们先来看看FragmentStateAdapter
源码:
public abstract class FragmentStateAdapter extends
RecyclerView.Adapter<FragmentViewHolder> implements StatefulAdapter {
// 通过FragmentStateAdapter声明中的泛型可以知道,
// ViewPager2之所以能够在RecyclerView的基础上能对外屏蔽对ViewHolder的使用,
// 其内部是借助FragmentViewHolder实现的,其内部就new了一个FrameLayout。
@Override
public final FragmentViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return FragmentViewHolder.create(parent);
}
@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
// 内部会最终回调到createFragment用来创建当前Fragment
ensureFragment(position);
/** Special case when {@link RecyclerView} decides to keep the {@link container}
* attached to the window, but not to the view hierarchy (i.e. parent is null) */
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);
}
}
});
}
// 回收已经不在item集合中的Fragment,节省内存开销
gcFragments();
}
private void ensureFragment(int position) {
long itemId = getItemId(position);
if (!mFragments.containsKey(itemId)) {
// TODO(133419201): check if a Fragment provided here is a new Fragment
Fragment newFragment = createFragment(position);
newFragment.setInitialSavedState(mSavedStates.get(itemId));
mFragments.put(itemId, newFragment);
}
}
void gcFragments() {
if (!mHasStaleFragments || shouldDelayFragmentTransactions()) {
return;
}
// Remove Fragments for items that are no longer part of the data-set
Set<Long> toRemove = new ArraySet<>();
for (int ix = 0; ix < mFragments.size(); ix++) {
long itemId = mFragments.keyAt(ix);
if (!containsItem(itemId)) {
toRemove.add(itemId);
mItemIdToViewHolder.remove(itemId); // in case they're still bound
}
}
// Remove Fragments that are not bound anywhere -- pending a grace period
if (!mIsInGracePeriod) {
mHasStaleFragments = false; // we've executed all GC checks
for (int ix = 0; ix < mFragments.size(); ix++) {
long itemId = mFragments.keyAt(ix);
if (!isFragmentViewBound(itemId)) {
toRemove.add(itemId);
}
}
}
for (Long itemId : toRemove) {
removeFragment(itemId);
}
}
// onViewAttachToWindow的时候调用placeFragmentInViewHolder,
// 将FragmentViewHolder的container与当前Fragment绑定
@Override
public final void onViewAttachedToWindow(@NonNull final FragmentViewHolder holder) {
placeFragmentInViewHolder(holder);
gcFragments();
}
void placeFragmentInViewHolder(@NonNull final FragmentViewHolder holder) {
Fragment fragment = mFragments.get(holder.getItemId());
if (fragment == null) {
throw new IllegalStateException("Design assumption violated.");
}
FrameLayout container = holder.getContainer();
View view = fragment.getView();
if (!fragment.isAdded() && view != null) {
throw new IllegalStateException("Design assumption violated.");
}
// { f:added, v:notCreated, v:notAttached} -> schedule callback for when created
if (fragment.isAdded() && view == null) {
scheduleViewAttach(fragment, container);
return;
}
// { f:added, v:created, v:attached } -> check if attached to the right container
if (fragment.isAdded() && view.getParent() != null) {
if (view.getParent() != container) {
addViewToContainer(view, container);
}
return;
}
// { f:added, v:created, v:notAttached} -> attach view to container
if (fragment.isAdded()) {
addViewToContainer(view, container);
return;
}
// { f:notAdded, v:notCreated, v:notAttached } -> add, create, attach
if (!shouldDelayFragmentTransactions()) {
scheduleViewAttach(fragment, container);
mFragmentManager.beginTransaction()
.add(fragment, "f" + holder.getItemId())
.setMaxLifecycle(fragment, STARTED)
.commitNow();
mFragmentMaxLifecycleEnforcer.updateFragmentMaxLifecycle(false);
} else {
if (mFragmentManager.isDestroyed()) {
return; // nothing we can do
}
mLifecycle.addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
if (shouldDelayFragmentTransactions()) {
return;
}
source.getLifecycle().removeObserver(this);
if (ViewCompat.isAttachedToWindow(holder.getContainer())) {
placeFragmentInViewHolder(holder);
}
}
});
}
}
}
public final class FragmentViewHolder extends ViewHolder {
private FragmentViewHolder(@NonNull FrameLayout container) {
super(container);
}
// FragmentViewHolder实际上就创建了一个FrameLayout
@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;
}
}
通过上面源码分析可以知道,虽然Fragment
没有被复用,但是通过复用了ViewHolder
的container
实现了Framgent
的交替显示
4 总结
总结一下本章节的内容:
Fragment+ViewPager
只能横向滚动,性能相对较差。对应不同的Adapter
效果不一样。Fragment+ViewPager
使用FragmentPagerAdapter
,范围外fragments
会保存在内存中(detach
),但是fragment
对应的View
会被销毁,fragments
对应的SavedState
会保存,FragmentPagerAdapter
内存较大但页面切换更友好,适用于Fragment
数量少的情况Fragment+ViewPager
使用FragmentStatePagerAdapter
,范围外fragments
不会保存在内存中(remove
),View
也会被销毁。只保存范围内fragments
对应的SavedState
。这个SavedState
在Fragment
的生命周期回调中供外部传参数,和Activity
类似。内存占用少,页面切换稍差。适用于Fragment
数量多的情况。Fragment+ViewPager2
能横向或纵向滚动,继承自RecyclerView
所以基本有其所有的优点,包括内存占用、缓存管理等。FragmentStateAdapter
继承自RecyclerView.Adapter
,虽然Fragment
没有被复用,但是通过复用了ViewHolder
的container
实现了Framgent
的交替显示。