文章目录
继承关系
Adapter是一个接口:
BaseAdapter是一个抽象类,BaseAdapter也实现了ListAdapter接口,通常会继承BaseAdapter类来实现更复杂的需求:
常用的Adapter
适配器对象充当视图(AdapterView)与该视图的基础数据之间的桥梁,ListView ,GridView等均为AdapterView的子类,所以通常配合适配器使用。适配器提供对数据项的访问, 适配器还负责为数据集中的每个项目生成一个View 。
ArrayAdapter
常用的构造函数:ArrayAdapter(Context context, int resource, T[] objects)
,依次传入的三个参数为:①当前上下文;②ListView等组件子项布局的id;③需要适配的数据。
示例
运行效果:
代码:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView listView = findViewById(R.id.listView);
List<String> list = new ArrayList<>();
for(int i = 0; i < 20; i++){
list.add(String.valueOf(i));
}
ArrayAdapter<String> arrayAdapter = new ArrayAdapter<>(MainActivity.this, android.R.layout.simple_list_item_1, list);
listView.setAdapter(arrayAdapter);
}
}
R.layout.simple_list_item_1
注:上面ArrayAdapter构造函数的第二个参数R.layout.simple_list_item_1
是Android提供的列表item,下面列出的是其它自带的item,也可以使用自己自定义的xml文件。常用的几个:
- android.R.layout.simple_list_item_1 //一行text
- android.R.layout.simple_list_item_2 //一行title,一行text
- android.R.layout.simple_list_item_single_choice //单选按钮
- android.R.layout.simple_list_item_multiple_choice //多选按钮
- android.R.layout.simple_list_item_checked //勾选框
SimpleAdapter
虽然字面上看是“简单适配器”,但是SimpleAdapter功能强大,可以实现复杂的效果,已能满足许多需求。
构造函数: SimpleAdapter(Context context, List<? extends Map<String, ?>> data, int resource, String[] from, int[] to)
,传入的五个参数分别是:①当前上下文;②数据列表,列表中的每个条目对应于列表中的一行,映射包含每一行的数据,并且应该包含“from”中指定的所有条目;③显示列表项数据的视图资源符,此布局中需包含“to”参数中定义的视图;④与每个列表项关联的映射中的列名;⑤在“from”参数中显示的列表项视图。
示例
运行效果:
代码:
- 自定义每个列表项布局:list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<ImageView
android:id="@+id/ivChatHead"
android:layout_width="64dp"
android:layout_height="64dp"
android:baselineAlignBottom="true"
android:paddingLeft="8dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/tvName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="8dp"
android:textColor="#000000"
android:textSize="20sp" />
<TextView
android:id="@+id/tvContent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="8px"
android:textColor="#808080"
android:textSize="14sp" />
</LinearLayout>
</LinearLayout>
- MainActivity.java
public class MainActivity extends AppCompatActivity {
private int[] chatHead = new int[]{R.mipmap.chathead1, R.mipmap.chathead2, R.mipmap.chathead3};
private String[] name = new String[]{"张三", "李四", "王五"};
private String[] content = new String[]{"我是张三,大家好", "今天天气不错", "好嗨哟"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView listView = findViewById(R.id.listView);
List<Map<String, Object>> list = new ArrayList<>();
for (int i = 0; i < name.length; i++) {
Map<String, Object> map = new HashMap<>();
map.put("chatHead", chatHead[i]);
map.put("name", name[i]);
map.put("content", content[i]);
list.add(map);
}
SimpleAdapter myAdapter = new SimpleAdapter(this, list, R.layout.list_item,
new String[]{"chatHead", "name", "content"}, new int[]{R.id.ivChatHead, R.id.tvName, R.id.tvContent});
listView.setAdapter(myAdapter);
}
}
调整为类似微信列表布局
微信的列表布局与上面的差别在分隔线,上面使用的是默认的分隔线。这里做了两个调整:①将组件调整对齐;②更改分隔线为自定义。
运行效果:
代码:
- 自定义分隔线:list_item_driver.xml
<?xml version="1.0" encoding="utf-8"?>
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@color/lineColorGray"
android:insetLeft="70dp">
</inset>
注: "inset"标签,可以将其它的Drawable内嵌到自己当中(上面引入了颜色资源来设置分隔线的颜色),并可以在四周预留出一定的间距。
- 列表项布局:list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<LinearLayout
android:layout_width="70dp"
android:layout_height="70dp"
android:gravity="center">
<ImageView
android:id="@+id/ivChatHead"
android:layout_width="50dp"
android:layout_height="50dp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="70dp"
android:orientation="vertical"
android:gravity="center_vertical">
<TextView
android:id="@+id/tvName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#000000"
android:textSize="20sp" />
<TextView
android:id="@+id/tvContent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#808080"
android:textSize="15sp" />
</LinearLayout>
</LinearLayout>
- activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="@drawable/list_item_driver"
android:dividerHeight="1dp">
</ListView>
</android.support.constraint.ConstraintLayout>
注:给ListView设置自定义分隔线时,必须重新设置线高(android:dividerHeight),否则分隔线将不显示。具体可以查看ListView的源码,如果不设置高度,Android会将高度设置为-1。
参数from是如何与参数to绑定的
使用SimpleAdapter之后可能会产生疑问
:
为什么to参数中的View会显示from参数的值为自己的text,如果form参数不是String/int类型的,而是Bitmap类型的,那么to参数中的View还会显示吗?
答案是:不会。
这种情况就需要自定义Adapter了,SimpleAdapter参数的绑定是有限制的,查看SimpleAdapter的源码后这一点会更加清晰。
下面是从SimpleAdapter源码中提取出来的方法,可以回答上面的问题。
/**
* Called by bindView() to set the image for an ImageView but only if
* there is no existing ViewBinder or if the existing ViewBinder cannot
* handle binding to an ImageView.
* <p>
* This method is called instead of {@link #setViewImage(ImageView, String)}
* if the supplied data is an int or Integer.
*
* @param v ImageView to receive an image
* @param value the value retrieved from the data set
* @see #setViewImage(ImageView, String)
*/
public void setViewImage(ImageView v, int value) {
v.setImageResource(value);
}
/**
* Called by bindView() to set the image for an ImageView but only if
* there is no existing ViewBinder or if the existing ViewBinder cannot
* handle binding to an ImageView.
* <p>
* By default, the value will be treated as an image resource. If the
* value cannot be used as an image resource, the value is used as an
* image Uri.
* <p>
* This method is called instead of {@link #setViewImage(ImageView, int)}
* if the supplied data is not an int or Integer.
*
* @param v ImageView to receive an image
* @param value the value retrieved from the data set
* @see #setViewImage(ImageView, int)
*/
public void setViewImage(ImageView v, String value) {
try {
v.setImageResource(Integer.parseInt(value));
} catch (NumberFormatException nfe) {
v.setImageURI(Uri.parse(value));
}
}
/**
* Called by bindView() to set the text for a TextView but only if
* there is no existing ViewBinder or if the existing ViewBinder cannot
* handle binding to a TextView.
*
* @param v TextView to receive text
* @param text the text to be set for the TextView
*/
public void setViewText(TextView v, String text) {
v.setText(text);
}
如果to参数中的View是TextView的话,则会调用上面的setViewText()方法,将from参数设置为TextView的text,此时如果from参数不是String类型的而是int类型的,则会调用SimpleAdapter源码中的bindView(int position, View view)方法将int转换为String。
如果to参数中的View是ImageView 的话,则会调用上面的setViewImage()方法来设置ImageView 的背景图片,不过这里只接受int类型和uri类型的参数,所以传进来Bitmap类型的参数的话将不会正常显示,只好通过自定义Adapter的方式来弥补了。
自定义Adapter
这里用自定义的Adapter实现上面同样的效果,与上面不同的是:这里传进来的头像图片不是项目目录下的资源,而是某个文件目录下的图片。
示例
代码:
- 列表项数据类:MyListItemInfo.java
public class MyListItemInfo {
private Bitmap chatHead;
private String name;
private String content;
public Bitmap getChatHead() {
return chatHead;
}
public void setChatHead(Bitmap chatHead) {
this.chatHead = chatHead;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
- 自定义Adapter:MyListAdapter .java
public class MyListAdapter extends BaseAdapter {
private Context context = null;
private List<MyListItemInfo> list = null;
public MyListAdapter(Context context, List<MyListItemInfo> list){
this.context = context;
this.list = list;
}
@Override
public int getCount() {
int count = 0;
if (list != null) {
count = list.size();
}
return count;
}
@Override
public MyListItemInfo getItem(int position) {
MyListItemInfo item = null;
if (list != null) {
item = list.get(position);
}
return item;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
//convertView参数用于将之前加载好的布局进行缓存,以便旧的View可以重用
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
//如果convertView为null,则创建一个ViewHolder对象,并将控件的实例放在ViewHolder中
if (convertView == null) {
viewHolder = new ViewHolder();
LayoutInflater mInflater = LayoutInflater.from(context);
convertView = mInflater.inflate(R.layout.list_item, parent);
viewHolder.ivChatHead = (ImageView) convertView.findViewById(R.id.ivChatHead);
viewHolder.tvName = (TextView) convertView.findViewById(R.id.tvName);
viewHolder.tvContent = (TextView) convertView.findViewById(R.id.tvContent);
//将Holder存储到convertView中
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
//为viewHolder设置项目数据
MyListItemInfo myListItemInfo = getItem(position);
if (myListItemInfo != null) {
viewHolder.ivChatHead.setImageBitmap(myListItemInfo.getChatHead());
viewHolder.tvName.setText(myListItemInfo.getName());
viewHolder.tvContent.setText(myListItemInfo.getContent());
}
return convertView;
}
//使用ViewHolder对控件的实例进行缓存
private static class ViewHolder{
private ImageView ivChatHead;
private TextView tvName;
private TextView tvContent;
}
}
- MainActivity.java
public class MainActivity extends AppCompatActivity {
private List<Bitmap> chatHeadList = null;
private String[] name = new String[]{"张三", "李四", "王五"};
private String[] content = new String[]{"我是张三,大家好", "今天天气不错", "好嗨哟"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String filePath= Environment.getExternalStorageDirectory().toString() + "/AAAMyImage";
//运行时权限申请
if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
}else {
try {
chatHeadList = getBitmapFromFiles(filePath);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
ListView listView = findViewById(R.id.listView);
List<MyListItemInfo> list = new ArrayList<>();
int nameLength = name.length;
for (int i = 0; i < nameLength; i++) {
MyListItemInfo myListItemInfo = new MyListItemInfo();
if (chatHeadList != null) {
myListItemInfo.setChatHead(chatHeadList.get(i));
}
myListItemInfo.setName(name[i]);
myListItemInfo.setContent(content[i]);
list.add(myListItemInfo);
}
MyListAdapter myAdapter = new MyListAdapter(this, list);
listView.setAdapter(myAdapter);
}
public static List<Bitmap> getBitmapFromFiles(String path) throws FileNotFoundException {
File file = new File(path);
//listFiles是获取该目录下所有文件和目录的绝对路径
File[] files = file.listFiles();
if (files == null){
Log.e("error","空目录");
//return null;
}
List<Bitmap> bitmapList = new ArrayList<>();
int filesLength = files.length;
for(int i =0; i < filesLength; i++){
FileInputStream fis = new FileInputStream(files[i]);
//把流转化为Bitmap图片
bitmapList.add(BitmapFactory.decodeStream(fis));
}
return bitmapList;
}
}
- 程序中需要读取本地文件,需添加权限:AndroidManifest.xml
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />