当用户从网络上读取微薄的时候,如果一下子全部加载用户未读的微薄这将耗费比较长的时间,造成不好的用户体验,同时一屏的内容也不足以显示如此多的内容。这时候,我们就需要用到另一个功能,那就是listview的分页了。通过分页分次加载数据,用户看多少就去加载多少。
通常这也分为两种方式,一种是设置一个按钮,用户点击即加载。另一种是当用户滑动到底部时自动加载。今天我就和大家分享一下这个功能的实现。
首先,写一个xml文件,moredata.xml,该文件即定义了放在listview底部的视图:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<?xml version= "1.0" encoding= "utf-8" ?>
<linearlayout xmlns:android= "http://schemas.android.com/apk/res/android"
android:layout_width= "match_parent"
android:layout_height= "match_parent"
android:orientation= "vertical" >
<button
android:id= "@+id/bt_load"
android:layout_width= "fill_parent"
android:layout_height= "wrap_content"
android:text= "加载更多数据" />
<progressbar
android:id= "@+id/pg"
android:layout_width= "wrap_content"
android:layout_height= "wrap_content"
android:layout_gravity= "center_horizontal"
android:visibility= "gone"
/>
</linearlayout>
|
可以看到是一个按钮和一个进度条。因为只做一个演示,这里简单处理,通过设置控件的visibility,未加载时显示按钮,加载时就显示进度条。
写一个item.xml,大家应该很熟悉了。用来定义listview的每个item的视图。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<?xml version= "1.0" encoding= "utf-8" ?>
<linearlayout xmlns:android= "http://schemas.android.com/apk/res/android"
android:layout_width= "match_parent"
android:layout_height= "match_parent"
android:orientation= "vertical" >
<textview
android:id= "@+id/tv_title"
android:textsize= "20sp"
android:layout_width= "wrap_content"
android:layout_height= "wrap_content"
android:layout_margintop= "5dp"
/>
<textview
android:textsize= "12sp"
android:id= "@+id/tv_content"
android:layout_width= "wrap_content"
android:layout_height= "wrap_content"
android:layout_margintop= "5dp"
/>
</linearlayout>
|
main.xml就不贴了,整个主界面就一个listview。
直接先看下activity的代码,在里面实现分页效果。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
|
package com.notice.moredate;
import java.util.arraylist;
import java.util.hashmap;
import android.app.activity;
import android.os.bundle;
import android.os.handler;
import android.view.view;
import android.view.view.onclicklistener;
import android.widget.abslistview;
import android.widget.abslistview.onscrolllistener;
import android.widget.button;
import android.widget.listview;
import android.widget.progressbar;
import android.widget.simpleadapter;
import android.widget.toast;
public class moredatelistactivity extends activity implements onscrolllistener {
// listview的adapter
private simpleadapter msimpleadapter;
private listview lv;
private button bt;
private progressbar pg;
private arraylist<hashmap<string,string>> list;
// listview底部view
private view moreview;
private handler handler;
// 设置一个最大的数据条数,超过即不再加载
private int maxdatenum;
// 最后可见条目的索引
private int lastvisibleindex;
/** called when the activity is first created. */
@override
public void oncreate(bundle savedinstancestate) {
super .oncreate(savedinstancestate);
setcontentview(r.layout.main);
maxdatenum = 22 ; // 设置最大数据条数
lv = (listview) findviewbyid(r.id.lv);
// 实例化底部布局
moreview = getlayoutinflater().inflate(r.layout.moredate, null );
bt = (button) moreview.findviewbyid(r.id.bt_load);
pg = (progressbar) moreview.findviewbyid(r.id.pg);
handler = new handler();
// 用map来装载数据,初始化10条数据
list = new arraylist<hashmap<string,string>>();
for ( int i = 0 ; i < 10 ; i++) {
hashmap<string, string> map = new hashmap<string, string>();
map.put( "itemtitle" , "第" + i + "行标题" );
map.put( "itemtext" , "第" + i + "行内容" );
list.add(map);
}
// 实例化simpleadapter
msimpleadapter = new simpleadapter( this , list, r.layout.item,
new string[] { "itemtitle" , "itemtext" },
new int [] { r.id.tv_title, r.id.tv_content });
// 加上底部view,注意要放在setadapter方法前
lv.addfooterview(moreview);
lv.setadapter(msimpleadapter);
// 绑定监听器
lv.setonscrolllistener( this );
bt.setonclicklistener( new onclicklistener() {
@override
public void onclick(view v) {
pg.setvisibility(view.visible); // 将进度条可见
bt.setvisibility(view.gone); // 按钮不可见
handler.postdelayed( new runnable() {
@override
public void run() {
loadmoredate(); // 加载更多数据
bt.setvisibility(view.visible);
pg.setvisibility(view.gone);
msimpleadapter.notifydatasetchanged(); // 通知listview刷新数据
}
}, 2000 );
}
});
}
private void loadmoredate() {
int count = msimpleadapter.getcount();
if (count + 5 < maxdatenum) {
// 每次加载5条
for ( int i = count; i < count + 5 ; i++) {
hashmap<string, string> map = new hashmap<string, string>();
map.put( "itemtitle" , "新增第" + i + "行标题" );
map.put( "itemtext" , "新增第" + i + "行内容" );
list.add(map);
}
} else {
// 数据已经不足5条
for ( int i = count; i < maxdatenum; i++) {
hashmap<string, string> map = new hashmap<string, string>();
map.put( "itemtitle" , "新增第" + i + "行标题" );
map.put( "itemtext" , "新增第" + i + "行内容" );
list.add(map);
}
}
}
@override
public void onscroll(abslistview view, int firstvisibleitem,
int visibleitemcount, int totalitemcount) {
// 计算最后可见条目的索引
lastvisibleindex = firstvisibleitem + visibleitemcount - 1 ;
// 所有的条目已经和最大条数相等,则移除底部的view
if (totalitemcount == maxdatenum + 1 ) {
lv.removefooterview(moreview);
toast.maketext( this , "数据全部加载完成,没有更多数据!" , toast.length_long).show();
}
}
@override
public void onscrollstatechanged(abslistview view, int scrollstate) {
// 滑到底部后自动加载,判断listview已经停止滚动并且最后可视的条目等于adapter的条目
if (scrollstate == onscrolllistener.scroll_state_idle
&& lastvisibleindex == msimpleadapter.getcount()) {
// 当滑到底部时自动加载
// pg.setvisibility(view.visible);
// bt.setvisibility(view.gone);
// handler.postdelayed(new runnable() {
//
// @override
// public void run() {
// loadmoredate();
// bt.setvisibility(view.visible);
// pg.setvisibility(view.gone);
// msimpleadapter.notifydatasetchanged();
// }
//
// }, 2000);
}
}
}
|
通过注释,大家应该很容易理解了。这里做下简单的解析。首先要注意的是,addfootview方法一定要在setadapter方法之前,否则会无效。addfootview方法为listview底部加入一个视图,在本例中就是那个button加progressbar的视图。当用户点击按钮时,调用loadmoredate方法,为listview绑定更多的数据,通过adapter的notifydatasetchanged方法通知listview刷新,显示刚加入的数据。
这里用handler异步延迟2秒操作,模仿加载过程。同时listview绑定了onscrolllistener监听器,并且实现了onscroll和onscrollstatechanged方法。在后者方法中,我们通过判断listview已经停止滚动并且最后可视的条目等于adapter的条目,可以知道用户已经滑动到底部并且自动加载,代码中将这部分代码注释掉了,大家可以自己试下。
代码中还加入了一个maxdatenum变量,用来记录最大的数据数量。也就是说网络或者其他地方一共的数据。通过onscroll方法判断用户加载完这些数据后,移除listview底部视图,不让继续加载。同时在loadmoredate方法中也对最大数据量做相应的操作来判断加载数量。(默认加载5条,不足5条时加载剩余的)。
看下效果图:
关于onscrolllistener事件顺序次数的简要分析
在 android 的 onscrolllistener 整个事件我主要分析下他的执行顺序:
实现滚动事件的监听接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
new abslistview.onscrolllistener(){
@override
public void onscrollstatechanged(abslistview abslistview, int scrollstate)
{
switch (scrollstate) {
case abslistview.onscrolllistener.scroll_state_touch_scroll:
// 手指触屏拉动准备滚动,只触发一次 顺序: 2
break ;
case abslistview.onscrolllistener.scroll_state_fling:
// 持续滚动开始,只触发一次 顺序: 4
break ;
case abslistview.onscrolllistener.scroll_state_idle:
// 整个滚动事件结束,只触发一次 顺序: 6
break ;
default :
break ;
}
}
@override
public void onscroll(abslistview abslistview, int i, int i1, int i2)
{
// 一直在滚动中,多次触发 顺序: 1、3、5
}
}
|
之前一直很迷糊,后来仔细测试后得出上面代码注释中的结论。
另外对于 listview 图片列表的滚动,应该在scroll_state_fling时让图片不显示,提高滚动性能让滚动更平滑,scroll_state_idle时显示当前屏幕可见的图片,对于onscroll()接口方法基本上不用他。
补充:
1. 当手指只轻触屏幕不拉动只会触发一次onscroll方法,不会触发其他滚动事件;
2. 如果手指触碰屏幕后停滞一下再滑动则首先执行一次onscroll方法 然后才是scroll_state_touch_scroll事件;
3. 如果手指碰到屏幕后直接滑动则第一次就执行scroll_state_touch_scroll事件;
3. 触发scroll_state_touch_scroll事件后还会继续多次触发onscroll事件,而不是直接触发scroll_state_fling事件;
4. 滚动后不一定会触发 scroll_state_fling事件,可能和手指滑动的距离有关系;
5. 滚动的过程中会多次调用onscroll方法;
6. 除了onscroll触发多次,其他事件在整个过程中只会触发一次。