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
实现懒加载效果。
本文是博主对Adapter(适配器)的一些理解,为了加深对Adapter的理解以及记录自己的阶段学习而写,同时也适合初学者阅读,参考本条博客的逻辑进行学习。
在Android x以前,我们实现懒加载通常是通过 setUserVisibleHint 方法来控制Fragment是否可见。在Android x之后,Google 提供了新的方案给我们。今天我们就来学习一下。
终于建了一个自己个人小站:https://huangtianyu.gitee.io,以后优先更新小站博客,欢迎进站,O(∩_∩)O~~
在目前的软硬件环境下,Native App与Web App在用户体验上有着明显的优势,但在实际项目中有些会因为业务的频繁变更而频繁的升级客户端,造成较差的用户体验,而这也恰恰是Web App的优势。
基于Android官方AsyncListUtil优化改进RecyclerView分页加载机制(一)
Android AsyncListUtil是Android官方提供的专为列表这样的数据更新加载提供的异步加载组件。