Android 源码解析之Adapter和AdapterView与适配器模式

概述

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

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<T> 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<String, ?>> data,
            @LayoutRes int resource, String[] from, @IdRes int[] to) {
 //...
 }

SimpleAdapter中允许传入一个List<? extends Map<String, ?>>类型的数据源,
每个数据项对应一个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 的继承体系如下,

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<ListAdapter>.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
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值