Today’s article is about ViewPager, and many students may be wondering: How can you write such “low-level” content? Why is that? Because the vast majority of the students are wrong, of course, the main reason is that the search engine launched most of the articles are wrong!

The body of the

Incorrect usage

I don’t know how many of you use ViewPager that 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
    }

    inner class ViewPagerAdapter(val fragments: List<Fragment>, fm: FragmentManager) : FragmentPagerAdapter(fm) {
        override fun getItem(position: Int): Fragment {
            return fragments[position]
        }

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

If those of you who are watching this are okay with this usage. Then there is no doubt that you must read this article, because the above usage completely misinterprets the use of fragments in ViewPager.

2. Correct usage

I guess some of you might have a question, but what is the correct way to use it?

Of course, some students refuted: with what do you say your writing is right? Do you even have to ask? Just because I’m big!! . Google docs: 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
    }

    inner class 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(): Int {
            return3}}}Copy the code

Do you see the difference between the two? Yes, the only difference is the implementation of the getItem() method. If you understand the getItem() call, you can understand the proper use of fragments in ViewPager. So let’s go straight to the source code and get a feel for the design of ViewPager.

FragmentPagerAdapter source code

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

    1. The 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 three steps, our current Fragment is already out.

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

When we setAdapter, we’ll go to the Popuate method:

Void populate(int newCurrentItem) {// populate //.... // Determine whether to initialize the current Fragment based on whether the Item (Fragment) exists in the current positionif(curItem == null && N > 0) {// instantiateItem curItem = addNewItem(mCurItem, curIndex); } // After initializing the current, will be based onlimitTo initialize the preloaded.... // This method in FragmentPagerAdapter calls FM commit mAdapter.finishUpdate(this); } /** * this will call instantiateItem(), */ 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

It’s only here that we see the FragmentPagerAdapter’s control over Fragment initialization:

public Object instantiateItem(@NonNull ViewGroup container, int position) {
    if(mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } position final Long itemId = getItemId(position); // Generate a tag String name = makeFragmentName(container.getid (), itemId); / / by the tag created above, trying to find a Fragment instance in fragmentManager fragments fragments = mFragmentManager. FindFragmentByTag (name); // If found, call attach directlyif(fragment ! = null) {if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
        mCurTransaction.attach(fragment);
    } else{// Otherwise call getItem() and get the Fragment instance 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 to the code specify how the FragmentPagerAdapter initializes the Fragment based on the current position. Let’s go over it briefly:

  • Tags generated based on a set of rules, findFragmentByTag() is used to find if any fragments have been generated.
  • If not, call getItem() and get our own Fragment instance of the rewritten return.

Because of the above process, we can make it clear that the first usage of the opening sentence must be wrong! Because we can get a message from the source: for Adapter, getItem() is called to initialize the Fragment only if no Fragment instance is found in FragmentManage. So this is actually a common lazy loading mechanism.

The Fragment Fragment is initialized with a new Fragment, which is meaningless. Because if we have three fragments in our ViewPager and the user doesn’t slide to the third Fragment, then the new Fragment is wasted.

Let’s talk more about the mOffscreenPageLimit in step 2. This is used for preloading, and the minimum value is 1. Populate () determines how many fragments populate the left and right sides of the preloaded position based on mOffscreenPageLimit. 1 means 1 Fragment is preloaded on either side.

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

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

4. Gracefully load data when sliding onto the current Fragment

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

In addition to the default BEHAVIOR_SET_USER_VISIBLE_HINT, the system also provides BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT. This behavior, as its name suggests, only calls the Fragment’s onResume() method when sliding on it.

But note the onResume() callback. The method before onResume is already called when getItem() instantiates the Fragment.

Therefore, the BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT can be used to initialize the Fragment only when the current Fragment is visible.

5. Misuse of getItemPosition(

The getItemPosition() method was rewritten by a friend in a company project:

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

Is there a problem with writing this? Say yes and yes, say no and no! Why such an ambiguous answer? So this method is very special.

The comment for this method says: When the return POSITION_UNCHANGED, that means the current view didn’t change, and return POSITION_NONE means the view changed. Comments can be a bit abstract, so let’s understand this approach in conjunction with the source code.

This method is only called in the ViewPager dataSetChanged(), so we can confirm that overriding this method will only take effect when actively trying to update the ViewPager.

void dataSetChanged() {
    // forLoop through all fragments and determine if remove is required based on the return value of 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;
        }

        ifMitems.remove (I); (newPos == pagerAdapter.position_none) {POSITION_NONE: mitems.remove (I); (newPos == pagerAdapter.position_none) { mAdapter.destroyItem(this, ii.position, ii.object); Populate (); populate(); populate(); populate(); populate(setCurrentItemInternal(newCurrItem, false.true);
}

Copy the code

GetItemPosition () : getItemPosition() When we want to use notifyDataSetChanged() to refresh the ViewPager, the return of getItemPosition() determines whether the current Fragment needs to be removed. So when we didn’t need to remove the current Fragment, we returned POSITION_UNCHANGED (so that the Fragment didn’t change any state), Otherwise, return POSITION_NONE (so that the Fragment is removed and then reinitialized with a new Fragment). We can do diff operations like RecyclerView.

Rewrite getItemPosition() reasonably based on its own product logic to avoid unnecessary Fragment destruction and reconstruction.

How to actively get the Fragment instance of a 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 learned that findFragmentByTag() attempts to retrieve an instance of the current Fragment from the FragmentPagerAdapter. 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 get fragments in ViewPager in this way too. Don’t be one of those search engine answers: call getItem()! With the above source code analysis, I guess you already get how wrong these usage is!!

The end of the

OK, this time want to talk is so much ~ later article, I will strive to be in the absolutely correct circumstances again issued, as far as possible do not mislead people’s children!

After all, as far as ViewPager is concerned today, I actually started out with the wrong way to write it. Yes, I was misled by the wrong articles from search engines!

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

Author: Salted fish is turning over link: juejin.cn/post/684490…