Tenho uma pergunta sobre o RecyclerView.State do Android .
Estou usando um RecyclerView, como posso usar e vinculá-lo ao RecyclerView.State?
Meu objetivo é salvar a posição de rolagem do RecyclerView .
Tenho uma pergunta sobre o RecyclerView.State do Android .
Estou usando um RecyclerView, como posso usar e vinculá-lo ao RecyclerView.State?
Meu objetivo é salvar a posição de rolagem do RecyclerView .
Respostas:
Como você planeja salvar a última posição salva com RecyclerView.State
?
Você sempre pode contar com o bom estado de salvamento. Estenda RecyclerView
e substitua onSaveInstanceState () e onRestoreInstanceState()
:
@Override
protected Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
LayoutManager layoutManager = getLayoutManager();
if(layoutManager != null && layoutManager instanceof LinearLayoutManager){
mScrollPosition = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition();
}
SavedState newState = new SavedState(superState);
newState.mScrollPosition = mScrollPosition;
return newState;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
super.onRestoreInstanceState(state);
if(state != null && state instanceof SavedState){
mScrollPosition = ((SavedState) state).mScrollPosition;
LayoutManager layoutManager = getLayoutManager();
if(layoutManager != null){
int count = layoutManager.getItemCount();
if(mScrollPosition != RecyclerView.NO_POSITION && mScrollPosition < count){
layoutManager.scrollToPosition(mScrollPosition);
}
}
}
}
static class SavedState extends android.view.View.BaseSavedState {
public int mScrollPosition;
SavedState(Parcel in) {
super(in);
mScrollPosition = in.readInt();
}
SavedState(Parcelable superState) {
super(superState);
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(mScrollPosition);
}
public static final Parcelable.Creator<SavedState> CREATOR
= new Parcelable.Creator<SavedState>() {
@Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
A partir do recyclerview:1.2.0-alpha02
lançamento StateRestorationPolicy
foi introduzido. Pode ser uma abordagem melhor para o problema em questão.
Este tópico foi abordado no artigo médio de desenvolvedores Android .
Além disso, @ rubén-viguera compartilhou mais detalhes na resposta abaixo. https://stackoverflow.com/a/61609823/892500
Se você estiver usando LinearLayoutManager, ele vem com api linearLayoutManagerInstance.onSaveInstanceState () e restore api linearLayoutManagerInstance.onRestoreInstanceState (...)
Com isso, você pode salvar o pacote devolvido em seu outState. por exemplo,
outState.putParcelable("KeyForLayoutManagerState", linearLayoutManagerInstance.onSaveInstanceState());
e restaure a posição de restauração com o estado que você salvou. por exemplo,
Parcelable state = savedInstanceState.getParcelable("KeyForLayoutManagerState");
linearLayoutManagerInstance.onRestoreInstanceState(state);
Para encerrar, seu código final será semelhante a
private static final String BUNDLE_RECYCLER_LAYOUT = "classname.recycler.layout";
/**
* This is a method for Fragment.
* You can do the same in onCreate or onRestoreInstanceState
*/
@Override
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
if(savedInstanceState != null)
{
Parcelable savedRecyclerLayoutState = savedInstanceState.getParcelable(BUNDLE_RECYCLER_LAYOUT);
recyclerView.getLayoutManager().onRestoreInstanceState(savedRecyclerLayoutState);
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(BUNDLE_RECYCLER_LAYOUT, recyclerView.getLayoutManager().onSaveInstanceState());
}
Editar: Você também pode usar as mesmas apis com o GridLayoutManager, pois é uma subclasse de LinearLayoutManager. Obrigado @wegsehen pela sugestão.
Edit: Lembre-se, se você também estiver carregando dados em um thread de segundo plano, você precisará fazer uma chamada para onRestoreInstanceState dentro de seu método onPostExecute / onLoadFinished para que a posição seja restaurada após a mudança de orientação, por exemplo
@Override
protected void onPostExecute(ArrayList<Movie> movies) {
mLoadingIndicator.setVisibility(View.INVISIBLE);
if (movies != null) {
showMoviePosterDataView();
mDataAdapter.setMovies(movies);
mRecyclerView.getLayoutManager().onRestoreInstanceState(mSavedRecyclerLayoutState);
} else {
showErrorMessage();
}
}
RecyclerView
mas não se você tiver um ViewPager
com um RecycerView
em cada página.
onCreateView
, mas após o carregamento dos dados. Veja a resposta de @Farmaker aqui.
Loja
lastFirstVisiblePosition = ((LinearLayoutManager)rv.getLayoutManager()).findFirstCompletelyVisibleItemPosition();
Restaurar
((LinearLayoutManager) rv.getLayoutManager()).scrollToPosition(lastFirstVisiblePosition);
e se isso não funcionar, tente
((LinearLayoutManager) rv.getLayoutManager()).scrollToPositionWithOffset(lastFirstVisiblePosition,0)
Armazenar onPause()
e restauraronResume()
lastFirstVisiblePosition
para 0
após restaurá-lo. Este caso é útil quando você tem um fragmento / atividade que carrega dados diferentes em um RecyclerView
, por exemplo, um aplicativo explorador de arquivos.
SwipeRefreshLayout
?
RecyclerView
Eu queria salvar a posição de rolagem do Recycler View ao navegar para fora da minha atividade de lista e clicar no botão Voltar para navegar de volta. Muitas das soluções fornecidas para esse problema eram muito mais complicadas do que o necessário ou não funcionavam para minha configuração, então pensei em compartilhar minha solução.
Primeiro salve o estado da instância em onPause, como muitos mostraram. Acho que vale a pena enfatizar aqui que esta versão de onSaveInstanceState é um método da classe RecyclerView.LayoutManager.
private LinearLayoutManager mLayoutManager;
Parcelable state;
@Override
public void onPause() {
super.onPause();
state = mLayoutManager.onSaveInstanceState();
}
A chave para fazer isso funcionar corretamente é certificar-se de chamar onRestoreInstanceState depois de conectar seu adaptador, como alguns indicaram em outros threads. No entanto, a chamada do método real é muito mais simples do que muitos indicaram.
private void someMethod() {
mVenueRecyclerView.setAdapter(mVenueAdapter);
mLayoutManager.onRestoreInstanceState(state);
}
I Defina as variáveis em onCreate (), salve a posição de rolagem em onPause () e defina a posição de rolagem em onResume ()
public static int index = -1;
public static int top = -1;
LinearLayoutManager mLayoutManager;
@Override
public void onCreate(Bundle savedInstanceState)
{
//Set Variables
super.onCreate(savedInstanceState);
cRecyclerView = ( RecyclerView )findViewById(R.id.conv_recycler);
mLayoutManager = new LinearLayoutManager(this);
cRecyclerView.setHasFixedSize(true);
cRecyclerView.setLayoutManager(mLayoutManager);
}
@Override
public void onPause()
{
super.onPause();
//read current recyclerview position
index = mLayoutManager.findFirstVisibleItemPosition();
View v = cRecyclerView.getChildAt(0);
top = (v == null) ? 0 : (v.getTop() - cRecyclerView.getPaddingTop());
}
@Override
public void onResume()
{
super.onResume();
//set recyclerview position
if(index != -1)
{
mLayoutManager.scrollToPositionWithOffset( index, top);
}
}
Você não precisa mais salvar e restaurar o estado sozinho. Se você definir um ID exclusivo em xml e recyclerView.setSaveEnabled (true) (true por padrão), o sistema fará isso automaticamente. Aqui está mais sobre isso: http://trickyandroid.com/saving-android-view-state-correctly/
Todos os gerenciadores de layout incluídos na biblioteca de suporte já sabem como salvar e restaurar a posição de rolagem.
A posição de rolagem é restaurada na primeira passagem de layout, o que acontece quando as seguintes condições são atendidas:
RecyclerView
RecyclerView
Normalmente você define o gerenciador de layout em XML, então tudo que você precisa fazer agora é
RecyclerView
Você pode fazer isso a qualquer momento, não está restrito a Fragment.onCreateView
ou Activity.onCreate
.
Exemplo: certifique-se de que o adaptador esteja conectado sempre que os dados forem atualizados.
viewModel.liveData.observe(this) {
// Load adapter.
adapter.data = it
if (list.adapter != adapter) {
// Only set the adapter if we didn't do it already.
list.adapter = adapter
}
}
A posição de rolagem salva é calculada a partir dos seguintes dados:
Portanto, você pode esperar uma pequena incompatibilidade, por exemplo, se seus itens tiverem dimensões diferentes na orientação retrato e paisagem.
O RecyclerView
/ ScrollView
/ qualquer coisa precisa ter um android:id
para que seu estado seja salvo.
LinearLayoutManager.SavedState
: cs.android.com/androidx/platform/frameworks/support/+/…
Aqui está minha abordagem para salvá-los e manter o estado de visualização de reciclagem em um fragmento. Primeiro, adicione mudanças de configuração em sua atividade pai conforme o código abaixo.
android:configChanges="orientation|screenSize"
Então, em seu Fragment, declare esses métodos de instância
private Bundle mBundleRecyclerViewState;
private Parcelable mListState = null;
private LinearLayoutManager mLinearLayoutManager;
Adicione o código abaixo em seu método @onPause
@Override
public void onPause() {
super.onPause();
//This used to store the state of recycler view
mBundleRecyclerViewState = new Bundle();
mListState =mTrailersRecyclerView.getLayoutManager().onSaveInstanceState();
mBundleRecyclerViewState.putParcelable(getResources().getString(R.string.recycler_scroll_position_key), mListState);
}
Adicione o método onConfigartionChanged como abaixo.
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
//When orientation is changed then grid column count is also changed so get every time
Log.e(TAG, "onConfigurationChanged: " );
if (mBundleRecyclerViewState != null) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
mListState = mBundleRecyclerViewState.getParcelable(getResources().getString(R.string.recycler_scroll_position_key));
mTrailersRecyclerView.getLayoutManager().onRestoreInstanceState(mListState);
}
}, 50);
}
mTrailersRecyclerView.setLayoutManager(mLinearLayoutManager);
}
Esta abordagem resolverá seu problema. se você tiver um gerenciador de layout diferente, então apenas substitua o gerenciador de layout superior.
É assim que eu restauro a posição do RecyclerView com GridLayoutManager após a rotação quando você precisa recarregar dados da Internet com AsyncTaskLoader.
Faça uma variável global de Parcelable e GridLayoutManager e uma string final estática:
private Parcelable savedRecyclerLayoutState;
private GridLayoutManager mGridLayoutManager;
private static final String BUNDLE_RECYCLER_LAYOUT = "recycler_layout";
Salvar o estado do gridLayoutManager em onSaveInstance ()
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(BUNDLE_RECYCLER_LAYOUT,
mGridLayoutManager.onSaveInstanceState());
}
Restaurar em onRestoreInstanceState
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
//restore recycler view at same position
if (savedInstanceState != null) {
savedRecyclerLayoutState = savedInstanceState.getParcelable(BUNDLE_RECYCLER_LAYOUT);
}
}
Então, quando o carregador obtém dados da Internet, você restaura a posição de reciclagem de visualização em onLoadFinished ()
if(savedRecyclerLayoutState!=null){
mGridLayoutManager.onRestoreInstanceState(savedRecyclerLayoutState);
}
.. claro que você tem que instanciar o gridLayoutManager dentro de onCreate. Felicidades
Eu tive o mesmo requisito, mas as soluções aqui não me ajudaram muito, devido à fonte de dados para o recyclerView.
Eu estava extraindo o estado LinearLayoutManager de RecyclerViews em onPause ()
private Parcelable state;
Public void onPause() {
state = mLinearLayoutManager.onSaveInstanceState();
}
Parcelable state
é salvo em onSaveInstanceState(Bundle outState)
e extraído novamente em onCreate(Bundle savedInstanceState)
, quando savedInstanceState != null
.
No entanto, o adaptador recyclerView é preenchido e atualizado por um objeto ViewModel LiveData retornado por uma chamada DAO para um banco de dados Room.
mViewModel.getAllDataToDisplay(mSelectedData).observe(this, new Observer<List<String>>() {
@Override
public void onChanged(@Nullable final List<String> displayStrings) {
mAdapter.swapData(displayStrings);
mLinearLayoutManager.onRestoreInstanceState(state);
}
});
Por fim, descobri que restaurar o estado da instância diretamente após os dados serem definidos no adaptador manteria o estado de rolagem nas rotações.
Suspeito que isso seja porque o LinearLayoutManager
estado que eu restaurei estava sendo sobrescrito quando os dados foram retornados pela chamada do banco de dados ou o estado restaurado não fazia sentido em um LinearLayoutManager 'vazio'.
Se os dados do adaptador estiverem disponíveis diretamente (ou seja, não contigentes em uma chamada de banco de dados), a restauração do estado da instância no LinearLayoutManager poderá ser feita após o adaptador ser configurado no recyclerView.
A distinção entre os dois cenários me sustentou por muito tempo.
No Android API Nível 28, simplesmente garanto que configurei meu LinearLayoutManager
e RecyclerView.Adapter
no meu Fragment#onCreateView
método, e tudo Just Worked ™ ️. Eu não precisava fazer nada onSaveInstanceState
ou onRestoreInstanceState
trabalhar.
A resposta de Eugen Pechanec explica por que isso funciona.
A partir da versão 1.2.0-alpha02 da biblioteca androidx recyclerView, agora é gerenciado automaticamente. Basta adicioná-lo com:
implementation "androidx.recyclerview:recyclerview:1.2.0-alpha02"
E use:
adapter.stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY
O enum StateRestorationPolicy tem 3 opções:
Observe que, no momento desta resposta, a biblioteca recyclerView ainda está em alpha03, mas a fase alpha não é adequada para fins de produção .
Para mim, o problema era que eu configurava um novo gerenciador de layout toda vez que trocava meu adaptador, perdendo a posição de rolagem no recyclerView.
Gostaria apenas de compartilhar a situação recente que encontro com o RecyclerView. Espero que qualquer pessoa com o mesmo problema se beneficie.
Meu requisito de projeto: Portanto, tenho um RecyclerView que lista alguns itens clicáveis em minha atividade principal (Atividade-A). Quando o item é clicado, uma nova atividade é exibida com os detalhes do item (Atividade-B).
Eu implementei no Manifesto arquivo que a Activity-A é pai da Activity-B, dessa forma, tenho um botão voltar ou home na ActionBar da Activity-B
Problema: toda vez que pressiono o botão Voltar ou Início na ActionBar da Activity-B, a Activity-A entra em todo o ciclo de vida da atividade começando em onCreate ()
Embora eu tenha implementado um onSaveInstanceState () salvando a Lista do Adaptador do RecyclerView, quando a Activity-A inicia seu ciclo de vida, o saveInstanceState é sempre nulo no método onCreate ().
Pesquisando mais na internet, me deparei com o mesmo problema, mas a pessoa percebeu que no botão Voltar ou Início abaixo do dispositivo Anroid (botão Voltar / Início padrão), a Atividade-A não entra no Ciclo de Vida da Atividade.
Solução:
Eu habilitei o botão home ou voltar na Activity-B
No método onCreate (), adicione esta linha supportActionBar? .SetDisplayHomeAsUpEnabled (true)
No método overide fun onOptionsItemSelected (), verifiquei o item.ItemId no qual o item foi clicado com base no id. O Id para o botão Voltar é
android.R.id.home
Em seguida, implemente uma chamada de função finish () dentro de android.R.id.home
Isso encerrará a Atividade-B e trará o Acitivy-A sem passar por todo o ciclo de vida.
Para minhas necessidades, esta é a melhor solução até agora.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_show_project)
supportActionBar?.title = projectName
supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when(item?.itemId){
android.R.id.home -> {
finish()
}
}
return super.onOptionsItemSelected(item)
}
Eu tive o mesmo problema com uma pequena diferença, eu estava usando Databinding , e estava configurando o adaptador recyclerView e o layoutManager nos métodos de binding.
O problema era que a vinculação de dados estava acontecendo depois de onResume, então estava redefinindo meu layoutManager depois de onResume e isso significa que estava rolando de volta para o lugar original todas as vezes. Portanto, quando parei de definir layoutManager nos métodos do adaptador de ligação de dados, ele voltou a funcionar bem.
No meu caso, eu estava configurando o RecyclerView layoutManager
tanto em XML quanto em onViewCreated
. Remover a atribuição no onViewCreated
consertou.
with(_binding.list) {
// layoutManager = LinearLayoutManager(context)
adapter = MyAdapter().apply {
listViewModel.data.observe(viewLifecycleOwner,
Observer {
it?.let { setItems(it) }
})
}
}
Activity.java:
public RecyclerView.LayoutManager mLayoutManager;
Parcelable state;
mLayoutManager = new LinearLayoutManager(this);
// Inside `onCreate()` lifecycle method, put the below code :
if(state != null) {
mLayoutManager.onRestoreInstanceState(state);
}
@Override
protected void onResume() {
super.onResume();
if (state != null) {
mLayoutManager.onRestoreInstanceState(state);
}
}
@Override
protected void onPause() {
super.onPause();
state = mLayoutManager.onSaveInstanceState();
}
Por que estou usando o OnSaveInstanceState()
in onPause()
significa, embora a mudança para outra atividade onPause seja chamada. Isso salvará a posição do scroll e restaurará a posição quando voltarmos de outra atividade.