ListView
- ListView
- GridView
- ExpandableListView
- 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功能.
- 实现 :
- 更改ListView为ExpandableStickyListHeadersListView
- 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的实现
本文详细介绍了Android中ListView组件的使用方法及优化技巧,包括缓存机制、适配器编写、复用convertView、减少findViewById调用、分页加载数据等,并对比了ListView与其他列表组件如GridView、ExpandableListView的区别。
380

被折叠的 条评论
为什么被折叠?



