J'utilise 2 composants du jetpack: la bibliothèque de pagination et la navigation.
Dans mon cas, j'ai 2 fragments: ListMoviesFragment & MovieDetailFragment
lorsque je fais défiler une certaine distance et clique sur un élément de film de la vue de recyclage, MovieDetailFragment est attaché et ListMoviesFragment est dans le backstack. Ensuite, j'appuie sur le bouton de retour, le ListMoviesFragment est ramené du backstack.
Le point est en position de défilement et les éléments de ListMoviesFrament sont réinitialisés exactement comme la première fois qu'ils sont attachés à son activité. Alors, comment garder les états de recyclage pour éviter cela?
D'une autre manière, comment conserver des états de fragment entier comme cacher/montrer un fragment avec FragmentTransaction de manière traditionnelle mais de manière moderne (navigation)
Mes exemples de codes:
disposition des fragments:
<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"/>
Code de kotlin de fragment:
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()
}
}
Chaque fois que le fragment revient, il obtient la nouvelle PagedList, cela provoque l'adaptateur à présenter de nouvelles données, vous verrez donc la vue du recycleur se déplacer vers le haut.
En déplaçant la création de PagedList vers viewModel et en vérifiant de renvoyer l'existant PagedList au lieu d'en créer un nouveau, le problème sera résolu. Bien sûr, cela dépend de l'activité de votre application, la création d'une nouvelle PagedList peut nécessiter, mais vous pouvez la contrôler complètement. (ex: quand tirer pour rafraîchir, quand l'utilisateur entre des données pour rechercher ...)
Éléments à garder à l'esprit lors de la résolution de ces problèmes:
Afin de permettre au système d'exploitation de gérer automatiquement la restauration de l'état d'une vue, vous devez fournir un identifiant. Je pense que ce n'est pas le cas (car vous devez identifier le RecyclerView pour lier les données).
Vous devez utiliser le FragmentManager correct. J'ai eu le même problème avec un fragment imbriqué et tout ce que j'avais à faire était d'utiliser ChildFragmentManager.
SaveInstanceState () est déclenchée par l'activité. Tant que l'activité est vivante, elle ne sera pas appelée.
Vous pouvez utiliser ViewModels pour conserver l'état et le restaurer dans onViewCreated ()
Enfin, le contrôleur de navigation crée une nouvelle instance d'un fragment chaque fois que nous naviguons vers une destination. Évidemment, cela ne fonctionne pas bien avec le maintien d'un état persistant. Jusqu'à ce qu'il y ait un correctif officiel, vous pouvez vérifier la solution de contournement suivante qui prend en charge l'attachement/détachement ainsi que différentes backstacks par onglet. Même si vous n'utilisez pas BottomNavigationView, vous pouvez l'utiliser pour implémenter un mécanisme d'attachement/détachement.
Désolé d'être en retard. J'ai eu un problème similaire et j'ai trouvé une solution (peut-être pas la meilleure). Dans mon cas, j'ai adapté les changements d'orientation. Ce dont vous avez besoin est d'enregistrer et de récupérer l'état LayoutManager ET la dernière clé de l'adaptateur.
Dans l'activité/le fragment qui montre votre RecyclerView, déclarez 2 variables:
private Parcelable layoutState;
private int lastKey;
Dans 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());
}
}
Dans 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;
}
}
}
});
}
Et c'est tout. Votre RecyclerView devrait maintenant se restaurer correctement.