本文介绍了Android ListView 实现上拉加载的示例代码,分享给大家,具体如下:
我们先分析一下如何实现 ListView 上拉加载。
- 当我们上拉的时候,会出现一个提示界面,即 ListView 的 Footer 布局。
- ListView 要实现滚动,所以要监听 ListView 滚动事件,即 OnScrollListener() 事件。
- 当我们开始滚动时,Footer 布局才慢慢显示出来,所以需要监听 ListView 的 onTouch() 事件。
实现思路
- 首先判断 ListView 加载时机,当 ListView 的 lastVisibleItem == totalItemCount 时表示当前处于 ListView 最底端,此时允许下拉。
- 自定义一个 FooterView,将 FooterView 添加到 ListView 底部,在上拉时候的显示和完成时候的隐藏。
- 定义一个加载接口,当上拉动作完成时候回调,用于标记状态并加载最新数据进行展示。
1、定义 Footer
Footer 要实现的效果:
第一次上拉时,Footer 逐渐显示,文字显示为下拉可以加载,箭头向上,进度条隐藏。
当松开加载的时候,箭头隐藏,进度条展示,文字改为正在加载。
1、Footer 加载时状态变化
定义一个如上图所示的 Footer 的 XML 文件 footer_layout.xml
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
|
<? xml version = "1.0" encoding = "utf-8" ?> < RelativeLayout xmlns:android = "http://schemas.android.com/apk/res/android" xmlns:tools = "http://schemas.android.com/tools" android:layout_width = "match_parent" android:layout_height = "wrap_content" android:paddingBottom = "10dp" android:paddingTop = "10dp" > < LinearLayout android:id = "@+id/layout" android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:layout_centerHorizontal = "true" android:layout_marginTop = "10dp" android:gravity = "center" android:orientation = "vertical" > < TextView android:id = "@+id/tv_tip" android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:text = "下拉可以刷新" android:textSize = "12sp" /> </ LinearLayout > < ImageView android:id = "@+id/img_arrow" android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:layout_marginRight = "10dp" android:layout_toLeftOf = "@+id/layout" android:src = "@drawable/pull_to_refresh_arrow" /> < ProgressBar android:id = "@+id/progress" style = "@style/progressBar_custom_drawable" android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:layout_centerVertical = "true" android:layout_marginRight = "10dp" android:layout_toLeftOf = "@+id/img_arrow" android:visibility = "gone" tools:visibility = "visible" /> </ RelativeLayout > |
2、初始化布局
定义一个 RefreshListView 类继承 ListView,重写构造函数,并将 Footer 添加到 ListView 中。
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
|
public class RefreshListView extends ListView { private View header; private int headerHeight; //顶部布局高度 private int firstVisibleItem; //当前第一个 Item 可见位置 private float startY; //按下时开始的Y值 private int scrollState; //当前滚动状态 private View footer; private int footerHeight; //底部布局高度 private float lastY; private boolean canLoadMoreEnabled; //是否允许加载更多 public RefreshListView(Context context) { super (context); initView(context); } public RefreshListView(Context context, AttributeSet attrs) { super (context, attrs); initView(context); } public RefreshListView(Context context, AttributeSet attrs, int defStyleAttr) { super (context, attrs, defStyleAttr); initView(context); } private void initView(Context context) { header = LayoutInflater.from(context).inflate(R.layout.header_layout, null ); footer = LayoutInflater.from(context).inflate(R.layout.footer_layout, null ); measureView(header); measureView(footer); //这里获取高度的时候需要先通知父布局header占用的空间 headerHeight = header.getMeasuredHeight(); footerHeight = footer.getMeasuredHeight(); topPadding(-headerHeight); bottomPadding(-footerHeight); //用于隐藏 Footer this .addHeaderView(header); this .addFooterView(footer); this .setOnScrollListener( this ); } /** * 设置 Footer 布局的下边距 * 以隐藏 Footer * @param topPadding */ private void bottomPadding( int bottomPadding) { footer.setPadding(footer.getPaddingLeft(), footer.getPaddingTop(), footer.getPaddingRight(), bottomPadding); footer.invalidate(); } } |
3、实现上拉加载
给 ListView 设置监听
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
|
public class RefreshListView extends ListView implements AbsListView.OnScrollListener { private int firstVisibleItem; //当前第一个 Item 可见位置 private int scrollState; //当前滚动状态 private void initView(Context context) { header = LayoutInflater.from(context).inflate(R.layout.header_layout, null ); footer = LayoutInflater.from(context).inflate(R.layout.footer_layout, null ); measureView(header); measureView(footer); //这里获取高度的时候需要先通知父布局header占用的空间 headerHeight = header.getMeasuredHeight(); footerHeight = footer.getMeasuredHeight(); topPadding(-headerHeight); bottomPadding(-footerHeight); this .addHeaderView(header); this .addFooterView(footer); this .setOnScrollListener( this ); } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { this .scrollState = scrollState; } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { this .firstVisibleItem = firstVisibleItem; this .lastVisibleItem = firstVisibleItem + visibleItemCount; this .totalItemCount = totalItemCount; } } |
加载的时机是判断lastVisibleItem == totalItemCount,而上拉事件我们需要重写 onTouchEvent() 事件,首先定义几个状态。
1
2
3
4
5
6
7
|
private float lastY; private static int state; //当前状态 private final static int NONE = 0 ; //正常状态 private final static int PULL = 1 ; //下拉状态 private final static int RELEASE = 2 ; //释放状态 private final static int REFRESHING = 3 ; //正在刷新状态 |
在 onTouchEvent 中,在 ACTION_DOWN 时,记录最开始的 Y 值,然后在 ACTION_MOVE 事件中实时记录移动距离 space,不断刷新 FooterView 的 bootomPadding,让它跟随滑动距离进行显示,继续滑动,当 space 大于了 FooterHeight 时,状态给为 RELEASE,表示可以释放进行刷新操作。
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
|
@Override public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: //最顶部 if (firstVisibleItem == 0 ) { //刷新 canRefreshEnabled = true ; startY = ev.getY(); } else if (lastVisibleItem == totalItemCount) { //加载更多 canLoadMoreEnabled = true ; lastY = ev.getY(); } break ; case MotionEvent.ACTION_MOVE: onMove(ev); break ; case MotionEvent.ACTION_UP: if (state == RELEASE) { //如果已经释放,则可以提示刷新数据 state = REFRESHING; if (iRefreshListener != null ) { iRefreshListener.onRefresh(); } if (iLoadMoreListener != null ) { iLoadMoreListener.onLoadMore(); } } else if (state == PULL) { //如果是在下拉状态,不刷新数据 state = NONE; } if (canRefreshEnabled) { refreshViewByState(); } if (canLoadMoreEnabled) { loadViewByState(); } canLoadMoreEnabled = false ; canRefreshEnabled = false ; break ; } return super .onTouchEvent(ev); } /** * 判断移动过程中的操作 * * @param ev */ private void onMove(MotionEvent ev) { int tempY = ( int ) ev.getY(); int refreshSpace = ( int ) (tempY - startY); //向下移动的距离 int topPadding = refreshSpace - headerHeight; //在移动过程中不断设置 topPadding int loadSpace = ( int ) (lastY - tempY); //向上移动的距离 int bottomPadding = loadSpace - footerHeight; //在移动过程中不断设置 bottomPadding switch (state) { case NONE: //下拉移动距离大于0 if (refreshSpace > 0 ) { state = PULL; //状态变成下拉状态 refreshViewByState(); } //上拉移动距离大于0 if (loadSpace > 0 ) { state = PULL; //状态变成下拉状态 loadViewByState(); } break ; case PULL: if (canRefreshEnabled) { topPadding(topPadding); //在移动过程中不断设置 topPadding,让 Header 随着下拉动作慢慢显示 } if (canLoadMoreEnabled) { bottomPadding(bottomPadding); //在移动过程中不断设置 bottomPadding,让 Footer 随着上拉动作慢慢显示 } //移动距离大于headerHeight并且正在滚动 if (canRefreshEnabled && refreshSpace > (headerHeight + 30 ) && scrollState == SCROLL_STATE_TOUCH_SCROLL) { state = RELEASE; //提示释放 refreshViewByState(); } //移动距离大于footerHeight并且正在滚动 if (canLoadMoreEnabled && loadSpace > footerHeight + 30 && scrollState == SCROLL_STATE_TOUCH_SCROLL) { state = RELEASE; //提示释放 loadViewByState(); //刷新footer布局 } break ; case RELEASE: if (canRefreshEnabled) { topPadding(topPadding); //移动距离小于headerHeight if (refreshSpace < headerHeight + 30 ) { state = PULL; //提示下拉 } else if (refreshSpace <= 0 ) { state = NONE; } refreshViewByState(); //更新header } if (canLoadMoreEnabled) { bottomPadding(bottomPadding); //移动距离小于footerHeight if (loadSpace < footerHeight + 30 ) { state = PULL; //提示下拉 } else if (loadSpace <= 0 ) { state = NONE; } loadViewByState(); //更新footer } break ; } } |
加载数据的时候,要根据状态不断改变 FooterView 的显示,箭头定义一个旋转动画让其跟随滑动距离实现旋转,进度条也设置了逐帧动画实现自定义进度条。
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
|
private void loadViewByState() { TextView tip = footer.findViewById(R.id.tv_tip); ImageView arrow = footer.findViewById(R.id.img_arrow); ProgressBar progressBar = footer.findViewById(R.id.progress); progressBar.setBackgroundResource(R.drawable.custom_progress_bar); AnimationDrawable animationDrawable = (AnimationDrawable) progressBar.getBackground(); //给箭头设置动画 RotateAnimation anim = new RotateAnimation( 0 , 180 , RotateAnimation.RELATIVE_TO_SELF, 0 .5f, RotateAnimation.RELATIVE_TO_SELF, 0 .5f); RotateAnimation anim1 = new RotateAnimation( 180 , 0 , RotateAnimation.RELATIVE_TO_SELF, 0 .5f, RotateAnimation.RELATIVE_TO_SELF, 0 .5f); anim.setDuration( 200 ); anim.setFillAfter( true ); anim1.setDuration( 200 ); anim1.setFillAfter( true ); switch (state) { case NONE: //正常,footer不显示 bottomPadding(-footerHeight); arrow.clearAnimation(); break ; case PULL: //下拉状态 arrow.setVisibility(VISIBLE); //箭头显示,进度条隐藏 progressBar.setVisibility(GONE); if (animationDrawable.isRunning()) { animationDrawable.stop(); } tip.setText( "上拉可以加载" ); arrow.clearAnimation(); arrow.setAnimation(anim); //箭头向下 break ; case RELEASE: //释放状态 arrow.setVisibility(VISIBLE); //箭头显示,进度条隐藏 progressBar.setVisibility(GONE); if (animationDrawable.isRunning()) { //停止动画播放 animationDrawable.stop(); } tip.setText( "松开开始加载" ); arrow.clearAnimation(); arrow.setAnimation(anim); //箭头向上 break ; case REFRESHING: //刷新状态 bottomPadding( 50 ); arrow.setVisibility(GONE); //箭头显示,进度条隐藏 progressBar.setVisibility(VISIBLE); animationDrawable.start(); tip.setText( "正在加载..." ); arrow.clearAnimation(); break ; } } |
4、下拉刷新完成回调
当上拉加载完成时,我们需要实现数据的刷新,并且要通知 Adapter 刷新数据,这里我们定义一个监听接口实现回调即可。回调在 ACTION_UP 的 RELEASE 状态下进行注册。
1
2
3
4
5
6
7
8
9
|
private ILoadMoreListener iLoadMoreListener; public void setILoadMoreListener(ILoadMoreListener iLoadMoreListener) { this .iLoadMoreListener = iLoadMoreListener; } public interface ILoadMoreListener { void onLoadMore(); } |
5、测试
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
|
package com.dali.refreshandloadmorelistview; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import java.util.ArrayList; public class MainActivity extends Activity implements RefreshListView.IRefreshListener, RefreshListView.ILoadMoreListener { private ArrayList<ApkEntity> apk_list; private ListAdapter adapter; private RefreshListView listView; @Override protected void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); setData(); showList(apk_list); } private void showList(ArrayList<ApkEntity> apk_list) { if (adapter == null ) { listView = (RefreshListView) findViewById(R.id.listview); listView.setIRefreshListener( this ); listView.setILoadMoreListener( this ); adapter = new ListAdapter( this , apk_list); listView.setAdapter(adapter); } else { adapter.onDateChange(apk_list); } } private void setData() { apk_list = new ArrayList<ApkEntity>(); for ( int i = 0 ; i < 10 ; i++) { ApkEntity entity = new ApkEntity(); entity.setName( "默认数据" + i); entity.setDes( "这是一个神奇的应用" ); entity.setInfo( "50w用户" ); apk_list.add(entity); } } private void setRefreshData() { for ( int i = 0 ; i < 2 ; i++) { ApkEntity entity = new ApkEntity(); entity.setName( "默认数据 + 刷新" + i); entity.setDes( "这是一个神奇的应用" ); entity.setInfo( "50w用户" ); apk_list.add( 0 , entity); } } private void setLoadData() { for ( int i = 0 ; i < 2 ; i++) { ApkEntity entity = new ApkEntity(); entity.setName( "默认数据 + 加载" + (adapter.getCount() + i)); entity.setDes( "这是一个神奇的应用" ); entity.setInfo( "50w用户" ); apk_list.add(entity); } } @Override public void onRefresh() { //添加刷新动画效果 Handler handler = new Handler(); handler.postDelayed( new Runnable() { @Override public void run() { //获取最新数据 setRefreshData(); //通知界面显示数据 showList(apk_list); //通知 ListView 刷新完成 listView.refreshComplete(); } }, 2000 ); } @Override public void onLoadMore() { Handler handler = new Handler(); handler.postDelayed( new Runnable() { @Override public void run() { //获取最新数据 setLoadData(); //通知界面显示数据 showList(apk_list); //通知 ListView 刷新完成 listView.loadMoreComplete(); } }, 2000 ); } } |
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。
原文链接:https://www.jianshu.com/p/e8ee83fe1215