- 简书
- CSDN
Fragment(三)ViewPager中使用Fragment
通过这篇博客,我们能知道以下问题:
-
Fragment
加载到ViewPage
上的过程和用户可见性判断 -
Fragment
加载到ViewPage2
上的过程和用户可见性判断
ViewPager
中使用 Fragment
1. Fragment
怎样加载到 ViewPage
上
使用 ViewPager
需要一个 PagerAdapter
,我们如果使用 Fragment
填充 ViewPager
的话,需要使用的是 FragmentPagerAdapter
或者 FragmentStatePagerAdapter
,当然这两个 Adapter
都是继承至 PagerAdapter
的,这两个 Adapter
有什么差别,在后面我们会说到。
对于 PagerAdapter
我们主要看三个方法 instantiateItem()
、destroyItem()
和 setPrimaryItem()
:
-
instantiateItem()
:创建ViewPager
页面时调用 -
destroyItem()
:移除ViewPager
页面时调用 -
setPrimaryItem()
:ViewPager
页面改变显示位置时调用(比如调用adapter.setCurrentItem()
方法时)
当然还有两个方法 startUpdate()
和 finishUpdate()
,分别表示将要开始改变位置和位置改变完成时调用,代码比较简单,直接看一下。就不多说了【FragmentPagerAdapter
和 FragmentStatePagerAdapter
中都一样】
@Override
public void startUpdate(@NonNull ViewGroup container) {
if (container.getId() == View.NO_ID) {
throw new IllegalStateException("ViewPager with adapter " + this
+ " requires a view id");
}
}
@Override
public void finishUpdate(@NonNull ViewGroup container) {
if (mCurTransaction != null) {
if (!mExecutingFinishUpdate) {
try {
mExecutingFinishUpdate = true;
mCurTransaction.commitNowAllowingStateLoss();
} finally {
mExecutingFinishUpdate = false;
}
}
mCurTransaction = null;
}
}
ViewPager
改变位置时的方法调用顺序
当改变 ViewPager
的位置时,方法的调用顺序为: startUpdate()
-> instantiateItem()
【ViewPager 中的 addNewItem() 方法内部调用】 -> destroyItem()
-> setPrimaryItem()
-> finishUpdate()
。
注意:因为 ViewPager
的缓存和预加载机制,instantiateItem()
和destroyItem()
方法会调用多次,并且顺序并非一个调用多次之后另一个在调用多次,而是交叉的调用。
instantiateItem()
方法解析
Fragment
加载到 ViewPage
上,主要在 instantiateItem()
方法中:
-
FragmentPagerAdapter
类// FragmentPagerAdapter 类 public Object instantiateItem(@NonNull ViewGroup container, int position) { if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } final long itemId = getItemId(position); String name = makeFragmentName(container.getId(), itemId); // 当前需要显示的Fragment,是否已经存在 Fragment fragment = mFragmentManager.findFragmentByTag(name); if (fragment != null) { // 存在,调用 attach() 方法 mCurTransaction.attach(fragment); } else { // 不存在,调用 add() 方法 fragment = getItem(position); mCurTransaction.add(container.getId(), fragment, makeFragmentName(container.getId(), itemId)); } return fragment; }
先通过
FragmentManager
判断将要显示的Fragment
是否已经在容器中存在,如果存在调用FragmentTransaction#attach()
方法,否则通过getItem(position)
方法获取将要显示的Fragment
,然后调用FragmentTransaction#add()
方法将Fragment
添加到容器中。 -
FragmentStatePagerAdapter
类// FragmentStatePagerAdapter 类 public Object instantiateItem(@NonNull ViewGroup container, int position) { if (mFragments.size() > position) { // 从集合中获取将要显示的 Fragment Fragment f = mFragments.get(position); if (f != null) { return f; } } if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } // 集合中没有获取到,就调用 getItem() 方法获取Fragment Fragment fragment = getItem(position); // 状态处理 if (mSavedState.size() > position) { Fragment.SavedState fss = mSavedState.get(position); if (fss != null) { fragment.setInitialSavedState(fss); } } // 将当前位置之前的所有位置,添加 null 元素进行占位,保证集合中的位置和 ViewPager 中的位置一一对应 while (mFragments.size() <= position) { mFragments.add(null); } // 当前位置,使用 fragment 对象替换 null 元素 mFragments.set(position, fragment); // 调用 add() 方法 mCurTransaction.add(container.getId(), fragment); return fragment; }
首先从缓存集合中获取将要显示的
Fragmnet
,如果获取到了,就直接返回;没有获取到就通过getItem(position)
方法获取将要显示的Fragment
,然后将其保存到缓存列表中,最后调用FragmentTransaction#add()
方法将Fragment
添加到容器中。
通过 instantiateItem()
这个方法,发现不管 FragmentPagerAdapter
还是 FragmentStatePagerAdapter
,最终都是调用 FragmentTransaction#add()
方法将 Fragment
添加到容器中。
2. ViewPager
中 Fragment
用户可见性判断
因为 ViewPager
的预加载机制,在 onResume()
监听是不准确的。
这时候,我们可以通过 setUserVisibleHint()
方法来监听,当方法传入值为true
的时候,说明Fragment
可见,为false
的时候说明Fragment
被切走了。但是需要注意的是,这个方法不属于生命周期方法,所以它可能在生命周期方法执行之前就执行了,也就是说,有可能执行这个方法的时候,Fragment
还没有被添加到容器中,所以需要进行判断一下。
AndroidX优化方案
在 AndroidX 当中,FragmentPagerAdapter
和 FragmentStatePagerAdapter
的构造方法,添加一个 behavior
参数实现的。
// FragmentPagerAdapter
@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;
}
// FragmentStatePagerAdapter
@Deprecated
public FragmentStatePagerAdapter(@NonNull FragmentManager fm) {
this(fm, BEHAVIOR_SET_USER_VISIBLE_HINT);
}
public FragmentStatePagerAdapter(@NonNull FragmentManager fm,
@Behavior int behavior) {
mFragmentManager = fm;
mBehavior = behavior;
}
如果我们指定不同的 behavior
,会有不同的表现:
-
BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT
:ViewPager
中切换Fragment,setUserVisibleHin()
t 方法将不再被调用,他会确保onResume()
的正确调用时机 -
BEHAVIOR_SET_USER_VISIBLE_HINT
:跟之前的方式是一致的,我们可以通过setUserVisibleHint()
结合Fragment
的生命周期来监听。
具体实现
具体的实现这个功能在 instantiateItem()
和 setPrimaryItem()
方法中,分别来看两个方法
-
instantiateItem()
方法// FragmentPagerAdapter#instantiateItem() public Object instantiateItem(@NonNull ViewGroup container, int position) { if (fragment != mCurrentPrimaryItem) { fragment.setMenuVisibility(false); if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED); } else { fragment.setUserVisibleHint(false); } } } // FragmentStatePagerAdapter#instantiateItem() public Object instantiateItem(@NonNull ViewGroup container, int position) { fragment.setMenuVisibility(false); if (mBehavior == BEHAVIOR_SET_USER_VISIBLE_HINT) { fragment.setUserVisibleHint(false); } if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED); } }
FragmentPagerAdapter
和FragmentStatePagerAdapter
中的instantiateItem()
关于behavior
的操作基本一样,如果是mBehavior == BEHAVIOR_SET_USER_VISIBLE_HINT
,调用Fragment#setUserVisibleHint()
方法,否则调用FragmentTransaction#setMaxLifecycle()
方法,将最大生命周期状态设置为Lifecycle.State.STARTED
,也就是将Fragment
的生命周期方法中兴到onStart()
,相关文章: 简书·《Fragment(二)状态改变与管理》、CSDN·《Fragment(二)状态改变与管理》。 -
setPrimaryItem()
方法// FragmentPagerAdapter/FragmentStatePagerAdapter#setPrimaryItem() 方法是一样的 public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) { Fragment fragment = (Fragment)object; if (fragment != mCurrentPrimaryItem) { if (mCurrentPrimaryItem != null) { // 当前显示Fragment mCurrentPrimaryItem.setMenuVisibility(false); if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { // mBehavior 状态为 BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } // 最大生命周期设置为STARTED,生命周期回退到onPause() mCurTransaction.setMaxLifecycle(mCurrentPrimaryItem, Lifecycle.State.STARTED); } else { // 可见性设置为false mCurrentPrimaryItem.setUserVisibleHint(false); } } // 将要显示的Fragment fragment.setMenuVisibility(true); if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { // mBehavior 状态为 BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } // 最大生命周期设置为RESUMED,调用 onResume() 方法 mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.RESUMED); } else { // 可见性设置为true fragment.setUserVisibleHint(true); } mCurrentPrimaryItem = fragment; } }
FragmentPagerAdapter
和FragmentStatePagerAdapter
的setPrimaryItem()
方法是一样的,具体的步骤在代码中已有注释,就不在重复。相关文章: 简书·《Fragment(二)状态改变与管理》、CSDN·《Fragment(二)状态改变与管理》。
结论
-
当
mBehavior
设置为BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT
会通过FragmentTransaction#setMaxLifecycle()
来修改当前Fragment
和将要显示的Fragment
的状态,使得只有正在显示的Fragment
执行到onResume()
方法,其他Fragment
只会执行到onStart()
方法,并且当Fragment
切换到显示时执行onResume()
方法,切换到不显示状态时触发onPause()
方法。 -
当
mBehavior
设置为BEHAVIOR_SET_USER_VISIBLE_HINT
时,当Frament
可见性发生变化时调用setUserVisibleHint()
,但需注意Fragment
是否已经加载到容器中。
3. FragmentPagerAdapter
和 FragmentStatePagerAdapter
的差别
FragmentPagerAdapter
和 FragmentStatePagerAdapter
的差别,可以从这两个类的 destroyItem()
方法中查看:
// FragmentPagerAdapter
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
Fragment fragment = (Fragment) object;
// 关键代码
mCurTransaction.detach(fragment);
if (fragment.equals(mCurrentPrimaryItem)) {
mCurrentPrimaryItem = null;
}
}
// FragmentStatePagerAdapter
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
Fragment fragment = (Fragment) object;
// 关键代码
mCurTransaction.remove(fragment);
if (fragment.equals(mCurrentPrimaryItem)) {
mCurrentPrimaryItem = null;
}
}
从上面的代码我们可以直接的看到区别:
-
FragmentPagerAdapter
调用FragmentTransaction#detach()
方法,只是销毁了Fragment
的视图,但是没有移除它。具体生命周期方法过程 =>onCreateView()
->onDestroyView()
-
FragmentStatePagerAdapter
调用FragmentTransaction#remove()
方法,移除了不用的Fragment
,具体生命周期方法过程 =>onAttach()
->onDetach()
ViewPager2
中使用 Fragment
ViewPager2
是基于 RecyclerView
实现的
public ViewPager2(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize(context, attrs);
}
private void initialize(Context context, AttributeSet attrs) {
mRecyclerView = new RecyclerViewImpl(context);
attachViewToParent(mRecyclerView, 0, mRecyclerView.getLayoutParams());
}
使用 ViewPager2
加载 Fragment
也有自己特殊的 Adapter
-- FragmentStateAdapter extends RecyclerView.Adapter<FragmentViewHolder>
,具体的使用也很简单:
class Vp2FragmentAdapter(
activity: FragmentActivity,
private val fragments: List<Vp2Fragment>
) :
FragmentStateAdapter(activity) {
override fun getItemCount(): Int {
return fragments.size
}
override fun createFragment(position: Int): Fragment {
return fragments[position]
}
}
重写对应方法,返回总的大小和每个 Fragment
。
Fragment
怎样加载到 ViewPage2
上
因为 FragmentStateAdapter
继承 RecyclerView.Adapter
,所以我们主要看其中的 onCreateViewHolder()
和 onBindViewHolder()
方法:
FragmentStateAdapter
的 onCreateViewHolder()
方法
// FragmentStateAdapter#onCreateViewHolder()
public final FragmentViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return FragmentViewHolder.create(parent);
}
返回一个 FragmentViewHolder
对象,创建过程如下:
public final class FragmentViewHolder extends ViewHolder {
private FragmentViewHolder(@NonNull FrameLayout container) {
super(container);
}
@NonNull static FragmentViewHolder create(@NonNull ViewGroup parent) {
FrameLayout container = new FrameLayout(parent.getContext());
container.setLayoutParams(
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
container.setId(ViewCompat.generateViewId());
container.setSaveEnabled(false);
return new FragmentViewHolder(container);
}
@NonNull FrameLayout getContainer() {
return (FrameLayout) itemView;
}
}
继承 RecyclerView.ViewHolder
,内部创建一个 FrameLayout
作为每一个 item 的根布局,也就是我们最终的包含关系如下 RecyclerView
-> FrameLayout
-> Fragment
。这个方法比较简单,我们接着看下一个方法(onBindViewHolder()
)
FragmentStateAdapter
的 onBindViewHolder()
方法
public final void onBindViewHolder(final @NonNull FragmentViewHolder holder, int position) {
// 1. 内部调用抽象方法 createFragment() 创建Fragment,需要子类实现
ensureFragment(position);
// 2. 获取Item的根布局 FrameLayout ,增加布局完成监听
final FrameLayout container = holder.getContainer();
if (ViewCompat.isAttachedToWindow(container)) {
container.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
if (container.getParent() != null) {
container.removeOnLayoutChangeListener(this);
// 调用方法
placeFragmentInViewHolder(holder);
}
}
});
}
// 3. 需要从RecyclerView上移除的Fragment
gcFragments();
}
-
1.调用
FragmentStateAdapter#ensureFragment()
方法// FragmentStateAdapter#ensureFragment() private void ensureFragment(int position) { long itemId = getItemId(position); if (!mFragments.containsKey(itemId)) { Fragment newFragment = createFragment(position); newFragment.setInitialSavedState(mSavedStates.get(itemId)); mFragments.put(itemId, newFragment); } } public abstract @NonNull Fragment createFragment(int position);
-
2.获取Item的根布局
FrameLayout
,增加布局完成监听,调用placeFragmentInViewHolder()
方法void placeFragmentInViewHolder(@NonNull final FragmentViewHolder holder) { Fragment fragment = mFragments.get(holder.getItemId()); FrameLayout container = holder.getContainer(); View view = fragment.getView(); // Fragment已经add状态,直接将Fragment的View增加到Item上的FrameLayout上显示 if (fragment.isAdded() && view.getParent() != null) { if (view.getParent() != container) { addViewToContainer(view, container); } return; } if (fragment.isAdded()) { addViewToContainer(view, container); return; } // 如果Fragment不是 add 状态,那么就通过 FragmentManager 添加 Fragment,设置最大生命周期值为 STARTED if (!shouldDelayFragmentTransactions()) { scheduleViewAttach(fragment, container); mFragmentManager.beginTransaction() .add(fragment, "f" + holder.getItemId()) .setMaxLifecycle(fragment, STARTED) .commitNow(); mFragmentMaxLifecycleEnforcer.updateFragmentMaxLifecycle(false); } else { // 延迟递归调用 if (mFragmentManager.isDestroyed()) { return; // nothing we can do } mLifecycle.addObserver(new LifecycleEventObserver() { @Override public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) { if (shouldDelayFragmentTransactions()) { return; } source.getLifecycle().removeObserver(this); if (ViewCompat.isAttachedToWindow(holder.getContainer())) { // 延迟递归调用 placeFragmentInViewHolder(holder); } } }); } }
通过以上两步,我们的
Fragment
就已经加载和显示到ViewPager2
上了。 -
3.需要从
RecyclerView
上移除的Fragment
->gcFragments()
void gcFragments() { // 查询需要移除的 `Fragment` Set<Long> toRemove = new ArraySet<>(); for (int ix = 0; ix < mFragments.size(); ix++) { long itemId = mFragments.keyAt(ix); if (!containsItem(itemId)) { toRemove.add(itemId); mItemIdToViewHolder.remove(itemId); // in case they're still bound } } if (!mIsInGracePeriod) { mHasStaleFragments = false; // we've executed all GC checks for (int ix = 0; ix < mFragments.size(); ix++) { long itemId = mFragments.keyAt(ix); if (!isFragmentViewBound(itemId)) { toRemove.add(itemId); } } } // 遍历移除Fragment for (Long itemId : toRemove) { // 调用方法,根据id移除对应的Fragment removeFragment(itemId); } } private void removeFragment(long itemId) { Fragment fragment = mFragments.get(itemId); // 移除Fragment mFragmentManager.beginTransaction().remove(fragment).commitNow(); // 从缓存数据集中移除 mFragments.remove(itemId); }
Fragment
在 ViewPage2
上用户可见性
我们查看源码,发现回调方法 onAttachedToRecyclerView()
中有相关的代码:
@CallSuper
@Override
public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
checkArgument(mFragmentMaxLifecycleEnforcer == null);
mFragmentMaxLifecycleEnforcer = new FragmentMaxLifecycleEnforcer();
mFragmentMaxLifecycleEnforcer.register(recyclerView);
}
接着看 FragmentMaxLifecycleEnforcer#register()
的实现:
void register(@NonNull RecyclerView recyclerView) {
mViewPager = inferViewPager(recyclerView);
// 页面改变监听,回调updateFragmentMaxLifecycle()方法
mPageChangeCallback = new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageScrollStateChanged(int state) {
updateFragmentMaxLifecycle(false);
}
@Override
public void onPageSelected(int position) {
updateFragmentMaxLifecycle(false);
}
};
mViewPager.registerOnPageChangeCallback(mPageChangeCallback);
}
继续查看 updateFragmentMaxLifecycle()
方法:
void updateFragmentMaxLifecycle(boolean dataSetChanged) {
mPrimaryItemId = currentItemId;
FragmentTransaction transaction = mFragmentManager.beginTransaction();
Fragment toResume = null;
// 遍历所有缓存的Fragment
for (int ix = 0; ix < mFragments.size(); ix++) {
long itemId = mFragments.keyAt(ix);
Fragment fragment = mFragments.valueAt(ix);
if (!fragment.isAdded()) {
continue;
}
// 判断是否Fragment是滑出隐藏还是滑入显示
if (itemId != mPrimaryItemId) {
// 滑出的设置最大生命周期为 STARTED(由 RESUMED 变为 STARTED),也就是回调 onPause() 方法
transaction.setMaxLifecycle(fragment, STARTED);
} else {
// 需要显示的 Fragment
toResume = fragment; // itemId map key, so only one can match the predicate
}
fragment.setMenuVisibility(itemId == mPrimaryItemId);
}
// 如果有需要显示的,将其生命周期状态设置为 RESUMED,调用 onResume() 方法
if (toResume != null) { // in case the Fragment wasn't added yet
transaction.setMaxLifecycle(toResume, RESUMED);
}
// 提交修改
if (!transaction.isEmpty()) {
transaction.commitNow();
}
}
通过以上代码,我们知道了,在 ViewPager2
中使用 Fragment
,它的生命周期方法是正常的,只有正在显示的 Fragment
执行到 onResume()
方法,其他 Fragment
只会执行到 onStart()
方法,并且当 Fragment
切换到显示时执行 onResume()
方法,切换到不显示状态时触发 onPause()
方法。
网友评论