zoukankan      html  css  js  c++  java
  • 19、Android--ListView

    ListView

    listView是用来解决大量的相似数据显示问题,同时大量的数据会导致不断在内存中创建对象,可能导致OOM。

    listView属性

    ListView的常用属性如下表所示:

    名称 描述
    android:choiceMode none,值为 0,表示无选择模式。 singleChoice,值为 1,表示最多可以有一项被选中。 multipleChoice,值为 2,表示可以多项被选中。
    android:divider 规定 List 项目之间用某个图形或颜色来分隔。
    android:dividerHeight 分隔符的高度。
    android:entries 引用一个将使用在此 ListView 里的数组。
    android:footerDividersEnabled 设成 flase 时,此 ListView 将不会在页脚视图前画分隔符。 此属性缺省值为 true。 属性值必须设置为 true 或false。
    android:headerDividersEnabled 设成 flase 时,此 ListView 将不会在页眉视图后画分隔符。此属性缺省值为 true。

    该控件采用MVC的设计模式,而且还有大量适配器,一般情况是:BaseXXX、BasicXXX、SimpleXXX、DefaultXXX。

    除此之外,来看看ListView的常用事件:

    事件 描述
    setOnclickListener() 列表点击事件
    setOnItemLongClickListener() 条目长按事件
    setOnItemClickListener() 条目点击事件
    setOnScrollListener() 列表滚动事件
    setOnItemSelectedListener() 条目选择事件
    setOnTouchListener() 列表触摸事件

    简析Adapter

    让我们来看看常用的Adapter:

    名称 构造参数 功能
    ArrayAdapter context:上下文,一般是this。
    textViewResourceId:指定自定义的布局文件
    objects:ListView视图中的类,类型根据泛型而变化
    支持泛型操作,只能展示一行文字
    SimpleAdapter context:上下文,一般是this。
    data:代表整个ListView的List集合
    resource:自定义的布局文件或系统布局文件
    from:对应的key的数组。
    to:对应的value的数组。
    同样具有良好扩展性的一个Adapter,可以自定义多种效果。
    CursorAdapter context:上下文,一般是this。
    cursor:游标 flags:标志位
    显示简单文本类型的listView,一般在数据库那里会用到。
    BaseAdapter 没有构造方法 抽象类,实际开发中我们会继承这个类并且重写相关方法,用得最多的一个Adapter。

    ArrayAdapter

    ArrayAdapter是BaseAdapter的子类,主要用于存放字符串。

    public class MainActivity extends Activity {
        private ListView mListView;
        private static final String[] mDatas = {"功能1","功能2","功能3"};
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mListView = (ListView) findViewById(R.id.listview);
            // 设置适配器,第三个数组是根据泛型而变化的
            mListView.setAdapter(new ArrayAdapter<String>(this, R.layout.list_item, mDatas));
        }
    }  
    

    需要注意的是,上面的list_item布局中,TextView必须作为根节点,否则报错:

    <?xml version="1.0" encoding="utf-8"?>
    <TextView xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/tv_content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />  
    

    如果需要使用系统自定义的样式,由于ArrayAdapter是单行显示,所以只能用simple_list_item_1

    mListView.setAdapter(new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,
            new String[]{"功能1","功能2","功能3"}));   
    

    如果ArrayAdapter想要实现更多的效果则需要自定义ArrayAdapter,不过目前这种方式已经过时。

    SimpleAdapter

    SimpleAdapter也是BaseAdapter的子类,用来实现一些图片、文字并排的效果。

    public class MainActivity extends Activity {
        private ListView mListView;
        private String[] mDatas = {"声音","显示","存储","电池","应用"};
        private int[] resources = {R.drawable.akb,R.drawable.akc,R.drawable.akd,R.drawable.ake,R.drawable.akf};
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mListView = (ListView) findViewById(R.id.listview);
            // 构建数据模型
            List<Map<String, Object>> lists = new ArrayList<Map<String,Object>>();
            for (int i = 0; i < resources.length; i++) {
                Map<String, Object> maps = new HashMap<String, Object>();
                maps.put("name", mDatas[i]);
                maps.put("drawable", resources[i]);
                lists.add(maps);
            }
            mListView.setAdapter(new SimpleAdapter(this, lists, R.layout.item, 
                    new String[]{"name","drawable"}, new int[]{R.id.tv_content,R.id.iv_img}));
            
        }
    }  
    

    如果需要用到系统的样式,有如下系统样式:

    simple_list_item_1:单行文本组成
    simple_list_item_2:两行文本组成
    simple_list_item_checked:每项都是由一个已选中的列表框。
    simple_list_item_single_choice:都带有一个单选按纽。
    simple_list_item_multiple:全部带有一个复选框。

    CursorAdapter

    它同样是BaseAdapter的子类,它为Cursor和ListView提供连接的桥梁。

    newView():并不是每次都被调用,它只在实例化和数据增加时调用,而修改条目的内容时不会调用。
    bindView():在绘制item之前或重绘时一定会调用。
    changeCursor():类似于notifyDataSetChange()方法。

    从源码中可以看到:我们在写CursorAdapter时必须实现它的两个方法:

    /**
     * Makes a new view to hold the data pointed to by cursor.
     * @param context Interface to application's global information
     * @param cursor The cursor from which to get the data. The cursor is already
     * moved to the correct position.
     * @param parent The parent to which the new view is attached to
     * @return the newly created view.
     */
    public abstract View newView (Context context, Cursor cursor, ViewGroup parent);
    
     /**
     * Bind an existing view to the data pointed to by cursor
     * @param view Existing view, returned earlier by newView
     * @param context Interface to application's global information
     * @param cursor The cursor from which to get the data. The cursor is already
     * moved to the correct position.
     */
    public abstract void bindView(View view, Context context, Cursor cursor);
    

    简单的示例代码如下:

    @Override
    public View newView(Context context, Cursor cursor, ViewGroup parent) { 
    	ViewHolder viewHolder= new ViewHolder();
    	LayoutInflater inflater=(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE );
    	View view=inflater.inflate(R.layout.item_contacts ,parent,false);
    	viewHolder. tv_name=(TextView) view.findViewById(R.id.tv_showusername );
    	viewHolder. tv_phonenumber=(TextView) view.findViewById(R.id.tv_showusernumber );
    	view.setTag(viewHolder);
    	Log. i("cursor" ,"newView=" +view);
    	return view;
    }
    
    @Override
    public void bindView(View view, Context context, Cursor cursor) {
    	Log. i("cursor" ,"bindView=" +view);
    	ViewHolder viewHolder=(ViewHolder) view.getTag();
    	//从数据库中查询姓名字段
    	String name=cursor.getString(cursor.getColumnIndex(PersonInfo.NAME));
    	//从数据库中查询电话字段
    	String phoneNumber=cursor.getString(cursor.getColumnIndex(PersonInfo.PHONENUMBER));
    
    	viewHolder. tv_name.setText(name);
    	viewHolder. tv_phonenumber.setText(phoneNumber);
    }
    

    调用newView方法实例化条目,然后调用bindView绘制条目,当只绘制时不会调用newView方法。

    btn_save.setOnClickListener( new OnClickListener() {                
    public void onClick(View v) {
    	  userName=et_name.getText().toString();
    	  userPhoneNumber=et_phonenumber .getText().toString();
    	  ContentValues contentValues= new ContentValues();
    	  contentValues.put(PersonInfo. NAME, userName);
    	  contentValues.put(PersonInfo.PHONENUMBER ,userPhoneNumber );
    	  //把EditText中的文本插入数据库
    	  dataBase.insert(PersonInfo. PERSON_INFO_TABLE, null,contentValues);
    	  //根据 _id 降序插叙数据库保证最后插入的在最上面
    	  Cursor myCursor = dataBase.query(PersonInfo. PERSON_INFO_TABLE, null, null, null, null, null, orderBy);
    	  //Cursor改变调用chanageCursor()方法
    	  myCursorAdapter.changeCursor(myCursor);
      }
    });
    

    ListView复用

    ListView的复用机制可以参考下图:

    BaseAdapter是经常用到的基础数据适配器,它的主要用途是将一组数据传到像ListView、Spinner、Gallery及GrideView等组件。

    (1) getCount():是listView的长度。
    (2) getView(): 根据这个长度逐一绘制它的每一行。
    (3) getItem()和getItemId()则需要处理和取得Adapter中的数据时调用。

    其中,getView()方法的写法如下:

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View view = View.inflate(MainActivity.this, R.layout.item, null);
        TextView tvContent = (TextView) findViewById(R.id.tv_content);
        tvContent.setText(mDatas.get(position));
        return view;
    }  
    

    复用对象

    由于上方的代码每次需要一个View对象都会重新inflate一个view出来,没有实现对象的复用。
    而系统给我们提供convertView,代表的是可复用的对象,当它为空则创建一个对象,否则直接复用。

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View view;
        if(convertView == null){
            view = View.inflate(MainActivity.this, R.layout.item, null);
        }else{
            view = convertView;
        }
        TextView tvContent = (TextView) view.findViewById(R.id.tv_content);
        tvContent.setText(mDatas.get(position));
        return view;
    }
    

    减少查找次数

    当converView为空时,会重新inflate一个View对象,除此之外还会findViewById进行查找工作,我们可以通过一个ViewHolder类来存储对应的

    成员变量,然后通过getTag和setTag来操作,这时,当convertView为空时,只需要取出ViewHolder中存储的成员变量进行复用即可。

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder;
        if(convertView == null){
            convertView = View.inflate(MainActivity.this, R.layout.item, null);
            holder = new ViewHolder();
            holder.mTvContent = (TextView) convertView.findViewById(R.id.tv_content);
            convertView.setTag(holder);
        }else{
            holder = (ViewHolder) convertView.getTag();
        }
        holder.mTvContent.setText(mDatas.get(position));
        return convertView;
    }
    class ViewHolder{
        TextView mTvContent;
    }
    

    ListView多样式

    ListView已结内置实现多种样式的功能,使用步骤如下:

    重写getViewTypeCount() -- 该方法返回多个不同的布局总数。
    重写getItemViewType(int) -- 根据position返回响应的Item。
    根据view item的类型,在getView中创建正确的convertView。

    1、创建MyAdapter继承BaseAdapter,在适配的getItemViewType()中通过计算得出不同的状态,用常量进行标记,其中type必须从0开始,否则会报数组角标越界异常。

    public static final int TYPE_1 = 0;
    public static final int TYPE_2 = 1;
    public static final int TYPE_3 = 2;  
    @Override
    public int getItemViewType(int position) {
        int p = position % 6;
        if(p == 0){
            return TYPE_1;
        }else if (p < 3) {
            return TYPE_2;
        }else if (p < 6) {
            return TYPE_3;
        }else{
            return TYPE_1;
        }
    }
    

    2、在getViewTypeCount()中获取不同布局的种类数

    @Override
    public int getViewTypeCount() {
        return 3;
    } 
    

    3、此时我们需要给定义三个不同的布局,并创建三个不同的ViewHolder来针对不同的布局进行缓存复用

    <TextView
        android:id="@+id/textview1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:color/holo_green_dark"
        android:gravity="center"
        android:text="我是绿色的" />  
    

    其他三个布局都是如此,只是指定的背景颜色不一样,同时定义三个ViewHolder

    class ViewHolder1{
        TextView textView;
    }
    class ViewHolder2{
        TextView textView;
    }
    class ViewHolder3{
        TextView textView;
    }
    

    4、此时我们在getView中来判断常量,进行填充不同的布局以及设置资源等操作

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        
        ViewHolder1 holder1 = null;
        ViewHolder2 holder2 = null;
        ViewHolder3 holder3 = null;
        
        int type = getItemViewType(position);
        if(convertView == null){
            // 按当前所需样式,确定new出的布局
            switch (type) {
            case TYPE_1:
                convertView = View.inflate(MainActivity.this, R.layout.item1, null);
                holder1 = new ViewHolder1();
                holder1.textView = (TextView) convertView.findViewById(R.id.textview1);
                convertView.setTag(holder1);
                break;
            case TYPE_2:
                convertView = View.inflate(MainActivity.this, R.layout.item2, null);
                holder2 = new ViewHolder2();
                holder2.textView = (TextView) convertView.findViewById(R.id.textview2);
                convertView.setTag(holder2);
                break;
            case TYPE_3:
                convertView = View.inflate(MainActivity.this, R.layout.item3, null);
                holder3 = new ViewHolder3();
                holder3.textView = (TextView) convertView.findViewById(R.id.textview3);
                convertView.setTag(holder3);
                break;
            }
        }else{
            switch (type) {
            case TYPE_1:
                holder1 = (ViewHolder1) convertView.getTag();
                break;
            case TYPE_2:
                holder2 = (ViewHolder2) convertView.getTag();
                break;
            case TYPE_3:
                holder3 = (ViewHolder3) convertView.getTag();
                break;
            }
        }
        
        // 根据不同样式设置资源
        switch (type) {
        case TYPE_1:
            holder1.textView.setText("我是绿色"+mDatas.get(position));
            break;
        case TYPE_2:
            holder2.textView.setText("我是蓝色"+mDatas.get(position));
            break;
        case TYPE_3:
            holder3.textView.setText("我是红色"+mDatas.get(position));
            break;
        }
        return convertView;
    } 
    

    ListView的动画

    1、在ListView布局使用layoutAnimation属性引入一个动画文件。

    <ListView 
        android:id="@+id/listview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layoutAnimation="@anim/list_item_animation">
    </ListView>  
    

    2、在anin文件下创建该布局动画文件list_item_animation

    <?xml version="1.0" encoding="utf-8"?>
    <layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
        android:delay="0.2"
        android:animation="@anim/item_animation"
        android:animationOrder="normal"/>  
    

    3、动画文件又引入item_animation文件,放在res/ani目录下,该文件描述动画效果。

    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android">
        <translate 
            android:fromXDelta="100%"
            android:fromYDelta="0"
            android:toXDelta="0"
            android:toYDelta="0"
            android:duration="1000"/>
        <alpha 
            android:fromAlpha="0"
            android:toAlpha="1"
            android:duration="1000"/>
        <rotate 
            android:fromDegrees="0"
            android:toDegrees="360"
            android:pivotX="50%"
            android:pivotY="50%"
            android:duration="1000"/>
    </set>  
    

    4、我们也可以在代码中来设置Item的加载动画

    Animation animation = AnimationUtils.loadAnimation(this, R.anim.item_animation);
    LayoutAnimationController animationController = new LayoutAnimationController(animation);
    animationController.setDelay(0.4f);// 设置间隔时间
    animationController.setOrder(LayoutAnimationController.ORDER_NORMAL);// 设置列表显示顺序
    mListView.setLayoutAnimation(animationController);  
    

    ListView的焦点

    要想获取焦点,需要在Item布局的根节点添加上述属性,android:descendantFocusability="blocksDescendants" 即可,另外该属性有三个可供选择的值:

    beforeDescendants:viewgroup会优先其子类控件而获取到焦点
    afterDescendants:viewgroup只有当其子类控件不需要获取焦点时才获取焦点
    blocksDescendants:viewgroup会覆盖子类控件而直接获得焦点

    例如:ListView的Item条目中的Button事件冲突解决。

    在ItemView配置的xml文件中的根节点添加属性android:descendantFocusability="blocksDescendants"

    在要添加事件的控件上添加android:focusable="false"

    示例代码如下所示:

    <?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="wrap_content"
        android:descendantFocusability="blocksDescendants"
        android:orientation="vertical">
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:focusable="false"
            android:text="Button"/>
    </LinearLayout>
    

    复选框错位问题

    1、首先是activity_maind的代码

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="cn.legend.check.MainActivity" >
        <ListView
            android:id="@+id/listview"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    </RelativeLayout>  
    

    2、在MainActivity中实例化ListView,并模拟数据,设置适配器

    public class MainActivity extends Activity {
        private ListView mListView;
        private MyAdapter mAdapter;
        private ArrayList<String> mDatas;
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mListView = (ListView) findViewById(R.id.listview);
            mDatas = new ArrayList<String>();
            initData();
            mAdapter = new MyAdapter(mDatas, this);
            mListView.setAdapter(mAdapter);
            mListView.setOnItemClickListener(new OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                    ViewHolder holder = (ViewHolder) view.getTag();
                    holder.cb.toggle();
                    // 获取到存储状态的数据集合,每次点击条目则让checkbox状态改变,并且将状态存储到状态集合中
                    MyAdapter.getSelectMaps().put(position, holder.cb.isChecked());
                }
            });
        }
        private void initData() {
            for (int i = 0; i < 100; i++) {
                mDatas.add("data" + "   " + i);
            }
        }
    }  
    

    3、接下来是适配器类的编写,具体思路是用HashMap将状态存储起来,然后防止Checkbox由于复用而错乱

    public class MyAdapter extends BaseAdapter {
        private ArrayList<String> mDatas;
        private static HashMap<Integer, Boolean> selectMaps;
        private Context context;
        public MyAdapter(ArrayList<String> data, Context context) {
            this.context = context;
            this.mDatas = data;
            selectMaps = new HashMap<Integer, Boolean>();
            initData();
        }
        private void initData() {
            for (int i = 0; i < mDatas.size(); i++) {
                getSelectMaps().put(i, false);
            }
        }
        @Override
        public int getCount() {
            return mDatas.size();
        }
        @Override
        public Object getItem(int position) {
            return mDatas.get(position);
        }
        @Override
        public long getItemId(int position) {
            return position;
        }
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder = null;
            if (convertView == null) {
                convertView = View.inflate(context, R.layout.item, null);
                holder = new ViewHolder();
                holder.tv = (TextView) convertView.findViewById(R.id.item_tv);
                holder.cb = (CheckBox) convertView.findViewById(R.id.item_cb);
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder) convertView.getTag();
            }
            holder.tv.setText(mDatas.get(position));
            holder.cb.setChecked(getSelectMaps().get(position));
            return convertView;
        }
        class ViewHolder {
            TextView tv;
            CheckBox cb;
        }
        public static HashMap<Integer, Boolean> getSelectMaps() {
            return selectMaps;
        }
    }     
    

    4、其中使用到的item布局文件

    <?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" >
        <TextView
            android:id="@+id/item_tv"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginBottom="5dp"
            android:layout_marginTop="5dp"
            android:layout_marginLeft="5dp"
            android:layout_weight="1"
            android:gravity="center_vertical" />
        <CheckBox
            android:id="@+id/item_cb"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="5dp"
            android:layout_marginTop="5dp"
            android:layout_marginRight="5dp"
            android:clickable="false"
            android:focusable="false"
            android:focusableInTouchMode="false"
            android:gravity="center_vertical" />
    </LinearLayout>  
    
  • 相关阅读:
    bzoj-4433 小凸玩矩阵(二分图,二分+匈牙利)
    HDU-2255 奔小康赚大钱(二分图、km算法、模板)
    python queue和生产者和消费者模型
    python Events
    python递归锁与信号量
    python 线程锁
    python GIL锁
    python 守护进程
    python 继承式多线程
    python 多线程效果演示
  • 原文地址:https://www.cnblogs.com/pengjingya/p/5509137.html
Copyright © 2011-2022 走看看