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()
}
}
フラグメントが再び戻るたびに、新しいPagedListを取得します。これにより、アダプターに新しいデータが表示されるため、リサイクラービューが一番上に移動します。
PagedListの作成をviewModelに移動して、新しいPage1Listを作成する代わりに既存のPagedListを返すようにチェックすると、問題が解決します。もちろん、アプリによっては新しいPagedListを作成する必要がある場合がありますが、完全に制御できます。 (例:プルして更新するとき、ユーザーが検索するデータを入力するとき...)
これらの問題に対処する際の注意事項:
OSにビューの状態の復元を自動的に処理させるには、IDを指定する必要があります。私はそうではないと思います(データをバインドするためにRecyclerViewを識別する必要があるためです)。
正しいFragmentManagerを使用する必要があります。ネストされたフラグメントで同じ問題があり、ChildFragmentManagerを使用するだけで済みました。
SaveInstanceState()はアクティビティによってトリガーされます。アクティビティが存続している限り、呼び出されません。
ViewModelを使用して状態を保持し、onViewCreated()で復元できます。
最後に、ナビゲーションコントローラは、目的地に移動するたびにフラグメントの新しいインスタンスを作成します。明らかに、これは永続的な状態を維持する場合にはうまく機能しません。公式の修正があるまで、タブごとに異なるバックスタックをアタッチ/デタッチすることをサポートする次の回避策を確認できます。 BottomNavigationViewを使用しない場合でも、それを使用してアタッチ/デタッチメカニズムを実装できます。
遅くなってすみません。私は同様の問題を抱えていて、解決策を見つけました(おそらく最善ではありません)。私の場合、方向の変更を適用しました。保存して取得する必要があるのは、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が正しく復元されます。