ViewPager2+Fragment 操作上の注意
記事ディレクトリ
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
+ ステートメント サイクルの検出 + キャッシュの最適化
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();
}
}
しかし ではViewPager2
、Fragment
メソッドsetUserVisibleHint
とonHiddenChanged
メソッドは実行されません。
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
ステートメント サイクルでonStart
andメソッドを使用して、遅延読み込みを実行できるようですonResume
。
プリロード
データ要求がonCreateView
またはonStart
インターフェイスのオフスクリーン要求を実行できます。
FragmentStateAdapter
ViewPager2
から継承RecyclerView
、最も可能性が高いのFragmentStateAdapter
は から継承RecyclerView.Adapter
:
public abstract class FragmentStateAdapter extends
RecyclerView.Adapter<FragmentViewHolder> implements StatefulAdapter {
}
onCreateViewHolder
onCreateViewHolder
RecycleVeiw
は の作成に使用される方法です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
onBindViewHolder
RecycleVeiw
は、データ バインディングに使用されるメソッドです。
@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
onViewAttachedToWindow
ViewHolder
ページに表示されるコールバックです。
@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 部分更新
要約する
この記事では主にViewPager2
連携の利用方法Fragment
と利用過程で注意すべき問題点を紹介し、ちなみにTabLayout、OnPageChangeCallback、DiffUtilなどにも言及しています。
記事はここですべて説明します。他に連絡が必要な場合は、メッセージを残してください〜!〜!
著者の記事をもっと読みたい場合は、私の個人ブログと公開アカウントをチェックしてください。