Fragment(三)ViewPager中使用Fragment

2022-07-14  本文已影响0人  ITRenj

Fragment(三)ViewPager中使用Fragment

博客对应的Demo地址:GitHubGitee

通过这篇博客,我们能知道以下问题:

ViewPager 中使用 Fragment

1. Fragment 怎样加载到 ViewPage

使用 ViewPager需要一个 PagerAdapter,我们如果使用 Fragment 填充 ViewPager 的话,需要使用的是 FragmentPagerAdapter 或者 FragmentStatePagerAdapter ,当然这两个 Adapter 都是继承至 PagerAdapter 的,这两个 Adapter 有什么差别,在后面我们会说到。

对于 PagerAdapter 我们主要看三个方法 instantiateItem()destroyItem()setPrimaryItem()

当然还有两个方法 startUpdate()finishUpdate(),分别表示将要开始改变位置和位置改变完成时调用,代码比较简单,直接看一下。就不多说了【FragmentPagerAdapterFragmentStatePagerAdapter 中都一样】

@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() 方法中:

通过 instantiateItem() 这个方法,发现不管 FragmentPagerAdapter 还是 FragmentStatePagerAdapter,最终都是调用 FragmentTransaction#add() 方法将 Fragment 添加到容器中。

2. ViewPagerFragment 用户可见性判断

因为 ViewPager 的预加载机制,在 onResume() 监听是不准确的。

这时候,我们可以通过 setUserVisibleHint() 方法来监听,当方法传入值为true的时候,说明Fragment可见,为false的时候说明Fragment被切走了。但是需要注意的是,这个方法不属于生命周期方法,所以它可能在生命周期方法执行之前就执行了,也就是说,有可能执行这个方法的时候,Fragment 还没有被添加到容器中,所以需要进行判断一下。

AndroidX优化方案

在 AndroidX 当中,FragmentPagerAdapterFragmentStatePagerAdapter 的构造方法,添加一个 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,会有不同的表现:

具体实现

具体的实现这个功能在 instantiateItem()setPrimaryItem() 方法中,分别来看两个方法

结论

3. FragmentPagerAdapterFragmentStatePagerAdapter 的差别

FragmentPagerAdapterFragmentStatePagerAdapter 的差别,可以从这两个类的 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;
    }
}

从上面的代码我们可以直接的看到区别:

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() 方法:

FragmentStateAdapteronCreateViewHolder() 方法

// 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()

FragmentStateAdapteronBindViewHolder() 方法

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();
}

FragmentViewPage2上用户可见性

我们查看源码,发现回调方法 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() 方法。

上一篇 下一篇

猜你喜欢

热点阅读