ListView是我们开发过程中最常用的控件之一,由于手机屏幕空间都比较有限度,能够一次性在屏幕上显示的内容并不多,当我们程序中有大量的数据需要展示的时候,就可以借助它来完成。它允许用户通过手指上下滑动的方式将屏幕外的数据滚动到屏幕内,同时屏幕上原有的数据则会滚动出屏幕。
一、ListVeiw的简单用法
首先在XML文件中定义ListView的布局
<ListView android:id="@+id/listView01" android:layout_width="match_parent" android:layout_height="match_parent" ></ListView>
然后修改activity中的代码,如下所示:
1 public class SecondActivity extends AppCompatActivity { 2 3 private String[] data = {"Apple", "Banana", "Orange", "Watermelon", "Pear"}; 4 5 @Override 6 protected void onCreate(Bundle savedInstanceState) { 7 super.onCreate(savedInstanceState); 8 setContentView(R.layout.activity_second); 9 10 ArrayAdapter<String> adapter = new ArrayAdapter<String>(SecondActivity.this, android.R.layout.simple_list_item_1,data); 11 ListView listView = (ListView)findViewById(R.id.listView01); 12 listView.setAdapter(adapter); 13 } 14 15 }
这样,我们就简单的实现了ListView的数据展示。不过,数组中的数据是无法直接传递给ListView的,这里我们就要提到适配器的概念了。Android中提供了很多的适配器类,我们先说下ArrayAdapter,它可以通过泛型来指定要适配的数据类型,然后在构造函数中把要适配的数据传入即可。它有多个构造函数的重载,我们可以根据实际情况选择最合适的一种。上面的代码中由于我们提供的数据都是字符串,因此将ArrayAdapter的泛型指定为String,然后在ArrayAdapter的构造函数中依次传入当前上下文、ListView的子项布局的id、以及要适配的数据。注意,我们使用android.R.layout.simple_list_item_1作为它的子项布局id ,这是Android内置的布局文件,里面只有一个TextView,可用于简单的显示一段文本。调用ListViewd的setAdapter()方法,将构造好的适配器对象传递进去,这样ListView和数据之间的关联就建立完成了。
二、定制ListView
在项目中ListView不但要展示大量的数据,显示也要非常的美观,只显示一段文本的ListView太过于单调,下面就来介绍一下如何去定制ListView的界面,让它可以显示更加丰富的内容。这里我们让ListView的每一行显示一张图片和一个文本,详细代码如下所示:
(1)首先在activity对应的XML文件activity_list_view.xml里定义ListView布局
<ListView android:id="@+id/listView02" android:layout_width="match_parent" android:layout_height="match_parent" ></ListView>
(2)自定义ListView的子项布局,新建一个XML文件custom_item.xml:
<ImageView android:id="@+id/imageView01" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/listView_TextVeiw01" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_marginLeft="10dp" />
(3)自定义一个实体类CustomClass,作为ListView适配器的适配类型。
1 /* 2 *定制CustomClass实体类,作为ListView适配器的适配类型 3 *name:表示图片的含义 4 *imageId:表示对应的图片资源ID 5 */ 6 public class CustomClass { 7 private String name; 8 private int imageId; 9 10 public CustomClass(String name, int imageId){ 11 this.name = name; 12 this.imageId = imageId; 13 } 14 15 public String getName() { 16 return name; 17 } 18 19 public int getImageId() { 20 return imageId; 21 } 22 }
(4)自定义适配器CustomAdapter
1 /* 2 * CustomAdapter重写了父类的一组构造函数,用于将上下文、ListVeiw的子项布局的id和数据都传递进来 3 * 另外,又重写了getView()方法,这个方法在每个子项被滚动到屏幕内的时候调用 4 * 在getView方法中,首先通过getItem()方法得到当前项的CustomClass实例,然后使用LayoutInflater来 5 * 为这个子项加载我们传入的布局,接着调用Veiw的findViewById()方法分别获取到ImageVeiw和TextView的实例, 6 * 并分别调用他们的setImageResource()和setText()方法来设置显示的图片和文字,最后将布局返回。 7 * */ 8 9 10 public class CustomAdapter extends ArrayAdapter<CustomClass> { 11 private int resourceId; 12 13 public CustomAdapter(Context context, int textViewResourceId, List<CustomClass> objects){ 14 super(context, textViewResourceId, objects); 15 resourceId = textViewResourceId; 16 } 17 18 public View getView(int position, View convertView, ViewGroup parent){ 19 CustomClass custom = getItem(position); 20 View view = LayoutInflater.from(getContext()).inflate(resourceId, null); 21 ImageView customImage = (ImageView)view.findViewById(R.id.imageView01); 22 TextView customTextView = (TextView)view.findViewById(R.id.listView_TextVeiw01); 23 customImage.setImageResource(custom.getImageId()); 24 customTextView.setText(custom.getName()); 25 return view; 26 } 27 }
(5)下面修改ListViewActivity中的代码
1 public class ListViewActivity extends AppCompatActivity { 2 3 private List<CustomClass> customList = new ArrayList<CustomClass>(); 4 5 @Override 6 protected void onCreate(Bundle savedInstanceState) { 7 super.onCreate(savedInstanceState); 8 setContentView(R.layout.activity_list_view); 9 10 initCustoms(); 11 CustomAdapter adapter = new CustomAdapter(ListViewActivity.this, R.layout.custom_item, customList); 12 ListView listView = (ListView)findViewById(R.id.listView02); 13 listView.setAdapter(adapter); 14 15 } 16 //初始化数据 17 private void initCustoms(){ 18 CustomClass chat = new CustomClass("Chat", R.drawable.chat_pressed); 19 customList.add(chat); 20 21 CustomClass friends = new CustomClass("Friends", R.drawable.friends_pressed); 22 customList.add(friends); 23 24 } 25 }
总结:自定义ListView主要自定义了适配器、适配器指定的泛型、ListView的子项,理解了整体流程,实现起来就会更佳方便。
(6)提升ListView的运行效率
目前我们定制的ListView的运行效率是很低的,因为CustomAdapter的getView()方法中每次都将布局重新加载了一遍,当ListView快速滚动的时候这就会成为性能瓶颈。在getView()中有一个convertView参数,该参数用于将之前加载好的布局进行缓存,以便之后可以进行重用。下面对getView()中的代码进行修改:
public View getView(int position, View convertView, ViewGroup parent){ CustomClass custom = getItem(position); View view; if (convertView == null){ view = LayoutInflater.from(getContext()).inflate(resourceId, null); }else { view = convertView; } ImageView customImage = (ImageView)view.findViewById(R.id.imageView01); TextView customTextView = (TextView)view.findViewById(R.id.listView_TextVeiw01); customImage.setImageResource(custom.getImageId()); customTextView.setText(custom.getName()); return view; }
可以看到,我们在getView()中的代码进行了判断,如果convertView为空,则使用LayoutInflater去加载布局,如果不为空则直接对convertView进行重用。这样就大大提高了ListView的运行效率,在快速滚动的时候也可以表现出更好的性能。
虽然在运行的时候已经不会再重复去加载布局,但是每次在getView方法中还是会调用View的findViewById()方法来获取一次控件的实例,我们可以借助ViewHolder来对这部分性能进行优化,修改CustomAdapter中的代码,如下所示:
1 public View getView(int position, View convertView, ViewGroup parent){ 2 CustomClass custom = getItem(position); 3 View view; 4 ViewHolder viewHolder; 5 if (convertView == null){ 6 view = LayoutInflater.from(getContext()).inflate(resourceId, null); 7 8 viewHolder = new ViewHolder(); 9 viewHolder.customImage = (ImageView)view.findViewById(R.id.imageView01); 10 viewHolder.customTextView = (TextView)view.findViewById(R.id.listView_TextVeiw01); 11 view.setTag(viewHolder); //将ViewHolder存储在View中 12 13 }else { 14 view = convertView; 15 viewHolder = (ViewHolder)view.getTag(); //重新获取ViewHolder 16 } 17 18 viewHolder.customImage.setImageResource(custom.getImageId()); 19 viewHolder.customTextView.setText(custom.getName()); 20 21 return view; 22 } 23 24 class ViewHolder{ 25 ImageView customImage; 26 TextView customTextView; 27 }
新增一个内部类ViewHolder,用于对控件的实例进行缓存。当convertView为空的时候,创建一个ViewHolder对象,并将控件的实例都存放在ViewHolder里,然后调用View的setTag()方法,将ViewHolder对象存储在View中。当convertView不为空的时候则调用View的getTag()方法,把ViewHolder重新取出。这样所有控件的实例都缓存在了ViewHolder里,就没有必要每次都通过findViewById()方法来获取控件实例了。
(7)ListView的点击事件
修改ListViewActivity中的代码,如下所示:
1 protected void onCreate(Bundle savedInstanceState) { 2 super.onCreate(savedInstanceState); 3 setContentView(R.layout.activity_list_view); 4 5 initCustoms(); 6 CustomAdapter adapter = new CustomAdapter(ListViewActivity.this, R.layout.custom_item, customList); 7 ListView listView = (ListView)findViewById(R.id.listView02); 8 listView.setAdapter(adapter); 9 10 listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { 11 @Override 12 public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) { 13 CustomClass custom = customList.get(i); 14 Toast.makeText(ListViewActivity.this, custom.getName(), Toast.LENGTH_SHORT).show(); 15 } 16 });