preface

Creative process: May 22, 2020, starting around 4pm, writing the epilogue at 9:55pm. Part FIVE and Part six will be added from 11 PM to 12 PM.

Haven’t written an article in a while, this time it’s not because of laziness… I’m really busy…

Today’s article is about ViewPager, many students may laugh: how to write such “low-level” content! Why is that? Because the vast majority of students are wrong, of course, the main reason is that the search engine launched to the article is mostly wrong!

Further: An article analyzing the ViewPager memory issues has also been published:

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

FragmentStatePagerAdapter optimization in the ViewPager

Wrong use of ViewPager: What does ViewPager2 do?

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

One, wrong usage

I wonder how many of you use ViewPager this way?

class TestViewPagerActivity : BaseActivity() {
    private lateinit var adapter: ViewPagerAdapter
    private val fragments = mutableListOf<Fragment>().apply {
        add(TestFragment1.newInstance("Page 1"))
        add(TestFragment2.newInstance("Page - 2"))
        add(TestFragment3.newInstance("Page - 3"))}override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState) setContentView(R.layout.activity_test_view_pager) adapter = ViewPagerAdapter(fragmentData,  supportFragmentManager) vp.adapter = adapter } innerclass ViewPagerAdapter(val fragments: List<Fragment>, fm: FragmentManager) : FragmentPagerAdapter(fm) {
        override fun getItem(position: Int): Fragment {
            return fragments[position]
        }

        override fun getCount(a): Int {
            return fragments.size
        }
    }
}
Copy the code

If those of you who see this are okay with this usage. Then you definitely have to read this article, because the above usage completely misrepresents the use of fragments in ViewPager.

Second, correct usage

I guess some of you might be wondering, what’s the correct way to use it?

Of course, some students refuted: Why do you say your writing method is right? Do you even have to ask? Because I’m big!! . Google docs now: ViewPager

class TestViewPagerActivity : BaseActivity() {
    private lateinit var adapter: ViewPagerAdapter

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState) setContentView(R.layout.activity_test_view_pager) adapter = ViewPagerAdapter(fragmentData,  supportFragmentManager) vp.adapter = adapter } innerclass ViewPagerAdapter(fm: FragmentManager) : FragmentPagerAdapter(fm) {
        override fun getItem(position: Int): Fragment {
            return when (position) {
                0 -> TestFragment1.newInstance("Page 1")
                1 -> TestFragment2.newInstance("Page - 2")
                else -> TestFragment3.newInstance("Page - 3")}}override fun getCount(a): Int {
            return 3}}}Copy the code

Do you see the difference? Yes, the difference is only in the implementation of the getItem() method. If you know how to call getItem(), you’ll know how to use fragments correctly in a ViewPager. So next we directly on the source code intuitive experience of ViewPager design.

Three, FragmentPagerAdapter source code

ViewPager support for fragments is very simple, the overall process:

    1. SetAdapter initializes the current Fragment based on the current position
    1. The Fragment that needs to be “preloaded” is then initialized based on the value of mOffscreenPageLimit
    1. After initializing the initialized Fragment, call commit() to notify the FragmentManager to attach the Fragment

After these 3 steps, our current Fragment will be out.

Next, let’s use the source code to understand the above steps 1, 2, 3.

When we setAdapter, we go to the popuate method:

void populate(int newCurrentItem) { / / in the ViewPager
    / /...
    // Initialize the Fragment based on the current position to determine whether the Fragment exists or not
    if (curItem == null && N > 0) {
        // This will go to instantiateItem
        curItem = addNewItem(mCurItem, curIndex);
    }
    // After initializing the current, the preloaded.... is initialized based on the limit
    // This method calls the COMMIT of FM in the FragmentPagerAdapter
    mAdapter.finishUpdate(this);
}

/** * this will call instantiateItem(), where the actual implementation is in FragmentPagerAdapter */
ItemInfo addNewItem(int position, int index) {
    ItemInfo ii = new ItemInfo();
    ii.position = position;
    ii.object = mAdapter.instantiateItem(this, position);
    ii.widthFactor = mAdapter.getPageWidth(position);
    if (index < 0 || index >= mItems.size()) {
        mItems.add(ii);
    } else {
        mItems.add(index, ii);
    }
    return ii;
}
Copy the code

At this point, we see how the FragmentPagerAdapter controls Fragment initialization:

public Object instantiateItem(@NonNull ViewGroup container, int position) {
    if (mCurTransaction == null) {
        mCurTransaction = mFragmentManager.beginTransaction();
    }
    // Find the itemId based on position. The default implementation of this method is position
    final long itemId = getItemId(position);

    // Generate a tag
    String name = makeFragmentName(container.getId(), itemId);
    // Use the tag generated above to try to find an instance of the Fragment in the fragmentManager
    Fragment fragment = mFragmentManager.findFragmentByTag(name);
    If attach is found, call attach directly
    if(fragment ! =null) {
        if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
        mCurTransaction.attach(fragment);
    } else {
    	// Otherwise call getItem() and get an instance of the Fragment based on our own implementation.
        fragment = getItem(position);
        if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
        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;
}
Copy the code

The comments in this code specify how the FragmentPagerAdapter will initialize the Fragment based on the current position. Just a quick recap:

  • FindFragmentByTag () is used to find out if a Fragment has been generated, based on the tag generated by a set of rules.
  • If not, call getItem() and get our own rewritten Fragment instance.

Because of the above process, we can clearly start the first usage must be wrong! GetItem () will only be used to initialize the Fragment if no Fragment instance is found in FragmentManage. So this is a common lazy loading mechanism.

The first step is to initialize the Fragment by adding new to all fragments. Because if we have three fragments on the ViewPager, and the user doesn’t slide to the third Fragment, then the new Fragment is going to be wasted.

Let’s talk about the mOffscreenPageLimit in Step 2. Experienced professionals know that this is used for preloading and that the minimum value is 1. Populate () method based on mOffscreenPageLimit to determine how many fragments on the left and right sides of the preload position, 1 means that the left and right sides of the preload 1.

Since the minimum of mOffscreenPageLimit is 1, we need to load at least 2 fragments at a time. Sometimes we need to do some data loading when we slide to a Fragment.

Faced with such a scenario, we typically try to do a logical callback for visibility using methods such as onHiddenChanged()/setUserVisibleHint(). In fact, if you have a newer version of the fragment library in your project, you will find that the system provides a more convenient and elegant way.

Load data when sliding to the current Fragment more elegantly

Under the new version of fragments, FragmentStatePagerAdapter in use, we will find the default constructor is out of date:

@Deprecated
public FragmentStatePagerAdapter(@NonNull FragmentManager fm) {
    this(fm, BEHAVIOR_SET_USER_VISIBLE_HINT);
}
Copy the code

The default BEHAVIOR_SET_USER_VISIBLE_HINT is added to the constructor, and the BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT is also provided. And this behavior, just like the name, is that you only call the onResume() method on the Fragment when you’re sliding over the Fragment.

But notice the callback onResume(). The method before onResume is already done when you instantiate the Fragment in getItem().

Therefore, we only want to initialize the Fragment when the current Fragment is visible, so we can use BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT.

GetItemPosition () abuse

The getItemPosition() method has been overwritten in a company project:

public int getItemPosition(@NonNull Object object) {
    return POSITION_NONE;
}
Copy the code

Does that make any sense? Say have also have, say not also have no! Why such an ambiguous answer? So this is a very special method.

The comment on this method says: When return POSITION_UNCHANGED, it means the current view did not change, and return POSITION_NONE means it changed. The comments can be a bit abstract, but let’s use the source code to understand the method.

This method will only be called in ViewPager’s dataSetChanged(), so we can be sure that overriding this method will only take effect if we actively attempt to update the ViewPager.

void dataSetChanged(a) {
    // For loops through all fragments and determines whether to remove them based on the value returned by getItemPosition()
    for (int i = 0; i < mItems.size(); i++) {
        final ItemInfo ii = mItems.get(i);
        final int newPos = mAdapter.getItemPosition(ii.object);

        if (newPos == PagerAdapter.POSITION_UNCHANGED) {
            continue;
        }

        if (newPos == PagerAdapter.POSITION_NONE) {
        	// As you can see, if it is POSITION_NONE, remove the Fragment under the current I
        	// Omit part of the code
            mItems.remove(i);          
            mAdapter.destroyItem(this, ii.position, ii.object);
        }
        // Omit part of the code
    }
    // Omit part of the code
    Populate () calls populate() again to redo the initialization
    setCurrentItemInternal(newCurrItem, false.true);
}

Copy the code

With the above source logic, we can actually see what getItemPosition() means: When we want to refresh the ViewPager with notifyDataSetChanged(), the return of getItemPosition() determines whether the current Fragment needs to be removed. So when we don’t need to remove the current Fragment, return POSITION_UNCHANGED (so the Fragment doesn’t change any state), If not, return POSITION_NONE (the Fragment is removed and the new Fragment is initialized). We can make diff operations similar to RecyclerView.

GetItemPosition () should be properly rewritten based on its own product logic to avoid unnecessary Fragment destruction and reconstruction.

How to proactively get the Fragment instance of ViewPager

As we all know, the FragmentManager provides us with findFragmentById()/findFragmentByTag(). The same is true for ViewPager. In part 3 of the source code analysis, we know that the FragmentPagerAdapter also attempts to retrieve the current Fragment instance by findFragmentByTag(). And the implementation of the tag comes from makeFragmentName(Container.getid (), itemId)

private static String makeFragmentName(int viewId, long id) {
    return "android:switcher:" + viewId + ":" + id;
}
Copy the code

So, we can also get fragments in ViewPager in this way. Do not like the search engine launched those answers: ** active call what getItem()! ** with the above source code analysis, I guess you have to get how wrong these usage is!!

The end of the

OK, this time want to chat is so much ~ later article, I will strive to be in absolutely correct circumstance again send out, as far as possible do not mislead people’s children!

After all, as far as today’s ViewPager is concerned, IN fact, I also used that wrong way to write in the beginning, yes, is the search engine launched by the wrong article misled!

Since he stepped on the pit, try to fill a is a!

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 ~