1.ListView
ListView列表视图控件是Android中常用的控件之一,其直接继承了AbsListView,是一个以垂直方式在项目中显示View视图的列表。ListView的数据项,来自一个继承了ListAdapter接口的适配器。
ListView的常用属性一般就是用来设置列表的间隔、分割线、表头、表尾等属性的,常用属性有以下几个,并且Android也为其提供了对应的setter/getter方法:
- android:divider:使用一个Drawable或者color设置数据项之间的间隔样式。
- android:dividerHeight:设置数据项之间的间隔距离。
- android:entries:设置一个资源Id用于填充ListView的数据项。
- android:footerDividersEnabled:设定列表表尾是否显示分割线,如果有表尾的话。
- android:headerDividerEnabled:设定列表表头是否显示分割线,如果有表头的话。
ListView提供了一些方法,用于操作ListView。这里介绍一些常用的方法,更多的请参见API文档:
- void addFooterView(View v):添加表尾View视图。
- boolean removeFooterView(View v):移除一个表尾View视图。
- void addHeaderView(View v):添加一个表头View视图。
- boolean removeHeaderView(View v):移除一个表头View视图。
- ListAdapter getAdapter():获取当前绑定的ListAdapter适配器。
- void setAdapter(ListAdapter adapter):设置一个ListAdapter适配器到当前ListView中。
- void setSelection(int posotion):设定当前选中项。
- long[] getCheckItemIds():获取当前选中项。
作为一个列表选择控件,ListView具有一些选中选项可以触发的事件,但它本身没有定义这些事件,均继承自间接父类AdapterView。ListView支持的几个常用事件有以下几个:
- AdapterView.OnItemCLickListener:列表项被点击时触发。
- AdapterView.OnItemLongClickListener:列表项被长按时触发。
- AdapterView.OnItemSelectedListener:列表项被选择时触发。
2. ListView两种使用方式
在Android项目中使用ListView,有两种方式,一种是通过一个继承了ListActivity的Activity,在其中设定ListAdapter,对于这种方式,比较适用于整个页面就是一个ListView;第二种方式就是直接使用ListView控件,这种方式也是项目中比较常用的方式。
接下来就对这两种使用ListView方式进行详细讲解。首先新建一个listViewTest工程,并让Android studio帮我们自动生成主活动和布局。在activity_main.xml布局文件中添加两个按钮,如下所示:
<?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:orientation="vertical"
tools:context="com.fd.listviewtest.MainActivity">
<Button
android:id="@+id/btn_list_activity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="使用ListActivity"
android:textAllCaps="false" />
<Button
android:id="@+id/btn_list_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="使用ListView"
android:textAllCaps="false" />
</LinearLayout>
MainActivity的java代码如下所示:
package com.fd.listviewtest;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bindView();
}
private void bindView() {
findViewById(R.id.btn_list_activity).setOnClickListener(this);
findViewById(R.id.btn_list_view).setOnClickListener(this);
}
@Override
public void onClick(View v) {
Intent intent;
switch (v.getId()){
case R.id.btn_list_activity:
intent = new Intent(this, UseListActivity.class);
startActivity(intent);
break;
case R.id.btn_list_view:
intent = new Intent(this, UseListViewActivity.class);
startActivity(intent);
break;
}
}
}
布局和代码都很简单,这里就不作讲解了。
2.1 ListActivity
ListActivity继承了Activity,并通过绑定一个ListAdapter来显示一个数据列表。需要注意的是,如果对列表项的数据格式没有特殊要求,它完全可以不使用布局文件即可创建一个ListView,因为ListActivity类本身已经包含了一个ListView。因此在onCreate()方法中,不需要调用setContentView()方法来从一个布局文件加载用户界面。
在ListActivity的onCreate()方法中,可以直接使用this.setListAdapter()方法为这个ListView设定ListAdapter。如果想获得并操作这个ListActivity自带的ListView,可以使用this.getListView()方法获取。
下面讲解一下使用继承ListActivity的方式来实现ListView,因为这里只是使用一个ArrayAdapter填充数据,因此无需指定布局文件。UseListActivity的代码如下:
package com.fd.listviewtest;
import android.app.ListActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;
public class UseListActivity extends ListActivity {
String[] animal = {"Duck", "Pig", "Panda", "Fish", "Tiger", "Cat", "Dog", "Bird"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ListView listView = getListView();
listView.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, animal));
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
Toast.makeText(this, "你选择了:" + animal[position], Toast.LENGTH_SHORT).show();
}
}
可以看到,在onCreate方法中,首先通过getListView方法获取到ListActivity类中定义的ListView对象,然后通过ListView对象的setAdapter方法来设置适配器及数据。同时实现了每个列表项的点击事件onListItemClick。现在运行一下程序,效果如下图所示:
2.2使用ListView控件构建
上面介绍的这种方式会将整个Activity都作为一个ListView,但是在实际项目中,一般还是把ListView作为一个数据显示控件,填充在布局中。
2.2.1 ListView的简单用法
创建UseListViewActivity活动并生成activity_use_list_view.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/lv"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
布局文件中只是简单的添加了ListView控件,并为控件指定了一个id,同时将控件的宽度和高度都设置为match_parent,这样ListView就可以占满整个布局空间。
接下来修改UseListViewActivity中的代码,如下所示:
package com.fd.listviewtest;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
public class UseListViewActivity extends AppCompatActivity {
String[] animal = {"Duck", "Pig", "Panda", "Fish", "Tiger", "Cat", "Dog", "Bird"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_use_list_view);
ListView listView = (ListView) findViewById(R.id.lv);
ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, animal);
listView.setAdapter(adapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
Toast.makeText(UseListViewActivity.this, animal[position], Toast.LENGTH_SHORT).show();
}
});
}
}
上述代码同样是使用了ArrayAdapter适配器来将数据传递给ListView。同时使用setOnItemClickListener()方法为ListView注册了一个监听器,当用户点击了ListView中的任何一个子项时,就会回调onItemClick方法。在这个方法中可以通过position参数判断出用户点击的是哪一个子项,然后获取到相应信息。
运行一下程序,并点击其中某一项,效果如下所示:
可以看到,效果和上个例子一样。
2.2.2 定制ListView界面
只能显示一段文本的ListView实存太单调了,我们可以对ListView的界面进行定制,让其可以显示更加丰富的内容。下面我们要定制一个有图片有文字有选择框的ListView,怎么做呢?
首先要准备好一组图片,分别对应上面提供的每一种动物。
接下来定义一个实体类,作为ListView适配器的适配类型。新建类Animal,代码如下所示:
package com.fd.listviewtest;
/**
* Created by Administrator on 2018-07-27.
*/
public class Animal {
private String name;
private int imageId;
public Animal(String name, int imageId) {
this.name = name;
this.imageId = imageId;
}
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;
}
}
类中有两个字段,name表示动物名字,imageId表示动物对应的图片资源id。
接着需要为ListView的子项指定一个我们自定义的布局,在layout目录下新建animal_item.xml文件,代码如下所示:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/ll_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="@+id/iv_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/tv_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="10dp"
android:layout_weight="1" />
<RadioGroup
android:id="@+id/rg_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginEnd="10dp"
android:orientation="horizontal">
<RadioButton
android:id="@+id/rb_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:text="选中" />
<RadioButton
android:id="@+id/rb_deselect"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="取消" />
</RadioGroup>
</LinearLayout>
在这个布局中,我们定义了一个ImageView用于显示动物图片,又定义了一个TextView用于显示动物名称,最后定义了两个单选按钮用于表示选中和取消。
接下来创建一个自定义的适配器,这个适配器继承自ArrayAdapter,并将泛型指定为Animal类。其代码如下:
package com.fd.listviewtest;
import android.content.Context;
import android.support.annotation.IdRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RadioGroup;
import android.widget.TextView;
import android.widget.Toast;
import java.util.List;
public class AnimalAdapter extends ArrayAdapter<Animal> {
private int resourceId;
private String radioText="选中";
public AnimalAdapter(Context context, int resource, List<Animal> objects) {
super(context, resource, objects);
resourceId = resource;
}
@NonNull
@Override
public View getView(final int position, @Nullable View convertView, @NonNull ViewGroup parent) {
Animal animal = getItem(position);
View view;
view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
LinearLayout ll_view = view.findViewById(R.id.ll_view);
ImageView imageView = view.findViewById(R.id.iv_image);
TextView textView = view.findViewById(R.id.tv_text);
RadioGroup rg_group = view.findViewById(R.id.rg_group);
rg_group.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, @IdRes int checkedId) {
switch (checkedId){
case R.id.rb_select:
radioText = "选中";
break;
case R.id.rb_deselect:
radioText = "取消";
break; }
}
});
ll_view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getContext(), "你点击了第" + position + "项" + " ,你选择:" + radioText, Toast.LENGTH_SHORT).show();
}
});
imageView.setImageResource(animal.getImageId());
textView.setText(animal.getName());
return view;
}
}
AnimalAdapter重写了父类的一组构造方法,用于将上下文、ListView子项布局id和数据传递进来。另外还重写了getView方法,这个方法在每个子项被滚动到屏幕内的时候会被调用。
在getView方法中首先用LayoutInflater的inflate方法来为这个子项加载我们自定义的布局,然后用findViewById方法分别找到view内控件并对各个控件进行相应的处理,最后将这个view返回。
下面修改UseListViewActivity的代码,如下所示:
package com.fd.listviewtest;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
public class UseListViewActivity extends AppCompatActivity {
String[] animal = {"Duck", "Pig", "Panda", "Fish", "Tiger", "Cat", "Dog", "Bird"};
private List<Animal> animalLists = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_use_list_view);
InitAnimals();
ListView listView = (ListView) findViewById(R.id.lv);
AnimalAdapter adapter = new AnimalAdapter(UseListViewActivity.this, R.layout.animal_item, animalLists);
listView.setAdapter(adapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
Animal animal = animalLists.get(position);
Toast.makeText(UseListViewActivity.this, animal.getName(), Toast.LENGTH_SHORT).show();
}
});
}
private void InitAnimals() {
for (int i = 0; i < 2; i++) {
Animal duck = new Animal("Duck", R.mipmap.duck);
animalLists.add(duck);
Animal pig = new Animal("Pig", R.mipmap.pig);
animalLists.add(pig);
Animal panda = new Animal("Panda", R.mipmap.panda);
animalLists.add(panda);
Animal fish = new Animal("Fish", R.mipmap.fish);
animalLists.add(fish);
Animal tiger = new Animal("Pear", R.mipmap.tiger);
animalLists.add(tiger);
Animal cat = new Animal("Grape", R.mipmap.cat);
animalLists.add(cat);
Animal dog = new Animal("Pineapple", R.mipmap.dog);
animalLists.add(dog);
Animal bird = new Animal("Strawberry", R.mipmap.bird);
animalLists.add(bird);
}
}
}
代码不难,就不作详细解释了。
现在运行一下程序,效果如图所示:
2.2.3 提升ListView的运行效率
上面例子中ListView的运行效率是很低的,因为在AnimalAdapter的getView方法中,每次都将布局重新加载一遍,当ListView快速滚动的时候,这就有可能出现问题。
getView方法中有一个convertView参数,这个参数用于将之前加载 好的布局进行缓存,以便之后可以进行重用。修改AnimalAdapter的代码,如下所示:
package com.fd.listviewtest;
import android.content.Context;
import android.support.annotation.IdRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RadioGroup;
import android.widget.TextView;
import android.widget.Toast;
import java.util.List;
public class AnimalAdapter extends ArrayAdapter<Animal> {
private int resourceId;
private String radioText="选中";
public AnimalAdapter(Context context, int resource, List<Animal> objects) {
super(context, resource, objects);
resourceId = resource;
}
@NonNull
@Override
public View getView(final int position, @Nullable View convertView, @NonNull ViewGroup parent) {
Animal animal = getItem(position);
View view;
if (convertView == null) {
view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
}else{
view = convertView;
}
LinearLayout ll_view = view.findViewById(R.id.ll_view);
ImageView imageView = view.findViewById(R.id.iv_image);
TextView textView = view.findViewById(R.id.tv_text);
RadioGroup rg_group = view.findViewById(R.id.rg_group);
rg_group.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, @IdRes int checkedId) {
switch (checkedId){
case R.id.rb_select:
radioText = "选中";
break;
case R.id.rb_deselect:
radioText = "取消";
break; }
}
});
ll_view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getContext(), "你点击了第" + position + "项" + " ,你选择:" + radioText, Toast.LENGTH_SHORT).show();
}
});
imageView.setImageResource(animal.getImageId());
textView.setText(animal.getName());
return view;
}
}
在getView方法中,我们对contentView进行了判断,如果其为null,就使用LayoutInflater去加载布局,如果不为null,就直接对contentView进行重用。这样就大大提高了ListView的运行效率,在快速滚动的时候也可以表现出更好的性能。
虽然上述代码已经不会再去重复加载布局,但是每次在getView方法中还是会调用View的findViewById方法来获取一次控制实例。因此,我们可以使用一个ViewHolder来对这部分内容进行优化,修改AnimalAdapter的代码,如下所示:
package com.fd.listviewtest;
import android.content.Context;
import android.support.annotation.IdRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RadioGroup;
import android.widget.TextView;
import android.widget.Toast;
import java.util.List;
public class AnimalAdapter extends ArrayAdapter<Animal> {
private int resourceId;
private String radioText="选中";
public AnimalAdapter(Context context, int resource, List<Animal> objects) {
super(context, resource, objects);
resourceId = resource;
}
@NonNull
@Override
public View getView(final int position, @Nullable View convertView, @NonNull ViewGroup parent) {
Animal animal = getItem(position);
View view;
ViewHolder viewHolder;
if (convertView == null) {
view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
viewHolder = new ViewHolder();
viewHolder.ll_view = view.findViewById(R.id.ll_view);
viewHolder.imageView = view.findViewById(R.id.iv_image);
viewHolder.textView = view.findViewById(R.id.tv_text);
viewHolder.rg_group = view.findViewById(R.id.rg_group);
view.setTag(viewHolder);
} else {
view = convertView;
viewHolder = (ViewHolder) view.getTag();
}
viewHolder.rg_group.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, @IdRes int checkedId) {
switch (checkedId){
case R.id.rb_select:
radioText = "选中";
break;
case R.id.rb_deselect:
radioText = "取消";
break; }
}
});
viewHolder.ll_view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getContext(), "你点击了第" + position + "项" + " ,你选择:" + radioText, Toast.LENGTH_SHORT).show();
}
});
viewHolder.imageView.setImageResource(animal.getImageId());
viewHolder.textView.setText(animal.getName());
return view;
}
class ViewHolder {
LinearLayout ll_view;
ImageView imageView;
TextView textView;
RadioGroup rg_group;
}
}
上述代码中,我们新增了一个内部类ViewHolder,用于对控件的实例进行缓存。当contentView为null的时候,创建 一个ViewHolder对象,并将控件的实例存放在ViewHolder中,然后调用setTag方法将ViewHolder对象存储在View中。当contentView不为null的时候,通过getTag方法将ViewHolder取出来。