Android 下拉刷新
一、實現分析
????? 實現原理:
? ? ?(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。
上面我們分析了實現過程的輪廓,接下來,我們通過細節說明和代碼具體實現。
二、 初始化
? ? 一切狀態顯示都是用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);
}默認就顯示完成了。
三、?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.
四、?scrollState監聽記錄
scrollState有3種,使用onScrollStateChanged()方法監聽記錄。
private?int?mCurrentScrollState;
@Override
public?void?onScrollStateChanged(AbsListView view, int?scrollState) {
????mCurrentScrollState = scrollState;
}
然后即可使用mCurrentScrollState作為后面判斷的條件了。
五、?刷新狀態分析
? ? 因為一些地方需要知道我們處在正常狀態下還是進入下拉刷新狀態還是松手反彈狀態,比如,
? ? (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將是后面我們處理邊界的重要變量。
六、下拉效果的特殊處理
所謂的特殊處理,當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折扣問題。
六、 反彈效果的特殊處理
松手后我們需要一個柔性的反彈效果,意味著我們彈回去的過程需要分一步步走,我的解決方案是:
在子線程里計算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;
??????? }
??? }
};
? 為了一下子看的明確,我把效果中的數據處理代碼也貼出來了。
七、切入數據加載過程
上面數據后臺處理我們用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) {
?????????? //擴展操作
??????? }
??? };
}
很方便了。
八、?擴展"更多"功能
下拉刷新之外,我們也可以通過相同方法使用FooterView切入底部"更多"過程,這里我就不詳細說明了
九、源碼
上面的每段代碼都看做是"零部件",需要組合一下。
因為我們上面實現了下拉刷新,還增加了"更多"功能,我們直接命名這個類為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);
??? }
}
11.小結
這個只是一個原型,無論代碼風格和邏輯處理,我覺得還有改進的空間,我會在后續逐漸改善的。
? ? 以下是例子效果
? ??https://github.com/openproject/world/blob/master/baseworld/src/com/tianxia/lib/baseworld/widget/RefreshListView.java
? ??https://github.com/openproject/world/blob/master/healthworld/src/com/tianxia/app/healthworld/infomation/InfomationTabActivity.java
? ? 期待有建設性的意見改善這個實現。
?
?
轉載于:https://www.cnblogs.com/zhangping/p/3514030.html
總結
以上是生活随笔為你收集整理的Android 下拉刷新的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SQLserver单表数据导入导出
- 下一篇: 水池数目---深搜思想