[Android フレームワーク シリーズ] 第 15 章 Fragment+ViewPager と Viewpager2 の関連原則

1 はじめに

前章[Android フレームワーク シリーズ] 第 14 章 Fragment の基本原則 (AndroidX 版)では Fragment の基本原則を学びましたが、この章では、一般的に使用される用途Fragment+ViewPagerFragment+ViewPager2関連する用途、および基本的なソース コード分析について学びます。

ここに画像の説明を挿入します

2 フラグメント+ViewPager

私たちは一般的に と という 2 つの実装クラスを使用しますPagerAdapter今日はそれらの使用方法と比較方法を学びます。FragmentStatePagerAdapterFragmentPagerAdapter

2.1 FragmentPagerAdapter と FragmentStatePagerAdapter の違い

1. fragmentsオブジェクトの処理:
FragmentPagerAdapter: スコープ外はfragmentsメモリ ( ) に保存されますdetachが、fragment対応するオブジェクトはView破棄されます
FragmentStatePagerAdapter。 : スコープ外はメモリ ( )fragmentsに保存されず、同様に破棄されます。2. ステータス処理: :範囲外の対応するものを保存します。 :範囲内の対応するもののみを保存しますこれは、ライフサイクル コールバックで渡される外部パラメーターに使用され、同様のものです3. 適用可能なシナリオ: 同じ量メモリは大きくなりますが、ページの切り替えはよりフレンドリーです。メモリの使用量は少なく、ページの切り替えはわずかに悪くなります。removeView

FragmentPagerAdapterfragmentsSavedState
FragmentStatePagerAdapterfragmentsSavedStateSavedStateFragmentActivity
fragmentsFragmentPagerAdapterFragmentStatePagerAdapter

したがって、数が少ない状況FragmentPagerAdapterに適しており、 Fragment の数が多い状況にも適しています。FragmentFragmentStatePagerAdapter

まずは見てみましょうFragmentPagerAdapter:

	@Override
    public Object instantiateItem(ViewGroup container, int position) {
    
    
        if (mCurTransaction == null) {
    
    
            mCurTransaction = mFragmentManager.beginTransaction();
        }

        final long itemId = getItemId(position);

        // Do we already have this fragment?
        //判断请求的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); 
            //调用这个方法来生成新的Fragment
            if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
            // 将新生成的Fragment存储起来,以便以后再次用到时,直接attach()
            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 destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
    
    
        Fragment fragment = (Fragment) object;

        if (mCurTransaction == null) {
    
    
            mCurTransaction = mFragmentManager.beginTransaction();
        }
        if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
                + " v=" + fragment.getView());
        mCurTransaction.detach(fragment);
        if (fragment.equals(mCurrentPrimaryItem)) {
    
    
            mCurrentPrimaryItem = null;
        }
    }

FragmentPagerAdapterdestroyItem呼び出されるメソッドはフラグメントdetach()の状態を変更するだけです。つまり、adapter時間全体を削除することによってのみ、生成されたすべてのフラグメントをFragment削除できます。そうでない場合、フラグメントはメモリ内に直接存在します。

次に比較しますFragmentStatePagerAdapter:

  @Override
    public Object instantiateItem(@NonNull ViewGroup container, int position) {
    
    
        // If we already have this item instantiated, there is nothing
        // to do.  This can happen when we are restoring the entire pager
        // from its saved state, where the fragment manager has already
        // taken care of restoring the fragments we previously had instantiated.
        // mFragments中对应位置有Fragment的情况下直接返回
        if (mFragments.size() > position) {
    
    
            Fragment f = mFragments.get(position);
            if (f != null) {
    
    
                return f;
            }
        }

        if (mCurTransaction == null) {
    
    
            mCurTransaction = mFragmentManager.beginTransaction();
        }

        Fragment fragment = getItem(position);
        if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
        if (mSavedState.size() > position) {
    
    
            Fragment.SavedState fss = mSavedState.get(position);
            if (fss != null) {
    
    
                fragment.setInitialSavedState(fss);
            }
        }
        while (mFragments.size() <= position) {
    
    
            mFragments.add(null);
        }
        fragment.setMenuVisibility(false);
        if (mBehavior == BEHAVIOR_SET_USER_VISIBLE_HINT) {
    
    
            fragment.setUserVisibleHint(false);
        }

        mFragments.set(position, fragment);
        mCurTransaction.add(container.getId(), fragment);

        if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
    
    
            mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
        }

        return fragment;
    }
    
  	@Override
    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
    
    
        Fragment fragment = (Fragment) object;

        if (mCurTransaction == null) {
    
    
            mCurTransaction = mFragmentManager.beginTransaction();
        }
        if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
                + " v=" + ((Fragment)object).getView());
        while (mSavedState.size() <= position) {
    
    
            mSavedState.add(null);
        }
        mSavedState.set(position, fragment.isAdded()
                ? mFragmentManager.saveFragmentInstanceState(fragment) : null);
        mFragments.set(position, null);

		// 缓存中移除fragment,下次使用得重新创建
        mCurTransaction.remove(fragment);
        if (fragment.equals(mCurrentPrimaryItem)) {
    
    
            mCurrentPrimaryItem = null;
        }
    }

FragmentStatePagerAdapterdestroyItemメソッドが呼び出されたときに削除されたFragmentため、次回再作成する必要があります。Fragment

3 フラグメント+ViewPager2

ViewPager2これはAndroid Jetpackライブラリ内のコンポーネントであり、アプリケーションでページ切り替えやスライド効果を実装するために使用されるコンテナです。実際、ViewPager2それ自体は から継承されていますRecyclerView[Android フレームワーク シリーズ] 第 12 章 RecycleView 関連の原則と 4 レベルのキャッシュ戦略分析を確認できます。

3.1 ViewPager2の役割と目的

3.1.1 ViewPager2 の使用シナリオ

さまざまなシナリオで使用できる強力なスライディング コンテナです。柔軟なページ切り替え機能とレイアウトのカスタマイズ機能を提供し、アプリケーション インターフェイスをよりリッチでインタラクティブにします。次のシナリオで使用できます。

  1. オンボーディング ページまたはウェルカム ページの実装
    ViewPager2を使用して、ユーザーがスワイプしてアプリの機能を紹介したり、ウェルカム コンテンツを表示したりできるオンボーディング ページまたはウェルカム ページを作成できます。

  2. [画像ブラウザの作成]
    ViewPager2を使用して画像ブラウザを作成すると、ユーザーはスライドしてさまざまな画像を切り替えることができ、ズームとジェスチャの操作がサポートされます。

  3. カルーセルの構築は
    ViewPager2、アダプタを通じてさまざまなカルーセル項目を動的にロードし、自動サイクル スクロールを提供できるカルーセル関数の構築に非常に適しています。

  4. タブ付きレイアウトの実装
    組み合わせを使用してタブ付きレイアウトを作成でき、ユーザーはタブをスライドさせることで異なるコンテンツ ページ間を切り替えることができますTabLayoutViewPager2

  5. 縦スライド ページの作成
    とは異なりViewPagerViewPager2縦スライドに対応しているため、縦スライド ナビゲーション メニューや縦ニュース リストなど、縦スライド ページ レイアウトの作成に使用できます。

  6. ページング データ表示の実装を
    ViewPager2使用すると、ページごとに大量のデータをロードし、各ページにコンテンツの一部を表示するなど、ページング データを表示できます。

  7. ネストされたスライディング レイアウトは、
    ViewPager2他のスライディング コンポーネント ( などRecyclerView) とネストして使用して、複雑なスライディング レイアウト構造を実装できます。

  8. カスタマイズされたスライド効果の実現
    カスタマイズされたコンバーター ( Transformer) を使用すると、グラデーション、ズーム、回転など、さまざまなクールなページ切り替え効果を実現できます。

3.1.2 ViewPager と比較した ViewPager2 の改善点と利点

ViewPager2ViewPagerはオリジナルの改良バージョンであり、より優れたパフォーマンス、より柔軟なアダプタ、およびより豊富な機能を提供します。これは、スライディング ページ レイアウトを構築するための推奨コンポーネントです。アプリケーションでさまざまなスライディング ページ要件を実装し、より良いユーザー エクスペリエンスを提供できます。次のような改善点と利点があります。

  1. 垂直スライドのサポートが
    ViewPager2改良ViewPagerされており、最も重要な改良点の 1 つは です支持垂直滑动AndroidではViewPager横スライドのみ対応しています。これにより、ViewPager2垂直レイアウトや特定のシナリオを作成する際に、垂直スライド機能がより便利かつ柔軟になります。

  2. パフォーマンスと安定性の向上
    ViewPager2内部実装はRecyclerViewコンテナとして使用され、ViewPager実装に依存しなくなります。これにより、ViewPager2のようなRecyclerView利点得られます。同時に、誤ったエントリ位置やタイミングの悪いデータ更新など、いくつかの既知の問題や不安定性も解決されました。更好的性能和内存管理更流畅的滑动体验及更好的布局回收和复用机制ViewPager2ViewPager

  3. ページとしての Fragment の使用をサポートします。 Fragment
    とは異なり、渡したり適応したりせずにViewPagerFragment のページとしてのViewPager2使用を直接サポートしますこれにより、ページ管理とライフサイクル処理が簡素化され、より直感的で一貫した使用エクスペリエンスが提供されます。FragmentFragmentPagerAdapterFragmentStatePagerAdapter

  4. より柔軟なアダプターには、
    ViewPager2新しいアダプター インターフェイス、RecyclerView.Adapterのサブクラスが導入されていますRecyclerView.Adapterこれにより、アダプターの作成と管理がより柔軟になり、さらに多くの機能と拡張性が提供されます。

  5. より豊富な機能とインターフェイス
    ViewPager2により、ページのプリロードのサポート、より強力なページ切り替えアニメーションのサポート、より充実したコールバック インターフェイスなど、多くの新しい機能とインターフェイスが提供されます。これらの機能とインターフェイスにより、開発者はViewPager2動作と外観をより詳細に制御およびカスタマイズできるようになります。

3.2 フラグメント状態アダプター

ViewPager2上記でいくつかの違いと最適化の比較ポイントについて学びました。引き続き、対応するものViewPagerを見てみましょうそれが から継承することがわかっているため、それに対応するものはから継承する必要があり、友人はそれを理解できるはずです。次に、追加する結合はコンテナーとして表示されます。再利用に焦点を当てていますが、 にあるものは再利用されない、つまり、あるだけ作成する必要があるため、これらをどのように変換するか?ViewPager2Adapter
ViewPager2RecyclerViewFragmentStateAdapterRecyclerView.AdapterRecyclerViewItemFragment
RecyclerView.AdapterViewHolderFragmentStateAdapterFramgentitemFragment

まずソースコードを見てみましょうFragmentStateAdapter:

public abstract class FragmentStateAdapter extends
        RecyclerView.Adapter<FragmentViewHolder> implements StatefulAdapter {
    
    
        
    // 通过FragmentStateAdapter声明中的泛型可以知道,
    // ViewPager2之所以能够在RecyclerView的基础上能对外屏蔽对ViewHolder的使用,
    // 其内部是借助FragmentViewHolder实现的,其内部就new了一个FrameLayout。
    @Override
    public final FragmentViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    
    
        return FragmentViewHolder.create(parent);
    }

    @Override
    public final void onBindViewHolder(final @NonNull FragmentViewHolder holder, int position) {
    
    
        final long itemId = holder.getItemId();
        final int viewHolderId = holder.getContainer().getId();
        final Long boundItemId = itemForViewHolder(viewHolderId); // item currently bound to the VH
        if (boundItemId != null && boundItemId != itemId) {
    
    
            removeFragment(boundItemId);
            mItemIdToViewHolder.remove(boundItemId);
        }

        mItemIdToViewHolder.put(itemId, viewHolderId); // this might overwrite an existing entry
        // 内部会最终回调到createFragment用来创建当前Fragment
        ensureFragment(position);

        /** Special case when {@link RecyclerView} decides to keep the {@link container}
         * attached to the window, but not to the view hierarchy (i.e. parent is null) */
        final FrameLayout container = holder.getContainer();
        if (ViewCompat.isAttachedToWindow(container)) {
    
    
            if (container.getParent() != null) {
    
    
                throw new IllegalStateException("Design assumption violated.");
            }
            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);
                    }
                }
            });
        }
		
		// 回收已经不在item集合中的Fragment,节省内存开销
        gcFragments();
    }

   	private void ensureFragment(int position) {
    
    
        long itemId = getItemId(position);
        if (!mFragments.containsKey(itemId)) {
    
    
            // TODO(133419201): check if a Fragment provided here is a new Fragment
            Fragment newFragment = createFragment(position);
            newFragment.setInitialSavedState(mSavedStates.get(itemId));
            mFragments.put(itemId, newFragment);
        }
    }
    
    void gcFragments() {
    
    
        if (!mHasStaleFragments || shouldDelayFragmentTransactions()) {
    
    
            return;
        }

        // Remove Fragments for items that are no longer part of the data-set
        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
            }
        }

        // Remove Fragments that are not bound anywhere -- pending a grace period
        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);
                }
            }
        }

        for (Long itemId : toRemove) {
    
    
            removeFragment(itemId);
        }
    }


	// onViewAttachToWindow的时候调用placeFragmentInViewHolder,
	// 将FragmentViewHolder的container与当前Fragment绑定
    @Override
    public final void onViewAttachedToWindow(@NonNull final FragmentViewHolder holder) {
    
    
        placeFragmentInViewHolder(holder);
        gcFragments();
    }

 	void placeFragmentInViewHolder(@NonNull final FragmentViewHolder holder) {
    
    
        Fragment fragment = mFragments.get(holder.getItemId());
        if (fragment == null) {
    
    
            throw new IllegalStateException("Design assumption violated.");
        }
        FrameLayout container = holder.getContainer();
        View view = fragment.getView();

        if (!fragment.isAdded() && view != null) {
    
    
            throw new IllegalStateException("Design assumption violated.");
        }

        // { f:added, v:notCreated, v:notAttached} -> schedule callback for when created
        if (fragment.isAdded() && view == null) {
    
    
            scheduleViewAttach(fragment, container);
            return;
        }

        // { f:added, v:created, v:attached } -> check if attached to the right container
        if (fragment.isAdded() && view.getParent() != null) {
    
    
            if (view.getParent() != container) {
    
    
                addViewToContainer(view, container);
            }
            return;
        }

        // { f:added, v:created, v:notAttached} -> attach view to container
        if (fragment.isAdded()) {
    
    
            addViewToContainer(view, container);
            return;
        }

        // { f:notAdded, v:notCreated, v:notAttached } -> add, create, attach
        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);
                    }
                }
            });
        }
    }
    
}
public final class FragmentViewHolder extends ViewHolder {
    
    
    private FragmentViewHolder(@NonNull FrameLayout container) {
    
    
        super(container);
    }

	// FragmentViewHolder实际上就创建了一个FrameLayout
    @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;
    }
}

Fragment上記のソースコード解析から、再利用ではないものの、再利用ViewHolderによって代替表示containerが実現されていることがわかります。Framgent

4 まとめ

この章の内容を要約すると、次のようになります。

  1. Fragment+ViewPager水平方向にしかスクロールできず、パフォーマンスは比較的悪いです。対応するAdapterエフェクトも異なります。
  2. Fragment+ViewPagerを使用するとFragmentPagerAdapter、スコープ外はfragmentsメモリ ( ) に保存されますdetachが、fragment該当するものはView破棄され、fragments該当するものはSavedState保存されます。FragmentPagerAdapterメモリは大きくなりますが、ページの切り替えはよりフレンドリーで、Fragment少量に適しています。
  3. Fragment+ViewPager使用するとFragmentStatePagerAdapterスコープ外のfragmentsメモリ ( ) には保存されず、破棄されます。範囲内の対応するものだけが保存されますこれは、ライフサイクル コールバックで渡される外部パラメーターに使用され、同様のものですメモリ使用量が少なく、ページ切り替えが若干悪いです。大量の場合に適しています。removeViewfragmentsSavedStateSavedStateFragmentActivityFragment
  4. Fragment+ViewPager2水平方向または垂直方向にスクロールでき、RecyclerViewメモリ使用量、キャッシュ管理などを含む基本的にすべての利点を継承します。FragmentStateAdapterから継承されますRecyclerView.Adapterが、Fragment再利用されませんが、代替表示は再利用ViewHolderによってcontainer実装されます。Framgent

おすすめ

転載: blog.csdn.net/u010687761/article/details/132674389