概述

Android中大量存在着适配器模式,其中的设计思路就是Adapter(提供数据)设在到AdapterView(展示数据集合的视图),其中Adapter体系结构如下

AdapterViewListViewGridViewSpinner和ExpandableListView等,AdapterAdapterView又使用了观察者模式,
其中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的继承类有ListViewGridViewExpandableListView
相较于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, SpinnerAppCompatSpinner
相较于Adapter,SpinnerAdapter中新增了如下方法

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

BaseAdapter

BaseAdapter实现了ListAdapterSpinnerAdapter ,源码地址 : 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文件,textViewResourceIditem中的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中添加了对数据源的addaddAllinsertremoveclear操作,此时如果对数据源进行相关操作,
会抛出Java.lang.UnsupportedOperationException异常,因为Arrays.asList()转换的Listjava.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

SimpleAdapterArrayAdapter一样,都是实现了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<? extends Map> data,            @LayoutRes int resource, String[] from, @IdRes int[] to) { //... }

SimpleAdapter中允许传入一个List<? extends Map>类型的数据源,
每个数据项对应一个Mapfrom表示的是Mapkey的数组。
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.

参考:
使用详解及源码解析Android中的Adapter、BaseAdapter、ArrayAdapter、SimpleAdapter和SimpleCursorAdapter

Android设计模式源码解析之ListView观察者模式

Adapter数据变化改变现有View的实现原理及案例

更多相关文章

  1. AsyncHttpClient的使用
  2. Android(安卓)8.0源码编译问题记录
  3. Android(安卓)锁屏和黑屏的广播消息
  4. 关于android与h5互调简介
  5. Android中铃声总结【安卓源码解析一】
  6. ClipDrawable让开发变得更简单
  7. Android(安卓)Volley框架
  8. AndroidO SystemUI-QuickSettings
  9. Android应用程序组件间通信(一)——Intent类简介

随机推荐

  1. android页面切换动画
  2. android 打开app先显示欢迎界面后自动跳
  3. com.android.ide.common.signing.Keytool
  4. 如何关闭android studio3.0的自动保存功
  5. android p 4G LTE 默认关闭修改
  6. Android(安卓)GridView 例子
  7. PC端与android手机端使用adb forword通信
  8. Android网络电话软件Sipdroid试用
  9. 编译android出现"too many open files"的
  10. OpenGL ES 画正方形