ListView
一、ListView的简单用法
首先新建一个ListViewTest项目,并让ADT自动帮我们创建好活动。然后修改activity_main.xml中的代码,如下所示:
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 android:layout_width="match_parent" 3 android:layout_height="match_parent" 4 android:orientation="vertical" > 5 6 <ListView 7 android:id="@+id/layout_listView_one_listView1" 8 android:layout_width="match_parent" 9 android:layout_height="wrap_content" > 10 </ListView> 11 12 </LinearLayout>
接下来修改MainActivity中的代码,如下所示:
1 package com.example.mmm; 2 3 import android.app.Activity; 4 import android.os.Bundle; 5 import android.view.Menu; 6 import android.view.MenuItem; 7 import android.view.Window; 8 import android.widget.ArrayAdapter; 9 import android.widget.ListView; 10 11 public class MainActivity extends Activity { 12 13 14 private String[] data={"Apple","Banana","Orange","Watermelon","Pear","Grape","Pineapple","Strawberry","Cherry","Mango"}; 15 @Override 16 protected void onCreate(Bundle savedInstanceState) { 17 18 super.onCreate(savedInstanceState); 19 requestWindowFeature(Window.FEATURE_NO_TITLE); 20 setContentView(R.layout.listview); 21 ArrayAdapter<String> adapter =new ArrayAdapter<String>(MainActivity.this,android.R.layout.simple_list_item_1,data); 22 ListView listview=(ListView)findViewById(R.id.listView_1); 23 listview.setAdapter(adapter); 24 } 25 26 @Override 27 public boolean onCreateOptionsMenu(Menu menu) { 28 29 // Inflate the menu; this adds items to the action bar if it is present. 30 getMenuInflater().inflate(R.menu.main, menu); 31 return true; 32 } 33 34 @Override 35 public boolean onOptionsItemSelected(MenuItem item) { 36 37 // Handle action bar item clicks here. The action bar will 38 // automatically handle clicks on the Home/Up button, so long 39 // as you specify a parent activity in AndroidManifest.xml. 40 int id = item.getItemId(); 41 if (id == R.id.action_settings) { 42 return true; 43 } 44 return super.onOptionsItemSelected(item); 45 } 46 }
结果:
二、定制listview界面
只能显示一段文本的ListView实在是单调了,我们现在就来对ListView的界面进行定制,让他可以显示更加丰富的内容。
首先需要准备好一组图片,分别对应上面提供的每一种水果,待会儿我们要让这些水果名称旁边都有一个图样。
1.接着定义一个实体类,作为ListView适配器类型。新建类Fruit,代码如下所示:
1 package com.example.mmm; 2 3 4 public class Fruit { 5 private String name; 6 private int imageId; 7 public Fruit(String n,int im){ 8 this.name = n; 9 this.imageId = im; 10 } 11 public String getName(){ 12 return name; 13 } 14 public int getImageId(){ 15 return imageId; 16 } 17 }
2.然后需要为ListView指向一个自定义的布局,在layout目录下新建fruit_item.xml,代码如是:
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:orientation="vertical" > 6 7 <TextView 8 android:id="@+id/fruit_name" 9 android:layout_width="wrap_content" 10 android:layout_height="wrap_content" 11 android:text="TextView" /> 12 <ImageView 13 android:id="@+id/fruit_image" 14 android:layout_width="wrap_content" 15 android:layout_height="wrap_content" 16 android:layout_gravity="center" 17 android:layout_marginLeft="10dip"/> 18 19 20 </LinearLayout>
3.接下来需要创建一个自定义的适配器,这个适配器继承自ArrayAdapter,并将泛型指定为Fruit类,新建一个FruitAdapter:
1)继承ArrayAdapter,并将泛型指定为Fruit类。
2)定义一个私有的整型实例变量resourceId
3)继承父类的有三个参数的构造函数,将其中整型参数的值赋值给resourceId
4)重写(Override)父类的有三个参数的getView方法,
5)在此方法中定义一个Fruit fruit,用getItem方法得到Fruit实例。
6)在此方法中定义一个View view,这对应着fruit_item.xml这个自定义布局,调用view的findViewById方法,分别把ImageView和TextView装进去,然后调用ImageView的setImageResource(),把fruit.getImageId()作为参数传入,调用TextView的setText,把fruit.getName()作为参数传入,然后将view返回。注意此方法不需要人为调用。
View的初始化:
LayoutInflater的inflate方法作用是将layout的xml布局文件实例化为View类对象,第一个参数是布局文件的地址,即刚被赋值 的resource,第二个参数是null
getContext的作用是获得上下文
总结:适配器的作用就是给布局文件插了一个吸管,将Fruit类中的数据传入到该布局文件中
1 package com.example.mmm; 2 3 import java.util.List; 4 5 import android.content.Context; 6 import android.view.LayoutInflater; 7 import android.view.View; 8 import android.view.ViewGroup; 9 import android.widget.ArrayAdapter; 10 import android.widget.ImageView; 11 import android.widget.TextView; 12 13 public class FruitAdapter extends ArrayAdapter<Fruit>{ 14 private int resourceId; 15 16 public FruitAdapter(Context context, int textViewResourceId, List<Fruit> objects) 17 { 18 super(context, textViewResourceId, objects); 19 // TODO Auto-generated constructor stub 20 resourceId=textViewResourceId; 21 } 22 public View getView(int position,View convertView,ViewGroup parent){ 23 Fruit fruit=getItem(position);//获取当前项的Fruit实例 24 View view =LayoutInflater.from(getContext()).inflate(resourceId,null);//创建一个view,与自定义布局上的对应起来,然后把组装好的视图作为返回值传出去 25 ImageView fruitImage =(ImageView)view.findViewById(R.id.fruit_image); 26 TextView fruitName=(TextView)view.findViewById(R.id.fruit_name); 27 fruitImage.setImageResource(fruit.getImageId()); 28 fruitName.setText(fruit.getName()); 29 return view; 30 31 } 32 33 34 }
4.修改MainActivity中的代码:
1)初始化ArrayList,泛型为Fruit,实例对象fruitList,使用了多态,为什么?
2)初始化水果数据,将Fuit的实例对象们添加到FruitList中
3)初始化FruitAdapter 的实例adapter,传入三个参数,上下文(MainActivity.this),布局文件的地址(R.layout.fruit_item)和数据源fruitList。
4)实例化ListView 对象listview,用findViewById
5)调用listview方法setAdapter,传入参数adapter
总结:当新建的适配器类FruitAdapter在主类中被初始化后,且作为到listview的setAdapter方法的参数时,数据源才开始和模板布局文件相匹配,一个个对应并且每一对作为一个整体添加到listview中,利用适配器的好处是,即使有n多个数据,也只需一个模板布局文件和一个适配器类就行了。
1 package com.example.mmm; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 import android.app.Activity; 7 import android.os.Bundle; 8 import android.view.Menu; 9 import android.view.MenuItem; 10 import android.view.Window; 11 import android.widget.ArrayAdapter; 12 import android.widget.ListView; 13 14 public class MainActivity extends Activity { 15 16 17 private String[] data={"Apple","Banana","Orange","Watermelon","Pear","Grape","Pineapple","Strawberry","Cherry","Mango"}; 18 private List<Fruit> fruitList =new ArrayList<Fruit>(); 19 @Override 20 protected void onCreate(Bundle savedInstanceState) { 21 22 super.onCreate(savedInstanceState); 23 requestWindowFeature(Window.FEATURE_NO_TITLE); 24 setContentView(R.layout.listview); 25 initFruits();//初始化水果数据 26 FruitAdapter adapter =new FruitAdapter (MainActivity.this,R.layout.fruit_item,fruitList); 27 ListView listview=(ListView)findViewById(R.id.listView_1); 28 listview.setAdapter(adapter); 29 } 30 public void initFruits(){ 31 Fruit apple=new Fruit("apple",R.drawable.apple_pic); 32 fruitList.add(apple); 33 Fruit banana=new Fruit("banana",R.drawable.banana_pic); 34 fruitList.add(banana); 35 Fruit orange=new Fruit("orange",R.drawable.orange_pic); 36 fruitList.add(orange); 37 Fruit pear=new Fruit("pear",R.drawable.pear_pic); 38 fruitList.add(pear); 39 Fruit cherry=new Fruit("cherry",R.drawable.cherry_pic); 40 fruitList.add(cherry); 41 Fruit mango=new Fruit("mango",R.drawable.mango_pic); 42 fruitList.add(mango); 43 Fruit pineapple=new Fruit("pineapple",R.drawable.pineapple_pic); 44 fruitList.add(pineapple); 45 Fruit grape=new Fruit("grape",R.drawable.grape_pic); 46 fruitList.add(grape); 47 Fruit strawberry=new Fruit("strawberry",R.drawable.strawberry_pic); 48 fruitList.add(strawberry); 49 Fruit watermelon=new Fruit("watermelon",R.drawable.watermelon_pic); 50 fruitList.add(watermelon); 51 } 52 @Override 53 public boolean onCreateOptionsMenu(Menu menu) { 54 55 // Inflate the menu; this adds items to the action bar if it is present. 56 getMenuInflater().inflate(R.menu.main, menu); 57 return true; 58 } 59 60 @Override 61 public boolean onOptionsItemSelected(MenuItem item) { 62 63 // Handle action bar item clicks here. The action bar will 64 // automatically handle clicks on the Home/Up button, so long 65 // as you specify a parent activity in AndroidManifest.xml. 66 int id = item.getItemId(); 67 if (id == R.id.action_settings) { 68 return true; 69 } 70 return super.onOptionsItemSelected(item); 71 } 72 }
结果
:
三、提升ListView的运行效率
之所以说ListView难用,是因为他还有很多的细节可以优化,其中很重要的一点就是她的运行效率,目前我们的ListView的运行效率是很低的,因为在FruitAdapter中的getView()方法中每次都将布局重新加载了一遍,比如我们上述有10个数据源,屏幕只能一次显示3个,那么需要滚动屏幕3次,每滚动一次就重新加载一次布局,也就一共加载了4次布局,如果滚动到底端又反过来往上滚的话,由于之前的没有缓存,又要重新调用getView方法,重新加载布局。当ListView快速滚动的时候,这就成为了性能的瓶颈
仔细观察getView方法中有个convertView参数,这个参数用于将之前加载好的布局进行缓存,以便以后可以重用,修改FruitAdapter中的代码,如是:
1 package com.example.mmm; 2 3 import java.util.List; 4 5 import android.content.Context; 6 import android.view.LayoutInflater; 7 import android.view.View; 8 import android.view.ViewGroup; 9 import android.widget.ArrayAdapter; 10 import android.widget.ImageView; 11 import android.widget.TextView; 12 13 public class FruitAdapter extends ArrayAdapter<Fruit>{ 14 private int resourceId; 15 16 public FruitAdapter(Context context, int textViewResourceId, List<Fruit> objects) 17 { 18 super(context, textViewResourceId, objects); 19 // TODO Auto-generated constructor stub 20 resourceId=textViewResourceId; 21 } 22 public View getView(int position,View convertView,ViewGroup parent){//系统隐式调用,不需要认为调用 23 Fruit fruit=getItem(position);//获取当前项的Fruit实例 24 View view ; 25 if(convertView==null){ 26 view=LayoutInflater.from(getContext()).inflate(resourceId,null);//创建一个view,与自定义布局上的对应起来,然后把组装好的视图作为返回值传出去 27 }else 28 view=convertView; 29 ImageView fruitImage =(ImageView)view.findViewById(R.id.fruit_image); 30 TextView fruitName=(TextView)view.findViewById(R.id.fruit_name); 31 fruitImage.setImageResource(fruit.getImageId()); 32 fruitName.setText(fruit.getName()); 33 return view; 34 35 } 36 37 38 }
可以看到我们对convertView参数做了判断,如果convertView为空,这表明这一项数据源对应的那个VIew是第一次被加载,就把该view与布局上的模板对应起来。
如果convertView不为null,表明该项数据源对应的View已经被加载过了,直接将convertView赋值给view即可,这样就实现了,已经被加载过的view不用重新对应自定义布局,也就是V重新加载了。这样在快速滚动的时候也能表现出较好的性能。
不过,我们这个代码还是可以继续优化的,虽然现在已经不会再重复区加载布局,但是每次在getView方法中还是会调用View的findViewById方法来获取一次控件的实例。我们借助一个ViewHolder来对这部分性能进行优化,修改FruitAdapter中的代码,如是:
1 package com.example.mmm; 2 3 import java.util.List; 4 5 import android.content.Context; 6 import android.view.LayoutInflater; 7 import android.view.View; 8 import android.view.ViewGroup; 9 import android.widget.ArrayAdapter; 10 import android.widget.ImageView; 11 import android.widget.TextView; 12 13 public class FruitAdapter extends ArrayAdapter<Fruit>{ 14 private int resourceId; 15 16 public FruitAdapter(Context context, int textViewResourceId, List<Fruit> objects) 17 { 18 super(context, textViewResourceId, objects); 19 // TODO Auto-generated constructor stub 20 resourceId=textViewResourceId; 21 } 22 public View getView(int position,View convertView,ViewGroup parent){//系统隐式调用,不需要认为调用 23 Fruit fruit=getItem(position);//获取当前项的Fruit实例 24 View view ; 25 ViewHolder viewholder; 26 if(convertView==null){ 27 viewholder =new ViewHolder(); 28 view=LayoutInflater.from(getContext()).inflate(resourceId,null);//创建一个view,与自定义布局上的对应起来,然后把组装好的视图作为返回值传出去 29 viewholder.fruitImage =(ImageView) view.findViewById(R.id.fruit_image); 30 viewholder.fruitName =(TextView) view.findViewById(R.id.fruit_name); 31 32 view.setTag(viewholder); 33 }else{ 34 view=convertView; 35 viewholder =(ViewHolder)view.getTag(); 36 } 37 viewholder.fruitImage.setImageResource(fruit.getImageId()); 38 viewholder.fruitName.setText(fruit.getName()); 39 return view; 40 41 } 42 } 43 class ViewHolder{ 44 ImageView fruitImage; 45 TextView fruitName; 46 }
我们新增了一个ViewHolder内部类,用于对空间的实现进行缓存,当convertView为空的时候创建一个ViewHolder对象,并将控件的实例都存放在ViewHolder里,然后调用View的setTag()方法,j将Viewholder对象存储在View,当convertView 不为空时,把ViewHolder重新取出,这样所有实例都缓存在了ViewHolder里没有必要每次都通过findViewById的方法来获取控件实例了。
有了这两步的优化之后,我们ListView的运行效果就已经非常不错了。
总结:
ListView运行效率优化方法有两种
1.通过convertView参数对布局进行缓存
2.通过新建一个含有两个实例变量的ViewHolder类对控件进行缓存。
四、ListView的点击事件
话说回来,ListView的滚动毕竟只是满足了我们的视觉效果,如果不能点击的话也没有什么卵用,因此接下来我们将要学习ListView如何响应用户的点击事件,就该MainActivity.javah中的代码,如是:
1 package com.example.mmm; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 import android.app.Activity; 7 import android.os.Bundle; 8 import android.view.Menu; 9 import android.view.MenuItem; 10 import android.view.View; 11 import android.view.Window; 12 import android.widget.AdapterView; 13 import android.widget.AdapterView.OnItemClickListener; 14 import android.widget.ArrayAdapter; 15 import android.widget.ListView; 16 import android.widget.Toast; 17 18 public class MainActivity extends Activity { 19 20 21 private String[] data={"Apple","Banana","Orange","Watermelon","Pear","Grape","Pineapple","Strawberry","Cherry","Mango"}; 22 private List<Fruit> fruitList =new ArrayList<Fruit>(); 23 @Override 24 protected void onCreate(Bundle savedInstanceState) { 25 26 super.onCreate(savedInstanceState); 27 requestWindowFeature(Window.FEATURE_NO_TITLE); 28 setContentView(R.layout.listview); 29 initFruits();//初始化水果数据 30 FruitAdapter adapter =new FruitAdapter (MainActivity.this,R.layout.fruit_item,fruitList); 31 ListView listview=(ListView)findViewById(R.id.listView_1); 32 listview.setOnItemClickListener(new OnItemClickListener(){ 33 public void onItemClick(AdapterView<?>parent,View view ,int position,long id){ 34 Fruit fruit =fruitList.get(position); 35 Toast.makeText(MainActivity.this, fruit.getName(), Toast.LENGTH_SHORT).show(); 36 } 37 }); 38 listview.setAdapter(adapter); 39 } 40 public void initFruits(){ 41 Fruit apple=new Fruit("apple",R.drawable.apple_pic); 42 fruitList.add(apple); 43 Fruit banana=new Fruit("banana",R.drawable.banana_pic); 44 fruitList.add(banana); 45 Fruit orange=new Fruit("orange",R.drawable.orange_pic); 46 fruitList.add(orange); 47 Fruit pear=new Fruit("pear",R.drawable.pear_pic); 48 fruitList.add(pear); 49 Fruit cherry=new Fruit("cherry",R.drawable.cherry_pic); 50 fruitList.add(cherry); 51 Fruit mango=new Fruit("mango",R.drawable.mango_pic); 52 fruitList.add(mango); 53 Fruit pineapple=new Fruit("pineapple",R.drawable.pineapple_pic); 54 fruitList.add(pineapple); 55 Fruit grape=new Fruit("grape",R.drawable.grape_pic); 56 fruitList.add(grape); 57 Fruit strawberry=new Fruit("strawberry",R.drawable.strawberry_pic); 58 fruitList.add(strawberry); 59 Fruit watermelon=new Fruit("watermelon",R.drawable.watermelon_pic); 60 fruitList.add(watermelon); 61 } 62 @Override 63 public boolean onCreateOptionsMenu(Menu menu) { 64 65 // Inflate the menu; this adds items to the action bar if it is present. 66 getMenuInflater().inflate(R.menu.main, menu); 67 return true; 68 } 69 70 @Override 71 public boolean onOptionsItemSelected(MenuItem item) { 72 73 // Handle action bar item clicks here. The action bar will 74 // automatically handle clicks on the Home/Up button, so long 75 // as you specify a parent activity in AndroidManifest.xml. 76 int id = item.getItemId(); 77 if (id == R.id.action_settings) { 78 return true; 79 } 80 return super.onOptionsItemSelected(item); 81 } 82 }
无论点击哪一个Item,都会调用ListView的setOnItemClickListener方法,根据position参数确定点击的是哪个Fruit