ニュースアイテムを表示するListView
があります。画像、タイトル、テキストが含まれています。画像は(キューとすべての)別のスレッドに読み込まれ、画像がダウンロードされると、リストアダプターでnotifyDataSetChanged()
を呼び出して画像を更新します。これは機能しますが、getView()
はすべての表示項目に対してnotifyDataSetChanged()
を呼び出すため、getView()
は頻繁に呼び出されます。リスト内の単一のアイテムのみを更新したい。どうすればいいですか?
現在のアプローチで抱えている問題は次のとおりです。
あなたの情報Michelleのおかげで、答えが見つかりました。 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で寄せられたもので、こちらで見ることができます。
基本的にRomain Guyが説明するのは、 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に基づいた新しいビューを使用したい!未来は素晴らしいですね! :-)
これは私がやった方法です:
後で更新できるように、アイテム(行)には一意の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を扱う方が優れています!また、表示されるアイテムのupdateを呼び出す必要があります。
このモデルクラスオブジェクトのように、最初にモデルクラスをグローバルとして取得します
SampleModel golbalmodel=new SchedulerModel();
そしてそれをグローバルに初期化します
グローバルモデルに初期化することにより、モデルによってビューの現在の行を取得します
SampleModel data = (SchedulerModel) sampleList.get(position);
golbalmodel=data;
変更する値を設定するグローバルモデルオブジェクトメソッドに設定し、notifyDataSetChangedを追加してその動作を確認します
golbalmodel.setStartandenddate(changedate);
notifyDataSetChanged();
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の場合、このコードを使用してください
答えは明確で正解です。ここでCursorAdapter
ケースのアイデアを追加します。
CursorAdapter
(またはResourceCursorAdapter
、またはSimpleCursorAdapter
)をサブクラス化する場合、ViewBinder
を実装するか、bindView()
およびnewView()
メソッドをオーバーライドします。これらは、引数で現在のリストアイテムインデックスを受け取りません。したがって、いくつかのデータが到着し、関連する表示リスト項目を更新する場合、それらのインデックスをどのように知るのでしょうか?
私の回避策は:
newView()
からこのリストにアイテムを追加しますnotifyDatasetChanged()
を実行してすべてを更新するよりも優れていますビューのリサイクルにより、保存および反復する必要があるビュー参照の数は、画面に表示されるリストアイテムの数とほぼ等しくなります。
私は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回更新するコードを実装する必要はありません。
まさにこれを使った
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);
}
RecyclyerView method void notifyItemChanged(int position)
、create CustomBaseAdapter classのような別のソリューションを作成しました:
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
変数にもCustomDataSetObservable classを作成することを忘れないでください。
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)
があり、ボタンのクリックまたはそのメソッドを呼び出したい場所から行を更新したいときに、そのメソッドを呼び出すことができます。そして出来上がり、あなたの単一の行が即座に更新されます。
私の解決策:正しい場合は、リスト全体を再描画せずにデータと表示可能なアイテムを更新します。それ以外の場合は、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のビットマップをリサイクルする必要があるかどうかを判断するために使用します)。
前述のように、「可視」ビューの合計数はおおよそ画面に収まる量です(向きの変更などは無視されます)。