1 Introduction
In the previous chapter [Android Framework Series] Chapter 14 Core Principles of Fragment (AndroidX version), we learned the core principles of Fragment. In this chapter, we learn the commonly used Fragment+ViewPager
and Fragment+ViewPager2
related uses and some basic source code analysis.
2 Fragment+ViewPager
We commonly use two PagerAdapter
implementation classes, namely FragmentStatePagerAdapter
and FragmentPagerAdapter
. Today, we will learn how to use them and compare them.
2.1 The difference between FragmentPagerAdapter and FragmentStatePagerAdapter
1. fragments
Object processing:
FragmentPagerAdapter
: Out of scope fragments
will be saved in memory ( detach
), but fragment
the corresponding object View
will be destroyed
FragmentStatePagerAdapter
: Out of scope fragments
will not be saved in memory ( remove
), View
and will also be destroyed.
2. Status processing: : The corresponding ones
FragmentPagerAdapter
outside the range will be saved : Only the corresponding ones within the range will be saved . This is used for external parameters to be passed in the life cycle callback, and is similar. 3. Applicable scenarios: The same amount , the memory is larger, but the page switching is more friendly; the memory usage is small, the page switching is slightly worse.fragments
SavedState
FragmentStatePagerAdapter
fragments
SavedState
SavedState
Fragment
Activity
fragments
FragmentPagerAdapter
FragmentStatePagerAdapter
Therefore, FragmentPagerAdapter
it is suitable for Fragment
situations where the number is small, and FragmentStatePagerAdapter
it is suitable for situations where the number of Fragments is large.
Let’s take a look first 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
The destroyItem
method called detach()
only changes the Fragment state, which means that only by eliminating the entire adapter
time can all the generated ones Fragment
be eliminated, otherwise they will be directly in the memory.
Next we compare 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
It was removed when destroyItem
the method was called Fragment
, so it needs to be re-created next time.Fragment
3 Fragment+ViewPager2
ViewPager2
It is Android Jetpack
a component in the library and is a container used to implement page switching and sliding effects in the application. In fact, ViewPager2
itself is inherited fromRecyclerView
. You can review our [Android Framework Series] Chapter 12 RecycleView related principles and four-level cache strategy analysis
3.1 The role and purpose of ViewPager2
3.1.1 ViewPager2 usage scenarios
It is a powerful sliding container that can be used in a variety of scenarios. It provides flexible page switching and layout customization functions, making the application interface richer and more interactive. It can be used in the following scenarios:
-
Implementing onboarding or welcome pages
ViewPager2
can be used to create onboarding or welcome pages that let users swipe through to introduce app features or display welcome content. -
Create a picture browser
ViewPager2
can be used to create a picture browser, allowing users to switch between different pictures by sliding, and supports zoom and gesture interaction. -
Building carousels
ViewPager2
is very suitable for building carousel functions, which can dynamically load different carousel items through adapters and provide automatic cycle scrolling. -
Implementing tabbed layout
Combination can be used to create a tabbed layout, allowing users to switch between different content pages by sliding tabsTabLayout
.ViewPager2
-
Create a vertical sliding page
UnlikeViewPager
,ViewPager2
it supports vertical sliding, so it can be used to create vertical sliding page layouts, such as vertical sliding navigation menus or vertical news lists. -
Implementing paging data display
ViewPager2
can be used to display paging data, such as loading a large amount of data by page and displaying a portion of the content on each page. -
Nested sliding layout
ViewPager2
can be used nested with other sliding components (such asRecyclerView
) to implement complex sliding layout structures. -
Achieve customized sliding effects
By using a customized converter (Transformer
), you can achieve various cool page switching effects, such as gradient, zoom, rotation, etc.
3.1.2 Improvements and advantages of ViewPager2 compared to ViewPager
ViewPager2
is ViewPager
an improved version of the original, offering better performance, more flexible adapters, and richer functionality. It is the preferred component for building sliding page layouts. It can implement various sliding page requirements in applications and provide a better user experience. It has the following improvements and advantages:
-
Support for vertical sliding
ViewPager2
isViewPager
improved upon, and one of the most significant improvements is支持垂直滑动
. InViewPager
Android, only horizontal sliding is supported. This makesViewPager2
the vertical sliding function more convenient and flexible when creating vertical layouts or specific scenarios. -
Better performance and stability
ViewPager2
The internal implementation is usedRecyclerView
as a container and no longer relies onViewPager
the implementation. This gives the advantageViewPager2
of e.g. At the same time, some known problems and instabilities have also been solved , such as incorrect entry positions and untimely refresh of data.RecyclerView
更好的性能和内存管理
更流畅的滑动体验
及更好的布局回收和复用机制
ViewPager2
ViewPager
-
Supports the use of Fragment as a page.
Unlike FragmentViewPager
,ViewPager2
it directly supports the useFragment
of Fragment as a page without passingFragmentPagerAdapter
orFragmentStatePagerAdapter
adapting. This simplifies page management and lifecycle handling and provides a more intuitive and consistent usage experience. -
More flexible adapters
ViewPager2
introduce new adapter interfaces,RecyclerView.Adapter
subclasses ofRecyclerView.Adapter
. This makes adapter creation and management more flexible while providing more functionality and extensibility. -
Richer functions and interfaces
ViewPager2
provide many new functions and interfaces, such as support for page preloading, more powerful page switching animation support, richer callback interfaces, etc. These features and interfaces give developers greater control and customizationViewPager2
of behavior and appearance.
3.2 FragmentStateAdapter
Above we have learned about some differences and optimization points of ViewPager2
comparison ViewPager
, let's continue to look at ViewPager2
the corresponding ones Adapter
.
We know that ViewPager2
it inherits from RecyclerView
, then its corresponding one FragmentStateAdapter
must inherit from RecyclerView.Adapter
, which friends should be able to understand. Then RecyclerView
each Item
join we add in Fragment
is presented as a container.
RecyclerView.Adapter
The focus is ViewHolder
on the reuse, but the ones in FragmentStateAdapter
are Framgent
not reused, that is, as many as there item
are should be created Fragment
, so how to convert them?
Let’s take a look at the source code first 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;
}
}
From the above source code analysis, we can know that although Fragment
it is not reused, the alternate display is achieved ViewHolder
through reuse.container
Framgent
4 Summary
To summarize the contents of this chapter:
Fragment+ViewPager
It can only scroll horizontally and its performance is relatively poor. Corresponding to differentAdapter
effects are different.Fragment+ViewPager
When usedFragmentPagerAdapter
, the outside of the scopefragments
will be saved in the memory (detach
), butfragment
the corresponding onesView
will be destroyed, andfragments
the corresponding onesSavedState
will be saved.FragmentPagerAdapter
The memory is larger but the page switching is more friendly, suitable forFragment
small quantities.Fragment+ViewPager
When usedFragmentStatePagerAdapter
,fragments
it will not be saved in memory (remove
) outside the scope andView
will also be destroyed.fragments
Only the corresponding ones within the range are savedSavedState
. This is used for external parameters to be passed in the life cycle callback, andSavedState
is similar. The memory usage is small and the page switching is slightly poor. Suitable for large quantities.Fragment
Activity
Fragment
Fragment+ViewPager2
It can scroll horizontally or vertically, and it inheritsRecyclerView
basically all its advantages, including memory usage, cache management, etc.FragmentStateAdapter
Inherited fromRecyclerView.Adapter
, althoughFragment
not reused, the alternate display is implementedViewHolder
through reuse.container
Framgent