[转载]ListView中使用自定义Adapter及时更新数据
摘要 在项目中,遇到不能ListView及时更新的问题。写了一个demo,其中也遇到一些问题,一并写出来。 好吧,上代码 : public class PersonAdapter extends BaseAdapter { private ArrayListPersonBean mList; private Context mContext; public PersonAdapter(Arr
在项目中,遇到不能ListView及时更新的问题。写了一个demo,其中也遇到一些问题,一并写出来。好吧,上代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
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,里面的代码都应该比较熟悉。里面注意这点代码:
1
2
3
4
|
public void refresh(ArrayList<PersonBean> list) { mList = list; notifyDataSetChanged(); } |
在初始化PersonAdapter的时候,需要外部导入一个mList。
1
2
3
4
|
public PersonAdapter(ArrayList<PersonBean> list, Context context) { mList = list; mContext = context; } |
在使用这种类型时,在Activity使用mAdapter.notifyDataSetChanged()时候,有时候会发现数据不能够及时的更新。这个时候,就比较需要调用refresh()这个方法了。
下面看一下主Activity:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
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上面有一句注释掉的
1
|
final Message message = new Message(); |
如果用这个message,注释run方法体内的message,运行程序,在我机子上,发送第四个消息时,就会报android.util.AndroidRuntimeException:This message is already in use这个错,message已经被使用。所以,每一次发送,都要重新创建一个新的message。也可以使用一下语句:
1
|
message = mHandler.obtainMessage(); |
里面主要看一下handler中重写handlerMessage这个方法:
1
2
3
4
5
6
7
8
|
@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这个方法的设计是典型观察者模式。看一下源代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
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方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
/** * 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的定义如下:
1
|
protected final ArrayList<T> mObservers = new ArrayList<T>(); |
通过遍历一个ArrayList来通知各个观察者。
前面说到了,我们可以调用registerDataSetObserver注册为观察者,但是是在哪注册的呢?因为如果没有注册,adapter就不应该发生变化。所以,我们看下ListView的SetAdapter这个方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
@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的注册。
1
2
3
|
if (mAdapter != null && mDataSetObserver != null ) { mAdapter.unregisterDataSetObserver(mDataSetObserver); } |
然后,把传入的adapter这个参数,赋值给mAdapter:
1
2
3
4
5
|
if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) { mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter); } else { mAdapter = adapter; } |
赋值成功后:
1
2
3
|
if (mAdapter != null ) { mDataSetObserver = new AdapterDataSetObserver(); mAdapter.registerDataSetObserver(mDataSetObserver); |
重新为mDataSetObserver赋值,然后把mAdapter注册为mDataSetObserver的观察者。
至此,思路应该清晰了:在listview的setAdapter中把adapter注册为mDataSetObserver的观察者。当数据变化时,就可以调用notifyDataSetChanged方法来提示观察者数据已经变化。
最后就是说一下,里面PersonBean类,就是一个实体类,很简单,不在详述。