ListView中使用自定义Adapter及时更新数据

在项目中,遇到不能ListView及时更新的问题。写了一个demo,其中也遇到一些问题,一并写出来。好吧,上代码:

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方法来提示观察者数据已经变化。

最后就是说一下,里面PersonBean类,就是一个实体类,很简单,不在详述。

最后,源代码:http://download.csdn.net/detail/aomandeshangxiao/4704585