listView绝对可以称得上是android中最常用的控件之一,几乎所有的应用程序都会用到它。由于手机屏幕的空间很有限,能够一次性在屏幕上显示的内容并不度,当我们又需要展示大量的数据时,就可以用到listView。ListView允许用户通过手指上下滑动的方式将屏幕外的数据滚动到屏幕内,原有的数据则滚动出屏幕。我们其实每天都在用这个控件,例如微博,头条,QQ,微信等等。相比其他的控件ListView就显得尤为重要,当然也很难。我们单独花一天来学。
ListView简单用法
先新建一个ListViewTest项目,并让android Studio自动帮我们创建好活动。然后修改activity_main.xml代码,如下所示:
android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent"> android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/list_view"/>
然后修改MainActivity中的代码,如下所示:
public class MainActivityextends AppCompatActivity {
private String[] data={"Apple","Banana","Orange","Pear","Grape","Cherry","Mango","Strawberry","Watermelon","Pear","Apple"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ArrayAdapter adapter=new ArrayAdapter(MainActivity.this,android.R.layout.simple_list_item_1,data);
ListView listView=(ListView)findViewById(R.id.list_view);
listView.setAdapter(adapter);
}
}
ArrayAdapter的构造函数中依次传入当前上下文、ListView子项布局的id,以及要适配的数据。
注意:我们使用了android:R.id.layout.simple_list_item_1作为ListView子项布局的id,这是android内置的布局文件,里面只有一个TextView,可用于简单的显示一段文本。
现在运行一下程序吧,看看效果如何。
定制ListView的界面
只能显示一段文本的ListView太单调了,我们给它添加一些图片吧(图片不能过大,不然系统会报错)。
接着我们在定义一个实体类,作为ListView适配器适配类型。新建类Fruit,代码如下:
public class Fruit {
private Stringname;
private int imageId;
public Fruit(String name,int imageId){
this.name=name;
this.imageId=imageId;
}
public StringgetName(){
return name;
}
public int getImageId(){
return imageId;
}
}
Fruit类中只有两个字段,name表示水果名,imageId表示水果对应图片的资源id。
然后在layout下新建fruit_item.xml文件,代码如下:
android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent"> android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/fruit_img"/> android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/fruit_name" android:layout_gravity="center_vertical" android:layout_marginLeft="10dp"/>
接下来需要创建一个自定义的适配器,这个适配器继承自ArrayAdapter,并将泛型指定为Fruit类。新建类FruitAdapter,代码如下:
public class FruitAdapterextends ArrayAdapter {
private int resourceId;
public FruitAdapter(Context context, int textViewResourceId, List objects){
super(context,textViewResourceId,objects);
resourceId=textViewResourceId;
}
@Override
public ViewgetView(int position, View convertView, ViewGroup parent){
Fruit fruit=getItem(position);
View view= LayoutInflater.from(getContext()).inflate(resourceId,parent,false);
ImageView fruitimage=(ImageView)view.findViewById(R.id.fruit_img);
TextView fruitname=(TextView)view.findViewById(R.id.fruit_name);
fruitimage.setImageResource(fruit.getImageId());
fruitname.setText(fruit.getName());
return view;
}
}
FriutAdapter重写了父类的一组构造函数,用于将上选文、ListView子项布局的id和数据都传递进来。另外有重写了getView()方法,这个方法在每个子项被滚到屏幕内的时候会被调用。在getView()方法中,首先通过getItem()方法得到当前项的Fruit实例,然后使用LayoutInflater来为这个子项加载我们传入的布局。
这里LayoutInflater的inflate()方法接收3个参数,第三个参数指定成false,表示只让我们在父布局中声明layout属性生效,但不会为这个View添加父布局,因为一旦View有了父布局,就不能再添加到ListView中了。
最后修改MainActivity中的代码,如下所示:
public class MainActivityextends AppCompatActivity {
private ListfruitList=new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initFruits();
FruitAdapter adapter=new FruitAdapter(MainActivity.this,R.layout.fruit_item,fruitList);
ListView listview=(ListView)findViewById(R.id.list_view);
listview.setAdapter(adapter);
}
private void initFruits() {
for(int i=0;i <2;i++){
Fruit apple=new Fruit("Apple",R.drawable.apple);
fruitList.add(apple);
Fruit banana=new Fruit("banana",R.drawable.banana);
fruitList.add(banana);
Fruit orange=new Fruit("orange",R.drawable.orange);
fruitList.add(orange);
Fruit water=new Fruit("watermelon",R.drawable.water);
fruitList.add(water);
Fruit pear=new Fruit("pear",R.drawable.pear);
fruitList.add(pear);
Fruit Str=new Fruit("Strawberry",R.drawable.stra);
fruitList.add(Str);
Fruit Cherry=new Fruit("Cherry",R.drawable.cherry);
fruitList.add(Cherry);
Fruit Mango=new Fruit("Mango",R.drawable.mango);
fruitList.add(Mango);
Fruit Grape=new Fruit("Grape",R.drawable.grape);
fruitList.add(Grape);
}
}
}
这里添加了一个initFruits()的方法,用于初始化所以得水果数据。在Friut类的构造函数中将水果的名字和对应的图片id传入,然后把创建好的对象添加到水果列表中。
现在运行一下程序,看看效果图吧。
提升ListView的运行效率
之所以说ListView控件很难用,是因为他有很多的细节可以优化,运行效率很重要。目前我们ListView的运行效率是很低的,因为在前面FruitAdapter的getView()方法中,每次都将布局重新加载一遍,当ListView快速滚动时,这会成为性能的瓶颈。
仔细观察会发现,getView()方法中还有一个concertView参数,这个参数用于将之前加载好的布局进行缓存,以便之后可以进行重用。修改FruitAdapter中的代码,如下所示:
public class FruitAdapterextends ArrayAdapter {
private int resourceId;
public FruitAdapter(Context context, int textViewResourceId, List objects){
super(context,textViewResourceId,objects);
resourceId=textViewResourceId;
}
@Override
public ViewgetView(int position, View convertView, ViewGroup parent){
Fruit fruit=getItem(position);
View view;
if(convertView ==null){
view = LayoutInflater.from(getContext()).inflate(resourceId,parent,false);
}else {
view= convertView;
}
//View view= LayoutInflater.from(getContext()).inflate(resourceId,parent,false);
ImageView fruitimage=(ImageView)view.findViewById(R.id.fruit_img);
TextView fruitname=(TextView)view.findViewById(R.id.fruit_name);
fruitimage.setImageResource(fruit.getImageId());
fruitname.setText(fruit.getName());
return view;
}
}
修改加粗部分,//后面是之前使用的。
可以看到现在我们在getView()方法中进行判断,如果convertView为null,则使用LayoutInflater去加载布局,如果不为null则直接对convertView进行重用。这样就大大提高了ListView的运行效率,在快速滚动的时候也可以表现出更好的性能。
不过还可以进一步的优化。内容如下:
public class FruitAdapterextends ArrayAdapter {
private int resourceId;
public FruitAdapter(Context context, int textViewResourceId, List objects){
super(context,textViewResourceId,objects);
resourceId=textViewResourceId;
}
@Override
public ViewgetView(int position, View convertView, ViewGroup parent){
Fruit fruit=getItem(position);
View view;
ViewHolder viewHolder;
if(convertView ==null){
view = LayoutInflater.from(getContext()).inflate(resourceId,parent,false);
viewHolder=new ViewHolder();
viewHolder.fruitimage=(ImageView)view.findViewById(R.id.fruit_img);
viewHolder.fruitname=(TextView)view.findViewById(R.id.fruit_name);
//将viewHolder储存在view中
view.setTag(viewHolder);
}else {
view= convertView;
//重新获取ViewHolder
viewHolder=(ViewHolder)view.getTag();
}
viewHolder.fruitimage.setImageResource(fruit.getImageId());
viewHolder.fruitname.setText(fruit.getName());
return view;
}
class ViewHolder{
ImageViewfruitimage;
TextViewfruitname;
}
}
可能有点难度。来解释一下吧。我们新增了一个内部类ViewHolder,用于对控件的实例进行缓存。当convertView为null时,创建一个ViewHolder对象,并将控件的实例存放在ViewHolder里,然后调用View的setTag()方法,将viewHolder对象储存在view中。当convertView不为null时,则调用getTag()方法,把ViewHolder重新取出来。这样所以得控件的实例都缓存在了viewHolder里,就没必要每次都通过findViewById()方法来获取控件实例了。
当然,如果你的技术支持你还可以继续优化。
ListView的点击事件
接下来我们来响应用户的点击事件吧。修改MainActivity中的代码,如下所示:
public class MainActivityextends AppCompatActivity {
private ListfruitList=new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initFruits();
FruitAdapter adapter=new FruitAdapter(MainActivity.this,R.layout.fruit_item,fruitList);
ListView listview=(ListView)findViewById(R.id.list_view);
listview.setAdapter(adapter);
listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView parent, View view, int position, long id) {
Fruit fruit=fruitList.get(position);
Toast.makeText(MainActivity.this,fruit.getName(),Toast.LENGTH_SHORT).show();
}
});
}
private void initFruits() {
for(int i=0;i <2;i++){
Fruit apple=new Fruit("Apple",R.drawable.apple);
fruitList.add(apple);
Fruit banana=new Fruit("banana",R.drawable.banana);
fruitList.add(banana);
Fruit orange=new Fruit("orange",R.drawable.orange);
fruitList.add(orange);
Fruit water=new Fruit("watermelon",R.drawable.water);
fruitList.add(water);
Fruit pear=new Fruit("pear",R.drawable.pear);
fruitList.add(pear);
Fruit Str=new Fruit("Strawberry",R.drawable.stra);
fruitList.add(Str);
Fruit Cherry=new Fruit("Cherry",R.drawable.cherry);
fruitList.add(Cherry);
Fruit Mango=new Fruit("Mango",R.drawable.mango);
fruitList.add(Mango);
Fruit Grape=new Fruit("Grape",R.drawable.grape);
fruitList.add(Grape);
}
}
}
这样我们点击listView中任意一个Item就会响应了。我们使用setOnItemClickListener()方法为ListView注册了一个监听器,当用户点击了ListView中任何一个子项时,就会回调onItemClick()方法。通过这个方法的position参数判断出用户点击的是哪一个子项,然后获取相应的水果,通过Toast将获取的水果名显示出来。