Listview几乎是最常见但是却比较难使用的控件了,这篇文章由浅入深讲解了listview的使用和优化。
首先来看listview的最简单使用,依赖ArrayAdapter
public class MyActivity extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView mylist = (ListView) findViewById(R.id.mylist);
ArrayList<String> arr = new ArrayList<String>();
Collections.addAll(arr, new String[]{"ssss","asdas","wqeqweqw",
"ewrrcxxx","ssss","asdas","wqeqweqw","ewrrcxxx"});
//设置适配器
mylist.setAdapter(new ArrayAdapter(this, android.R.layout.simple_expandable_list_item_1,
arr));
}
}
然后是调用 ContentProvider作为数据源的使用,下来以电话薄作为例子,依赖 SimpleCursorAdapter
public class MyActivity extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView mylist = (ListView) findViewById(R.id.mylist);
Cursor cursor = getContentResolver().query(People.CONTENT_URI, null, null, null, null);
//让Activity替我们管理cursor,使他们同生命周期
startManagingCursor(cursor);
//配置SimpleCursorAdapter
mylist.setAdapter(
new SimpleCursorAdapter(this, android.R.layout.simple_expandable_list_item_1,
cursor,new String[]{People.NAME}, new int[]{android.R.id.text1}));
}
}
最后是更为复杂的布局,包括imageview,textview
public class MyActivity extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView mylist = (ListView) findViewById(R.id.mylist);
/**
* 其中string[]数组为map的key值
* int[]数组为对应控件ID
*/
SimpleAdapter adapter = new SimpleAdapter(this,getData(),R.layout.vlist,
new String[]{"title","info","img"},
new int[]{R.id.title,R.id.info,R.id.img});
mylist.setAdapter(adapter);
}
private List<Map<String, Object>> getData() {
List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
Map<String, Object> map = new HashMap<String, Object>();
map.put("title", "G1");
map.put("info", "google 1");
map.put("img", R.drawable.ic_launcher);
list.add(map);
map = new HashMap<String, Object>();
map.put("title", "G2");
map.put("info", "google 2");
map.put("img", R.drawable.ic_launcher);
list.add(map);
map = new HashMap<String, Object>();
map.put("title", "G3");
map.put("info", "google 3");
map.put("img", R.drawable.ic_launcher);
list.add(map);
return list;
}
}
从上面例子可以看出,使用SimpleAdapter,可以设置Imageview,需要做的是传入一个 List<Map<String,?>>,然后 后两个参数数组,分别对应map的key值和控件ID
在来看添加了选择框的listview
public class MyActivity extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView mylist = (ListView) findViewById(R.id.mylist);
ArrayList<String> arr = new ArrayList<String>();
Collections.addAll(arr, new String[]{"ssss","asdas","wqeqweqw",
"ewrrcxxx","ssss","asdas","wqeqweqw","ewrrcxxx"});
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_single_choice,
arr);
mylist.setAdapter(adapter);
mylist.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
}
}
上面代码中有一个setChoiceMode来设置选择框是单选,其他选项还有
CHOICE_MODE_MULTIPLE(多选),
CHOICE_MODE_NONE(默认),
CHOICE_MODE_SINGLE(单选)
当然我们也可以在xml里面设置这个选项
android:choiceMode="multipleChoice"
如果是多选按钮,我们还要为listview设置一个MultiChoiceModeListener。另外,
我们需要使用getCheckItemIds
来获得当前的选项id(不是position)。
以上都是listview的简单使用,不涉及交互,只是简单的UI展示,下面要说明自定义Adapter来展示更复杂的功能
首先说listview绘制视图的原理:系统首先调用getCount()函数,根据他的返回值得到ListView的长度,然后一条条的绘制
package com.test;
import java.util.List;
import org.w3c.dom.ls.LSInput;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
public class MyAdapter extends BaseAdapter {
List<String> mlist = null;
Context mContext = null;
LayoutInflater mInflater;
ViewHolder holder;
public MyAdapter(List<String> mlist, Context mContext) {
super();
this.mlist = mlist;
this.mContext = mContext;
mInflater = LayoutInflater.from(mContext);
}
@Override
public int getCount() {
return mlist.size();
}
@Override
public String getItem(int position) {
return mlist.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
//静态内部类
static class ViewHolder{
ImageView img;
TextView text;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
//缓存机制
if(view == null){
view = mInflater.inflate(R.layout.vlist, null);
holder = new ViewHolder();
holder.text = (TextView) view.findViewById(R.id.title);
holder.img = (ImageView) view.findViewById(R.id.img);
view.setTag(holder);
}else{
//减少了findviewbyid()
holder = (ViewHolder)view.getTag();
}
//为控件设置值
holder.text.setText(mlist.get(position));
holder.img.setImageResource(R.drawable.ic_launcher);
return view;
}
}
上面代码有几个优化的地方:
1,使用convertView进行视图缓存,从而减少inflate()操作
对XML布局文件的inflate操作开销大,尽管为了更高效的解析,布局文件被编译成了二进制形式,然而inflate()方法会调用rinflate()从根视图递归地遍历一整棵由XML块(包装编译后的XML文件)组成的树,并且实例化每个子视图。
adapter内部有一个Recycler的构件用于缓存视图,原理如下
2,使用viewHolder,getTag()方式来重新找到控件,减少findViewById()
其中viewHolder是一个静态内部类,静态类只会在第一次加载时会耗费比较长时间,但是后面就可以很好帮助加载,同时保证了内存中只有一个ViewHolder,节省了内存的开销。
而使用getTag()方法可以更加快捷地找到控件,减少了findviewbyid();
下面再提几点listview的优化:
1,ExpandableListView 与 ListActivity 由官方提供的,里面要使用到的ListView是已经经过优化的ListView,如果大家的需求可以用Google自带的ListView满足的的话尽量用官方的,绝对没错!
2,对于图片的优化,这个非常重要,可以写另外一篇文章来说明,总的来说就是使用弱引用,缓存机制,异步加载
3,快速滑动时不显示图片。当快速滑动列表时(SCROLL_STATE_FLING),item中的图片或获取需要消耗资源的view,可以不显示出来;而处于其他两种状态(SCROLL_STATE_IDLE 和SCROLL_STATE_TOUCH_SCROLL),则将那些view显示出来
4,避免listview周围控件的大小变化,引起listview的变化,因为一旦listview大小变化时,就会产生重绘
getView()方法已经做了优化,所以很多聪明的想法其实是不必要的,例如:
listview的基本优化就是以上,但是我还有提一个listview根据需要加载不同项目布局(item layout)的情况。
你需要做这些:
- 重(@Override)写 getViewTypeCount() – 返回你有多少个不同的布局
- 重写 getItemViewType(int) – 由position返回view type id
- 根据view item的类型,在getView中创建正确的convertView
package com.test;
import java.util.List;
import org.w3c.dom.ls.LSInput;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
public class MyAdapter extends BaseAdapter {
List<String> mlist = null;
Context mContext = null;
LayoutInflater mInflater;
ViewHolder holder;
public MyAdapter(List<String> mlist, Context mContext) {
super();
this.mlist = mlist;
this.mContext = mContext;
mInflater = LayoutInflater.from(mContext);
}
/**
* 该方法返回不同item_layout的数目
*/
@Override
public int getViewTypeCount() {
return 2;
}
/**
* 返回对应position的item_layout类型
*/
@Override
public int getItemViewType(int position) {
//这里用奇偶数来区分
return position%2;
}
@Override
public int getCount() {
return mlist.size();
}
@Override
public String getItem(int position) {
return mlist.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
//静态内部类
static class ViewHolder{
ImageView img;
TextView text;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
int type = getItemViewType(position);
//缓存机制
if(view == null){
//根据不同的type采用不同的布局
switch(type){
case 0:
view = mInflater.inflate(R.layout.vlist, null);
holder = new ViewHolder();
holder.text = (TextView) view.findViewById(R.id.title);
holder.img = (ImageView) view.findViewById(R.id.img);
view.setTag(holder);
break;
case 1:
view = mInflater.inflate(R.layout.vlist2, null);
holder = new ViewHolder();
holder.text = (TextView) view.findViewById(R.id.title);
holder.img = (ImageView) view.findViewById(R.id.img);
view.setTag(holder);
break;
}
}else{
//减少了findviewbyid()
holder = (ViewHolder)view.getTag();
}
//为控件设置值
holder.text.setText(mlist.get(position));
holder.img.setImageResource(R.drawable.ic_launcher);
return view;
}
}