這個下拉效果在網上最早的例子恐怕就是johan nilsson的實作,http://johannilsson.com/2011/03/13/android-pull-to-refresh-update.html。
後面的很多例子應該都是仿照這個寫的,下面的這個例子就是對這個例子的修改,先看下一個點選的效果,我看到其他的分析部落格裡面沒有談到這一點,在這個代碼中,我們一直看到是listview的第二項,而listview的第一項被遮擋了起來,滑動至第一項:
點選頭條,頭條會變成以下:
然後,過一段時間,重新整理完成以後,listview又setselection(1),增加一條資料,同時,把頂部給遮擋住:
這是點選重新整理,然後是下拉重新整理:
最後結果和點選重新整理相同。那現在開始看下代碼:
首先看下所用到的控件和變量:
[java] view
plaincopy
// 狀态
private static final int tap_to_refresh = 1;//點選重新整理
private static final int pull_to_refresh = 2; //拉動重新整理
private static final int release_to_refresh = 3; //釋放重新整理
private static final int refreshing = 4; //正在重新整理
// 目前滑動狀态
private int mcurrentscrollstate;
// 目前重新整理狀态
private int mrefreshstate;
//頭視圖的高度
private int mrefreshviewheight;
//頭視圖 原始的top padding 屬性值
private int mrefreshoriginaltoppadding;
private int mlastmotiony;
// 監聽對listview的滑動動作
private onrefreshlistener monrefreshlistener;
//箭頭圖檔
private static int refreshicon = r.drawable.goicon;
//listview 滾動監聽器
private onscrolllistener monscrolllistener;
private layoutinflater minflater;
private relativelayout mrefreshview;
//頂部重新整理時出現的控件
private textview mrefreshviewtext;
private imageview mrefreshviewimage;
private progressbar mrefreshviewprogress;
private textview mrefreshviewlastupdated;
// 箭頭動畫效果
//變為向下的箭頭
private rotateanimation mflipanimation;
//變為逆向的箭頭
private rotateanimation mreverseflipanimation;
//是否反彈
private boolean mbouncehack;
看下點選重新整理的代碼過程:
在init()方法中初始化各個控件及設定監聽:
private void init(context context) {
// load all of the animations we need in code rather than through xml
mflipanimation = new rotateanimation(0, -180,rotateanimation.relative_to_self,
0.5f,rotateanimation.relative_to_self, 0.5f);
mflipanimation.setinterpolator(new linearinterpolator());
mflipanimation.setduration(250);
mflipanimation.setfillafter(true);
mreverseflipanimation = new rotateanimation(-180, 0,rotateanimation.relative_to_self, 0.5f,rotateanimation.relative_to_self, 0.5f);
mreverseflipanimation.setinterpolator(new linearinterpolator());
mreverseflipanimation.setduration(250);
mreverseflipanimation.setfillafter(true);
minflater = (layoutinflater) context.getsystemservice(context.layout_inflater_service);
mrefreshview = (relativelayout) minflater.inflate(r.layout.pull_to_refresh_header, this, false);
mrefreshviewtext =(textview) mrefreshview.findviewbyid(r.id.pull_to_refresh_text);
mrefreshviewimage =(imageview) mrefreshview.findviewbyid(r.id.pull_to_refresh_image);
mrefreshviewprogress =(progressbar) mrefreshview.findviewbyid(r.id.pull_to_refresh_progress);
mrefreshviewlastupdated =(textview) mrefreshview.findviewbyid(r.id.pull_to_refresh_updated_at);
mrefreshviewimage.setminimumheight(50);
mrefreshview.setonclicklistener(new onclickrefreshlistener());
mrefreshoriginaltoppadding = mrefreshview.getpaddingtop();
mrefreshstate = tap_to_refresh;
//為listview頭部增加一個view
addheaderview(mrefreshview);
super.setonscrolllistener(this);
measureview(mrefreshview);
mrefreshviewheight = mrefreshview.getmeasuredheight();
}
我們看到,mrefreshview控件既是listview用于重新整理的頭控件,這裡它設定了監聽事件:
mrefreshview.setonclicklistener(new onclickrefreshlistener());
我們再來看下監聽事件的定義:
private class onclickrefreshlistener implements onclicklistener {
@override
public void onclick(view v) {
if (mrefreshstate != refreshing) {
prepareforrefresh();
onrefresh();
}
}
調用了preparforrefresh()(準備重新整理)和onrefresh()(重新整理)兩個方法,然後在檢視這兩個方法的定義:
public void prepareforrefresh() {
resetheaderpadding(); // 恢複header的邊距
mrefreshviewimage.setvisibility(view.gone);
// we need this hack, otherwise it will keep the previous drawable.
// 注意加上,否則仍然顯示之前的圖檔
mrefreshviewimage.setimagedrawable(null);
mrefreshviewprogress.setvisibility(view.visible);
// set refresh view text to the refreshing label
mrefreshviewtext.settext(r.string.pull_to_refresh_refreshing_label);
mrefreshstate = refreshing;
public void onrefresh() {
if (monrefreshlistener != null) {
monrefreshlistener.onrefresh();
其中,後者還是回調方法。
我們看下preparforrefresh()方法中,引用了resetheadpadding()方法:
/**
* sets the header padding back to original size.
* 将head的邊距重置為初始的數值
*/
private void resetheaderpadding() {
mrefreshview.setpadding(
mrefreshview.getpaddingleft(),
mrefreshoriginaltoppadding,
mrefreshview.getpaddingright(),
mrefreshview.getpaddingbottom());
}
從新設定下header距上下左右的距離。
最重要的方法應該是:onscroll()和ontouchevent()方法,先看下ontouchevent()方法:
@override
public boolean ontouchevent(motionevent event) {
//目前手指的y值
final int y = (int) event.gety();
mbouncehack = false;
switch (event.getaction()) {
case motionevent.action_up:
//将垂直滾動條設定為可用狀态
if (!isverticalscrollbarenabled()) {
setverticalscrollbarenabled(true);
}
if (getfirstvisibleposition() == 0 && mrefreshstate != refreshing) {
// 拖動距離達到重新整理需要
if ((mrefreshview.getbottom() >= mrefreshviewheight
|| mrefreshview.gettop() >= 0)
&& mrefreshstate == release_to_refresh) {
// 把狀态設定為正在重新整理
// initiate the refresh
mrefreshstate = refreshing; //将标量設定為,正在重新整理
// 準備重新整理
prepareforrefresh();
// 重新整理
onrefresh();
} else if (mrefreshview.getbottom() < mrefreshviewheight
|| mrefreshview.gettop() <= 0) {
// abort refresh and scroll down below the refresh view
//停止重新整理,并且滾動到頭部重新整理視圖的下一個視圖
resetheader();
setselection(1); //定位在第二個清單項
}
break;
case motionevent.action_down:
// 獲得按下y軸位置
mlastmotiony = y;
break;
case motionevent.action_move:
//更行頭視圖的toppadding 屬性
applyheaderpadding(event);
return super.ontouchevent(event);
當按下的時候,記錄按下y軸的位置,然後在move中調用了applyheaderpadding()方法,我們再看下這個方法:
// 獲得header距離
private void applyheaderpadding(motionevent ev) {
//擷取累積的動作數
int pointercount = ev.gethistorysize();
for (int p = 0; p < pointercount; p++) {
//如果是釋放将要重新整理狀态
if (mrefreshstate == release_to_refresh) {
if (isverticalfadingedgeenabled()) {
setverticalscrollbarenabled(false);
//曆史累積的高度
int historicaly = (int) ev.gethistoricaly(p);
// calculate the padding to apply, we divide by 1.7 to
// simulate a more resistant effect during pull.
// 計算申請的邊距,除以1.7使得拉動效果更好
int toppadding = (int) (((historicaly - mlastmotiony)- mrefreshviewheight) / 1.7);
mrefreshview.setpadding(
mrefreshview.getpaddingleft(),
toppadding,
mrefreshview.getpaddingright(),
mrefreshview.getpaddingbottom());
通過記錄滑動距離,實時變化頭部mrefreshview的上下左右的距離。
最後,看下手指松開的action_up:
case motionevent.action_up:
mrefreshstate = refreshing; //将标量設定為:正在重新整理
當滑動距離大于一個item的距離時,添加一個item,否則,彈回。
看完ontouchevent(),然後再看一下onscroll()方法:
public void onscroll(abslistview view, int firstvisibleitem,int visibleitemcount, int totalitemcount) {
// when the refresh view is completely visible, change the text to say
// "release to refresh..." and flip the arrow drawable.
// 在refreshview完全可見時,設定文字為松開重新整理,同時翻轉箭頭
//如果是接觸滾動狀态,并且不是正在重新整理的狀态
if (mcurrentscrollstate == scroll_state_touch_scroll&& mrefreshstate != refreshing) {
if (firstvisibleitem == 0) {
//如果顯示出來了第一個清單項,顯示重新整理圖檔
mrefreshviewimage.setvisibility(view.visible);
//如果下拉了listiview,則顯示上拉重新整理動畫
if ((mrefreshview.getbottom() >= mrefreshviewheight + 20|| mrefreshview.gettop() >= 0)
&& mrefreshstate != release_to_refresh) {
mrefreshviewtext.settext(r.string.pull_to_refresh_release_label);
mrefreshviewimage.clearanimation();
mrefreshviewimage.startanimation(mflipanimation);
mrefreshstate = release_to_refresh;
//如果下拉距離不夠,則回歸原來的狀态
} else if (mrefreshview.getbottom() < mrefreshviewheight + 20
&& mrefreshstate != pull_to_refresh) {
mrefreshviewtext.settext(r.string.pull_to_refresh_pull_label);
if (mrefreshstate != tap_to_refresh) {
mrefreshviewimage.clearanimation();
mrefreshviewimage.startanimation(mreverseflipanimation);
mrefreshstate = pull_to_refresh;
} else {
mrefreshviewimage.setvisibility(view.gone);
resetheader();
//如果是滾動狀态+ 第一個視圖已經顯示+ 不是重新整理狀态
} else if (mcurrentscrollstate == scroll_state_fling && firstvisibleitem == 0
&& mrefreshstate != refreshing) {
setselection(1);
mbouncehack = true;
} else if (mbouncehack && mcurrentscrollstate == scroll_state_fling) {
setselection(1);
if (monscrolllistener != null) {
monscrolllistener.onscroll(view, firstvisibleitem,visibleitemcount, totalitemcount);
該方法是在滑動過程中,各種狀況的處理。
onscroll()方法和ontouchevent()方法的執行過程應該是,先ontouchevent()的action_down,然後是action_move和onscroll()方法同時進行,最後是ontouchevent()的action_up。也可以自己打log看一下。這樣在ontouchevent()處理header,就是mrefreshview的外部的各個熟悉,onscroll()裡面處理header(mrefreshview)裡面内部的控件變化,從邏輯上來說比較清晰。
在onscroll()中,引用方法resetheader()方法:
* resets the header to the original state.
* 重置header為之前的狀态
private void resetheader() {
if (mrefreshstate != tap_to_refresh) {
mrefreshstate = tap_to_refresh;
resetheaderpadding();
// 将重新整理圖示換成箭頭
// set refresh view text to the pull label
mrefreshviewtext.settext(r.string.pull_to_refresh_tap_label);
// replace refresh drawable with arrow drawable
// 清除動畫
mrefreshviewimage.setimageresource(refreshicon);
// clear the full rotation animation
mrefreshviewimage.clearanimation();
// hide progress bar and arrow.
// 隐藏圖示和進度條
mrefreshviewimage.setvisibility(view.gone);
mrefreshviewprogress.setvisibility(view.gone);
resethead就是header(mrefreshview)的内部的具體操作。
當一切都完成以後,就可以調用onrefreshcomplete()方法:
* resets the list to a normal state after a refresh.
* 重置listview為普通的listview
* @param lastupdated
* last updated at.
*/
public void onrefreshcomplete(charsequence lastupdated) {
setlastupdated(lastupdated);
onrefreshcomplete();
/**
* 重置listview為普通的listview,
*/
public void onrefreshcomplete() {
resetheader();
// if refresh view is visible when loading completes, scroll down to
// the next item.
if (mrefreshview.getbottom() > 0) {
invalidateviews(); //重繪視圖
重新繪制listivew,然後setselection(1)。完成!