2010-09-16 87 views
114

我有一個ListView顯示新聞項目。它們包含圖像,標題和一些文字。圖像被加載到單獨的線程中(帶有隊列和全部),並且當圖像被下載時,我現在在列表適配器上調用notifyDataSetChanged()來更新圖像。這是有效的,但getView()調用過於頻繁,因爲notifyDataSetChanged()對所有可見項調用getView()。我想更新列表中的單個項目。我將如何做到這一點?如何更新ListView中的單個行?

問題,我有我目前的做法是:

  1. 滾動很慢
  2. 我有淡入其中發生的每列表中的一個新的圖像加載時的圖像動畫。

回答

176

我找到了答案,感謝您的信息米歇爾。 您的確可以使用View#getChildAt(int index)獲得正確的視圖。問題在於它從第一個可見項開始計數。事實上,你只能得到可見的物品。你可以用ListView#getFirstVisiblePosition()來解決這個問題。

例子:

private void updateView(int index){ 
    View v = yourListView.getChildAt(index - 
     yourListView.getFirstVisiblePosition()); 

    if(v == null) 
     return; 

    TextView someText = (TextView) v.findViewById(R.id.sometextview); 
    someText.setText("Hi! I updated you manually!"); 
} 
+2

注意自我:mImgView.setImageURI()將更新列表項,但只會更新一次;所以我最好使用mLstVwAdapter.notifyDataSetInvalidated()代替。 – kellogs 2011-11-16 19:55:24

+0

該解決方案有效。但是這隻能用yourListView.getChildAt(index)來完成。並改變視圖。兩種解決方案都可以工 – MobiDev 2013-01-11 06:43:07

+1

@erik可以幫助我在這個http://stackoverflow.com/questions/18312539/update-the-valview-of-item-in-the-listview-android?noredirect=1#comment26873838_18312539 – Developer 2013-08-19 12:34:01

65

這個問題已經被問在谷歌I/2010 O,你可以在這裏觀看:

The world of ListView, time 52:30

基本上什麼羅曼蓋伊解釋是調用getChildAt(int)ListView拿到視圖和(我認爲)撥打getFirstVisiblePosition()找出位置和指數之間的相關性。作爲一個例子,我認爲他可能指的是方法ShelvesActivity.updateBookCovers(),但我找不到getFirstVisiblePosition()的調用。

真棒最新通報COMING:

的RecyclerView將在不久的將來解決這個問題。作爲http://www.grokkingandroid.com/first-glance-androids-recyclerview/指出的那樣,你就可以調用方法可以準確地指定的變化,如:

void notifyItemInserted(int position) 
void notifyItemRemoved(int position) 
void notifyItemChanged(int position) 

而且,大家會使用基於RecyclerView新的意見,因爲他們將得到獎勵漂亮的動畫!未來看起來很棒! :-)

+2

我張貼的解決方案。感謝您的信息! – Erik 2010-09-16 14:46:32

+3

好東西! :)也許你也可以在這裏添加一個空檢查 - 如果視圖當前不可用,v可能爲空。當然,如果用戶滾動ListView,這些數據將會消失,所以還應該更新適配器中的數據(也許不需要調用notifyDataSetChanged())。一般來說,我認爲在適配器中保留所有這些邏輯是一個好主意,即將ListView引用傳遞給適配器。 – mreichelt 2010-09-17 21:45:02

+0

這適用於我,除了 - 視圖離開屏幕時,重新進入更改時重置。我猜是因爲在getview中它仍然在接收原始信息。我如何解決這個問題? – gloscherrybomb 2012-01-23 20:39:05

2

的答案是明確的,正確的,我加入了一個想法CursorAdapter情況下在這裏。

如果youre繼承CursorAdapter(或ResourceCursorAdapter,或SimpleCursorAdapter),那麼你要麼實現ViewBinder或覆蓋bindView()newView()方法,這些不爭論收到當前列表項指標。因此,當一些數據到達並且你想更新相關的可見列表項目時,你怎麼知道他們的索引?

我的解決方法是:

  • 保存所有創建的列表項視圖列表,從newView()
  • 項目添加到這個列表當數據到達,迭代它們,看看哪一個需要更新 - 更好不是做notifyDatasetChanged()並刷新所有的人

由於查看回收視圖引用的次數,我需要存儲和迭代將是大致相等的顯示在屏幕上列表中的項目數量。

1

我使用了提供Erik的代碼,效果很好,但是我有一個複雜的自定義適配器用於我的listview,並且面臨更新UI的兩次實現代碼。我試圖從我的適配器getView方法得到了新的觀點(即持有的ListView數據ArrayList中有媒體鏈接被更新/更改):

View cell = lvOptim.getChildAt(index - lvOptim.getFirstVisiblePosition()); 
if(cell!=null){ 
    cell = adapter.getView(index, cell, lvOptim); //public View getView(final int position, View convertView, ViewGroup parent) 
    cell.startAnimation(animationLeftIn()); 
} 

它的工作很好,但我不知道這是否是一個好實踐。 所以我不需要實現兩次更新列表項的代碼。

0

我的解決方案: 如果它是正確的*,更新數據和可查看項目,而無需重新繪製整個列表。否則notifyDataSetChanged。

正確 - OLDDATA大小==新的數據的大小,和舊的數據ID和它們的順序==新的數據ID和順序

如何:

/** 
* A View can only be used (visible) once. This class creates a map from int (position) to view, where the mapping 
* is one-to-one and on. 
* 
*/ 
    private static class UniqueValueSparseArray extends SparseArray<View> { 
    private final HashMap<View,Integer> m_valueToKey = new HashMap<View,Integer>(); 

    @Override 
    public void put(int key, View value) { 
     final Integer previousKey = m_valueToKey.put(value,key); 
     if(null != previousKey) { 
      remove(previousKey);//re-mapping 
     } 
     super.put(key, value); 
    } 
} 

@Override 
public void setData(final List<? extends DBObject> data) { 
    // TODO Implement 'smarter' logic, for replacing just part of the data? 
    if (data == m_data) return; 
    List<? extends DBObject> oldData = m_data; 
    m_data = null == data ? Collections.EMPTY_LIST : data; 
    if (!updateExistingViews(oldData, data)) notifyDataSetChanged(); 
    else if (DEBUG) Log.d(TAG, "Updated without notifyDataSetChanged"); 
} 


/** 
* See if we can update the data within existing layout, without re-drawing the list. 
* @param oldData 
* @param newData 
* @return 
*/ 
private boolean updateExistingViews(List<? extends DBObject> oldData, List<? extends DBObject> newData) { 
    /** 
    * Iterate over new data, compare to old. If IDs out of sync, stop and return false. Else - update visible 
    * items. 
    */ 
    final int oldDataSize = oldData.size(); 
    if (oldDataSize != newData.size()) return false; 
    DBObject newObj; 

    int nVisibleViews = m_visibleViews.size(); 
    if(nVisibleViews == 0) return false; 

    for (int position = 0; nVisibleViews > 0 && position < oldDataSize; position++) { 
     newObj = newData.get(position); 
     if (oldData.get(position).getId() != newObj.getId()) return false; 
     // iterate over visible objects and see if this ID is there. 
     final View view = m_visibleViews.get(position); 
     if (null != view) { 
      // this position has a visible view, let's update it! 
      bindView(position, view, false); 
      nVisibleViews--; 
     } 
    } 

    return true; 
} 

,當然還有:

@Override 
public View getView(final int position, final View convertView, final ViewGroup parent) { 
    final View result = createViewFromResource(position, convertView, parent); 
    m_visibleViews.put(position, result); 

    return result; 
} 

忽略綁定視圖的最後一個參數(我用它來確定是否需要爲ImageDrawable回收位圖)。

如上所述,'可見'視圖的總數大致是適合屏幕的數量(忽略方向更改等),因此沒有大的記憶方式。

6

這是我做的:

你的項目(行)必須有唯一的ID,這樣可以在以後更新它們。當列表從適配器獲取視圖時,設置每個視圖的標記。 (您也可以使用鍵標籤如果默認標籤用於其他地方)

@Override 
public View getView(int position, View convertView, ViewGroup parent) 
{ 
    View view = super.getView(position, convertView, parent); 
    view.setTag(getItemId(position)); 
    return view; 
} 

對於更新檢查列表的每一個元素,如果與給定id的看法是有它的可見,所以我們進行了更新。

private void update(long id) 
{ 

    int c = list.getChildCount(); 
    for (int i = 0; i < c; i++) 
    { 
     View view = list.getChildAt(i); 
     if ((Long)view.getTag() == id) 
     { 
      // update view 
     } 
    } 
} 

它實際上比其他方法更容易,當你處理id,而不是位置更好!此外,您必須致電更新以查看可見的項目。

1
int wantedPosition = 25; // Whatever position you're looking for 
int firstPosition = linearLayoutManager.findFirstVisibleItemPosition(); // This is the same as child #0 
int wantedChild = wantedPosition - firstPosition; 

if (wantedChild < 0 || wantedChild >= linearLayoutManager.getChildCount()) { 
    Log.w(TAG, "Unable to get view for desired position, because it's not being displayed on screen."); 
    return; 
} 

View wantedView = linearLayoutManager.getChildAt(wantedChild); 
mlayoutOver =(LinearLayout)wantedView.findViewById(R.id.layout_over); 
mlayoutPopup = (LinearLayout)wantedView.findViewById(R.id.layout_popup); 

mlayoutOver.setVisibility(View.INVISIBLE); 
mlayoutPopup.setVisibility(View.VISIBLE); 

對於RecycleView請使用此代碼

+0

我在recycleView上實現了它,它工作。但是當你滾動列表時,它會再次發生變化。任何幫助? – 2015-08-25 13:21:12

+0

使用get和settag – 2015-09-15 12:28:52

0

正是我用這個

private void updateSetTopState(int index) { 
     View v = listview.getChildAt(index - 
       listview.getFirstVisiblePosition()+listview.getHeaderViewsCount()); 

     if(v == null) 
      return; 

     TextView aa = (TextView) v.findViewById(R.id.aa); 
     aa.setVisibility(View.VISIBLE); 
    } 
0

我提出了另一種解決方案,像RecyclyerView方法無效notifyItemChanged(int position),創建CustomBaseAdapter類就是這樣的:

public abstract class CustomBaseAdapter implements ListAdapter, SpinnerAdapter { 
    private final CustomDataSetObservable mDataSetObservable = new CustomDataSetObservable(); 

    public boolean hasStableIds() { 
     return false; 
    } 

    public void registerDataSetObserver(DataSetObserver observer) { 
     mDataSetObservable.registerObserver(observer); 
    } 

    public void unregisterDataSetObserver(DataSetObserver observer) { 
     mDataSetObservable.unregisterObserver(observer); 
    } 

    public void notifyDataSetChanged() { 
     mDataSetObservable.notifyChanged(); 
    } 

    public void notifyItemChanged(int position) { 
     mDataSetObservable.notifyItemChanged(position); 
    } 

    public void notifyDataSetInvalidated() { 
     mDataSetObservable.notifyInvalidated(); 
    } 

    public boolean areAllItemsEnabled() { 
     return true; 
    } 

    public boolean isEnabled(int position) { 
     return true; 
    } 

    public View getDropDownView(int position, View convertView, ViewGroup parent) { 
     return getView(position, convertView, parent); 
    } 

    public int getItemViewType(int position) { 
     return 0; 
    } 

    public int getViewTypeCount() { 
     return 1; 
    } 

    public boolean isEmpty() { 
     return getCount() == 0; 
    } { 

    } 
} 

不要忘記創建CustomDataSetObservable類也爲mDataSetObservable變量CustomAdapterClass,像這樣:

public class CustomDataSetObservable extends Observable<DataSetObserver> { 

    public void notifyChanged() { 
     synchronized(mObservers) { 
      // since onChanged() is implemented by the app, it could do anything, including 
      // removing itself from {@link mObservers} - and that could cause problems if 
      // an iterator is used on the ArrayList {@link mObservers}. 
      // to avoid such problems, just march thru the list in the reverse order. 
      for (int i = mObservers.size() - 1; i >= 0; i--) { 
       mObservers.get(i).onChanged(); 
      } 
     } 
    } 

    public void notifyInvalidated() { 
     synchronized (mObservers) { 
      for (int i = mObservers.size() - 1; i >= 0; i--) { 
       mObservers.get(i).onInvalidated(); 
      } 
     } 
    } 

    public void notifyItemChanged(int position) { 
     synchronized(mObservers) { 
      // since onChanged() is implemented by the app, it could do anything, including 
      // removing itself from {@link mObservers} - and that could cause problems if 
      // an iterator is used on the ArrayList {@link mObservers}. 
      // to avoid such problems, just march thru the list in the reverse order. 
      mObservers.get(position).onChanged(); 
     } 
    } 
} 
階級 CustomBaseAdapter

有一個方法notifyItemChanged(int position),你可以調用這個方法當你想要更新任何一個行時(從按鈕點擊或任何你想調用該方法的地方)。瞧!你的單行將即時更新..

2

先把模型類作爲全球這樣的模型類對象

SampleModel golbalmodel=new SchedulerModel(); 

,並初始化全球

得到的當前行視圖由模型由初始化的它全局模型

SampleModel data = (SchedulerModel) sampleList.get(position); 
       golbalmodel=data; 

設置改變後的值到全局模型對象的方法被設置,並添加notifyDataSetChanged其對我的作品

golbalmodel.setStartandenddate(changedate); 

notifyDataSetChanged(); 

Here is a related question on this with good answers.