ViewPager2+Fragment 操作上の注意

ViewPager2+Fragment 操作上の注意

良い気分

ViewPager2 の紹介

ViewPager2公式サイト紹介

ViewPager2 公式ウェブサイトのサンプル

ViewPager2正式版のリリースから1年以上が経過し、ViewPagerすでに更新が停止しており、代わりにViewPager2の使用を推奨しています。ViewPager2最下層はRecyclerView実装に基づいているため、多くの利点が得られますRecyclerView

  • 伝統的PagerAdapterで統一されたものAdapterを放棄しますAPI
  • 水平レイアウトと垂直レイアウトの両方を自由にスライドできます。
  • サポートDiffUitl、部分的なリフレッシュを実現できます。
  • サポートRTL(右から左へ)、海外に行く必要がある一部のアプリに非常に役立ちます。
  • クールなジャンプアニメーションを実現するためのItemDecoratorコロケーションをサポートします。PageTransformer

ViewPager2それはより協調的なFragment使用であり、助けが必要ですFragmentStateAdapter

TabLayout関連するコードを直接読むか、ViewPager2 の公式 Web サイトでサンプルを実行することができます(ここでは説明を繰り返しません)。

以下は、主に使用過程で遭遇した問題について話します~!

実運用効果

天井を上にスワイプ + タイトル ページを左右にスライド + 縦横にスライドして一覧表示 + タイトル ページのデータと数量を更新

天井を上にスワイプ
CoordinatorLayout+ AppBarLayout+CollapsingToolbarLayout
左右にスライド
ViewPager2+ TabLayout+Fragment
水平および垂直スライド リスト
RecycleView+NestedScrollableHost
タイトル ページのデータと数量
TabLayoutMediator+ ステートメント サイクルの検出 + キャッシュの最適化

viewpage2_tabLayout_nestedscroll.gif

RecycleView と Viewpage2 の間のスライド競合

/**
 * Created by Tanzhenxing
 * Date: 2021/4/7 7:04 下午
 * Description:解决 [RecyclerView] 嵌套到 [androidx.viewpager2.widget.ViewPager2] 左右滑动冲突
 * 目前只解决了左右滑动冲突
 */
class RecyclerViewAtViewPager2 : RecyclerView {
    
    
    constructor(context: Context) : this(context, null)
    constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

    var x1 = 0f
    var x2 = 0f
    override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
    
    
        if(event!!.action == MotionEvent.ACTION_DOWN) {
    
    
            x1 = event.x
        } else if(event.action == MotionEvent.ACTION_MOVE) {
    
    
            x2 = event.x
        } else if (event.action == MotionEvent.ACTION_CANCEL
            || event.action == MotionEvent.ACTION_UP) {
    
    
            x2 = 0f
            x1 = 0f
        }
        val xOffset= x2-x1
        if (layoutManager is LinearLayoutManager) {
    
    
            val linearLayoutManager = layoutManager as LinearLayoutManager
            if (linearLayoutManager.orientation == HORIZONTAL) {
    
    
                if ((xOffset <= 0 && canScrollHorizontally(1))
                    || (xOffset >= 0 && canScrollHorizontally(-1))) {
    
    
                    this.parent?.requestDisallowInterceptTouchEvent(true)
                } else {
    
    
                    this.parent?.requestDisallowInterceptTouchEvent(false)
                }
            } else {
    
    
                // TODO: 2021/4/8 目前没有实现上下滑动和 [androidx.viewpager2.widget.ViewPager2]上下滑动的冲突
            }
        } else {
    
    
            handleDefaultScroll()
        }

        return super.dispatchTouchEvent(event)
    }

    fun handleDefaultScroll() {
    
    
        val canScrollHorizontally = canScrollHorizontally(-1) || canScrollHorizontally(1)
        val canScrollVertically = canScrollVertically(-1) || canScrollVertically(1)
        if (canScrollHorizontally || canScrollVertically) {
    
    
            this.parent?.requestDisallowInterceptTouchEvent(true)
        } else {
    
    
            this.parent?.requestDisallowInterceptTouchEvent(false)
        }
    }
}

ViewPager2 での Fragment の遅延読み込み

遅延読み込み

Fragment一般的に、ページ上でデータの遅延ロードを使用する場合、onHiddenChangedメソッドを使用して表示と非表示を判断し、最初に表示されたときにインターフェイスを呼び出します。

 @Override
public final void onHiddenChanged(boolean hidden) {
    
    
  super.onHiddenChanged(hidden);
  if (!hidden) {
    
    
    onUserVisible();
  } else {
    
    
    onUserGone();
  }
}

しかし ではViewPager2FragmentメソッドsetUserVisibleHintonHiddenChangedメソッドは実行されません。

  • ViewPager最初のページを表示し、バックグラウンドでログを切り取ります。
04-17 16:45:10.992 D/tanzhenxing:11(22006): onCreate
04-17 16:45:10.992 D/tanzhenxing:11(22006): onCreateView:
04-17 16:45:11.004 D/tanzhenxing:11(22006): onActivityCreated
04-17 16:45:11.004 D/tanzhenxing:11(22006): onViewStateRestored: 184310198
04-17 16:45:11.004 D/tanzhenxing:11(22006): onStart
04-17 16:45:11.004 D/tanzhenxing:11(22006): onResume
04-17 16:45:18.739 D/tanzhenxing:11(22006): onPause
04-17 16:45:18.779 D/tanzhenxing:11(22006): onStop

次に、フォアグラウンド ログに戻ります。

04-17 16:53:40.749 D/tanzhenxing:11(22006): onStart
04-17 16:53:40.752 D/tanzhenxing:11(22006): onResume
  • ViewPager最初のページを表示し、手動で 2 ページ目のログにスライドします。
04-17 16:54:44.168 D/tanzhenxing:12(22006): onCreate
04-17 16:54:44.168 D/tanzhenxing:12(22006): onCreateView:
04-17 16:54:44.178 D/tanzhenxing:12(22006): onActivityCreated
04-17 16:54:44.178 D/tanzhenxing:12(22006): onViewStateRestored: 47009644
04-17 16:54:44.178 D/tanzhenxing:12(22006): onStart
04-17 16:54:44.553 D/tanzhenxing:11(22006): onPause
04-17 16:54:44.554 D/tanzhenxing:12(22006): onResume

Fragmentステートメント サイクルでonStartandメソッドを使用して、遅延読み込みを実行できるようですonResume

プリロード

データ要求がonCreateViewまたはonStartインターフェイスのオフスクリーン要求を実行できます。

FragmentStateAdapter

ViewPager2から継承RecyclerView、最も可能性が高いのFragmentStateAdapterは から継承RecyclerView.Adapter:

public abstract class FragmentStateAdapter extends 
  RecyclerView.Adapter<FragmentViewHolder> implements StatefulAdapter {
    
    
}

onCreateViewHolder

onCreateViewHolderRecycleVeiwは の作成に使用される方法ですViewHolder:

@NonNull
@Override
public final FragmentViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
    
    
  return FragmentViewHolder.create(parent);
}

FragmentViewHolder主な役割は、以下を提供するFrameLayoutことによってFragmentコンテナーとして使用されます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);
}

onBindViewHolder

onBindViewHolderRecycleVeiwは、データ バインディングに使用されるメソッドです。

@Override
public final void onBindViewHolder(final @NonNull FragmentViewHolder holder, int position) {
    
    
    /**部分代码省略 */
	  ensureFragment(position);
    /**部分代码省略 */
  	gcFragments();
}

ensureFragment(position)、最終的に内部的にコールバックしてcreateFragment現在の を作成しますFragment

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

mFragmentsキャッシュはFragment後でplaceFramentInViewholder使用するために作成されます。gcFragments使用されなくなったものFragment(対応するアイテムは削除されています) は、メモリのオーバーヘッドを節約するために再利用されます。

onViewAttachedToWindow

onViewAttachedToWindowViewHolderページに表示されるコールバックです。

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

FragmentStateAdapter使用

  • Fragmentオブジェクトコンテナ;
  • 生産fragment識別id;
class MyFragmentStateAdapter(val data: List<Int>, fragment: Fragment) : FragmentStateAdapter(fragment){
    
    
    private val fragments = mutableMapOf<Int, Fragment>()
    override fun createFragment(position: Int): Fragment {
    
    
        val value = data[position]
        val fragment = fragments[value]
        if (fragment != null) {
    
    
            return fragment
        }
        val cardFragment =
            NestedAllRecyclerViewFragment.create(value)
        fragments[value] = cardFragment
        return cardFragment
    }

    /**
     * 根据数据生成唯一id
     *
     * 如果不重写,那么在调用[notifyDataSetChanged]更新的时候
     *
     * 会抛出```new IllegalStateException("Fragment already added")```异常
     */
    override fun getItemId(position: Int): Long {
    
    
        return data[position].toLong()
    }

    /**
     * 用来判断当前id对应的fragment是否添加过
     */
    override fun containsItem(itemId: Long): Boolean {
    
    
        data.forEach {
    
    
            if (it.toLong() == itemId) {
    
    
                return true
            }
        }
        return false
    }
    override fun getItemCount(): Int {
    
    
        return data.size
    }
}

進行中

Fragment インスタンスを取得する

fun getCurrentFragment(position: Int): Fragment? =
        fragment.childFragmentManager.findFragmentByTag("f$position")

ソースコード分析:

public abstract class FragmentStateAdapter extends RecyclerView.Adapter<FragmentViewHolder>
    implements StatefulAdapter {
    
    
    /**部分代码省略 */
    void placeFragmentInViewHolder(@NonNull
    final FragmentViewHolder holder) {
    
    
        /**部分代码省略 */
        if (!shouldDelayFragmentTransactions()) {
    
    
            scheduleViewAttach(fragment, container);
            mFragmentManager.beginTransaction()
                            .add(fragment, "f" + holder.getItemId())
                            .setMaxLifecycle(fragment, STARTED).commitNow();
            mFragmentMaxLifecycleEnforcer.updateFragmentMaxLifecycle(false);
        }

        /**部分代码省略 */
    }

    /**部分代码省略 */
    @Override
    public long getItemId(int position) {
    
    
        return position;
    }
}

はいFragment追加しますTAG"f" + holder.getItemId()

例外処理

  • 初期化中にクラッシュが発生しました。
Fragment HomeFragment{
    
    b793d14 (e67290fe-7ab1-4b2b-b98c-4e08d146644c)} has not been attached yet.
com.xxx.xxx.xxx.adapter.HomeFragmentStateAdapter.<init>(SourceFile:29)

開発過程で問題が発生した場合は、建設FragmentStateAdapter中にプロジェクトFragmentの状態を判断する必要がありますisAdded()

  • データの更新中に発生したクラッシュ:
Fragment already added

getItemIdリスト内のインデックスではなく、データに関連する値を返すメソッドをオーバーライドします。独自性を表しているのでFragment、再利用できるかどうか。

ViewPager2 スライド監視

public abstract static class OnPageChangeCallback {
    
    
    //当前页面开始滑动时
    public void onPageScrolled(int position, float positionOffset,@Px int positionOffsetPixels) {
    
    
    }
    //当页面被选中时
    public void onPageSelected(int position) {
    
    
    }
    //当前页面滑动状态变动时
    public void onPageScrollStateChanged(@ScrollState int state) {
    
    
    }
}

達成:

var pageChangeCallback = object : ViewPager2.OnPageChangeCallback() {
    
    
  override fun onPageSelected(position: Int) {
    
    
    //需要注意的是postion需要做大于0的判断
  }
}

TabLayout+TabLayoutMediator

TABスライディングやジャンプとの連想を実感できて便利ですViewPager

implementation 'com.google.android.material:material:1.2.0'

推奨されるmaterialバージョン番号はおよそ です1.0.0。それ以外の場合、TAB実装されたカスタム レイアウト幅で問題が発生します。

使用: ViewPager2 公式 Web サイトのサンプル

DiffUtil 部分更新

DiffUtil とその差分アルゴリズム

要約する

この記事では主にViewPager2連携の利用方法Fragmentと利用過程で注意すべき問題点を紹介し、ちなみにTabLayoutOnPageChangeCallbackDiffUtilなどにも言及しています。

記事はここですべて説明します。他に連絡が必要な場合は、メッセージを残してください〜!〜!

著者の記事をもっと読みたい場合は、私の個人ブログと公開アカウントをチェックしてください。
ブックシティの活性化

おすすめ

転載: blog.csdn.net/stven_king/article/details/116781155