ListView中使用自定義Adapter及時更新資料

傲慢的上校發表於2012-10-30

        又到10點半,時間真是過的真快。在專案中,遇到不能ListView及時更新的問題。寫了一個demo,其中也遇到一些問題,一併寫出來。前幾個月總是有點懶,但是這個月總算是湊夠4篇了。


程式碼比較簡單,遇到點簡單的問題,弄到了現在。

好吧,上程式碼

public class PersonAdapter extends BaseAdapter {
	private ArrayList<PersonBean> mList;
	private Context mContext;
	
	public PersonAdapter(ArrayList<PersonBean> list, Context context) {
		mList = list;
		mContext = context;
	}
	
	public void refresh(ArrayList<PersonBean> list) {
		mList = list;
		notifyDataSetChanged();
	}
	

	@Override
	public int getCount() {
		return mList.size();
	}

	@Override
	public Object getItem(int position) {
		return mList.get(position);
	}

	@Override
	public long getItemId(int position) {
		return position;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		Holder holder = null;
		if (convertView == null) {
			LayoutInflater inflater = LayoutInflater.from(mContext);
			convertView = inflater.inflate(R.layout.list_item, null);
			holder = new Holder();
			holder.mNameText = (TextView)convertView.findViewById(R.id.name_text);
			holder.mIDText = (TextView)convertView.findViewById(R.id.id_text);
			convertView.setTag(holder);
		} else {
			holder = (Holder) convertView.getTag();
		}
		holder.mNameText.setText(mList.get(getCount() - position - 1).getName());
		holder.mIDText.setText(mList.get(getCount() - position - 1).getID());
		return convertView;
	}

	class Holder {
		private TextView mNameText, mIDText;
	}
}

PersonAdapter繼承自BaseAdapter,裡面的程式碼都應該比較熟悉。裡面注意這點程式碼:

public void refresh(ArrayList<PersonBean> list) {
		mList = list;
		notifyDataSetChanged();
	}

在初始化PersonAdapter的時候,需要外部匯入一個mList。

public PersonAdapter(ArrayList<PersonBean> list, Context context) {
		mList = list;
		mContext = context;
	}

在使用這種型別時,在Activity使用mAdapter.notifyDataSetChanged()時候,有時候會發現資料不能夠及時的更新。這個時候,就比較需要呼叫refresh()這個方法了。

下面看一下主Activity:

public class ListViewRefreshActivity extends Activity {

	private ListView mListView;
	private ArrayList<PersonBean> mList;
	private PersonAdapter mAdapter;
	private Handler mHandler;
	private String mName, mID;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		mListView = (ListView)findViewById(R.id.listView);
		mList = new ArrayList<PersonBean>();
		mAdapter = new PersonAdapter(mList, ListViewRefreshActivity.this);
		mListView.setAdapter(mAdapter);

		mHandler = new Handler() {

			@Override
			public void handleMessage(Message msg) {
				super.handleMessage(msg);
				mList.add((PersonBean) msg.obj);
				Log.v("@@@@@@", "this is get message");
				mAdapter.refresh(mList);
//				mAdapter.notifyDataSetChanged();
			}
		};

//		final Message message = new Message();
		new Thread(new Runnable() {

			@Override
			public void run() {
				try {
					for (int i = 0; i < 10; i++) {
						mName = "hao :" + i;
						mID = "" + i;
						PersonBean bean = new PersonBean();
						bean.setID(mID);
						bean.setName(mName);
						Message message = new Message();
						message.obj = bean;
						Thread.sleep(3000);
						mHandler.sendMessage(message);
//						mHandler.sendMessageDelayed(message, 10000);
					}}catch (Exception e) {
						e.printStackTrace();
					}
			}
		}).start();
	}
}
先說一個小bug吧,看一下在new Thread上面有一句註釋掉的

final Message message = new Message();

如果用這個message,註釋run方法體內的message,執行程式,在我機子上,傳送第四個訊息時,就會報android.util.AndroidRuntimeException:This message is already in use這個錯,message已經被使用。所以,每一次傳送,都要重新建立一個新的message。也可以使用一下語句:

message = mHandler.obtainMessage();
裡面主要看一下handler中重寫handlerMessage這個方法:

@Override
			public void handleMessage(Message msg) {
				super.handleMessage(msg);
				mList.add((PersonBean) msg.obj);
				Log.v("@@@@@@", "this is get message");
				mAdapter.refresh(mList);
//				mAdapter.notifyDataSetChanged();
			}

當然,在這個小例子中,使用mAdapter.refresh這個方法更麻煩點,直接呼叫notifyDataSetChange就可以達到效果,如果你的程式碼裡面不能達到效果,就可以使用mAdapter.refresh試一下。

notifyDataSetChanged這個方法的設計是典型觀察者模式。看一下原始碼:

public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
    private final DataSetObservable mDataSetObservable = new DataSetObservable();

    public boolean hasStableIds() {
        return false;
    }
    
    public void registerDataSetObserver(DataSetObserver observer) {
        mDataSetObservable.registerObserver(observer);
    }

    public void unregisterDataSetObserver(DataSetObserver observer) {
        mDataSetObservable.unregisterObserver(observer);
    }
    
    /**
     * Notifies the attached observers that the underlying data has been changed
     * and any View reflecting the data set should refresh itself.
     */
    public void notifyDataSetChanged() {
        mDataSetObservable.notifyChanged();
    }

    /**
     * Notifies the attached observers that the underlying data is no longer valid
     * or available. Once invoked this adapter is no longer valid and should
     * not report further data set changes.
     */
    public void notifyDataSetInvalidated() {
        mDataSetObservable.notifyInvalidated();
    }

有一個資料被觀察者:mDataSetObservable。當被觀察者資料發生改變時,通知觀察者。我們使用registerDataSetObserver這個方法註冊觀察者。都是呼叫notifyDataSetChanged方法。就是告訴觀察者,資料有所改變。在這個方法中,又呼叫了DataSetObserveable的notifyChanged方法:

/**
     * Invokes onChanged on each observer. Called when the data set being observed has
     * changed, and which when read contains the new state of the data.
     */
    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();
            }
        }
    }

看一下他的方法說明:當資料被觀察到已經改變,呼叫每一個觀察者的onChanged方法去讀取資料的最新狀態。

mObservers的定義如下:

protected final ArrayList<T> mObservers = new ArrayList<T>();

通過遍歷一個ArrayList來通知各個觀察者。

前面說到了,我們可以呼叫registerDataSetObserver註冊為觀察者,但是是在哪註冊的呢?因為如果沒有註冊,adapter就不應該發生變化。所以,我們看下ListView的SetAdapter這個方法:

@Override
    public void setAdapter(ListAdapter adapter) {
        if (mAdapter != null && mDataSetObserver != null) {
            mAdapter.unregisterDataSetObserver(mDataSetObserver);
        }

        resetList();
        mRecycler.clear();

        if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
            mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
        } else {
            mAdapter = adapter;
        }

        mOldSelectedPosition = INVALID_POSITION;
        mOldSelectedRowId = INVALID_ROW_ID;

        // AbsListView#setAdapter will update choice mode states.
        super.setAdapter(adapter);

        if (mAdapter != null) {
            mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
            mOldItemCount = mItemCount;
            mItemCount = mAdapter.getCount();
            checkFocus();

            mDataSetObserver = new AdapterDataSetObserver();
            mAdapter.registerDataSetObserver(mDataSetObserver);

            mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());

            int position;
            if (mStackFromBottom) {
                position = lookForSelectablePosition(mItemCount - 1, false);
            } else {
                position = lookForSelectablePosition(0, true);
            }
            setSelectedPositionInt(position);
            setNextSelectedPositionInt(position);

            if (mItemCount == 0) {
                // Nothing selected
                checkSelectionChanged();
            }
        } else {
            mAreAllItemsSelectable = true;
            checkFocus();
            // Nothing selected
            checkSelectionChanged();
        }

        requestLayout();
    }
如果mAdapter和mDataSetObserver都不為空的話,取消mAdapter對mDataSetObserver的註冊。

if (mAdapter != null && mDataSetObserver != null) {
            mAdapter.unregisterDataSetObserver(mDataSetObserver);
        }
然後,把傳入的adapter這個引數,賦值給mAdapter:

 if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
            mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
        } else {
            mAdapter = adapter;
        }

賦值成功後:

if (mAdapter != null) {
            mDataSetObserver = new AdapterDataSetObserver();
            mAdapter.registerDataSetObserver(mDataSetObserver);

重新為mDataSetObserver賦值,然後把mAdapter註冊為mDataSetObserver的觀察者。

至此,思路應該清晰了:在listview的setAdapter中把adapter註冊為mDataSetObserver的觀察者。當資料變化時,就可以呼叫notifyDataSetChanged方法來提示觀察者資料已經變化。

關於觀察者的詳細情況:淺學設計模式之觀察者<Observer>模式及在android中的應用


最後就是說一下,裡面PersonBean類,就是一個實體類,很簡單,不在詳述。

最後,原始碼:http://download.csdn.net/detail/aomandeshangxiao/4704585







相關文章