1. ListView 显示原理
ListView显示数据原理实际就是MVC设计模式,如下图所示:
- Model(模型) – 数据集合,用来存储数据组织
- View(视图) – ListView,负责数据显示
- Controller(控制器) - 负责管理Model,并设置要显示的哪个具体数据.
2. ListView 控件继承关系
java.lang.Object
↳ android.view.View
↳ android.view.ViewGroup
↳ android.widget.AdapterView<android.widget.ListAdapter>
↳ android.widget.AbsListView
↳ android.widget.ListView
3. ListView 控件的属性
属性 | 说明 |
---|---|
android:footerDividersEnabled | 是否在 footerView(表尾) 前绘制一个分隔条,默认为 true |
android:headerDividersEnabled | 是否在 headerView(表头) 前绘制一个分隔条,默认为 true |
android:divider | 设置分隔条,可以用颜色分割,也可以用 drawable 资源分割 |
android:dividerHeight | 设置分隔条的高度 |
android:entries | ListView 要显示的数据资源 |
表头表尾分割线的设置:
只能在 Java 中写代码进行设置了,可供我们调用的方法如下
方法 | 说明 |
---|---|
addHeaderView(View v) | 添加headView(表头),括号中的参数是一个View对象 |
addFooterView(View v) | 添加footerView(表尾),括号中的参数是一个View对象 |
addHeaderView(headView, null, false) | 和前面的区别:设置Header是否可以被选中 |
addFooterView(View,view,false) | 和前面的区别:设置 Footer 是否可以被选中 |
4. Adapter 继承关系
几个常用类的使用准则 :
类 | 说明 |
---|---|
BaseAdapter | 抽象类,用得最多的 Adapter |
ArrayAdapter | 支持泛型操作,最简单的 Adapter,只能展现一行文字 |
SimpleAdapter | 同样具有良好扩展性的 Adapter,可以自定义多种效果 |
SimpleCursorAdapter | 用于显示简单文本类型的 ListView,不推荐使用 XXXXX |
5. ArrayAdapter 构造函数的第二个参数
这其实是我们要给 ListView 设置的模板,有好几种
-
simple_list_item_1
单独一行的文本框
-
simple_list_item_2
两个文本框组成
-
simple_list_item_checked
每项都是由一个已选中的列表项
-
simple_list_item_multiple_choice
都带有一个复选框
-
simple_list_item_single_choice
都带有一个单选钮
6. 自定义类继承 BaseAdapter
常用的需要覆盖的方法:
方法 | 说明 |
---|---|
View getView(int position, View convertView, ViewGroup parent) | 获取显示数据集中指定位置的数据的视图 |
int getCount() | 数据集中有多少项目 |
Object getItem(int position) | 获取与数据集中指定位置关联的数据项 |
long getItemId(int position) | 获取与列表中指定位置关联的行 ID |
7. ListView 实现动态增删改查
动态增加数据,要么要删除一些数据,要么要更改一些数据,这时候,我们的 ListView 就要同步更改才对
来源: https://www.twle.cn/l/yufei/android/android-basic-listview-add.html
7.1 activity_main.xml
添加一个 ListView
<?xml version="1.0" encoding="utf-8" ?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp"
android:orientation="vertical" >
<include
android:id="@+id/talk_empty"
layout="@layout/listview_empty"/>
<ListView
android:id="@+id/listview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
7.2 在 res/layout
目录下新建一个列表项的布局 listview_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:padding="8dp"
android:orientation="horizontal">
<ImageView
android:id="@+id/avatar"
android:layout_weight="1"
android:layout_width="32dp"
android:layout_height="32dp"/>
<TextView
android:id="@+id/say"
android:layout_weight="7"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:textSize="18sp" />
</LinearLayout>
7.3 在 MainActivity.java
同一目录下新建一个 Bean
类 TalkBean.java
package cn.twle.android.listviewcrud;
public class TalkBean {
private int avatar_id;
private String say;
public TalkBean() {}
public TalkBean(int avatar_id, String say) {
this.avatar_id = avatar_id;
this.say = say;
}
public int getAvatarId() {
return avatar_id;
}
public String getSay() {
return say;
}
public void setAvatarId(int avatarId) {
this.avatar_id = avatarId;
}
public void setSay(String say) {
this.say = say;
}
}
7.4 在 MainActivity.java
目录下新建一个 Adapter
文件 TalkAdapter.java
package cn.twle.android.listviewcrud;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.view.LayoutInflater;
import java.util.LinkedList;
public class TalkAdapter extends BaseAdapter {
private Context mContext;
private LinkedList<TalkBean> mData;
public TalkAdapter() {}
public TalkAdapter(LinkedList<TalkBean> mData, Context mContext) {
this.mData = mData;
this.mContext = mContext;
}
@Override
public int getCount() {
return mData.size();
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if(convertView == null){
convertView = LayoutInflater.from(mContext).inflate(R.layout.listview_item,parent,false);
holder = new ViewHolder();
holder.avatar = (ImageView) convertView.findViewById(R.id.avatar);
holder.say = (TextView) convertView.findViewById(R.id.say);
convertView.setTag(holder);
}else{
holder = (ViewHolder) convertView.getTag();
}
holder.avatar.setImageResource(mData.get(position).getAvatarId());
holder.say.setText(mData.get(position).getSay());
return convertView;
}
private class ViewHolder{
ImageView avatar;
TextView say;
}
}
7.5 在 res/layout
目录下新建一个 listview_empty.xml
用于在ListView 没有数据的时候,显示说明
需要在 activity_main.xml
包含 listview_empty.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:gravity="center"
android:textSize="18sp"
android:textColor="#333333"
android:text="没有可显示的数据"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
7.5 MainActivity.java
调用 setEmptyView(View) ,但没有数据的时候显示
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ListView;
import android.widget.Toast;
import android.widget.AdapterView;
import android.view.View;
import java.util.LinkedList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private ListView listview;
private TalkAdapter talkAdapter = null;
private List<TalkBean> mData = null;
private int flag = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listview = (ListView) findViewById(R.id.listview);
View listview_empty = findViewById(R.id.talk_empty);
listview.setEmptyView(listview_empty); // 但没有 数据时,显示
// 准备数据源
mData = new LinkedList<TalkBean>();
// 将数据源添加到 适配器 中
talkAdapter = new TalkAdapter((LinkedList<TalkBean>) mData,MainActivity.this);
// 将适配器数据 添加到 ListView
listview.setAdapter(talkAdapter);
}
}
如果数据量一多,就特别卡顿,有时候会导致卡死,是什么原因造成的呢?
是因为 getView()
方法,界面上有多少列就会调用多少次 getView()
,每次都是新 inflate 一个 View,都要进行这个 XML 的解析,这样会 很浪费资源
所以我们要对 Adapter
进行优化,有两个目标:
- 复用
convertView
因为界面上有多少个 Item,就要调用 getView()
多少次;
每次都要加载一次 xml
其实这个 convertView
是系统提供给我们的可供服用的 View
,既然可复用,那么只要做一下判断就好
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null){
convertView = LayoutInflater.from(mContext).inflate(R.layout.list_item,parent,false);
}
.....
2. 用 ViewHolder
重用组件,不用每次都 `findViewById()
上面的优化只做到了 XML 文件只加载一次,但 convertView.findViewById
还是调用了多次
其实我们的 ListView
中的每一项都是一模一样的布局,能否只调用一次就好?
答案是肯定的,但需要自己定义一个 ViewHolder
类来对这一部分进行性能优化
ViewHolder
其实就是一个静态类
static class ViewHolder{
ImageView img_icon;
TextView txt_aName;
TextView txt_aSpeak;
}