Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 源碼解析之Adapter和AdapterView

Android 源碼解析之Adapter和AdapterView

編輯:關於Android編程

概述

在Android中大量存在著適配器模式,其中的設計思路就是Adapter(提供數據)設在到AdapterView(展示數據集合的視圖),其中Adapter體系結構如下

而AdapterView有ListView、GridView、Spinner和ExpandableListView等,Adapter和AdapterView又使用了觀察者模式,
其中Adapter相當於被觀察者,AdapterView相當於觀察者

Adapter體系

Adapter

Adapter是一個頂層接口,源碼地址: http://www.grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.1.1_r1/android/widget/Adapter.java
其中定義了如下方法:

void registerDataSetObserver(DataSetObserver observer);注冊觀察者. void unregisterDataSetObserver(DataSetObserver observer);反注冊觀察者. int getCount();返回Adapter中數據集的數量. Object getItem(int position);根據position獲取數據集中相應的數據項. long getItemId(int position);獲取postion位置數據項的id,通常為position. boolean hasStableIds();當數據源發生了變化的時候,原有數據項id會不會變化.true表示不變,false可能變化.默認為false. View getView(int position, View convertView, ViewGroup parent);根據position創建對應的ui子項.

ListAdapter

ListAdapter繼承自Adapter,源碼地址: http://www.grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.1.1_r1/android/widget/ListAdapter.java
在AbsListView中的setAdapter(ListAdapter adapter)方法中傳入的就是這個Adapter,AbsListView的繼承類有ListView、GridView和ExpandableListView
相較於Adapter,ListAdapter中增加了如下方法

boolean areAllItemsEnabled();Adapter中所有的數據源是否是enabled的. boolean isEnabled(int position);對應position的Item是否是enabled的.

SpinnerAdapter

SpinnerAdapter也是繼承自Adapter,源碼地址: http://www.grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.1.1_r1/android/widget/SpinnerAdapter.java
在AbsSpinner中的setAdapter(SpinnerAdapter adapter)方法中傳入的就是這個Adapter,AbsSpinner的繼承類有Gallery, Spinner和AppCompatSpinner
相較於Adapter,SpinnerAdapter中新增了如下方法

View getDropDownView(int position, View convertView, ViewGroup parent);此方法如getview的聲明類似.主要供AbsSpinner生成下拉彈出框的UI

BaseAdapter

BaseAdapter實現了ListAdapter和SpinnerAdapter ,源碼地址 : http://www.grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.1.1_r1/android/widget/BaseAdapter.java
BaseAdapter中實現了觀察者模式,其中維護了一個 DataSetObservable,用於數據集變化的觀察者操作.
而且BaseAdapter中重寫了getDropDownView,但是其中直接調用了getView方法並返回.
其中復寫了一些方法,設置了默認值,留下一寫用戶必須實現的比如getView方法等.

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

    public boolean hasStableIds() {
        return false;
    }

   //...

    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;
    }
}

ArrayAdapter

ArrayAdapter繼承BaseAdapter抽象類,並實現了Filterable, ThemedSpinnerAdapter接口,源碼地址: http://www.grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.1.1_r1/android/widget/ArrayAdapter.java
其中ArrayAdapter的構造方法

    /**
     * Constructor
     *
     * @param context The current context.
     * @param resource The resource ID for a layout file containing a layout to use when
     *                 instantiating views.
     * @param textViewResourceId The id of the TextView within the layout resource to be populated
     * @param objects The objects to represent in the ListView.
     */
    public ArrayAdapter(@NonNull Context context, @LayoutRes int resource,
            @IdRes int textViewResourceId, @NonNull List objects) {
        mContext = context;
        mInflater = LayoutInflater.from(context);
        mResource = mDropDownResource = resource;
        mObjects = objects;
        mFieldId = textViewResourceId;
    }

其中resource是數據項對應的layout文件,textViewResourceId是item中的TextView的id(因為ArrayAdapter只能顯示文本列表,Layout中必須包含TextView)
如果我們的Layout文件以TextView作為根節點,那麼id傳入0即可,及調用其重載構造函數即可.否則就會調用view.findViewById(mFieldId);找到TextView.

// ArrayAdapter$getView()
@Override
    public @NonNull View getView(int position, @Nullable View convertView,
            @NonNull ViewGroup parent) {
        return createViewFromResource(mInflater, position, convertView, parent, mResource);
    }

    private @NonNull View createViewFromResource(@NonNull LayoutInflater inflater, int position,
            @Nullable View convertView, @NonNull ViewGroup parent, int resource) {
        //...

            if (mFieldId == 0) {
                //  If no custom field is assigned, assume the whole resource is a TextView
                text = (TextView) view;
            } else {
                //  Otherwise, find the TextView field within the layout
                text = (TextView) view.findViewById(mFieldId);
               //...
            }

        //...

        final T item = getItem(position);
        if (item instanceof CharSequence) {
            text.setText((CharSequence) item);
        } else {
            text.setText(item.toString());
        }

        return view;
    }

其中objects就是數據源,但是這個數據源需要注意的是不能傳入數組 ,因為如果傳入數組最終會通過Arrays.asList()轉換為list,
但是ArrayAdapter中添加了對數據源的add、addAll、insert、remove、clear操作,此時如果對數據源進行相關操作,
會拋出Java.lang.UnsupportedOperationException異常,因為Arrays.asList()轉換的List是java.util.AbstractList類型.
見源碼部分:

 public ArrayAdapter(@NonNull Context context, @LayoutRes int resource,
            @IdRes int textViewResourceId, @NonNull T[] objects) {
        this(context, resource, textViewResourceId, Arrays.asList(objects));
    }
// ArrayAdapter 的增加操作
public void add(@Nullable T object) {
        synchronized (mLock) {
            if (mOriginalValues != null) {
                mOriginalValues.add(object);
            } else {
                mObjects.add(object);
            }
        }
        if (mNotifyOnChange) notifyDataSetChanged();
    }

如上就是ArrayAdapter中的add操作,這裡有一個mNotifyOnChange變量控制是否需要調用notifyDataSetChanged方法來刷新界面,這在增加多條數據的時候尤其有用,
因為調用此方法後要渲染整個UI,多次重新繪制會降低性能,因此可以利用此變量在數據添加完成後調用刷新操作.

SimpleAdapter

SimpleAdapter和 ArrayAdapter一樣,都是實現了BaseAdapter,源碼地址: http://www.grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.1.1_r1/android/widget/SimpleAdapter.java
我們看一下SimpleAdapter的構造函數

 public SimpleAdapter(Context context, List> data,
            @LayoutRes int resource, String[] from, @IdRes int[] to) {
 //...
 }

SimpleAdapter中允許傳入一個List>類型的數據源,
每個數據項對應一個Map,from表示的是Map中key的數組。
to 這個數組中傳入的就是要設置數據的id,數組長度為map的大小.

SimpleAdapter的簡單示例: SimpleAdapterDemo.java

SimpleCursorAdapter

SimpleCursorAdapter源碼地址: http://www.grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.1.1_r1/android/widget/SimpleCursorAdapter.java
其繼承路線如下所示:
SimpleCursorAdapter->ResourceCursorAdapter->CursorAdapter->BaseAdapter

SimpleCursorAdapter常和數據庫關聯使用,比如如下示例展示手機中的聯系人列表

public class MyListActivity extends ListActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Get a cursor with all people
        Cursor c = getContentResolver().query(Contacts.CONTENT_URI,
                CONTACT_PROJECTION, null, null, null);
        startManagingCursor(c);

        ListAdapter adapter = new SimpleCursorAdapter(this,
                // Use a template that displays a text view
                android.R.layout.simple_list_item_1,
                // Give the cursor to the list adatper
                c,
                // Map the NAME column in the people database to...
                new String[] {Contacts.DISPLAY_NAME},
                // The "text1" view defined in the XML template
                new int[] {android.R.id.text1});
        setListAdapter(adapter);
    }

    private static final String[] CONTACT_PROJECTION = new String[] {
        Contacts._ID,
        Contacts.DISPLAY_NAME
    };
}

如上示例僅僅是一個簡單的示例,在實際使用中我們一般會添加Loader來加載Contentrovider的數據,因為這是一個耗時的操作.

AdapterView與Adapter的綁定

AdapterView 的繼承體系如下,其中大多數我們都有使用過,在我們使用過程中,一般都是調用方法setAdapter(Adapter)來設置數據,
之後數據就可以展示到界面上來了,也就是說他們之間的綁定操作就在此了.在之前的Adapter模式中有介紹,這裡我們以ListView為例來看看他們是如何通信的

我們都知道給ListView設置Adapter的時候,一般都是繼承BaseAdapter,且其中有一個方法notifyDataSetChanged來重新繪制界面
可以查看上面BaseAdapter的源碼

  public void registerDataSetObserver(DataSetObserver observer) {
         mDataSetObservable.registerObserver(observer);
  }
  public void unregisterDataSetObserver(DataSetObserver observer) {
         mDataSetObservable.unregisterObserver(observer);
  }
 public void notifyDataSetChanged() {
        mDataSetObservable.notifyChanged();
 }

其調用了DataSetObservable的提示更新的方法,這裡采用了Observable/Observer(觀察者模式).

 // $DataSetObservable .notifyChanged()
 // 通知觀察者
 public void notifyChanged() {
        synchronized(mObservers) {
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onChanged();
            }
        }
    }

 // abstract DataSetObserver.onChanged()
 // 觀察者更新
 public void onChanged() {
         // Do nothing
     }

其中 BaseAdapter中還要兩個方法用於 注冊和反注冊觀察者 ,觀察者只有注冊到了被觀察者中才能起作用,我們來看一下setAdapter方法的源碼

 @Override
    public void setAdapter(ListAdapter adapter) {
        //...
        // AbsListView#setAdapter will update choice mode states.
        super.setAdapter(adapter);

        if (mAdapter != null) {
            //...

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

            mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());

           //...
        } else {
           //...
        }

        requestLayout();
    }

這裡可以看到setAdapter方法中構建了一個AdapterDataSetObserver並設置給了Adapter,即注冊了觀察者,之後就可以調用觀察者的通知方法了.
繼續追蹤,我們可以找到這個AdapterDataSetObserver是在AbsListView中定義的

    //繼承自AdapterView內部類AdapterDataSetObserver的AdapterDataSetObserver
    class AdapterDataSetObserver extends AdapterView.AdapterDataSetObserver {
        @Override
        public void onChanged() {
            super.onChanged();
            //...
        }

        @Override
        public void onInvalidated() {
            super.onInvalidated();
            //...
        }
    }

總結起來就是AdapterView中的setAdapter方法會創建一個觀察者注冊到Adapter來觀察數據集合的改變,
當Adapter中持有的數據集合改變的時候,就會通知觀察者來更新UI.

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved