Android 中的Adapter
前言
Adapter作为安卓中经常用到并且重要的一个类,是数据与视图进行交互的桥梁,官方给的的解释如下:
An Adapter object acts as a bridge between an AdapterView and the underlying data for that view. The Adapter provides access to the data items. The Adapter is also responsible for making a View for each item in the data set.
大致意思是:Adapter作为视图(AdapterView)与数据交互的桥梁,是数据的入口;同时也能够对每一项数据进行操作
可能会有个疑问,难道每次赋值都需要Adapter?
答案是否定的,注意原文中的view
是AdapterView
,指明了使用的范围。
那什么是AdapterView呢?
An AdapterView is a view whose children are determined by an Adapter.
See ListView, GridView, Spinner and Gallery for commonly used subclasses of AdapterView.
可以看到,AdapterView
是一个View,但是它的子类由Adapter
(可能有各种各样的Adapter,此处是泛指)决定,如ListView
、GridView
等
所以,我们可以知道如果一个View直接或间接继承了AdapterView,那么它数据与视图的绑定就需要用Adapter来实现,这样做在布局复杂时能降低代码的耦合性,数据与视图完全分离,通过Adapter做为桥梁将它们连接起来。
官方文档也给出以一些封装好的Adapter
|
使用
下面简单介绍几个常用的Adapter
ArrayAdapter
通常与ListView、RecyclerView等列表控件一起使用,数据可以是简单的String
类型,也可以是复杂类型,如ArrayAdapter< User > 此处的User就是一个复杂的数据类型。
简单使用
先看下XML文件,只有一个ListView控件
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent">
</ListView>
</LinearLayout>
实现代码
...
//简单的String类型item
ArrayAdapter<String> arrayAdapter;
arrayAdapter = new ArrayAdapter<String>(
this,
//此处为方便显示,引用系统的布局文件
android.R.layout.simple_list_item_1,
new String[]{"小明", "小红", "小花", "小胖"});
//设置Adapter
mListView.setAdapter(arrayAdapter);
...
运行结果:
当布局比较复杂时,或者item中需要显示多项数据时,需要继承 ArrayAdapter< T >
item 布局
<?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="80dp">
<ImageView
android:id="@+id/icon_head"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_alignParentStart="true"
android:layout_centerInParent="true"
android:layout_marginStart="10dp"
android:src="@drawable/icon_common_food"/>
<TextView
android:id="@+id/tv_user_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_marginStart="10dp"
android:layout_toRightOf="@+id/icon_head"
android:text="糊涂虫"
android:textColor="@color/colorText"
android:textSize="14sp"/>
<TextView
android:id="@+id/tv_user_level"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_marginLeft="5dp"
android:layout_toRightOf="@+id/iv_level_img"
android:text="初级画师"
android:textColor="@color/colorRed"
android:textSize="12sp"/>
<ImageView
android:id="@+id/iv_level_img"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_centerInParent="true"
android:layout_marginLeft="20dp"
android:layout_toRightOf="@+id/tv_user_name"
android:src="@drawable/icon_common_assessment_selected"/>
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:layout_alignParentBottom="true"
android:background="@color/colorDivLine"/>
<ImageView
android:id="@+id/imageView6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignStart="@+id/tv_user_name"
android:layout_below="@+id/tv_user_name"/>
</RelativeLayout>
这算是稍微复杂一点的布局,此时仅仅用 String 类型就不行了,需要自己定义一个Adapter 继承自ArrayAdapter
首先定义Bean 类
public class User {
private String name;
private String career;
private int iconHead;
public String getCareer() {
return career;
}
public void setCareer(String career) {
this.career = career;
}
public int getIconHead() {
return iconHead;
}
public void setIconHead(int iconHead) {
this.iconHead = iconHead;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
定义一个UserAdapter 继承 ArrayAdapter
public class UserAdapter extends ArrayAdapter<User> {
/**
* Context:上下文
* resource:布局文件ID
* objects:数据对象
*/
//定义资源文件ID,也可以不要这个参数直接指定
private int resourceId;
private Context mContext;
//直接指定布局文件
// public UserAdapter(Context context, List<User> objects) {
// super(context, R.layout.item_list_demo, objects);
// }
public UserAdapter(Context context, int resourceId, List<User> objects) {
super(context, resourceId, objects);
this.resourceId = resourceId;
this.mContext = context;
}
//重写getView方法
@Override
public View getView(int position, View convertView, ViewGroup parent) {
User user = getItem(position);
View view = LayoutInflater.from(mContext).inflate(resourceId, null);
ImageView iconHeader = (ImageView) view.findViewById(R.id.icon_head);
TextView userName = (TextView) view.findViewById(R.id.tv_user_name);
TextView userCareer = (TextView) view.findViewById(R.id.tv_user_level);
//设置头像和昵称
iconHeader.setImageResource(user.getIconHead());
userName.setText(user.getName());
userCareer.setText(user.getCareer());
return view;
}
}
然后再Activity中调用,与string的时候类似
List<User> users = new ArrayList<User>();
for (int i = 0; i < 10; i++) {
User user = new User();
user.setCareer("逗比逗");
user.setName("哈哈");
user.setIconHead(R.drawable.icon_common_doutu);
users.add(user);
}
UserAdapter adapter = new UserAdapter(this, R.layout.item_list_demo, users);
mListView.setAdapter(adapter);
显示效果
SimpleAdapter
使用方便,可扩展性强,简化控件的定义方式(findViewById),数据类型通常是HashMap
List<Map<String, Object>> users = new ArrayList<Map<String, Object>>();
for (int i = 0; i < 10; i++) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("userName", "呵呵");
map.put("career", "逗比逗");
map.put("iconHeader", R.drawable.icon_common_food);
users.add(map);
}
SimpleAdapter simpleAdapter = new SimpleAdapter(
this, users, R.layout.item_list_demo,
//key值
new String[]{"userName", "career", "iconHeader"},
//对应的控件
new int[]{R.id.tv_user_name, R.id.tv_user_level, R.id.icon_head});
mListView.setAdapter(simpleAdapter);
显示效果
可以看到,布局文件还是跟之前一样,但是代码减少了不少,这里不需要再单独写Adapter,不需要指定布局,只用指定控件的id可以了(但所指定的必须在同一个布局中),特别方便。在做布局或者数据测试的时候经常用到,但是如果布局中有一些事件要处理的话,就不合适了,这时候需要自定义adapter
自定义Adapter
有时候布局比较复杂,而且布局中的还有各种事件要处理,这时候需要自定义Adapter,继承自BaseAdapter
同样是上面的布局,我们用自定义Adapter来实现,首先定义Adapter
public class CustomAdapter extends BaseAdapter {
private List<User> mUsers;
private LayoutInflater mInflater;
private int selectedPosition = -1;
//设置选中的位置
public void setSelectedPosition(int position) {
selectedPosition = position;
Logger.d(selectedPosition);
}
public CustomAdapter(Context context, List<User> users) {
mInflater = LayoutInflater.from(context);
this.mUsers = users;
}
@Override
public int getCount() {
return mUsers.size();
}
@Override
public Object getItem(int position) {
return mUsers.get(position);
}
@Override
public long getItemId(int position) {
return mUsers.size();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
User user = (User) getItem(position);
View view = null;
ViewHolder viewHolder;
if (convertView != null) {
view = convertView;
viewHolder = (ViewHolder) view.getTag();
} else {
view = mInflater.inflate(R.layout.item_list_demo, null);
viewHolder = new ViewHolder(view);
view.setTag(viewHolder);
}
//设置头像和昵称
viewHolder.mIconHead.setImageResource(user.getIconHead());
viewHolder.mTvUserName.setText(user.getName());
viewHolder.mTvUserLevel.setText(user.getCareer());
//设置选中事件,选中时将当前item设为黄色
if (selectedPosition == position) {
viewHolder.mRl.setBackgroundColor(Color.YELLOW);
} else {
viewHolder.mRl.setBackgroundColor(Color.WHITE);
}
return view;
}
static class ViewHolder {
@BindView(R.id.icon_head)
ImageView mIconHead;
@BindView(R.id.tv_user_name)
TextView mTvUserName;
@BindView(R.id.tv_user_level)
TextView mTvUserLevel;
@BindView(R.id.iv_level_img)
ImageView mIvLevelImg;
@BindView(R.id.imageView6)
ImageView mImageView6;
@BindView(R.id.rl)
RelativeLayout mRl;
ViewHolder(View view) {
ButterKnife.bind(this, view);
}
}
}
有没有感觉这个Adapter很熟悉,没错,与之前的自定义的ArrayAdapter 基本上差不多。其实ArrayAdapter 也是继承自BaseAdapter,封装程度更高,暴露的方便比较少。因为从类名就可以看到其用处,而BaseAdapter是基类,通过集成它你可以定义任何你想要的Adapter。
注:代码中用到了注解和缓存模式
Activity调用,方法大同小异
List<User> users = new ArrayList<User>();
for (int i = 0; i < 10; i++) {
User user = new User();
user.setCareer("逗比逗");
user.setName("哈哈");
user.setIconHead(R.drawable.icon_common_doutu);
users.add(user);
}
final CustomAdapter adapter = new CustomAdapter(this, users);
mListView.setAdapter(adapter);
//设置ListView的点击事件
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
adapter.setSelectedPosition(position);
//通知布局改变了,不加这句不显示效果
adapter.notifyDataSetChanged();
}
});
看看效果
至此,常用的Adapter基本上介绍完了,如果碰到比较复杂的需求可以尝试自定义Adapter,正常开发中基本上都是自定义Adapter。系统提供的一般测试数据的时候用
尾巴
简单的介绍了几个常用的Adapter的使用,如果有不对的地方还请指正哈。
当然,可能你会觉得这些Adapter使用好烦啊,又要定义控件,又要设置数据的,能不能用更少的代码实现更多的功能。答案是,当然有了!所以才说,懒人改变世界,233333333333
- https://github.com/JoanZapata/base-adapter-helper
应该是我所知道的最早的对listview,gridview封装的adapter了- https://github.com/CymChad/BaseRecyclerViewAdapterHelper
针对RecyclerView 封装的Adapter,很强大,能够减少70%的代码