前言:生活多惊喜,多点无畏,少点伤悲,明天会更好。
1、adapter的引入
ListView在RecyclerView出现以前是apk应用中最常出现的展示数据列表的View,ListView用数据列表的形式展示数据的同时,我们只需要传入相应的适配器,它会自动根据提供数据的长度把所有的数据依次展示出来。
ListView可以展示简单的一行文本,也可以展示一个组合的控件,这些都跟listview的适配器息息相关。ListView功能强大,使用灵活多变,如此强大的功能是如何实现的呢,只传入数据和设置适配器就可以展示出界面其内部又是如何工作的呢。ListView中用到的适配器跟GOF中的适配器模式是一个道理,今天我们就来看看ListView中的各种适配器。
ListView的适配器包括ArrayAdapter,SimpleAdapter,SimpleCursorAdapter,BaseAdapter:
2、ArrayAdapter:
ArrayAdapter可以实现简单的ListView的数据绑定。默认情况下,ArrayAdapter把传入适配器的数组的数据的toString值绑定到layout中预先定义的TextView控件上。使用ArrayAdapter只能显示简单的文本信息,其他的复杂信息无法显示,但对于某些简单的信息展示,ArrayAdapter的使用非常方便快捷。
例子:
ArrayAdapter使用的布局
</RelativeLayout>
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView
android:id="@+id/tv1"
android:textSize="15sp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</RelativeLayout>
public class MainActivity1 extends AppCompatActivity {
private ListView listView;
private List<HashMap<String ,String>> listdata=null;
String[] strs = new String[] {
"first", "second", "third", "fourth", "fifth","first", "second", "third", "fourth", "fifth","first", "second", "third", "fourth", "fifth","first", "second", "third", "fourth", "fifth"
,"first", "second", "third", "fourth", "fifth","first", "second", "third", "fourth", "fifth","first", "second", "third", "fourth", "fifth"
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main1);
listView= (ListView) findViewById(R.id.listview1);
initData();
listView.setAdapter(new ArrayAdapter<String>(MainActivity1.this,android.R.layout.simple_list_item,strs));
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Toast.makeText(MainActivity1.this,"click="+position,Toast.LENGTH_SHORT).show();
}
});
// listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
}
public void initData(){
for (int i=0;i<50;i++){
HashMap<String, String> map = new HashMap<String, String>();
map.put("key1","item="+i);
map.put("key2","itemxxxxxx="+i);
listdata.add(map);
}
}
}
3、SimpleAdapter:
当我们想利用ListView显示图片时,ArrayAdapter是无法满足需求的,这时候可以使用SimpleAdapter。SimpleAdapter通过它自定义ListView中的item的内容,比如图片、多选框等,利用new String[]进行指定,绑定多个控件显示多个信息,当需要显示相对复杂点的界面时可以使用。
SimpleCursorAdapter跟SimpleAdapter类似,可以接收数据库数据进行显示, ListAdapter listAdapter = new SimpleCursorAdapter(this, android.R.layout.simple_expandable_list_item_1,cursor, new String[]{}, new int[]{});可以直接传入游标进行数据的显示。
布局文件:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView
android:id="@+id/tv1"
android:textSize="15sp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<TextView
android:id="@+id/tv2"
android:layout_below="@id/tv1"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<ImageView
android:id="@+id/iv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
/>
public class MainActivity extends AppCompatActivity {
private List<HashMap<String ,Object>> listdata=null;
private ListView listView;
private TextView tv1;
private TextView tv2;
private ImageView iv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main1);
listView = (ListView) findViewById(R.id.listview1);
tv1 = (TextView) findViewById(R.id.tv1);
tv2 = (TextView) findViewById(R.id.tv2);
iv = (ImageView) findViewById(R.id.iv);
initData();
SimpleAdapter sa=new SimpleAdapter(MainActivity.this,listdata,R.layout.mylistviewitme1,new String []{"key1","key2","iv"},new int[]{R.id.tv1,R.id.tv2,R.id.iv});
listView.setAdapter(sa);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Toast.makeText(MainActivity.this,"click="+position,Toast.LENGTH_SHORT).show();
}
});
}
public void initData(){
listdata=new ArrayList<HashMap<String ,Object>>();
for (int i=0;i<50;i++){
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("key1","item="+i);
map.put("key2","itemxxxxxx="+i);
map.put("iv",R.mipmap.ic_launcher);
listdata.add(map);
}
}
}
4、BaseAdapter:
上面介绍的三种adapter都是系统为我们封装好的,使用非常的方便只需要new Adapter时传入相应的数据就可以很好的展示页面,但是有时候,列表不光会用来做显示用,我们同样可以在在上面添加按钮事件。在布局文件中添加button,利用上面的方法定义一个适配器,然后将数据映射到布局文件上,但按钮是无法映射的,即使你成功的用布局文件显示出了按钮也无法添加按钮的响应,这时必须要重写一个类继承BaseAdapter。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView
android:id="@+id/tv1"
android:textSize="15sp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<TextView
android:id="@+id/tv2"
android:layout_below="@id/tv1"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<ImageView
android:id="@+id/iv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
/>
<Button
android:id="@+id/button"
android:layout_below="@id/tv2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="click"/>
</RelativeLayout>
package exercise.ts.com.listviewdemo;
/**
* Created by user on 17-4-3.
*/
public class ListviewAdapterDemo1 extends BaseAdapter {
private List<HashMap<String ,Object>> listdata=null;
LayoutInflater layoutInflater;
private Context context;
ListviewAdapterDemo1(Context context){
this.context=context;
this.layoutInflater=LayoutInflater.from(context);
initData();
}
public void initData(){
listdata=new ArrayList<HashMap<String ,Object>>();
for (int i=0;i<50;i++){
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("key1","item="+i);
map.put("key2","itemxxxxxx="+i);
map.put("iv",R.mipmap.ic_launcher);
listdata.add(map);
}
}
@Override
public int getCount() {
return listdata.size();
}
@Override
public Object getItem(int position) {
return listdata.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHold viewHold ;
if (convertView == null){
viewHold=new ViewHold();
convertView=layoutInflater.inflate(R.layout.mylistviewitme1,null);
viewHold.tv1= (TextView) convertView.findViewById(R.id.tv1);
viewHold.tv2= (TextView) convertView.findViewById(R.id.tv2);
viewHold.iv= (ImageView) convertView.findViewById(R.id.iv);
viewHold.button= (Button) convertView.findViewById(R.id.button);
convertView.setTag(viewHold);
}else{
viewHold= (ViewHold) convertView.getTag();
}
viewHold.tv1.setText(listdata.get(position).get("key1").toString());
viewHold.tv2.setText(listdata.get(position).get("key2").toString());
viewHold.iv.setImageResource((Integer) listdata.get(position).get("iv"));
viewHold.button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(context,"button click",Toast.LENGTH_SHORT).show();
}
});
viewHold.iv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(context,"tupianclick",Toast.LENGTH_SHORT).show();
}
});
return convertView;
}
final class ViewHold{
public TextView tv1;
public TextView tv2;
public ImageView iv;
public Button button;
}
}
5 、BaseAdapter适配器源码分析
看一下用到的几种适配器的继承结构:
可以看到simpleAdapter、SimpleCursorAdapter、ArrayAdapter都是BaseAdapter的子类。我们只需要研究BaseAdapter就可以知道其内部的工作原理。
5.1 Adapter接口
所有的适配器都实现了Adapter接口。
public interface Adapter {
void registerDataSetObserver(DataSetObserver observer);
void unregisterDataSetObserver(DataSetObserver observer);
int getCount();
Object getItem(int position);
long getItemId(int position);
boolean hasStableIds();
View getView(int position, View convertView, ViewGroup parent);
static final int IGNORE_ITEM_VIEW_TYPE = AdapterView.ITEM_VIEW_TYPE_IGNORE;
int getItemViewType(int position);
int getViewTypeCount();
static final int NO_SELECTION = Integer.MIN_VALUE;
boolean isEmpty();
}
public abstract void registerDataSetObserver (DataSetObserver observer)
Adapter表示一个数据源,这个数据源是有可能发生变化的,比如增加了数据、删除了数据、修改了数据,当数据发生变化的时候,它要通知相应的AdapterView做出相应的改变。为了实现这个功能,Adapter使用了观察者模式,Adapter本身相当于被观察的对象,AdapterView相当于观察者,通过调用registerDataSetObserver方法,给Adapter注册观察者。
public abstract void unregisterDataSetObserver (DataSetObserver observer)
通过调用unregisterDataSetObserver方法,反注册观察者。
public abstract int getCount ()
返回Adapter中数据的数量。
public abstract Object getItem (int position)
Adapter中的数据类似于数组,里面每一项就是对应一条数据,每条数据都有一个索引位置,即position,根据position可以获取Adapter中对应的数据项。
public abstract long getItemId (int position)
获取指定position数据项的id,通常情况下会将position作为id。在Adapter中,相对来说,position使用比id使用频率更高。
public abstract boolean hasStableIds ()
hasStableIds表示当数据源发生了变化的时候,原有数据项的id会不会发生变化,如果返回true表示Id不变,返回false表示可能会变化。Android所提供的Adapter的子类(包括直接子类和间接子类)的hasStableIds方法都返回false。
public abstract View getView (int position, View convertView, ViewGroup parent)
getView是Adapter中一个很重要的方法,该方法会根据数据项的索引为AdapterView创建对应的UI项,所以最终就是靠getView的方法输出每一个要显示的UI。至于如何把xml文件编程UI项不是本文要讲解的内容,有机会可以在以后写相应的文章,其实也简单,最终还是解析xml,遍历解析的xml生成view。
5.2 ListAdapter接口
public interface ListAdapter extends Adapter {
public boolean areAllItemsEnabled();
boolean isEnabled(int position);
}
ListAdapter接口继承自Adapter接口,ListAdapter可以作为AbsListView的数据源,AbsListView的子类有ListView、GridView和ExpandableListView。
ListAdapter相比Adapter新增了areAllItemsEnabled和isEnabled两个方法。
5.3 SpinnerAdapter接口
public interface SpinnerAdapter extends Adapter {
public View getDropDownView(int position, View convertView, ViewGroup parent);
}
SpinnerAdapter接口继承自Adapter接口,SpinnerAdapter可以作为AbsSpinner的数据源,AbsSpinner的子类有Gallery, Spinner和AppCompatSpinner。
相比Adapter,SpinnerAdapter中新增了getDropDownView方法,该方法与Adapter接口中定义的getView方法类似,该方法主要是供AbsSpinner调用,用于生成Spinner下拉弹出区域的UI。在SpinnerAdapter的子类BaseAdapter中,getDropDownView方法默认直接调用了getView方法。
ArrayAdapter和SimpleAdapter都重写了getDropDownView方法,这两个类中的getDropDownView方法与其getView的方法都调用了createViewFromResource方法,所以这两个类中方法getView与方法getDropDownView代码基本一致。
CursorAdapter也重写了getView与getDropDownView方法,虽然这两个方法没有使用公共代码,但是这两个方法代码逻辑一致。
综上,我们可知当我们在覆写getDropDownView方法时,应该尽量使其与getView的代码逻辑一致。
5.4 BaseAdapter
源码如下:
public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
private final DataSetObservable mDataSetObservable = new DataSetObservable();
public boolean hasStableIds() {
return false;
}
public void registerDataSetObserver(DataSetObserver observer) {
mDataSetObservable.registerObserver(observer);
}
public void unregisterDataSetObserver(DataSetObserver observer) {
mDataSetObservable.unregisterObserver(observer);
}
public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
}
public void notifyDataSetInvalidated() {
mDataSetObservable.notifyInvalidated();
}
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;
}
}
BaseAdapter是抽象类,其实现了ListAdapter接口和SpinnerAdapter接口。
BaseAdapter实现了观察者模式,Adapter接口定义了方法registerDataSetObserver和unregisterDataSetObserver,
BaseAdapter中维护了一个DataSetObservable类型的变量mDataSetObservable,并实现了方法registerDataSetObserver和unregisterDataSetObserver。当数据发生改变时,notifyDataSetChanged()内部调用mDataSetObservable.notifyChanged(),
会去更新视图。
BaseAdapter重写了getDropDownView方法,其内部调用了getView方法,所以利用BaseAdapter时需要重写getView。
ListAdapter接口继承自Adapter接口,ListAdapter可以作为AbsListView的数据源,AbsListView的子类有ListView、GridViewExpandableListView。
ListAdapter相比Adapter新增了areAllItemsEnabled和isEnabled两个方法。
待续!还没写完。