Android 如何更新 ListView 中的单行?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3724874/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-20 11:32:46  来源:igfitidea点击:

How can I update a single row in a ListView?

androidandroid-listviewandroid-arrayadapter

提问by Erik

I have a ListViewwhich displays news items. They contain an image, a title and some text. The image is loaded in a separate thread (with a queue and all) and when the image is downloaded, I now call notifyDataSetChanged()on the list adapter to update the image. This works, but getView()is getting called too frequently, since notifyDataSetChanged()calls getView()for all visible items. I want to update just the single item in the list. How would I do this?

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

Problems I have with my current approach are:

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

  1. Scrolling is slow
  2. I have a fade-in animation on the image which happens every time a single new image in the list is loaded.
  1. 滚动很慢
  2. 我在图像上有一个淡入动画,每次加载列表中的单个新图像时都会发生这种情况。

回答by Erik

I found the answer, thanks to your information Michelle. You can indeed get the right view using View#getChildAt(int index). The catch is that it starts counting from the first visible item. In fact, you can only get the visible items. You solve this with ListView#getFirstVisiblePosition().

多亏了米歇尔的信息,我找到了答案。您确实可以使用View#getChildAt(int index). 问题是它从第一个可见项目开始计数。实际上,您只能获取可见项。你用ListView#getFirstVisiblePosition().

Example:

例子:

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

回答by mreichelt

This question has been asked at the Google I/O 2010, you can watch it here:

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

The world of ListView, time 52:30

ListView 的世界,时间 52:30

Basically what Romain Guy explains is to call getChildAt(int)on the ListViewto get the view and (I think) call getFirstVisiblePosition()to find out the correlation between position and index.

基本上什么罗曼盖伊解释是调用getChildAt(int)ListView得到的看法和(我认为)调用getFirstVisiblePosition(),找出位置和指数之间的相关性。

Romain also points to the project called Shelvesas an example, I think he might mean the method ShelvesActivity.updateBookCovers(), but I can't find the call of getFirstVisiblePosition().

Romain 还以名为Shelves的项目为例,我想他的意思可能是方法ShelvesActivity.updateBookCovers(),但是我找不到getFirstVisiblePosition().

AWESOME UPDATES COMING:

很棒的更新即将到来:

The RecyclerView will fix this in the near future. As pointed out on http://www.grokkingandroid.com/first-glance-androids-recyclerview/, you will be able to call methods to exactly specify the change, such as:

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

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

Also, everyone will wantto use the new views based on RecyclerView because they will be rewarded with nicely-looking animations! The future looks awesome! :-)

此外,每个人都希望使用基于 RecyclerView 的新视图,因为他们将获得漂亮的动画奖励!未来看起来很棒!:-)

回答by Ali

This is how I did it:

我是这样做的:

Your items (rows) must have unique ids so you can update them later. Set the tag of every view when the list is getting the view from adapter. (You can also use key tag if the default tag is used somewhere else)

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

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

For the update check every element of list, if a view with given id is there it's visible so we perform the update.

对于更新,检查列表的每个元素,如果有给定 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
        }
    }
}

It's actually easier than other methods and better when you dealing with ids not positions! Also you must call update for items which get visible.

当您处理 ids 而不是位置时,它实际上比其他方法更容易和更好!此外,您必须为可见的项目调用更新。

回答by koteswara D K

get the model class first as global like this model class object

像这个模型类对象一样首先将模型类作为全局对象

SampleModel golbalmodel=new SchedulerModel();

and initialise it to global

并将其初始化为全局

get the current row of the view by the model by initialising the it to global model

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

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

set the changed value to global model object method to be set and add the notifyDataSetChanged its works for me

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

golbalmodel.setStartandenddate(changedate);

notifyDataSetChanged();

Here is a related question on this with good answers.

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

回答by Libin Thomas

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

For RecycleView please use this code

对于 RecycleView,请使用此代码

回答by Pēteris Caune

The answers are clear and correct, I'll add an idea for CursorAdaptercase here.

答案清晰正确,我将CursorAdapter在此处添加案例的想法。

If youre subclassing CursorAdapter(or ResourceCursorAdapter, or SimpleCursorAdapter), then you get to either implement ViewBinderor override bindView()and newView()methods, these don't receive current list item index in arguments. Therefore, when some data arrives and you want to update relevant visible list items, how do you know their indices?

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

My workaround was to:

我的解决方法是:

  • keep a list of all created list item views, add items to this list from newView()
  • when data arrives, iterate them and see which one needs updating--better than doing notifyDatasetChanged()and refreshing all of them
  • 保留所有创建的列表项视图的列表,从 newView()
  • 当数据到达时,迭代它们,看看哪个需要更新——比做notifyDatasetChanged()和刷新所有更好

Due to view recycling the number of view references I'll need to store and iterate will be roughly equal the number of list items visible on screen.

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

回答by Primoz990

I used the code that provided Erik, works great, but i have a complex custom adapter for my listview and i was confronted with twice implementation of the code that updates the UI. I've tried to get the new view from my adapters getView method(the arraylist that holds the listview data has allready been updated/changed):

我使用了提供 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());
}

It's working well, but i dont know if this is a good practice. So i don't need to implement the code that updates the list item two times.

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

回答by chefish

exactly I used this

正是我用这个

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

回答by Aprido Sandyasa

I made up another solution, like RecyclyerViewmethod void notifyItemChanged(int position), create CustomBaseAdapterclass just like this:

我提出了另一种解决方案,像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;
    } {

    }
}

Don't forget to create a CustomDataSetObservable classtoo for mDataSetObservablevariable in CustomAdapterClass, like this:

不要忘了创建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();
        }
    }
}

on class CustomBaseAdapterthere is a method notifyItemChanged(int position), and you can call that method when you want update a row wherever you want (from button click or anywhere you want call that method). And voila!, your single row will update instantly..

在类CustomBaseAdapter 上有一个方法notifyItemChanged(int position),当您想在任何地方更新一行时(从按钮单击或您想调用该方法的任何地方),您可以调用该方法。瞧!,您的单行将立即更新..

回答by JRun

My solution: If it is correct*, update the data and viewable items without re-drawing the whole list. Else notifyDataSetChanged.

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

Correct - oldData size == new data size, and old data IDs and their order == new data IDs and order

正确 - 旧数据大小 == 新数据大小,以及旧数据 ID 及其顺序 == 新数据 ID 和顺序

How:

如何:

/**
 * 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;
}

and of course:

而且当然:

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

Ignore the last param to bindView (I use it to determine whether or not I need to recycle bitmaps for ImageDrawable).

忽略 bindView 的最后一个参数(我用它来确定是否需要为 ImageDrawable 回收位图)。

As mentioned above, the total number of 'visible' views is roughly the amount that fits on the screen (ignoring orientation changes etc), so no biggie memory-wise.

如上所述,“可见”视图的总数大致是适合屏幕的数量(忽略方向变化等),因此在内存方面没有什么大不了的。