ListView常用知识点归纳

作者: 陈利健 | 来源:发表于2015-01-30 14:45 被阅读757次

    适配器

    适配器实现过程:
    共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);
    }
    

    }

    相关文章

      网友评论

      本文标题:ListView常用知识点归纳

      本文链接:https://www.haomeiwen.com/subject/ixklxttx.html