preface
ViewPager2 recently released 1.0.0-Alpha04, adding offscreenPageLimit, which is not ViewPager friendly, now officially continues this function, this time is the mules or the equine? Quickly pull out of the walk;
Reading Guide:
- The content is based on ViewPager2
1.0.0 - alpha04
Version explanation, because the official version has not been released, if there are functional changes, please note - ViewPager2: ViewPager2
offscreenPageLimit
Features andpreload
Mechanism, including Adapter state and Fragment lifecycle and other content
ViewPager ills
What the hell is a disease? It’s not that serious. ViewPager has two faults: cannot close preload and update Adapter does not take effect, so I say offscreenPageLimit is not friendly on ViewPager; Essentially, this is because offscreenPageLimit cannot be set to 0(which is supposed to turn off preloading);
When you switch to the current page, the layout of the left and right sides of the ViewPager will be preloaded by default, even though the views on both sides are not visible. Since ViewPager sets limits on offscreenPageLimit, page preloading is inevitable;
ViewPager
private static final int DEFAULT_OFFSCREEN_PAGES = 1;
public void setOffscreenPageLimit(int limit) {
if (limit< DEFAULT_OFFSCREEN_PAGES) {// do not allow less than 1 log.w (TAG,"Requested offscreen page limit " + limit + " too small; defaulting to "
+ DEFAULT_OFFSCREEN_PAGES);
limit = DEFAULT_OFFSCREEN_PAGES;
}
if (limit! = mOffscreenPageLimit) { mOffscreenPageLimit =limit; populate(); }}Copy the code
The ViewPager force preloading logic still exists when Fragment is used with ViewPager
Fragment Indicates the cause and effect of lazy loading
Say first PagerAdapter:
PagerAdapter common methods are as follows:
instantiateItem(ViewGroup container, int position)
Initialize the ItemView and return the ItemView to be addeddestroyItem(iewGroup container, int position, Object object)
Destroy ItemView to remove the specified ItemViewisViewFromObject(View view, Object object)
Whether the View and Object correspondsetPrimaryItem(ViewGroup container, int position, Object object)
The main Item of the current pagegetCount()
Get number of items
SetPrimaryItem (ViewGroup Container, int Position, Object Object). This method indicates that the current page is displaying the primary Item. What is the primary Item? If the preloaded ItemView is already onscreen, the current PrimaryItem remains unchanged until the new ItemView is fully onscreen and the swiping has stopped.
Because the ViewPager layout inevitably preload, cause PagerAdapter must call instantiateItem(ViewGroup Container, int position) method in advance, InstantiateItem () is the only entry method to create an ItemView, So the PagerAdapter implementation class FragmentPagerAdapter and FragmentStatePagerAdapter must grasp the method to create fragments object;
As it happens, a FragmentPagerAdapter and FragmentStatePagerAdapter sink in instantiateItem () to create and add or attach operation, There is no operation on the Fragment in the setPrimaryItem() method;
Preload, therefore, leads to invisible fragments of calls onCreate, onCreateView, onResume method, the user only through fragments. SetUserVisibleHint () method for identification.
Most lazy loading is to fiddle with fragments, using a combination of the lifecycle method and setUserVisibleHint status to control the lazy loading of data, while the layout can only be accessed earlier;
ViewPager2 basic use
- Build. Gradle introduced
implementation 'androidx. Viewpager2: viewpager2:1.0.0 - alpha04'
Copy the code
- Adding layout files
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
Copy the code
- Set the ViewHolder + Adapter
ViewPager2 viewPager = findViewById(R.id.view_pager2);
viewPager.setAdapter(new RecyclerView.Adapter<ViewHolder>() {
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_card_layout, parent, false);
ViewHolder viewHolder = new ViewHolder(itemView);
return viewHolder;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.labelCenter.setText(String.valueOf(position));
}
@Override
public int getItemCount() {
returnSIZE; }})); static class ViewHolder extends RecyclerView.ViewHolder{ private final TextView labelCenter; public ViewHolder(@NonNull View itemView) { super(itemView); labelCenter = itemView.findViewById(R.id.label_center); }}Copy the code
- Set the fragments + Adapter
viewPager.setAdapter(new FragmentStateAdapter(this) {
@NonNull
@Override
public Fragment getItem(int position) {
return new VSFragment();
}
@Override
public int getItemCount() {
returnSIZE; }});Copy the code
ViewPager2 use is very simple, even more simple than ViewPager, as long as familiar with RecyclerView children will certainly write ViewPager2;
ViewPager2: ViewPager2
setAdapter()
Setting the adaptersetOrientation()
Setting the layout directionsetCurrentItem()
Sets the current Item subscriptbeginFakeDrag()
Let’s simulate dragfakeDragBy()
Simulate dragendFakeDrag()
Simulation drag endssetUserInputEnabled()
Sets whether to allow user input/touchsetOffscreenPageLimit()
Set the number of off-screen loading pagesregisterOnPageChangeCallback()
Registration page changes callbacksetPageTransformer()
Set the transformation effect when the page slides
Many interesting effects, please run the official DEMO(github.com/googlesampl…) ;
Important announcement
I was wondering if offscreenPageLimit can be called preloading. If it can be on ViewPager, then it might be confusing on ViewPager2. Because ViewPager2 has a full set of RecyclerView caching strategies, including RecyclerView preloading; To avoid confusion, in the following article I define offscreenPageLimit as off-screen loading, preloading only represents the RecyclerView preloading;
ViewPager2 loads off-screen
In 1.0.0-Alpha04, ViewPager2 provides off-screen loading, which seems to have the same meaning as ViewPager’s preloading memory;
ViewPager2
public static final int OFFSCREEN_PAGE_LIMIT_DEFAULT = 0;
public void setOffscreenPageLimit(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;
// Trigger layout so prefetch happens through getExtraLayoutSize()
mRecyclerView.requestLayout();
}
Copy the code
From the code can be seen, ViewPager2 off-screen loading can be at least 0, just from this step, I’m bold to guess ViewPager2 support so-called lazy loading, with curiosity, take a look at OffscreenPageLimit implementation principle;
ViewPager2.LinearLayoutManagerImpl
@Override
protected void calculateExtraLayoutSpace(@NonNull RecyclerView.State state,
@NonNull int[] extraLayoutSpace) {
int pageLimit = getOffscreenPageLimit();
if(pageLimit == OFFSCREEN_PAGE_LIMIT_DEFAULT) {// If equal to the default value (0), call the base class method // Onlydo custom prefetching of offscreen pages if requested
super.calculateExtraLayoutSpace(state, extraLayoutSpace);
return; // Return offscreenSpace final int offscreenSpace = getPageSize() * pageLimit; extraLayoutSpace[0] = offscreenSpace; extraLayoutSpace[1] = offscreenSpace; }Copy the code
OffscreenPageLimit is essentially rewrite LinearLayoutManager calculateExtraLayoutSpace method, this method is the latest recyclerView package to join function;
CalculateExtraLayoutSpace method defines the extra space layout, what is the layout of the extra space? The default space is equal to the width and height of the RecyclerView, and the RecyclerView is defined as a RecyclerView to enlarge the space that can be allocated to the layout. The extraLayoutSpace parameter is an int of length 2. The first data takes the extra space on the left/top and the second data takes the extra space on the right/bottom. So the appellate code is indicated to expand the left/right/top offscreenSpace;
So in this code, OffscreenPageLimit is actually enlargethe layout space of the LinearLayoutManager, so let’s see how it works;
Layout comparison
To compare the effect of loading the layout, I prepared the LinearLayout to display both ViewPager and ViewPager2, set the same Item layout and data source, and then used the Android layout analysis tool to grab the layout structure of both. The code is simpler, so I won’t post it.
The defaultoffscreenPageLimit
ViewPager defaults to 1 and cannot be less than 1, ViewPager2 defaults to 0, ViewPager2 defaults to 0, ViewPager defaults to 1, ViewPager2 defaults to 0, ViewPager2 defaults to 0
Set up theoffscreenPageLimit=2
When setting the same offscreenPageLimit, both will pre-lay out the left and right (top and bottom) offscreenPageLimit ItemView for both;
ViewPager2 offscreenPageLimit = 0; ViewPager2 offscreenPageLimit = 0;
ViewPager2 preload and cache
ViewPager2 preload that is, RecyclerView preload, code in the RecyclerView GapWorker, this knowledge may be some students are not very understand, recommended to see this blog medium.com/google-deve… ;
Preloading is enabled on ViewPager2 by default. It is possible to preload a piece of data when dragging or Fling the control. Here’s a schematic of the preloading:
How do I turn off preloading?
((RecyclerView)viewPager.getChildAt(0)).getLayoutManager().setItemPrefetchEnabled(false);
Copy the code
The preloading switch is on LayoutManager. You just need to get LayoutManager and call setItemPrefetchEnabled() to control the switch.
ViewPager2 cache 2 ItemViews by default, and in the latest RecyclerView can customize the number of items cached;
RecyclerView
public void setItemViewCacheSize(int size) {
mRecycler.setViewCacheSize(size);
}
Copy the code
Summary: There is no essential difference between preloading and caching at the View level. Preloading and off-screen loading are fundamentally different at the View level. The off-screen loading View has been added to the parent.
Impact on Adapter when the Adapter is loaded in advance
Pre-loading refers to the loading of the layout while the current position is not visible. This includes both pre-loading and off-screen loading.
ViewPager2 Adapter is essentially recyclerView. Adapter, the following list of common methods:
onCreateViewHolder(ViewGroup parent, int viewType)
Create the ViewHolderonBindViewHolder(VH holder, int position)
Binding ViewHolderonViewRecycled(VH holder)
When the View is recycledonViewAttachedToWindow(VH holder)
The current View is loaded into the windowonViewDetachedFromWindow(VH holder)
The current View is removed from the windowgetItemCount()
// Get the number of items
The following is mainly for the creation of ItemView, not to discuss the recycling situation;
onBindViewHolder
Both preload and off-screen load are calledonViewAttachedToWindow
Off screen loading ItemView is called, visible ItemView is calledonViewDetachedFromWindow
ItemView from visible to invisible (except off-screen) must be called
Summary: Preloading and caching are no different at the Adapter level, and both call onBindViewHolder; Pre-loading and off-screen loading are fundamentally different at the Adapter level. An off-screen loaded View calls onViewAttachedToWindow.
ViewPager2 supports fragments
Currently, ViewPager2 support for fragments is limited to FragmentStateAdapter, which is very simple to use:
By default, ViewPager2 turns on preloading and turns off off-screen loading. In this case, how about switching pages to face Fragment life weeks?
1. Impact of disabling preloading on fragments: After verification, whether preloading is enabled has no impact on the life cycle of fragments. The result is the same as the figure above.
If offscreenPageLimit is set to 1, the Fragment is displayed.
Interpretation of print results:
Note: The subscript of log starts from 2 and the page number starts from 1. Please correct it by yourself.
- By default,
ViewPager2
It caches two pieces of data, so it makes sense to slide to page 4 and remove the Fragment on page 1. - Set offscreenPageLimit=1,
ViewPager2
On the first page, we load two pieces of data, which makes sense, because we load the View on the next page ahead of time; Every time you slide through the next page, the next page array will be loaded, until page 5, and the first page will be removedFragment
; Page 6 will remove page 2Fragment
If offscreenPageLimit=1, ViewPager2 can hold up to 3 ItemViews, plus 2 cached ItemViews, so that’s 5. Since offscreenPageLimit will place one on either side of ViewPager2, it can hold up to 4 forwards and 1 backwards (preloading has no effect on fragments, so it doesn’t count). It’s natural to recycle the first one after the fifth.
FragmentStateAdapter source code simple interpretation
OnCreateViewHolder () method
public final FragmentViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return FragmentViewHolder.create(parent);
}
static FragmentViewHolder create(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);
}
Copy the code
OnCreateViewHolder () creates a FrameLayout with width and height MATCH_PARENT. Note that this is not a Fragment rootView like PagerAdapter;
onBindViewHolder()
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 Fragment(position); // This might overwrite an existing Fragment(position); /** Specialcasewhen {@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 the ItemView is already added to the Window and the parent is not null, the binding viewHoder operation is triggered;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); / / to bind fragments and ViewHolder placeFragmentInViewHolder (holder); }}}); } // Collect Fragments gcFragments(); }Copy the code
onBindViewHolder()
The first thing it does is it gets the correspondingFragment
, which means preloadedFragment
Objects are created in advance;- If the current Holder-.ItemView has been added to the screen and is laid out and the parent is not empty, bind the Fragment to the ViewHodler;
- Each call will be gc once, the main reason to avoid the user to modify the data source caused by garbage objects;
onViewAttachedToWindow()
public final void onViewAttachedToWindow(@NonNull final FragmentViewHolder holder) {
placeFragmentInViewHolder(holder);
gcFragments();
}
Copy the code
The onViewAttachedToWindow() method calls onViewAttachedToWindow to bind the Fragment to hodler;
onViewRecycled()
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);
}
}
Copy the code
Fragment removal will only happen when onViewRecycled();
Core add operations:
// Add Fragment. RootView to FrameLayout; scheduleViewAttach(fragment, container); / / will rootI mFragmentManager. BeginTransaction (). The add (fragments,"f"+ holder.getItemId()).commitNow(); // Listen to onFragmentViewCreated method, Private void scheduleViewAttach(final Fragment Fragment) final FrameLayout container) { // After a config change, Fragments that werein FragmentManager will be recreated. Since
// ViewHolder container ids are dynamically generated, we opted to manually handle
// attaching Fragment views to containers. For consistency, we use the same mechanism for
// all Fragment views.
mFragmentManager.registerFragmentLifecycleCallbacks(
new FragmentManager.FragmentLifecycleCallbacks() {
@Override
public void onFragmentViewCreated(@NonNull FragmentManager fm,
@NonNull Fragment f, @NonNull View v,
@Nullable Bundle savedInstanceState) {
if(f == fragment) { fm.unregisterFragmentLifecycleCallbacks(this); addViewToContainer(v, container); }}},false);
}
Copy the code
More detailed FragmentStateAdapter source code interpretation please look forward to;
but!!!
Failed to listen to setUserVisibleHint in Fragment
In setting up offscreenPageLimit > 0, is to monitor of fragments are less than setUserVisibleHint call, I checked the source code didn’t call, and this method is marked out of date, so, The ViewPager set of lazy loading fragments will not work here;
(offscreenPageLimit = 0) (offscreenPageLimit = 0) (offscreenPageLimit = 0)
Adapter summary:
- At present
ViewPager2
rightFragment
Support is available onlyFragmentStateAdapter
.FragmentStateAdapter
In case ofpreload
, will only be createdFragment
Object, will not putFragment
It’s actually added to the layout, so it comes with lazy loading effects; FragmentStateAdapter
It won’t last foreverFragment
Instance, recycledItemView
Will removeFragment
Therefore, prepare for data recovery after Fragment reconstruction.FragmentStateAdapter
Handles off-screen when offscreenPageLimit>0 is encounteredFragment
And visibleFragment
It doesn’t make any difference, so it doesn’t passsetUserVisibleHint
To judge whether it is displayed or not, this point should be noted;
ViewPager lazy loading please note
In the new version of Fragment (Version 1.1.0 - alpha07
), the methodsetUserVisibleHint
Has been dated by FragmentTransactionsetMaxLifecycle
Substitute, the new versionFragmentPagerAdapter
You can set the call life cycle directly, which represents ViewPager+Fragment lazy loading has a better solution, please note
The last
ViewPager2 has more advantages
Because this chapter is a bit long, there is no comprehensive introduction of ViewPager2, does not mean that ViewPager is only this, as far as the current version, ViewPager2’s advantages or unique functions are as follows:
- Supports RecyclerView level reuse
- Support preloading and off-screen loading (described in this chapter)
- Support dynamic update Adapter(ViewPager pit one)
- Support simulated drag and drop
- Supports vertical sliding
- Support for page sliding state listening and page transformations (continuing the function of ViewPager)
- That’s all I can think of
conclusion
ViewPager2 is more powerful than ViewPager, whether it is efficient reuse, lazy loading, or update the effective Adapter. If you want to try to upgrade, I’m very happy, but from the current version, Use the combination of Fragment+offscreenPageLimit>0 with caution.