当用户旋转手机并保留ArrayAdapter中的所有数据时,保留列表视图项的好scheme
我使用listView的片段。 我通过自定义加载程序(从Internet)接收的数据填充与此列表视图关联的ArrayAdapter。 自定义ArrayAdapter支持无限滚动(分页)。
当用户旋转设备并在ListView中保持滚动位置时,在ArrayAdapter中存储项目的最佳方式是什么?
我正在考虑用ArrayAdapter创build非可视化片段,并使用setRetainInstance方法来保存值。
任何build议更好的解决scheme?
要使用Android框架和Fragment生命周期,您应该在Fragment中实现onSaveInstanceState
方法。 为了简单起见,我假定你有一个String值的数组,你可以得到(我通常扩展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 – 请参阅片段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回收内存的危险。 保存状态不受此影响,但使用实例variables或隐藏片段会导致数据丢失。
如果savedStateInstance
为null,那么没有状态可以恢复。
if (values != null)
仅仅是为了防止没有数组被保存的可能性,但是如果你编写你的ArrayAdapter来处理一个空数据集,你将不需要这个。
最终的解决scheme,如果你的行是你自己的类的一个实例,而不是单个数据项,则是在该类上实现savedState.putParcelableArray("myKey", myArray)
接口,然后你可以使用savedState.putParcelableArray("myKey", myArray)
。 你会惊奇地发现如何实现Parcelable是多么有用 – 它允许你在intents中传递你的类,并且允许你写更清晰的代码。
当设备旋转时,应用程序重新启动。 所以onSaveInstance
在应用程序被销毁之前被调用。 您可以将arrays适配器保存在onSaveInstance
并且当应用程序再次启动时最终调用onCreate
时,您可以检索arrays适配器并将其设置为列表视图。
将适配器的项目保存在onSaveInstance
非常简单。
现在你需要保存位置(滚动)。 你可以这样做
简单的方法
由于改变screenOrientation会弄乱事情,你可以允许一些边缘的错误,只需要在onSaveInstance
,使用listView.getFirstVisiblePosition()
保存第一个可见的项目。
然后在你的onRestoreInstance
你可以检索这个索引,使用listview.setSelection(position)
来滚动到同一个项目。 当然,你可以使用listview.smoothScrollToPosition(position)
但那会很奇怪。
困难的方式
要获得更准确的位置,您需要再下降1个级别:获取滚动位置(不是索引)并保存该位置。 然后在恢复后,回滚到这个位置。 在你的onSaveInstance
,保存从listview.getScrollY()
返回的位置。 当你在onRestoreInstance
获取这个位置时,你可以使用listview.scrollTo(0, position)
滚动到这个位置。
为什么这真的很难?
简单。 你很可能无法回滚到这个位置,因为你的列表视图还没有通过测量和布局。 在使用getViewObserver().addOnGlobalLayoutListener
设置适配器之后,可以通过等待布局来getViewObserver().addOnGlobalLayoutListener
。 在这个callback中,发布一个可运行的滚动到该位置。
我build议使用第一种“简单的方法”,因为一般情况下,当屏幕方向改变时,用户可能将手指移回。 我们只需要向他展示“给与拿”的相同的项目。
当方向改变活动生命周期调用onPause
然后onRestart
做这样的事情 –
@Override protected void onRestart() { super.onRestart(); mAdapter.getFilter().filter(getFilterSettings()); mAdapter.notifyDataSetChanged(); }
我不知道你的ArrayAdapter
或List
支持它的内容,但是如何使数据支持列表序列化,保存,然后在重新创build视图时加载它呢?
传统上,我在看到应用程序closures时试图存储数据时使用的这种方法,或者在后台中留下风险。 在这里有一个很好的序列化完成的示例代码。 他们遍历自定义对象的ArrayList
,将其写入文件并稍后重新打开。 我认为如果你实现了这种方法,在活动被销毁之前在onPause()
写入你的支持List
或者ArrayAdapter
的数据,你可以在活动重新创build时重新加载这个文件。
其他方法(a / k / a备份计划):
(1)简单但是马虎 – 如果你的列表是一些基本的,比如一个string列表,你总是可以考虑把值单独写入SharedPreferences
并在重新加载时回收它们。 只要确保在存储过程中分配一些唯一的ID。
注意,虽然这可能工作, SharedPreferecnes
通常不是devise来处理大量的数据,所以如果你是名单很长,我会避免这种做法。 然而对于一些数据点,我看不出是一个问题。
(2)稍微困难但有风险 – 如果您是List
支持,则适配器包含实现parcelable
或已经可序列化的对象,请考虑将List
通过Intent
传递给位于后台的现有活动,并使用callback来检索该数据活动重新创build时。 这与创build背景Fragment
想法类似。 这两种方法都存在您所针对的Activity
或Fragment
将不存在的风险。
为了保存列表视图的数据,在分段重新创build期间不要一遍又一遍的重新加载,就是将这些数据保存到一个类的静态成员中。 就像是
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 } }
这可以用来保存单个会话的任何种类的数据