Android筆記之:App列表之下拉刷新的使用
下拉刷新界面最初流行于iphone應(yīng)用界面,如圖:
然后在Android中也逐漸被應(yīng)用,比如微博,資訊類。
所以,今天要實(shí)現(xiàn)的結(jié)果應(yīng)該也是類似的,先貼出最終完成效果,如下圖,接下來我們一步一步實(shí)現(xiàn)。
1. 流程分析
下拉刷新最主要的流程是:
(1). 下拉,顯示提示頭部界面(HeaderView),這個過程提示用戶"下拉刷新"
(2). 下拉到一定程度,超出了刷新最基本的下拉界限,我們認(rèn)為達(dá)到了刷新的條件,提示用戶可以"松手刷新"了,效果上允許用戶繼續(xù)下拉
(3). 用戶松手,可能用戶下拉遠(yuǎn)遠(yuǎn)不止提示頭部界面,所以這一步,先反彈回僅顯示提示頭部界面,然后提示用戶"正在加載"。
(4). 加載完成后,隱藏提示頭部界面。
示意圖如下:
->
->
2. 實(shí)現(xiàn)分析
當(dāng)前我們要實(shí)現(xiàn)上述流程,是基于ListView的,所以對應(yīng)ListView本身的功能我們來分析一下實(shí)現(xiàn)原理:
(1). 下拉,顯示提示頭部界面,這個過程提示用戶"下拉刷新"
a. 下拉的操作,首先是監(jiān)聽滾動,ListView提供了onScroll()方法
b. 與下拉類似一個動作向下飛滑,所以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. 下拉的過程中,我們可能還需要下拉到多少的邊界值處理,重寫onTouchEvent(MotionEvent ev){}方法,可依據(jù)ACTION_DOWN,ACTION_MOVE,ACTION_UP實(shí)現(xiàn)更精細(xì)的判斷。
(2). 下拉到一定程度,超出了刷新最基本的下拉界限,我們認(rèn)為達(dá)到了刷新的條件,提示用戶可以"松手刷新"了,效果上允許用戶繼續(xù)下拉
a. 達(dá)到下拉刷新界限,一般指達(dá)到header的高度的,所以有兩步,第一,獲取header的高度,第二,當(dāng)header.getBottom()>=header的高度時,我們認(rèn)為就達(dá)到了刷新界限值
b. 繼續(xù)允許用戶下拉,當(dāng)header完全下拉后,默認(rèn)無法繼續(xù)下拉,但是可以增加header的PaddingTop實(shí)現(xiàn)這種效果
(3). 用戶松手,可能用戶下拉遠(yuǎn)遠(yuǎn)不止提示頭部界面,所以這一步,先反彈回僅顯示提示頭部界面,然后提示用戶"正在加載"。
a. 松手后反彈,這個不能一下子彈回去,看上去太突然,需要一步一步柔性的彈回去,像彈簧一樣,我們可以new一個Thread循環(huán)計算減少PaddingTop,直到PaddingTop為0,反彈結(jié)束。
b. 正在加載,在子線程里處理后臺任務(wù)
(4). 加載完成后,隱藏提示頭部界面。
a. 后臺任務(wù)完成后,我們需要隱藏header,setSelection(1)即實(shí)現(xiàn)了從第2項開始顯示,間接隱藏了header。
上面我們分析了實(shí)現(xiàn)過程的輪廓,接下來,通過細(xì)節(jié)說明和代碼具體實(shí)現(xiàn)。
3. 初始化
一切狀態(tài)顯示都是用HeaderView顯示的,所以我們需要一個HeaderView的layout,使用addHeaderView方法添加到ListView中。
同時,默認(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"
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>
代碼中在構(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)高度測量
因?yàn)橄吕紿eaderView全部顯示出來,就由提示"下拉刷新"變?yōu)?松手刷新",全部顯示的出來的測量標(biāo)準(zhǔn)就是header.getBottom()>=header的高度。
所以,首先我們需要測量HeaderView的默認(rèn)高度。
//因?yàn)槭窃跇?gòu)造函數(shù)里測量高度,應(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();
}
后面我們就會用到這個mHeaderHeight.
5. scrollState監(jiān)聽記錄
scrollState有3種,使用onScrollStateChanged()方法監(jiān)聽記錄。
private int mCurrentScrollState;
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
mCurrentScrollState = scrollState;
}
然后即可使用mCurrentScrollState作為后面判斷的條件了。
6. 刷新狀態(tài)分析
因?yàn)橐恍┑胤叫枰牢覀兲幵谡顟B(tài)下還是進(jìn)入下拉刷新狀態(tài)還是松手反彈狀態(tài),比如,
(1). 在非正常的狀態(tài)下,我們不小心飛滑了一下(松手的瞬間容易出現(xiàn)這種情況),我們不能setSelection(1)的,否則總是松手后header跳的一下消失掉了。
(2). 下拉后要做一個下拉效果的特殊處理,需要用到OVER_PULL_REFRESH(松手刷新狀態(tài)下)
(3). 松手反彈后要做一個反彈效果的特殊處理,需要用到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ài)
private final static int OVER_PULL_REFRESH = 2; //進(jìn)入松手刷新狀態(tài)
private final static int EXIT_PULL_REFRESH = 3; //松手后反彈后加載狀態(tài)
private int mPullRefreshState = 0; //記錄刷新狀態(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à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ài)
if (mPullRefreshState == ENTER_PULL_REFRESH || mPullRefreshState == NONE_PULL_REFRESH) {
mPullRefreshState = OVER_PULL_REFRESH;
//下面是進(jìn)入松手刷新狀態(tài)需要做的一個顯示改變
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的距離下拉,給用戶一種艱難下拉,該松手的彈簧感覺。
這個在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);
}
//重復(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ài)
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里監(jiān)聽到了進(jìn)入松手刷新狀態(tài),onTouchEvent就開始在ACTION_MOVE中處理1/3折扣問題。
7. 反彈效果的特殊處理
松手后我們需要一個柔性的反彈效果,意味著我們彈回去的過程需要分一步步走,我的解決方案是:
在子線程里計算PaddingTop,并減少到原來的3/4,循環(huán)通知主線程,直到PaddingTop小于1(這個值取一個小值,合適即可)。
松手后,當(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; //沒有達(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);//處理后臺加載數(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ù)處理代碼也貼出來了。
8. 切入數(shù)據(jù)加載過程
上面數(shù)據(jù)后臺處理我們用sleep(2000)來處理,實(shí)際處理中,作為公共組件,我們也不好把具體代碼直接寫在這里,我們需要一個更靈活的分離:
(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;
}
//我們需要重寫上面的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) {
//擴(kuò)展操作
}
};
}
很方便了。
9. 擴(kuò)展"更多"功能
下拉刷新之外,我們也可以通過相同方法使用FooterView切入底部"更多"過程,這里我就不詳細(xì)說明了
10. 源碼
上面的每段代碼都看做是"零部件",需要組合一下。
因?yàn)槲覀兩厦鎸?shí)現(xiàn)了下拉刷新,還增加了"更多"功能,我們直接命名這個類為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ài)
private final static int OVER_PULL_REFRESH = 2; //進(jìn)入松手刷新狀態(tài)
private final static int EXIT_PULL_REFRESH = 3; //松手后反彈和加載狀態(tài)
private int mPullRefreshState = 0; //記錄刷新狀態(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; //沒有達(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à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ài)
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) {
//飛滑狀態(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);
}
}
- Android下拉刷新完全解析,教你如何一分鐘實(shí)現(xiàn)下拉刷新功能(附源碼)
- Android下拉刷新ListView——RTPullListView(demo)
- android開發(fā)教程之實(shí)現(xiàn)listview下拉刷新和上拉刷新效果
- Android中使用RecyclerView實(shí)現(xiàn)下拉刷新和上拉加載
- android下拉刷新ListView的介紹和實(shí)現(xiàn)代碼
- Android新浪微博下拉刷新(最新消息顯示在最上面)
- Android PullToRefreshLayout下拉刷新控件的終結(jié)者
- Android下拉刷新上拉加載控件(適用于所有View)
- Android實(shí)現(xiàn)上拉加載更多以及下拉刷新功能(ListView)
- Android開發(fā)中下拉刷新如何實(shí)現(xiàn)
相關(guān)文章
Android中使用TextView實(shí)現(xiàn)圖文混排的方法
向TextView或EditText中添加圖像比直接添加文本復(fù)雜一點(diǎn)點(diǎn),需要用到<img>標(biāo)簽。接下來通過本文給大家介紹Android中使用TextView實(shí)現(xiàn)圖文混排的方法,希望對大家有所幫助2016-02-02Android自定義LocationMarker的實(shí)現(xiàn)詳解
這篇文章主要為大家詳細(xì)介紹一個比較簡單的東西:自定義繪制Marker 其實(shí)就是自定義view, 跟軌跡沒太多關(guān)聯(lián),感興趣的小伙伴可以跟隨小編一起了解一下2023-02-02使用ViewPager2實(shí)現(xiàn)簡易輪播圖效果
這篇文章主要為大家詳細(xì)介紹了使用ViewPager2實(shí)現(xiàn)簡易輪播圖效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-09-09Android開發(fā)之Flutter與webview通信橋梁實(shí)現(xiàn)
這篇文章主要為大家介紹了Android開發(fā)之Flutter與webview通信橋梁實(shí)現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06Android Studio獲取SHA1值實(shí)例詳解
這篇文章主要介紹了Android Studio獲取SHA1值實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2017-06-06Android開發(fā)應(yīng)用中Broadcast Receiver組件詳解
本篇文章主要介紹了Android開發(fā)應(yīng)用中Broadcast Receiver組件詳解,想要學(xué)習(xí)的同學(xué)可以了解一下。2016-11-11關(guān)于AndroidStudio R文件莫名其妙缺失的快速解決方法
下面小編就為大家?guī)硪黄P(guān)于AndroidStudio R文件莫名其妙缺失的快速解決方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-03-03微信支付僅能成功調(diào)用一次問題的解決方法(Android)
這篇文章主要介紹了微信支付僅能成功調(diào)用一次問題的解決方法,感興趣的小伙伴們可以參考一下2016-08-08