Android 自定义 ListView 上下拉动“刷新最新”和“加载更多”歌曲列表
本文内容
- 环境
- 测试数据
- 项目结构
- 演示
- 参考资料
本文演示,上拉刷新最新的歌曲列表,和下拉加载更多的歌曲列表。所谓“刷新最新”和“加载更多”是指日期。演示代码太多,点击此处下载,自己调试一下。
下载 Demo
环境
- Windows 2008 R2 64 位
- Eclipse ADT V22.6.2,Android 4.4.3
- SAMSUNG GT-I9008L,Android OS 2.2.2
测试数据
本演示的歌曲信息,共有 20 条,包括歌手名、歌曲名、时长、缩略图,为了简单起见都存在本地,你当然也可以通过网络获得。同时,当下拉刷新最新或上拉加载更多时,为了简单,20 条歌曲信息是取模运算循环使用的。
package com.my.android.app.data;
import com.my.android.app.R;
public class TestData {
public static String[] title = new String[] { "Someone Like You",
"Space Bound", "Stranger In Moscow", "Love The Way You Lie",
"Khwaja Mere Khwaja", "All My Days", "Life For Rent",
"Love To See You Cry", "The Good, The Bad And The Ugly",
"Show me the meaning", "Someone Like You", "Space Bound",
"Stranger In Moscow", "Love The Way You Lie", "Khwaja Mere Khwaja",
"All My Days", "Life For Rent", "Love To See You Cry",
"The Good, The Bad And The Ugly", "Show me the meaning" };
public static String[] artist = new String[] { "Adele", "Eminem",
"Michael Jackson", "Rihanna", "A R Rehman", "Alexi Murdoch",
"Dido", "Enrique Iglesias", "Ennio Morricone", "Backstreet Boys",
"Adele", "Eminem", "Michael Jackson", "Rihanna", "A R Rehman",
"Alexi Murdoch", "Dido", "Enrique Iglesias", "Ennio Morricone",
"Backstreet Boys" };
public static int[] thumb = new int[] { R.drawable.adele,
R.drawable.eminem, R.drawable.mj, R.drawable.rihanna,
R.drawable.arrehman, R.drawable.alexi_murdoch, R.drawable.dido,
R.drawable.enrique, R.drawable.ennio, R.drawable.backstreet_boys,
R.drawable.adele, R.drawable.eminem, R.drawable.mj,
R.drawable.rihanna, R.drawable.arrehman, R.drawable.alexi_murdoch,
R.drawable.dido, R.drawable.enrique, R.drawable.ennio,
R.drawable.backstreet_boys };
public static String[] duration = new String[] { "4:47", "4:38", "5:44",
"4:23", "6:58", "4:47", "3:41", "4:07", "2:42", "3:56", "4:47",
"4:38", "5:44", "4:23", "6:58", "4:47", "3:41", "4:07", "2:42",
"3:56" };
}
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
项目结构

图 1 项目结构
- com.my.android.app.activit 包,是主程序和三个演示的界面后台;
- com.my.android.app.activit.data 包,是测试数据;
- com.my.android.app.activit.view 包,是实现下拉上拉“刷新最新”和“加载更多”。
演示
本程序主界面,如图 2 所示。包含三个演示,“example 1:Simple Push and Pull”是上拉下拉刷新最新和加载更多的演示,刷新或加载的内容只是当前时间而已;“example 2:Custom Music List”是显示歌曲列表。
当点击第三个按钮“Combine example 1 and 2”时,就是本文想要达到的效果,如图 3 所示。
- 初始状态为图 3 左边,加载 20 个音乐;
- 当用手指向下拉动时,获得最新的 10 个,如图 3 中间所示;
- 当向上拉动时,加载之前的 10 个,如图 3 右边所示。

图 2 主程序

图 3 本文想要达到的效果:下拉或上拉,刷新最新或加载更多的歌曲列表
刚开始,若想实现这个功能,着实不易,主要是没思路。疑问在于,如何实现下拉和上拉?如何显示歌曲列表?又如何刷新最近、加载更多的歌曲列表?可你要是研究一下本程序前面的两个演示:example 1 和 example 2,这两个示例在网上很容易找到,那么,以上所有的问题,都迎刃而解了。

图 4 简单的下拉或上拉,刷新最新或加载更多
图 4 演示的核心代码如下所示:
package com.my.android.app.activity;
import java.util.ArrayList;
import java.util.Date;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
//import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.RelativeLayout;
import android.widget.Toast;
import com.my.android.app.R;
import com.my.android.app.view.PushPullList;
import com.my.android.app.view.PushPullListListener;
@SuppressLint("HandlerLeak")
public class PushPullListTest extends Activity implements PushPullListListener {
PushPullList list;
ArrayList<String> data;
ArrayAdapter<String> adapter;
// 刷新控件状态
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == 0) { // 刷新最新
adapter.notifyDataSetChanged();
list.doneRefresh();
Toast.makeText(PushPullListTest.this,
"新加载" + msg.arg1 + "条数据!", Toast.LENGTH_LONG).show();
} else if (msg.what == 1) { // 加载更多
adapter.notifyDataSetChanged();
list.doneMore();
} else {
super.handleMessage(msg);
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pushpulllist);
// 添加自定义控件
list = new PushPullList(this);
RelativeLayout root = (RelativeLayout) findViewById(R.id.root_a);
root.addView(list);
data = new ArrayList<String>();
for (int i = 1; i < 10; ++i) {
data.add(new Date().toLocaleString());
}
adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_expandable_list_item_1, data);
list.setAdapter(adapter);
list.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
long arg3) {
Toast.makeText(PushPullListTest.this, data.get(arg2 - 1),
Toast.LENGTH_LONG).show();
// Log.i("", data.get(arg2 - 1));
}
});
list.setDoMoreWhenBottom(false); // 滚动到低端的时候不自己加载更多
list.setOnRefreshListener(this); // 刷新最新 的监听
list.setOnMoreListener(this); // 加载更多 的监听
}
@Override
public boolean onRefreshOrMore(PushPullList dynamicListView,
boolean isRefresh) {
if (isRefresh) {
new Thread(r_refresh).start();
} else {
new Thread(r_more).start();
}
return false;
}
// 刷新最新,插入前边
Runnable r_refresh = new Runnable() {
@Override
public void run() {
// 下拉,刷新最新
ArrayList<String> temp = new ArrayList<String>();
for (int i = 0; i < 3; ++i) {
temp.add(0, new Date().toLocaleString());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (data) {
data.addAll(0, temp);
}
Message message = new Message();
message.what = 0;
message.arg1 = temp.size();
handler.sendMessage(message);
}
};
// 加载更多,插入末尾
Runnable r_more = new Runnable() {
@Override
public void run() {
// 上拉,加载更多
ArrayList<String> temp = new ArrayList<String>();
for (int i = 0; i < 3; ++i) {
temp.add(new Date().toLocaleString());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (data) {
data.addAll(temp);
}
handler.sendEmptyMessage(1);
}
};
}
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
图 4 主要是自定义了一个 Listview,并继承 android.widget.AbsListView.OnScrollListener 接口,至于如何定义 PushPullListListener 和 PushPullList,请自行下载源代码,这样就能实现上拉或下拉功能。通过上面代码,你就能了解基本的运行情况。
onCreate 方法,创建一个自定义 ListView——PushPullList,并添加到页面,然后创建一个 Adapter,用 list.setAdapter 设置该 Adaper; onRefreshOrMore 方法,是分别利用两个线程,下拉或上拉后,刷新最新或加载更多。

图 5 歌曲列表
图 5 核心代码如下所示:
package com.my.android.app.activity;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.Toast;
import com.my.android.app.data.TestData;
import com.my.android.app.R;
public class CustomListTest extends Activity {
ListView list;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_customlist);
// 创建一个List集合,List集合的元素是Map
List<Map<String, Object>> listItems = new ArrayList<Map<String, Object>>();
for (int i = 0; i < TestData.artist.length; i++) {
Map<String, Object> listItem = new HashMap<String, Object>();
listItem.put("thumb", TestData.thumb[i]);
listItem.put("artist", TestData.artist[i]);
listItem.put("title", TestData.title[i]);
listItem.put("duration", TestData.duration[i]);
listItems.add(listItem);
}
// 创建一个SimpleAdapter
SimpleAdapter simpleAdapter = new SimpleAdapter(this, listItems,
R.layout.list_row, new String[] { "artist", "title", "thumb",
"duration" }, new int[] { R.id.artist, R.id.title,
R.id.imagethumb, R.id.duration });
list = (ListView) findViewById(R.id.mylist_a);
// 为ListView设置Adapter
list.setAdapter(simpleAdapter);
// 为ListView的列表项单击事件绑定事件监听器
list.setOnItemClickListener(new OnItemClickListener() {
// 第position项被单击时激发该方法。
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
Toast.makeText(getApplicationContext(),
TestData.artist[position] + "被单击了", Toast.LENGTH_SHORT)
.show();
}
});
list.setOnItemSelectedListener(new OnItemSelectedListener() {
// 第position项被选中时激发该方法。
@Override
public void onItemSelected(AdapterView<?> parent, View view,
int position, long id) {
Toast.makeText(getApplicationContext(),
TestData.artist[position] + "被选中了", Toast.LENGTH_SHORT)
.show();
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
/*
* list = (ListView) findViewById(R.id.mylist); adapter = new
* ComplexAdapter(this, TestData.thumb_url); list.setAdapter(adapter);
*/
/*
* Button b = (Button) findViewById(R.id.btn_clear_a);
* b.setOnClickListener(new OnClickListener() {
*
* @Override public void onClick(View arg0) {
* adapter.imageLoader.clearCache(); adapter.notifyDataSetChanged(); }
* });
*/
}
@Override
public void onDestroy() {
list.setAdapter(null);
super.onDestroy();
}
}
注意上面代码段中 Adapter。图 5 的目的是如何利用 android.widget.SimpleAdapter 在 android.widget.ListView 显示歌曲列表。
因此,若将图 5 的 android.widget.ListView 改为图 4 的自定义 ListView,就能实现想要的功能,而且修改起来相当容易,只改一个地方。如果想实现更强大的功能,还可以继承 android.widget.BaseAdapter 自定义 Adpater,本文只是使用 SimpleAdapter 。核心代码如下所示:
package com.my.android.app.activity;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.my.android.app.R;
import com.my.android.app.data.TestData;
import com.my.android.app.view.PushPullList;
import com.my.android.app.view.PushPullListListener;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.RelativeLayout;
import android.widget.SimpleAdapter;
import android.widget.Toast;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemSelectedListener;
public class ComplexListTest extends Activity implements PushPullListListener {
PushPullList list;
List<Map<String, Object>> listItems = null;
SimpleAdapter simpleAdapter = null;
// ArrayAdapter<String> adapter;
int len = TestData.artist.length;
int step = 10;
// 刷新控件状态
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == 0) { // 刷新最新
simpleAdapter.notifyDataSetChanged();
list.doneRefresh();
Toast.makeText(ComplexListTest.this, "新加载" + msg.arg1 + "条数据!",
Toast.LENGTH_LONG).show();
} else if (msg.what == 1) { // 加载更多
simpleAdapter.notifyDataSetChanged();
list.doneMore();
} else {
super.handleMessage(msg);
}
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_complexlist);
// 添加自定义控件
list = new PushPullList(this);
RelativeLayout root = (RelativeLayout) findViewById(R.id.root_b);
root.addView(list);
// 创建一个List集合,List集合的元素是Map
listItems = new ArrayList<Map<String, Object>>();
for (int i = 0; i < TestData.artist.length; i++) {
Map<String, Object> listItem = new HashMap<String, Object>();
listItem.put("thumb", TestData.thumb[i]);
listItem.put("artist", TestData.artist[i]);
listItem.put("title", TestData.title[i]);
listItem.put("duration", TestData.duration[i]);
listItems.add(listItem);
}
// 创建一个SimpleAdapter
simpleAdapter = new SimpleAdapter(this, listItems, R.layout.list_row,
new String[] { "artist", "title", "thumb", "duration" },
new int[] { R.id.artist, R.id.title, R.id.imagethumb,
R.id.duration });
// 为ListView设置Adapter
list.setAdapter(simpleAdapter);
list.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
long arg3) {
Toast.makeText(ComplexListTest.this,
listItems.get(arg2 - 1).toString(), Toast.LENGTH_LONG)
.show();
// Log.i("", data.get(arg2 - 1));
}
});
list.setDoMoreWhenBottom(false); // 滚动到低端的时候不自己加载更多
list.setOnRefreshListener(this); // 刷新最新 的监听
list.setOnMoreListener(this); // 加载更多 的监听
}
@Override
public void onDestroy() {
list.setAdapter(null);
super.onDestroy();
}
@Override
public boolean onRefreshOrMore(PushPullList dynamicListView,
boolean isRefresh) {
if (isRefresh) {
new Thread(r_refresh).start();
} else {
new Thread(r_more).start();
}
return false;
}
// 刷新最新,插入前边
Runnable r_refresh = new Runnable() {
@Override
public void run() {
// 下拉,刷新最新
int pos = 0;
List<Map<String, Object>> temp = new ArrayList<Map<String, Object>>();
for (int i = len + 1; i <= len + step; ++i) {
pos = i % TestData.artist.length;
Map<String, Object> item = new HashMap<String, Object>();
item.put("thumb", TestData.thumb[pos]);
item.put("artist", TestData.artist[pos]);
item.put("title", TestData.title[pos]);
item.put("duration", TestData.duration[pos]);
temp.add(item);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (listItems) {
listItems.addAll(0, temp);
}
len = len + step;
Message message = new Message();
message.what = 0;
message.arg1 = temp.size();
handler.sendMessage(message);
}
};
// 加载更多,插入末尾
Runnable r_more = new Runnable() {
@Override
public void run() {
// 上拉,加载更多
int pos = 0;
List<Map<String, Object>> temp = new ArrayList<Map<String, Object>>();
for (int i = len + 1; i <= len + step; ++i) {
pos = i % TestData.artist.length;
Map<String, Object> item = new HashMap<String, Object>();
item.put("thumb", TestData.thumb[pos]);
item.put("artist", TestData.artist[pos]);
item.put("title", TestData.title[pos]);
item.put("duration", TestData.duration[pos]);
temp.add(item);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (listItems) {
listItems.addAll(0, temp);
}
len = len + step;
handler.sendEmptyMessage(1);
}
};
}
除了改变了 Adapter 外,其他代码都没有变化。
参考资料
- Android 自定义 ListView 上下拉动刷新最新和加载更多
- Android ListView 和 ***Adapter 从本地/网络获取歌曲列表
- Android 自定义 ListView 显示网络上 JSON 格式歌曲列表
Android 自定义 ListView 上下拉动“刷新最新”和“加载更多”歌曲列表的更多相关文章
- 自定义ListView下拉刷新上拉加载更多
自定义ListView下拉刷新上拉加载更多 自定义RecyclerView下拉刷新上拉加载更多 Listview现在用的很少了,基本都是使用Recycleview,但是不得不说Listview具有划时 ...
- Android 自定义 ListView 上下拉动刷新最新和加载更多
本文内容 开发环境 演示上下拉动刷新最新和加载更多 ListView 参考资料 本文演示上下拉动,刷新最新和加载更多,这个效果很常见,比如,新闻资讯类 APP,当向下拉动时,加载最新的资讯:向上拉动时 ...
- ListView下拉刷新上拉加载更多实现
这篇文章将带大家了解listview下拉刷新和上拉加载更多的实现过程,先看效果(注:图片中listview中的阴影可以加上属性android:fadingEdge="none"去掉 ...
- react-native-page-listview使用方法(自定义FlatList/ListView下拉刷新,上拉加载更多,方便的实现分页)
react-native-page-listview 对ListView/FlatList的封装,可以很方便的分页加载网络数据,还支持自定义下拉刷新View和上拉加载更多的View.兼容高版本Flat ...
- android ListView下拉刷新 上拉加载更多
背景 最近在公司的项目中要使用到ListView的下拉刷新和上拉加载更多(貌似现在是个项目就有这个功能!哈哈),其实这个东西GitHub上很多,但是我感觉那些框架太大,而且我这个项目只用到了ListV ...
- listview下拉刷新和上拉加载更多的多种实现方案
listview经常结合下来刷新和上拉加载更多使用,本文总结了三种常用到的方案分别作出说明. 方案一:添加头布局和脚布局 android系统为listview提供了addfootview ...
- Android项目:使用pulltorefresh开源项目扩展为下拉刷新上拉加载更多的处理方法,监听listview滚动方向
很多android应用的下拉刷新都是使用的pulltorefresh这个开源项目,但是它的扩展性在下拉刷新同时又上拉加载更多时会有一定的局限性.查了很多地方,发现这个开源项目并不能很好的同时支持下拉刷 ...
- listview下拉刷新上拉加载扩展(二)-仿美团外卖
经过前几篇的listview下拉刷新上拉加载讲解,相信你对其实现机制有了一个深刻的认识了吧,那么这篇文章我们来实现一个高级的listview下拉刷新上拉加载-仿新版美团外卖的袋鼠动画: 项目结构: 是 ...
- Android 实现下拉刷新和上拉加载更多的RECYCLERVIEW和SCROLLVIEW
PullRefreshRecyclerView.java /** * 类说明:下拉刷新上拉加载更多的RecyclerView * Author: gaobaiq * Date: 2016/5/9 18 ...
随机推荐
- perl 信号
来自:http://www.bagualu.net/wordpress/?p=1628 使用signal,能让你的程序功能更丰富.要在Linux下列出所有的signal, 利用kill -l即可. 下 ...
- AngularJS使用OData请求ASP.NET Web API资源的思路
本篇整理AngularJS使用OData请求ASP.NET Web API资源的思路. 首先给ASP.NET Web API插上OData的翅膀,通过NuGet安装OData. 然后,给control ...
- 在ASP.NET MVC中使用Knockout实践08,使用foreach绑定集合
本篇体验使用 foreach 绑定一个Product集合. 首先使用构造创建一个View Model. var Product = function(data) { this.name = ko.ob ...
- NSDictionary 详解
1.使用dictionaryWithObjectsAndKeys方法存储数据时,中间任何一个对象都不能为nil,否则它后面都对象都无法存入aFiledic.因为dictionaryWithObject ...
- sqlite数据库实现字符串查找的方法(instr,substring,charindex替代方案)
sqlite数据库是一款轻型的数据库,是遵守ACID的关联式数据库管理系统,资源占用低,执行效率高,可以跨平台使用,已被广泛使用.作为一款轻量级的数据库,功能自然会有所欠缺,比如数据库加密,用户权限设 ...
- Vmware ESXi添加共享磁盘
1,ssh登录(两个虚拟机所在的宿主机)物理主机,创建共享磁盘(该步骤也可图形化操作) #cd /vmfs/volumes/4ffd9951-2c12a99a-ff2a-3440b59cd4f ...
- .NET:Attribute 入门(内训教程)
背景 接触过的语言中,C#(.NET 平台的多数语言都支持).Java 和 Python 都支持这个特性,本文重点介绍 C# 中的应用,这里简单的对 C#.java 和 Python 中的 Attri ...
- 疑犯追踪第一季/全集Person Of Interest迅雷下载
本季Person of Interest Season 1 第一季(2011)看点:如今,<疑犯追踪>正在纽约热拍,在11月1日的片场,刚刚完成了一场爆炸的戏.另外,<探索者传说第一 ...
- Python数据分析笔记
最近在看Python数据分析这本书,随手记录一下读书笔记. 工作环境 本书中推荐了edm和ipython作为数据分析的环境,我还是刚开始使用这种集成的环境,觉得交互方面,比传统的命令行方式提高了不少. ...
- MAPI错误0x80040107
MAPI错误0x80040107 的解决方案: The MAPI error means there's an "invalid entry" within the contac ...