preface

Still decided to write the ViewPager2, after all, for the ViewPager has written 3, also not bad this last shiver. If you haven’t read the previous 3 articles, you can read them here:

You’re probably using your ViewPager wrong.

Wrong ViewPager usage (continued), causing a memory leak? Out of memory?

FragmentStatePagerAdapter optimization in the ViewPager

Wrong use of ViewPager: What does ViewPager2 do?

To end today’s article, it has become a small series by accident.

This series of articles examines the root causes and solutions for problems such as memory overruns, Bundle exceptions, and so on. ViewPager scene more students, suggest to have a look at the whole series, and welcome to discuss together.

The body of the

First of all, ViewPager2 is stable, and you can enjoy using it:

dependencies {
    implementation "Androidx. Viewpager2: viewpager2:1.0.0."
}
Copy the code

Understanding basic API usage is definitely the official API, basic usage, and migrating ViewPager to ViewPager2.

First, basic usage

There is only one Adapter that is slightly different from the ViewPager, except for the layout:

private inner class ScreenSlidePagerAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) {
    override fun getItemCount(a): Int = NUM_PAGES
    // new own Fragment
    override fun createFragment(position: Int): Fragment = ScreenSlidePageFragment() 
}
Copy the code

Easy to migrate, getItemCount() is getCount() in ViewPager, createFragment() is getItem() in ViewPager.

Based on these two methods, the official gave an additional explanation:

As you can see, createFragment() needs to provide new instances, not reuse instances. This is an official use of your ViewPager. Indirect evidence of.

1.1. Different constructors

You can see that the Adapter method in ViewPager2 is much more reasonably named, createFragment(). Obviously we should return the Fragment we need to create in this method.

If you’ve noticed, ViewPager2’s constructor accepts a FragmentActivity or Fragment, not a FragmentManager. This is an official way of telling you which FragmentManager to use and when:

public FragmentStateAdapter(@NonNull FragmentActivity fragmentActivity) {
    this(fragmentActivity.getSupportFragmentManager(), fragmentActivity.getLifecycle());
}

public FragmentStateAdapter(@NonNull Fragment fragment) {
    this(fragment.getChildFragmentManager(), fragment.getLifecycle());
}
Copy the code

Of course, This does not mean that you should always use FragmentStateAdapter(@nonnull Fragment Fragment) in the Fragment, but you should always use FragmentStateAdapter(@nonnull Fragment Fragment) in the Activity FragmentActivity FragmentActivity).

Here’s a quote on the website:

In most cases, it’s better to use it this way.

So it’s not always clear how to use it. If you understand the design of the ViewPager and the design of the FragmentManager, you can actually choose which constructor to use according to your needs.

For more code, see the Google demo

1.2, can DiffUtil

As you all know, ViewPager2 is based on RecycleView, so it’s bound to use DiffUtils. In reality, however, there are two additional methods to override to use DiffUtil:

GetItemId () is the interface API for dynamically updating fragments in ViewPager era.

This is not to say that getItemId() only works in DiffUtil. GetItemId () is similar to the effect in ViewPager. As Long as the return of getItemId() for the same position is different, the re-createFragment () is triggered. It is possible to update the Fragment, but it has an experience flaw: it will “flash”.

A quick post on Google’s usage:

private val items = (1.9.).map { longToItem(nextValue++) }.toMutableList()

object : FragmentStateAdapter(this) {
    override fun createFragment(position: Int): PageFragment {
        val itemId = items.itemId(position)
        val itemText = items.getItemById(itemId)
        return PageFragment.create(itemText)
    }
    override fun getItemCount(a): Int = items.size
    // These are the two main methods
    override fun getItemId(position: Int): Long = items.itemId(position)
    override fun containsItem(itemId: Long): Boolean = items.contains(itemId)
}
Copy the code

Of course, the demo also mentions the use of DiffUtil:

/** using [DiffUtil] */
val idsOld = items.createIdSnapshot()
performChanges()
val idsNew = items.createIdSnapshot()
DiffUtil.calculateDiff(object : DiffUtil.Callback() {
    override fun getOldListSize(a): Int = idsOld.size
    override fun getNewListSize(a): Int = idsNew.size

    override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
        idsOld[oldItemPosition] == idsNew[newItemPosition]

    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) =
        areItemsTheSame(oldItemPosition, newItemPosition)
}, true).dispatchUpdatesTo(viewPager.adapter!!)
Copy the code

You might look at this and ask: What’s the difference between notifyDataSetChanged() and DiffUtils?

I can’t answer this question because the answer is the difference between notifyDataSetChanged() and DiffUtil… DiffUtil was developed to solve the problem of data diff, after all notifyDataSetChanged() updates everything in one go.

Of course, because of the particularity of ViewPager2, whether really go new Fragment, but also based on the implementation of getItemId(). But notifyDataSetChanged() will actually call onCreateViewHolder(); DiffUtil is controlled by our own implementation.

That’s the difference.

1.3. Fit TabLayout

This part is a bit of a “pain in the stomach” because of the uniqueness of ViewPager2, adapting TabLayout is a bit of a chore. If you can’t adapt it yourself, you can use Google’s adaptation solution:

TabLayoutMediator(tabLayout, viewPager) { tab, position ->
    tab.text = "The Title you need to display."
}.attach()
Copy the code

TabLayoutMediator this class, you need to com. Google. Android. Material: material: 1.1.0 and above.

Two, the principle analysis

First, let’s look at the FragmentStateAdapter:

public abstract class FragmentStateAdapter extends RecyclerView.Adapter<FragmentViewHolder> implements StatefulAdapter
Copy the code

Very direct inheritance of recyclerView. Adapter, ViewPager2 mechanism is not out of the RecycleView. So next, let’s take a look at onCreateViewHolder() and onBindViewHolder().

OnCreateViewHolder () generates a parent layout.

public final FragmentViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    return FragmentViewHolder.create(parent);
}

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(a) {
        return(FrameLayout) itemView; }}Copy the code

The main content is in onBindViewHolder() :

final LongSparseArray<Fragment> mFragments = new LongSparseArray<>();

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); 
    // Determine whether to removeFragment() when onBindViewHolder() is required
    if(boundItemId ! =null&& boundItemId ! = itemId) { removeFragment(boundItemId); mItemIdToViewHolder.remove(boundItemId); } mItemIdToViewHolder.put(itemId, viewHolderId);// Determine whether to call createFragment()
    ensureFragment(position);
    // Omit part of the code
    // Special case remove to reference
    gcFragments();
}

private void ensureFragment(int position) {
    // Check whether mFragemtns has a cache based on the return of getItemId()
    long itemId = getItemId(position);
    if (!mFragments.containsKey(itemId)) {
        Fragment newFragment = createFragment(position);
        newFragment.setInitialSavedState(mSavedStates.get(itemId));
        mFragments.put(itemId, newFragment);
    }
}
Copy the code

The process inside onBindViewHolder() is relatively straightforward, as is the ViewPager. The RecycleView uses the RecycleView to determine whether a new bind is needed or not, based on whether there is a cache.

What’s different here is the strategy for removing the cache, as seen above with removeFragments():

private void removeFragment(long itemId) {
    Fragment fragment = mFragments.get(itemId);
    // Omit blank
    / / remove the View
    if(fragment.getView() ! =null) {
        ViewParent viewParent = fragment.getView().getParent();
        if(viewParent ! =null) { ((FrameLayout) viewParent).removeAllViews(); }}/ / remove state
    if(! containsItem(itemId)) { mSavedStates.remove(itemId); }// Remove the Fragemnt if it is not added
    if(! fragment.isAdded()) { mFragments.remove(itemId);return;
    }
    If containsItem(itemId) is true, save the state and remove it
    if (fragment.isAdded() && containsItem(itemId)) {
        mSavedStates.put(itemId, mFragmentManager.saveFragmentInstanceState(fragment));
    }
    mFragmentManager.beginTransaction().remove(fragment).commitNow();
    mFragments.remove(itemId);
}
Copy the code

The remove method is called in three places, one atonBindViewHolder()In the

  • We’ve already seen the first one, only inonBindViewHolder()If the id of the current bind’s ViewHolder is not the itemId of the current ViewHolder. (that is, only when re-notify occurs)
  • The second is in gcFragment(), which only works in! MHasStaleFragments,shouldDelayFragmentTransactions()Is called when both are false. (I.e., savestate)
  • So normally, this will only be called back when onViewRecycled() is used.

Therefore, ViewPager2’s Fragment removal strategy is entirely based on the RecycleView (and of course the load strategy is also based on the RecycleView, after all, it starts together in the onBindViewHolder() method).

The end of the

ViewPager2 as a whole is nothing special, after all, there are countless ViewPager implementations based on RecycleView.

This is the end of my ViewPager series.

We’ll talk about Jetpack more or less based on official sources and our own projects.

I am a fresh graduate, recently and friends to maintain a public number, content is we in the transition from fresh graduate to development of this road to step on the pit, as well as our step by step learning records, if interested in friends can pay attention to, together with the ~