編輯:關於Android編程
然後在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"
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);
}
}
代碼不多,就懶得加注釋了.原諒我的懶. 縮略圖獲取那塊,沒有加用線程.可能會在有些低端機上面或者高清的視頻會出現FC的情況.需要的童鞋.自行添加. &nb
閒來無聊,最近了解了一下多媒體的一些初步知識。音頻播放主要設計到MediaPlayer這個類,播放音頻的路徑可以來源於三個地方。1.應用內的音頻public class
我們在Android系統增加硬件服務的目的是為了讓應用層的APP能夠通過Java接口來訪問硬件服務。那麼, APP如何通過Java接口來訪問Application Fra
第5節 Toast5.1 使用效果Toast用來向用戶彈出一個提示框,然後自動消失,就像這樣,面包機烤好面包後,就騰的一下把面包從面包機裡彈出來。而這個控件顯示時也像是從