Ben bir ListView
önceki ekrana dönmeden önce kullanıcı etrafında kaydırma uzun bir var. Kullanıcı bunu ListView
tekrar açtığında , listenin daha önce olduğu noktaya kaydırılmasını istiyorum. Bunu nasıl başaracağınıza dair bir fikrin var mı?
Ben bir ListView
önceki ekrana dönmeden önce kullanıcı etrafında kaydırma uzun bir var. Kullanıcı bunu ListView
tekrar açtığında , listenin daha önce olduğu noktaya kaydırılmasını istiyorum. Bunu nasıl başaracağınıza dair bir fikrin var mı?
Yanıtlar:
Bunu dene:
// 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);
Açıklama:
ListView.getFirstVisiblePosition()
en üstteki görünür liste öğesini döndürür. Ancak bu öğe kısmen görünmeyebilir ve listenin tam kaydırma konumunu geri yüklemek isterseniz bu ofseti almanız gerekir. Yani ListView.getChildAt(0)
döner View
üst liste öğesi için, sonra View.getTop() - mList.getPaddingTop()
üstünden ofset onun göreceli döndürür ListView
. Ardından, ListView
'nin kaydırma konumunu geri yüklemek için ListView.setSelectionFromTop()
istediğimiz öğenin dizinini ve üst kenarını üstünden konumlandırmak için bir ofset ile çağırırız ListView
.
int index = mList.getFirstVisiblePosition();
geri ve tek bir satır: mList.setSelectionFromTop(index, 0);
. Büyük cevap olsa (+1)! Bu soruna zarif bir çözüm arıyordum.
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) tarafından önerilen çözümü benimsedim ve bu benim için işe yarıyor. Ben de benzer bir tekniği kullandıklarını, "Rehber" uygulaması için Android kaynak kodunda gördük. Daha fazla ayrıntı eklemek istiyorum: ListActivity'den türetilen sınıfımda:
private static final String LIST_STATE = "listState";
private Parcelable mListState = null;
Sonra, bazı yöntem geçersiz kılar:
@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);
}
Tabii ki "loadData" DB veri almak ve listeye koymak benim işlevim.
Froyo cihazımda, bu hem telefon yönünü değiştirdiğinizde hem de bir öğeyi düzenleyip listeye geri döndüğünüzde çalışır.
mListState = getListView().onSaveInstanceState().
temelde cihaz döndürme çağrılırken içerik görünümü oluşturulmadığı için bu çökecektir.
onRestoreInstanceState
asla çağrılmaz :(
Çok basit bir yol:
/** Save the position **/
int currentPosition = listView.getFirstVisiblePosition();
//Here u should save the currentPosition anywhere
/** Restore the previus saved position **/
listView.setSelection(savedPosition);
SetSelection yöntemi listeyi sağlanan öğeye sıfırlar. Dokunmatik modda değilse, dokunmatik modda öğe yalnızca ekranda konumlandırılacaksa, öğe gerçekten seçilecektir.
Daha karmaşık bir yaklaşım:
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);
Bununla ilgili ilginç bir şey buldum.
SetSelection ve scrolltoXY'yi denedim ama hiç çalışmadı, liste aynı pozisyonda kaldı, bazı deneme ve hatalardan sonra işe yarayan aşağıdaki kodu aldım
final ListView list = (ListView) findViewById(R.id.list);
list.post(new Runnable() {
@Override
public void run() {
list.setSelection(0);
}
});
Runnable'ı göndermek yerine runOnUiThread'i de denerseniz (en azından bazı cihazlarda)
Bu, doğrudan olması gereken bir şey için çok garip bir çözümdür.
setSelectionFromTop()
çalışmıyor.
DİKKAT!! AbsListView'da, ListView.getFirstVisiblePosition () 0 ise onSaveState () işlevinin düzgün çalışmasına izin vermeyen bir hata vardır.
Ekranın çoğunu kaplayan büyük resimleriniz varsa ve ikinci resme ilerlerseniz, ancak ilkinden biraz gösteriliyorsa, kaydırma konumu kaydedilmeyecek ...
itibaren AbsListView.java:1650 (yorum maden)
// this will be false when the firstPosition IS 0
if (haveChildren && mFirstPosition > 0) {
...
} else {
ss.viewTop = 0;
ss.firstId = INVALID_POSITION;
ss.position = 0;
}
Ancak bu durumda, aşağıdaki koddaki 'üst', durumun doğru bir şekilde geri yüklenmesini engelleyen diğer sorunlara neden olan negatif bir sayı olacaktır. 'Üst' negatif olduğunda, bir sonraki çocuğu alın
// 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);
}
}
}
Bu yeterli
Bazıları bu soruna bir çözüm arayanlar için, sorunun kökü liste görünümleri bağdaştırıcınızı ayarladığınız yerde olabilir. Bağdaştırıcıyı liste görünümünde ayarladıktan sonra kaydırma konumunu sıfırlar. Dikkate alınması gereken bir şey. Biz liste görünümüne referans kapmak sonra benim onCreateView içine adaptör ayarı taşındı ve benim için sorunu çözdü. =)
Bunu gönderiyorum çünkü kimsenin bundan bahsetmediğine şaşırdım.
Kullanıcı geri düğmesini tıkladıktan sonra, çıktığı haliyle liste görünümüne geri döner.
Bu kod, "yukarı" düğmesini geri düğmesiyle aynı şekilde davranacak şekilde geçersiz kılar, böylece Liste Görünümü -> Ayrıntılar -> Liste Görüntüsüne Dön (ve başka seçenek yok) durumunda bu, kaydırma konumunu ve içeriği korumak için en basit koddur liste görünümünde.
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return(true);
}
return(super.onOptionsItemSelected(item)); }
Dikkat: Ayrıntılar etkinliğinden başka bir etkinliğe gidebiliyorsanız, yukarı düğmesi sizi bu etkinliğe geri döndürür, böylece bunun çalışması için geri düğmesi geçmişini değiştirmeniz gerekir.
Değil sadece midir android:saveEnabled="true"
ListView xml bildirimi, yeterli uzunlukta içinde?
EN İYİ ÇÖZÜM:
// 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);
}
});
SONRASI VE İPLİKTE ÇAĞRIYORUZ!
Durumu yeniden yüklemeden ve sonra geri yüklemeden önce kaydederseniz, yeniden yükleme sonrasında kaydırma durumunu koruyabilirsiniz. Benim durumumda eşzamansız bir ağ isteği yaptım ve listeyi tamamlandıktan sonra geri aramada yeniden yükledim. Devleti buradan geri yüklerim. Kod örneği Kotlin'dir.
val state = myList.layoutManager.onSaveInstanceState()
getNewThings() { newThings: List<Thing> ->
myList.adapter.things = newThings
myList.layoutManager.onRestoreInstanceState(state)
}
Bir etkinlikte barındırılan parçaları kullanıyorsanız, bunun gibi bir şey yapabilirsiniz:
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
Kendinizin kaydırma konumunu kaydediyor / geri yüklüyorsanız , android çerçevede zaten uygulanan işlevselliği çoğaltırsınız. ListView
Geri yükleme ince kaydırma konumu sadece iyi kendi başına hariç bir uyarı: @aaronvargas bir hata var belirtildiği gibi AbsListView
ilk liste öğesi için ince kaydırma konumu geri izin vermez. Bununla birlikte, kaydırma konumunu geri yüklemenin en iyi yolu geri yüklemektir. Android çerçevesi sizin için daha iyisini yapacaktır. Sadece aşağıdaki koşulları yerine getirdiğinizden emin olun:
setSaveEnabled(false)
yöntem çağırmadığınızdan ve android:saveEnabled="false"
özniteliği ayarlamadığınızdan emin olunExpandableListView
geçersiz long getCombinedChildId(long groupId, long childId)
uzun yöntem döndürmesi için geçersiz kılma yöntemi (sınıftaki varsayılan uygulama BaseExpandableListAdapter
negatif sayı döndürür). İşte örnekler:.
@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
veya ExpandableListView
kullanılırsa, fragmanı aktivite rekreasyonunda yeniden yaratmayın (örneğin ekran döndürmeden sonra). Parçayı findFragmentByTag(String tag)
yöntemle elde edin .ListView
bir android:id
ve benzersizdir.İlk liste öğesiyle yukarıda belirtilen uyarıdan kaçınmak için adaptörünüzü 0 konumunda özel kukla sıfır piksel yükseklik görünümü döndürme biçiminde oluşturabilirsiniz. ListView
İşte basit örnek proje, kaydırma konumlarının açıkça kaydedilmediği halde ince kaydırma konumlarını gösterir ListView
ve ExpandableListView
geri yükler. / restore. Başka bir uygulamaya geçici geçiş, çift ekran döndürme ve test uygulamasına geri dönme gibi karmaşık senaryolar için bile hassas kaydırma konumu mükemmel bir şekilde geri yüklenir. Uygulamadan açıkça çıkıyorsanız (Geri düğmesine basarak) kaydırma konumunun kaydedilmeyeceğini (diğer tüm Görünümlerin durumlarını kaydetmeyeceğini) lütfen unutmayın.
https://github.com/voromto/RestoreScrollPosition/releases
SimpleCursorAdapter kullanarak LoaderManager.LoaderCallbacks uygulayan ListActivity'den türetilen bir etkinlik için, etkinlik neredeyse her zaman yeniden başlatıldığından ve ayrıntılar görünümü kapatıldığında bağdaştırıcı yeniden yüklendiğinden onReset () içindeki konumu geri yüklemek için çalışmadı. Hile onLoadFinished () içindeki konumu geri yüklemekti:
onListItemClick () içinde:
// 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 () içinde:
// restore the selected item which was saved on item click
// when details are closed and list is shown again
getListView().setSelectionFromTop(index, top);
onBackPressed () içinde:
// Show the top item at next start of the app
index = 0;
top = 0;
Burada sunulan çözümlerin hiçbiri benim için işe yaramadı. Benim durumumda, ListView
içinde Fragment
değiştirdiğim bir var FragmentTransaction
, bu yüzden Fragment
her parça gösterildiğinde yeni bir örnek oluşturulur, bu da ListView
durumun üyesi olarak depolanamayacağı anlamına gelirFragment
.
Bunun yerine, devleti özel Application
sınıfımda depoladım . Aşağıdaki kod size bunun nasıl çalıştığı hakkında bir fikir vermelidir:
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 ... */
}
Temel fikir, durumu parça örneğinin dışında saklamanızdır. Uygulama sınıfınızda statik bir alana sahip olma fikrinden hoşlanmıyorsanız, bunu bir parça arabirimi uygulayarak ve durumu etkinliğinizde depolayarak yapabilirsiniz.
Başka bir çözüm de depolamak olacaktır SharedPreferences
, ancak biraz daha karmaşık hale gelir ve durumun uygulama lansmanlarında kalıcı olmasını istemediğiniz sürece uygulama başlatmasında temizlediğinizden emin olmanız gerekir.
Ayrıca, "ilk öğe görünür durumdayken kaydedilmeyen kaydırma konumu" ndan kaçınmak için, 0px
yüksekliği yüksek olan bir sahte öğe görüntüleyebilirsiniz . Bu, getView()
adaptörünüzde geçersiz kılmayla elde edilebilir , örneğin:
@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);
}
Cevabım Firebase içindir ve 0 konumu geçici bir çözümdür
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) {
}
});
ve dönüştürmek
0 konumu a kullanmadığınız boş bir öğe ekleyin
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;
}
}
Bu işi kullanıcıyı rahatsız etmeden geçici olarak gördüm, umarım sizin için çalışır
Aşağıdaki kodu kullanın:
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());
}
ve verileriniz her yenilendiğinde aşağıdaki kodu kullanın:
adapter.notifyDataSetChanged();
mList.setSelectionFromTop(index, top);
FirebaseListAdapter kullanıyorum ve çalışmak için herhangi bir çözüm alamadım. Sonunda bunu yaptım. Sanırım daha zarif yollar var ama bu tam ve çalışan bir çözüm.
Oluşturmadan önce:
private int reset;
private int top;
private int index;
FirebaseListAdapter içinde:
@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();
}
}
Ben de FirebaseRecyclerAdapter için bunu çözmek zorunda beri burada çözüm gönderiyorum:
Oluşturmadan önce:
private int reset;
private int top;
private int index;
FirebaseRecyclerAdapter içinde:
@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'un mükemmel cevabını açıklığa kavuşturmak ve onu parçalar için ve bir "ana" ListView parçasından "ayrıntılar" parçasına ve sonra tekrar "kaptana" gitmek istediğimiz olağan durum için ayarlamak için
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
}
Buradaki "sihir", ayrıntılar parçasından ListView parçasına geri döndüğümüzde, görünümün yeniden oluşturulmadığı, ListView'ın bağdaştırıcısını ayarlamadığımız için her şeyin bıraktığımız gibi kalmasıdır!