Je travaille sur une application APOD simple qui implémente:
RecyclerView
CardView
Firebase
Picasso
L'application récupère les images et le texte de Firebase
et Firebase Storage
, les affiche dans un CardView
et définit un OnClickListener
pour chaque View
. Lorsque l'utilisateur clique sur une image, j'ouvre un nouveau Activity
via un Intent
. Le deuxième Activity
affiche l'image cliquée d'origine et plus d'informations à ce sujet.
J'ai implémenté tout cela en utilisant un GridLayoutManager
, 1 colonne si le téléphone de l'utilisateur est VERTICAL, 3 colonnes si le téléphone de l'utilisateur est HORIZONTAL.
Le problème que j'ai est que je n'arrive pas à enregistrer la position de RecyclerView
sur le changement d'orientation. J'ai essayé toutes les options que j'ai pu trouver, mais aucune ne semble fonctionner. La seule conclusion que j'ai pu trouver, c'est que lors de la rotation, je détruis les Firebase
de ChildEventListener
pour éviter une fuite de mémoire, et une fois l'orientation terminée, Firebase
interroge à nouveau la base de données en raison de la nouvelle instance de ChildEventListener
.
Existe-t-il un moyen de sauvegarder la position de mon RecyclerView
sur le changement d'orientation? Je ne veux pas Android:configChanges
car cela ne me permettra pas de modifier ma disposition, et j'ai déjà essayé d'enregistrer en tant que Parcelable
, ce qui n'a pas réussi. Je suis sûr que c'est quelque chose de facile qui me manque, mais bon, je suis nouveau dans le développement. Toute aide ou suggestion sur mon code est grandement appréciée. Merci!
Vous trouverez ci-dessous mes cours, que je n'ai abrégés qu'au code nécessaire.
MainActivity
public class MainActivity extends AppCompatActivity {
private RecyclerAdapter mRecyclerAdapter;
private DatabaseReference mDatabaseReference;
private RecyclerView mRecyclerView;
private Query query;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
setContentView(R.layout.activity_main);
getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
final int columns = getResources().getInteger(R.integer.gallery_columns);
mDatabaseReference = FirebaseDatabase.getInstance().getReference();
query = mDatabaseReference.child("apod").orderByChild("date");
mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
mRecyclerView.setHasFixedSize(true);
mRecyclerView.setLayoutManager(new GridLayoutManager(this, columns));
mRecyclerAdapter = new RecyclerAdapter(this, query);
mRecyclerView.setAdapter(mRecyclerAdapter);
}
@Override
public void onDestroy() {
mRecyclerAdapter.cleanupListener();
}
}
RecyclerAdapter
public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.ApodViewHolder> {
private final Context mContext;
private final ChildEventListener mChildEventListener;
private final Query mDatabaseReference;
private final List<String> apodListIds = new ArrayList<>();
private final List<Apod> apodList = new ArrayList<>();
public RecyclerAdapter(final Context context, Query ref) {
mContext = context;
mDatabaseReference = ref;
ChildEventListener childEventListener = new ChildEventListener() {
@Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
int oldListSize = getItemCount();
Apod apod = dataSnapshot.getValue(Apod.class);
//Add data and IDs to the list
apodListIds.add(dataSnapshot.getKey());
apodList.add(apod);
//Update the RecyclerView
notifyItemInserted(oldListSize - getItemCount() - 1);
}
@Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
}
@Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
String apodKey = dataSnapshot.getKey();
int apodIndex = apodListIds.indexOf(apodKey);
if (apodIndex > -1) {
// Remove data and IDs from the list
apodListIds.remove(apodIndex);
apodList.remove(apodIndex);
// Update the RecyclerView
notifyDataSetChanged();
}
}
@Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
}
@Override
public void onCancelled(DatabaseError databaseError) {
}
};
ref.addChildEventListener(childEventListener);
mChildEventListener = childEventListener;
}
@Override
public int getItemCount() {
return apodList.size();
}
public void cleanupListener() {
if (mChildEventListener != null) {
mDatabaseReference.removeEventListener(mChildEventListener);
}
}
}
MODIFIER:
J'ai finalement pu faire ce travail en utilisant plusieurs facteurs. Si l'un de ces éléments était omis, cela ne fonctionnerait tout simplement pas.
Créer un nouveau GridLayoutManager
dans mon onCreate
gridLayoutManager = new GridLayoutManager(getApplicationContext(), columns);
Enregistrer l'état RecyclerView dans onPause
private final String KEY_RECYCLER_STATE = "recycler_state";
private static Bundle mBundleRecyclerViewState;
private Parcelable mListState = null;`
@Override
protected void onPause() {
super.onPause();
mBundleRecyclerViewState = new Bundle();
mListState = mRecyclerView.getLayoutManager().onSaveInstanceState();
mBundleRecyclerViewState.putParcelable(KEY_RECYCLER_STATE, mListState);
}
Incluez configChanges
dans le AndroidManifest.xml
L'enregistrement et la restauration de l'état RecyclerView
n'étaient pas suffisants. J'ai aussi dû aller à contre-courant et changer mon AndroidManifest.xml
vers 'Android: configChanges = "orientation | screenSize"'
<activity
Android:name=".MainActivity"
Android:configChanges="orientation|screenSize"
>
Restaurer l'état RecyclerView
dans onConfigurationChanged
en utilisant un Handler
Le gestionnaire était l'une des parties les plus importantes, s'il n'est pas inclus, il ne fonctionnera tout simplement pas et la position du RecyclerView
sera réinitialisée à 0. Je suppose que cela a été une faille remontant à quelques années, et n'a jamais été corrigé. J'ai ensuite changé le GridLayoutManager
setSpanCount
en fonction de l'orientation.
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
columns = getResources().getInteger(R.integer.gallery_columns);
if (mBundleRecyclerViewState != null) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
mListState = mBundleRecyclerViewState.getParcelable(KEY_RECYCLER_STATE);
mRecyclerView.getLayoutManager().onRestoreInstanceState(mListState);
}
}, 50);
}
// Checks the orientation of the screen
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
gridLayoutManager.setSpanCount(columns);
} else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
gridLayoutManager.setSpanCount(columns);
}
mRecyclerView.setLayoutManager(gridLayoutManager);
}
Comme je l'ai dit, tous ces changements devaient être inclus, au moins pour moi. Je ne sais pas si c'est le moyen le plus efficace, mais après avoir passé de nombreuses heures, cela fonctionne pour moi.
Alors que l'activité de rotation est détruite et recréée à nouveau, cela signifie que tous vos objets sont détruits et recréés et que la mise en page est à nouveau redessinée.
Si vous voulez arrêter la recréation d'activité, vous pouvez utiliser Android: configChanges mais c'est une mauvaise pratique et pas la bonne façon.
Il ne reste plus qu'à sauvegarder la position de la vue de recyclage avant la rotation et à l'obtenir après la recréation de l'activité.
// pour sauvegarder la position de recyclage
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
// Save UI state changes to the savedInstanceState.
// This bundle will be passed to onCreate if the process is
// killed and restarted.
savedInstanceState.putInt("position", mRecyclerView.getAdapterPosition()); // get current recycle view position here.
super.onSaveInstanceState(savedInstanceState);
}
// pour restaurer la valeur
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
setContentView(R.layout.activity_main);
getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
final int columns = getResources().getInteger(R.integer.gallery_columns);
mDatabaseReference = FirebaseDatabase.getInstance().getReference();
query = mDatabaseReference.child("apod").orderByChild("date");
mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
mRecyclerView.setHasFixedSize(true);
mRecyclerView.setLayoutManager(new GridLayoutManager(this, columns));
mRecyclerAdapter = new RecyclerAdapter(this, query);
mRecyclerView.setAdapter(mRecyclerAdapter);
if(savedInstanceState != null){
// scroll to existing position which exist before rotation.
mRecyclerView.scrollToPosition(savedInstanceState.getInt("position"));
}
}
J'espère que cela vous aidera.
En fait, il existe une bien meilleure approche pour restaurer la position de la vue du recycleur lors de la recréation de l'activité, et cela en utilisant l'option de gestionnaire de disposition de la vue du recycleur pour stocker et restaurer la dernière position (le dernier état de la disposition de la vue du recycleur). Jetez un oeil à ce tutoriel .
En quelques mots, vous devez appeler les méthodes du gestionnaire de disposition onSaveInstanceState () et onRestoreInstanceState (état Parcelable) dans les méthodes d'activité correspondantes. À la fin, vous devez restaurer l'état du gestionnaire de disposition juste après avoir rempli l'adaptateur avec des données.
Si vous jetez un œil à l'intérieur de l'implémentation de LinearLayoutManager par exemple, vous verrez comment cette classe gère l'état (stockage/restauration)
L'activité est recréée chaque fois qu'il y a un changement d'orientation. Vous devrez donc enregistrer la position dans le bundle, puis la réutiliser lorsque onCreate () sera appelé la deuxième fois.
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
int recyclerViewPosition;
if(savedInstanceState != null){
recyclerViewPosition = savedInstanceState.getInt(RECYCLER_VIEW_POSITION);
}
mRecyclerView.scrollToPosition(recyclerViewPosition);
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
//save the current recyclerview position
outState.putInt(RECYCLER_VIEW_POSITION, mRecyclerView.getAdapterPosition());
}
Vous voudrez peut-être jeter un œil à RecyclerView.State () link pour plus d'options sur la façon de restaurer l'état de la vue du recycleur.