Android(安卓)源码解析之Adapter和AdapterView与适配器模式
概述
在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<? extends Map> data, @LayoutRes int resource, String[] from, @IdRes int[] to) { //... }
SimpleAdapter
中允许传入一个List<? extends Map
类型的数据源,
每个数据项对应一个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.
参考:
使用详解及源码解析Android中的Adapter、BaseAdapter、ArrayAdapter、SimpleAdapter和SimpleCursorAdapter
Android设计模式源码解析之ListView观察者模式
Adapter数据变化改变现有View的实现原理及案例
更多相关文章
- AsyncHttpClient的使用
- Android(安卓)8.0源码编译问题记录
- Android(安卓)锁屏和黑屏的广播消息
- 关于android与h5互调简介
- Android中铃声总结【安卓源码解析一】
- ClipDrawable让开发变得更简单
- Android(安卓)Volley框架
- AndroidO SystemUI-QuickSettings
- Android应用程序组件间通信(一)——Intent类简介