我正在使用带有 listView 的片段。我通过自定义加载程序(来自互联网)中接收的数据填充与此列表视图关联的 ArrayAdapter。自定义 ArrayAdapter 支持无限滚动(分页)。
当用户旋转设备并在 ListView 中保持滚动位置时,将项目存储在 ArrayAdapter 中的最佳方法是什么?
我正在考虑使用 ArrayAdapter 创建非可视片段,并使用 setRetainInstance 方法来保存值。
对更好的解决方案有什么建议吗?
我正在使用带有 listView 的片段。我通过自定义加载程序(来自互联网)中接收的数据填充与此列表视图关联的 ArrayAdapter。自定义 ArrayAdapter 支持无限滚动(分页)。
当用户旋转设备并在 ListView 中保持滚动位置时,将项目存储在 ArrayAdapter 中的最佳方法是什么?
我正在考虑使用 ArrayAdapter 创建非可视片段,并使用 setRetainInstance 方法来保存值。
对更好的解决方案有什么建议吗?
要使用 Android 框架和 Fragment 生命周期,您应该onSaveInstanceState
在 Fragment 中实现该方法。为简单起见,我假设您有一个可以访问的字符串值数组(我通常扩展 ArrayAdapter 以封装视图构造并提供访问整个底层数据集的便捷方法):
public void onSaveInstanceState(Bundle savedState) {
super.onSaveInstanceState(savedState);
// Note: getValues() is a method in your ArrayAdapter subclass
String[] values = mAdapter.getValues();
savedState.putStringArray("myKey", values);
}
然后,您可以在 onCreate 方法(或 onCreateView 或 onActivityCreated - 请参阅Fragment JavaDoc)中检索数据,如下所示:
public void onCreate (Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
String[] values = savedInstanceState.getStringArray("myKey");
if (values != null) {
mAdapter = new MyAdapter(values);
}
}
...
}
这可确保正确处理所有生命周期事件,而不会丢失数据,包括设备旋转和用户切换到其他应用程序。不使用onSaveInstanceState
和使用内存的危险是Android回收该内存的危险。保存状态不会受此影响,但使用实例变量或隐藏片段会导致数据丢失。
如果savedStateInstance
为空,则没有要恢复的状态。
这if (values != null)
只是为了防止没有保存数组的可能性,但是如果您编写 ArrayAdapter 来处理空数据集,则不需要它。
最终的解决方案是,如果您的行是您自己的类之一而不是单个数据项的实例,则在该类上实现 Parcelable 接口,那么您可以使用savedState.putParcelableArray("myKey", myArray)
. 你会惊讶于知道如何实现 Parcelable 是多么有用——它允许你在内部意图中传递你的类,并允许你编写更清晰的代码。
当设备旋转时,应用程序会重新启动。所以onSaveInstance
在应用程序被销毁之前调用。您可以将数组适配器保存在onSaveInstance
其中,当onCreate
应用程序再次启动时最终调用时,您可以检索数组适配器并将其设置为列表视图。
将适配器的项目保存在onSaveInstance
.
现在您需要保存位置(滚动)。你可以这样做
简单的方法
由于更改 screenOrientation 无论如何都会搞砸,您可以允许一些边际错误,并简单地在您onSaveInstance
的listView.getFirstVisiblePosition()
.
然后在您的中,onRestoreInstance
您可以检索此索引以使用 滚动到同一项目listview.setSelection(position)
。当然,你可以使用listview.smoothScrollToPosition(position)
,但这会很奇怪。
艰难的道路
要获得更准确的位置,您需要再下降 1 级:获取滚动位置(不是索引)并保存该位置。然后在恢复后,滚动回这个位置。在您的onSaveInstance
中,保存从 返回的位置listview.getScrollY()
。当您在 中检索此位置时onRestoreInstance
,您可以使用 滚动回该位置listview.scrollTo(0, position)
。
为什么这条路真的很难?
简单的。您很可能无法滚动回该位置,因为您的列表视图尚未通过测量和布局。您可以通过在使用设置适配器后等待布局来克服这个问题getViewObserver().addOnGlobalLayoutListener
。在此回调中,发布一个 runnable 以滚动到该位置。
我建议使用第一种“简单方法”,因为通常当屏幕方向改变时,用户可能会向后移动手指。我们只需要向他展示“给予或接受”相同的项目。
当方向改变活动生命周期调用onPause
然后onRestart
做这样的事情 -
@Override
protected void onRestart() {
super.onRestart();
mAdapter.getFilter().filter(getFilterSettings());
mAdapter.notifyDataSetChanged();
}
为列表视图保存数据以免在片段重新创建期间一次又一次地重新加载,就是将该数据保存到类的静态成员中。就像是
class YourData{
public static List<YourDataObject> listViewData;
}
然后从适配器首先从互联网或本地数据库加载数据,然后将检索到的数据设置为该静态成员,如下所示:
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Run the process to get data from database
YourData.listViewData = dataRetrievedFromDatabase;
}
然后在您的 onResume 方法中将这些值更新为适配器,例如:
@Override
public void onResume() {
super.onResume();
if(YourData.listViewData!=null){
yourListAdapater.setData = YourData.listViewData;
listView.setAdapter(yourListAdapater);
}else{
//run your method to get data from server
}
}
这可用于为单个会话保存任何类型的数据
我不知道您的内容ArrayAdapter
或List
支持它的内容,但是如何使支持列表的数据可序列化,保存它,然后在重新创建视图时加载它呢?
传统上,当您尝试在应用程序关闭或因留在后台而被杀死的风险时尝试存储数据时,我已经看到了这种方法。有一篇很棒的关于序列化的文章,这里附有示例代码。他们通过获取ArrayList
自定义对象,将其写入文件并稍后重新打开它。我认为如果您实现了这种方法,在活动被销毁之前写入支持的数据,List
那么您可以在重新创建活动时重新加载该文件。ArrayAdapter
onPause()
其他方法(a/k/a 备份计划):
(1) 简单但草率 - 如果您的列表是一些原始的,例如字符串列表,您总是可以考虑将值单独写入SharedPreferences
并在重新加载时回收它们。只要确保在存储过程中分配一些唯一的 id。
注意虽然这可能有效,但SharedPreferecnes
通常不是为处理大量数据而设计的,所以如果你的列表很长,我会避免这种方法。但是,对于一些数据点,我认为这不是问题。
(2) 稍微困难但有风险 - 如果您List
支持的适配器包含实现parcelable
或已经可序列化的对象,请考虑将List
via传递Intent
给在后台的现有活动,并在活动时使用回调来检索该数据重新创建。这类似于您创建背景的想法Fragment
。这两种方法都存在您所针对的Activity
或Fragment
您的目标将不存在的风险。