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种监听器:

  1. OnItemClickListener: 处理视图中单个条目的点击事件。(OnItemSelectedListener,参数与其一致,只是监听用户手指行为不同)
  2. 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);
}

}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容