本文对 ListView 中的一些常用技巧做一个总结。附:虽然现在 RecyclerView 已逐渐取代 ListView,但实际情况是大部分项目中还在使用 ListView。当然,后续我会在我的博客中详细介绍 RecyclerView,敬请期待。
前言
ListView 作为 Android 中常用的列表控件,用以向用户展示列表信息。可以说,我们开发的项目中 listView 随处可见,也是 Android 程序员面试时,最常被问的一个知识点,下面我就将 listView 中的常用技巧一一罗列出来。
listView 的缓存机制以及 ViewHolder 的使用
这两个知识点是最常用的优化 listView 的技巧。
1.首先,来说说 listView 的缓存机制,再这之前,我们先看看下面这张图:
我先来说一下这张图的意思,从左边第一张图开始,当我们的手机上显示一个 listView 列表的时候,如图有 7 个 item,当我们向上拖动 listView,进入第二个示意图,注意细节,第二个图例表示的是,当向上拖动时,这是 item00 正准备消失在手机屏幕上但还没有完全消失时,item07 出现在我们的视野内,这时,item07 也只是显示一半,这种情况下,item07 还不是从缓存中复用的 item,而是重新 new 的一个 View.我们继续向上拖动,到了第三个图例,这时 item00 已经完全消失在屏幕上,这时系统不会销毁这 item00,而是将其放到了缓存中,那么当我们继续向上滑动时,我们就可以利用 listView 的 缓存机制,复用缓存中 item,用它显示 item08,而不是再次的重新 new 一个.
2.通过 缓存机制 和 ViewHolder 配合使用
知道了 缓存机制,我们还需要知道 ViewHolder,这时 Google 大会上推荐我们使用的 listView 优化方案。
我们知道,我们通常写的视图 xml 文件,实际上是一个 DOM 树结构,而 findViewById()
实际上是遍历整个视图树,当你的 listView 中的 item 非常复杂时,findViewById()
所需的时间就越长,这是非常耗性能的。
1 |
static class { TextView title; |
我们可以在第一次创建 listView 中的 item 的同时,创建一个 ViewHolder,将需要遍历的视图保存在其中,通过 View.setTag()
保存在缓存中,这样下次重用的时候就避免的再次 findViewById()
的操作了。
注意:这样做虽然消耗了一些内存,但是与
findViewById()
遍历所需的时间相比,显得微不足道。(据测试,使用 ViewHolder,效率提高 50%)
3.示例代码
1 |
public View getView(int position, View convertView, ViewGroup parent) { |
设置 listView 中的分割线
通过 android:divider
和 android:dividerHeight
这两个属性,我们可以设置设置分割线。另外,除了为分割线设置颜色以外,我们还可以为其设置图片资源:
1 |
android:divider="@drawable:split_pic" |
隐藏 listView 的滚动条
我们在拖动 listView 时,默认情况下是有滚动条的,我们可以通过设置 scrollbars
属性来控制滚动条的状态。
1 |
android:scrollbars="none" |
设置为 none
,则不会出现滚动条。
取消 listView 的 item 的点击效果
listView 默认下,但我们点击其中的 item 时,会有点击效果。通过设置 listSelector
属性来取消点击效果。
1 |
android:listSelector="#00000000" |
你还可以使用 Android 自带的透明色来实现这个效果:
1 |
android:listSelector="@android:color/transparent" |
设置 listView 需要显示在其中某一项
listView 默认情况下是显示在第一项的,有时候,需求是显示在指定项,那么要如何实现呢?
代码控制:
1 |
listView.setSelection(position) |
通过制定 position,来设置需要显示在第几项。但是,这种滑动操作是瞬间完成的,有时候,我们的需求是 平滑 的滑动到某个位置,通过下面的代码可以实现这种效果:
1 |
mListView.smoothScrollBy(distance, duration); |
动态修改 listView
listView 中的数据通常情况下,在做了某种操作后,需要动态更新,如何实现这种功能呢?
通过更新 listView 中的数据源,一般情况下都是一个 List<Object>
,我们通过向 list 中添加数据,然后调用 mAdapter.notifyDataSetChanged()
方法,就能实现 listView 的动态更新了。
1 |
mData.add("info"); |
遍历 listView 中的 item
通过 getChildAt()
来获取某个 View:
1 |
for (int i = 0; i< mListView.getChildCount(); i++=) { |
交互,当 listView 为空时
当我们 listView 加载数据为空时,列表就是空白一片,这一点对于用户体验是不好的,应该给用户以提示。
1 |
<?xml version="1.0" encoding="utf-8"?> |
这里,我们使用一张 数据为空 的图片,当 listView 为空时,就显示这张 数据为空 的图片,通过以下代码可以达到这种效果:
1 |
ListView listView = (ListView) findViewById(R.id.listview); |
listView 滑动监听
滑动监听是 listView 非常重要的技巧,我们通常需要监听不同的事件,来做不同的逻辑处理。通常情况下,我们还需要借助 GestureDetector 手势识别,VelocityTracker 滑动速度监测等。比如说,下拉刷新,快速滑动不异步加载图片等等。
下面我们介绍两种 listView 滑动事件的方法:
1.OnTouchListener
OnTouchListener 是 View 中的监听事件,我们可以用来监听 ACTION_DOWN, ACTION_MOVE, ACTION_UP 这三个事件发生的坐标,从而得知用户滑动的方向,做相应的逻辑处理,代码如下:
1 |
mListView.setOnTouchListener(new View.OnTouchListener() { public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { |
2.OnScrollListener
OnScrollListener 是 AbsListView 中定义的监听事件,它封装了很多与 ListView 相关的信息。我们先看看 OnScrollListener 的一般使用方法:
1 |
mListView.setOnScrollListener(new AbsListView.OnScrollListener() { public void onScrollStateChanged(AbsListView view, int scrollState) { |
看上面的代码,我们知道 OnScrollListener 中定义了两个回调方法,分别是:
- 1.
onScrollStateChanged()
- 2.
onScroll()
先说说 onScrollStateChanged()
,我们可以看到参数 scrollState
对应三种模式:
- 1.
OnScrollListener.SCROLL_STATE_IDLE
: 滚动停止时; - 2.
OnScrollListener.SCROLL_STATE_TOUCH_SCROLL
: 正在滚动时; - 3.
OnScrollListener.SCROLL_STATE_FLING
: 用力滑动。
注意,但我们没有用力滑动时,这个方法只会调用 2 次,否则调用 3 次,差别就是 OnScrollListener.SCROLL_STATE_FLING
这个模式。
再来看看 onScroll()
这个回调,它会在 listView 滚动时一直调用,方法中的 3 个参数准确的显示了当前 listView 的滚动的状态:
- 1.
firstVisibleItem
: 当前能够看见的第一个 item 的 ID; - 2.
visibleItemCount
: 当前能看见的 item 的总数; - 3.
totalItemCount
: 整个 listView 中 item 的总数;
通过这几个参数,我们能很方便的判断出是否滚动到了最后一行,代码如下:
1 |
if (firstVisibleItem + visibleItemCount == totalItemCount && totalItemCount > 0) { |
我们还可以通过代码来判断滚动的方向:
1 |
if (firstVisibleItem > lastVisibleItemPosition) { |
另外,listView 还提供了一些封装的方法来获取当前可视的 item 的相关信息:
1 |
// 获取屏幕区域内的第一个 item 的 id |
本文参考: