รักษา / บันทึก / เรียกคืนตำแหน่งการเลื่อนเมื่อกลับไปที่ ListView


397

ฉันมีความยาวListViewที่ผู้ใช้สามารถเลื่อนไปมาก่อนที่จะกลับไปที่หน้าจอก่อนหน้า เมื่อผู้ใช้เปิดListViewอีกครั้งฉันต้องการให้รายการเลื่อนไปยังจุดเดียวกับที่เคยเป็นก่อนหน้านี้ ความคิดใด ๆ เกี่ยวกับวิธีการบรรลุสิ่งนี้?


22
ผมคิดว่าการแก้ปัญหาดังกล่าวโดยยูจีน Mymrin / Giorgio Barchiesi จะดีกว่าคำตอบที่ได้รับการยอมรับ
ไมราเวลเลอร์

คำตอบ:


631

ลองสิ่งนี้:

// 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's เลื่อนตำแหน่งที่เราเรียกว่ามีค่าดัชนีของรายการที่เราต้องการและชดเชยการวางตำแหน่งขอบด้านบนจากด้านบนของที่ListView.setSelectionFromTop()ListView


2
ไม่เข้าใจว่ามันใช้งานอย่างไร ฉันใช้ listView.getFirstVisiblePosition () เท่านั้นและเข้าใจสิ่งนั้น แต่ไม่แน่ใจว่าเกิดอะไรขึ้นที่นี่ โอกาสของคำอธิบายสั้น ๆ ? :)
HXCaine

56
ดังนั้น ListView.getFirstVisiblePosition () จะส่งคืนรายการที่มองเห็นได้ด้านบน แต่รายการนี้อาจถูกเลื่อนบางส่วนออกจากมุมมองและถ้าคุณต้องการคืนค่าตำแหน่งการเลื่อนที่แน่นอนของรายการคุณจะต้องได้รับการชดเชยนี้ ดังนั้น ListView.getChildAt (0) จะคืนค่ามุมมองสำหรับรายการบนสุดจากนั้น View.getTop () จะส่งคืนออฟเซ็ตสัมพัทธ์จากด้านบนของ ListView จากนั้นในการคืนค่าตำแหน่งเลื่อนของ ListView เราเรียกใช้ ListView.setSelectionFromTop () พร้อมกับดัชนีของรายการที่เราต้องการและออฟเซ็ตเพื่อจัดตำแหน่งขอบด้านบนจากด้านบนของ ListView ชัดเจนหรือไม่
เอียน

15
นี้สามารถทำได้ง่ายจะใช้เส้นหนึ่งที่จะบันทึก: และเพียงหนึ่งเส้นที่จะเรียกคืน:int index = mList.getFirstVisiblePosition(); mList.setSelectionFromTop(index, 0);คำตอบที่ดี (+1)! ฉันได้รับการมองหาทางออกที่สง่างามสำหรับปัญหานี้
Phil

17
@Phil วิธีแก้ปัญหาแบบง่าย ๆ ของคุณไม่ทำงานเพื่อคืนค่าตำแหน่งการเลื่อนที่แน่นอน
Ixx

2
ตามที่ @nbarraille กล่าวว่ารหัสควรอ่านเพิ่มเติมเช่น "v.getTop () - mList.getPaddingTop ()" ไม่เช่นนั้นคุณจะใช้เวลาหนึ่งชั่วโมงเหมือนที่ฉันพยายามหาสาเหตุว่าทำไมมันถึงทำให้ผมร่วงอีกครั้ง ...
jdowdell

544
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);
    }
}

106
ฉันคิดว่านี่เป็นคำตอบที่ดีที่สุดเนื่องจากวิธีนี้คืนค่าตำแหน่งการเลื่อนที่แน่นอนและไม่เพียง แต่รายการแรกที่มองเห็นได้
Mira Weller

9
คำตอบที่ยอดเยี่ยม แต่จะไม่ทำงานเมื่อโหลดเนื้อหาแบบไดนามิกและคุณต้องการบันทึกสถานะในเมธอด onPause ()
Gio

13
ทางออกที่ดี ปัญหาเดียว หากรายการมีขนาดใหญ่และคุณเลื่อน แต่รายการแรกยังคงมองเห็นได้รายการจะข้ามไปด้านบน ถ้าคุณเลื่อนไปยังรายการที่สองหรืออื่นใดการทำงานทุกอย่าง
passsy

4
@passsy คุณเคยพบวิธีแก้ปัญหารายการกระโดดกลับไปด้านบนหรือไม่?
Papajohn000

2
ตามที่ aaronvargas กล่าวสิ่งนี้ใช้ไม่ได้เมื่อ ListView.getFirstVisiblePosition () คือ 0
VinceStyling

54

ฉันนำโซลูชันที่แนะนำโดย @ (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 ของฉันสิ่งนี้ใช้ได้ทั้งเมื่อคุณเปลี่ยนการวางแนวโทรศัพท์และเมื่อคุณแก้ไขรายการแล้วกลับไปที่รายการ


1
วิธีนี้ใช้ได้ผลสำหรับฉัน คนอื่นไม่ได้ การบันทึก / เรียกคืนสถานะอินสแตนซ์ ListView ทำงานอย่างไรเกี่ยวกับการลบและแทรกรายการใน ListView
ไบรอัน

2
FYI ถ้าคุณใช้สิ่งนี้ในส่วน (ฉันกำลังใช้แฟรกเมนต์รายการ) และนำทางไปยังแฟรกเมนต์อื่นสิ่งนี้จะล้มเหลวเนื่องจากมุมมองเนื้อหาไม่ได้ถูกสร้างขึ้นเมื่อเรียกmListState = getListView().onSaveInstanceState().ส่วนใหญ่เกี่ยวกับการหมุนอุปกรณ์
Mgamerz

2
onRestoreInstanceStateไม่เคยถูกเรียกว่า :(
dVaffection

1
@GiorgioBarchiesi ใครคือ @ (เคิร์กวูล) โซลูชันที่เหมาะกับคุณ เห็นได้ชัดว่าผู้ใช้เปลี่ยนชื่อของเขา / เธอ
Piovezan

1
ใช่นั่นคือศาสนาที่เป็นทางการ แต่ในกรณีของฉันข้อมูลถูกโหลดภายในเครื่องมีความยาว จำกัด และโหลดได้ในเสี้ยววินาทีดังนั้นฉันจึงไป :-) erethic
Giorgio Barchiesi

26

วิธีที่ง่ายมาก:

/** 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);

2
มีข้อผิดพลาดในคำตอบของคุณ วิธีการนี้เรียกว่า setSelection
Janusz

ความคิดทำงานได้ดี แต่มีปัญหาหลอกตำแหน่งที่มองเห็นได้ครั้งแรกอาจแสดงเพียงเล็กน้อย (อาจเป็นเพียงส่วนเล็ก ๆ ของแถว) และ setSelection () ตั้งค่าแถวนี้ให้มองเห็นได้อย่างสมบูรณ์เมื่อการคืนค่าอยู่ ทำ
rantravee

โปรดดูตัวเลือกที่สองของฉัน ฉันเพิ่งเพิ่มไปยังคำตอบแรกของฉัน
Francesco Laurita

27
view.getScrollX () และ view.getScrollY () ส่งคืน 0 เสมอ!
rantravee

3
ดูโซลูชันของฉัน (ยอมรับ) ด้านบนโดยใช้ View.getChildAt () และ View.getTop () คุณไม่สามารถใช้ View.getScrollY () ใน ListView ได้เนื่องจาก ListView จะรักษาการเลื่อนภายใน
เอียน

17

ฉันพบสิ่งที่น่าสนใจเกี่ยวกับสิ่งนี้

ฉันลอง setSelection และ scrolltoXY แต่มันไม่ทำงานเลยรายการยังคงอยู่ในตำแหน่งเดิมหลังจากการทดลองและข้อผิดพลาดฉันได้รับรหัสต่อไปนี้ที่ใช้งานได้

final ListView list = (ListView) findViewById(R.id.list);
list.post(new Runnable() {            
    @Override
    public void run() {
        list.setSelection(0);
    }
});

ถ้าแทนที่จะโพสต์ Runnable คุณลอง runOnUiThread มันไม่ทำงาน (อย่างน้อยในบางอุปกรณ์)

นี่เป็นวิธีแก้ปัญหาที่แปลกมากสำหรับบางสิ่งที่ควรตรงไป


1
ฉันพบปัญหาเดียวกัน! และsetSelectionFromTop()ไม่ทำงาน
ofavre

+1 listnew.post () ไม่ทำงานบนอุปกรณ์บางชนิดและในอุปกรณ์อื่น ๆ บางอย่างมันไม่ทำงานในบางกรณี แต่ runOnUIThread ทำงานได้ดี
Omar Rehman

@Omar คุณสามารถยกตัวอย่างของอุปกรณ์และระบบปฏิบัติการที่ไม่สามารถใช้งานได้หรือไม่ ฉันลองใช้ HTC G2 (2.3.4) และ Galaxy Nexus (4.1.2) แล้วมันก็ใช้ได้ทั้งคู่ ไม่มีคำตอบอื่นใดในกระทู้นี้ที่ใช้ได้กับอุปกรณ์ใด ๆ ของฉัน
Dan J

2
listview.post ทำงานได้ดีบน Galaxy Note ของฉันกับ 4.0.4 แต่ไม่ทำงานบน Nexus One ที่มี 2.3.6 อย่างไรก็ตาม onOnUIThread (การกระทำ) ทำงานได้ดีบนอุปกรณ์ทั้งสอง
โอมาร์เรห์แมน

11

ข้อควรระวัง !! มีข้อผิดพลาดใน 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);

มันใช้งานได้เหมือนมีเสน่ห์ แต่ฉันก็ไม่เข้าใจ หากไอเท็มแรกยังคงมองเห็นได้จะทำให้เด็กคนถัดไปรู้สึกอย่างไร ไม่ควร setSelectionFromTop ด้วยดัชนีใหม่ทำให้รายการนั้นเริ่มต้นจากลูกคนที่สอง? ฉันยังเห็นคนแรกได้อย่างไร
tafi

@tafi รายการแรกอาจปรากฏเป็น 'บางส่วน' ดังนั้นด้านบนของรายการนั้นจะอยู่เหนือหน้าจอและจะเป็นจำนวนลบ (นี่เป็นสาเหตุของปัญหาอื่น ๆ ที่ฉันจำไม่ได้ ... ) ดังนั้นเราจึงใช้ 'อันดับสูงสุด' ของรายการที่สองสำหรับการจัดวางซึ่งควรจะมีอยู่เสมอ (?) หรือจะไม่มีการเลื่อนในครั้งแรก !
aaronvargas

6
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 ไม่ถูกบันทึกระหว่างกิจกรรมหรือไม่ ฉันโพสต์คำถามต่อไปนี้ไว้ที่นี่: stackoverflow.com/questions/35413495/… ?
AJW

6

สำหรับบางคนที่กำลังมองหาวิธีแก้ไขปัญหานี้รากของปัญหาอาจเป็นที่ที่คุณกำลังตั้งค่าอะแด็ปเตอร์มุมมองรายการของคุณ หลังจากที่คุณตั้งค่าการ์ดเชื่อมต่อใน listview มันจะรีเซ็ตตำแหน่งการเลื่อน สิ่งที่ต้องพิจารณา ฉันย้ายการตั้งค่าอะแดปเตอร์ไปที่ onCreateView ของฉันหลังจากที่เราทำการอ้างอิงไปยัง listview และมันแก้ปัญหาให้ฉันได้ =)


1

ฉันกำลังโพสต์สิ่งนี้เพราะฉันประหลาดใจที่ไม่มีใครพูดถึงเรื่องนี้

หลังจากผู้ใช้คลิกปุ่มย้อนกลับเขาจะกลับไปที่ listview ในสถานะเดียวกับที่เขาออกไป

รหัสนี้จะแทนที่ปุ่ม "up" เพื่อทำงานในลักษณะเดียวกับปุ่มย้อนกลับในกรณีของ Listview -> Details -> Back to Listview (และไม่มีตัวเลือกอื่น ๆ ) นี่เป็นรหัสที่ง่ายที่สุดในการรักษา scrollposition และเนื้อหา ในมุมมองรายการ

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

ข้อควรระวัง: หากคุณสามารถไปที่กิจกรรมอื่นจากกิจกรรมรายละเอียดปุ่มขึ้นจะนำคุณกลับสู่กิจกรรมนั้นดังนั้นคุณจะต้องจัดการประวัติย้อนกลับเพื่อให้สิ่งนี้ทำงานได้


1

ไม่ได้เป็นเพียงแค่android:saveEnabled="true"ใน ListView XML ประกาศเพียงพอหรือไม่


1
android: saveEnabled ถูกตั้งค่าเป็นจริงโดยค่าเริ่มต้น มันไม่ได้ทำอะไรเลย
แบรด

ไม่ ยังไม่พอคุณต้องติดตามstackoverflow.com/questions/3014089/…หรือstackoverflow.com/questions/3014089/…
Rahul Mandaliya

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.post(new Runnable() {
    @Override
    public void run() {
      mList.setSelectionFromTop(index, top);
   }
});

คุณต้องโทรในโพสต์และในกระทู้!


1

คุณสามารถรักษาสถานะการเลื่อนหลังจากโหลดใหม่ได้หากคุณบันทึกสถานะก่อนที่จะโหลดซ้ำและกู้คืนหลังจากนั้น ในกรณีของฉันฉันทำการร้องขอเครือข่ายแบบอะซิงโครนัสและโหลดรายการในการติดต่อกลับหลังจากเสร็จสิ้น นี่คือที่ฉันเรียกคืนสถานะ ตัวอย่างโค้ดคือ Kotlin

val state = myList.layoutManager.onSaveInstanceState()

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

    myList.adapter.things = newThings
    myList.layoutManager.onRestoreInstanceState(state)
}

0

หากคุณใช้แฟรกเมนต์ที่โฮสต์ในกิจกรรมคุณสามารถทำสิ่งนี้ได้:

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());
          }
     }
}

0

หากคุณกำลังบันทึก / กู้คืนตำแหน่งการเลื่อนของListViewตัวคุณเองคุณกำลังทำซ้ำฟังก์ชั่นการใช้งานแล้วในกรอบ Android การListViewคืนค่าตำแหน่งการเลื่อนที่ดีเพียงอย่างเดียวของตัวเองยกเว้นหนึ่ง caveat: @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


0

สำหรับกิจกรรมที่ได้รับจาก ListActivity ที่ใช้ LoaderManager.LoaderCallbacks โดยใช้ SimpleCursorAdapter มันไม่ทำงานเพื่อกู้คืนตำแหน่งใน 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;

0

วิธีแก้ปัญหาที่เสนอที่นี่ดูเหมือนจะไม่เหมาะกับฉัน ในกรณีของฉันฉันมีListViewในFragmentที่ฉันเปลี่ยนในFragmentTransactionดังนั้นใหม่Fragmentเช่นจะถูกสร้างขึ้นในแต่ละครั้งส่วนจะแสดงซึ่งหมายความว่ารัฐไม่สามารถเก็บไว้ในฐานะสมาชิกของListViewFragment

แต่ฉันจบลงด้วยการจัดเก็บสถานะใน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

คำตอบของฉันคือ 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 เพิ่มรายการว่างที่คุณไม่ได้ใช้

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;
}
}

ฉันได้เห็นงานนี้เป็นการชั่วคราวโดยไม่รบกวนผู้ใช้ฉันหวังว่าจะได้ผลกับคุณ


0

ใช้รหัสด้านล่างนี้:

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);

0

ฉันใช้FirebaseListAdapterและไม่สามารถแก้ไขปัญหาได้ ในที่สุดฉันก็ลงมือทำสิ่งนี้ ฉันเดาว่ามันมีวิธีที่สง่างามกว่า แต่มันก็เป็นโซลูชั่นที่สมบูรณ์และใช้งานได้

ก่อนหน้านี้สร้าง:

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ฉันกำลังโพสต์โซลูชันที่นี่เช่นกัน:

ก่อนหน้านี้สร้าง:

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();
    }
}

0

เพื่อชี้แจงคำตอบที่ดีเยี่ยมของ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 ดังนั้นทุกอย่างยังคงอยู่ในขณะที่เราทิ้งไว้!

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.