如何更新ListView中的单行?


131

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

我目前的方法存在以下问题:

  1. 滚动很慢
  2. 我在图像上有淡入动画,每次加载列表中的单个新图像时都会发生。

Answers:


199

有了您的信息,米歇尔,我找到了答案。您确实可以使用来获得正确的视图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年

此解决方案有效。但这只能通过yourListView.getChildAt(index);来完成。并更改视图。两种解决方案都可以!!
AndroidDev

如果仅在位置在getFirstVisiblePosition()和getLastVisiblePosition()之间的情况下,仅在适配器上调用getView()会发生什么?会一样吗?我认为它将像往常一样更新视图,对吗?
Android开发人员

就我而言,有时视图为null,因为每个项目都是异步更新的,因此最好检查view!= null
Khawar 2014年

2
效果很好。它可以帮助我完成在类似instagram的应用中实现“赞”功能的任务。我正在使用一个自定义适配器,在list元素内的like按钮中广播,启动一个asynctask以父片段上的广播接收器告诉后端有关like的消息,最后是Erik的答案来更新列表。
2014年

71

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

ListView的世界,时间52:30

基本上什么罗曼盖伊解释是调用getChildAt(int)ListView得到的看法和(我认为)调用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的新视图,因为它们将获得精美外观的动画!未来看起来棒极了!:-)


3
好东西!:)也许您也可以在此处添加null检查-如果当前无法使用视图,则v可能为null。当然,如果用户滚动ListView,此数据将消失,因此也应更新适配器中的数据(也许不调用notifyDataSetChanged())。总的来说,我认为将所有这些逻辑保留在适配器中是一个好主意,即将ListView引用传递给它。
mreichelt

这对我有用,除了-当视图离开屏幕时,当它重新进入时,更改被重置。我猜是因为在getview中它仍在接收原始信息。我该如何解决?
gloscherrybomb 2012年

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而不是头寸时会更好!您还必须为可见的项目调用更新。


3

首先像这个模型类对象一样获得全局的模型类

SampleModel golbalmodel=new SchedulerModel();

并初始化为全局

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

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

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

golbalmodel.setStartandenddate(changedate);

notifyDataSetChanged();

这是一个与此相关的问题,答案很好。


这是正确的方法,因为您将获取要更新的对象,先对其进行更新,然后通知适配器,这将使用新数据更改行信息。
加斯顿Saillén

2

答案是明确正确的,在此我将为CursorAdapter案例添加一个思路。

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

我的解决方法是:

  • 保留所有已创建列表项视图的列表,然后从 newView()
  • 当数据到达时,对其进行迭代,并查看哪些数据需要更新-比完成notifyDatasetChanged()并刷新所有数据更好

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


2
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,请使用此代码


我在recycleView上实现了它,它起作用了。但是,当您滚动列表时,它会再次发生变化。有什么帮助吗?
法希德·纳德姆

1

我使用了提供Erik的代码,效果很好,但是我为列表视图使用了一个复杂的自定义适配器,并且我遇到了两次更新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());
}

它运行良好,但是我不知道这是否是一个好习惯。因此,我不需要实现两次更新列表项的代码。


1

正是我用这个

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

我正在获取“相对视图”布局,如​​何在相对布局内找到Textview?
Girish

1

我提出了另一种解决方案,像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),并且可以在想要更新行的任何位置(从按钮单击或任何要调用该方法的地方)调用该方法。瞧!,您的单行将立即更新。


0

我的解决方案:如果正确*,请更新数据和可见项,而无需重新绘制整个列表。否则notifyDataSetChanged。

正确-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的位图)。

如上所述,“可见”视图的总数大约是适合屏幕显示的数量(忽略方向更改等),因此从内存角度来讲并不是很大。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.