Tôi có câu hỏi về RecyclerView.State của Android .
Tôi đang sử dụng RecyclerView, làm cách nào tôi có thể sử dụng và liên kết nó với RecyclerView.State?
Mục đích của tôi là lưu vị trí cuộn của RecyclerView .
Tôi có câu hỏi về RecyclerView.State của Android .
Tôi đang sử dụng RecyclerView, làm cách nào tôi có thể sử dụng và liên kết nó với RecyclerView.State?
Mục đích của tôi là lưu vị trí cuộn của RecyclerView .
Câu trả lời:
Bạn dự định lưu vị trí đã lưu cuối cùng bằng cách RecyclerView.State
nào?
Bạn luôn có thể dựa vào trạng thái lưu tốt. Mở rộng RecyclerView
và ghi đè onSaveInstanceState () và 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];
}
};
}
Bắt đầu từ recyclerview:1.2.0-alpha02
phát hành StateRestorationPolicy
đã được giới thiệu. Nó có thể là một cách tiếp cận tốt hơn cho vấn đề đã cho.
Chủ đề này đã được đề cập trong bài báo phương tiện dành cho nhà phát triển Android .
Ngoài ra, @ rubén-viguera đã chia sẻ chi tiết hơn trong câu trả lời bên dưới. https://stackoverflow.com/a/61609823/892500
Nếu bạn đang sử dụng LinearLayoutManager, nó đi kèm với lưu trữ sẵn api linearLayoutManagerInstance.onSaveInstanceState () và khôi phục api linearLayoutManagerInstance.onRestoreInstanceState (...)
Cùng với đó, bạn có thể lưu bưu kiện đã trả lại vào outState của mình. ví dụ,
outState.putParcelable("KeyForLayoutManagerState", linearLayoutManagerInstance.onSaveInstanceState());
và khôi phục vị trí khôi phục với trạng thái bạn đã lưu. ví dụ,
Parcelable state = savedInstanceState.getParcelable("KeyForLayoutManagerState");
linearLayoutManagerInstance.onRestoreInstanceState(state);
Để tóm tắt tất cả, mã cuối cùng của bạn sẽ trông giống như
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());
}
Chỉnh sửa: Bạn cũng có thể sử dụng cùng một apis với GridLayoutManager, vì nó là một lớp con của LinearLayoutManager. Cảm ơn @wegsehen vì đề xuất.
Chỉnh sửa: Hãy nhớ rằng, nếu bạn cũng đang tải dữ liệu trong một chuỗi nền, bạn sẽ cần gọi đến onRestoreInstanceState trong phương thức onPostExecute / onLoadFinishing của mình để vị trí được khôi phục khi thay đổi hướng, ví dụ:
@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
nhưng không hoạt động nếu bạn có ViewPager
với RecycerView
trên mỗi trang.
onCreateView
, mà là sau khi tải dữ liệu. Xem câu trả lời của @Farmaker tại đây.
Cửa hàng
lastFirstVisiblePosition = ((LinearLayoutManager)rv.getLayoutManager()).findFirstCompletelyVisibleItemPosition();
Khôi phục
((LinearLayoutManager) rv.getLayoutManager()).scrollToPosition(lastFirstVisiblePosition);
và nếu điều đó không hiệu quả, hãy thử
((LinearLayoutManager) rv.getLayoutManager()).scrollToPositionWithOffset(lastFirstVisiblePosition,0)
Đặt cửa hàng vào onPause()
và khôi phụconResume()
lastFirstVisiblePosition
để 0
sau khi khôi phục nó. Trường hợp này hữu ích khi bạn có một phân đoạn / hoạt động tải dữ liệu khác nhau trong một RecyclerView
, chẳng hạn như ứng dụng trình khám phá tệp.
SwipeRefreshLayout
?
RecyclerView
Tôi muốn lưu vị trí cuộn của Recycler View khi điều hướng khỏi hoạt động trong danh sách của mình và sau đó nhấp vào nút quay lại để điều hướng trở lại. Nhiều giải pháp được cung cấp cho vấn đề này phức tạp hơn nhiều so với mức cần thiết hoặc không hoạt động đối với cấu hình của tôi, vì vậy tôi nghĩ tôi sẽ chia sẻ giải pháp của mình.
Đầu tiên, lưu trạng thái phiên bản của bạn trong onPause như nhiều trạng thái đã hiển thị. Tôi nghĩ điều đáng nhấn mạnh ở đây là phiên bản onSaveInstanceState này là một phương thức từ lớp RecyclerView.LayoutManager.
private LinearLayoutManager mLayoutManager;
Parcelable state;
@Override
public void onPause() {
super.onPause();
state = mLayoutManager.onSaveInstanceState();
}
Chìa khóa để làm cho điều này hoạt động bình thường là đảm bảo bạn gọi onRestoreInstanceState sau khi bạn gắn bộ điều hợp của mình, như một số đã chỉ ra trong các chuỗi khác. Tuy nhiên, cách gọi phương thức thực tế đơn giản hơn nhiều so với nhiều người đã chỉ ra.
private void someMethod() {
mVenueRecyclerView.setAdapter(mVenueAdapter);
mLayoutManager.onRestoreInstanceState(state);
}
Tôi Đặt các biến trong onCreate (), lưu vị trí cuộn trong onPause () và đặt vị trí cuộn trong 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);
}
}
Bạn không cần phải tự mình lưu và khôi phục trạng thái nữa. Nếu bạn đặt ID duy nhất trong xml và hệ thống RecyclerView.setSaveEnabled (true) (true theo mặc định) sẽ tự động thực hiện. Đây là thông tin thêm về điều này: http://trickyandroid.com/saving-android-view-state-correctly/
Tất cả các trình quản lý bố cục có trong thư viện hỗ trợ đã biết cách lưu và khôi phục vị trí cuộn.
Vị trí cuộn được khôi phục trên lần vượt qua bố cục đầu tiên xảy ra khi các điều kiện sau được đáp ứng:
RecyclerView
RecyclerView
Thông thường, bạn đặt trình quản lý bố cục trong XML, vì vậy tất cả những gì bạn phải làm bây giờ là
RecyclerView
Bạn có thể làm điều này bất cứ lúc nào, không bị hạn chế Fragment.onCreateView
hoặc Activity.onCreate
.
Ví dụ: Đảm bảo bộ điều hợp được đính kèm mỗi khi dữ liệu được cập nhật.
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
}
}
Vị trí cuộn đã lưu được tính toán từ dữ liệu sau:
Do đó, bạn có thể mong đợi sự không khớp đôi chút, ví dụ nếu các mặt hàng của bạn có kích thước khác nhau theo hướng dọc và ngang.
Các RecyclerView
/ ScrollView
/ bất cứ điều gì cần phải có một android:id
cho trạng thái của nó được cứu rỗi.
LinearLayoutManager.SavedState
: cs.android.com/androidx/platform/frameworks/support/+/…
Đây là Phương pháp tiếp cận của tôi để lưu chúng và duy trì trạng thái tái chế trong một mảnh. Đầu tiên, hãy thêm các thay đổi cấu hình trong hoạt động mẹ của bạn như mã bên dưới.
android:configChanges="orientation|screenSize"
Sau đó, trong Fragment của bạn, hãy khai báo phương thức instance này
private Bundle mBundleRecyclerViewState;
private Parcelable mListState = null;
private LinearLayoutManager mLinearLayoutManager;
Thêm mã bên dưới vào phương thức @onPause của bạn
@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);
}
Thêm phương thức onConfigartionChanged như bên dưới.
@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);
}
Sự tiếp cận này sẽ giải quyết vấn đề của bạn. nếu Bạn có trình quản lý bố cục khác thì chỉ cần thay thế trình quản lý bố trí trên.
Đây là cách tôi khôi phục vị trí RecyclerView với GridLayoutManager sau khi xoay khi bạn cần tải lại dữ liệu từ internet bằng AsyncTaskLoader.
Tạo một biến toàn cục của Parcelable và GridLayoutManager và một chuỗi cuối cùng tĩnh:
private Parcelable savedRecyclerLayoutState;
private GridLayoutManager mGridLayoutManager;
private static final String BUNDLE_RECYCLER_LAYOUT = "recycler_layout";
Lưu trạng thái của gridLayoutManager trong onSaveInstance ()
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(BUNDLE_RECYCLER_LAYOUT,
mGridLayoutManager.onSaveInstanceState());
}
Khôi phục trong 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);
}
}
Sau đó, khi trình tải tìm nạp dữ liệu từ internet, bạn khôi phục vị trí tái chế trong onLoadFinishing ()
if(savedRecyclerLayoutState!=null){
mGridLayoutManager.onRestoreInstanceState(savedRecyclerLayoutState);
}
.. tất nhiên bạn phải khởi tạo gridLayoutManager bên trong onCreate. Chúc mừng
Tôi cũng có yêu cầu tương tự, nhưng các giải pháp ở đây không giúp tôi vượt qua được giới hạn, do nguồn dữ liệu cho Chế độ xem tái chế.
Tôi đang giải nén trạng thái LinearLayoutManager của RecyclerViews trong onPause ()
private Parcelable state;
Public void onPause() {
state = mLinearLayoutManager.onSaveInstanceState();
}
Parcelable state
được lưu vào onSaveInstanceState(Bundle outState)
và được giải nén lại vào onCreate(Bundle savedInstanceState)
khi nào savedInstanceState != null
.
Tuy nhiên, bộ điều hợp RecyclerView được điền và cập nhật bởi một đối tượng ViewModel LiveData bằng lệnh gọi DAO đến cơ sở dữ liệu Phòng.
mViewModel.getAllDataToDisplay(mSelectedData).observe(this, new Observer<List<String>>() {
@Override
public void onChanged(@Nullable final List<String> displayStrings) {
mAdapter.swapData(displayStrings);
mLinearLayoutManager.onRestoreInstanceState(state);
}
});
Cuối cùng tôi nhận thấy rằng việc khôi phục trạng thái phiên bản trực tiếp sau khi dữ liệu được đặt trong bộ điều hợp sẽ giữ trạng thái cuộn qua các lần xoay.
Tôi nghi ngờ điều này là do LinearLayoutManager
trạng thái mà tôi đã khôi phục bị ghi đè khi dữ liệu được trả về bởi lệnh gọi cơ sở dữ liệu hoặc trạng thái được khôi phục là vô nghĩa đối với một LinearLayoutManager 'trống'.
Nếu dữ liệu bộ điều hợp có sẵn trực tiếp (nghĩa là không liên quan đến lệnh gọi cơ sở dữ liệu), thì việc khôi phục trạng thái phiên bản trên LinearLayoutManager có thể được thực hiện sau khi bộ điều hợp được đặt trên RecyclerView.
Sự khác biệt giữa hai kịch bản đã khiến tôi cảm thấy khó chịu.
Trên Android API Cấp 28, tôi chỉ cần đảm bảo rằng tôi đã thiết lập phương pháp của mình LinearLayoutManager
và RecyclerView.Adapter
trong Fragment#onCreateView
phương pháp của mình cũng như mọi thứ Just Worked ™ ️. Tôi không cần phải làm bất cứ onSaveInstanceState
hay onRestoreInstanceState
làm việc.
Câu trả lời của Eugen Pechanec giải thích tại sao điều này lại hiệu quả.
Bắt đầu từ phiên bản 1.2.0-alpha02 của thư viện androidx remlerView, giờ đây nó được quản lý tự động. Chỉ cần thêm nó với:
implementation "androidx.recyclerview:recyclerview:1.2.0-alpha02"
Và sử dụng:
adapter.stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY
Enum StateRestorationPolicy có 3 tùy chọn:
Lưu ý rằng tại thời điểm câu trả lời này, thư viện RecyclerView vẫn ở alpha03, nhưng giai đoạn alpha không phù hợp cho mục đích sản xuất .
Đối với tôi, vấn đề là tôi thiết lập một trình quản lý bố cục mới mỗi khi tôi thay đổi bộ điều hợp của mình, làm mất vị trí cuộn của hàng đợi trên RecyclerView.
Tôi chỉ muốn chia sẻ tình trạng khó khăn gần đây mà tôi gặp phải với RecyclerView. Tôi hy vọng rằng bất cứ ai gặp phải vấn đề tương tự sẽ được hưởng lợi.
Yêu cầu dự án của tôi: Vì vậy, tôi có RecyclerView liệt kê một số mục có thể nhấp trong Hoạt động chính của tôi (Hoạt động-A). Khi Mục được nhấp vào, một Hoạt động mới được hiển thị với Chi tiết Mục (Hoạt động-B).
Tôi đã triển khai trong Tệp kê khai tệp rằng Activity-A là cha của Activity-B, theo cách đó, tôi có nút quay lại hoặc nút trang chủ trên ActionBar của Activity-B
Sự cố: Mỗi khi tôi nhấn nút Quay lại hoặc Trang chủ trong Thanh tác vụ Activity-B, Activity-A sẽ đi vào toàn bộ Vòng đời hoạt động bắt đầu từ onCreate ()
Mặc dù tôi đã triển khai onSaveInstanceState () lưu Danh sách Bộ điều hợp của RecyclerView, khi Activity-A bắt đầu vòng đời của nó, thì saveInstanceState luôn là null trong phương thức onCreate ().
Tìm hiểu sâu hơn trên internet, tôi gặp vấn đề tương tự nhưng người đó nhận thấy rằng nút Quay lại hoặc Trang chủ bên dưới thiết bị Anroid (nút Quay lại / Trang chủ mặc định), Activity-A không đi vào Vòng đời hoạt động.
Giải pháp:
Tôi đã bật nút trang chủ hoặc nút quay lại trên Activity-B
Trong phương thức onCreate (), thêm dòng này supportActionBar? .SetDisplayHomeAsUpEnabled (true)
Trong phương thức onOptionsItemSelected () vui nhộn ghi đè, tôi đã kiểm tra item.ItemId mà item được nhấp dựa trên id. Id cho nút quay lại là
android.R.id.home
Sau đó, thực hiện một lệnh gọi hàm finish () bên trong android.R.id.home
Điều này sẽ kết thúc Activity-B và mang lại Acitivy-A mà không cần trải qua toàn bộ vòng đời.
Đối với yêu cầu của tôi, đây là giải pháp tốt nhất cho đến nay.
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)
}
Tôi đã gặp vấn đề tương tự với một sự khác biệt nhỏ, tôi đang sử dụng Databinding và tôi đang đặt bộ điều hợp RecyclerView và layoutManager trong các phương thức liên kết.
Vấn đề là ràng buộc dữ liệu đã xảy ra sau onResume, vì vậy nó đang đặt lại layoutManager của tôi sau onResume và điều đó có nghĩa là nó luôn cuộn lại vị trí ban đầu. Vì vậy, khi tôi ngừng thiết lập layoutManager trong các phương thức bộ điều hợp Databinding, nó hoạt động tốt trở lại.
Trong trường hợp của tôi, tôi đã thiết lập RecyclerView layoutManager
cả trong XML và trong onViewCreated
. Xóa bài tập đã onViewCreated
sửa nó.
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();
}
Tại sao tôi đang sử dụng OnSaveInstanceState()
trong onPause()
phương tiện, khi chuyển sang hoạt động khác onPause sẽ called.It sẽ tiết kiệm được rằng vị trí cuộn và khôi phục lại vị trí khi chúng tôi trở về từ hoạt động khác.