preface
ViewPager+Fragment+TabLayout (FragmentPagerAdapter);
inner class OperationManageFragmentPagerAdapter( fm: FragmentManager) :
FragmentPagerAdapter(fm.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT){
private val mTitles:ArrayList<String> = getTitles()
private fun getTitles(): ArrayList<String>{
}
override fun getItem(position: Int): Fragment {
return fragments[position]
}
override fun getCount(): Int {
return fragments.size
}
override fun getPageTitle(position: Int): CharSequence {
return mTitles[position]
}
}
Copy the code
Take a look directly at the source of the constructor:
/**
* Constructor for {@link FragmentPagerAdapter} that sets the fragment manager for the adapter.
* This is the equivalent of calling {@link #FragmentPagerAdapter(FragmentManager, int)} and
* passing in {@link #BEHAVIOR_SET_USER_VISIBLE_HINT}.
*
* <p>Fragments will have {@link Fragment#setUserVisibleHint(boolean)} called whenever the
* current Fragment changes.</p>
*
* @param fm fragment manager that will interact with this adapter
* @deprecated use {@link #FragmentPagerAdapter(FragmentManager, int)} with
* {@link #BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT}
*/
@Deprecated
public FragmentPagerAdapter(@NonNull FragmentManager fm) {
this(fm, BEHAVIOR_SET_USER_VISIBLE_HINT);
}
Copy the code
The suggestion here is to use the two-parameter constructor, which requires a BEHAVIOR to be passed, and the default single-parameter value BEHAVIOR_SET_USER_VISIBLE_HINT is the BEHAVIOR_SET_USER_VISIBLE_HINT. And then of course the BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT value is BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT, so what’s the use of those two values? Why should WE abandon one of them.
Source analysis
Look directly at the explanation of the 2 values:
/**
* Indicates that {@link Fragment#setUserVisibleHint(boolean)} will be called when the current
* fragment changes.
*
* @deprecated This behavior relies on the deprecated
* {@link Fragment#setUserVisibleHint(boolean)} API. Use
* {@link #BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT} to switch to its replacement,
* {@link FragmentTransaction#setMaxLifecycle}.
* @see #FragmentPagerAdapter(FragmentManager, int)
*/
@Deprecated
public static final int BEHAVIOR_SET_USER_VISIBLE_HINT = 0;
Copy the code
- The current API version is deprecated.
- When this policy is used, the fragment calls back to the setUserVisibleHint() method when switching.
/**
* Indicates that only the current fragment will be in the {@link Lifecycle.State#RESUMED}
* state. All other Fragments are capped at {@link Lifecycle.State#STARTED}.
*
* @see #FragmentPagerAdapter(FragmentManager, int)
*/
public static final int BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT = 1;
Copy the code
- Indicates that when switching fragments, the current fragment will call back the life cycle to RESUMED, and other fragments will call back the life cycle to STARTED.
- The setUserVisibleHint() method is not called back.
- You can manually set the highest level of the fragment lifecycle by calling setMaxLifecycle.
Next, take a look at the call procedure, which is called when the ViewPager switches
mAdapter.setPrimaryItem(this, mCurItem, curItem.object);
We can switch the fragment through the Adapter, and then implement this function in the FragmentPagerAdapter class:
public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
Fragment fragment = (Fragment)object;
if(fragment ! = mCurrentPrimaryItem) {if(mCurrentPrimaryItem ! =null) {
mCurrentPrimaryItem.setMenuVisibility(false);
if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
mCurTransaction.setMaxLifecycle(mCurrentPrimaryItem, Lifecycle.State.STARTED);
} else {
mCurrentPrimaryItem.setUserVisibleHint(false);
}
}
fragment.setMenuVisibility(true);
if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.RESUMED);
} else {
fragment.setUserVisibleHint(true); } mCurrentPrimaryItem = fragment; }}Copy the code
- For the old fragment, if the BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT mode is BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT, set the lifecycle of the fragment to STARTED. Otherwise, call setUserVisibleHint(false).
- For the fragment to be displayed, if the BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT mode is BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT, set the interval to RESUMED. Otherwise, run setUserVisibleHint(true).
Code validation
Add the following test code to the item ItemListFragment:
// Test the code
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
Log.i(TAG, "setUserVisibleHint: type = $mQueryType isVisibleToUser = $isVisibleToUser")
}
override fun onResume() {
super.onResume()
Log.i(TAG, "onResume: type = $mQueryType")
}
override fun onStart() {
super.onStart()
Log.i(TAG, "onStart: type = $mQueryType")}Copy the code
Using a single-argument constructor results in:
You will find that the life cycle calls back onResume and onRestart functions for both the displayed and undisplayed interfaces. Switch from pending to Processed to all:
Life cycle callbacks will no longer be called back. Call back the setUserVisibleHint function for the invisible fragment and then for the fragment that is about to be visible.
Use the recommended non-obsolete constructor:
The printed result is:
The setUserVisibleHint function calls back to the onResume method only if it is visible, otherwise only to the onStart method. Switch from pending to Processed to all:
role
No matter which method of fragment switching is implemented, there is only one purpose, that is to judge the user visibility, above which is the most common scenario:
- Lazy loading: When the fragment content is large, ViewPager will cache at least two fragment instances created. If the network loading is performed when the instance is created, a large number of concurrent requests will be caused, which will affect the display of the visible fragment content. So it’s better to load network requests when the fragment is visible.
- For background services, some services need to be stopped when the fragment is not visible, such as switching to the map page to stop the location service, which can save APP memory consumption and improve performance.
Suspects to reassure
There was a problem with the previous single-argument print
To solve this problem, check the source code and find two places to call back to this method:
One is when the fragment is instantiated, and the other is when the fragment is set, which means that when the fragment is created, it will inevitably call back to setUserVisibleHint, and then it will call back to true again in setPrimaryItem. If you want to debug the program and slow it down, you will find that there are 4 callbacks:
The reason why they are all is because the callback for parsing type displays the default value after the callback.