1. 程式人生 > >轉載:從源代碼的角度分析--在BaseAdapter調用notifyDataSetChanged()之後發生了什麽

轉載:從源代碼的角度分析--在BaseAdapter調用notifyDataSetChanged()之後發生了什麽

boolean abs when inf store checked 我們 return 回調

利用Adapter作為ListView的適配器,為ListView提供數據。選中某一項後,要讓這一項變成選中狀態,也就是背景圖片要換一下。下面我就用一個小例子來模擬。重點不在於實現,而是了解Adapter中notifyDataSetChanged()背後的運行機制。

我們先做一個小Demo(文中涉及的Demo在文章末尾),功能是選中某一項後,背景顏色會變紅。代碼非常簡單,這裏就不解釋了。值得註意的是,當我們需要ListView進行刷新的時候,我們需要調用Adapter.notifyDataSetChanged()來讓界面刷新。

技術分享圖片
 1 public class MainActivity extends Activity {
 2     @Override
 3     protected void onCreate(Bundle savedInstanceState) {
 4         
 5         super.onCreate(savedInstanceState);
 6         setContentView(R.layout.activity_main);
 7         
 8     ListView main_list = (ListView)this.findViewById(R.id.main_list);
 9     MyArrayAdapter mArrayList=new MyArrayAdapter(this,R.layout.list_item,getData());
10     main_list.setAdapter(mArrayList);
11     main_list.setOnItemClickListener(mArrayList);
12     }
13 
14     private String[] getData() { 
15         return new String[]{"測試數據1","測試數據2","測試數據3","測試數據4"};
16     }
17 }
技術分享圖片

適配器MyArrayAdapter代碼:

技術分享圖片
 1 public class MyArrayAdapter extends ArrayAdapter<String> implements
 2         OnItemClickListener {
 3 
 4     private int itemClicked;
 5     
 6     public MyArrayAdapter(Context context, int textViewResourceId,
 7             String[] objects) {
 8         super(context,  textViewResourceId, objects);
 9         
10     }
11 @Override
12 public View getView(int position, View convertView, ViewGroup parent) {
13 
14     convertView=super.getView(position, convertView, parent);
15     //如果是被點擊的項,變換顏色
16     if (position==this.itemClicked) {
17         convertView.setBackgroundColor(Color.RED);
18     }else {
19         convertView.setBackgroundColor(Color.WHITE);
20     }
21     return convertView;
22 }
23     @Override   
24     public void onItemClick(AdapterView<?> parent, View view, int position,
25             long id) {
26         //設置某項被點擊
27         itemClicked=position;
28         this.notifyDataSetChanged();
29     }
30 
31 }
技術分享圖片

下面就讓我們跟進去MyArrayAdapter.notifyDataSetChange()中看看。在本文中,我所查看的Android源代碼是4.4.0的,不同版本可能有所出入。

1     public void notifyDataSetChanged() {
2         super.notifyDataSetChanged();
3         mNotifyOnChange = true;
4     }

源代碼就簡單兩句話,那麽繼續看看super是什麽?

public class ArrayAdapter<T> extends BaseAdapter implements Filterable 

從類的聲明中,父類就是ArrayAdapter,而ArrayList的父類是BaseAdapter。我們跟進BaseAdapter中看看。

技術分享圖片
 1 public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
 2     private final DataSetObservable mDataSetObservable = new DataSetObservable();
 3     //...省略不必要的代碼
 4     public void registerDataSetObserver(DataSetObserver observer) {
 5         mDataSetObservable.registerObserver(observer);
 6     }
 7 
 8     public void unregisterDataSetObserver(DataSetObserver observer) {
 9         mDataSetObservable.unregisterObserver(observer);
10     }
11     
12     public void notifyDataSetChanged() {
13         mDataSetObservable.notifyChanged();
14     }
15     
16     public void notifyDataSetInvalidated() {
17         mDataSetObservable.notifyInvalidated();
18     }
19     //...省略不必要的代碼
20 }
技術分享圖片

我們發現其實就是DataSetObservable這個對象在發生作用,但是DataSetObservable這個對象估計就是一個簡單的觀察者的實現,Android框架的編寫者不大可能將業務邏輯放在這裏面,不過我們還是要確認是不是跟我們所想的一樣。

技術分享圖片
 1 public class DataSetObservable extends Observable<DataSetObserver> {
 2     /**
 3      * Invokes onChanged on each observer. Called when the data set being observed has
 4      * changed, and which when read contains the new state of the data.
 5      */
 6     public void notifyChanged() {
 7         synchronized(mObservers) {
 8             for (DataSetObserver observer : mObservers) {
 9                 observer.onChanged();
10             }
11         }
12     }
13 
14     /**
15      * Invokes onInvalidated on each observer. Called when the data set being monitored
16      * has changed such that it is no longer valid.
17      */
18     public void notifyInvalidated() {
19         synchronized (mObservers) {
20             for (DataSetObserver observer : mObservers) {
21                 observer.onInvalidated();
22             }
23         }
24     }
25 }
技術分享圖片

果然,跟預想的一樣,它只是簡單地調用了綁定在它身上的回調接口。那麽BaseAdapter.notifyDataSetChange()的接口具體是在哪裏綁定的呢?很有可能在構造函數中綁定,我們跟進ArrayListAdapter看看。

技術分享圖片
 1     public ArrayAdapter(Context context, int textViewResourceId, List<T> objects) {
 2         init(context, textViewResourceId, 0, objects);
 3     }
 4     
 5     private void init(Context context, int resource, int textViewResourceId, List<T> objects) {
 6         mContext = context;
 7         mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
 8         mResource = mDropDownResource = resource;
 9         mObjects = objects;
10         mFieldId = textViewResourceId;
11     }
技術分享圖片

ArrayListAdapter中有很多構造函數,但是幾經輾轉全部都會轉到init()函數中,很遺憾,我們撲空了。那麽還在哪裏可能綁定notifyDataSetChange()回調函數呢?其實從MainActivity中Adapter的初始化過程中,基本上只能鎖定在MainActivity第十行中setAdapter函數中。接下去看看 public void setAdapter(ListAdapter adapter)這個函數。

技術分享圖片
 1     public void setAdapter(ListAdapter adapter) {
 2         if (null != mAdapter) {
 3             mAdapter.unregisterDataSetObserver(mDataSetObserver);
 4         }
 5 
 6         resetList();
 7         mRecycler.clear();
 8 
 9         if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
10             mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
11         } else {
12             mAdapter = adapter;
13         }
14 
15         mOldSelectedPosition = INVALID_POSITION;
16         mOldSelectedRowId = INVALID_ROW_ID;
17         if (mAdapter != null) {
18             mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
19             mOldItemCount = mItemCount;
20             mItemCount = mAdapter.getCount();
21             checkFocus();
22 
23             mDataSetObserver = new AdapterDataSetObserver();
24             mAdapter.registerDataSetObserver(mDataSetObserver);
25 
26             mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
27 
28             int position;
29             if (mStackFromBottom) {
30                 position = lookForSelectablePosition(mItemCount - 1, false);
31             } else {
32                 position = lookForSelectablePosition(0, true);
33             }
34             setSelectedPositionInt(position);
35             setNextSelectedPositionInt(position);
36 
37             if (mItemCount == 0) {
38                 // Nothing selected
39                 checkSelectionChanged();
40             }
41 
42             if (mChoiceMode != CHOICE_MODE_NONE &&
43                     mAdapter.hasStableIds() &&
44                     mCheckedIdStates == null) {
45                 mCheckedIdStates = new LongSparseArray<Boolean>();
46             }
47 
48         } else {
49             mAreAllItemsSelectable = true;
50             checkFocus();
51             // Nothing selected
52             checkSelectionChanged();
53         }
54 
55         if (mCheckStates != null) {
56             mCheckStates.clear();
57         }
58         
59         if (mCheckedIdStates != null) {
60             mCheckedIdStates.clear();
61         }
62 
63         requestLayout();
64     }
技術分享圖片

setAdapter(...)這個函數有點長,不過我們只需要關註跟notifiDataSetChange()有關的實現,也就是第23、24行。不過這裏另一個值得關註的點就是第63行,requestLayout()這個函數,它主要就是用來刷新界面,讓界面重新繪制的。在23,、24行,綁定了一個AdapterDataSetObserver對象,下面我們就跟進去看看。從前面DataSetObservable的實現中,我們知道了它在notifyDataSetChange()的時候會調用DataSetObserver的onChange()。

技術分享圖片
 1   class AdapterDataSetObserver extends DataSetObserver
 2   {
 3     private Parcelable mInstanceState = null;
 4 
 5     AdapterDataSetObserver() {
 6     }
 7     public void onChanged() { mDataChanged = true;
 8       mOldItemCount = mItemCount;
 9       mItemCount = getAdapter().getCount();
10 
11       if ((getAdapter().hasStableIds()) && (mInstanceState != null) && (mOldItemCount == 0) && (mItemCount > 0))
12       {
13         onRestoreInstanceState(mInstanceState);
14         mInstanceState = null;
15       } else {
16         rememberSyncState();
17       }
18       checkFocus();
19       requestLayout();
20     }
21     //...省略不必要代碼
22 }
技術分享圖片

終於,在第19行,我們看見了requestLayout(),它就是用來重繪界面的,它在ViewRootImpl.java中有具體的實現。

1     public void requestLayout() {
2         checkThread();
3         mLayoutRequested = true;
4         scheduleTraversals();
5     }

關於scheduleTraversals()的實現,涉及到Android中View的繪制流程,感興趣的可以看看《Android視圖狀態及重繪流程分析,帶你一步步深入了解View(三)》。

到了這裏,我們就清楚了notifyDataSetChange()背後的實現機制了,在不知不覺之間Android框架幫我們幹了很多事情,不過需要提醒的時,每一次notifyDataSetChange()都會引起界面的重繪。當需要修改界面上View的相關屬性的時候,最後先設置完成再調用notifyDataSetChange()來重繪界面。

轉載鏈接:https://www.cnblogs.com/kissazi2/p/3721941.html

轉載:從源代碼的角度分析--在BaseAdapter調用notifyDataSetChanged()之後發生了什麽