Android列表控件(1)

后面的布局文件我不会再写出来了. 多余代码影响阅读性.

介绍下BaseAdapter相对于父类ListAdapter增加的方法. 这些方法都不是必须的

// 判断适配器是否存在item
boolean isEmpty ()

// DropDownView等方法是重写的SpinnerAdapter的. 所以会在讲解Spinner的时候详细讲. ListView和GridView用不到
View getDropDownView (int position,
View convertView,
ViewGroup parent)

boolean hasStableIds ()

void notifyDataSetChanged () // 数据如果发生变化通知ListView局部更新

void notifyDataSetInvalidated () //数据如果发生变化通知ListView整个更新

ArrayAdapter

ArrayAdapter是BaseAdapter的子类, 进行了进一步的封装, 能够快速实现最简单的字符串列表(同时限制了数据只能是单一的字符串). 注意这不是抽象类. 可以直接创建对象.

特点:

  • 只需要构造方法就可以构造出一个ListView出来
  • 自定义很弱, ListView的数据只能是字符串.

创建ArrayAdapter的时候需要指定泛型ArrayAdapter<T>. 泛型决定了构造方法能接受的数据类型

构造方法

ArrayAdapter (Context context, // 上下文
int resource) // 布局id. 只支持根布局是TextView的布局

ArrayAdapter (Context context,
int resource, // 这个构造方法就支持任意布局了
int textViewResourceId) // 指定一个Textview的id来设置数据.

ArrayAdapter (Context context,
int resource,
T[] objects) // 直接在构造方法添加数据, 必须是字符串的数组

ArrayAdapter (Context context,
int resource,
int textViewResourceId,
T[] objects) // 同上

ArrayAdapter (Context context,
int resource,
List objects)// 添加数据集合, 同样必须是字符串

ArrayAdapter (Context context,
int resource,
int textViewResourceId, // 同上
List objects) // 添加数据集合

示例

String[] array = {“用户”, “首页”, “设置”, “关于”, “反馈”};

// 这里的android.R.layout.simple_list_item_1是系统提供的TextView布局文件推荐直接拿来用. 可以点开看下源码.
listView.setAdapter(new ArrayAdapter(this, android.R.layout.simple_list_item_1, array));

看上去是不是很方便就实现了ListView.

注意

  1. ArrayAdapter内部已经对ListView进行了Item复用

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
2. 传入的数据必须是字符串. 源码中进行了判断

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
3. ArrayAdapter并没有进行ViewHolder的复用.

方法

// 添加一个数据
void add (T object)

// 添加多个数据
void addAll (T… items)

// 添加集合
void addAll (Collection<? extends T> collection)

// 删除全部数据
void clear ()

// 删除指定数据
void remove (T object)

// 对应位置插入数据
void insert (T object,
int index)

// 默认为true. 所以每次你对数据修改的时候都会调用notifyOnChange方法. 多次调用影响效率(频繁刷新UI). 可以用该方法设置false.然后自己调用notifyChange等方法来更新Item的UI.
void setNotifyOnChange (boolean notifyOnChange)

// 这是一个静态方法直接创建ListView. 简单直接
ArrayAdapter createFromResource (Context context,
int textArrayResId, // 文本数据
int textViewResId) // 文本控件id

// 得到传入的上下文
Context getContext ()

// 通过数据得到索引
int getPosition (T item)

再介绍两个用于重写支持ListView的筛选Item功能的方法

// 过滤器. 例如联系人的联想筛选
Filter getFilter ()

// 对数据使用标准的比较器排序操作
void sort (Comparator<? super T> comparator)

SimpleAdapter

SimpleAdapter是这三种中最复杂的适配器, 但是数据填充ListView的item很方便. 同样非抽象类可以直接创建对象使用.

同样先介绍构造方法

SimpleAdapter (Context context,
List<? extends Map

示例

创建一个ListView作为数据

// List集合存储Map集合代表数据
List<Map<String, Object>> list = new ArrayList<>();

Map<String, Object> map = new HashMap<>();
map.put(“icon”, R.mipmap.ic_launcher);
map.put(“name”, “设置”);

Map<String, Object> map2 = new HashMap<>();
map2.put(“icon”, R.mipmap.ic_launcher);
map2.put(“name”, “关于”);

list.add(map);
list.add(map2);

// String数据的值是Map集合中的键, 对应int数组中的控件id. 将键对应的值填充到对应的id控件上
listView.setAdapter(new SimpleAdapter(this,list, R.layout.list_item, new String[]{“icon”, “name”}, new int[]{R.id.icon, R.id.name}));

查看源码可以看出来其实SimpleAdapter只支持TextView和Checkable以及ImageView三种控件的属性值设定, 如果非该三种将抛出语法异常信息.

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如果传入ViewBinder接口可以在回调方法内手动处理数据填充

SimpleAdapter.ViewBinder getViewBinder ()
void setViewBinder (SimpleAdapter.ViewBinder viewBinder)

WrapperListAdapter

之前就提过这个适配器和BaseAdapter一样继承自ListAdapter. 不过这是接口. 内部就一个方法:

// 返回适配器对象. 等同于getAdapter
ListAdapter getWrappedAdapter ()

HeaderViewListAdapter

支持头布局和脚布局的ListAdapter

构造方法

// 可以看出主要就是加入两个包含头布局和脚布局的集合外加一个普通ListAdapter即可
HeaderViewListAdapter (ArrayList<ListView.FixedViewInfo> headerViewInfos,
ArrayList<ListView.FixedViewInfo> footerViewInfos,
ListAdapter adapter)

其实ListView支持直接添加头布局和脚布局, 这个之前提过. addHeaderViewaddFootView内部实现就是将原有适配器包裹成HeaderViewListAdapter.

FixedViewInfo

public class FixedViewInfo {
// 视图
public View view;
// 数据
public Object data;
// 是否可选择. Selectable状态
public boolean isSelectable;
}

点击事件

// 普通点击事件
void setOnClickListener (View.OnClickListener l)

// item点击事件
void setOnItemClickListener (AdapterView.OnItemClickListener listener)

// item长按点击事件
void setOnItemLongClickListener (AdapterView.OnItemLongClickListener listener)

// item选择事件
void setOnItemSelectedListener (AdapterView.OnItemSelectedListener listener)

多条目

常常一个列表中不可能条目都是一模一样的, 需要掺杂一些不同类型的Item.

下面介绍两个方法getViewTypeCountgetItemViewType

默认情况下两个方法实现

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们通过重写BaseAdapter中的这两个方法实现多条目

Tip: 如果直接在getView方法中通过position判断的话会导致视图复用机制(convertView)无法正常运行. 会导致视图错乱.

内存优化

划动屏幕时ListView从屏幕消失的条目会被销毁, 而新出现在屏幕的条目会被创建. 如果在大量的且显示内容过多的条目快速划动会造成回收内存的速度赶不上创建条目对象的速度, 而造成oom内存溢出. 为了避免这种情况ListView需要进行内存复用优化.

复用条目

条目在每次被显示在屏幕上的时候都会调用getView()方法, 如果每次在该方法中都创建一个View对象的情况下有大量的条目会导致OOM内存溢出, 所以使用的时候需要复用这个View对象, 而getView方法中已经缓存了这个对象即convertView参数,只需要调用即可.

/**

  • 返回条目显示内容, 就是一个View对象

  • @param position 条目所在位置

  • @param convertView 条目复用对象

  • @param parent

  • @return
    */
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
    View view = null;

// 如果convertView不为空则复用
if (convertView == null) {
view = View.inflate(MainActivity.this, R.layout.list_item, null);
}else {
view = convertView;
}

TextView text = (TextView) view.findViewById(R.id.text);
text.setText(“当前条目” + position);
return view;
}

ViewHolder

每次findViewById都会进行整个布局的遍历, 容易影响程序的运行效率, 所以可以创建ViewHolder类进行控件引用的存储. 然后每个Item对该ViewHolder进行存取操作;

关键方法setTag()

//在外面先定义,ViewHolder静态类
static class ViewHolder {
public ImageView img;
public TextView title;
public TextView info;
}

//然后重写getView
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if(convertView == null)
{
holder = new ViewHolder();

convertView = mInflater.inflate(R.layout.list_item, null);

holder.img = (ImageView)item.findViewById(R.id.img)
holder.title = (TextView)item.findViewById(R.id.title);
holder.info = (TextView)item.findViewById(R.id.info);

convertView.setTag(holder);
}else
{
holder = (ViewHolder)convertView.getTag();
}
holder.img.setImageResource(R.drawable.ic_launcher);
holder.title.setText(“Hello”);
holder.info.setText(“World”);
}

return convertView;
}

RecyclerView都是强制使用ViewHolder;

选择模式

ListView其实支持多种选择模式(ChoiceModel)的设置

/**

  • 没有选择模式
    /
    public static final int CHOICE_MODE_NONE = 0;
    /
    *
  • 单选模式
    /
    public static final int CHOICE_MODE_SINGLE = 1;
    /
    *
  • 多选模式
    /
    public static final int CHOICE_MODE_MULTIPLE = 2;
    /
    *
  • The list allows multiple choices in a modal selection mode
    */
    public static final int CHOICE_MODE_MULTIPLE_MODAL = 3;

通过布局属性设置选择模式

android:choiceMode

ConstantValueDescription
multipleChoice2多选模式
multipleChoiceModal3屏蔽点击事件的多选模式
none0非选模式
singleChoice1单选模式

关于选择模式的方法都在AbsListView抽象类中

boolean isItemChecked (int position)
// 判断指定位置的item是否被选中

void setItemChecked (int position,
boolean value)
// 设置item选中

int getCheckedItemCount ()
// 被选中的item数量

long[] getCheckedItemIds ()
// 如果非非选模式并且(hasStableIds() == true) 才有效

int getCheckedItemPosition ()
// 选择的位置索引(只在单选模式有效)

SparseBooleanArray getCheckedItemPositions ()
// 得到被选中的所有item的位置

void clearChoices ()
// 清理所有被选择

int getChoiceMode ()
// 选择模式

void setChoiceMode (int choiceMode)
// 设置选择模式

void setMultiChoiceModeListener (AbsListView.MultiChoiceModeListener listener)
// 多选监听器

选择模式生效必须满足以下两点:

  1. 设置选择模式(默认情况是单选模式)

  2. Item没有子控件拦截焦点

android:focusable=“false”

屏蔽点击事件的多选模式

该模式下要想进入多选模式有以下两种方式:

  1. 长按item
  2. 通过函数调用setItemChecked

GridView

翻译即"网格视图"的意思. 和ListView的区别就是网格列表.

但是注意GridView并不是水平滑动布局, 只不过是增加列数的ListView(即满足列数限制就换行, 默认一行即ListView). 需要水平滑动布局可以使用HorizontalScrollView以及RecyclerView的GridLayoutManager布局

GridView兼容ListView的所有适配器.

布局属性

每个属性都有对应的方法. 详细介绍看方法.

属性描述
android:columnWidth列宽(默认无效)需要配合拉伸模式使用
android:numColumns列数(默认为1)
android:stretchMode拉伸模式
android:horizontalSpacing水平间距
android:verticalSpacing垂直间距
android:gravity对齐方式

示例

用法和ListView一样. 支持之前讲过的所有的ListAdapter适配器;

mList.setAdapter(new ArrayAdapter(this, android.R.layout.simple_list_item_1, title));

mList.setNumColumns(2); // 设置列数. 默认1. 即和ListView没区别

列宽和间距

GridView就像一个网格列表. 学会如何排列网格是非常重要的.

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

上图标注了GridView的列宽以及水平和垂直间距

网格宽度

// 格子宽度. 像素单位
int getColumnWidth ()
void setColumnWidth (int columnWidth)

Tip: 如果在item的布局中设置了固定大小, 会导致裁剪效果.

拉伸模式

拉伸模式我认为是GridView最重要也是最难理解的地方. 注意理解我的分析

// 设置拉伸模式
void setStretchMode (int stretchMode)

支持四种拉伸模式

  1. NO_STRETCH

不拉伸, 尺寸自己控制. 该模式下必须设置网格宽度否则什么都不显示

  1. STRETCH_COLUMN_WIDTH (拉伸列宽)

默认模式, 列宽由屏幕拉伸决定. 所以之前介绍的setColumnWidth无效. 网格内容会根据屏幕的大小来比例缩放控制

  1. STRETCH_SPACING (拉伸间距)

该模式必须制定列宽否则不显示, 同时自己指定的间距(包括水平间距和垂直间距)无效 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. STRETCH_SPACING_UNIFORM

此模式下列宽和间距都是有效设置值, 并且水平方向最左边也会有间距.

但是由于都是有效值所以无法做到屏幕均布的效果

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

除了STRETCH_COLUMN_WIDTH其他模式都需要指定网格宽度(setColumnWidth).

总结:

  1. 拉伸间距或者列宽就无法设置其值.
  2. 如果列宽和间距都非拉伸模式就无法均布
  3. 如果不是拉伸列宽的情况下就必须制定列宽值否则不显示内容.

间距

// 设置水平间隔
int getHorizontalSpacing ()
void setHorizontalSpacing (int horizontalSpacing)

// 设置垂直间隔
void setVerticalSpacing (int verticalSpacing)
int getVerticalSpacing ()

列数

void setNumColumns (int numColumns)
int getNumColumns ()

对齐方式

int getGravity ()
void setGravity (int gravity)

方法介绍

// 返回适配器
ListAdapter getAdapter ()

列表嵌套

这里介绍GridView和ListView之间或者两种相同列表的相互嵌套.

列表嵌套的问题分为两种情况:

  1. 显示不完整
  2. 被嵌套的列表无法滑动

第一种

如果存在列表嵌套了一个高度为wrap_content|match_parent的列表时会发现被嵌套的列表无法完全显示, 但是如果固定的高度就不会发生这种情况, 但是很多数据都并不是固定的而是通过数据的数量动态加载.

通过自定义onMeasure方法给被嵌套的ListView一个无限的高度最大值

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}

integer的最大值是32位, 之所以右移两位是因为在MeasureSpec中前两位表示模式

// 移位位数 30 private static final int MODE_SHIFT = 30;

// int 型占 32 位,左移 30 位,该属性表示掩码值,用来与 size 和 mode 进行 “&” 运算,获取对应值。
private static final int MODE_MASK = 0x3 << MODE_SHIFT;

// 左移 30 位,其值为 00…(此处省略 30 个0)
public static final int UNSPECIFIED = 0 << MODE_SHIFT;

// 左移 30 位,其值为 01…(此处省略 30 个0)
public static final int EXACTLY = 1 << MODE_SHIFT;

// 左移 30 位,其值为 10…(此处省略 30 个0)
public static final int AT_MOST = 2 << MODE_SHIFT;

如果不想重写就通过ListView的Item数量来动态的设置ListView的固定高度

private void setListViewHeight(ListView listView) {

ListAdapter listAdapter = listView.getAdapter();

if (listAdapter == null) {
return;
}

int totalHeight = 0;

for (int i = 0; i < listAdapter.getCount(); i++) {

View listItem = listAdapter.getView(i, null, listView);
listItem.measure(0, 0);
totalHeight += listItem.getMeasuredHeight();
}

ViewGroup.LayoutParams params = listView.getLayoutParams();
params.height = totalHeight

  • (listView.getDividerHeight() * (listAdapter.getCount() - 1));

listView.setLayoutParams(params);

}

或者你可以在getView方法中设置每个View对象的layoutParams

第二种

上面介绍的两种方法只是针对ListView被嵌套时不显示的问题. 但是如果ListView里面嵌套的要是一个可滑动的ListView就需要另外解决了.

其实Google已经考虑到这种问题了, 提供方法可以直接生效

ViewCompat.setNestedScrollingEnabled(mList,true); // api21以上可以直接使用View而不是ViewCompat

关于NestedScrollView以及ScrollView中嵌套ListView或GridView不会出现第二种情况, 但是也会出现显示不完全. 同样解决方法.

对于RecyclerView中嵌套GridView和ListView第二种方法的解决办法就失效了.

ExpandableListView

ExpandableListView 是支持分组展开的ListView.

主要分为两部分:组和子列表

Attributes

指示器图标

android:groupIndicator

android:childIndicator

指示器间隔

android:indicatorEnd
android:indicatorLeft
android:indicatorRight
android:indicatorStart

android:childIndicatorEnd
android:childIndicatorLeft
android:childIndicatorRight
android:childIndicatorStart

指示器图标会和你的getView()视图内容重叠. 建议给item设置一个padding

子列表分割线

android:childDivider

分割线可以是图片或者颜色. 但是无论如何都是一个高度为1dp的全屏宽度的分割线. 且必须适配器isChildSelectable()方法返回true才会显示.

去除默认的指示器和分割线

android:divider=“@null”
android:groupIndicator=“@null”

ExpandableListAdapter

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

ExpandaleListAdapter属于接口. 一般情况直接使用其子类.

abstract boolean areAllItemsEnabled()

// 该方法用于适配器返回视图数据
abstract Object getChild(int groupPosition, int childPosition)

// 子列表item id
abstract long getChildId(int groupPosition, int childPosition)

// 子列表item 视图
abstract View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent)

// 子列表
abstract int getChildrenCount(int groupPosition)

abstract long getCombinedChildId(long groupId, long childId)

abstract long getCombinedGroupId(long groupId)

// 组对象
abstract Object getGroup(int groupPosition)

// 组数量
abstract int getGroupCount()

// 组id
abstract long getGroupId(int groupPosition)

// 返回组视图
abstract View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent)

// id是否稳定, 该方法等同于ListView
abstract boolean hasStableIds()

// 子列表是否可选
abstract boolean isChildSelectable(int groupPosition, int childPosition)

abstract boolean isEmpty()

// 组折叠回调
abstract void onGroupCollapsed(int groupPosition)

// 组展开回调
abstract void onGroupExpanded(int groupPosition)

BaseExpandableListAdapter

类似BaseAdapter, 需要实现的方法上面已经介绍过了. 下面直接示例;

public class CustomExpandableListAdapter extends BaseExpandableListAdapter {

private Context mContext;
private List mGroupData;
private List<ArrayList> mChildData;

public CustomExpandableListAdapter(Context context, List groupData, List<ArrayList> childData) {
mContext = context;
mGroupData = groupData;
mChildData = childData;
}

@Override public int getGroupCount() {
return mGroupData.size();
}

@Override public int getChildrenCount(int i) {
return mChildData.size();
}

@Override public Object getGroup(int i) {
return mGroupData.get(i);
}

@Override public Object getChild(int i, int i1) {
return mChildData.get(i).get(i1);
}

最后

在这里我和身边一些朋友特意整理了一份快速进阶为Android高级工程师的系统且全面的学习资料。涵盖了Android初级——Android高级架构师进阶必备的一些学习技能。

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

}

@Override public int getChildrenCount(int i) {
return mChildData.size();
}

@Override public Object getGroup(int i) {
return mGroupData.get(i);
}

@Override public Object getChild(int i, int i1) {
return mChildData.get(i).get(i1);
}

最后

在这里我和身边一些朋友特意整理了一份快速进阶为Android高级工程师的系统且全面的学习资料。涵盖了Android初级——Android高级架构师进阶必备的一些学习技能。

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

[外链图片转存中…(img-rLoboWjI-1714364634660)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值