概述

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的继承类有GallerySpinnerAppCompatSpinner
相较于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抽象类,并实现了FilterableThemedSpinnerAdapter接口,源码地址: 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()
@Overridepublic @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 TextViewtext = (TextView) view;} else {// Otherwise, find the TextView field within the layouttext = (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

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<? 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 {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// Get a cursor with all peopleCursor 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 viewandroid.R.layout.simple_list_item_1,// Give the cursor to the list adatperc,// Map the NAME column in the people database to...new String[] {Contacts.DISPLAY_NAME},// The "text1" view defined in the XML templatenew 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方法的源码

 @Overridepublic 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的AdapterDataSetObserverclass AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {@Overridepublic void onChanged() {super.onChanged();//...}@Overridepublic void onInvalidated() {super.onInvalidated();//...}}

总结起来就是AdapterView中的setAdapter方法会创建一个观察者注册到Adapter来观察数据集合的改变,
Adapter中持有的数据集合改变的时候,就会通知观察者来更新UI.

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

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

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

Android 源码解析之Adapter和AdapterView与适配器模式相关推荐

  1. Android源码解析--AlertDialog及AlertDialog.Builder

    昨天晚上弄到很晚,简单的看了下Dialog的源码,说要分析下建造者模式,在dialog里面的应用其实是在AlertDialog中. 按照惯例,先看类说明: [java] view plaincopy ...

  2. Android源码解析(一)动画篇-- Animator属性动画系统

    Android源码解析-动画篇 Android源码解析(一)动画篇-- Animator属性动画系统 Android源码解析(二)动画篇-- ObjectAnimator Android在3.0版本中 ...

  3. Android源码解析--SwipeMenuListView仿QQ聊天左滑

    版权声明:本文为博主原创文章,转载请标明出处. https://blog.csdn.net/lyhhj/article/details/50612714 绪论: 好久没写博客了,最近比较懒,不想写博客 ...

  4. Android源码解析之Bitmap占用内存正确的计算公式 你知道吗

    Bitmap 前言 Bitmap简介 像素存储方式 图片压缩格式 Bitmap内存计算 获取Bitmap所占内存 计算所占内存 举例 Bitmap.getAllocationByteCount Bit ...

  5. Android源码解析

    一.Android系统启动过程 启动电源以及系统启动:当电源按下时引导芯片代码从预定义的地方(固化在ROM)开始执行.加载引导程序BootLoader到RAM中并执行 引导程序BootLoader:它 ...

  6. Android 源码解析之AsyncTask

    AsyncTask相信大家都不陌生,它是为了简化异步请求.更新UI操作而诞生的.使用它不仅可以完成我们的网络耗时操作,而且还可以在完成耗时操作后直接的更新我们所需要的UI组件.这使得它在android ...

  7. 2048android源代码,VasSonic Android源码解析

    VasSon一很等指似很一者下插近直好一的的有段文,ic是腾讯推出的为了提高H5页面首屏加载速度而推出的高性能Hybrid框架,目前广泛应用在QQ商城等Hybrid界面中,以提高用户体调代求学功解宗维 ...

  8. Android源码解析--dropbox日志:DropBoxManagerService(DBMS)服务详解

    DropBoxManagerService 简介 DropBoxManagerService(简称DBMS)是日志相关的服务,用于生成与管理 系统运行时的一些日志文件.日志文件大多记录的是系统或某个应 ...

  9. Android源码解析--超好看的下拉刷新动画

    本篇博客代码下载地址:https://github.com/Yalantis/Taurus 最近在github上看到了好多高端.大气.上档次的动画效果,如果给你的项目中加上这些动画,相信你的app一定 ...

最新文章

  1. Android Studio 中修改versionCode跟versionName (更新版本)
  2. eclipse编辑java_15个小type:教你高效使用Eclipse Java IDE
  3. 使用ListView实现汽泡短信聊天
  4. Yarn ResourceManager High Availability
  5. Entity Framework 6以Code First方式搭建Sqlite数据库环境
  6. 简述计算机文件的命名办法,如何进行文件命名-如何进行文件管理
  7. Phonetics: Lecture Three 语音 第三课 Teacher:Patrick
  8. 求N个数的最大公倍数
  9. Jmeter之JDBC Request与mysql
  10. Tomcat的部署+第一个Servlet
  11. java 加法程序_使用JAVAEE编写简单的加法程序
  12. Application page and Site page
  13. matlab imhist与histeq函数
  14. 天轰穿·甜老丝儿。科创少年
  15. 3种方法设置PPT文件保护
  16. 软件测试周刊(第54期):管他乐观还是悲观,都滚蛋,干就完了。
  17. html5图片做成简单拼图,html5版canvas自由拼图实例_html5教程技巧
  18. latex图表中英文双标题的使用技巧
  19. EDM系统看板多邮件模板邮箱配置地址簿​EDM营销推送邮件系统开发
  20. kubernetes1.6中redis-mong-zookeepe-rabbitmq集群部署

热门文章

  1. FreeRTOS调度器启动
  2. 程序员熬夜保健实用大全
  3. app上架百度手机助手流程
  4. caffe batchnorm
  5. win10自带的便笺便签有哪些方便快捷的操作小技巧?
  6. 用户和计算机硬盘系统的接口,[pc玩家]关于固态硬盘接口和协议的那些事儿
  7. C++:STL超全用法归纳
  8. android internal storage 路径,内部存储InternalStorage和外部存储ExternalStorage-Android
  9. 设计模式—工厂设计模式
  10. java excel数据组装json成数据