Как сохранить позицию 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)