13 tháng 1, 2011

Ví dụ làm loading ở cuối ListView và tự động load thêm dữ liệu



Nếu các bạn dùng ứng dụng Gmail, Android Market thì sẽ thấy trong các listview ở dưới cùng đều có hiện một cái loading animation sẽ load thêm dữ liệu khi mình scroll xuống tới cái loading đó. Hôm nay mình sẽ demo cách để làm việc này, khá đơn giản thôi.
Các bạn có sẵn code của list14 trong ApiDemos rồi, copy cái adapter đó ra dùng cho tiện nhé. Trong cái adapter đó, các bạn thêm giúp một hàm appendItems() để có thể nối thêm dữ liệu vào adapter hiện hành:
public void appendItems(ArrayList items) {
mItems.addAll(items);
notifyDataSetChanged();
}


Với ListView của mình, các bạn cần thêm vào một FooterView chính là cái hiển thị loading đó, lưu ý cơ bản là chỉ có thể thêm footer/header vào trong ListView trước khi setAdapter, xòn xóa thì lúc nào cũng được

LayoutInflater inflater = LayoutInflater.from(this);
mLoadMoreFooter = inflater.inflate(R.layout.loading_footer_item, null);
getListView().addFooterView(mLoadMoreFooter);

Rồi, giờ cần bắt sự kiện khi nào mình scroll list tới cuối và cái loading được hiện lên thì mình sẽ load thêm dữ liệu mới và add vào list, trong sự kiện onScroll() của ListView mình sẽ xử lý như sau:

@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
// minus 1 here because the total list item count include the
// loading footer
if (view.getLastVisiblePosition() >= totalItemCount - 1)
checkLoadMore();
}

Rồi, cái hàm checkLoadMore đơn giản chỉ là kiểm tra xem lúc đó có đang load dữ liệu chưa, dĩ nhiên nếu đang load dữ liệu rồi thì mình ko load thêm làm gì nữa, còn nếu chưa thì mình bắt đầu load thêm dữ liệu, vậy thôi
private void checkLoadMore() {
if (mLoading)
return;

/*
* if the loading footer is still there, then we will load more items
*/
if (getListView().getFooterViewsCount() != 0) {
loadMoreItems();
}
}

private void loadMoreItems() {
mLoading = true;

new Thread() {
public void run() {
try {
// in this example, we just simply wait for 2 seconds
Thread.sleep(2000);
Message msg = mMainhandler.obtainMessage(
MSG_APPEND_MORE_ITEMS, generateList());
mMainhandler.sendMessage(msg);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
}.start();
}

Hàm loadMoreItems() thì vì đây là ví dụ nên mình chỉ cho làm mỗi việc là đợi 2 giây rồi tạo dữ liệu giả ra, coi như là đã lấy được dữ liệu từ server về rồi chẳng hạn. Hàm generateList() tạo ra một list với 15 String.
Vì mình load dữ liệu ở một thread riêng biệt, mà khi muốn cập nhật trên giao diện (chụ thể ở đây là ListView) thì mình cần phải làm trong main thread, nên mình sẽ gửi một message tới handler của main thread để xử lý.
switch (msg.what) {
case MSG_APPEND_MORE_ITEMS:
ArrayList newData = (ArrayList) msg.obj;
if (mAdapter == null) {
mAdapter = new SimpleAdapter(LoadingListDemoActivity.this,
newData);
setListAdapter(mAdapter);
} else {
mAdapter.appendItems(newData);
}

// if the list we get from server is smaller than 15 (in this
// example)
// then that means there's nothing more to get, remove the
// loading item now
if (newData.size() <>
getListView().removeFooterView(mLoadMoreFooter);
}
mLoading = false;
break;
}

Đoạn code ở trên là: nếu như dữ liệu lấy về không đủ số lượng đã yêu cầu thì hiểu như là đã khôngcòn gì để load thêm nữa, vậy thì sẽ xóa cái loading ra khỏi list, vậy là xong :)

Đây là toàn bộ code của Activity:

package tvtbinh.bino.demo.loadingfooter;

import java.util.ArrayList;

import android.app.ListActivity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.AbsListView.OnScrollListener;

public class LoadingListDemoActivity extends ListActivity {
private SimpleAdapter mAdapter;
private View mLoadMoreFooter;

private boolean mLoading;

private static final int MSG_APPEND_MORE_ITEMS = 0;

private static final int NUMBER_OF_ITEMS_REQUESTED = 15;

/**
* Use this field to count how many trunks that we have loaded if it's more
* than 4 then we will not load anymore
*/
private int mTrunksCount = 0;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

// inflate the loading item and add to the ListView
// we all know that the footers/headers must be addded before setting
// the adapter for ListView
LayoutInflater inflater = LayoutInflater.from(this);
mLoadMoreFooter = inflater.inflate(R.layout.loading_footer_item, null);
getListView().addFooterView(mLoadMoreFooter);

getListView().setOnScrollListener(mScrollListener);

mLoading = false;

mAdapter = new SimpleAdapter(LoadingListDemoActivity.this,
new ArrayList());
setListAdapter(mAdapter);
}

/**
* A mocking method for getting data from some source asynchronously
*/
private void loadMoreItems() {
mLoading = true;

new Thread() {
public void run() {
try {
// in this example, we just simply wait for 2 seconds
Thread.sleep(2000);
Message msg = mMainhandler.obtainMessage(
MSG_APPEND_MORE_ITEMS, generateList());
mMainhandler.sendMessage(msg);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
}.start();
}

/**
* Generate an arrayList with 15 strings
*/
private ArrayList generateList() {
ArrayList results = new ArrayList();

mTrunksCount++;
if (mTrunksCount <= 4) {
for (int i = 0; i <>
results.add(String.valueOf(System.currentTimeMillis()));
}
}
return results;
}

private Handler mMainhandler = new Handler() {
public void handleMessage(Message msg) {

switch (msg.what) {
case MSG_APPEND_MORE_ITEMS:
ArrayList newData = (ArrayList) msg.obj;
if (mAdapter == null) {
mAdapter = new SimpleAdapter(LoadingListDemoActivity.this,
newData);
setListAdapter(mAdapter);
} else {
mAdapter.appendItems(newData);
}

// if the list we get from server is smaller than 15 (in this
// example)
// then that means there's nothing more to get, remove the
// loading item now
if (newData.size() <>
getListView().removeFooterView(mLoadMoreFooter);
}
mLoading = false;
break;
}
};
};

/**
* Check if should load more item at this time
*/
private void checkLoadMore() {
if (mLoading)
return;

/*
* if the loading footer is still there, then we will load more items
*/
if (getListView().getFooterViewsCount() != 0) {
loadMoreItems();
}
}

private OnScrollListener mScrollListener = new OnScrollListener() {

@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// We have nothing to do with this right now
}

@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
// minus 1 here because the total list item count include the
// loading footer
if (view.getLastVisiblePosition() >= totalItemCount - 1)
checkLoadMore();
}
};

private static class SimpleAdapter extends BaseAdapter {
private ArrayList mItems;
private LayoutInflater mInflater;
private Bitmap mIcon1;
private Bitmap mIcon2;

public SimpleAdapter(Context context, ArrayList items) {
mItems = new ArrayList(items);
mInflater = LayoutInflater.from(context);

// Icons bound to the rows.
mIcon1 = BitmapFactory.decodeResource(context.getResources(),
R.drawable.icon48x48_1);
mIcon2 = BitmapFactory.decodeResource(context.getResources(),
R.drawable.icon48x48_2);
}

public void appendItems(ArrayList items) {
mItems.addAll(items);
notifyDataSetChanged();
}

@Override
public int getCount() {
if (mItems == null) {
return 0;
}
return mItems.size();
}

@Override
public Object getItem(int position) {
return mItems.get(position);
}

@Override
public long getItemId(int position) {
return position;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;

if (convertView == null) {
convertView = mInflater.inflate(R.layout.list_item_icon_text,
null);
holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(R.id.text);
holder.icon = (ImageView) convertView.findViewById(R.id.icon);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}

holder.text.setText(mItems.get(position));
holder.icon.setImageBitmap((position & 1) == 1 ? mIcon1 : mIcon2);

return convertView;
}

static class ViewHolder {
TextView text;
ImageView icon;
}

}
}