Как сохранить позицию RecyclerView после изменения ориентации, используя Firebase & ChildEventListener?
Я работаю над простым приложением APOD, которое реализует:
-
RecyclerView
-
CardView
-
Firebase
-
Picasso
Приложение захватывает изображения и текст из Firebase
и Firebase Storage
, отображает их в CardView
и устанавливает OnClickListener
для каждого View
. Когда пользователь нажимает на изображение, я открываю новую Activity
через Intent
. Вторая Activity
отображает исходное изображение с кликом и дополнительную информацию об этом.
Я реализовал все это, используя столбец GridLayoutManager
, 1, если пользовательский телефон VERTICAL, 3 столбца, если пользовательский телефон HORIZONTAL.
Проблема, с которой я сталкиваюсь, заключается в том, что я не могу сохранить позицию RecyclerView
при изменении ориентации. Я пробовал все, что мог найти, но никто не работает. Единственный вывод, который я мог придумать, заключается в том, что при ротации я уничтожаю Firebase
ChildEventListener
чтобы избежать утечки памяти, и как только ориентация завершена, Firebase
повторно запрашивает базу данных из-за нового экземпляра ChildEventListener
.
Есть ли способ сохранить позицию RecyclerView
при изменении ориентации? Я не хочу android:configChanges
поскольку он не позволит мне изменить мой макет, и я уже пытался сохранить как Parcelable
, что было неудачно. Я уверен, что это что-то легкое, мне не хватает, но, эй, я новичок в разработке. Любая помощь или предложения по моему коду очень приветствуются. Благодарю!
Ниже приведены мои классы, которые я сократил только до необходимого кода.
Основная деятельность
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);
}
}
}
Ответы
Ответ 1
РЕДАКТИРОВАТЬ:
Наконец, я смог сделать эту работу с использованием нескольких факторов. Если бы кто-то из них был упущен, это просто не сработало бы.
Создайте новый GridLayoutManager
в моей onCreate
gridLayoutManager = new GridLayoutManager(getApplicationContext(), columns);
Сохранить состояние RecyclerView в 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);
}
Включить configChanges
в AndroidManifest.xml
Не удалось RecyclerView
и восстановить состояние RecyclerView
. Мне также пришлось пойти против зерна и изменить мой AndroidManifest.xml
на "android: configChanges =" orientation | screenSize "'
<activity
android:name=".MainActivity"
android:configChanges="orientation|screenSize"
>
Восстановить состояние RecyclerView
в onConfigurationChanged
с помощью Handler
Обработчик был одной из самых важных частей, если он не был включен, он просто не будет работать, и позиция RecyclerView
будет сброшена на 0. Я думаю, что это был недостаток, который возвращался на несколько лет и никогда не был исправлен. Затем я изменил GridLayoutManager
setSpanCount
зависимости от ориентации.
@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);
}
Как я уже сказал, все эти изменения нужно было включить, по крайней мере, для меня. Я не знаю, является ли это наиболее эффективным способом, но, проведя много часов, он работает для меня.
Ответ 2
В то время как активность ротации уничтожается и снова создается заново, это означает, что все ваши объекты были уничтожены и повторно созданы, а также снова переработаны макеты.
Это вы хотите остановить повторное создание активности, которую вы можете использовать android: configChanges, но это плохая практика, а также неправильный путь.
Единственное, что осталось до сих пор, - сохранить позицию повторного использования перед ротацией и получить ее после повторной работы.
//сохранение позиции recyclerview
@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);
}
//для восстановления значения
@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"));
}
}
Надеюсь, они вам помогут.
Ответ 3
Активность воссоздается каждый раз, когда происходит изменение ориентации. Поэтому вам нужно будет сохранить позицию в комплекте, а затем повторно использовать ее, когда вызов onCreate() будет вызван во второй раз.
@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());
}
Возможно, вам захочется взглянуть на ссылку RecyclerView.State() для получения дополнительных сведений о том, как восстановить состояние просмотра ресайклеров.
Ответ 4
На самом деле существует гораздо лучший подход к восстановлению позиции просмотра ресайклеров, когда активность воссоздается, и это с помощью опции менеджера компоновки просмотра ресайклера для хранения и восстановления последней позиции (последнего состояния макета ресайклеров). Взгляните на этот урок.
С помощью нескольких слов вам нужно вызвать методы управления компоновкой onSaveInstanceState() и onRestoreInstanceState (Parcelable state) внутри соответствующих методов активности. В конце вам нужно восстановить состояние менеджера компоновки сразу после того, как вы заполнили адаптер данными.
Если вы посмотрите, например, на реализацию LinearLayoutManager, вы увидите, как этот класс управляет состоянием (store/restore)