【问题标题】:How to save and restore RecyclerView 's scroll exact position? [duplicate]如何保存和恢复 RecyclerView 的滚动精确位置? [复制]
【发布时间】:2015-09-12 09:32:30
【问题描述】:

对不起,我的英语不好。

我想要确切的位置。而在activity/fragmentdestroyed之后(不管是我销毁还是系统销毁),我重新打开activity/fragment,RecyclerView也可以是与被销毁的位置相同

“相同”并不是“项目位置”,因为可能项目只是上次显示的一部分。

我想恢复完全相同的位置。

我尝试了以下一些方法,但没有一种是完美的。

有人可以帮忙吗?

1.第一种方式。

我使用onScrolled计算滚动的确切位置,当滚动停止时,保存位置。当 spinner 更改数据集fragment onCreat 时,恢复所选数据集的位置。一些数据集可能有很多很多行。

app被销毁后可以保存恢复,但可能计算量太大?

会导致

Skipped 60 frames! The application may be doing too much work on its main thread. attempt to finish an input event but the input event receiver has already been disposed. android total arena pages for jit.

如果scrollY很大,比如111111,当打开fragment时,RecyclerView会先显示列表从第一个item开始,延迟一段时间后,滚动到scrollY位置。

p>

如何做到不耽误?

    recyclerView.addOnScrollListener(new OnScrollListener() {
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, final int dy) {
            super.onScrolled(recyclerView, dx, dy);
            handler.post(new Runnable() {
                
                @Override
                public void run() {
                    if (isTableSwitched) {
                        scrollY = 0;
                        isTableSwitched = false;
                    }
                    scrollY = scrollY + dy;
                    Log.i("dy——scrollY——table", String.valueOf(dy) + "——" + String.valueOf(scrollY) + tableName);
                }
            });
        }
        
        
        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
            
            switch (newState) {
            case RecyclerView.SCROLL_STATE_IDLE:
                saveRecyclerPosition(tableName, scrollY);
                break;
            case RecyclerView.SCROLL_STATE_DRAGGING:
                break;
            case RecyclerView.SCROLL_STATE_SETTLING:
                break;
         }
        }

    });

public void saveRecyclerPosition(String tableName, int y) {
    prefEditorSettings.putInt(KEY_SCROLL_Y + tableName, y);
    prefEditorSettings.commit();
}

public void restoreRecyclerViewState(final String tableName) {
    handler.post(new Runnable() {
        
        @Override
        public void run() {
            recyclerView.scrollBy(0, prefSettings.getInt(KEY_SCROLL_Y + tableName, 0));
        }
    });
}


//use Spinner to choose dataset
spnWordbook.setOnItemSelectedListener(new OnItemSelectedListener() {
        
        @Override
        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
            isTableSwitched = true;
            tableName= parent.getItemAtPosition(position).toString(); 
            prefEditorSettings.putString(KEY_SPINNER_SELECTED_TABLENAME, tableName);
            prefEditorSettings.commit();
            wordsList = WordsManager.getWordsList(tableName);
            wordCardAdapter.updateList(wordsList);
            
            restoreRecyclerViewState(tableName);
        }

        @Override
        public void onNothingSelected(AdapterView<?> parent) {
        }
    });

2.第二种方式。

这可以完美运行,但它只能在片段生命周期中工作。

我尝试使用ObjectOutputStreamObjectInputStream来保存和恢复HashMap,但是不起作用。

如何保存在内存中?

private HashMap <String, Parcelable> hmRecyclerViewState = new HashMap<String, Parcelable>();   

public void saveRecyclerPosition(String tableName) {    
    recyclerViewState = recyclerView.getLayoutManager().onSaveInstanceState();
    hmRecyclerViewState.put(tableName, recyclerViewState);      
}

public void restoreRecyclerViewState(final String tableName) {
    if ( hmRecyclerViewState.get(tableName) != null) {
        recyclerViewState = hmRecyclerViewState.get(tableName);
        recyclerView.getLayoutManager().onRestoreInstanceState(recyclerViewState);
        recyclerViewState = null;
}

3.第三条路。

我改了第二种方式,使用Bundle而不是HashMap,但是也有同样的问题,退出fragment生命周期就不行了。

我使用manually serialize/deserialize android bundlebundle保存在内存中,但是在恢复时,它没有得到有效的bundle

================================================ ========================= 解决方案

谢谢大家!

我终于解决了这个问题,你可以在my code看到,只要看到这2个方法:saveRecyclerViewPositionrestoreWordRecyclerViewPosition

【问题讨论】:

  • 可能只是从布局管理器获取第一个显示的项目ID及其偏移量,然后您可以移动到重新创建的位置并通过偏移量调整滚动。如果您的数据集在此期间发生更改,这也可能会起作用。
  • @bleeding182 谢谢。我也有这种想法,但我不知道如何获得项目的偏移量?每个项目都有不同的高度
  • 你可以从 layoutmanager 中获取视图,然后读取 getTop() 或 getDecoratedTop(),这与 recyclerview.top 之间的差异将是你的偏移量;)即使你没有得到向右偏移,您至少会在正确的位置而不跳帧
  • 当我在onScrollStateChanged 中使用lastCompletelyPosition = ((LinearLayoutManager)recyclerView.getLayoutManager()).findLastCompletelyVisibleItemPosition();lastCompleteChild = recyclerView.getChildAt(lastCompletelyPosition); 时无法获取项目视图,lastCompleteChild.getTop() 将抛出InvocationTargetException

标签: android listview android-recyclerview


【解决方案1】:

所以现在我有时间浪费了;)这是一个完整的答案:

基本布局。是的,它可以被优化。这不是重点;)

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</LinearLayout>

您要做的是获取第一个可见项目及其位置。如果您有固定的 id,您可以添加逻辑以在适配器中查找 id/位置以获取 id。

一些基本适配器:

public class TestAdapter extends RecyclerView.Adapter {
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new RecyclerView.ViewHolder(LayoutInflater.from(parent.getContext()).inflate(android.R.layout.simple_list_item_1, parent, false)) {
        };
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        ((TextView) holder.itemView).setText("Position " + position);
    }

    @Override
    public int getItemCount() {
        return 30;
    }
}

您可以随意保存它的状态,我使用了 SharedPreferences,并在开始时再次阅读。我发布了延迟滚动,因为如果滚动到位置紧跟在它后面,它就不起作用。这只是一个基本示例,如果您多玩一下 LayoutManager,可能会有更好的方法。

import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";
    private RecyclerView recyclerView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        recyclerView = (RecyclerView) findViewById(R.id.recycler);

        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setAdapter(new TestAdapter());

    }

    @Override
    protected void onResume() {
        super.onResume();
        final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
        recyclerView.scrollToPosition(preferences.getInt("position", 0));
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                recyclerView.scrollBy(0, - preferences.getInt("offset", 0));
            }
        }, 500);
    }

    @Override
    protected void onPause() {
        super.onPause();
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);

        View firstChild = recyclerView.getChildAt(0);
        int firstVisiblePosition = recyclerView.getChildAdapterPosition(firstChild);
        int offset = firstChild.getTop();

        Log.d(TAG, "Postition: " + firstVisiblePosition);
        Log.d(TAG, "Offset: " + offset);

        preferences.edit()
                .putInt("position", firstVisiblePosition)
                .putInt("offset", offset)
                .apply();
    }
}

这会做什么,它会再次显示正确的项目,并在 500 毫秒后跳回正确的位置。

一定要添加空检查和排序!

希望这会有所帮助!

【讨论】:

    猜你喜欢
    • 2020-05-22
    • 1970-01-01
    • 2021-08-20
    • 2020-11-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-02
    相关资源
    最近更新 更多