Android Listview 自定义BaseAdapter的实现及Listview优化示例
上一篇文章中我们讲了Android Listview SimpleAdapter的使用完整示例(实现用户列表)_左眼看成爱的博客-CSDN博客
本示例实现的效果图:
每个item中的checkbox选中事件实现单独监听处理:
取消选中:
整个item点击事件(共存):
这篇文章我来讲一下 自定义BaseAdapter的实现示例及优势在哪里? 通过查看SimpleAdapter的实现源码,我们可以发现SimpleAdapter实现的view复用方式其实就是convertView复用。但并没有避免复用时重复的调用findViewById,所以SimpleAdapter很难实现给item中的按钮或Checkbox单独添加监听事件。而且使用 SimpleAdapter也无法解决Listview中带checkbox时滑动选中状态混乱的问题。
所以通常我们都会自定义一个继承自BaseAdapter(已继承ListViewAdapter)来实现更加灵活强大的listview适配器,通过查看源码可以发现:ArrayAdapter(继承自BaseAdapter),SimpleAdapter(继承自BaseAdapter)的类,所以,这节我们讲一下如何重写BaseAdapter 重写getView()方法,如何使用ViewHolder优化findView次数来实现我们自己想要的功能。
基于 BaseAdapter 使用 ListView
添加 ListView 组件,存放数据,设置列表项的布局文件都和 SimpleAdapter 中的操作相同
创建一个 Adapter 继承 BaseAdapter,并实现抽象方法。
BaseAdapter 有 4 个抽象方法需要去实现:
int getCount(); //返回的是数据源对象的个数,即列表项数
Object getItem(int var1); //返回指定位置position上的列表项
long getItemId(int var1); //返回指定位置处的行ID
View getView(int var1, View var2, ViewGroup var3); //返回列表项对应的视图
继承 BaseAdapter 时需要去实现这 4 个抽象方法,这几个抽象方法都是 Adapter 接口中定义的方法。
前三个方法基本很简单不需要太多关注,重点在getView方法的实现
继承 BaseAdapter 基本样式:
package com.example.Listview_BaseAdapter;
/**
* @author wh445306
* @version 1.0
* @Description MyAdapter
* @Date 2023-03-17 13:10
*/
public class MyAdapter extends BaseAdapter {
List<Map<String,Object>> list;
LayoutInflater mInflater;
Context context;
// MyAdapter构造函数 建议把上下文context也传进来
public MyAdapter(Context context, List<Map<String, Object>> list){
super();
mInflater = LayoutInflater.from(context);
this.list = list;
this.context=context;
}
@Override
public int getCount() {
return list.size();
}
@Override
public Object getItem(int position) {
return list.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
return null;
}
}
public View getView(final int position, View convertView, ViewGroup parent) {
return null;
}
第一个参数: int position,一般BaseAdapter都是很多类型一样的数据展示在界面,
该属性是判断显示在界面上的是第几个,通过position在BaseAdapter自定义的数组或者集合中取值。并展示在界面上。
第二个参数: View converView View converView是展示在界面上的一个item。因为手机屏幕就那么大,所以一次展示给用户看见的内容是固定的,如果你List中有1000条数据,不应该new1000个converView,那样内存肯定不足,应该学会控件重用,滑出屏幕的converView就在下面新进来的item中重新使用,只是修改下他展示的值。这样能减去很多消耗。
不过有的时候我们不能对其进行重构 比如带CheckBox的item,如果你使用判断,在你选中某个item的CheckBox时滑动时会出现混乱,这时你就必须去掉判断对其进行重构。
第三个参数:ViewGroup parent 这个属性是加载xml视图时使用。
所以在下面的代码中我们为了提高性能会先判定converView是否为空,为空的话猜重新创建并且后面选择保存布局到缓存
下面我们上一个完整示例代码:
MainActivity单元:
package com.example.Listview_BaseAdapter;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MainActivity extends AppCompatActivity {
String[] names={"张三","李四","王五","听雨","若兰","海子","大眼","卡卡","小米","小恐龙"};
String[] ids={"wh445306","jyw8886","bmw8899","xx8yzz","888yzx","776yy9","99zz9","ka8ka8","xiaoni8","xkl888"};
String[] ages={"28岁","27岁","22岁","24岁","28岁","18岁","15岁","13岁","17岁","20岁"};
//String[] tels={"18322228898","13922278898","13719780706","15322228898"};
//int[] pics={R.drawable.userface1,R.drawable.userface2,R.drawable.userface3,R.drawable.userface4};
List<Map<String,Object>> list = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
for (int i = 0; i < names.length; i++) {
Map<String,Object> map= new HashMap<>();
map.put("name",names[i]);
map.put("id",ids[i]);
map.put("age",ages[i]);
map.put("tel","138"+(int)(Math.random()*90000000+10000000));
int picID = getResources().getIdentifier("userface"+(i+1), "drawable", getPackageName());
map.put("pic",picID);
map.put("box",false);
list.add(map);
}
ListView listView=findViewById(R.id.lvTest);
/* 使用SimpleAdapter
SimpleAdapter adapter = new SimpleAdapter(MainActivity.this, list,R.layout.list_item,
new String[] { "name", "id", "age","tel","pic" },
new int[] { R.id.txtUserName, R.id.txtUserID, R.id.txtUserAge,R.id.txtUserTel,R.id.imgHead });
*/
// 使用BaseAdapter
MyAdapter adapter =new MyAdapter(this,list);
listView.setAdapter(adapter);
//为 ListView 的列表项添加鼠标点击事件
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
/**
* @param adapterView 发生单击事件的列表项 ListView
* @param view view是当前listview中的item的view的布局,就是可用这个view获取里面控件id后操作控件
* @param i 在列表项中的位置 position
* @param l 被单击列表项的行ID
*/
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
String Tag = "onItemClick======";
Log.d(Tag, "position=" + i);
Log.d(Tag, "行 ID" + l);
/* HashMap<String,String> map=(HashMap<String,String>)adapterView.getItemAtPosition(i);
String Text= map.get("name");
String id= map.get("id");
*/
String Text= list.get(i).get("name").toString();
String id= list.get(i).get("id").toString();
Toast.makeText(MainActivity.this, "您点击的行:Name:="+Text+" id:="+id, Toast.LENGTH_SHORT).show();
}
});
}
}
BaseAdapter实现单元:
package com.example.Listview_BaseAdapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author wh445306
* @version 1.0
* @Description MyAdapter
* @Date 2023-03-17 13:10
*/
public class MyAdapter extends BaseAdapter {
List<Map<String,Object>> list;
LayoutInflater mInflater;
Context context;
// 用于记录listView中的复选框有哪些是被选中的
HashMap<Integer, Boolean> state = new HashMap<>();
// MyAdapter构造函数 建议把上下文context也传进来
public MyAdapter(Context context, List<Map<String, Object>> list){
super();
mInflater = LayoutInflater.from(context);
this.list = list;
this.context=context;
}
@Override
public int getCount() {
return list.size();
}
@Override
public Object getItem(int position) {
return list.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if(convertView==null){
// 首次convertView为null时才会加载item布局文件
holder= new ViewHolder();
convertView= mInflater.inflate(R.layout.list_item,null);
// 通过ViewHolder持有view中的子控件
holder.name=convertView.findViewById(R.id.txtUserName);
holder.id=convertView.findViewById(R.id.txtUserID);
holder.age=convertView.findViewById(R.id.txtUserAge);
holder.tel=convertView.findViewById(R.id.txtUserTel);
holder.pic=convertView.findViewById(R.id.imgHead);
holder.box =convertView.findViewById(R.id.chkBox);
// 再通过setTag的形式和view绑定
convertView.setTag(holder);
}else {
holder= (ViewHolder) convertView.getTag();
}
// 使用ViewHolder实现View组件的缓存和重用,重用View时就不用通过findViewById重新寻找view组件
Map<String,Object> map = list.get(position);
//holder.name.setText((String)list.get(position).get("name"));
holder.name.setText((String)map.get("name"));
holder.id.setText((String)map.get("id"));
holder.age.setText((String)map.get("age"));
holder.tel.setText((String)map.get("tel"));
holder.pic.setImageResource((int) map.get("pic"));
//holder.box.setChecked((Boolean) map.get("box"));
// 根据state设置复选框是否被选中
// 但是由于setChecked方法会触发下面setOnCheckedChangeListener
// 所以需要在setOnCheckedChangeListener中添加一句,判断是用户点击的才触发处理
holder.box.setChecked(state.get(position));
holder.box.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
// 忽略非人为点击
if(!buttonView.isPressed())return;
// 记录那些复选框被选中,保存在List<map>中
if (isChecked) {
state.put(position, isChecked);
} else {
state.remove(position);
}
// 在这里处理选中或取消选中的操作
if (isChecked) {
Toast.makeText(context, "您选中了:Name:="+ list.get(position).get("name"), Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context, "您取消了选中:Name:="+ list.get(position).get("name"), Toast.LENGTH_SHORT).show();
}
}
});
return convertView;
}
// 定义一个ViewHolder静态类来持有convertView的每一个子控件
public static class ViewHolder{
public TextView name;
public TextView id;
public TextView age;
public TextView tel;
public ImageView pic;
public CheckBox box;
}
}
item布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--顶部头像信息栏布局-->
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp">
<androidx.cardview.widget.CardView
android:id="@+id/imgCard"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
app:cardCornerRadius="5dp"
app:cardElevation="5dp"
android:layout_margin="10dp">
<ImageView
android:id="@+id/imgHead"
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@drawable/userface1"/>
</androidx.cardview.widget.CardView>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp">
<TextView
android:id="@+id/txtUserName"
android:text="IT情深"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#000000"
android:textSize="18sp"/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:text="ID:"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"/>
<TextView
android:id="@+id/txtUserID"
android:text="wh445306"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_marginTop="5dp"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:text="年龄:"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/txtUserAge"
android:text="28岁"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp" />
<CheckBox
android:id="@+id/chkBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="false"
android:focusableInTouchMode="false"
/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:text="手机号码:"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
<TextView
android:id="@+id/txtUserTel"
android:text="13318780706"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:text="最后登录时间:"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/txtLastIP"
android:text="2023-03-11 14:56"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
主布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<ListView
android:id="@+id/lvTest"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
还没有评论,来说两句吧...