ViewPager+FragmentPagerAdapter 被标记为过时
Tab
页是绝大多数项目中很常见的样式了,如果Tab
采用的是ViewPager+FragmentPagerAdapter
的方式来布局,使用如下:
val mViewPager: ViewPager by id(R.id.m_pager)
val mViewPagerAdapter = ChapterViewPagerAdapter(childFragmentManager)
mViewPager.adapter = mViewPagerAdapter
注:其中ChapterViewPagerAdapter
对应的FragmentPagerAdapter
实现。
源码浅析
FragmentPagerAdapter
点进去发现目前的使用方式已经被标记为过时了,源码如下:
private final int mBehavior;
@Deprecated
public FragmentPagerAdapter(@NonNull FragmentManager fm) {
this(fm, BEHAVIOR_SET_USER_VISIBLE_HINT);
}
public FragmentPagerAdapter(@NonNull FragmentManager fm,
@Behavior int behavior) {
mFragmentManager = fm;
mBehavior = behavior;
}
之前使用的一个参数的构造方法已经被标记位Deprecated
了,系统推荐使用2个参数的构造方法,所以直接来看第2个参数的含义是什么:
@Retention(RetentionPolicy.SOURCE)
@IntDef({BEHAVIOR_SET_USER_VISIBLE_HINT, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT})
private @interface Behavior { }
@Deprecated
public static final int BEHAVIOR_SET_USER_VISIBLE_HINT = 0;
public static final int BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT = 1;
FragmentPagerAdapter
构造方法第2个参数传入的是一个int
值并且只能传入上面对应的两个值:BEHAVIOR_SET_USER_VISIBLE_HINT、BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT
。
FragmentPagerAdapter(FragmentManager fm)
默认给我们传入的是BEHAVIOR_SET_USER_VISIBLE_HINT
,此值也被标记为Deprecated
了,重点来看BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT
的含义,先来看一下被赋值的mBehavior
都在哪里使用了,按关键字搜一下:
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
final long itemId = getItemId(position);
// Do we already have this fragment?
String name = makeFragmentName(container.getId(), itemId);
Fragment fragment = mFragmentManager.findFragmentByTag(name);
if (fragment != null) {
if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
mCurTransaction.attach(fragment);
} else {
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;
}
@Override
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;
}
}
instantiateItem()
是初始化Fragment
时调用的方法,setPrimaryItem()
是替换显示的Fragment
时执行,逻辑很简单,以instantiateItem()
为例,
看下mBehavior
做了哪些改动:
if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
} else {
fragment.setUserVisibleHint(false);
}
可以看到如果FragmentPagerAdapter
构造函数中传入的是BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT
,则会执行FragmentTransaction.setMaxLifecycle()
,否则会执行我们熟悉的Fragment.setUserVisibleHint()
方法,继续看setMaxLifecycle()
用来干什么的。
@NonNull
public FragmentTransaction setMaxLifecycle(@NonNull Fragment fragment,
@NonNull Lifecycle.State state) {
addOp(new Op(OP_SET_MAX_LIFECYCLE, fragment, state));
return this;
}
第2个参数传入的是Lifecycle.State
,其实是依赖 Jetpack Lifecycle
生命周期来管理Fragment
的状态,顾名思义,setMaxLifecycle
是为了设置Fragment
的最大状态、
- Fragment状态值:
INITIALIZING、CREATED、ACTIVITY_CREATED、STARTED、RESUMED
- Lifecycle State:生命周期状态,包括
DESTROYED、INITIALIZED、CREATED、STARTED、RESUMED
,两者关系:
以FragmentTransaction.setMaxLifecycle()
参数传入Lifecycle.State.STARTED
为例:
Lifecycle.State.STARTED
对应Fragment
的STARTED
状态,如果当前Fragment
状态低于STARTED
,那么Fragment
的状态会变为STARTED
,以当前Fragment
状态为CREATED
为例,接下来会依次执行onCreateView()、onActivityCreate()和onStart()
方法;- 如果当前
Fragment
状态高于STARTED
,也就是RESUMED
,那么Fragment
的状态会被强制降为STARTED
,接下来会执行onPause()
方法。 - 如果当前
Fragment
的状态恰好为STARTED
,那么就什么都不做。
结论
看到这里,基本知道如何替换setUserVisibleHint()了,列一下结论:
- 使用
FragmentPagerAdapter
时直接使用两个参数的构造方法FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)
- 本质上是通过
FragmentTransaction的setMaxLifecycle()
方法来替代setUserVisibleHint()
方法实现Fragment
的懒加载效果。instantiateItem()
中setMaxLifecycle()
设置的Fragment
状态为STARTED
,即通过ViewPager.offscreenPageLimit
设置提前初始化时,临近的Fragment
最多执行到onStart()
方法,不会再执行onResume()
方法了。 - 如果需要
Fragment
只执行一次对应逻辑且Fragment
重新加载View
时需要重置,之前通常会通过setUserVisibleHint(isVisibleToUser: Boolean)
里通过isVisibleToUser
以及自定义的isFirstLoad
去判断,现在可以直接将逻辑写到onResume
中,形如:
private var isFirstLoad: Boolean = true //是否第一次加载
@Override
public void onResume() {
super.onResume();
if (isFirstLoad) {
//......
isFirstLoad = false;
}
}
//对应Fragment的CREATED状态
@Override
public void onDestroyView() {
super.onDestroyView();
isFirstLoad = true;
}
举个栗子
//LazyViewPagerAdapter.kt
class LazyViewPagerAdapter(fm: FragmentManager) :
FragmentPagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
override fun getCount(): Int = 3
override fun getItem(position: Int): Fragment {
return LazyFragment.newInstance(position.toString())
}
}
//MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var mViewPager: ViewPager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mViewPager = findViewById(R.id.view_pager)
val adapter = LazyViewPagerAdapter(supportFragmentManager)
mViewPager.adapter = adapter
mViewPager.offscreenPageLimit = 1 //默认是1 小于1的时候会被置为1
}
}
//LazyFragment.kt
class LazyFragment : Fragment() {
private var position: String? = null
private var param2: String? = null
private lateinit var mTvContent: TextView
override fun onAttach(context: Context) {
super.onAttach(context)
arguments?.let {
position = it.getString(ARG_PARAM1)
param2 = it.getString(ARG_PARAM2)
}
log("Fragment$position: onAttach()")
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
log("Fragment$position: onCreate()")
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_lazy, container, false)
mTvContent = view.findViewById(R.id.tv_content)
mTvContent.text = position.toString()
log("Fragment$position: onCreateView()")
return view
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
log("Fragment$position: onActivityCreated()")
}
override fun onStart() {
super.onStart()
log("Fragment$position: onStart()")
}
override fun onResume() {
super.onResume()
log("Fragment$position: onResume()")
}
override fun onPause() {
super.onPause()
log("Fragment$position: onPause()")
}
override fun onStop() {
super.onStop()
log("Fragment$position: onStop()")
}
override fun onDestroyView() {
super.onDestroyView()
log("Fragment$position: onDestroyView()")
}
override fun onDestroy() {
super.onDestroy()
log("Fragment$position: onDestroy()")
}
override fun onDetach() {
super.onDetach()
log("Fragment$position: onDetach()")
}
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
super.setUserVisibleHint(isVisibleToUser)
log("Fragment$position: setUserVisibleHint()->$isVisibleToUser")
}
companion object {
@JvmStatic
fun newInstance(param1: String = "") =
LazyFragment().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, "")
position = param1
}
}
}
}
不传BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT时
LazyViewPagerAdapter
构造参数中没有传 BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT
,日志如下:
可以看到,setUserVisibleHint()
会执行,且当前显示Fragment0
的时候,Fragment1
的onResume()
也执行了。
传入BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT时
LazyViewPagerAdapter
构造参数中传入BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT
参数,日志如下:
可以看到,如果把BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT参数去掉,setUserVisibleHint()不再执行,且当前显示Fragment0的时候,Fragment1的执行到onStart()之后不再执行。对应了instantiateItem()
的 mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED)
其他
测试中的Fragment
是androidx.fragment:fragment:1.1.0
版本,且使用的是ViewPager
。在Fragment
高版本(如测试使用1.3.6版本)中,FragmentPagerAdapter
整个类已经被标记为过时了,推荐直接使用ViewPager2
实现懒加载效果。