Android列表控件

ListAdapter属于接口, 一般情况并不直接使用, 因为没必要重写全部方法. 一般使用其子类.

// 是否启用item. 如果fasle则不启用. item处于无法选择和点击的状态
boolean isEnabled(int position);

// 可以看到没有position参数. 所以如果返回fasle则全部item都处于不启用状态
public boolean areAllItemsEnabled();

继承父类的方法

int getCount () // 决定ListView的Item数量

Object getItem (int position) // 得到item 数据. 这里返回的值会在ListView中使用到

long getItemId (int position) // 得到item 的 id. 这里返回的值会在ListView中使用到

// 返回Item类型, 类型是否相同决定是否复用item
int getItemViewType (int position)

// 返回Item视图内容
View getView (int position, // 位置
View convertView, // 复用视图
ViewGroup parent) // 父容器

// 返回Item类型数量
int getViewTypeCount ()

// id是否唯一
boolean hasStableIds ()

boolean isEmpty () // 是否为空

void registerDataSetObserver (DataSetObserver observer) // 注册数据观察者

void unregisterDataSetObserver (DataSetObserver observer) // 取消数据观察者

是否唯一

hasStableIds()这个方法是判断id是否是有效. 返回true有效false无效.

  • 有效的情况下会通过getItemId()的返回id值来判断item是否是相同
  • 无效的情况下会默认使用item的position来当作id

BaseAdapter

首先我讲讲最常用适配器 BaseAdapter.

特点:

  • ListView支持高度自定义的Item

  • 需要自己重写该适配器来使用

重写方法

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

// 这是写了个匿名类
mListView.setAdapter(new BaseAdapter() {
/**

  • 控制ListView的Item的数量
  • @return
    */
    @Override
    public int getCount() {
    return title.length;
    }

/**

  • 控制ListView的某些方法返回的Object数据.
  • 例如ListView的getItemAtPosition()方法. 通过位置索引得到数据对象, 即该方法返回的Object对象
  • @param position
  • @return
    */
    @Override
    public Object getItem(int position) {
    return null;
    }

/**

  • 每次点击item都会回调该方法. 同样是为了ListView的getItemIdAtPosition()方法能够得到item的id
  • @param position
  • @return
    */
    @Override
    public long getItemId(int position) {
    return 0;
    }

/**

  • 控制ListView的Item的视图显示
  • @param position 当前显示的视图位置
  • @param convertView 缓存的视图. 用于复用item
  • @param parent 父容器布局
  • @return
    */
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

View view = View.inflate(MainActivity.this, R.layout.item_list, null);

// 根据传入的数据进行修改
TextView text = ButterKnife.findById(view, R.id.text);
text.setText(title[position]);

return view;
}
});

在主布局中添加控件

注意inflate item 视图的时候是否启用parent.

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

介绍下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属于接口. 一般情况直接使用其子类.

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

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

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

img

img

img

img

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

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

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

总结

算法知识点繁多,企业考察的题目千变万化,面对越来越近的“金九银十”,我给大家准备好了一套比较完善的学习方法,希望能帮助大家在有限的时间里尽可能系统快速的恶补算法,通过高效的学习来提高大家面试中算法模块的通过率。

这一套学习资料既有文字档也有视频,里面不仅仅有关键知识点的整理,还有案例的算法相关部分的讲解,可以帮助大家更好更全面的进行学习,二者搭配起来学习效果会更好。

部分资料展示:




有了这套学习资料,坚持刷题一周,你就会发现自己的算法知识体系有明显的完善,离大厂Offer的距离更加近。

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

[外链图片转存中…(img-kIYAubt3-1712549897615)]

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

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

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

总结

算法知识点繁多,企业考察的题目千变万化,面对越来越近的“金九银十”,我给大家准备好了一套比较完善的学习方法,希望能帮助大家在有限的时间里尽可能系统快速的恶补算法,通过高效的学习来提高大家面试中算法模块的通过率。

这一套学习资料既有文字档也有视频,里面不仅仅有关键知识点的整理,还有案例的算法相关部分的讲解,可以帮助大家更好更全面的进行学习,二者搭配起来学习效果会更好。

部分资料展示:

[外链图片转存中…(img-TEanN4Yh-1712549897616)]
[外链图片转存中…(img-cygdncMo-1712549897616)]
[外链图片转存中…(img-9W5jtAno-1712549897616)]
[外链图片转存中…(img-mOEwLTDS-1712549897616)]

有了这套学习资料,坚持刷题一周,你就会发现自己的算法知识体系有明显的完善,离大厂Offer的距离更加近。

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

  • 14
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值