Android列表控件(1),系列教学

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);
}

@Override public long getGroupId(int i) {
return i;
}

@Override public long getChildId(int i, int i1) {
return i+i1;
}

@Override public boolean hasStableIds() {
return true;
}

@Override public View getGroupView(int i, boolean b, View view, ViewGroup viewGroup) {

if(view == null) {
}

View groupView = LayoutInflater.from(mContext).inflate(R.layout.item_group_text, viewGroup, false);
TextView tvTitle = ButterKnife.findById(groupView, R.id.tv_title);
tvTitle.setText(mGroupData.get(i));
return groupView;
}

@Override public View getChildView(int i, int i1, boolean b, View view, ViewGroup viewGroup) {
View childView = LayoutInflater.from(mContext).inflate(R.layout.item_group_text, viewGroup, false);
TextView tvTitle = ButterKnife.findById(childView, R.id.tv_title);
tvTitle.setText(mChildData.get(i).get(i1));
return childView;
}

@Override public boolean isChildSelectable(int i, int i1) {
return true;
}
}

SimpleExpandableListAdapter

SimpleExpandableListAdapter和SimpleAdapter差不多不属于抽象类, 只需要使用构造方法创建实例即可.

SimpleExpandableListAdapter (Context context,
List<? extends Map

SimpleExpandableListAdapter (Context context,
List<? extends Map

SimpleExpandableListAdapter (Context context,
List<? extends Map

从构造方法可以看到SimpleExpandableListAdapter还支持两种固定的多类型布局. 不过需要注意的是多类型布局的groupTo/childTo控件id还是必须包括在内的.

监听器

子列表点击事件

void setOnChildClickListener (ExpandableListView.OnChildClickListener onChildClickListener)

组点击事件

void setOnGroupClickListener (ExpandableListView.OnGroupClickListener onGroupClickListener)

组收缩和展开事件

void setOnGroupCollapseListener (ExpandableListView.OnGroupCollapseListener onGroupCollapseListener)

void setOnGroupExpandListener (ExpandableListView.OnGroupExpandListener onGroupExpandListener)

取消组的折叠和收缩只需要在组点击事件的回调中返回true即可

mExpand.setOnGroupClickListener(new ExpandableListView.OnGroupClickListener() {
@Override
public boolean onGroupClick(ExpandableListView expandableListView, View view, int i,
long l) {
return true;
}
});

Spinner

虽然Spinner是容器布局不过并不支持子控件.因为其继承了AdapterView;

简单实现

布局中创建控件

values/strings 创建数组实体

Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune

Attributes

// 水平和垂直偏移. 只有垂直是有效的
android:dropDownHorizontalOffset

android:dropDownVerticalOffset

android:dropDownWidth // 下拉弹窗宽度

android:dropDownSelector // 下拉颜色选择器

android:gravity

android:popupBackground // 下拉弹窗背景颜色

android:spinnerMode // 对话框/下拉弹窗

android:prompt // 对话框模式的标题, 注意需要引用string

选择监听

mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { } @Override public void onNothingSelected(AdapterView<?> parent) {
}
});
如果只是想使用下拉列表可以看看ListPopupWindow; 单纯的下拉列表, 提供依附功能和自定义宽高;

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后看一下学习需要的所有知识点的思维导图。在刚刚那份学习笔记里包含了下面知识点所有内容!文章里已经展示了部分!如果你正愁这块不知道如何学习或者想提升学习这块知识的学习效率,那么这份学习笔记绝对是你的秘密武器!

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

,而且极易碰到天花板技术停滞不前!**

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-jnuHkYmN-1712420133950)]

[外链图片转存中…(img-Ki2AoA5e-1712420133950)]

[外链图片转存中…(img-crayi2Vo-1712420133950)]

[外链图片转存中…(img-sn6B9Vag-1712420133950)]

[外链图片转存中…(img-M4aaljj8-1712420133951)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后看一下学习需要的所有知识点的思维导图。在刚刚那份学习笔记里包含了下面知识点所有内容!文章里已经展示了部分!如果你正愁这块不知道如何学习或者想提升学习这块知识的学习效率,那么这份学习笔记绝对是你的秘密武器!

[外链图片转存中…(img-CTiyKgWQ-1712420133951)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值