リストビューの単一の行を更新するにはどうすればよいですか?


131

ListViewニュースを表示するを持っています。画像、タイトル、テキストが含まれています。画像は(キューとすべての)別のスレッドに読み込まれ、画像がダウンロードされたらnotifyDataSetChanged()、リストアダプターを呼び出して画像を更新します。これは機能しますが、すべての可視アイテムを呼び出すためgetView()、頻繁にnotifyDataSetChanged()呼び出さgetView()れています。リスト内の1つのアイテムのみを更新したい。どうすればいいですか?

現在のアプローチで私が抱えている問題は次のとおりです。

  1. スクロールが遅い
  2. 画像にフェードインアニメーションがあり、リスト内の新しい画像が1つ読み込まれるたびに発生します。

回答:


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()はリスト項目を更新しますが、1回だけです。そのため、代わりにmLstVwAdapter.notifyDataSetInvalidated()を使用する方がよいでしょう。
ケルログ2011年

このソリューションは機能します。ただし、これはyourListView.getChildAt(index);でのみ実行できます。ビューを変更します。両方のソリューションが機能します!!
AndroidDev 2013年

位置がgetFirstVisiblePosition()とgetLastVisiblePosition()の間にある場合にのみ、アダプターでgetView()を呼び出すとどうなりますか?同じように機能しますか?いつものようにビューが更新されると思いますよね?
Android開発者

私の場合、各アイテムが非同期に更新されているため、ビューがnullになることがあります。ビュー!= nullかどうかを確認することをお
勧めし

2
よく働く。InstagramのようなアプリでLike機能を実装するタスクを完了するのに役立ちます。カスタムアダプター、リスト要素内の[いいね!]ボタンのブロードキャストを使用し、非同期フラグメントを起動して親フラグメントのブロードキャストレシーバーでバックエンドにいいね!と伝え、最後にリストを更新するためのErikの回答を返しました。
Josh、

71

この質問はGoogle I / O 2010で行われたものであり、ここで見ることができます。

リストビューの世界、時間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(またはResourceCursorAdapter、またはSimpleCursorAdapter)の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に実装しました。しかし、リストをスクロールすると、再び変更されます。何か助け?
Fahid Nadeem

1

私はErikを提供するコードを使用しましたが、うまく機能しますが、リストビュー用の複雑なカスタムアダプターがあり、UIを更新するコードの2回の実装に直面しました。アダプターのgetViewメソッドから新しいビューを取得しようとしました(リストビューデータを保持する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());
}

それはうまく機能していますが、これが良い習慣かどうかはわかりません。したがって、リストアイテムを2回更新するコードを実装する必要はありません。


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

相対レイアウトとしてビューを取得していますが、相対レイアウト内でテキストビューを見つける方法は?
ギリッシュ

1

RecyclyerViewメソッドvoid などの別のソリューションを作成し、次のようにCustomBaseAdapterクラスをnotifyItemChanged(int position)作成します。

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の変数に対してCustomDataSetObservableクラスも作成することを忘れないでください。mDataSetObservable

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クラスにはmethodがありnotifyItemChanged(int position)、行を必要に応じて(ボタンのクリックから、またはそのメソッドを呼び出す場所から)更新したいときに、そのメソッドを呼び出すことができます。そして出来上がり!、1つの行が即座に更新されます。


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

bindViewの最後のパラメーターを無視します(これを使用して、ImageDrawableのビットマップをリサイクルする必要があるかどうかを判断します)。

上記のように、「可視」ビューの総数は、おおよそ画面に収まる量です(方向の変更などを無視)。そのため、メモリの点で大きな問題はありません。


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.