답변:
이 시도:
// 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();
하고 한 줄만 사용하여 복원 하면 훨씬 간단 해집니다 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)에서 제안한 솔루션을 채택했으며 나에게 효과적입니다. "Contacts"앱의 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()
작동하지 않습니다.
주의!! AbsListView에는 ListView.getFirstVisiblePosition ()이 0 인 경우 onSaveState ()가 올바르게 작동하지 않는 버그가 있습니다.
따라서 대부분의 화면을 차지하는 큰 이미지가 있고 두 번째 이미지로 스크롤하지만 첫 번째 이미지 중 일부가 표시되면 스크롤 위치가 저장되지 않습니다 ...
에서 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);
}
}
}
충분 해
아무도 이것을 언급하지 않았기 때문에 이것을 게시하고 있습니다.
사용자가 뒤로 버튼을 클릭하면 목록에서 나가는 것과 동일한 상태로 목록보기로 돌아갑니다.
이 코드는 "위로"버튼을 무시하여 뒤로 버튼과 동일한 방식으로 작동하므로 Listview-> Details-> Listview로 돌아 가기 (및 다른 옵션은 없음)의 경우 스크롤 위치와 내용을 유지하는 가장 간단한 코드입니다. 목록보기에서.
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
자신의 스크롤 위치를 저장 / 복원하는 경우 이미 안드로이드 프레임 워크에서 이미 구현 된 기능을 복제하고 있습니다. ListView
복원 벌금 스크롤 위치 만 아니라 그 자체를 제외하고 한가지주의 : @aaronvargas가에 버그가 언급 한 바와 같이 AbsListView
그 첫 번째 목록 항목에 대한 정밀한 스크롤 위치를 복원하지 않습니다가. 그럼에도 불구하고 스크롤 위치를 복원하는 가장 좋은 방법은 스크롤 위치를 복원하지 않는 것입니다. 안드로이드 프레임 워크가 당신을 위해 더 잘 할 것입니다. 다음 조건을 충족하는지 확인하십시오.
setSaveEnabled(false)
메소드를 호출 하지 않고 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
있으며 유일하다.첫 번째 목록 항목으로 위에서 언급 한 경고를 피하기 위해 ListView
위치 0에서 특수 더미 0 픽셀 높이보기를 반환하는 방식으로 어댑터를 만들 수 있습니다. 다음은 프로젝트의 간단한 스크롤 위치를 보여 ListView
주고 ExpandableListView
복원하는 반면 스크롤 위치는 명시 적으로 저장되지 않습니다. / 복원 다른 응용 프로그램으로 임시 전환, 이중 화면 회전 및 테스트 응용 프로그램으로 다시 전환하는 복잡한 시나리오에서도 미세한 스크롤 위치가 완벽하게 복원됩니다. 뒤로 버튼을 눌러 응용 프로그램을 명시 적으로 종료하면 스크롤 위치가 저장되지 않으며 다른 모든 뷰는 상태를 저장하지 않습니다.
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
에서 Fragment
교체하는 in이 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;
}
}
중지 :
@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;
}
}
중지 :
@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의 어댑터를 설정하지 않으므로 모든 것이 그대로 유지됩니다.