如何在方向更改后保留RecyclerView的位置,同时使用Firebase和ChildEventListener?



我正在研究一个实现的简单apod应用程序:

  • RecyclerView
  • CardView
  • Firebase
  • Picasso

该应用程序捕获FirebaseFirebase Storage中的图像和文本,在CardView中显示它们,并将OnClickListener设置为每个View。当用户单击图像时,我通过Intent打开新的Activity。第二个Activity显示原始单击图像以及有关它的更多信息。

我已经使用GridLayoutManager、1列实现了这一切,如果用户的手机是垂直的,如果用户的手机是水平的3列。

我遇到的问题是,我似乎无法保存RecyclerView在方向更改上的位置。我已经尝试了我可以找到的每个选项,但似乎没有任何选择。我唯一可以得出的结论是,在旋转时,我正在销毁FirebaseChildEventListener以避免内存泄漏,并且一旦取向完成,Firebase由于ChildEventListener的新实例而重新征服数据库。

有什么办法可以保存RecyclerView在方向更改上的位置?我不想要android:configChanges,因为它不会让我更改布局,并且我已经尝试保存为Parcelable,这是不成功的。我敢肯定,这很容易我缺少,但是,我是新手开发的东西。对我的代码的任何帮助或建议都非常感谢。谢谢!

以下是我的课程,我只缩短了必要的代码。

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);
    }
  }
}

编辑:

我终于能够使用多个因素进行这项工作。如果将其中的任何一个遗漏了,它根本无法使用。

在我的OnCreate

中创建新的GridLayoutManager
gridLayoutManager = new GridLayoutManager(getApplicationContext(), columns);

在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);
}

AndroidManifest.xml

中包括configChanges

保存和恢复RecyclerView状态还不够。我还必须违反谷物,然后将我的AndroidManifest.xml更改为'Android:configchanges =&quot'entrientation | screensize&quort''

<activity
    android:name=".MainActivity"
    android:configChanges="orientation|screenSize">

使用Handler

onConfigurationChanged中还原RecyclerView状态

处理程序是最重要的部分之一,如果不包括在内,它根本不起作用,RecyclerView的位置将重置为0。我猜这是一个缺陷,从来没有固定的。然后,我根据方向更改了GridLayoutManagersetSpanCount

@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);
}

就像我说的那样,至少对我来说,所有这些更改都需要包括在内。我不知道这是否是最有效的方法,但是花了很多小时,它对我有用。

,而旋转活动被破坏并重新销毁 - 再次创建,这意味着您的所有对象都被破坏和重新创建,并再次进行布局。

您想停止重新创建活动,您可以使用 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"));
   }  
  }

希望这些对您有帮助。

实际上,当活动重新创建活动时,有更好的方法可以恢复回收器视图位置,这是通过使用回收器查看布局管理器选项以存储和还原最后一个位置(最后一个回收器视图布局状态)。看看这个教程。

用几个单词,您需要在相应的活动方法中调用布局管理器方法OnSaveInstanceState()和OnrestoreInstancestate(包裹状态)。最后,在使用数据填充适配器后,您必须立即恢复布局管理器状态。

例如,如果您在LinearlayOutManager的实现中查看,则您将看到此类如何管理状态(Store/Restore)

i最后做了。没有黑客或更改subtest.xml。经过长时间的阅读,我找到了关键:

因此,如果您在每个屏幕旋转上不同步加载数据然后将其张贴到主线程中,通过循环队列,您始终会因失去以前的滚动位置而最终。实际上,任何

handler.post(this :: setListItems);

也会将其打破,因为此操作将发布到队列并在列表开始布局后交付。

因此,如果要还原回收器视图状态,则必须使用处理程序或缓存数据。这是代码样本(Androidx片段)。

private static String STATE = "STATE";
    
    // the recycler view manager
    private GridLayoutManager layoutManager;
    
    @Override
    public void onSaveInstanceState(@NonNull Bundle outState) {
        super.onSaveInstanceState(outState);
        // save recycler view layout
        if(layoutManager != null) {
            outState.putParcelable(STATE, layoutManager.onSaveInstanceState());
        }
    }
    
    
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
                             
        ...
        
        loadDataAsync(savedInstanceState);
        
    
        return view;
    }
    
    private void loadDataAsync(Bundle savedInstanceState){
    
        repository.getAll(new IMyListener()){
                @Override
                public void getAll(List<String> listResult) {
                        reyclerViewFilterableAdapter.setItems(listResult);
                        reyclerViewFilterableAdapter.notifyDataSetChanged();
                        new Handler().postDelayed(new Runnable() {
                            @Override
                            public void run() {
                                if (layoutManager != null) {
                                    layoutManager.onRestoreInstanceState(savedInstanceState.getParcelable(STATE));
                                }
                            }
                        }, 100);
                }
            });
    
    }

来源:https://medium.com/@dimezis/android-scroll-posity-restoring-done-done-right-cff1e2104ac7

每次有方向更改时都会重新创建活动。因此,您必须将位置保存在捆绑包中,然后在将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()链接,以获取有关如何还原回收器视图状态的更多选项。

最新更新