Android常用控件-ListView
由于手机屏幕有限, 所以ListView的使用非常的普遍. ListView就是用户可以通过手指上下滑动的方式来展现更多的数据.
ListView的简单使用:
新建一个ListViewDemo的项目,修改activity-main.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="vertical">
<ListView
android:id="@+id/list_view_demo"
android:layout_width="match_parent"
android:layout_height="match_parent">
</ListView>
</LinearLayout>
为ListView指定一个id, 这样就会在页面出现一个ListView的基本布局.
下面是MainAcitivity中代码:
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.ArrayAdapter;
import android.widget.ListView;
public class MainActivity extends AppCompatActivity {
private String[] data = {"爸爸","妈妈","姐姐","妹妹","二姑","三姑",
"四舅","五伯","哥哥","嫂嫂","爷爷","奶奶","姥姥","姥爷"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//android.R.layout.simple_list_item_1 作为ListView子项布局的id,
// 这是一个android内置的布局文件, 里面只有一个textview
//可用于简单的显示一段文本.
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,data);
ListView listView = (ListView)findViewById(R.id.list_view_demo);
listView.setAdapter(adapter);
}
}
ListView是用来展示大量数据的, 这里用了一个数组. 数组中的数据是无法直接传递给ListView的, 需要借助适配器来传递. 由于这里提供的数据都是字符串,因此将ArrayAdapter的泛型指定为String, 依次传递参数为, 上下文, 子项布局id, 数据.
ListView的setAdapter()方法表示将建好的设配器对象传递给listview, 这样页面和数据就进行了关联.
运行效果:
定制ListView的界面
正式的项目开发中, 不可能使用这么简单的, 很多情况是需要根据客户的需求, 进行定制开发, 下面我们就对上面的ListView进行简单的扩展.
我们把这个列表扩展成一个简单的通讯录功能功能, 会显示头像, 昵称, 和电话.
我们首先定义一个实体类, 作为ListView适配器的适配类型. 新建类Family, 代码如下:
public class Family {
private String name;
private int imageID;
private int phoneNum;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getImageID() {
return imageID;
}
public void setImageID(int imageID) {
this.imageID = imageID;
}
public int getPhoneNum() {
return phoneNum;
}
public void setPhoneNum(int phoneNum) {
this.phoneNum = phoneNum;
}
}
name:表示称呼
imageID: 表示头像id
phoneNum: 表示电话号码
我们需要为ListView的子项指定一个我们自定义的布局, 在layout的目录下新建family_item.xml, 代码如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/family_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/family_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="10dp"/>
<TextView
android:id="@+id/family_phone_num"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="10dp"/>
</LinearLayout>
在这个布局中, 定义了ImageView用于显示头像, TextView分别用来显示称谓和电话. 并且让TextView在垂直方向居中显示.
接下来我们需要创建自定义的适配器, 这个适配器继承ArrayAdapter, 并将泛型定义为Family类. 新建类FamilyAdapter, 代码如下:
public class FamilyAdapter extends ArrayAdapter<Family>{
private int resourceID;
public FamilyAdapter(Context context, int resource, List<Family> objects) {
super(context, resource, objects);
resourceID = resource;
}
@NonNull
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Family family = getItem(position); // 获取当前项的Family实例
View view = LayoutInflater.from(getContext()).inflate(resourceID, parent, false);
ImageView familyImage = (ImageView) view.findViewById(R.id.family_image);
TextView familyName = (TextView) view.findViewById(R.id.family_name);
TextView familyPhone = (TextView) view.findViewById(R.id.family_phone_num);
familyImage.setImageResource(family.getImageID());
familyName.setText(family.getName());
familyPhone.setText(family.getPhoneNum());
return view;
}
}
FamilyAdapter重写了父类的一组构造函数, 用于将上下文, ListView子项布局的id和数据都传递进来. 另外又重写了一个getView()方法, 这个方法在每个子项被滚动到屏幕内的时候会被调用.
在getView()中, 首先通过getItem()方法得到当前项的Family实例, 然后使用LayoutInflater来为这个子项加载我们传入的布局.
这里LayoutInflater的inflate()方法接收3个参数, inflate()可以动态加载一个布局文件的id, 布局id, 第二个参数是给加载好的布局添加一个父布局.第三个参数指定成false, 表示只将我们在父布局中声明的layout属性生效, 但不为这个View添加父布局, 因为一旦view有了父布局之后, 它就不能再添加到ListView中了.
之后我们调用View的findViewById()方法分别获取到ImageView和TextView的实例. 并分别调用他们的setImageResource()和setText()方法来设置显示的图片和文字, 最后将布局完成.
下面来修改MainActivity中的代码.
public class MainActivity extends AppCompatActivity {
private List<Family> familyList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initFamilys();// 初始化家庭数据
FamilyAdapter adapter = new FamilyAdapter(MainActivity.this,R.layout.family_item,familyList);
ListView listView = (ListView)findViewById(R.id.list_view_demo);
listView.setAdapter(adapter);
}
private void initFamilys() {
for(int i= 0; i<2; i++){
Family family1 = new Family("爸爸", R.mipmap.family_1,
"13813813888");
familyList.add(family1);
Family family2 = new Family("妈妈", R.mipmap.family_2,
"13813813888");
familyList.add(family2);
Family family3 = new Family("姐姐", R.mipmap.family_1,
"13813813888");
familyList.add(family3);
Family family4 = new Family("三姑", R.mipmap.family_2,
"13813813888");
familyList.add(family4);
Family family5 = new Family("妹妹", R.mipmap.family_1,
"13813813888");
familyList.add(family5);
Family family6 = new Family("二姑", R.mipmap.family_1,
"13813813888");
familyList.add(family6);
Family family7 = new Family("三姑", R.mipmap.family_2,
"13813813888");
familyList.add(family7);
Family family8 = new Family("四舅",
R.mipmap.family_1, "13813813888");
familyList.add(family8);
Family family9 = new Family("三姑", R.mipmap.family_2,
"13813813888");
familyList.add(family9);
Family family10 = new Family("哥哥", R.mipmap.family_1,
"13813813888");
familyList.add(family10);
Family family11 = new Family("嫂嫂", R.mipmap.family_1,
"13813813888");
familyList.add(family11);
Family family12 = new Family("爷爷", R.mipmap.family_2,
"13813813888");
familyList.add(family12);
Family family13 = new Family("奶奶", R.mipmap.family_1,
"13813813888");
familyList.add(family13);
Family family14 = new Family("姥姥", R.mipmap.family_2,
"13813813888");
familyList.add(family14);
Family family15 = new Family("姥爷", R.mipmap.family_1,
"13813813888");
familyList.add(family15);
}
}
}
运行效果:
相信你已经领悟到了诀窍, 只要修改family_item.xml中的内容, 就可以定制出各种复杂的界面了.
提升ListView的运行效率
之所以说ListView很难用, 是因为它有很多细节可以优化. 其中效率就是很重要的一点. 目前我们的运行效率极低, 是因为FamilyAdapter的getView()方法中, 每次都将布局重新加载一遍, 当ListView快速滚动的时候, 就会出现性能的瓶颈.
在getView()方法中还有一个converView参数, 这个参数用于将之前加载好的布局进行缓存, 以便之后可以重用. 所以修改代码如下:
Family family = getItem(position); // 获取当前项的Family实例
View view;
if(convertView == null){
view = LayoutInflater.from(getContext()).inflate(resourceID,
parent, false);
} else {
view = convertView;
}
convertView 为null的时候, 才去加载布局, 如果不为null, 则直接重用convertView. 这样就大大提高了ListView的运行效率. 加快滚动的时候也可以出现很好的性能.
当然这部分代码还是可以继续优化的, 虽然现在不会再去重复加载布局, 但是每次在getView()方法中还是会调用View的findViewById()方法来获取一次控件的实例.
我们可以借助一个ViewHolder来对这部分性能进行优化. 修改FamilyAdapter中的优化代码.
public View getView(int position, View convertView, ViewGroup parent) {
Family family = getItem(position); // 获取当前项的Family实例
View view;
ViewHolder viewHolder;
if(convertView == null){
view = LayoutInflater.from(getContext()).inflate(resourceID,
parent, false);
viewHolder = new ViewHolder();
viewHolder.familyImage = (ImageView) view.findViewById(R.id.family_image);
viewHolder.familyName = (TextView) view.findViewById(R.id.family_name);
viewHolder.familyPhone = (TextView) view.findViewById(R.id.family_phone_num);
view.setTag(viewHolder); //将ViewHolder储存在View中
} else {
view = convertView;
viewHolder = (ViewHolder) view.getTag();// 重新获取ViewHolder
}
viewHolder.familyImage.setImageResource(family.getImageID());
viewHolder.familyName.setText(family.getName());
viewHolder.familyPhone.setText(family.getPhoneNum());
return view;
}
class ViewHolder {
ImageView familyImage;
TextView familyName;
TextView familyPhone;
}
解读:
新增的ViewHolder用于对控件的实例进行缓存, 当convertView为null的时候, 创建一个ViewHolder对象, 并将控件实例都存在ViewHolder里, 然后调用view的setTag()方法, 将ViewHolder对象储存在view中.
当convertView不为null的时候, 则调用View的getTag()方法, 把ViewHolder重新取出. 这样所有的控件实例都缓存在了ViewHolder里面. 就没有必要每次通过findViewById()方法来获取控件实例了.
运行效果: