Android 返回ListView时保持/保存/恢复滚动位置

Android 返回ListView时保持/保存/恢复滚动位置,android,android-listview,scroll,scroll-position,Android,Android Listview,Scroll,Scroll Position,我有一个长的列表视图,用户可以在返回上一个屏幕之前滚动。当用户再次打开此ListView时,我希望列表滚动到与以前相同的位置。关于如何实现这一点有什么想法吗?一个非常简单的方法: /** Save the position **/ int currentPosition = listView.getFirstVisiblePosition(); //Here u should save the currentPosition anywhere /** Restore the previus

我有一个长的
列表视图
,用户可以在返回上一个屏幕之前滚动。当用户再次打开此
ListView
时,我希望列表滚动到与以前相同的位置。关于如何实现这一点有什么想法吗?

一个非常简单的方法:

/** Save the position **/
int currentPosition = listView.getFirstVisiblePosition();

//Here u should save the currentPosition anywhere

/** Restore the previus saved position **/
listView.setSelection(savedPosition);
该方法将列表重置为提供的项。如果不在触摸模式下,项目将实际被选中。如果在触摸模式下,项目将仅定位在屏幕上

更复杂的方法是:

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);
试试这个:

//保存索引和顶部位置
int index=mList.getFirstVisiblePosition();
视图v=mList.getChildAt(0);
int top=(v==null)?0:(v.getTop()-mList.getPaddingTop());
// ...
//恢复索引和位置
mList.setSelectionFromTop(索引,顶部);
说明:


ListView.getFirstVisiblePosition()
返回顶部可见的列表项。但此项可能会部分滚动到视图之外,如果要恢复列表的确切滚动位置,则需要获取此偏移量。因此
ListView.getChildAt(0)
返回顶部列表项的
视图
,然后
View.getTop()-mList.getPaddingTop()
返回其相对于
ListView
顶部的相对偏移量。然后,为了恢复
列表视图的滚动位置,我们调用
列表视图.setSelectionFromTop()
,其中包含我们想要的项目的索引和一个偏移量,以将其上边缘定位到
列表视图的顶部,我采用了@(Kirk Woll)建议的解决方案,它对我有效。我还看到“联系人”应用程序的Android源代码中使用了类似的技术。我想补充一些细节:
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);
    }
}
在我的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设备上,当您更改手机方向时,以及当您编辑一个项目并返回列表时,都可以使用此功能。

我发现了一些有趣的地方

我尝试了setSelection和scrolltoXY,但它根本不起作用,列表仍然保持在相同的位置,经过一些尝试和错误后,我得到了下面的代码

final ListView list = (ListView) findViewById(R.id.list);
list.post(new Runnable() {            
    @Override
    public void run() {
        list.setSelection(0);
    }
});
如果您尝试RunNouithRead而不是发布Runnable,则它也不起作用(至少在某些设备上)


这是一个非常奇怪的解决办法,应该是直截了当的。

小心!!如果ListView.getFirstVisiblePosition()为0,则AbsListView中存在一个错误,不允许onSaveState()正常工作

因此,如果您有占据屏幕大部分的大图像,并且您滚动到第二个图像,但第一个图像的一小部分正在显示,那么滚动位置将不会被保存

AbsListView.java:1650(我的评论)

但是在这种情况下,下面代码中的“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);
//保存索引和顶部位置
int index=getFirstVisiblePosition();
视图v=getChildAt(0);
int top=(v==null)?0:v.getTop();
if(top<0&&getChildAt(1)!=null){
索引++;
v=getChildAt(1);
top=v.getTop();
}
//将索引和顶部包裹起来
//恢复时,取消连接索引和顶部
listView.setSelectionFromTop(索引,顶部);

我之所以发布这篇文章,是因为我很惊讶没有人提到这一点

用户单击“后退”按钮后,将以退出列表视图时的相同状态返回列表视图

此代码将覆盖“向上”按钮,使其行为方式与后退按钮相同,因此在Listview->Details->back to Listview(无其他选项)的情况下,这是维护Listview中滚动位置和内容的最简单代码

 public boolean onOptionsItemSelected(MenuItem item) {
     switch (item.getItemId()) {
         case android.R.id.home:
             onBackPressed();
             return(true);
     }
     return(super.onOptionsItemSelected(item)); }

警告:如果您可以从“详细信息”活动转到另一个活动,则“向上”按钮会将您返回到该活动,因此您必须操作backbutton历史记录才能使其正常工作

在ListView xml声明中不仅仅是
android:saveEnabled=“true”
就足够了吗?

如果您使用活动中托管的片段,您可以执行以下操作:

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());
          }
     }
}
@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);
}
公共抽象类BaseFragment扩展了Fragment{
私有布尔值mSaveView=false;
私有软参考mViewReference;
@凌驾
CreateView上的公共视图(布局、充气机、视图组容器、捆绑包保存状态){
如果(mSaveView){
if(mViewReference!=null){
最终视图savedView=mViewReference.get();
if(savedView!=null){
if(savedView.getParent()!=null){
((视图组)savedView.getParent()).removeView(savedView);
返回savedView;
}
}
}
}
最终视图=充气机。充气(getFragmentResource(),容器,false);
mViewReference=新的软参考(视图);
返回视图;
}
受保护的void setSaveView(布尔值){
mSaveView=值;
}
}
公共类MyFragment扩展了BaseFragment{
@凌驾
CreateView上的公共视图(布局、充气机、视图组容器、捆绑包保存状态){
设置保存视图(true);
最终视图=super.onCreateView(充气机、容器、保存状态);
ListView placesList=(ListView)view.findViewById(R.id.places\u列表);
if(placesList.getAdapter()==null){
setAdapter(createAdapter());
}
}
}

这就足够了

如果你自己保存/恢复
列表视图的滚动位置
你实际上是在复制android fram中已经实现的功能
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);
        }
    }
}
@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;
}
// 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);
   }
});
// 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());
// restore the selected item which was saved on item click
// when details are closed and list is shown again
getListView().setSelectionFromTop(index, top);
// Show the top item at next start of the app
index = 0;
top = 0;
val state = myList.layoutManager.onSaveInstanceState()

getNewThings() { newThings: List<Thing> ->

    myList.adapter.things = newThings
    myList.layoutManager.onRestoreInstanceState(state)
}
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 ... */

}
@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);
}
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) {
        }
    });
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);
private int reset;
private int top;
private int index;
@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++;
     }

 }
@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;
    }
}
@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();
    }
}
private int reset;
private int top;
private int index;
@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++;
    }
}
@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;
    }
}
@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();
    }
}
    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
    }