版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認(rèn)領(lǐng)
文檔簡介
【移動應(yīng)用開發(fā)技術(shù)】App列表之下拉刷新
Android的ListView是應(yīng)用最廣的一個組件,功能強大,擴展性靈活(不局限于ListView本身一個類),前面的文章有介紹分組,拖拽,3D立體,游標(biāo),圓角,而今天我們要介紹的是另外一個擴展ListView:下拉刷新的ListView。
下拉刷新界面最初流行于iphone應(yīng)用界面,如圖:
然后在Android中也逐漸被應(yīng)用,比如微博,資訊類。
所以,今天要實現(xiàn)的結(jié)果應(yīng)該也是類似的,先貼出最終完成效果,如下圖,接下來我們一步一步實現(xiàn)。1.流程分析
下拉刷新最主要的流程是:
(1).下拉,顯示提示頭部界面(HeaderView),這個過程提示用戶"下拉刷新"
(2).下拉到一定程度,超出了刷新最基本的下拉界限,我們認(rèn)為達到了刷新的條件,提示用戶可以"松手刷新"了,效果上允許用戶繼續(xù)下拉
(3).用戶松手,可能用戶下拉遠(yuǎn)遠(yuǎn)不止提示頭部界面,所以這一步,先反彈回僅顯示提示頭部界面,然后提示用戶"正在加載"。
(4).加載完成后,隱藏提示頭部界面。
示意圖如下:2.實現(xiàn)分析
當(dāng)前我們要實現(xiàn)上述流程,是基于ListView的,所以對應(yīng)ListView本身的功能我們來分析一下實現(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提供了publicvoidonScrollStateChanged(AbsListViewview,intscrollState){}。
c.下拉的過程中,我們可能還需要下拉到多少的邊界值處理,重寫onTouchEvent(MotionEventev){}方法,可依據(jù)ACTION_DOWN,ACTION_MOVE,ACTION_UP實現(xiàn)更精細(xì)的判斷。
(2).下拉到一定程度,超出了刷新最基本的下拉界限,我們認(rèn)為達到了刷新的條件,提示用戶可以"松手刷新"了,效果上允許用戶繼續(xù)下拉
a.達到下拉刷新界限,一般指達到header的高度的,所以有兩步,第一,獲取header的高度,第二,當(dāng)header.getBottom()>=header的高度時,我們認(rèn)為就達到了刷新界限值
b.繼續(xù)允許用戶下拉,當(dāng)header完全下拉后,默認(rèn)無法繼續(xù)下拉,但是可以增加header的PaddingTop實現(xiàn)這種效果
(3).用戶松手,可能用戶下拉遠(yuǎn)遠(yuǎn)不止提示頭部界面,所以這一步,先反彈回僅顯示提示頭部界面,然后提示用戶"正在加載"。
a.松手后反彈,這個不能一下***回去,看上去太突然,需要一步一步柔性的彈回去,像彈簧一樣,我們可以new一個Thread循環(huán)計算減少PaddingTop,直到PaddingTop為0,反彈結(jié)束。
b.正在加載,在子線程里處理后臺任務(wù)
(4).加載完成后,隱藏提示頭部界面。
a.后臺任務(wù)完成后,我們需要隱藏header,setSelection(1)即實現(xiàn)了從第2項開始顯示,間接隱藏了header。上面我們分析了實現(xiàn)過程的輪廓,接下來,通過細(xì)節(jié)說明和代碼具體實現(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="/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center">
<ProgressBar
android:id="@+id/refresh_list_header_progressbar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone">
</ProgressBar>
<ImageView
android:id="@+id/refresh_list_header_pull_down"
android:layout_width="9dip"
android:layout_height="25dip"
android:layout_gravity="center"
android:src="@drawable/refresh_list_pull_down"
/>
<ImageView
android:id="@+id/refresh_list_header_release_up"
android:layout_width="9dip"
android:layout_height="25dip"
android:layout_gravity="center"
android:src="@drawable/refresh_list_release_up"
android:visibility="gone"
/>
<RelativeLayout
android:layout_width="180dip"
android:layout_height="wrap_content">
<TextView
android:id="@+id/refresh_list_header_text"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_alignParentTop="true"
android:textSize="12dip"
android:textColor="#192F06"
android:paddingTop="8dip"
android:text="@string/app_list_header_refresh_down"/>
<TextView
android:id="@+id/refresh_list_header_last_update"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_below="@id/refresh_list_header_text"
android:textSize="12dip"
android:textColor="#192F06"
android:paddingBottom="8dip"
android:text="@string/app_list_header_refresh_last_update"/>
</RelativeLayout>
</LinearLayout>
代碼中在構(gòu)造函數(shù)中添加init()方法加載如下:
private
LinearLayout
mHeaderLinearLayout
=
null;
private
TextView
mHeaderTextView
=
null;
private
TextView
mHeaderUpdateText
=
null;
private
ImageView
mHeaderPullDownImageView
=
null;
private
ImageView
mHeaderReleaseDownImageView
=
null;
private
ProgressBar
mHeaderProgressBar
=
null;
public
RefreshListView(Context
context)
{
this(context,
null);
}
public
RefreshListView(Context
context,
AttributeSet
attrs)
{
super(context,
attrs);
init(context);
}
void
init(final
Context
context)
{
mHeaderLinearLayout
=
(LinearLayout)
LayoutInflater.from(context).inflate(R.layout.refresh_list_header,
null);
addHeaderView(mHeaderLinearLayout);
mHeaderTextView
=
(TextView)
findViewById(R.id.refresh_list_header_text);
mHeaderUpdateText
=
(TextView)
findViewById(R.id.refresh_list_header_last_update);
mHeaderPullDownImageView
=
(ImageView)
findViewById(R.id.refresh_list_header_pull_down);
mHeaderReleaseDownImageView
=
(ImageView)
findViewById(R.id.refresh_list_header_release_up);
mHeaderProgressBar
=
(ProgressBar)
findViewById(R.id.refresh_list_header_progressbar);
setSelection(1);
}
默認(rèn)就顯示完成了。4.HeaderView的默認(rèn)高度測量
因為下拉到HeaderView全部顯示出來,就由提示"下拉刷新"變?yōu)?松手刷新",全部顯示的出來的測量標(biāo)準(zhǔn)就是header.getBottom()>=header的高度。
所以,首先我們需要測量HeaderView的默認(rè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)分析
因為一些地方需要知道我們處在正常狀態(tài)下還是進入下拉刷新狀態(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;
//進入下拉刷新狀態(tài)
private
final
static
int
OVER_PULL_REFRESH
=
2;
//進入松手刷新狀態(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))
{
//進入且僅進入下拉刷新狀態(tài)
if
(mPullRefreshState
==
NONE_PULL_REFRESH)
{
mPullRefreshState
=
ENTER_PULL_REFRESH;
}
}
else
if
(mCurrentScrollState
==SCROLL_STATE_TOUCH_SCROLL
&&
firstVisibleItem
==
0
&&
(mHeaderLinearLayout.getBottom()
>=
mHeaderHeight))
{
//下拉達到界限,進入松手刷新狀態(tài)
if
(mPullRefreshState
==
ENTER_PULL_REFRESH
||
mPullRefreshState
==
NONE_PULL_REFRESH)
{
mPullRefreshState
=
OVER_PULL_REFRESH;
//下面是進入松手刷新狀態(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))
{
//下拉達到界限,進入松手刷新狀態(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)聽到了進入松手刷新狀態(tài),onTouchEvent就開始在ACTION_MOVE中處理1/3折扣問題。7.反彈效果的特殊處理
松手后我們需要一個柔性的反彈效果,意味著我們彈回去的過程需要分一步步走,我的解決方案是:
在子線程里計算PaddingTop,并減少到原來的3/4,循環(huán)通知主線程,直到PaddingTop小于1(這個值取一個小值,合適即可)。
松手后,當(dāng)然是在onTouchEvent的ACTION_UP條件下處理比較方便:
//因為涉及到handler數(shù)據(jù)處理,為方便我們定義如下常量
private
final
static
int
REFRESH_BACKING
=
0;
//反彈中
private
final
static
int
REFRESH_BACED
=
1;
//達到刷新界限,反彈結(jié)束后
private
final
static
int
REFRESH_RETURN
=
2;
//沒有達到刷新界限,返回
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);//慢一點反彈,別一下子就彈回去了
}
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;//未達到刷新界限,直接返回
}
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:
//未達到刷新界限,返回
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)來處理,實際處理中,作為公共組件,我們也不好把具體代碼直接寫在這里,我們需要一個更靈活的分離:
(1).定義接口
(2).注入接口
//定義接口
public
interface
RefreshListener
{
Object
refreshing();
//加載數(shù)據(jù)
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切入底部"更多"過程,這里我就不詳細(xì)說明了10.源碼
上面的每段代碼都看做是"零部件",需要組合一下。
因為我們上面實現(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;
//進入下拉刷新狀態(tài)
private
final
static
int
OVER_PULL_REFRESH
=
2;
//進入松手刷新狀態(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;
//達到刷新界限,反彈結(jié)束后
private
final
static
int
REFRESH_RETURN
=
2;
//沒有達到刷新界限,返回
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))
{
//進入且僅進入下拉刷新狀態(tài)
if
(mPullRefreshState
==
NONE_PULL_REFRESH)
{
mPullRefreshState
=
ENTER_PULL_REFRESH;
}
}
else
if
(mCurrentScrollState
==
SCROLL_STATE_TOUCH_SCROLL
&&
firstVisibleItem
==
0
&&
(mHeaderLinearLayout.getBottom()
>=
mHeaderHeight))
{
//下拉達到界限,進入松手刷新狀態(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
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 《培養(yǎng)契約精神》課件
- 養(yǎng)老院老人物品寄存制度
- 養(yǎng)老院老人緊急救援人員考核獎懲制度
- 向量的數(shù)量積課件
- 房屋封陽臺協(xié)議書(2篇)
- 《廣汽鄉(xiāng)鎮(zhèn)巡展》課件
- 2025年威海c1貨運從業(yè)資格證模擬考試
- 《學(xué)會與父母溝通》課件-圖
- 2024年度物業(yè)維修基金管理合同示范3篇
- 2025年遵義貨運資格證培訓(xùn)考試題
- 隴東學(xué)院教師招聘考試題庫真題2023
- 038.神經(jīng)重癥患者中樞神經(jīng)系統(tǒng)感染多黏菌素局部應(yīng)用的中國專家共識(2024年版)
- 國有企業(yè)參與設(shè)立產(chǎn)業(yè)投資基金若干政策方案
- 2025屆北京數(shù)學(xué)六年級第一學(xué)期期末質(zhì)量檢測試題含解析
- 人教版2024七年級上冊生物期末復(fù)習(xí)背誦提綱
- 2024年事業(yè)單位考試公共基礎(chǔ)知識題庫300題(附答案與解析)
- 新版《鐵道概論》考試復(fù)習(xí)試題庫(含答案)
- 【課件】人居與環(huán)境-詩意的棲居+課件高中美術(shù)人美版(2019)+必修+美術(shù)鑒賞
- 血液透析遠(yuǎn)期并發(fā)癥及處理
- 四川快速INTL2000電梯控制系統(tǒng)電氣系統(tǒng)圖
- 專題00 2024年全國甲卷高考英語聽力(精美課件)
評論
0/150
提交評論