上一章节【Android Framework系列】第14章 Fragment核心原理(AndroidX版本)我们学习了Fragment的核心原理,本章节学习常用的Fragment+ViewPager
以及Fragment+ViewPager2
的相关使用和一些基本的源码分析。
我们常用的两个PagerAdapter
的实现类,也就是FragmentStatePagerAdapter
和FragmentPagerAdapter
,今天,我们就来学习一下它们的使用方法,并进行对比。
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
ViewPager2
是Android Jetpack
库中的一个组件,是用于在应用程序中实现页面切换和滑动效果的容器。其实ViewPager2
本身继承自RecyclerView
。可以复习下我们的【Android Framework系列】第12章 RecycleView相关原理及四级缓存策略分析
是一个功能强大的滑动容器,可以应用于多种场景中,提供了灵活的页面切换和布局定制功能,使得应用程序界面更加丰富和交互性强,可以用于以下场景:
实现引导页或欢迎页
ViewPager2
可以用于创建引导页或欢迎页,让用户通过滑动浏览介绍应用程序功能或展示欢迎内容。
创建图片浏览器
ViewPager2
可以用于创建图片浏览器,允许用户通过滑动来切换不同的图片,并支持缩放和手势交互。
构建轮播图
ViewPager2
非常适合构建轮播图功能,可以通过适配器动态加载不同的轮播项,并提供自动循环滚动的功能。
实现选项卡式布局
结合TabLayout
,ViewPager2
可以用于创建选项卡式布局,让用户通过滑动选项卡来切换不同的内容页面。
创建垂直滑动页面
与ViewPager
不同,ViewPager2
支持垂直方向的滑动,因此可以用于创建垂直滑动的页面布局,例如垂直滑动的导航菜单或垂直的新闻列表。
实现分页数据展示
ViewPager2
可以用于展示分页数据,例如将大量数据按页加载并在每一页中展示一部分内容。
嵌套滑动布局
ViewPager2
可以与其他滑动组件(如RecyclerView
)嵌套使用,实现复杂的滑动布局结构。
实现自定义的滑动效果
通过使用自定义的转换器(Transformer
),可以实现各种炫酷的页面切换效果,例如渐变、缩放、旋转等。
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
的行为和外观。
上面我们了解了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
的交替显示
总结一下本章节的内容:
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
的交替显示。