web-dev-qa-db-ja.com

ページングライブラリとナビゲーションアーキテクチャコンポーネントを使用して、recyclerviewの状態をフラグメントに保持する

Jetpackの2つのコンポーネント、ページングライブラリとナビゲーションを使用しています。

私の場合、2つのフラグメントがあります:ListMoviesFragment&MovieDetailFragment

特定の距離をスクロールしてrecyclerviewのムービーアイテムをクリックすると、MovieDetailFragmentがアタッチされ、ListMoviesFragmentがバックスタックにあります。次に、戻るボタンを押すと、ListMoviesFragmentがバックスタックから戻されます。

ポイントはスクロールされた位置であり、ListMoviesFramentのアイテムは、アクティビティに初めてアタッチするときとまったく同じようにリセットされます。それで、それを防ぐためにrecyclerviewの状態をどのように保つのですか?

別の方法で、FragmentTransactionを使用してフラグメントを非表示/表示するなど、フラグメント全体の状態を従来の方法で維持する方法(ナビゲーション)

私のサンプルコード:

フラグメントのレイアウト:

<FrameLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
         xmlns:tools="http://schemas.Android.com/tools"
         Android:layout_width="match_parent"
         Android:layout_height="match_parent"
         tools:context="net.karaokestar.app.SplashFragment">

<TextView
        Android:id="@+id/singer_label"
        Android:layout_width="wrap_content" Android:layout_height="wrap_content"
        Android:text="Ca sĩ"
        Android:textColor="@Android:color/white"
        Android:layout_alignParentLeft="true"
        Android:layout_toLeftOf="@+id/btn_game_more"
        Android:layout_centerVertical="true"
        Android:background="@drawable/shape_label"
        Android:layout_marginTop="10dp"
        Android:layout_marginBottom="@dimen/header_margin_bottom_list"
        Android:textStyle="bold"
        Android:padding="@dimen/header_padding_size"
        Android:textAllCaps="true"/>


<androidx.recyclerview.widget.RecyclerView
        Android:id="@+id/list_singers"
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content"/>

Kotlinコードのフラグメント:

    package net.karaokestar.app
    import Android.os.Bundle
    import Android.view.LayoutInflater
    import Android.view.View
    import Android.view.ViewGroup
    import androidx.fragment.app.Fragment
    import androidx.lifecycle.LiveData
    import androidx.lifecycle.Observer
    import androidx.navigation.fragment.findNavController
    import androidx.paging.LivePagedListBuilder
    import androidx.paging.PagedList
    import androidx.recyclerview.widget.LinearLayoutManager
    import kotlinx.Android.synthetic.main.fragment_splash.*
    import net.karaokestar.app.home.HomeSingersAdapter
    import net.karaokestar.app.home.HomeSingersRepository

    class SplashFragment : Fragment() {

        override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View? {
            // Inflate the layout for this fragment
            return inflater.inflate(R.layout.fragment_splash, container, false)
        }

        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            val singersAdapter = HomeSingersAdapter()
            singersAdapter.setOnItemClickListener{
findNavController().navigate(SplashFragmentDirections.actionSplashFragmentToSingerFragment2())
}
            list_singers.layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
            list_singers.setHasFixedSize(true)
            list_singers.adapter = singersAdapter

            getSingersPagination().observe(viewLifecycleOwner, Observer {
                singersAdapter.submitList(it)
            })
        }

        fun getSingersPagination() : LiveData<PagedList<Singer>> {
            val repository = HomeSingersRepository()
            val pagedListConfig = PagedList.Config.Builder().setEnablePlaceholders(true)
                .setPageSize(Configurations.SINGERS_PAGE_SIZE).setPrefetchDistance(Configurations.SINGERS_PAGE_SIZE).build()

            return LivePagedListBuilder(repository, pagedListConfig).build()
        }
    }
13
Slim_user71169

フラグメントが再び戻るたびに、新しいPagedListを取得します。これにより、アダプターに新しいデータが表示されるため、リサイクラービューが一番上に移動します。

PagedListの作成をviewModelに移動して、新しいPage1Listを作成する代わりに既存のPagedListを返すようにチェックすると、問題が解決します。もちろん、アプリによっては新しいPagedListを作成する必要がある場合がありますが、完全に制御できます。 (例:プルして更新するとき、ユーザーが検索するデータを入力するとき...)

1
Lạng Hoàng

これらの問題に対処する際の注意事項:

  1. OSにビューの状態の復元を自動的に処理させるには、IDを指定する必要があります。私はそうではないと思います(データをバインドするためにRecyclerViewを識別する必要があるためです)。

  2. 正しいFragmentManagerを使用する必要があります。ネストされたフラグメントで同じ問題があり、ChildFragmentManagerを使用するだけで済みました。

  3. SaveInstanceState()はアクティビティによってトリガーされます。アクティビティが存続している限り、呼び出されません。

  4. ViewModelを使用して状態を保持し、onViewCreated()で復元できます。

最後に、ナビゲーションコントローラは、目的地に移動するたびにフラグメントの新しいインスタンスを作成します。明らかに、これは永続的な状態を維持する場合にはうまく機能しません。公式の修正があるまで、タブごとに異なるバックスタックをアタッチ/デタッチすることをサポートする次の回避策を確認できます。 BottomNavigationViewを使用しない場合でも、それを使用してアタッチ/デタッチメカニズムを実装できます。

https://github.com/googlesamples/Android-architecture-components/blob/27c4045aa0e40d402bbbde16d7ae0c9822a34447/NavigationAdvancedSample/app/src/main/Java/com/example/Android/navigationadvancedsample/NavigationExtensions.kt

0
Vaios

遅くなってすみません。私は同様の問題を抱えていて、解決策を見つけました(おそらく最善ではありません)。私の場合、方向の変更を適用しました。保存して取得する必要があるのは、LayoutManagerの状態とアダプターの最後のキーです。

RecyclerViewを表示しているアクティビティ/フラグメントで、2つの変数を宣言します。

private Parcelable layoutState;
private int lastKey;

OnSaveInstanceStateで:

@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
    super.onSaveInstanceState(outState);
    if(adapter != null) {
        lastKey = (Integer)adapter.getCurrentList().getLastKey();
        outState.putInt("lastKey",lastKey);
        outState.putParcelable("layoutState",dataBinding.customRecyclerView
        .getLayoutManager().onSaveInstanceState());
    }      
 }

OnCreate:

 @Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    //.... setContentView etc...

    if(savedInstanceState != null) {
        layoutState = savedInstanceState.getParcelable("layoutState");
        lastKey = savedInstanceState.getInt("lastKey",0);
    }

    dataBinding.customRecyclerView
   .addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrollStateChanged(@NonNull RecyclerView   recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
        }

        @Override
        public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            if(lastKey != 0) {
                dataBinding.customRecyclerView.scrollToPosition(lastKey);
                Log.d(LOG_TAG, "list restored; last list position is: " + 
                ((Integer)adapter.getCurrentList().getLastKey()));
                lastKey = 0;
                if(layoutState != null) {
                    dataBinding.customRecyclerView.getLayoutManager()
                    .onRestoreInstanceState(layoutState);
                    layoutState = null;
                }
            }
        }


    });
}

以上です。これで、RecyclerViewが正しく復元されます。

0
Ignacio Garcia