本篇文章介紹了,在Android中App列表之下拉刷新的使用。需要的朋友參考下
Android的ListView是應用最廣的一個組件,功能強大,擴展性靈活(不侷限於ListView本身一個類),前面的文章有介紹分組,拖拽,3D立體,遊標,圓角,而今天我們要介紹的是另外一個擴展ListView:下拉刷新的ListView。
下拉刷新界面最初流行於iphone應用界面,如圖:
然後在Android中也逐漸被應用,比如微博,資訊類。
所以,今天要實現的結果應該也是類似的,先貼出最終完成效果,如下圖,接下來我們一步一步實現。
1.
流程分析
下拉刷新最主要的流程是:
(1).
下拉,顯示提示頭部界面(HeaderView),這個過程提示用戶"下拉刷新"
(2).
下拉到一定程度,超出了刷新最基本的下拉界限,我們認爲達到了刷新的條件,提示用戶可以"鬆手刷新"了,效果上允許用戶繼續下拉
(3).
用戶鬆手,可能用戶下拉遠遠不止提示頭部界面,所以這一步,先反彈回僅顯示提示頭部界面,然後提示用戶"正在加載"。
(4).
加載完成後,隱藏提示頭部界面。
示意圖如下:
->->
2. 實現分析
當前我們要實現上述流程,是基於ListView的,所以對應ListView本身的功能我們來分析一下實現原理:
(1).
下拉,顯示提示頭部界面,這個過程提示用戶"下拉刷新"
a.
下拉的操作,首先是監聽滾動,ListView提供了onScroll()方法
b.
與下拉類似一個動作向下飛滑,所以ListView的scrollState有3種值:SCROLL_STATE_IDLE, SCROLL_STATE_TOUCH_SCROLL, SCROLL_STATE_FLING,意思容易理解,而我們要下拉的觸發條件是SCROLL_STATE_TOUCH_SCROLL。判斷當前的下拉操作狀態,ListView提供了public
void onScrollStateChanged(AbsListView view, int scrollState) {}。
c.
下拉的過程中,我們可能還需要下拉到多少的邊界值處理,重寫onTouchEvent(MotionEvent
ev){}方法,可依據ACTION_DOWN,ACTION_MOVE,ACTION_UP實現更精細的判斷。
(2).
下拉到一定程度,超出了刷新最基本的下拉界限,我們認爲達到了刷新的條件,提示用戶可以"鬆手刷新"了,效果上允許用戶繼續下拉
a.
達到下拉刷新界限,一般指達到header的高度的,所以有兩步,第一,獲取header的高度,第二,當header.getBottom()>=header的高度時,我們認爲就達到了刷新界限值
b. 繼續允許用戶下拉,當header完全下拉後,默認無法繼續下拉,但是可以增加header的PaddingTop實現這種效果
(3).
用戶鬆手,可能用戶下拉遠遠不止提示頭部界面,所以這一步,先反彈回僅顯示提示頭部界面,然後提示用戶"正在加載"。
a.
鬆手後反彈,這個不能一下***回去,看上去太突然,需要一步一步柔性的彈回去,像彈簧一樣,我們可以new一個Thread循環計算減少PaddingTop,直到PaddingTop爲0,反彈結束。
b. 正在加載,在子線程裏處理後臺任務
(4).
加載完成後,隱藏提示頭部界面。
a.
後臺任務完成後,我們需要隱藏header,setSelection(1)即實現了從第2項開始顯示,間接隱藏了header。
上面我們分析了實現過程的輪廓,接下來,通過細節說明和代碼具體實現。
3. 初始化
一切狀態顯示都是用HeaderView顯示的,所以我們需要一個HeaderView的layout,使用addHeaderView方法添加到ListView中。
同時,默認狀態下,HeaderView是不顯示的,只是在下拉後才顯示,所以我們需要隱藏HeaderView且不影響後續的下拉顯示,用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"
style="?android:attr/progressBarStyleSmall"
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>
代碼中在構造函數中添加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);
}
默認就顯示完成了。
4. HeaderView的默認高度測量
因爲下拉到HeaderView全部顯示出來,就由提示"下拉刷新"變爲"鬆手刷新",全部顯示的出來的測量標準就是header.getBottom()>=header的高度。
所以,首先我們需要測量HeaderView的默認高度。
複製代碼 代碼如下:
//因爲是在構造函數裏測量高度,應該先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的上述代碼後面加上調用measureView後,使用getMeasureHeight()方法獲取header的高度:
複製代碼 代碼如下:
private int mHeaderHeight;
void
init(final Context context) {
... ...
measureView(mHeaderLinearLayout);
mHeaderHeight =
mHeaderLinearLayout.getMeasuredHeight();
}
後面我們就會用到這個mHeaderHeight.
5. scrollState監聽記錄
scrollState有3種,使用onScrollStateChanged()方法監聽記錄。
複製代碼 代碼如下:
private int
mCurrentScrollState;
@Override
public void
onScrollStateChanged(AbsListView view, int scrollState) {
mCurrentScrollState = scrollState;
}
然後即可使用mCurrentScrollState作爲後面判斷的條件了。
6. 刷新狀態分析
因爲一些地方需要知道我們處在正常狀態下還是進入下拉刷新狀態還是鬆手反彈狀態,比如,
(1).
在非正常的狀態下,我們不小心飛滑了一下(鬆手的瞬間容易出現這種情況),我們不能setSelection(1)的,否則總是鬆手後header跳的一下消失掉了。
(2). 下拉後要做一個下拉效果的特殊處理,需要用到OVER_PULL_REFRESH(鬆手刷新狀態下)
(3).
鬆手反彈後要做一個反彈效果的特殊處理,需要用到OVER_PULL_REFRESH和ENTER_PULL_REFRESH。
複製代碼 代碼如下:
private final static int NONE_PULL_REFRESH
= 0; //正常狀態
private final static int ENTER_PULL_REFRESH = 1;
//進入下拉刷新狀態
private final static int OVER_PULL_REFRESH = 2;
//進入鬆手刷新狀態
private final static int EXIT_PULL_REFRESH = 3;
//鬆手後反彈後加載狀態
private int mPullRefreshState = 0;
//記錄刷新狀態
@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))
{
//進入且僅進入下拉刷新狀態
if (mPullRefreshState ==
NONE_PULL_REFRESH) {
mPullRefreshState =
ENTER_PULL_REFRESH;
}
} else if (mCurrentScrollState
==SCROLL_STATE_TOUCH_SCROLL
&& firstVisibleItem ==
0
&& (mHeaderLinearLayout.getBottom() >=
mHeaderHeight)) {
//下拉達到界限,進入鬆手刷新狀態
if (mPullRefreshState
== ENTER_PULL_REFRESH || mPullRefreshState == NONE_PULL_REFRESH)
{
mPullRefreshState = OVER_PULL_REFRESH;
//下面是進入鬆手刷新狀態需要做的一個顯示改變
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) {
//飛滑狀態,不能顯示出header,也不能影響正常的飛滑
//只在正常情況下才糾正位置
if
(mPullRefreshState == NONE_PULL_REFRESH) {
setSelection(1);
}
}
}
mPullRefreshState將是後面我們處理邊界的重要變量。
6. 下拉效果的特殊處理
所謂的特殊處理,當header完全顯示後,下拉只按下拉1/3的距離下拉,給用戶一種艱難下拉,該鬆手的彈簧感覺。
這個在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:
//移動時手指的位置
mMoveY =
ev.getY();
if (mPullRefreshState == OVER_PULL_REFRESH)
{
//注意下面的mDownY在onScroll的第二個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);
}
//重複貼出下面這段需要注意的代碼
@Override
public
void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int
totalItemCount) {
... ...
else if (mCurrentScrollState ==
SCROLL_STATE_TOUCH_SCROLL
&& firstVisibleItem ==
0
&& (mHeaderLinearLayout.getBottom() >=
mHeaderHeight)) {
//下拉達到界限,進入鬆手刷新狀態
if (mPullRefreshState
== ENTER_PULL_REFRESH || mPullRefreshState == NONE_PULL_REFRESH)
{
mPullRefreshState = OVER_PULL_REFRESH;
mDownY =
mMoveY; //爲下拉1/3折扣效果記錄開始位置
mHeaderTextView.setText("鬆手刷新");//顯示鬆手刷新
mHeaderPullDownImageView.setVisibility(View.GONE);//隱藏"下拉刷新"
mHeaderReleaseDownImageView.setVisibility(View.VISIBLE);//顯示向上的箭頭
}
}
...
...
}
onScroll裏監聽到了進入鬆手刷新狀態,onTouchEvent就開始在ACTION_MOVE中處理1/3折扣問題。
7. 反彈效果的特殊處理
鬆手後我們需要一個柔性的反彈效果,意味着我們彈回去的過程需要分一步步走,我的解決方案是:
在子線程裏計算PaddingTop,並減少到原來的3/4,循環通知主線程,直到PaddingTop小於1(這個值取一個小值,合適即可)。
鬆手後,當然是在onTouchEvent的ACTION_UP條件下處理比較方便:
複製代碼 代碼如下:
//因爲涉及到handler數據處理,爲方便我們定義如下常量
private
final static int REFRESH_BACKING = 0; //反彈中
private final static int
REFRESH_BACED = 1; //達到刷新界限,反彈結束後
private final static int
REFRESH_RETURN = 2; //沒有達到刷新界限,返回
private final static int REFRESH_DONE
= 3; //加載數據結束
@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);//慢一點反彈,別一下子就彈回去了
} 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);
}
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);//處理後臺加載數據
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);
break;
default:
break;
}
}
};
爲了一下子看的明確,我把效果中的數據處理代碼也貼出來了。
8. 切入數據加載過程
上面數據後臺處理我們用sleep(2000)來處理,實際處理中,作爲公共組件,我們也不好把具體代碼直接寫在這裏,我們需要一個更靈活的分離:
(1). 定義接口
(2). 注入接口
複製代碼 代碼如下:
//定義接口
public interface RefreshListener
{
Object refreshing(); //加載數據
void refreshed(Object
obj); //外部可擴展加載完成後的操作
}
//注入接口
private Object mRefreshObject =
null; //傳值
private RefreshListener mRefreshListener = null;
public void
setOnRefreshListener(RefreshListener refreshListener) {
this.mRefreshListener =
refreshListener;
}
//我們需要重寫上面的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;
在其他地方我們就可以不修改這個listview組件的代碼,使用如下:
複製代碼 代碼如下:
public xxx implements
RefreshListener{
@Override
protected void onCreate(Bundle
savedInstanceState) {
super.onCreate(savedInstanceState);
//類似如下
((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) {
//擴展操作
}
};
}
很方便了。
9. 擴展"更多"功能
下拉刷新之外,我們也可以通過相同方法使用FooterView切入底部"更多"過程,這裏我就不詳細說明了
10. 源碼
上面的每段代碼都看做是"零部件",需要組合一下。
因爲我們上面實現了下拉刷新,還增加了"更多"功能,我們直接命名這個類爲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; //正常狀態
private final static
int ENTER_PULL_REFRESH = 1; //進入下拉刷新狀態
private final static int
OVER_PULL_REFRESH = 2; //進入鬆手刷新狀態
private final static int
EXIT_PULL_REFRESH = 3; //鬆手後反彈和加載狀態
private int mPullRefreshState =
0; //記錄刷新狀態
private final static int REFRESH_BACKING
= 0; //反彈中
private final static int REFRESH_BACED = 1;
//達到刷新界限,反彈結束後
private final static int REFRESH_RETURN = 2;
//沒有達到刷新界限,返回
private final static int REFRESH_DONE = 3;
//加載數據結束
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)) {
//進入且僅進入下拉刷新狀態
if (mPullRefreshState == NONE_PULL_REFRESH)
{
mPullRefreshState = ENTER_PULL_REFRESH;
}
} else if (mCurrentScrollState ==
SCROLL_STATE_TOUCH_SCROLL
&& firstVisibleItem ==
0
&& (mHeaderLinearLayout.getBottom() >=
mHeaderHeight)) {
//下拉達到界限,進入鬆手刷新狀態
if
(mPullRefreshState == ENTER_PULL_REFRESH || mPullRefreshState ==
NONE_PULL_REFRESH) {
mPullRefreshState =
OVER_PULL_REFRESH;
mDownY = mMoveY;
//爲下拉1/3折扣效果記錄開始位置
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) {
//飛滑狀態,不能顯示出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);
}
}