ListView

本文详细介绍了Android中ListView组件的使用方法及优化技巧,包括缓存机制、适配器编写、复用convertView、减少findViewById调用、分页加载数据等,并对比了ListView与其他列表组件如GridView、ExpandableListView的区别。

ListView

  1. ListView
  2. GridView
  3. ExpandableListView
  4. StickyListHeaders
01-ListView ####s

1. ListView的理解

convertView来自缓存池,缓存池由ListView维护,缓存池中的数据来自getView方法的返回值, 也就是说getView方法的返回值用完后并没有“浪费”,而是被系统放到ListView的缓存池里了. 缓存池的大小=屏幕显示的条目数+1,当滑动屏幕时,被隐藏的项会作为缓存对象,作为getView的参数传递进来。 只需修改此缓存对象的数据,就可以直接使用,而不需要再重新new一个新的对象,节省了内存,防止内存溢出。

2. ListView的使用

1. 定义一个实现BaseAdapter的适配器
2. 传入适配器

class ContactAdapter extends BaseAdapter {
//返回listView的条目数
  @Override
  public int getCount() {
    return contactList != null ? contactList.size() : 0;//避免空指针异常
  }
 //当显示ListView条目是会条用该方法。
  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
    View view = null;
    if (convertView == null) {
      LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
      view = inflater.inflate(R.layout.list_item_contact, null);
    } else {
      view = convertView;//复用convertView对象
    }
    Contact contact = contactList.get(position);
    //此处一定要从view对象中去找控件的ID号.否则会空指针异常
    TextView tv_name = (TextView) view.findViewById(R.id.tv_name);
    TextView tv_age = (TextView) view.findViewById(R.id.tv_age);
    TextView tv_phone = (TextView) view.findViewById(R.id.tv_phone);
    ImageView iv_avatar = (ImageView) view.findViewById(R.id.iv_avatar);
    switch (position % 10) {
    case 0:
      iv_avatar.setImageResource(R.drawable.iv0);//设置图片资源
      break;
    case 1:
      iv_avatar.setImageResource(R.drawable.iv1);
      break;
    //.......
    default:
      break;
    }
    tv_name.setText(contact.getName());
    tv_phone.setText(contact.getPhone());
    tv_age.setText(contact.getAge() + "");//此处一定要转换成字符串,有个重载的资源ID的方法
    return view;
  }
  @Override
  public Contact getItem(int position) {
    return contactList.get(position);
  }
  @Override
  public long getItemId(int position) {
    return position;
  }
}
ListView lv_contact = (ListView) findViewById(R.id.lv_contact);
lv_contact.setAdapter(new ContactAdapter());

3. 布局填充器


1. View view = View.inflate(MainActivity.this,R.layout.list_item_contact, null);
2. View view = LayoutInflater.from(MainActivity.this).inflate(R.layout.list_item_contact, null);
3. LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.list_item_contact, null);//通过获取系统服务
4. View inflate = LayoutInflater.from(this).inflate(R.layout.pb, ll_content, false);
第三个参数是true返回ViewGrop对象,false返回子类对象,第二种方式的重载方法

4. ListView的优化

1. 复用getView()方法的convertView属性

public View getView(int position, View convertView, ViewGroup parent) {
    if (convertView == null) {
        convertView = new TextView(MainActivity.this);
    } 
    TextView view = (TextView) convertView;
    view.setText("草莓");
    view.setTextColor(Color.RED);
    return convertView;//此处可以返回view,不影响
}

public View getView(int position, View convertView, ViewGroup parent) {
      // TODO Auto-generated method stub
    if (convertView == null) {
        convertView = new TextView(MainActivity.this);
    } 
    TextView view = (TextView) convertView;
    view.setText("草莓");
    view.setTextColor(Color.RED);
    return convertView;//此处可以返回view,不影响
}

2. 减少对findViewById ()递归方法的调用,创建视图的缓存类

* 定义一个静态类,该类的成员变量跟ListView 条目中用到的控件类型一致,但是报错,未找到原因.静态内部类只会加载一次.
* 目的是为了用该类将ListView 中的控件封装起来,
* 然后将该类跟ListView 的条目绑定起来
* 目的是为了减少findViewById 方法被调用次数
class ViewHolder {//定义一个类,该类用来存储在缓存池中
  TextView name;
  TextView age;
  TextView phone;
  ImageView avatar;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
  ViewHolder holder;
  if (convertView == null) {
    convertView = View.inflate(MainActivity.this, R.layout.list_item_contact, null);
    //创建屏幕显示条数+1条对象,放在缓存池中.这样子就减少了findViewById()调用的次数
    holder = new ViewHolder();
    holder.name = (TextView) convertView.findViewById(R.id.tv_name);
    holder.age = (TextView) convertView.findViewById(R.id.tv_age);
    holder.phone = (TextView) convertView.findViewById(R.id.tv_phone);
    holder.avatar = (ImageView) convertView.findViewById(R.id.iv_avatar);
    convertView.setTag(holder);//将封装好的holder 对象作为convertView 的属性,
  }
  //从convertView 中获取ViewHolder 对象
  holder = (ViewHolder) convertView.getTag();
  Contact contact = contactList.get(position);

  switch (position % 10) {
  case 0:
    holder.avatar.setImageResource(R.drawable.iv0);
    break;
  case 1:
    holder.avatar.setImageResource(R.drawable.iv1);
    break;
  case 2:
    holder.avatar.setImageResource(R.drawable.iv2);
    break;
  default:
    break;
  }
  holder.name.setText(contact.getName());
  holder.phone.setText(contact.getPhone());
  holder.age.setText(contact.getAge() + "");
  return convertView;
}

* 3. 分页加载数据*

01 - 数据库的分页查询

  • 需求 : 一次查询数据库中的一部分数据,而不是全部数据
  • 实现 : SQL语句 : select * from blacklist limit 20 offset 1
  • 代码 :

    /**
     * DESC 返回分页查询的数据: . <br/>
     * @param pageSize : 返回数据的条数
     * @param offSet : 开始查询位置
     */
    public ArrayList<BlackBean> getPart(int pageSize, int offSet) {
        ArrayList<BlackBean> list = new ArrayList<BlackBean>();
        BlackDBHelper helper = new BlackDBHelper(context);
        SQLiteDatabase db = helper.getReadableDatabase();
        Cursor cursor = db.rawQuery("select " + BlackDB.TABLE_BLACK.COL_NUM + ","
                + BlackDB.TABLE_BLACK.COL_TYPE + " from " + BlackDB.TABLE_BLACK.NAME + " limit "
                + pageSize + " offset " + offSet, null);
        if (cursor != null) {
            while (cursor.moveToNext()) {
                String num = cursor.getString(0);
                int type = cursor.getInt(1);
                BlackBean bean = new BlackBean();
                bean.num = num;
                bean.type = type;
                list.add(bean);
            }
        }
        cursor.close();
        db.close();
        return list;
    }
    

02- 加载更多的实现

  • 需求 : 分页加载后, 滑动到列表界面的底部时, 自动加载更多数据并显示,页眉页脚的展示
  • 实现 : 为listview增加setOnScrollListener监听
  • 代码 :

    private void initEvent() { 
    adapter = new BlackAdapter();
    // 为ListView添加一个底部视图
    // addFooterView 和 addHeaderView 方法一定要在setAdapter之前调用
    lv.addFooterView(loading);
    lv.setAdapter(adapter);
    // 为ListView设置一个空View
    lv.setEmptyView(ivEmpty);
    lv.setOnItemClickListener(this);
    // ListView滚动事件的监听
    lv.setOnScrollListener(this);
}
   public void onItemClick(AdapterView<?> parent, View view, int position,
        long id) {
    // 当前被选中的条目对应的JavaBean
    BlackBean bean = datas.get(position);

    Intent intent = new Intent(this, ManageBlackActivity.class);
    // 指定当前的操作的类型
    intent.putExtra(KEY_ACTION, ACTION_UPDATE);
    intent.setAction(ACTION_UPDATE);
    // 把当前条目的号码和类型,传递给下一个界面
    intent.putExtra(ManageBlackActivity.KEY_NUM, bean.num);
    intent.putExtra(ManageBlackActivity.KEY_TYPE, bean.type);
    // 被修改的数据的脚标
    intent.putExtra(ManageBlackActivity.KEY_POSITION, position);
    startActivityForResult(intent, REQ_CODE_UPDATE_BLACK);
}

// 状态发生改变时的监听
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {}

// 标志位:记录当前是否正在加载新的数据
private boolean isLoading = false;

// 标志位:记录当前是否有新的数据
private boolean isHasMore = true;
// 加载中的视图
private View loading;
// 加载中是否已经显示了
private boolean isLoadingMoreisShowing = true;

// 滚动过程中的监听
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
        int visibleItemCount, final int totalItemCount) {
    if (isLoading) {
        // 如果正在加载新数据,就直接中断
        return;
    }
    if (!isHasMore) {
        // 如果没有新数据了,就直接中断
        return;
    }
    // 滚动到ListView的底部了,加载新数据
    //lv.getLastVisiblePosition() == list.size()也可以这样判断
    if ((firstVisibleItem + visibleItemCount) == totalItemCount) {
        // 加载中视图没有显示的时候,再添加加载中
        if (!isLoadingMoreisShowing) {
            lv.addFooterView(loading);
            isLoadingMoreisShowing = true;
        }

        // 显示进度条
        pb.setVisibility(View.VISIBLE);
        // 更改标志位
        isLoading = true;
        new Thread(new Runnable() {
            public void run() {
                SystemClock.sleep(1000);// 20
                // 查出来的新的数据
                ArrayList<BlackBean> newData = BlackDbDAO.getPageData(
                        InterceptActivity.this, pageSize, datas.size());
                // 如果查出来的新数据总条数 < 我想获取的总数据条数, 就代表没有数据了
                if (newData.size() < pageSize) {
                    isHasMore = false;
                }

                // 把新的数据添加到集合
                datas.addAll(newData);
                runOnUiThread(new Runnable() {
                    public void run() {
                        // 更改标志位
                        isLoading = false;
                        // 隐藏进度条
                        pb.setVisibility(View.GONE);
                        // 数据加载完成了,再移除加载中视图
                        lv.removeFooterView(loading);
                        // 更改标志位
                        isLoadingMoreisShowing = false;
                        adapter.notifyDataSetChanged();
                    }
                });
            }
        }).start();

5. 常见Adapter

*1. ArrayAdapter 显示的单列数据:数组或者List 集合


ListView listView = (ListView) findViewById(R.id.listview);
String[] datas = new String[]{"zhangsan","lisi","wangwu","zhaoliu"};
/*
* 第一个参数是:上下文
* 第二个参数是:布局文件的id,这里使用Android 系统提供的简单布局
* 第三个参数是:要显示的数据,数组或者List 集合都行
*/
ArrayAdapter<String> arrayAdapter =new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, datas );
listView.setAdapter(arrayAdapter);

*2. SimpleAdapter:用于显示双列数据:


HashMap<String, String> map1 = new HashMap<String, String>();
map1.put("name", "张三");
map1.put("phone", "111111");
HashMap<String, String> map2 = new HashMap<String, String>();
map2.put("name", "李四");
map2.put("phone", "222222");
HashMap<String, String> map3 = new HashMap<String, String>();
map3.put("name", "王五");
map3.put("phone", "333333");
ArrayList<Map<String,String>> list = new ArrayList<Map<String,String>>();
list.add(map1);
list.add(map2);
list.add(map3);
ListView view = (ListView) findViewById(R.id.lv);
SimpleAdapter adapter = new SimpleAdapter(this, list,
    R.layout.simple_latout, new String[] { "name", "phone" },
    new int[] { R.id.tv1, R.id.tv2 });
view.setAdapter(adapter);

*3. CurcorAdapter


  • 需求 : 将Adapter改为继承自CursorAdapter
  • CursorAdapter使用的场景:
    • 仅使用数据库存储的数据
    • 数据量比较大
  • CursorAdapter的特点:
    • 不依赖list集合,而是依赖游标cursor
    • 可以做到边读取边查询,一般只读取一个页面的数据,占用非常小的内存
  • CursorAdapter的使用
    • 构造函数中必须传入查询要用到的Cursor
    • 必须实现两个方法
      • newView(),该方法和使用BaseAdapter一样,主要是用来构造Item视图
      • bindView(),绑定数据时使用
  • CursorAdapter使用的注意事项:
    • CursorAdapter的查询必须带有主键,即_ID列()
    • 在bindView方法中的游标cursor不需要移动,方法提供的cursor已经移动到条目对应的位置
    • 方法中的cursor不需要close
    • 如果要实现条目的点击事件,需要先调用cursor.moveToPosition(position)方法,此时cursor将移动到对应的条目,之后即可获取数据
  • 更改Cursor查询的排序
    • 正序和倒序 ” asc” : ” desc”
    • 之后调用adapter.changeCursor(cursor)方法

    // 1. 获取Cursor
    public static Cursor getAllContactsCursor(Context context, boolean order) {
        ContentResolver cr = context.getContentResolver();
        // 查询联系人号码的URI
        Uri uri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
        // 返回的数据的列
        String[] projection =
                new String[] {Phone._ID, Phone.DISPLAY_NAME, Phone.NUMBER, Phone.CONTACT_ID};
        String selection = null;
        String[] selectionArgs = null;
        String sortOrder = Phone.DISPLAY_NAME + (order ? " asc " : " desc ");
        Cursor cursor = cr.query(uri, projection, selection, selectionArgs, sortOrder);
        return cursor;
    }

    // 2. Adapter
    public class SelectNumCursorAdapter extends CursorAdapter {
        public SelectNumCursorAdapter(Context context, Cursor c) {
            super(context, c);
        }
        @Override
        public View newView(Context context, Cursor cursor, ViewGroup parent) {
            return View.inflate(context, R.layout.item_select_safe_num, null);
        }
        @Override
        public void bindView(View view, Context context, Cursor cursor) {
            ImageView icon = (ImageView) view.findViewById(R.id.item_select_safe_num_iv_icon);
            TextView name = (TextView) view.findViewById(R.id.item_select_safe_num_tv_name);
            TextView num = (TextView) view.findViewById(R.id.item_select_safe_num_tv_num);
            PhoneNumEntry entry = ContactDAO.getPhoneNumEntry(cursor);
            Bitmap bitmap = ContactDAO.getPhoto(context, entry.contactID);
            if (bitmap != null) {
                icon.setImageBitmap(bitmap);
            } else {
                icon.setImageResource(R.drawable.ic_launcher);
            }
            name.setText(entry.name);
            num.setText(entry.num);
        }
    }

    // 通知Adapter游标发生了改变
    adapter.changeCursor(cursor);

6. 相关属性

1.给ListView的条目设置
    - setOnItemClickListener点击监听事件
2. 监听ListView 的滚动状态
    - setOnScrollListener()
3. 让ListView 定位到某一位置
    - listview.setSelection(position);
4. 平滑滚动,但位置不准确
    - listview.smoothScrollToPosition(position);
5. ListView动态更新
    - adapter.notifyDataSetChanged();

7. 注意事项

1.ListLiew的height只能设置为match_parent.android:layout_height="match_parent"
//设置为"wrap_content",getView()会调用多次

2.
/*
* ViewGroup:如果不为null,则将R.layout.list_item转换为View对象的时候会考虑其宽和高属性值
* 如果为null,则不管R.layout.list_item布局文件的宽和高怎么写,都没效果
*/
View view2 = LayoutInflater.from(this).inflate(R.layout.list_item, ll_content, false);


3. 空View的添加
* 需求 : 为ListView添加一个空视图, 在没有数据的时候展示出来
* 代码 : 

// 界面上添加一个视图
<ImageView
    android:id="@+id/act_intercept_iv_empty"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:src="@drawable/empty"
    android:visibility="gone" />
// 在listview加载完数据后, 设置一个空view
    lv.setAdapter(adapter);
    lv.setEmptyView(iv_empty);
02-GridView
  • 需求 : 美化GridView的样式, 增加item的点击效果, 去掉黄色背景
  • 实现 : GridView的使用和ListView一样,点击效果使用selector来实现
  • 代码 :

    // GridView的优化
    <GridView
        android:id="@+id/act_main_grid"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="5dp"
        android:cacheColorHint="@android:color/transparent"  // 2.3 机型适配,去掉黄色
        android:horizontalSpacing="2dp" //指定Item之间的水平间距
        android:listSelector="@android:color/transparent"   // 去掉选中时的黄色背景
        android:numColumns="2"  //   指定列数
        android:verticalSpacing="2dp" >  // 指定Item之间的垂直间距
    </GridView>
    
    // Item的点击背景,设置为条目的点击背景
    <?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:drawable="@drawable/item_main_pressed" android:state_pressed="true"/>
        <item android:drawable="@drawable/item_main_normal"/>
    </selector>
    
03-ExpandableListView
  • 需求 : 实现ListView的嵌套
  • 实现 :

    • ListView 中显示 ListView, 这种视图叫 ExpandableListView
    • 实现步骤类似与ListView. 要定义对应的Adapter .
  • 代码 :

    //定义常用号码Adapter中一级目录的JavaBean
    public class FrequentlyGroupBean {
    
        public String groupTitle;
        public List<FrequentlyChildBean> childs;
    
    }
    //定义常用号码Adapter中二级目录的JavaBean
    public class FrequentlyChildBean {
        public String childName;
        public String ChildNum;
    }
    //常用号码中的Adapter
    public class FrequentlyNumAdapter extends BaseExpandableListAdapter {
        private Context context;
        private ArrayList<FrequentlyGroupBean> groupList;
        public FrequentlyNumAdapter(Context context, ArrayList<FrequentlyGroupBean> groupList) {
            this.context = context;
            this.groupList = groupList;
        }
        // 返回一级目录的个数
        @Override
        public int getGroupCount() {
            return groupList.size();
        }
        // 返回二级目录的个数
        @Override
        public int getChildrenCount(int groupPosition) {
            return groupList.get(groupPosition).childs.size();
        }
        @Override
        public FrequentlyGroupBean getGroup(int groupPosition) {
            return groupList.get(groupPosition);
        }
        @Override
        public FrequentlyChildBean getChild(int groupPosition, int childPosition) {
            return groupList.get(groupPosition).childs.get(childPosition);
        }
        @Override
        public long getGroupId(int groupPosition) {
            return groupPosition;
        }
        @Override
        public long getChildId(int groupPosition, int childPosition) {
            return childPosition;
        }
        @Override
        public boolean hasStableIds() {
            return false;
        }
        @Override
        public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
                ViewGroup parent) {
            GroupViewHolder groupViewHolder;
            if (convertView == null) {
                groupViewHolder = new GroupViewHolder();
                convertView = View.inflate(context, R.layout.item_frequently_num_group, null);
                groupViewHolder.groupTitle =
                        (TextView) convertView.findViewById(R.id.item_frequently_num_parent_tv);
                convertView.setTag(groupViewHolder);
            } else {
                groupViewHolder = (GroupViewHolder) convertView.getTag();
            }
            groupViewHolder.groupTitle.setText(groupList.get(groupPosition).groupTitle);
            return convertView;
        }
    
        @Override
        public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
                View convertView, ViewGroup parent) {
            ChildViewHolder childViewHolder;
            if (convertView == null) {
                childViewHolder = new ChildViewHolder();
                convertView = View.inflate(context, R.layout.item_frequently_num_child, null);
                childViewHolder.childName =
                        (TextView) convertView.findViewById(R.id.item_frequently_num_child_tv_name);
                childViewHolder.childNum =
                        (TextView) convertView.findViewById(R.id.item_frequently_num_child_tv_num);
                convertView.setTag(childViewHolder);
            } else {
                childViewHolder = (ChildViewHolder) convertView.getTag();
            }
            FrequentlyChildBean bean = groupList.get(groupPosition).childs.get(childPosition);
            childViewHolder.childName.setText(bean.childName);
            childViewHolder.childNum.setText(bean.ChildNum);
            return convertView;
        }
        @Override
        public boolean isChildSelectable(int groupPosition, int childPosition) {
        //要实现子类的可以响应点击事件,应返回true
            return false;
        }
        class GroupViewHolder {
            TextView groupTitle;
        }
        class ChildViewHolder {
            TextView childName;
            TextView childNum;
        }
    }
    
  • 需求 : –实现条目子父类的点击事件–
  • 实现 :

    • 实现ExpandableListView的点击事件(默认情况下,childView是不可点击的, 需要将Adapter的isChildSelectable() 方法返回值改为true)
    • 实现ExpandableListView仅有一个Group可被点击展开
  • 代码 :

    ExpandableListView  elv = (ExpandableListView) findViewById(R.id.activity_common_num_elv);
    adapter = new CommonNumAdapter();
    elv.setAdapter(adapter);
    //实现点击事件
    elv.setOnGroupClickListener(this);
    elv.setOnChildClickListener(this);
    
    // 记录, 之前被点开的Group的position;
    private int lastPosition = -1;
    @Override
    public boolean onGroupClick(ExpandableListView parent, View v,
            int groupPosition, long id) {
        // 没有任何一个条目被打开,点击某一个就打开某一个
        if (lastPosition == -1) {
            // 展开
            elv.expandGroup(groupPosition);
            lastPosition = groupPosition;
        } else {
            // 有一个被打开了, 点击已经打开的, 就关闭
            if (lastPosition == groupPosition) {
                // 折叠
                elv.collapseGroup(groupPosition);
                lastPosition = -1;
            } else {
                // 有一个被打开了, 点击其他的, 就关闭之前的,打开新的
                elv.collapseGroup(lastPosition);
                elv.expandGroup(groupPosition);
                lastPosition = groupPosition;
            }
    
        }
        // 自己处理了点击事件, 就要返回true
        return true;
    }
    
    @Override
    public boolean onChildClick(ExpandableListView parent, View v,
            int groupPosition, int childPosition, long id) {
        CommonNumChild bean = adapter.getChild(groupPosition, childPosition);
    
        Intent intent = new Intent();
        intent.setAction(Intent.ACTION_DIAL);
        intent.setData(Uri.parse("tel:" + bean.num));
        startActivity(intent);
    
        return false;
    }
    
04-StickyListHeaders
  • 需求 : 使用StickHeaderListView实现Header功能.
  • 实现 :
    1. 更改ListView为ExpandableStickyListHeadersListView
    2. Adapter实现StickyListHeadersAdapter接口, 并实现对应的方法
  • 代码 :

    public View getHeaderView(int position, View convertView, ViewGroup parent) {
        HeaderViewHolder holder;
        if (convertView == null) {
            holder = new HeaderViewHolder();
            convertView = View.inflate(context, R.layout.item_header, null);
            holder.header = (TextView) convertView.findViewById(R.id.item_header_tv);
            convertView.setTag(holder);
        } else {
            holder = (HeaderViewHolder) convertView.getTag();
        }
        AppRAMBean appRAMBean = datas.get(position);
        if (appRAMBean.isSystem) {
            holder.header.setText("系统进程" + sysSize + "个");
        } else {
            holder.header.setText("用户进程" + userSize + "个");
        }
        return convertView;
    }
    //用于区分不同的条目组的.
    public long getHeaderId(int position) {
        AppRAMBean appRAMBean = datas.get(position);
        return appRAMBean.isSystem ? 0 : 1;
    }
    

注:使用的Adapter的实现

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值