Android: ListView分页异步加载

当ListView加载的数据项数量非常庞大时,数据加载速度会非常慢,这样将严重影响应用的用户体验,甚至会提示程序无响应的错误。因此,对ListView采取分页异步的方式加载数据,对于改善用户体验和提高性能将有很大的帮助。
所谓分页,是指在加载数据时,先加载其中一部分数据,剩余的数据当用户下拉到ListView底部时再进行加载。

假设ListView显示项目条目界面如下:


当拉到最后一项时,将显示一个进度条,并提示正在加载:


为了获取手机中的联系人,添加一个服务类:DataService,该类方法getContacts()返回获取的联系人集合。getCount()方法用于获取手机中所有联系人的数量。

要读取通讯录,应在程序清单文件中声明该权限:

<uses-permission android:name="android.permission.READ_CONTACTS" />

 获取联系人的代码如下:

<span style="white-space:pre">	</span>/* Get the contacts.
	 * 
	 * @return a List content the contacts.
	 */
	public List<HashMap<String, String>> getContacts(int offset, int maxValue)
	{
		List<HashMap<String, String>> contacts = new ArrayList<HashMap<String, String>>();
		HashMap<String, String> map = null;

		// raw_contacts table
		Uri uri = Uri.parse("content://com.android.contacts/contacts");
		ContentResolver resolver = this.context.getContentResolver();

		String limit = "_id desc limit " + offset + " ," + maxValue;
		Cursor cursor = resolver.query(uri, new String[] { "_id" }, null, null, limit);
		int rowId = 0;
		Cursor dataCursor = null;

		while (cursor.moveToNext())
		{
			// data table
			rowId = cursor.getInt(cursor.getColumnIndex("_id"));
			uri = Uri.parse("content://com.android.contacts/contacts/" + rowId
					+ "/data");

			dataCursor = resolver.query(uri, new String[]
			{ "mimetype", "data1" }, null, null, null);

			map = new HashMap<String, String>();
			while (dataCursor.moveToNext())
			{
				String data = dataCursor.getString(dataCursor
						.getColumnIndex("data1"));
				String mineType = dataCursor.getString(dataCursor
						.getColumnIndex("mimetype"));

				if (mineType.equals("vnd.android.cursor.item/name"))
					map.put("name", data);
				else if (mineType.equals("vnd.android.cursor.item/phone_v2"))
					map.put("phone", data);
			}
			contacts.add(map);
		}
		return contacts;
	}

 

/**
 * Get the number of contacts.
 * 
 * @return the number of contacts.
 */
public long getCount()
{
	Uri uri = Uri.parse("content://com.android.contacts/contacts");
	ContentResolver resolver = this.context.getContentResolver();

	Cursor cursor = resolver.query(uri, new String[] { "_id" }, null, null, null);
	return cursor.getCount();
}

要想知道ListView什么拉到底部,需要为ListView添加一个监听对象,该对象应是实现的OnScrollListener接口的实例。添加一个实现OnScrollListener接口的内部类。

该接口有两个方法:

public void onScrollStateChanged(AbsListView view, int scrollState):

这个方法用于获取ListView在滚动时的状态,要想知道详细的状态,可以在日志控件台中打印scrollState参数。在这里我们不必关心这个方法。

public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
这个方法在ListView滚动时调用,我们关心的是totalItemCount这个参数。从名字中也可以推测到方法参数代表ListView中已加载的项目总数。

我们还要获取ListView当前页中的最后一个可见项目的索引:

int lastVisiblePosition = listView.getLastVisiblePosition();
最后一个可见项目始终在ListView的最下方,并且当列表后面还有可显示的项目时,最后一个可见项目始终是在变化的,直到已经到达底部。

到达底部后,我们就要开始加载下一页。那么怎么知道已经到达了底部呢?结合totalItemCount这个参数可以计算出来:当最后一个可见项目的索引+1 等于totalItemCount时,就已经到达了该页的底:

// reach the end of one page
if (lastVisiblePosition + 1 == totalItemCount && totalItemCount > 0)
为什么还要判断 totalItemCount > 0 呢,因为当ListView开始显示时,totalItemCount的值为0,而lastVisiblePositioin的值为-1,这样一来if中的条件成立,就会执行if中的代码,开始加载下一页,而这不是我们想要的结果。

我们还需要获取手机中通讯录中项目的数量,还要设定每页加载多少条项目,以便获取需要分几页来加载:

// Get the number of contacts.
long contactCount = dataService.getCount();
// How many items are visible in one page.
int pageItemCount = 30; 
// Total page count.
int maxPageCount = (int ) (contactCount%pageItemCount == 0 ? 
	contactCount/pageItemCount : contactCount/pageItemCount+1); // Total page count.
int currentPageIndex;

判断了当前加载的页面不超过最大页面数后,就可以进行加载了,我们开一条线程来获取通讯录,以实现异步加载。

@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
{		
	int lastVisiblePosition = listView.getLastVisiblePosition();
	// reach the end of one page
	if (lastVisiblePosition + 1 == totalItemCount && totalItemCount > 0)
	{
		currentPageIndex = totalItemCount % pageItemCount == 0 ? 
				totalItemCount / pageItemCount : totalItemCount / pageItemCount + 1;
		int nextPageIndex = currentPageIndex + 1;
		
		if (nextPageIndex <= maxPageCount && loadFinished)
		{
			// Add a footer to the listView
			listView.addFooterView(footer);
			// Start a thread to load the data.
			loadFinished = false;
			
			new Thread(new Runnable()
			{
				@Override
				public void run()
				{
					// Offset data that had been loaded.
						List<HashMap<String, String>> data = dataService.getContacts(currentPageIndex * 30, 30);
						handler.sendMessage(handler.obtainMessage(100, data));
					}
				}).start();
			}
		}
	}
}

 

在加载时,可以使用

listView.addFooterView(footer);
为ListView添加一个页脚,就是我们一开始显示的那个进度条和“正在加载”的文本。
在主线程中,new一个Handler对象,当我们刚刚开的线程获取了联系人数据后,就开始更新ListView的视图,把新增的数据追加到ListView底部:

Handler handler = new Handler()
{
	@Override
	public void handleMessage(Message msg)
	{
		@SuppressWarnings("unchecked")
		List<HashMap<String, String>> newData = (List<HashMap<String, String>>) msg.obj;
		MainActivity.this.data.addAll(newData);

		// Notifies the listView that the data set has been changed.
		adapter.notifyDataSetChanged();
		loadFinished = true;
		// Remove the footer from the listView.
		listView.removeFooterView(footer);
	}
};
需要注意的是,在线程中获取联系人数据时,我们是通过:

List<HashMap<String, String>> data = dataService.getContacts(currentPageIndex * 30, 30);
来实现,currentPageIndex * 30 是偏移量,因为我们要跳过已加载的数据项目。第二个参数30是指一次获取多少条项目。
另外,在getContacts()方法,中,我们使用:

String limit = "_id desc limit " + offset + " ," + maxValue;
Cursor cursor = resolver.query(uri, new String[] { "_id" }, null, null, limit);
来实现数据偏移,因为ContentResolver的query()方法内部也可通过组拼sql语句来实现的。


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值