ListView常用知识点归纳
适配器
适配器实现过程:
共3步:新建适配器-->添加数据源到适配器-->视图加载适配器
适配器的数据源:
ArrayAdapter:可以使数组或集合
SimpleAdapter:只能是特定泛型的集合
BaseAdapter:是一个抽象类,需要自己写一个类继承它。灵活性高,因为提供了4个方法去重写。其中有一个getView()方法,我们可以重写优化ListView的方法。SimpleAdapter是其子类。
1.创建ArrayAdapter:
public class Test1 extends Activity {
private ListView mListView;
private ArrayAdapter<String> arr_Adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.test1);
//准备好视图,我们的目标ListView
mListView = (ListView) findViewById(R.id.id_listView);
//准备好数据源(数组或集合)
String [] arr = {"Android_1","Android_2","Android_3","Android_4"};
//ArrayAdapter传入3个参数: 上下文,当前ListView加载的“每一个列表项所对应的布局文件”,数据源
//此例用了一个自带的布局文件,Ctrl+左键可以看到这其实是一个简单的TextView,ArrayAdapter功能有限,每个列表项布局只能是TextView
arr_Adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, arr);
mListView.setAdapter(arr_Adapter);
}
}
2.创建SimpleAdapter
SimpleAdapter不同ArrayAdapter每一个列表项只能显示TextView,它每一个列表项可以自己定义各种内容,所以一般需要自己创建布局。
res/layout/item.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="horizontal" >
<ImageView
android:id="@+id/pic"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:src="@drawable/ic_launcher" />
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="demo"
android:textColor="#000000"
android:textSize="20sp" />
</LinearLayout>
代码部分:
(1)根据需要定义ListView每一个列表项所实现的布局(上面已写)。
(2)定义一个HashMap构成的列表,将数据以键值对的方式存放在里面。
(3)构造SimpleAdapter对象。
(4)将LsitView绑定到SimpleAdapter上。
public class Test2 extends Activity {
private ListView mListView;
private SimpleAdapter sim_Adapter;
private List<Map<String,Object>> datalist;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.test2);
//准备好视图
mListView = (ListView) findViewById(R.id.id_listView);
//准备好数据源
datalist = new ArrayList<Map<String,Object>>();
/** SimpleAdapter传入3个参数:
* Context:上下文
* data:数据源(List<? extends Map<String,?>> data),是一个特定泛型的集合。简单讲就是Map组成的List集合。
* 每一个Map<>对应ListView列表中的一行。
* 每一个Map<>(key-value)中的key必须包含所有在from中指定的key
* 因为SimpleAdapter每一个列表项中既要放图片又要放文字,所以需要一个Map<>组合起来,不同于ArrayAdapter只有文字,数据源是简单的List集合。
* resource:列表项布局文件的id
* from:Map<>中的键; new String []
* to:填充那些组件(在列表项的布局文件中定义的) new int []
*/
sim_Adapter = new SimpleAdapter(this, getData(), R.layout.item, new String []{"pic","text"}, new int[]{R.id.pic, R.id.text});
mListView.setAdapter(sim_Adapter);
}
//通过方法返回数据源,更加灵活
private List<Map<String,Object>> getData() {
for(int i= 0 ; i<20; i++){
Map<String,Object> data = new HashMap<String, Object>();
//这里每一Map的键就是第4个参数声明的键,每一个Map的值就是第5个参数声明的组件类型,只不过此处要传入组件具体的显示内容
data.put("pic", R.drawable.ic_launcher);
data.put("text", "Android"+i );
//遍历每一个Map,添加到List集合中去
datalist.add(data);
}
return datalist;
}
}
3.创建BaseAdapter(及性能优化)
使用BaseAdapter必须写一个类继承它,BaseAdapter是一个抽象类,继承它必须实现它的方法。BaseAdapter的灵活性就在于它要重写4种方法。
原理:当系统开始绘制ListView的时候,首先调用getCount()方法。得到它的返回值,即ListView的长度。然后系统调用getView()方法,根据这个长度逐一绘制ListView的每一行。
重点是getView()方法 (常被用于优化):
getView()在每一个列表项被滚动到屏幕内的时候被调用,然后new一个View出来作为列表项的布局。假如有10000个列表项,就new 10000次 View出来吗?这肯定会极大的消耗资源,导致ListView滑动非常的慢。
convertView(每个列表项的布局,即View):
ListView第一次显示的时候,此时convertView是null,getView()会加载一个屏幕所有列表项的View,加载完convertView便不为null。然后在ListView滑动的过程中,会有列表项被滑出屏幕,它所对应的布局convertView便不再给它使用。所以我们可以对convertView进行复用,把失去原主人(旧列表项)的convertView赋给新出来的列表项。
View view;
if (convertView == null){
view = inflater.inflate(R.layout.item_addmore, null);
}else{
view = convertView;
}
ViewHolder
上面的优化仅仅复用了View,但是要得到其中的控件,需要在控件的容器中通过findViewById的方法来获得。
当convertView为null时,创建一个ViewHolder对象,并将控件的实例都存放在ViewHolder对象里。这样所有控件的实例都缓存在ViewHolder里。
setTage(), getTag()
实例缓存好了,怎么给View使用呢?当convertView为null时,用setTag()方法为每个View绑定一个存放控件的ViewHolder对象。当convertView不为null,重复利用已经创建的view的时候,使用getTag()方法获取绑定的ViewHolder对象,这样就避免了findViewById对控件的层层查询。
public class MyAdapter extends BaseAdapter {
ArrayList<ApkEntity> apk_list;
LayoutInflater inflater;
public MyAdapter(Context context, ArrayList<ApkEntity> apk_list) {
this.apk_list = apk_list;
this.inflater = LayoutInflater.from(context);
}
//返回值表示该Adapter共包含多个列表项
@Override
public int getCount() {
return apk_list.size();
}
//返回值表示第position处的列表项的内容
@Override
public Object getItem(int position) {
return apk_list.get(position);
}
//返回值表示第position处的列表项的id
@Override
public long getItemId(int position) {
return position;
}
//返回的view将作为列表框布局
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ApkEntity entity = apk_list.get(position);
ViewHolder holder;
if (convertView == null){
holder = new ViewHolder();
convertView = inflater.inflate(R.layout.item_addmore, null);
//得到各个控件的对象,并缓存到View中
holder.name_tv = (TextView) convertView
.findViewById(R.id.item3_apkname);
holder.des_tv = (TextView) convertView
.findViewById(R.id.item3_apkdes);
holder.info_tv = (TextView) convertView
.findViewById(R.id.item3_apkinfo);
convertView.setTag(holder);
}else{
holder = (ViewHolder) convertView.getTag();
}
holder.name_tv.setText(entity.getName());
holder.des_tv.setText(entity.getDes());
holder.info_tv.setText(entity.getInfo());
return convertView;
}
//布局里的控件
class ViewHolder {
TextView name_tv;
TextView des_tv;
TextView info_tv;
}
}
监听器
ListView中主要有2种监听器:
- OnItemClickListener: 处理视图中单个条目的点击事件。(OnItemSelectedListener,参数与其一致,只是监听用户手指行为不同)
- OnScrollListener:监听滚动变化,可以用于视图滚动中加载数据。
关于OnItemClickListener中4个参数:
举个例子解释:有X, Y两个listview,X里有1,2,3,4这4个item,Y里有a,b,c,d这4个item。如果点了b这个item,那么:
1.parent 相当于listview Y适配器的一个指针,可以通过它来获得Y里装着的一切东西,再通俗点就是说告诉你,你点的是Y,不是X 。
2.view 是你点b item的view的句柄,就是你可以用这个view,来获得b里的控件的id后操作控件。
3.position是b在Y适配器里的位置(生成listview时,适配器一个一个的做item,然后把他们按顺序排好队,在放到listview里,意思就是这个b是第position号做好的)。
4.id 是b在listview Y里的第几行的位置(很明显是第2行),大部分时候position和id的值是一样的。
关于OnScrollListener中4个参数:
滚动时一直回调,直到停止滚动时才停止回调。
firstVisibleItem:当前能看见的第一个列表项ID(从0开始)
visibleItemCount:当前能看见的列表项个数(小半个也算)
totalItemCount:列表项共数
//判断是否滚到最后一行
if (firstVisibleItem + visibleItemCount == totalItemCount && totalItemCount > 0) {
isLastRow = true;
在SimpleAdapter例子上,加上这两个监听器事件:
public class Test2 extends Activity implements OnItemClickListener, OnScrollListener{
private ListView mListView;
private SimpleAdapter sim_Adapter;
private List<Map<String,Object>> datalist;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.test2);
//准备好视图
mListView = (ListView) findViewById(R.id.id_listView);
//准备好数据源
datalist = new ArrayList<Map<String,Object>>();
/** SimpleAdapter传入3个参数:
* Context:上下文
* data:数据源(List<? extends Map<String,?>> data),是一个特定泛型的集合。简单讲就是Map组成的List集合。
* 每一个Map<>对应ListView列表中的一行。
* 每一个Map<>(key-value)中的key必须包含所有在from中指定的key
* 因为SimpleAdapter每一个列表项中既要放图片又要放文字,所以需要一个Map<>组合起来,不同于ArrayAdapter只有文字,数据源是简单的List集合。
* resource:列表项布局文件的id
* from:Map<>中的键; new String []
* to:填充那些组件(在列表项的布局文件中定义的) new int []
*/
sim_Adapter = new SimpleAdapter(this, getData(), R.layout.item, new String []{"pic","text"}, new int[]{R.id.pic, R.id.text});
mListView.setAdapter(sim_Adapter);
mListView.setOnItemClickListener(this);
mListView.setOnScrollListener(this);
}
//通过方法返回数据源,更加灵活
private List<Map<String,Object>> getData() {
Map<String,Object> data = new HashMap<String, Object>();
for(int i= 0 ; i<20; i++){
//这里每一Map的键就是第4个参数声明的键,每一个Map的值就是第5个参数声明的组件类型,只不过此处要传入组件具体的显示内容
data.put("pic", R.drawable.ic_launcher);
data.put("text", "Android"+i);
//遍历每一个Map,添加到List集合中去
datalist.add(data);
}
return datalist;
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
String text = mListView.getItemIdAtPosition(position)+"";
Toast.makeText(this, position +":" +text, Toast.LENGTH_SHORT).show();
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
switch (scrollState) {
case SCROLL_STATE_FLING:
Log.i("Main", "用户手指离开屏幕之前,由于用力滑了一下,视图仍继续向前滑动");
break;
case SCROLL_STATE_IDLE:
Log.i("Main", "视图已经停止滑动");
break;
case SCROLL_STATE_TOUCH_SCROLL:
Log.i("Main", "手指没有离开屏幕,视图正在滑动");
break;
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
}
}
滚动到底部,刷新添加更多内容:
1.自定义ListView
//自定义一个ListView,在构造方法里就给底部添加一个布局(加载提示布局)
public class LoadListView extends ListView implements OnScrollListener{
View footer;// 底部布局;
boolean isLastRow; //最后一行
boolean isLoading ;// 正在加载;
ILoadListener iLoadListener;
public LoadListView(Context context) {
super(context);
initView(context);
}
public LoadListView(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
public LoadListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView(context);
}
//添加底部加载提示布局到ListView
private void initView(Context context) {
LayoutInflater inflater = LayoutInflater.from(context);
footer = inflater.inflate(R.layout.footer_layout, null);
//设置底部布局一开始是隐藏的,只有当滚动到最低端才显示
footer.findViewById(R.id.load_layout).setVisibility(View.GONE);
//给ListView添加底部布局
this.addFooterView(footer);
this.setOnScrollListener(this);
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
//判断是否滚到最后一行
if (firstVisibleItem + visibleItemCount == totalItemCount && totalItemCount > 0) {
isLastRow = true;
}
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
//当滚到最后一行且停止滚动时,显示布局,加载数据
if(isLastRow && scrollState == SCROLL_STATE_IDLE){
if(!isLoading){
isLoading = true;
footer.findViewById(R.id.load_layout).setVisibility(View.VISIBLE);
//(加载更多)使用接口回调的方式(当监听到滑动到底端,只需调用接口的onLoad方法,剩余的操作交给onLoad方法就可以了)
iLoadListener.onLoad();
}
}
}
public void setInterface(ILoadListener iLoadListener){
this.iLoadListener = iLoadListener;
}
//加载更多数据的回调接口
public interface ILoadListener{
public void onLoad();
}
//加载完毕
public void loadComplete(){
isLoading = false;
footer.findViewById(R.id.load_layout).setVisibility(
View.GONE);
}
}
2.自定义Adapter
public class MyAdapter extends BaseAdapter {
ArrayList<ApkEntity> apk_list;
LayoutInflater inflater;
public MyAdapter(Context context, ArrayList<ApkEntity> apk_list) {
this.apk_list = apk_list;
this.inflater = LayoutInflater.from(context);
}
//刷新ListView,更新UI
public void onDateChange(ArrayList<ApkEntity> apk_list) {
this.apk_list = apk_list;
this.notifyDataSetChanged();
}
//返回值表示该Adapter共包含多个列表项
@Override
public int getCount() {
return apk_list.size();
}
//返回值表示第position处的列表项的内容
@Override
public Object getItem(int position) {
return apk_list.get(position);
}
//返回值表示第position处的列表项的id
@Override
public long getItemId(int position) {
return position;
}
//返回的view将作为列表框布局
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ApkEntity entity = apk_list.get(position);
ViewHolder holder;
if (convertView == null){
holder = new ViewHolder();
convertView = inflater.inflate(R.layout.item_addmore, null);
//得到各个控件的对象,并缓存到View中
holder.name_tv = (TextView) convertView
.findViewById(R.id.item3_apkname);
holder.des_tv = (TextView) convertView
.findViewById(R.id.item3_apkdes);
holder.info_tv = (TextView) convertView
.findViewById(R.id.item3_apkinfo);
convertView.setTag(holder);
}else{
holder = (ViewHolder) convertView.getTag();
}
holder.name_tv.setText(entity.getName());
holder.des_tv.setText(entity.getDes());
holder.info_tv.setText(entity.getInfo());
return convertView;
}
//布局里的控件
class ViewHolder {
TextView name_tv;
TextView des_tv;
TextView info_tv;
}
}
3.工具类
public class ApkEntity {
private String name;
private String des;
private String info;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDes() {
return des;
}
public void setDes(String des) {
this.des = des;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
}
4.注意主布局下的ListView改成自定义的ListView
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<com.chenlijian.listview_test.LoadListView
android:id="@+id/id_listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:cacheColorHint="#00000000"
android:dividerHeight="5dip" >
</com.chenlijian.listview_test.LoadListView>
</LinearLayout>
5.主代码
public class Test3 extends Activity implements ILoadListener {
private ArrayList<ApkEntity> apk_list = new ArrayList<ApkEntity>();
private MyAdapter adapter;
private LoadListView listView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.test3);
getData();
showListView(apk_list);
}
private void getData() {
for (int i = 0; i < 10; i++) {
ApkEntity entity = new ApkEntity();
entity.setName("测试程序");
entity.setInfo("50w用户");
entity.setDes("这是很棒的应用!");
apk_list.add(entity);
}
}
private void getLoadData() {
for (int i = 0; i < 4; i++) {
ApkEntity entity = new ApkEntity();
entity.setName("更多程序");
entity.setInfo("50w用户");
entity.setDes("这是一个神奇的应用!");
apk_list.add(entity);
}
}
private void showListView(ArrayList<ApkEntity> apk_list) {
if (adapter == null) {
listView = (LoadListView) findViewById(R.id.id_listView);
listView.setInterface(this);
adapter = new MyAdapter(this, apk_list);
listView.setAdapter(adapter);
}else{
adapter.onDateChange(apk_list);
}
}
//正常情况下,不要加延时,此处只为演示
@Override
public void onLoad() {
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
//获取更多数据
getLoadData();
//更新listview显示;
showListView(apk_list);
//通知listview加载完毕
listView.loadComplete();
}
}, 2000);
}
}