我如何更新ListView中的单个行?
我有一个ListView
显示新闻项目。 他们包含一个图像,一个标题和一些文字。 图像被加载到一个单独的线程(有一个队列和所有的),并且当图像被下载时,我现在调用列表适配器上的notifyDataSetChanged()
来更新图像。 这个工作,但getView()
被调用得太频繁,因为notifyDataSetChanged()
调用所有可见项的getView()
。 我想只更新列表中的单个项目。 我将如何做到这一点?
我现在的做法存在的问题是:
- 滚动缓慢
- 每次加载列表中的单个新图像时,图像上都会出现淡入淡出的animation。
我find了答案,感谢你的信息米歇尔。 你确实可以使用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!"); }
这个问题已经在Google I / O 2010上问过了,你可以在这里看到:
ListView的世界,时间52:30
基本上Romain Guy解释的是在ListView
上调用getChildAt(int)
来获得视图,并且(我认为)调用getFirstVisiblePosition()
来找出位置和索引之间的相关性。
Romain还指出了一个名为Shelves的项目,我想他可能是指ShelvesActivity.updateBookCovers()
方法,但是我找不到getFirstVisiblePosition()
的调用。
真棒更新来临:
RecyclerView将在不久的将来解决这个问题。 正如http://www.grokkingandroid.com/first-glance-androids-recyclerview/中指出的那样,您将能够调用方法来精确指定更改,例如:;
void notifyItemInserted(int position) void notifyItemRemoved(int position) void notifyItemChanged(int position)
此外,每个人都希望使用基于RecyclerView的新视图,因为他们将获得漂亮的animation效果! 未来看起来真棒! 🙂
这是我做到的:
您的项目(行)必须具有唯一的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不是位置时更好! 此外,您必须致电更新以获取可见的项目。
答案是清楚和正确的,我会在这里添加CursorAdapter
案例的一个想法。
如果您SimpleCursorAdapter
CursorAdapter
(或ResourceCursorAdapter
或SimpleCursorAdapter
)的子类,那么您既可以实现ViewBinder
也可以重写bindView()
和newView()
方法,但它们不会在参数中接收到当前列表项索引。 因此,当一些数据到达,你想更新相关的可见列表项目,你怎么知道他们的指数?
我的解决方法是:
- 保留所有创build的列表项目视图的列表,从
newView()
添加项目到这个列表 - 当数据到达时,迭代他们,看看哪一个需要更新 – 比
notifyDatasetChanged()
更好,并刷新所有这些
由于查看回收视图引用的数量,我需要存储和迭代将大致等于在屏幕上可见的列表项的数量。
像这个模型类对象一样首先获得模型类
SampleModel golbalmodel=new SchedulerModel();
并将其初始化为全局
通过将模型初始化为全局模型来获取模型的当前行视图
SampleModel data = (SchedulerModel) sampleList.get(position); golbalmodel=data;
将更改后的值设置为要设置的全局模型对象方法,并为其添加notifyDataSetChanged其作品
golbalmodel.setStartandenddate(changedate); notifyDataSetChanged();
这是一个相关的问题,有很好的答案。
我使用提供Erik的代码,效果很好,但我有一个复杂的自定义适配器为我的列表视图和我面临两次更新UI的代码实现。 我试图从我的适配器getView方法(包含列表视图数据的数组列表已更新/更改)的新视图:
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()); }
它运作良好,但我不知道这是否是一个好的做法。 所以我不需要实现两次更新列表项的代码。
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,请使用此代码
我的解决scheme:如果它是正确的*,更新数据和可查看项目,而不重新绘制整个列表。 否则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; }
忽略最后一个参数bindView(我用它来确定是否需要回收ImageDrawable位图)。
如上所述,“可见”视图的总数大致是适合屏幕上的数量(忽略方向变化等),所以没有大的记忆方式。
正是我用这个
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); }
我做了另一个解决scheme,像RecyclyerView方法void notifyItemChanged(int position)
,创buildCustomBaseAdapter类就像这样:
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; } { } }
不要忘了为CustomAdapterClass中的 mDataSetObservable
variables创build一个CustomDataSetObservable类 ,如下所示:
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(); } } }
在类notifyItemChanged(int position)
上有一个方法notifyItemChanged(int position)
,并且当你想要更新一个行时,你可以调用该方法(从button点击或任何你想要调用该方法的地方)。 瞧!你的单行将立即更新..