返回 ListView 时保持/保存/恢复滚动位置
Posted
技术标签:
【中文标题】返回 ListView 时保持/保存/恢复滚动位置【英文标题】:Maintain/Save/Restore scroll position when returning to a ListView 【发布时间】:2011-03-02 03:24:22 【问题描述】:我有一个很长的ListView
,用户可以在返回前一个屏幕之前滚动它。当用户再次打开此ListView
时,我希望将列表滚动到与之前相同的位置。关于如何实现这一点的任何想法?
【问题讨论】:
我认为 Eugene Mymrin / Giorgio Barchiesi 提到的解决方案比公认的答案更好 【参考方案1】:试试这个:
// save index and top position
int index = mList.getFirstVisiblePosition();
View v = mList.getChildAt(0);
int top = (v == null) ? 0 : (v.getTop() - mList.getPaddingTop());
// ...
// restore index and position
mList.setSelectionFromTop(index, top);
说明:ListView.getFirstVisiblePosition()
返回顶部可见列表项。但是这个item可能会被部分滚动出视图,如果你想恢复列表的确切滚动位置你需要得到这个偏移量。所以ListView.getChildAt(0)
返回顶部列表项的View
,然后View.getTop() - mList.getPaddingTop()
返回它与ListView
顶部的相对偏移量。然后,为了恢复 ListView
的滚动位置,我们调用 ListView.setSelectionFromTop()
并使用我们想要的项目的索引和一个偏移量来定位它的顶部边缘到 ListView
的顶部。
【讨论】:
不太了解它是如何工作的。我只使用 listView.getFirstVisiblePosition() 并理解这一点,但不确定这里发生了什么。有机会做个简短的解释吗? :) 所以 ListView.getFirstVisiblePosition() 返回顶部可见列表项。但是这个item可能会被部分滚动出视图,如果你想恢复列表的确切滚动位置你需要得到这个偏移量。所以 ListView.getChildAt(0) 返回顶部列表项的 View,然后 View.getTop() 返回其相对于 ListView 顶部的相对偏移量。然后,为了恢复 ListView 的滚动位置,我们调用 ListView.setSelectionFromTop() 并使用我们想要的项目的索引和一个偏移量来定位其顶部边缘与 ListView 顶部的距离。都清楚了吗? 这可以更简单,使用一行保存:int index = mList.getFirstVisiblePosition();
,只恢复一行:mList.setSelectionFromTop(index, 0);
。虽然答案很好(+1)!我一直在寻找一个优雅的解决方案来解决这个问题。
@Phil 您的简单解决方案无法恢复确切的滚动位置。
正如@nbarraille 所说,代码应该更像“v.getTop() - mList.getPaddingTop()”。否则你会像我一样花一个小时试图弄清楚为什么它总是只恢复一根头发......【参考方案2】:
Parcelable state;
@Override
public void onPause()
// Save ListView state @ onPause
Log.d(TAG, "saving listview state");
state = listView.onSaveInstanceState();
super.onPause();
...
@Override
public void onViewCreated(final View view, Bundle savedInstanceState)
super.onViewCreated(view, savedInstanceState);
// Set new items
listView.setAdapter(adapter);
...
// Restore previous state (including selected item index and scroll position)
if(state != null)
Log.d(TAG, "trying to restore listview state");
listView.onRestoreInstanceState(state);
【讨论】:
我认为这是最好的答案,因为这种方法可以恢复确切的滚动位置,而不仅仅是第一个可见项目。 很好的答案,但在动态加载内容并且您想在 onPause() 方法中保存状态时不起作用。 很好的解决方案。只有一个问题。如果项目很大并且您滚动,但第一个项目仍然可见,则列表会跳回顶部。如果您滚动到第二个项目或其他任何地方,一切正常 @passsy 你有没有找到解决方案让列表跳回顶部? 正如 aaronvargas 所说,当 ListView.getFirstVisiblePosition() 为 0 时,这是行不通的。【参考方案3】:我采用了@(Kirk Woll) 建议的解决方案,它对我有用。我还在“联系人”应用程序的 android 源代码中看到,他们使用了类似的技术。我想添加更多细节: 在我的 ListActivity 派生类之上:
private static final String LIST_STATE = "listState";
private Parcelable mListState = null;
然后,一些方法覆盖:
@Override
protected void onRestoreInstanceState(Bundle state)
super.onRestoreInstanceState(state);
mListState = state.getParcelable(LIST_STATE);
@Override
protected void onResume()
super.onResume();
loadData();
if (mListState != null)
getListView().onRestoreInstanceState(mListState);
mListState = null;
@Override
protected void onSaveInstanceState(Bundle state)
super.onSaveInstanceState(state);
mListState = getListView().onSaveInstanceState();
state.putParcelable(LIST_STATE, mListState);
当然,“loadData”是我从数据库中检索数据并将其放入列表的函数。
在我的 Froyo 设备上,这在您更改手机方向以及编辑项目并返回列表时都有效。
【讨论】:
这个解决方案对我有用。其他人没有。对于在 ListView 中删除和插入项目,保存/恢复 ListView 实例状态的行为如何? 仅供参考,如果您在片段中使用它(我正在使用列表片段)并导航到其他片段,这将崩溃,因为调用mListState = getListView().onSaveInstanceState().
时未创建内容视图主要是设备旋转.
onRestoreInstanceState
永远不会被调用 :(
@GiorgioBarchiesi 谁是@(Kirk Wool),他的解决方案适合您?用户显然更改了他/她的名字。
是的,那是官方宗教。但在我的情况下,数据是在本地加载的,长度非常有限,并且会在瞬间加载,所以我很兴奋:-)【参考方案4】:
一个很简单的方法:
/** Save the position **/
int currentPosition = listView.getFirstVisiblePosition();
//Here u should save the currentPosition anywhere
/** Restore the previus saved position **/
listView.setSelection(savedPosition);
setSelection 方法会将列表重置为提供的项目。如果不在触摸模式下,该项目将实际被选中,如果在触摸模式下,项目将仅定位在屏幕上。
更复杂的方法:
listView.setOnScrollListener(this);
//Implements the interface:
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount)
mCurrentX = view.getScrollX();
mCurrentY = view.getScrollY();
@Override
public void onScrollStateChanged(AbsListView view, int scrollState)
//Save anywere the x and the y
/** Restore: **/
listView.scrollTo(savedX, savedY);
【讨论】:
您的回答有误。该方法称为 setSelection 思路不错,但是有一个伪问题。第一个可见位置可能只显示一点点,(可能只是行的一小部分),setSelection() 设置这一行恢复时完全可见。 请看我的第二个选项。我刚刚将它添加到我的第一个答案中 view.getScrollX() 和 view.getScrollY() 总是返回 0 ! 看看我上面使用 View.getChildAt() 和 View.getTop() 的(接受的)解决方案。您不能在 ListView 上使用 View.getScrollY(),因为 ListView 在内部保持其滚动。【参考方案5】:我发现了一些有趣的事情。
我尝试了 setSelection 和 scrolltoXY 但它根本不起作用,列表保持在相同的位置,经过一些试验和错误后,我得到了以下有效的代码
final ListView list = (ListView) findViewById(R.id.list);
list.post(new Runnable()
@Override
public void run()
list.setSelection(0);
);
如果您尝试 runOnUiThread 而不是发布 Runnable,它也不起作用(至少在某些设备上)
对于应该直截了当的事情来说,这是一个非常奇怪的解决方法。
【讨论】:
我遇到了同样的问题!而setSelectionFromTop()
不起作用。
+1。 listnew.post(),在某些设备上不起作用,在某些其他设备上它在某些情况下不起作用,但 runOnUIThread 工作正常。
@Omar,你能举一些这不起作用的设备和操作系统的例子吗?我尝试了 HTC G2 (2.3.4) 和 Galaxy Nexus (4.1.2),两者都可以使用。此线程中的其他答案均不适用于我的任何设备。
listview.post 在装有 4.0.4 的 Galaxy Note 上运行良好,但在装有 2.3.6 的 Nexus One 上无法运行。但是,onOnUIThread(action) 在两种设备上都可以正常工作。【参考方案6】:
注意!!如果 ListView.getFirstVisiblePosition() 为 0,则 AbsListView 中存在不允许 onSaveState() 正常工作的错误。
因此,如果您有占据大部分屏幕的大图像,并且您滚动到第二个图像,但显示了第一个图像的一小部分,则不会保存滚动位置...
来自 AbsListView.java:1650(我的 cmets)
// this will be false when the firstPosition IS 0
if (haveChildren && mFirstPosition > 0)
...
else
ss.viewTop = 0;
ss.firstId = INVALID_POSITION;
ss.position = 0;
但在这种情况下,下面代码中的“顶部”将是一个负数,这会导致其他问题,从而阻止正确恢复状态。所以当'top'为负数时,获取下一个孩子
// save index and top position
int index = getFirstVisiblePosition();
View v = getChildAt(0);
int top = (v == null) ? 0 : v.getTop();
if (top < 0 && getChildAt(1) != null)
index++;
v = getChildAt(1);
top = v.getTop();
// parcel the index and top
// when restoring, unparcel index and top
listView.setSelectionFromTop(index, top);
【讨论】:
它就像一个魅力,但我并不完全理解它。如果第一个项目仍然可见,那么获取下一个孩子有什么意义?使用新索引的 setSelectionFromTop 不应该导致列表从第二个孩子开始显示吗?我怎么还能看到第一个? @tafi,第一项可能是“部分”可见的。所以该项目的顶部将在屏幕上方,因此将是一个负数。 (这会导致我不记得的其他问题......)所以我们使用第二个项目的“顶部”进行放置,它应该总是(?)可用,或者首先不会有任何滚动!【参考方案7】:对于一些正在寻找解决此问题的方法的人来说,问题的根源可能在于您设置列表视图适配器的位置。在列表视图上设置适配器后,它会重置滚动位置。只是需要考虑的事情。在我们获取对列表视图的引用后,我将适配器设置到我的 onCreateView 中,它为我解决了这个问题。 =)
【讨论】:
【参考方案8】:private Parcelable state;
@Override
public void onPause()
state = mAlbumListView.onSaveInstanceState();
super.onPause();
@Override
public void onResume()
super.onResume();
if (getAdapter() != null)
mAlbumListView.setAdapter(getAdapter());
if (state != null)
mAlbumListView.requestFocus();
mAlbumListView.onRestoreInstanceState(state);
够了
【讨论】:
嗨,您上面的代码是否可以帮助我创建一个 RecyclerView 列表,其中 instancestate 没有在活动之间保存?我在这里发布了以下问题:***.com/questions/35413495/…?【参考方案9】:发布这个是因为我很惊讶没有人提到这个。
用户单击后退按钮后,他将返回列表视图,状态与退出时相同。
此代码将覆盖“向上”按钮,使其与后退按钮的行为方式相同,因此在 Listview -> Details -> Back to Listview(没有其他选项)的情况下,这是保持滚动位置的最简单代码以及列表视图中的内容。
public boolean onOptionsItemSelected(MenuItem item)
switch (item.getItemId())
case android.R.id.home:
onBackPressed();
return(true);
return(super.onOptionsItemSelected(item));
注意:如果您可以从详细信息活动转到另一个活动,则向上按钮会将您返回到该活动,因此您必须操作后退按钮历史记录才能使其正常工作。
【讨论】:
【参考方案10】:ListView xml 声明中简单的android:saveEnabled="true"
还不够吗?
【讨论】:
android:saveEnabled 默认设置为 true。这没有任何作用。 没有。这还不够,你必须关注***.com/questions/3014089/… 或***.com/questions/3014089/…【参考方案11】:最佳解决方案是:
// save index and top position
int index = mList.getFirstVisiblePosition();
View v = mList.getChildAt(0);
int top = (v == null) ? 0 : (v.getTop() - mList.getPaddingTop());
// ...
// restore index and position
mList.post(new Runnable()
@Override
public void run()
mList.setSelectionFromTop(index, top);
);
你必须在 POST 和 Thread 中调用!
【讨论】:
【参考方案12】:如果您在重新加载之前保存状态并在之后恢复它,则可以在重新加载后保持滚动状态。就我而言,我发出了一个异步网络请求,并在完成后在回调中重新加载了列表。这是我恢复状态的地方。代码示例是 Kotlin。
val state = myList.layoutManager.onSaveInstanceState()
getNewThings() newThings: List<Thing> ->
myList.adapter.things = newThings
myList.layoutManager.onRestoreInstanceState(state)
【讨论】:
【参考方案13】:如果您使用托管在活动上的片段,您可以执行以下操作:
public abstract class BaseFragment extends Fragment
private boolean mSaveView = false;
private SoftReference<View> mViewReference;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
if (mSaveView)
if (mViewReference != null)
final View savedView = mViewReference.get();
if (savedView != null)
if (savedView.getParent() != null)
((ViewGroup) savedView.getParent()).removeView(savedView);
return savedView;
final View view = inflater.inflate(getFragmentResource(), container, false);
mViewReference = new SoftReference<View>(view);
return view;
protected void setSaveView(boolean value)
mSaveView = value;
public class MyFragment extends BaseFragment
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
setSaveView(true);
final View view = super.onCreateView(inflater, container, savedInstanceState);
ListView placesList = (ListView) view.findViewById(R.id.places_list);
if (placesList.getAdapter() == null)
placesList.setAdapter(createAdapter());
【讨论】:
【参考方案14】:如果您自己保存/恢复ListView
的滚动位置,您实际上是在复制已经在android 框架中实现的功能。 ListView
可以很好地自行恢复精细滚动位置,除了一个警告:正如@aaronvargas 提到的那样,AbsListView
中有一个错误,它不会让第一个列表项恢复精细滚动位置。然而,恢复滚动位置的最佳方法不是恢复它。 Android 框架会为您做得更好。只需确保您满足以下条件:
setSaveEnabled(false)
方法并且没有为xml布局文件中的列表设置android:saveEnabled="false"
属性
对于ExpandableListView
覆盖long getCombinedChildId(long groupId, long childId)
方法,使其返回正长整数(BaseExpandableListAdapter
类中的默认实现返回负数)。以下是示例:
.
@Override
public long getChildId(int groupPosition, int childPosition)
return 0L | groupPosition << 12 | childPosition;
@Override
public long getCombinedChildId(long groupId, long childId)
return groupId << 32 | childId << 1 | 1;
@Override
public long getGroupId(int groupPosition)
return groupPosition;
@Override
public long getCombinedGroupId(long groupId)
return (groupId & 0x7FFFFFFF) << 32;
如果在片段中使用了ListView
或ExpandableListView
,则不要在重新创建活动时重新创建片段(例如在屏幕旋转之后)。使用findFragmentByTag(String tag)
方法获取片段。
确保ListView
有一个android:id
并且它是唯一的。
为了避免上述对第一个列表项的警告,您可以制作适配器,使其在位置 0 处为 ListView
返回特殊的虚拟零像素高度视图。
这是一个简单的示例项目,显示ListView
和ExpandableListView
恢复它们的精细滚动位置,而它们的滚动位置没有明确保存/恢复。即使对于临时切换到其他应用程序、双屏旋转和切换回测试应用程序的复杂场景,精细滚动位置也能完美恢复。请注意,如果您明确退出应用程序(通过按下后退按钮),滚动位置将不会被保存(以及所有其他视图不会保存它们的状态)。
https://github.com/voromto/RestoreScrollPosition/releases
【讨论】:
【参考方案15】:对于从使用 SimpleCursorAdapter 实现 LoaderManager.LoaderCallbacks 的 ListActivity 派生的活动,它无法恢复 onReset() 中的位置,因为该活动几乎总是重新启动,并且在关闭详细信息视图时重新加载适配器。诀窍是在 onLoadFinished() 中恢复位置:
在 onListItemClick() 中:
// save the selected item position when an item was clicked
// to open the details
index = getListView().getFirstVisiblePosition();
View v = getListView().getChildAt(0);
top = (v == null) ? 0 : (v.getTop() - getListView().getPaddingTop());
在 onLoadFinished() 中:
// restore the selected item which was saved on item click
// when details are closed and list is shown again
getListView().setSelectionFromTop(index, top);
在 onBackPressed() 中:
// Show the top item at next start of the app
index = 0;
top = 0;
【讨论】:
【参考方案16】:这里提供的解决方案似乎都不适合我。就我而言,我在Fragment
中有一个ListView
,我将在FragmentTransaction
中替换它,因此每次显示片段时都会创建一个新的Fragment
实例,这意味着ListView
状态不能作为Fragment
的成员存储。
相反,我最终将状态存储在我的自定义 Application
类中。下面的代码应该让您了解它是如何工作的:
public class MyApplication extends Application
public static HashMap<String, Parcelable> parcelableCache = new HashMap<>();
/* ... code omitted for brevity ... */
public class MyFragment extends Fragment
private ListView mListView = null;
private MyAdapter mAdapter = null;
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState)
super.onViewCreated(view, savedInstanceState);
mAdapter = new MyAdapter(getActivity(), null, 0);
mListView = ((ListView) view.findViewById(R.id.myListView));
Parcelable listViewState = MyApplication.parcelableCache.get("my_listview_state");
if( listViewState != null )
mListView.onRestoreInstanceState(listViewState);
@Override
public void onPause()
MyApplication.parcelableCache.put("my_listview_state", mListView.onSaveInstanceState());
super.onPause();
/* ... code omitted for brevity ... */
基本思想是将状态存储在片段实例之外。如果您不喜欢在应用程序类中使用静态字段的想法,我想您可以通过实现片段接口并将状态存储在您的活动中来做到这一点。
另一种解决方案是将其存储在 SharedPreferences
中,但它会变得有点复杂,您需要确保在应用程序启动时清除它,除非您希望状态在应用程序启动时保持不变。
另外,为避免“第一项可见时未保存滚动位置”,您可以显示具有0px
高度的虚拟第一项。这可以通过覆盖适配器中的getView()
来实现,如下所示:
@Override
public View getView(int position, View convertView, ViewGroup parent)
if( position == 0 )
View zeroHeightView = new View(parent.getContext());
zeroHeightView.setLayoutParams(new ViewGroup.LayoutParams(0, 0));
return zeroHeightView;
else
return super.getView(position, convertView, parent);
【讨论】:
【参考方案17】:我的答案是针对 Firebase,位置 0 是一种解决方法
Parcelable state;
DatabaseReference everybody = db.getReference("Everybody Room List");
everybody.addValueEventListener(new ValueEventListener()
@Override
public void onDataChange(@NonNull DataSnapshot dataSnapshot)
state = listView.onSaveInstanceState(); // Save
progressBar.setVisibility(View.GONE);
arrayList.clear();
for (DataSnapshot messageSnapshot : dataSnapshot.getChildren())
Messages messagesSpacecraft = messageSnapshot.getValue(Messages.class);
arrayList.add(messagesSpacecraft);
listView.setAdapter(convertView);
listView.onRestoreInstanceState(state); // Restore
@Override
public void onCancelled(@NonNull DatabaseError databaseError)
);
和转换视图
位置 0 a 添加一个你不使用的空白项
public class Chat_ConvertView_List_Room extends BaseAdapter
private ArrayList<Messages> spacecrafts;
private Context context;
@SuppressLint("CommitPrefEdits")
Chat_ConvertView_List_Room(Context context, ArrayList<Messages> spacecrafts)
this.context = context;
this.spacecrafts = spacecrafts;
@Override
public int getCount()
return spacecrafts.size();
@Override
public Object getItem(int position)
return spacecrafts.get(position);
@Override
public long getItemId(int position)
return position;
@SuppressLint("SetTextI18n", "SimpleDateFormat")
@Override
public View getView(final int position, View convertView, ViewGroup parent)
if (convertView == null)
convertView = LayoutInflater.from(context).inflate(R.layout.message_model_list_room, parent, false);
final Messages s = (Messages) this.getItem(position);
if (position == 0)
convertView.getLayoutParams().height = 1; // 0 does not work
else
convertView.getLayoutParams().height = RelativeLayout.LayoutParams.WRAP_CONTENT;
return convertView;
我暂时看到了这个作品,没有打扰到用户,希望对你有用
【讨论】:
【参考方案18】:使用下面的代码:
int index,top;
@Override
protected void onPause()
super.onPause();
index = mList.getFirstVisiblePosition();
View v = challengeList.getChildAt(0);
top = (v == null) ? 0 : (v.getTop() - mList.getPaddingTop());
每当您刷新数据时,请使用以下代码:
adapter.notifyDataSetChanged();
mList.setSelectionFromTop(index, top);
【讨论】:
【参考方案19】:我正在使用 FirebaseListAdapter,但无法让任何解决方案发挥作用。我最终这样做了。我猜还有更优雅的方法,但这是一个完整且有效的解决方案。
onCreate 之前:
private int reset;
private int top;
private int index;
FirebaseListAdapter 内部:
@Override
public void onDataChanged()
super.onDataChanged();
// Only do this on first change, when starting
// activity or coming back to it.
if(reset == 0)
mListView.setSelectionFromTop(index, top);
reset++;
onStart:
@Override
protected void onStart()
super.onStart();
if(adapter != null)
adapter.startListening();
index = 0;
top = 0;
// Get position from SharedPrefs
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
top = sharedPref.getInt("TOP_POSITION", 0);
index = sharedPref.getInt("INDEX_POSITION", 0);
// Set reset to 0 to allow change to last position
reset = 0;
onStop:
@Override
protected void onStop()
super.onStop();
if(adapter != null)
adapter.stopListening();
// Set position
index = mListView.getFirstVisiblePosition();
View v = mListView.getChildAt(0);
top = (v == null) ? 0 : (v.getTop() - mListView.getPaddingTop());
// Save position to SharedPrefs
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
sharedPref.edit().putInt("TOP_POSITION" + "", top).apply();
sharedPref.edit().putInt("INDEX_POSITION" + "", index).apply();
由于我还必须为 FirebaseRecyclerAdapter 解决此问题,因此我也在此处发布解决方案:
onCreate 之前:
private int reset;
private int top;
private int index;
FirebaseRecyclerAdapter 内部:
@Override
public void onDataChanged()
// Only do this on first change, when starting
// activity or coming back to it.
if(reset == 0)
linearLayoutManager.scrollToPositionWithOffset(index, top);
reset++;
onStart:
@Override
protected void onStart()
super.onStart();
if(adapter != null)
adapter.startListening();
index = 0;
top = 0;
// Get position from SharedPrefs
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
top = sharedPref.getInt("TOP_POSITION", 0);
index = sharedPref.getInt("INDEX_POSITION", 0);
// Set reset to 0 to allow change to last position
reset = 0;
onStop:
@Override
protected void onStop()
super.onStop();
if(adapter != null)
adapter.stopListening();
// Set position
index = linearLayoutManager.findFirstVisibleItemPosition();
View v = linearLayoutManager.getChildAt(0);
top = (v == null) ? 0 : (v.getTop() - linearLayoutManager.getPaddingTop());
// Save position to SharedPrefs
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
sharedPref.edit().putInt("TOP_POSITION" + "", top).apply();
sharedPref.edit().putInt("INDEX_POSITION" + "", index).apply();
【讨论】:
【参考方案20】:澄清Ryan Newsom 的出色答案并针对片段进行调整,以及我们希望从“主”ListView 片段导航到“详细信息”片段然后返回“主”片段的常见情况
private View root;
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
if(root == null)
root = inflater.inflate(R.layout.myfragmentid,container,false);
InitializeView();
return root;
public void InitializeView()
ListView listView = (ListView)root.findViewById(R.id.listviewid);
BaseAdapter adapter = CreateAdapter();//Create your adapter here
listView.setAdpater(adapter);
//other initialization code
这里的“魔力”在于,当我们从详细信息片段导航回 ListView 片段时,不会重新创建视图,也不会设置 ListView 的适配器,所以一切都保持原样!
【讨论】:
如果您从服务器加载数据,则无法正常工作以上是关于返回 ListView 时保持/保存/恢复滚动位置的主要内容,如果未能解决你的问题,请参考以下文章
java [Android]返回ListView时保存滚动位置
java [Android]返回ListView 2时保存滚动位置
如何在应用重新启动之间保存和恢复 Flutter ListView 的滚动位置?
更新 SimpleCursorAdapter,同时保持 ListView 中的滚动位置