如何更新 ListView 中的单行?

我有一个 ListView显示新闻项目。它们包含一个图像、一个标题和一些文本。映像加载在一个单独的线程中(带有一个队列和所有线程) ,当映像被下载时,我现在在列表适配器上调用 notifyDataSetChanged()来更新映像。这是可行的,但是 getView()被调用得太频繁了,因为 notifyDataSetChanged()对所有可见项目都调用 getView()。我只想更新列表中的单个项目。我要怎么做?

我目前的方法存在的问题是:

  1. 滚动很慢
  2. 我有一个淡入动画的图像,每次发生一个新的图像在列表中加载。
150130 次浏览

这个问题已经在 Google I/O 2010上提出,你可以在这里看到:

ListView 的世界,时间52:30

基本上 Romain Guy 解释的是在 ListView上调用 getChildAt(int)来获得视图,(我认为)调用 getFirstVisiblePosition()来找出位置和指数之间的相关性。

Romain 还举了一个叫做 架子的项目作为例子,我想他可能指的是方法 ShelvesActivity.updateBookCovers(),但是我找不到 getFirstVisiblePosition()的调用。

精彩更新即将发布:

回收视图将在不久的将来解决这个问题。正如在 http://www.grokkingandroid.com/first-glance-androids-recyclerview/中指出的,您将能够调用方法来精确地指定更改,例如:

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

此外,每个人都将 想要使用新的视图基于回收视图,因为他们将获得漂亮的外观动画奖励!未来看起来棒极了!:-)

我找到了答案,多亏了你的信息,米歇尔。 您确实可以使用 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!");
}

答案是清楚和正确的,我会添加一个想法的 CursorAdapter的情况下在这里。

如果子类化 CursorAdapter(或者 ResourceCursorAdapter,或者 SimpleCursorAdapter) ,那么您可以实现 ViewBinder或者覆盖 bindView()newView()方法,这些方法不会在参数中接收当前列表项索引。因此,当一些数据到达并且您想要更新相关的可见列表项时,如何知道它们的索引?

我的解决办法是:

  • 保存所有创建的列表项视图的列表,从 newView()向该列表添加项
  • 当数据到达时,迭代它们,看看哪个需要更新——这比执行 notifyDatasetChanged()并刷新所有数据要好

由于视图循环,我需要存储和迭代的视图引用的数量将大致等于屏幕上可见的列表项的数量。

我使用的代码提供了 Erik,工作得很好,但我有一个复杂的自定义适配器为我的列表视图,我遇到了两次实现的代码,更新用户界面。我尝试从适配器 getView 方法获取新视图(保存 listview 数据的数组列表已经更新/更改) :

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

效果不错,但我不知道这是不是一个好的练习。 所以我不需要实现两次更新列表项的代码。

我的解决办法是: 如果正确 * ,则更新数据和可视项,而不重新绘制整个列表。

更正-oldData size = = 新数据大小,旧数据 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 的位图)。

如上所述,“可见”视图的总数大致相当于屏幕上的视图数量(忽略方向变化等) ,因此在内存方面没有太大问题。

我是这么做的:

您的项(行)必须具有唯一的 id,以便以后可以更新它们。当列表从适配器获取视图时,设置每个视图的标记。(如果默认标记在其他地方使用,也可以使用 key 标记)

@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 而不是位置时更好!此外,您还必须为可见的项目调用 update。

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

回收视图请使用此代码

我确实用了这个

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

我编写了另一个解决方案,像 回收站方法 void 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;
} {


}
}

不要忘记为 CustomAdapterClass中的 mDataSetObservable变量也创建一个 CustomDataSetObserver 类,如下所示:

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),当你想在任何地方更新一行时,你可以调用这个方法(通过点击按钮或者在任何你想调用这个方法的地方)。瞧!,您的单行将立即更新。.

首先获取模型类作为全局对象,就像这个模型类对象一样

SampleModel golbalmodel=new SchedulerModel();

并初始化为全球性的

通过将其初始化为全局模型来获取模型的当前视图行

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

将更改后的值设置为要设置的全局模型对象方法,并添加 notifyDataSetChanged

golbalmodel.setStartandenddate(changedate);


notifyDataSetChanged();

这里有一个相关的问题,有很好的答案。

除了这个解决方案(https://stackoverflow.com/a/3727813/5218712)只是想补充说,它应该工作只有当 listView.getChildCount() == yourDataList.size(); ListView 中可能有其他视图。

如何填充子元素的示例: listView.mChildren array