当用户旋转手机并保留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(); } 

我不知道你的ArrayAdapterList支持它的内容,但是如何使数据支持列表序列化,保存,然后在重新创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想法类似。 这两种方法都存在您所针对的ActivityFragment将不存在的风险。

为了保存列表视图的数据,在分段重新创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 } } 

这可以用来保存单个会话的任何种类的数据