最近項目中用到了listview的下拉重新整理的功能,總結了一下前輩們的代碼,單獨抽取出來寫了一個demo作為示例。
效果圖
下拉重新整理:
加載更多:
customlistview.java
package com.example.uitest.view;
import java.util.date;
import com.example.uitest.r;
import android.content.context;
import android.util.attributeset;
import android.util.log;
import android.view.layoutinflater;
import android.view.motionevent;
import android.view.view;
import android.view.viewgroup;
import android.view.animation.linearinterpolator;
import android.view.animation.rotateanimation;
import android.widget.abslistview;
import android.widget.abslistview.onscrolllistener;
import android.widget.baseadapter;
import android.widget.imageview;
import android.widget.linearlayout;
import android.widget.listview;
import android.widget.progressbar;
import android.widget.textview;
/**
* listview下拉重新整理
*
*/
public class customlistview extends listview implements onscrolllistener {
private final static int release_to_refresh = 0;
private final static int pull_to_refresh = 1;
private final static int refreshing = 2;
private final static int done = 3;
private final static int loading = 4;
// 實際的padding的距離與界面上偏移距離的比例
private final static int ratio = 3;
private layoutinflater inflater;
private linearlayout headview;
private textview tipstextview;
private textview lastupdatedtextview;
private imageview arrowimageview;
private progressbar progressbar;
private rotateanimation animation;
private rotateanimation reverseanimation;
// 用于保證starty的值在一個完整的touch事件中隻被記錄一次
private boolean isrecored;
private int headcontentwidth;
private int headcontentheight;
private int starty;
private int firstitemindex;
private int state;
private boolean isback;
private onrefreshlistener refreshlistener;
private onloadlistener loadlistener;
private boolean isrefreshable;
private progressbar moreprogressbar;
private textview loadmoreview;
private view moreview;
public customlistview(context context) {
super(context);
init(context);
}
public customlistview(context context, attributeset attrs) {
super(context, attrs);
private void init(context context) {
setcachecolorhint(context.getresources().getcolor(r.color.transparent));
inflater = layoutinflater.from(context);
headview = (linearlayout) inflater.inflate(r.layout.head, null);
arrowimageview = (imageview) headview.findviewbyid(r.id.head_arrowimageview);
arrowimageview.setminimumwidth(70);
arrowimageview.setminimumheight(50);
progressbar = (progressbar) headview.findviewbyid(r.id.head_progressbar);
tipstextview = (textview) headview.findviewbyid(r.id.head_tipstextview);
lastupdatedtextview = (textview) headview.findviewbyid(r.id.head_lastupdatedtextview);
measureview(headview);
headcontentheight = headview.getmeasuredheight();
headcontentwidth = headview.getmeasuredwidth();
headview.setpadding(0, -1 * headcontentheight, 0, 0);
headview.invalidate();
log.v("size", "width:" + headcontentwidth + " height:" + headcontentheight);
addheaderview(headview, null, false);
setonscrolllistener(this);
animation = new rotateanimation(0, -180, rotateanimation.relative_to_self, 0.5f, rotateanimation.relative_to_self, 0.5f);
animation.setinterpolator(new linearinterpolator());
animation.setduration(250);
animation.setfillafter(true);
reverseanimation = new rotateanimation(-180, 0, rotateanimation.relative_to_self, 0.5f, rotateanimation.relative_to_self, 0.5f);
reverseanimation.setinterpolator(new linearinterpolator());
reverseanimation.setduration(200);
reverseanimation.setfillafter(true);
state = done;
isrefreshable = false;
moreview = layoutinflater.from(context).inflate(r.layout.listfooter_more, null);
moreview.setvisibility(view.visible);
moreprogressbar = (progressbar) moreview.findviewbyid(r.id.pull_to_refresh_progress);
loadmoreview = (textview) moreview.findviewbyid(r.id.load_more);
moreview.setonclicklistener(new view.onclicklistener() {
@override
public void onclick(view v) {
onload();
}
});
addfooterview(moreview);
public void onscroll(abslistview arg0, int firstvisiableitem, int arg2, int arg3) {
firstitemindex = firstvisiableitem;
public void onscrollstatechanged(abslistview arg0, int arg1) {
public boolean ontouchevent(motionevent event) {
if (isrefreshable) {
switch (event.getaction()) {
case motionevent.action_down:
if (firstitemindex == 0 && !isrecored) {
isrecored = true;
starty = (int) event.gety();
}
break;
case motionevent.action_up:
if (state != refreshing && state != loading) {
if (state == done) {
}
if (state == pull_to_refresh) {
state = done;
changeheaderviewbystate();
if (state == release_to_refresh) {
state = refreshing;
onrefresh();
isrecored = false;
isback = false;
case motionevent.action_move:
int tempy = (int) event.gety();
if (!isrecored && firstitemindex == 0) {
starty = tempy;
if (state != refreshing && isrecored && state != loading) {
// 保證在設定padding的過程中,目前的位置一直是在head,否則如果當清單超出螢幕的話,當在上推的時候,清單會同時進行滾動
// 可以松手去重新整理了
setselection(0);
// 往上推了,推到了螢幕足夠掩蓋head的程度,但是還沒有推到全部掩蓋的地步
if (((tempy - starty) / ratio < headcontentheight) && (tempy - starty) > 0) {
state = pull_to_refresh;
changeheaderviewbystate();
}
// 一下子推到頂了
else if (tempy - starty <= 0) {
state = done;
// 往下拉了,或者還沒有上推到螢幕頂部掩蓋head的地步
// 還沒有到達顯示松開重新整理的時候,done或者是pull_to_refresh狀态
// 下拉到可以進入release_to_refresh的狀态
if ((tempy - starty) / ratio >= headcontentheight) {
state = release_to_refresh;
isback = true;
if (tempy - starty > 0) {
headview.setpadding(0, -1 * headcontentheight + (tempy - starty) / ratio, 0, 0);
headview.setpadding(0, (tempy - starty) / ratio - headcontentheight, 0, 0);
}
return super.ontouchevent(event);
// 當狀态改變時候,調用該方法,以更新界面
private void changeheaderviewbystate() {
switch (state) {
case release_to_refresh:
arrowimageview.setvisibility(view.visible);
progressbar.setvisibility(view.gone);
tipstextview.setvisibility(view.visible);
lastupdatedtextview.setvisibility(view.visible);
arrowimageview.clearanimation();
arrowimageview.startanimation(animation);
tipstextview.settext("松開重新整理");
break;
case pull_to_refresh:
// 是由release_to_refresh狀态轉變來的
if (isback) {
arrowimageview.clearanimation();
arrowimageview.startanimation(reverseanimation);
tipstextview.settext("下拉重新整理");
} else {
case refreshing:
headview.setpadding(0, 0, 0, 0);
progressbar.setvisibility(view.visible);
arrowimageview.setvisibility(view.gone);
tipstextview.settext("正在重新整理...");
case done:
headview.setpadding(0, -1 * headcontentheight, 0, 0);
arrowimageview.setimageresource(r.drawable.arrow);
tipstextview.settext("下拉重新整理");
public void setonrefreshlistener(onrefreshlistener refreshlistener) {
this.refreshlistener = refreshlistener;
isrefreshable = true;
public void setonloadlistener(onloadlistener loadlistener) {
this.loadlistener = loadlistener;
public interface onrefreshlistener {
public void onrefresh();
public interface onloadlistener {
public void onload();
@suppresswarnings("deprecation")
public void onrefreshcomplete() {
lastupdatedtextview.settext("最近更新:" + new date().tolocalestring());
changeheaderviewbystate();
private void onload() {
if (loadlistener != null) {
moreprogressbar.setvisibility(view.visible);
loadmoreview.settext(getcontext().getstring(r.string.load_more));
loadlistener.onload();
public void onloadcomplete() {
// moreview.setvisibility(view.gone);
moreprogressbar.setvisibility(view.gone);
loadmoreview.settext(getcontext().getstring(r.string.more_data));
private void onrefresh() {
if (refreshlistener != null) {
refreshlistener.onrefresh();
private void measureview(view child) {
viewgroup.layoutparams p = child.getlayoutparams();
if (p == null) {
p = new viewgroup.layoutparams(viewgroup.layoutparams.match_parent, viewgroup.layoutparams.wrap_content);
int childwidthspec = viewgroup.getchildmeasurespec(0, 0 + 0, p.width);
int lpheight = p.height;
int childheightspec;
if (lpheight > 0) {
childheightspec = measurespec.makemeasurespec(lpheight, measurespec.exactly);
} else {
childheightspec = measurespec.makemeasurespec(0, measurespec.unspecified);
child.measure(childwidthspec, childheightspec);
public void setadapter(baseadapter adapter) {
super.setadapter(adapter);
}
在 customlistview 中有2個回調接口,onrefreshlistener 和 onloadlistener ,分别對應 下拉和點選加載更多 時候的回調函數。在下拉重新整理完成之後要調用 mlistview.onrefreshcomplete(); 來隐藏掉 頭部,調用 mlistview.onloadcomplete(); 隐藏掉 底部的加載view。
header.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- listview的頭部 -->
<linearlayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
<!-- 内容 -->
<relativelayout
android:id="@+id/head_contentlayout"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingleft="30dp" >
<!-- 箭頭圖像、進度條 -->
<framelayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignparentleft="true"
android:layout_centervertical="true" >
<!-- 箭頭 -->
<imageview
android:id="@+id/head_arrowimageview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:contentdescription="@string/app_name"
android:src="@drawable/arrow" />
<!-- 進度條 -->
<progressbar
android:id="@+id/head_progressbar"
style="?android:attr/progressbarstylesmall"
android:indeterminatedrawable="@drawable/progressbar_bg"
android:visibility="gone" />
</framelayout>
<!-- 提示、最近更新 -->
<linearlayout
android:layout_centerhorizontal="true"
android:gravity="center_horizontal"
android:orientation="vertical" >
<!-- 提示 -->
<textview
android:id="@+id/head_tipstextview"
android:text="@string/pull_to_refresh_pull_label"
android:textcolor="@color/pull_refresh_textview"
android:textsize="20sp" />
<!-- 最近更新 -->
android:id="@+id/head_lastupdatedtextview"
android:text="@string/pull_to_refresh_refresh_lasttime"
android:textcolor="@color/gold"
android:textsize="10sp" />
</linearlayout>
</relativelayout>
</linearlayout>
listfooter_more.xml
android:layout_height="fill_parent"
android:gravity="center_horizontal"
android:orientation="horizontal"
android:padding="15dp"
>
<progressbar
android:id="@+id/pull_to_refresh_progress"
style="@android:style/widget.progressbar.small.inverse"
android:layout_width="wrap_content"
android:gravity="center"
android:indeterminate="true"
android:visibility="gone" >
</progressbar>
<textview
android:id="@+id/load_more"
android:layout_marginleft="10.0dp"
android:text="@string/more_data"
android:textcolor="@color/black" >
</textview>
mainactivity.java
package com.example.uitest;
import java.util.arraylist;
import java.util.list;
import android.app.activity;
import android.graphics.bitmapfactory;
import android.os.bundle;
import android.os.handler;
import android.view.menu;
import android.widget.adapterview;
import android.widget.adapterview.onitemclicklistener;
import com.example.uitest.model.appinfo;
import com.example.uitest.view.customlistview;
import com.example.uitest.view.customlistview.onloadlistener;
import com.example.uitest.view.customlistview.onrefreshlistener;
public class mainactivity extends activity {
private static final string tag = mainactivity.class.getsimplename();
private static final int load_data_finish = 10;
private static final int refresh_data_finish = 11;
private list<appinfo> mlist = new arraylist<appinfo>();
private customlistadapter madapter;
private customlistview mlistview;
private int count = 10;
private handler handler = new handler(){
public void handlemessage(android.os.message msg) {
switch (msg.what) {
case refresh_data_finish:
if(madapter!=null){
madapter.notifydatasetchanged();
mlistview.onrefreshcomplete(); //下拉重新整理完成
case load_data_finish:
mlistview.onloadcomplete(); //加載更多完成
default:
};
};
@override
protected void oncreate(bundle savedinstancestate) {
super.oncreate(savedinstancestate);
setcontentview(r.layout.activity_main);
buildappdata();
madapter = new customlistadapter(this);
mlistview = (customlistview) findviewbyid(r.id.mlistview);
mlistview.setadapter(madapter);
mlistview.setonrefreshlistener(new onrefreshlistener() {
public void onrefresh() {
//todo 下拉重新整理
log.e(tag, "onrefresh");
loaddata(0);
mlistview.setonloadlistener(new onloadlistener() {
public void onload() {
//todo 加載更多
log.e(tag, "onload");
loaddata(1);
mlistview.setonitemclicklistener(new onitemclicklistener() {
public void onitemclick(adapterview<?> parent, view view,
int position, long id) {
log.e(tag, "click position:" + position);
public void loaddata(final int type){
new thread(){
public void run() {
for(int i=count;i<count+10;i++){
appinfo ai = new appinfo();
ai.setappicon(bitmapfactory.decoderesource(getresources(),
r.drawable.ic_launcher));
ai.setappname("應用demo_" + i);
ai.setappver("版本: " + (i % 10 + 1) + "." + (i % 8 + 2) + "."
+ (i % 6 + 3));
ai.setappsize("大小: " + i * 10 + "mb");
mlist.add(ai);
count += 10;
try {
thread.sleep(300);
} catch (interruptedexception e) {
e.printstacktrace();
if(type==0){ //下拉重新整理
// collections.reverse(mlist); //逆序
handler.sendemptymessage(refresh_data_finish);
}else if(type==1){
handler.sendemptymessage(load_data_finish);
}.start();
/**
* 初始化應用資料
*/
private void buildappdata() {
for (int i = 0; i < 10; i++) {
appinfo ai = new appinfo();
ai.setappicon(bitmapfactory.decoderesource(getresources(),
r.drawable.ic_launcher));
ai.setappname("應用demo_" + i);
ai.setappver("版本: " + (i % 10 + 1) + "." + (i % 8 + 2) + "."
+ (i % 6 + 3));
ai.setappsize("大小: " + i * 10 + "mb");
mlist.add(ai);
public boolean oncreateoptionsmenu(menu menu) {
// inflate the menu; this adds items to the action bar if it is present.
getmenuinflater().inflate(r.menu.main, menu);
return true;
public class customlistadapter extends baseadapter {
private layoutinflater minflater;
public customlistadapter(context context) {
minflater = layoutinflater.from(context);
@override
public int getcount() {
return mlist.size();
public object getitem(int arg0) {
return mlist.get(arg0);
public long getitemid(int position) {
return position;
public view getview(int position, view convertview, viewgroup parent) {
if (getcount() == 0) {
return null;
viewholder holder = null;
if (convertview == null) {
convertview = minflater.inflate(r.layout.list_item, null);
holder = new viewholder();
holder.ivimage = (imageview) convertview
.findviewbyid(r.id.ivicon);
holder.tvname = (textview) convertview
.findviewbyid(r.id.tvname);
holder.tvver = (textview) convertview.findviewbyid(r.id.tvver);
holder.tvsize = (textview) convertview
.findviewbyid(r.id.tvsize);
convertview.settag(holder);
holder = (viewholder) convertview.gettag();
appinfo ai = mlist.get(position);
holder.ivimage.setimagebitmap(ai.getappicon());
holder.tvname.settext(ai.getappname());
holder.tvver.settext(ai.getappver());
holder.tvsize.settext(ai.getappsize());
return convertview;
public static class viewholder {
private imageview ivimage;
private textview tvname;
private textview tvver;
private textview tvsize;
list_item.xml
<relativelayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
<imageview
android:id="@+id/ivicon"
android:contentdescription="@string/image_desc"
android:src="@drawable/ic_launcher" />
<linearlayout
android:id="@+id/appinfo"
android:layout_width="match_parent"
android:layout_marginleft="5dip"
android:layout_torightof="@id/ivicon"
android:orientation="vertical" >
<textview
android:id="@+id/tvname"
android:text="@string/name"
android:textcolor="#000000"
android:textsize="16sp" />
android:id="@+id/tvver"
android:text="@string/ver"
android:textcolor="#666666"
android:textsize="13sp" />
android:id="@+id/tvsize"
android:text="@string/size"
</linearlayout>
<button
android:id="@+id/btnclick"
android:layout_width="80dip"
android:layout_alignparentright="true"
android:layout_centervertical="true"
android:focusable="false"
android:text="@string/mgr"
android:textcolor="#000000"
android:textsize="16sp" />
</relativelayout>