回答:
これを試して:
// 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
ます。
int index = mList.getFirstVisiblePosition();
1行、復元に1行のみを使用することで、さらに簡単にすることができますmList.setSelectionFromTop(index, 0);
。すばらしい回答ですが(+1)!私はこの問題のエレガントな解決策を探していました。
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);
}
}
@(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」は、DBからデータを取得してリストに配置するための関数です。
私のFroyoデバイスでは、これは電話の向きを変更したときと、アイテムを編集してリストに戻ったときの両方で機能します。
mListState = getListView().onSaveInstanceState().
主にデバイスのローテーションで呼び出すときにコンテンツビューが作成されないため、クラッシュします。
onRestoreInstanceState
呼び出されることは決してない:(
非常に簡単な方法:
/** 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を試しても機能しない(少なくとも一部のデバイスでは)
これは、単純明快なはずの何かに対する非常に奇妙な回避策です。
setSelectionFromTop()
動作しません。
注意!!ListView.getFirstVisiblePosition()が0の場合、onSaveState()が正しく機能しないというAbsListViewのバグがあります。
したがって、画面の大部分を占める大きな画像があり、2番目の画像までスクロールしたが、最初の画像の一部が表示されている場合、スクロール位置は保存されません...
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に設定し、問題を解決しました。=)
誰もこれに言及しなかったので驚いたので、これを投稿しています。
ユーザーが[戻る]ボタンをクリックした後、リストビューから出たときと同じ状態でリストビューに戻ります。
このコードは「上」ボタンをオーバーライドして「戻る」ボタンと同じように動作するため、リストビュー->詳細->リストビューに戻る(他のオプションなし)の場合、これはスクロール位置とコンテンツを維持する最も簡単なコードです。リストビューで。
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);
}
});
あなたはポストとスレッドで電話しなければなりません!
リロードする前に状態を保存し、後で復元すると、リロード後のスクロール状態を維持できます。私の場合、非同期ネットワークリクエストを作成し、完了後にコールバックでリストをリロードしました。これが状態を復元する場所です。コードサンプルはKotlinです。
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はバグがあるに述べたように:1つの注意点を除いてちょうどよく自分自身でリストア細かいスクロール位置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
復元する簡単なサンプルプロジェクトです。/復元。細かいスクロール位置は、一時的に他のアプリケーションに切り替えたり、画面を2度回転させたり、テストアプリケーションに切り替えたりする複雑なシナリオでも完全に復元されます。アプリケーションを明示的に終了する([戻る]ボタンを押す)場合、スクロール位置は保存されません(他のすべてのビューも状態を保存しません)。
https://github.com/voromto/RestoreScrollPosition/releases
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;
ここで提供されているソリューションはどちらも私にはうまくいかないようです。私の場合、私はで置き換えているListView
in を持っているので、フラグメントが表示されるたびに新しいインスタンスが作成されます。これは、状態をのメンバーとして保存できないことを意味します。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);
}
私の答えは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) {
}
});
そしてconvertView
位置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;
}
}
ユーザーの邪魔をせずにこの作品を一時的に見たことがあります。
以下のコードを使用してください:
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();
}
}
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のアダプターを設定しないため、すべてが残ったままになります。