返回 ListView 时保持/保存/恢复滚动位置
-
26-09-2019 - |
题
我有一个长 ListView
用户可以在返回上一屏幕之前滚动浏览。当用户打开这个 ListView
再次,我希望列表滚动到与之前相同的位置。关于如何实现这一目标有什么想法吗?
解决方案
尝试这种情况:
// 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()
返回顶部可见列表项。但是,这个项目可以部分地滚出视,如果你想恢复列表的确切滚动位置,你需要得到这个偏移量。所以ListView.getChildAt(0)
返回顶部列表项View
,然后View.getTop() - mList.getPaddingTop()
回到其相对从ListView
的顶偏。然后,恢复ListView
的滚动位置,我们称之为ListView.setSelectionFromTop()
与我们想要的项目的偏移量从ListView
的顶部位置的顶部边缘的指数和。
其他提示
Parcelable state;
@Override
public void onPause() {
// Save ListView state @ onPause
Log.d(TAG, "saving listview state @ onPause");
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);
}
}
我通过@(柯克沃尔)建议的解决方案,它为我工作。我也看到在“联系人”应用程序的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的装置,这两个作品,当你改变手机方向,当你编辑项目,并返回到列表中。
一个非常简单的方法:
/** 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和scrolltoXY,但它并没有在所有的工作中,依然名单在同一位置,一些试验和错误后,我得到了下面的代码,不工作
final ListView list = (ListView) findViewById(R.id.list);
list.post(new Runnable() {
@Override
public void run() {
list.setSelection(0);
}
});
如果不是张贴了Runnable尝试runOnUiThread它不工作是(至少在某些设备上)
这是一件应该直截了当一个很奇怪的解决方法。
小心!有在AbsListView一个错误,不允许onSaveState()来正确地工作,如果ListView.getFirstVisiblePosition()为0
所以,如果你有一个占据了屏幕的大部分大型图片和滚动到第二图像,但第一的很少显示,滚动位置将不被保存...
这的 AbsListView.java:1650 强>(注释矿)
// this will be false when the firstPosition IS 0
if (haveChildren && mFirstPosition > 0) {
...
} else {
ss.viewTop = 0;
ss.firstId = INVALID_POSITION;
ss.position = 0;
}
但是,在这种情况下,在代码中“顶部”下面将引起该防止状态,以正确地恢复的其他问题的负数。因此,当“顶”为负,获得下一子
// 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);
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);
}
}
}
够
对于一些寻找一个解决这个问题,这个问题的根源可能是你在哪里设置你的列表视图适配器。当您设置适配器上的列表视图,它重置滚动位置。只是要考虑的问题。我感动设置适配器插入我的onCreateView我们抢参考列表视图后,它解决了这个问题对我来说。 =)
上午张贴这是因为我很惊讶没有人提到这一点。
用户点击返回按钮,他将返回到ListView在相同的状态,因为他去的出来之后。
此代码将覆盖“向上”按钮的行为方式与后退按钮一样的,所以在列表视图的情况下 - >详细信息 - >返回列表视图(并且没有其他选项),这是维持的scrollPosition最简单的代码和在列表视图的内容。
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return(true);
}
return(super.onOptionsItemSelected(item)); }
注意:如果你可以去从细节上活动的另一个活动的向上键将返回你回到那个活动,所以你将不得不操纵后退按钮历史在为了这个工作。
不简单 android:saveEnabled="true"
在ListView xml中声明足够吗?
<强>最佳解决方案是:强>
// 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);
}
});
您必须拨打邮政与螺纹!
您可以在重装后保持滚动状态如果保存的状态重新加载之前和之后的恢复。在我来说,我做了一个异步网络请求并重新加载列表中的回调也就完成了。这是我恢复状态。代码示例是科特林。
val state = myList.layoutManager.onSaveInstanceState()
getNewThings() { newThings: List<Thing> ->
myList.adapter.things = newThings
myList.layoutManager.onRestoreInstanceState(state)
}
如果您正在使用托管在一个活动的片段,你可以做这样的事情:
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());
}
}
}
如果您正在保存/恢复滚动位置 ListView
你自己本质上是在复制 android 框架中已经实现的功能。这 ListView
除了一个警告之外,它本身就能很好地恢复精细滚动位置:正如 @aaronvargas 提到的,有一个错误 AbsListView
这不会让恢复第一个列表项的精细滚动位置。然而,恢复滚动位置的最佳方法不是恢复它。Android框架会为你做得更好。只需确保您满足以下条件:
- 确保您没有打电话
setSaveEnabled(false)
方法并没有设置android:saveEnabled="false"
xml 布局文件中列表的属性 - 为了
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
它是独一无二的。
为了避免上述第一个列表项的警告,您可以以返回特殊虚拟零像素高度视图的方式制作适配器。 ListView
在位置 0。这是简单的示例项目显示 ListView
和 ExpandableListView
恢复它们的精细滚动位置,而它们的滚动位置没有显式保存/恢复。即使对于临时切换到其他应用程序、双屏旋转和切换回测试应用程序的复杂场景,精细的滚动位置也能完美恢复。请注意,如果您明确退出应用程序(通过按“后退”按钮),则不会保存滚动位置(并且所有其他视图也不会保存其状态)。https://github.com/voromto/RestoreScrollPosition/releases
有关从ListActivity得出,使用SimpleCursorAdapter器具LoaderManager.LoaderCallbacks它不工作,以恢复在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;
无论是这里提供的解决方案,似乎为我工作。在我的情况下,我具有其中我替换一个ListView
一个Fragment
一个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);
}
我的回答为火力地堡和位置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) {
}
});
和convertView
位置0添加不使用空白项目
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;
}
}
我暂时看到了这个工作,而不打扰用户,我希望它为你的作品
使用此代码如下:
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);
我使用的 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();
}
}