Android Listview 自定义BaseAdapter的实现及Listview优化示例

- 日理万妓 2023-10-05 00:24 33阅读 0赞

上一篇文章中我们讲了Android Listview SimpleAdapter的使用完整示例(实现用户列表)_左眼看成爱的博客-CSDN博客

本示例实现的效果图:

每个item中的checkbox选中事件实现单独监听处理:

cd28ec7907f24d7386501719332e11a4.png

取消选中:

c5bb6f2eab484e7ca7c3481a3018e850.png

整个item点击事件(共存):

26cc64226d5c4ea886c6fd77be218f21.png

8bce5b5be7a84ce29bd1e758cdfe8be9.png

这篇文章我来讲一下 自定义BaseAdapter的实现示例及优势在哪里? 通过查看SimpleAdapter的实现源码,我们可以发现SimpleAdapter实现的view复用方式其实就是convertView复用。但并没有避免复用时重复的调用findViewById,所以SimpleAdapter很难实现给item中的按钮或Checkbox单独添加监听事件。而且使用 SimpleAdapter也无法解决Listview中带checkbox时滑动选中状态混乱的问题。

fffe605ffede4cce81bba453512f20bc.png

08005a9ad4e94eefbba6c541a575510f.png 1e3b1d2d2a6c4507bf7f6d0392a187b2.png

所以通常我们都会自定义一个继承自BaseAdapter(已继承ListViewAdapter)来实现更加灵活强大的listview适配器,通过查看源码可以发现:ArrayAdapter(继承自BaseAdapter),SimpleAdapter(继承自BaseAdapter)的类,所以,这节我们讲一下如何重写BaseAdapter 重写getView()方法,如何使用ViewHolder优化findView次数来实现我们自己想要的功能。

基于 BaseAdapter 使用 ListView

添加 ListView 组件,存放数据,设置列表项的布局文件都和 SimpleAdapter 中的操作相同

创建一个 Adapter 继承 BaseAdapter,并实现抽象方法。

BaseAdapter 有 4 个抽象方法需要去实现:

  1. int getCount(); //返回的是数据源对象的个数,即列表项数
  2. Object getItem(int var1); //返回指定位置position上的列表项
  3. long getItemId(int var1); //返回指定位置处的行ID
  4. View getView(int var1, View var2, ViewGroup var3); //返回列表项对应的视图

继承 BaseAdapter 时需要去实现这 4 个抽象方法,这几个抽象方法都是 Adapter 接口中定义的方法。

前三个方法基本很简单不需要太多关注,重点在getView方法的实现

继承 BaseAdapter 基本样式:

  1. package com.example.Listview_BaseAdapter;
  2. /**
  3. * @author wh445306
  4. * @version 1.0
  5. * @Description MyAdapter
  6. * @Date 2023-03-17 13:10
  7. */
  8. public class MyAdapter extends BaseAdapter {
  9. List<Map<String,Object>> list;
  10. LayoutInflater mInflater;
  11. Context context;
  12. // MyAdapter构造函数 建议把上下文context也传进来
  13. public MyAdapter(Context context, List<Map<String, Object>> list){
  14. super();
  15. mInflater = LayoutInflater.from(context);
  16. this.list = list;
  17. this.context=context;
  18. }
  19. @Override
  20. public int getCount() {
  21. return list.size();
  22. }
  23. @Override
  24. public Object getItem(int position) {
  25. return list.get(position);
  26. }
  27. @Override
  28. public long getItemId(int position) {
  29. return position;
  30. }
  31. @Override
  32. public View getView(final int position, View convertView, ViewGroup parent) {
  33. return null;
  34. }
  35. }

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是否为空,为空的话猜重新创建并且后面选择保存布局到缓存

下面我们上一个完整示例代码:

  1. MainActivity单元:
  2. package com.example.Listview_BaseAdapter;
  3. import android.util.Log;
  4. import android.view.View;
  5. import android.widget.AdapterView;
  6. import android.widget.ListView;
  7. import android.widget.Toast;
  8. import androidx.appcompat.app.AppCompatActivity;
  9. import android.os.Bundle;
  10. import java.util.ArrayList;
  11. import java.util.HashMap;
  12. import java.util.List;
  13. import java.util.Map;
  14. public class MainActivity extends AppCompatActivity {
  15. String[] names={"张三","李四","王五","听雨","若兰","海子","大眼","卡卡","小米","小恐龙"};
  16. String[] ids={"wh445306","jyw8886","bmw8899","xx8yzz","888yzx","776yy9","99zz9","ka8ka8","xiaoni8","xkl888"};
  17. String[] ages={"28岁","27岁","22岁","24岁","28岁","18岁","15岁","13岁","17岁","20岁"};
  18. //String[] tels={"18322228898","13922278898","13719780706","15322228898"};
  19. //int[] pics={R.drawable.userface1,R.drawable.userface2,R.drawable.userface3,R.drawable.userface4};
  20. List<Map<String,Object>> list = new ArrayList<>();
  21. @Override
  22. protected void onCreate(Bundle savedInstanceState) {
  23. super.onCreate(savedInstanceState);
  24. setContentView(R.layout.activity_main);
  25. for (int i = 0; i < names.length; i++) {
  26. Map<String,Object> map= new HashMap<>();
  27. map.put("name",names[i]);
  28. map.put("id",ids[i]);
  29. map.put("age",ages[i]);
  30. map.put("tel","138"+(int)(Math.random()*90000000+10000000));
  31. int picID = getResources().getIdentifier("userface"+(i+1), "drawable", getPackageName());
  32. map.put("pic",picID);
  33. map.put("box",false);
  34. list.add(map);
  35. }
  36. ListView listView=findViewById(R.id.lvTest);
  37. /* 使用SimpleAdapter
  38. SimpleAdapter adapter = new SimpleAdapter(MainActivity.this, list,R.layout.list_item,
  39. new String[] { "name", "id", "age","tel","pic" },
  40. new int[] { R.id.txtUserName, R.id.txtUserID, R.id.txtUserAge,R.id.txtUserTel,R.id.imgHead });
  41. */
  42. // 使用BaseAdapter
  43. MyAdapter adapter =new MyAdapter(this,list);
  44. listView.setAdapter(adapter);
  45. //为 ListView 的列表项添加鼠标点击事件
  46. listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
  47. /**
  48. * @param adapterView 发生单击事件的列表项 ListView
  49. * @param view view是当前listview中的item的view的布局,就是可用这个view获取里面控件id后操作控件
  50. * @param i 在列表项中的位置 position
  51. * @param l 被单击列表项的行ID
  52. */
  53. @Override
  54. public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
  55. String Tag = "onItemClick======";
  56. Log.d(Tag, "position=" + i);
  57. Log.d(Tag, "行 ID" + l);
  58. /* HashMap<String,String> map=(HashMap<String,String>)adapterView.getItemAtPosition(i);
  59. String Text= map.get("name");
  60. String id= map.get("id");
  61. */
  62. String Text= list.get(i).get("name").toString();
  63. String id= list.get(i).get("id").toString();
  64. Toast.makeText(MainActivity.this, "您点击的行:Name:="+Text+" id:="+id, Toast.LENGTH_SHORT).show();
  65. }
  66. });
  67. }
  68. }
  69. BaseAdapter实现单元:
  70. package com.example.Listview_BaseAdapter;
  71. import android.content.Context;
  72. import android.view.LayoutInflater;
  73. import android.view.View;
  74. import android.view.ViewGroup;
  75. import android.widget.*;
  76. import java.util.HashMap;
  77. import java.util.List;
  78. import java.util.Map;
  79. /**
  80. * @author wh445306
  81. * @version 1.0
  82. * @Description MyAdapter
  83. * @Date 2023-03-17 13:10
  84. */
  85. public class MyAdapter extends BaseAdapter {
  86. List<Map<String,Object>> list;
  87. LayoutInflater mInflater;
  88. Context context;
  89. // 用于记录listView中的复选框有哪些是被选中的
  90. HashMap<Integer, Boolean> state = new HashMap<>();
  91. // MyAdapter构造函数 建议把上下文context也传进来
  92. public MyAdapter(Context context, List<Map<String, Object>> list){
  93. super();
  94. mInflater = LayoutInflater.from(context);
  95. this.list = list;
  96. this.context=context;
  97. }
  98. @Override
  99. public int getCount() {
  100. return list.size();
  101. }
  102. @Override
  103. public Object getItem(int position) {
  104. return list.get(position);
  105. }
  106. @Override
  107. public long getItemId(int position) {
  108. return position;
  109. }
  110. @Override
  111. public View getView(final int position, View convertView, ViewGroup parent) {
  112. ViewHolder holder;
  113. if(convertView==null){
  114. // 首次convertView为null时才会加载item布局文件
  115. holder= new ViewHolder();
  116. convertView= mInflater.inflate(R.layout.list_item,null);
  117. // 通过ViewHolder持有view中的子控件
  118. holder.name=convertView.findViewById(R.id.txtUserName);
  119. holder.id=convertView.findViewById(R.id.txtUserID);
  120. holder.age=convertView.findViewById(R.id.txtUserAge);
  121. holder.tel=convertView.findViewById(R.id.txtUserTel);
  122. holder.pic=convertView.findViewById(R.id.imgHead);
  123. holder.box =convertView.findViewById(R.id.chkBox);
  124. // 再通过setTag的形式和view绑定
  125. convertView.setTag(holder);
  126. }else {
  127. holder= (ViewHolder) convertView.getTag();
  128. }
  129. // 使用ViewHolder实现View组件的缓存和重用,重用View时就不用通过findViewById重新寻找view组件
  130. Map<String,Object> map = list.get(position);
  131. //holder.name.setText((String)list.get(position).get("name"));
  132. holder.name.setText((String)map.get("name"));
  133. holder.id.setText((String)map.get("id"));
  134. holder.age.setText((String)map.get("age"));
  135. holder.tel.setText((String)map.get("tel"));
  136. holder.pic.setImageResource((int) map.get("pic"));
  137. //holder.box.setChecked((Boolean) map.get("box"));
  138. // 根据state设置复选框是否被选中
  139. // 但是由于setChecked方法会触发下面setOnCheckedChangeListener
  140. // 所以需要在setOnCheckedChangeListener中添加一句,判断是用户点击的才触发处理
  141. holder.box.setChecked(state.get(position));
  142. holder.box.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
  143. @Override
  144. public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
  145. // 忽略非人为点击
  146. if(!buttonView.isPressed())return;
  147. // 记录那些复选框被选中,保存在List<map>中
  148. if (isChecked) {
  149. state.put(position, isChecked);
  150. } else {
  151. state.remove(position);
  152. }
  153. // 在这里处理选中或取消选中的操作
  154. if (isChecked) {
  155. Toast.makeText(context, "您选中了:Name:="+ list.get(position).get("name"), Toast.LENGTH_SHORT).show();
  156. } else {
  157. Toast.makeText(context, "您取消了选中:Name:="+ list.get(position).get("name"), Toast.LENGTH_SHORT).show();
  158. }
  159. }
  160. });
  161. return convertView;
  162. }
  163. // 定义一个ViewHolder静态类来持有convertView的每一个子控件
  164. public static class ViewHolder{
  165. public TextView name;
  166. public TextView id;
  167. public TextView age;
  168. public TextView tel;
  169. public ImageView pic;
  170. public CheckBox box;
  171. }
  172. }

item布局:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:tools="http://schemas.android.com/tools"
  4. xmlns:app="http://schemas.android.com/apk/res-auto"
  5. android:layout_width="match_parent"
  6. android:layout_height="match_parent">
  7. <!--顶部头像信息栏布局-->
  8. <LinearLayout
  9. android:orientation="horizontal"
  10. android:layout_width="match_parent"
  11. android:layout_height="wrap_content"
  12. android:layout_margin="10dp">
  13. <androidx.cardview.widget.CardView
  14. android:id="@+id/imgCard"
  15. android:layout_width="wrap_content"
  16. android:layout_height="wrap_content"
  17. android:layout_gravity="center"
  18. app:cardCornerRadius="5dp"
  19. app:cardElevation="5dp"
  20. android:layout_margin="10dp">
  21. <ImageView
  22. android:id="@+id/imgHead"
  23. android:layout_width="100dp"
  24. android:layout_height="100dp"
  25. android:src="@drawable/userface1"/>
  26. </androidx.cardview.widget.CardView>
  27. <LinearLayout
  28. android:orientation="vertical"
  29. android:layout_width="match_parent"
  30. android:layout_height="wrap_content"
  31. android:layout_marginTop="5dp">
  32. <TextView
  33. android:id="@+id/txtUserName"
  34. android:text="IT情深"
  35. android:layout_width="wrap_content"
  36. android:layout_height="wrap_content"
  37. android:textColor="#000000"
  38. android:textSize="18sp"/>
  39. <LinearLayout
  40. android:orientation="horizontal"
  41. android:layout_width="wrap_content"
  42. android:layout_height="wrap_content">
  43. <TextView
  44. android:text="ID:"
  45. android:layout_width="match_parent"
  46. android:layout_height="wrap_content"
  47. android:layout_marginTop="5dp"/>
  48. <TextView
  49. android:id="@+id/txtUserID"
  50. android:text="wh445306"
  51. android:layout_width="wrap_content"
  52. android:layout_height="wrap_content"
  53. android:layout_marginLeft="5dp"
  54. android:layout_marginTop="5dp"/>
  55. </LinearLayout>
  56. <LinearLayout
  57. android:orientation="horizontal"
  58. android:layout_width="match_parent"
  59. android:layout_height="wrap_content">
  60. <TextView
  61. android:text="年龄:"
  62. android:layout_width="wrap_content"
  63. android:layout_height="wrap_content" />
  64. <TextView
  65. android:id="@+id/txtUserAge"
  66. android:text="28岁"
  67. android:layout_width="wrap_content"
  68. android:layout_height="wrap_content"
  69. android:layout_marginLeft="5dp" />
  70. <CheckBox
  71. android:id="@+id/chkBox"
  72. android:layout_width="wrap_content"
  73. android:layout_height="wrap_content"
  74. android:focusable="false"
  75. android:focusableInTouchMode="false"
  76. />
  77. </LinearLayout>
  78. <LinearLayout
  79. android:orientation="horizontal"
  80. android:layout_width="wrap_content"
  81. android:layout_height="wrap_content">
  82. <TextView
  83. android:text="手机号码:"
  84. android:layout_width="wrap_content"
  85. android:layout_height="wrap_content"
  86. />
  87. <TextView
  88. android:id="@+id/txtUserTel"
  89. android:text="13318780706"
  90. android:layout_width="wrap_content"
  91. android:layout_height="wrap_content"
  92. android:layout_marginLeft="5dp"
  93. />
  94. </LinearLayout>
  95. <LinearLayout
  96. android:orientation="horizontal"
  97. android:layout_width="wrap_content"
  98. android:layout_height="wrap_content">
  99. <TextView
  100. android:text="最后登录时间:"
  101. android:layout_width="wrap_content"
  102. android:layout_height="wrap_content"/>
  103. <TextView
  104. android:id="@+id/txtLastIP"
  105. android:text="2023-03-11 14:56"
  106. android:layout_width="match_parent"
  107. android:layout_height="wrap_content"/>
  108. </LinearLayout>
  109. </LinearLayout>
  110. </LinearLayout>
  111. </LinearLayout>

主布局文件

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout
  3. xmlns:android="http://schemas.android.com/apk/res/android"
  4. xmlns:tools="http://schemas.android.com/tools"
  5. xmlns:app="http://schemas.android.com/apk/res-auto"
  6. android:layout_width="match_parent"
  7. android:layout_height="match_parent"
  8. android:orientation="vertical"
  9. tools:context=".MainActivity">
  10. <ListView
  11. android:id="@+id/lvTest"
  12. android:layout_width="match_parent"
  13. android:layout_height="match_parent"/>
  14. </LinearLayout>

发表评论

表情:
评论列表 (有 0 条评论,33人围观)

还没有评论,来说两句吧...

相关阅读