您好,登錄后才能下訂單哦!
Android的ListView是應(yīng)用最廣的一個(gè)組件,功能強(qiáng)大,擴(kuò)展性靈活(不局限于ListView本身一個(gè)類(lèi)),前面的文章有介紹分組,拖拽,3D立體,游標(biāo),圓角,而今天我們要介紹的是另外一個(gè)擴(kuò)展ListView:下拉刷新的ListView。
下拉刷新界面最初流行于iphone應(yīng)用界面,如圖:
然后在Android中也逐漸被應(yīng)用,比如微博,資訊類(lèi)。
所以,今天要實(shí)現(xiàn)的結(jié)果應(yīng)該也是類(lèi)似的,先貼出最終完成效果,如下圖,接下來(lái)我們一步一步實(shí)現(xiàn)。
1. 流程分析
下拉刷新最主要的流程是:
(1). 下拉,顯示提示頭部界面(HeaderView),這個(gè)過(guò)程提示用戶(hù)"下拉刷新"
(2). 下拉到一定程度,超出了刷新最基本的下拉界限,我們認(rèn)為達(dá)到了刷新的條件,提示用戶(hù)可以"松手刷新"了,效果上允許用戶(hù)繼續(xù)下拉
(3). 用戶(hù)松手,可能用戶(hù)下拉遠(yuǎn)遠(yuǎn)不止提示頭部界面,所以這一步,先反彈回僅顯示提示頭部界面,然后提示用戶(hù)"正在加載"。
(4). 加載完成后,隱藏提示頭部界面。
示意圖如下:
2. 實(shí)現(xiàn)分析
當(dāng)前我們要實(shí)現(xiàn)上述流程,是基于ListView的,所以對(duì)應(yīng)ListView本身的功能我們來(lái)分析一下實(shí)現(xiàn)原理:
(1). 下拉,顯示提示頭部界面,這個(gè)過(guò)程提示用戶(hù)"下拉刷新"
a. 下拉的操作,首先是監(jiān)聽(tīng)滾動(dòng),ListView提供了onScroll()方法
b. 與下拉類(lèi)似一個(gè)動(dòng)作向下飛滑,所以ListView的scrollState有3種值:SCROLL_STATE_IDLE, SCROLL_STATE_TOUCH_SCROLL, SCROLL_STATE_FLING,意思容易理解,而我們要下拉的觸發(fā)條件是SCROLL_STATE_TOUCH_SCROLL。判斷當(dāng)前的下拉操作狀態(tài),ListView提供了public void onScrollStateChanged(AbsListView view, int scrollState) {}。
c. 下拉的過(guò)程中,我們可能還需要下拉到多少的邊界值處理,重寫(xiě)onTouchEvent(MotionEvent ev){}方法,可依據(jù)ACTION_DOWN,ACTION_MOVE,ACTION_UP實(shí)現(xiàn)更精細(xì)的判斷。
(2). 下拉到一定程度,超出了刷新最基本的下拉界限,我們認(rèn)為達(dá)到了刷新的條件,提示用戶(hù)可以"松手刷新"了,效果上允許用戶(hù)繼續(xù)下拉
a. 達(dá)到下拉刷新界限,一般指達(dá)到header的高度的,所以有兩步,第一,獲取header的高度,第二,當(dāng)header.getBottom()>=header的高度時(shí),我們認(rèn)為就達(dá)到了刷新界限值
b. 繼續(xù)允許用戶(hù)下拉,當(dāng)header完全下拉后,默認(rèn)無(wú)法繼續(xù)下拉,但是可以增加header的PaddingTop實(shí)現(xiàn)這種效果
(3). 用戶(hù)松手,可能用戶(hù)下拉遠(yuǎn)遠(yuǎn)不止提示頭部界面,所以這一步,先反彈回僅顯示提示頭部界面,然后提示用戶(hù)"正在加載"。
a. 松手后反彈,這個(gè)不能一下***回去,看上去太突然,需要一步一步柔性的彈回去,像彈簧一樣,我們可以new一個(gè)Thread循環(huán)計(jì)算減少PaddingTop,直到PaddingTop為0,反彈結(jié)束。
b. 正在加載,在子線程里處理后臺(tái)任務(wù)
(4). 加載完成后,隱藏提示頭部界面。
a. 后臺(tái)任務(wù)完成后,我們需要隱藏header,setSelection(1)即實(shí)現(xiàn)了從第2項(xiàng)開(kāi)始顯示,間接隱藏了header。
上面我們分析了實(shí)現(xiàn)過(guò)程的輪廓,接下來(lái),通過(guò)細(xì)節(jié)說(shuō)明和代碼具體實(shí)現(xiàn)。
3. 初始化
一切狀態(tài)顯示都是用HeaderView顯示的,所以我們需要一個(gè)HeaderView的layout,使用addHeaderView方法添加到ListView中。
同時(shí),默認(rèn)狀態(tài)下,HeaderView是不顯示的,只是在下拉后才顯示,所以我們需要隱藏HeaderView且不影響后續(xù)的下拉顯示,用setSelection(1)。
refresh_list_header.xml布局如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="center"> <ProgressBar android:id="@+id/refresh_list_header_progressbar" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:visibility="gone"> </ProgressBar> <ImageView android:id="@+id/refresh_list_header_pull_down" android:layout_width="9dip" android:layout_height="25dip" android:layout_gravity="center" android:src="@drawable/refresh_list_pull_down" /> <ImageView android:id="@+id/refresh_list_header_release_up" android:layout_width="9dip" android:layout_height="25dip" android:layout_gravity="center" android:src="@drawable/refresh_list_release_up" android:visibility="gone" /> <RelativeLayout android:layout_width="180dip" android:layout_height="wrap_content"> <TextView android:id="@+id/refresh_list_header_text" android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="center" android:layout_alignParentTop="true" android:textSize="12dip" android:textColor="#192F06" android:paddingTop="8dip" android:text="@string/app_list_header_refresh_down"/> <TextView android:id="@+id/refresh_list_header_last_update" android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="center" android:layout_below="@id/refresh_list_header_text" android:textSize="12dip" android:textColor="#192F06" android:paddingBottom="8dip" android:text="@string/app_list_header_refresh_last_update"/> </RelativeLayout> </LinearLayout>
代碼中在構(gòu)造函數(shù)中添加init()方法加載如下:
private LinearLayout mHeaderLinearLayout = null; private TextView mHeaderTextView = null; private TextView mHeaderUpdateText = null; private ImageView mHeaderPullDownImageView = null; private ImageView mHeaderReleaseDownImageView = null; private ProgressBar mHeaderProgressBar = null; public RefreshListView(Context context) { this(context, null); } public RefreshListView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } void init(final Context context) { mHeaderLinearLayout = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.refresh_list_header, null); addHeaderView(mHeaderLinearLayout); mHeaderTextView = (TextView) findViewById(R.id.refresh_list_header_text); mHeaderUpdateText = (TextView) findViewById(R.id.refresh_list_header_last_update); mHeaderPullDownImageView = (ImageView) findViewById(R.id.refresh_list_header_pull_down); mHeaderReleaseDownImageView = (ImageView) findViewById(R.id.refresh_list_header_release_up); mHeaderProgressBar = (ProgressBar) findViewById(R.id.refresh_list_header_progressbar); setSelection(1); }
默認(rèn)就顯示完成了。
4. HeaderView的默認(rèn)高度測(cè)量
因?yàn)橄吕紿eaderView全部顯示出來(lái),就由提示"下拉刷新"變?yōu)?松手刷新",全部顯示的出來(lái)的測(cè)量標(biāo)準(zhǔn)就是header.getBottom()>=header的高度。
所以,首先我們需要測(cè)量HeaderView的默認(rèn)高度。
//因?yàn)槭窃跇?gòu)造函數(shù)里測(cè)量高度,應(yīng)該先measure一下 private void measureView(View child) { ViewGroup.LayoutParams p = child.getLayoutParams(); if (p == null) { p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_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); }
然后在init的上述代碼后面加上調(diào)用measureView后,使用getMeasureHeight()方法獲取header的高度:
private int mHeaderHeight; void init(final Context context) { ... ... measureView(mHeaderLinearLayout); mHeaderHeight = mHeaderLinearLayout.getMeasuredHeight(); }
后面我們就會(huì)用到這個(gè)mHeaderHeight.
5. scrollState監(jiān)聽(tīng)記錄
scrollState有3種,使用onScrollStateChanged()方法監(jiān)聽(tīng)記錄。
private int mCurrentScrollState; @Override public void onScrollStateChanged(AbsListView view, int scrollState) { mCurrentScrollState = scrollState; }
然后即可使用mCurrentScrollState作為后面判斷的條件了。
6. 刷新?tīng)顟B(tài)分析
因?yàn)橐恍┑胤叫枰牢覀兲幵谡顟B(tài)下還是進(jìn)入下拉刷新?tīng)顟B(tài)還是松手反彈狀態(tài),比如,
(1). 在非正常的狀態(tài)下,我們不小心飛滑了一下(松手的瞬間容易出現(xiàn)這種情況),我們不能setSelection(1)的,否則總是松手后header跳的一下消失掉了。
(2). 下拉后要做一個(gè)下拉效果的特殊處理,需要用到OVER_PULL_REFRESH(松手刷新?tīng)顟B(tài)下)
(3). 松手反彈后要做一個(gè)反彈效果的特殊處理,需要用到OVER_PULL_REFRESH和ENTER_PULL_REFRESH。
private final static int NONE_PULL_REFRESH = 0; //正常狀態(tài) private final static int ENTER_PULL_REFRESH = 1; //進(jìn)入下拉刷新?tīng)顟B(tài) private final static int OVER_PULL_REFRESH = 2; //進(jìn)入松手刷新?tīng)顟B(tài) private final static int EXIT_PULL_REFRESH = 3; //松手后反彈后加載狀態(tài) private int mPullRefreshState = 0; //記錄刷新?tīng)顟B(tài) @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if (mCurrentScrollState ==SCROLL_STATE_TOUCH_SCROLL && firstVisibleItem == 0 && (mHeaderLinearLayout.getBottom() >= 0 && mHeaderLinearLayout.getBottom() < mHeaderHeight)) { //進(jìn)入且僅進(jìn)入下拉刷新?tīng)顟B(tài) if (mPullRefreshState == NONE_PULL_REFRESH) { mPullRefreshState = ENTER_PULL_REFRESH; } } else if (mCurrentScrollState ==SCROLL_STATE_TOUCH_SCROLL && firstVisibleItem == 0 && (mHeaderLinearLayout.getBottom() >= mHeaderHeight)) { //下拉達(dá)到界限,進(jìn)入松手刷新?tīng)顟B(tài) if (mPullRefreshState == ENTER_PULL_REFRESH || mPullRefreshState == NONE_PULL_REFRESH) { mPullRefreshState = OVER_PULL_REFRESH; //下面是進(jìn)入松手刷新?tīng)顟B(tài)需要做的一個(gè)顯示改變 mDownY = mMoveY;//用于后面的下拉特殊效果 mHeaderTextView.setText("松手刷新"); mHeaderPullDownImageView.setVisibility(View.GONE); mHeaderReleaseDownImageView.setVisibility(View.VISIBLE); } } else if (mCurrentScrollState ==SCROLL_STATE_TOUCH_SCROLL && firstVisibleItem != 0) { //不刷新了 if (mPullRefreshState == ENTER_PULL_REFRESH) { mPullRefreshState = NONE_PULL_REFRESH; } } else if (mCurrentScrollState == SCROLL_STATE_FLING && firstVisibleItem == 0) { //飛滑狀態(tài),不能顯示出header,也不能影響正常的飛滑 //只在正常情況下才糾正位置 if (mPullRefreshState == NONE_PULL_REFRESH) { setSelection(1); } } }
mPullRefreshState將是后面我們處理邊界的重要變量。
6. 下拉效果的特殊處理
所謂的特殊處理,當(dāng)header完全顯示后,下拉只按下拉1/3的距離下拉,給用戶(hù)一種艱難下拉,該松手的彈簧感覺(jué)。
這個(gè)在onTouchEvent里處理比較方便:
private float mDownY; private float mMoveY; @Override public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: //記下按下位置 //改變 mDownY = ev.getY(); break; case MotionEvent.ACTION_MOVE: //移動(dòng)時(shí)手指的位置 mMoveY = ev.getY(); if (mPullRefreshState == OVER_PULL_REFRESH) { //注意下面的mDownY在onScroll的第二個(gè)else中被改變了 mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(), (int)((mMoveY - mDownY)/3), //1/3距離折扣 mHeaderLinearLayout.getPaddingRight(), mHeaderLinearLayout.getPaddingBottom()); } break; case MotionEvent.ACTION_UP: ... ... break; } return super.onTouchEvent(ev); } //重復(fù)貼出下面這段需要注意的代碼 @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { ... ... else if (mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL && firstVisibleItem == 0 && (mHeaderLinearLayout.getBottom() >= mHeaderHeight)) { //下拉達(dá)到界限,進(jìn)入松手刷新?tīng)顟B(tài) if (mPullRefreshState == ENTER_PULL_REFRESH || mPullRefreshState == NONE_PULL_REFRESH) { mPullRefreshState = OVER_PULL_REFRESH; mDownY = mMoveY; //為下拉1/3折扣效果記錄開(kāi)始位置 mHeaderTextView.setText("松手刷新");//顯示松手刷新 mHeaderPullDownImageView.setVisibility(View.GONE);//隱藏"下拉刷新" mHeaderReleaseDownImageView.setVisibility(View.VISIBLE);//顯示向上的箭頭 } } ... ... }
onScroll里監(jiān)聽(tīng)到了進(jìn)入松手刷新?tīng)顟B(tài),onTouchEvent就開(kāi)始在ACTION_MOVE中處理1/3折扣問(wèn)題。
7. 反彈效果的特殊處理
松手后我們需要一個(gè)柔性的反彈效果,意味著我們彈回去的過(guò)程需要分一步步走,我的解決方案是:
在子線程里計(jì)算PaddingTop,并減少到原來(lái)的3/4,循環(huán)通知主線程,直到PaddingTop小于1(這個(gè)值取一個(gè)小值,合適即可)。
松手后,當(dāng)然是在onTouchEvent的ACTION_UP條件下處理比較方便:
//因?yàn)樯婕暗絟andler數(shù)據(jù)處理,為方便我們定義如下常量 private final static int REFRESH_BACKING = 0; //反彈中 private final static int REFRESH_BACED = 1; //達(dá)到刷新界限,反彈結(jié)束后 private final static int REFRESH_RETURN = 2; //沒(méi)有達(dá)到刷新界限,返回 private final static int REFRESH_DONE = 3; //加載數(shù)據(jù)結(jié)束 @Override public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { ... ... case MotionEvent.ACTION_UP: //when you action up, it will do these: //1. roll back util header topPadding is 0 //2. hide the header by setSelection(1) if (mPullRefreshState == OVER_PULL_REFRESH || mPullRefreshState == ENTER_PULL_REFRESH) { new Thread() { public void run() { Message msg; while(mHeaderLinearLayout.getPaddingTop() > 1) { msg = mHandler.obtainMessage(); msg.what = REFRESH_BACKING; mHandler.sendMessage(msg); try { sleep(5);//慢一點(diǎn)反彈,別一下子就彈回去了 } catch (InterruptedException e) { e.printStackTrace(); } } msg = mHandler.obtainMessage(); if (mPullRefreshState == OVER_PULL_REFRESH) { msg.what = REFRESH_BACED;//加載數(shù)據(jù)完成,結(jié)束返回 } else { msg.what = REFRESH_RETURN;//未達(dá)到刷新界限,直接返回 } mHandler.sendMessage(msg); }; }.start(); } break; } return super.onTouchEvent(ev); } private Handler mHandler = new Handler(){ @Override public void handleMessage(Message msg) { switch (msg.what) { case REFRESH_BACKING: mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(), (int) (mHeaderLinearLayout.getPaddingTop()*0.75f), mHeaderLinearLayout.getPaddingRight(), mHeaderLinearLayout.getPaddingBottom()); break; case REFRESH_BACED: mHeaderTextView.setText("正在加載..."); mHeaderProgressBar.setVisibility(View.VISIBLE); mHeaderPullDownImageView.setVisibility(View.GONE); mHeaderReleaseDownImageView.setVisibility(View.GONE); mPullRefreshState = EXIT_PULL_REFRESH; new Thread() { public void run() { sleep(2000);//處理后臺(tái)加載數(shù)據(jù) Message msg = mHandler.obtainMessage(); msg.what = REFRESH_DONE; //通知主線程加載數(shù)據(jù)完成 mHandler.sendMessage(msg); }; }.start(); break; case REFRESH_RETURN: //未達(dá)到刷新界限,返回 mHeaderTextView.setText("下拉刷新"); mHeaderProgressBar.setVisibility(View.INVISIBLE); mHeaderPullDownImageView.setVisibility(View.VISIBLE); mHeaderReleaseDownImageView.setVisibility(View.GONE); mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(), 0, mHeaderLinearLayout.getPaddingRight(), mHeaderLinearLayout.getPaddingBottom()); mPullRefreshState = NONE_PULL_REFRESH; setSelection(1); break; case REFRESH_DONE: //刷新結(jié)束后,恢復(fù)原始默認(rèn)狀態(tài) mHeaderTextView.setText("下拉刷新"); mHeaderProgressBar.setVisibility(View.INVISIBLE); mHeaderPullDownImageView.setVisibility(View.VISIBLE); mHeaderReleaseDownImageView.setVisibility(View.GONE); mHeaderUpdateText.setText(getContext().getString(R.string.app_list_header_refresh_last_update, mSimpleDateFormat.format(new Date()))); mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(), 0, mHeaderLinearLayout.getPaddingRight(), mHeaderLinearLayout.getPaddingBottom()); mPullRefreshState = NONE_PULL_REFRESH; setSelection(1); break; default: break; } } };
為了一下子看的明確,我把效果中的數(shù)據(jù)處理代碼也貼出來(lái)了。
8. 切入數(shù)據(jù)加載過(guò)程
上面數(shù)據(jù)后臺(tái)處理我們用sleep(2000)來(lái)處理,實(shí)際處理中,作為公共組件,我們也不好把具體代碼直接寫(xiě)在這里,我們需要一個(gè)更靈活的分離:
(1). 定義接口
(2). 注入接口
//定義接口 public interface RefreshListener { Object refreshing(); //加載數(shù)據(jù) void refreshed(Object obj); //外部可擴(kuò)展加載完成后的操作 } //注入接口 private Object mRefreshObject = null; //傳值 private RefreshListener mRefreshListener = null; public void setOnRefreshListener(RefreshListener refreshListener) { this.mRefreshListener = refreshListener; } //我們需要重寫(xiě)上面的mHandler如下代碼 case REFRESH_BACED: ... ... new Thread() { public void run() { if (mRefreshListener != null) { mRefreshObject = mRefreshListener.refreshing(); } Message msg = mHandler.obtainMessage(); msg.what = REFRESH_DONE; mHandler.sendMessage(msg); }; }.start(); break; case REFRESH_DONE: ... ... mPullRefreshState = NONE_PULL_REFRESH; setSelection(1); if (mRefreshListener != null) { mRefreshListener.refreshed(mRefreshObject); } break;
在其他地方我們就可以不修改這個(gè)listview組件的代碼,使用如下:
public xxx implements RefreshListener{ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //類(lèi)似如下 ((RefreshListView) listView).setOnRefreshListener(this); } @Override public Object refreshing() { String result = null; //result = FileUtils.readTextFile(file); return result; } @Override public void refreshed(Object obj) { if (obj != null) { //擴(kuò)展操作 } }; }
很方便了。
9. 擴(kuò)展"更多"功能
下拉刷新之外,我們也可以通過(guò)相同方法使用FooterView切入底部"更多"過(guò)程,這里我就不詳細(xì)說(shuō)明了
10. 源碼
上面的每段代碼都看做是"零部件",需要組合一下。
因?yàn)槲覀兩厦鎸?shí)現(xiàn)了下拉刷新,還增加了"更多"功能,我們直接命名這個(gè)類(lèi)為RefreshListView吧:
package com.tianxia.lib.baseworld.widget; import java.text.SimpleDateFormat; import java.util.Date; import android.content.Context; import android.os.Handler; import android.os.Message; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.AbsListView.OnScrollListener; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListAdapter; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.TextView; import com.tianxia.lib.baseworld.R; /** * 下拉刷新,底部更多 * */ public class RefreshListView extends ListView implements OnScrollListener{ private float mDownY; private float mMoveY; private int mHeaderHeight; private int mCurrentScrollState; private final static int NONE_PULL_REFRESH = 0; //正常狀態(tài) private final static int ENTER_PULL_REFRESH = 1; //進(jìn)入下拉刷新?tīng)顟B(tài) private final static int OVER_PULL_REFRESH = 2; //進(jìn)入松手刷新?tīng)顟B(tài) private final static int EXIT_PULL_REFRESH = 3; //松手后反彈和加載狀態(tài) private int mPullRefreshState = 0; //記錄刷新?tīng)顟B(tài) private final static int REFRESH_BACKING = 0; //反彈中 private final static int REFRESH_BACED = 1; //達(dá)到刷新界限,反彈結(jié)束后 private final static int REFRESH_RETURN = 2; //沒(méi)有達(dá)到刷新界限,返回 private final static int REFRESH_DONE = 3; //加載數(shù)據(jù)結(jié)束 private LinearLayout mHeaderLinearLayout = null; private LinearLayout mFooterLinearLayout = null; private TextView mHeaderTextView = null; private TextView mHeaderUpdateText = null; private ImageView mHeaderPullDownImageView = null; private ImageView mHeaderReleaseDownImageView = null; private ProgressBar mHeaderProgressBar = null; private TextView mFooterTextView = null; private ProgressBar mFooterProgressBar = null; private SimpleDateFormat mSimpleDateFormat; private Object mRefreshObject = null; private RefreshListener mRefreshListener = null; public void setOnRefreshListener(RefreshListener refreshListener) { this.mRefreshListener = refreshListener; } public RefreshListView(Context context) { this(context, null); } public RefreshListView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } void init(final Context context) { mHeaderLinearLayout = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.refresh_list_header, null); addHeaderView(mHeaderLinearLayout); mHeaderTextView = (TextView) findViewById(R.id.refresh_list_header_text); mHeaderUpdateText = (TextView) findViewById(R.id.refresh_list_header_last_update); mHeaderPullDownImageView = (ImageView) findViewById(R.id.refresh_list_header_pull_down); mHeaderReleaseDownImageView = (ImageView) findViewById(R.id.refresh_list_header_release_up); mHeaderProgressBar = (ProgressBar) findViewById(R.id.refresh_list_header_progressbar); mFooterLinearLayout = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.refresh_list_footer, null); addFooterView(mFooterLinearLayout); mFooterProgressBar = (ProgressBar) findViewById(R.id.refresh_list_footer_progressbar); mFooterTextView = (TextView) mFooterLinearLayout.findViewById(R.id.refresh_list_footer_text); mFooterLinearLayout.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (context.getString(R.string.app_list_footer_more).equals(mFooterTextView.getText())) { mFooterTextView.setText(R.string.app_list_footer_loading); mFooterProgressBar.setVisibility(View.VISIBLE); if (mRefreshListener != null) { mRefreshListener.more(); } } } }); setSelection(1); setOnScrollListener(this); measureView(mHeaderLinearLayout); mHeaderHeight = mHeaderLinearLayout.getMeasuredHeight(); mSimpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm"); mHeaderUpdateText.setText(context.getString(R.string.app_list_header_refresh_last_update, mSimpleDateFormat.format(new Date()))); } @Override public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: mDownY = ev.getY(); break; case MotionEvent.ACTION_MOVE: mMoveY = ev.getY(); if (mPullRefreshState == OVER_PULL_REFRESH) { mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(), (int)((mMoveY - mDownY)/3), mHeaderLinearLayout.getPaddingRight(), mHeaderLinearLayout.getPaddingBottom()); } break; case MotionEvent.ACTION_UP: //when you action up, it will do these: //1. roll back util header topPadding is 0 //2. hide the header by setSelection(1) if (mPullRefreshState == OVER_PULL_REFRESH || mPullRefreshState == ENTER_PULL_REFRESH) { new Thread() { public void run() { Message msg; while(mHeaderLinearLayout.getPaddingTop() > 1) { msg = mHandler.obtainMessage(); msg.what = REFRESH_BACKING; mHandler.sendMessage(msg); try { sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } } msg = mHandler.obtainMessage(); if (mPullRefreshState == OVER_PULL_REFRESH) { msg.what = REFRESH_BACED; } else { msg.what = REFRESH_RETURN; } mHandler.sendMessage(msg); }; }.start(); } break; } return super.onTouchEvent(ev); } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if (mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL && firstVisibleItem == 0 && (mHeaderLinearLayout.getBottom() >= 0 && mHeaderLinearLayout.getBottom() < mHeaderHeight)) { //進(jìn)入且僅進(jìn)入下拉刷新?tīng)顟B(tài) if (mPullRefreshState == NONE_PULL_REFRESH) { mPullRefreshState = ENTER_PULL_REFRESH; } } else if (mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL && firstVisibleItem == 0 && (mHeaderLinearLayout.getBottom() >= mHeaderHeight)) { //下拉達(dá)到界限,進(jìn)入松手刷新?tīng)顟B(tài) if (mPullRefreshState == ENTER_PULL_REFRESH || mPullRefreshState == NONE_PULL_REFRESH) { mPullRefreshState = OVER_PULL_REFRESH; mDownY = mMoveY; //為下拉1/3折扣效果記錄開(kāi)始位置 mHeaderTextView.setText("松手刷新");//顯示松手刷新 mHeaderPullDownImageView.setVisibility(View.GONE);//隱藏"下拉刷新" mHeaderReleaseDownImageView.setVisibility(View.VISIBLE);//顯示向上的箭頭 } } else if (mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL && firstVisibleItem != 0) { //不刷新了 if (mPullRefreshState == ENTER_PULL_REFRESH) { mPullRefreshState = NONE_PULL_REFRESH; } } else if (mCurrentScrollState == SCROLL_STATE_FLING && firstVisibleItem == 0) { //飛滑狀態(tài),不能顯示出header,也不能影響正常的飛滑 //只在正常情況下才糾正位置 if (mPullRefreshState == NONE_PULL_REFRESH) { setSelection(1); } } } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { mCurrentScrollState = scrollState; } @Override public void setAdapter(ListAdapter adapter) { super.setAdapter(adapter); setSelection(1); } private void measureView(View child) { ViewGroup.LayoutParams p = child.getLayoutParams(); if (p == null) { p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_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); } private Handler mHandler = new Handler(){ @Override public void handleMessage(Message msg) { switch (msg.what) { case REFRESH_BACKING: mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(), (int) (mHeaderLinearLayout.getPaddingTop()*0.75f), mHeaderLinearLayout.getPaddingRight(), mHeaderLinearLayout.getPaddingBottom()); break; case REFRESH_BACED: mHeaderTextView.setText("正在加載..."); mHeaderProgressBar.setVisibility(View.VISIBLE); mHeaderPullDownImageView.setVisibility(View.GONE); mHeaderReleaseDownImageView.setVisibility(View.GONE); mPullRefreshState = EXIT_PULL_REFRESH; new Thread() { public void run() { if (mRefreshListener != null) { mRefreshObject = mRefreshListener.refreshing(); } Message msg = mHandler.obtainMessage(); msg.what = REFRESH_DONE; mHandler.sendMessage(msg); }; }.start(); break; case REFRESH_RETURN: mHeaderTextView.setText("下拉刷新"); mHeaderProgressBar.setVisibility(View.INVISIBLE); mHeaderPullDownImageView.setVisibility(View.VISIBLE); mHeaderReleaseDownImageView.setVisibility(View.GONE); mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(), 0, mHeaderLinearLayout.getPaddingRight(), mHeaderLinearLayout.getPaddingBottom()); mPullRefreshState = NONE_PULL_REFRESH; setSelection(1); break; case REFRESH_DONE: mHeaderTextView.setText("下拉刷新"); mHeaderProgressBar.setVisibility(View.INVISIBLE); mHeaderPullDownImageView.setVisibility(View.VISIBLE); mHeaderReleaseDownImageView.setVisibility(View.GONE); mHeaderUpdateText.setText(getContext().getString(R.string.app_list_header_refresh_last_update, mSimpleDateFormat.format(new Date()))); mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(), 0, mHeaderLinearLayout.getPaddingRight(), mHeaderLinearLayout.getPaddingBottom()); mPullRefreshState = NONE_PULL_REFRESH; setSelection(1); if (mRefreshListener != null) { mRefreshListener.refreshed(mRefreshObject); } break; default: break; } } }; public interface RefreshListener { Object refreshing(); void refreshed(Object obj); void more(); } public void finishFootView() { mFooterProgressBar.setVisibility(View.GONE); mFooterTextView.setText(R.string.app_list_footer_more); } public void addFootView() { if (getFooterViewsCount() == 0) { addFooterView(mFooterLinearLayout); } } public void removeFootView() { removeFooterView(mFooterLinearLayout); } }
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。