在開發(fā)中下拉刷新實在是一種常見的不能再覺的需求了, 網(wǎng)上也有很多優(yōu)秀的第三方框架鸽疾。不過之前有一個項目要求刷新的布局從下面出現(xiàn)而不是上面,因為沒有在網(wǎng)上找到合適的第三方庫所以就自己寫了一個demo训貌,在這里與大家分享一下制肮。
首先說實現(xiàn)思路:
- 我們需要一個頭和身體且默認(rèn)是疊加在一起的.
- 獲取這兩個View且最大滑動范圍為頭的高度.
- 給身體設(shè)置滑動事件監(jiān)聽,改變身體的高度递沪,抬起時判斷是否刷新.
- 提供一個方面豺鼻,可以動態(tài)改變是否下拉刷新.
上面最為麻煩的就是動態(tài)改變是否顯頭部,因為要解決事件沖突款慨,并不是每次手指下滑都代表用戶想刷新數(shù)據(jù)儒飒,這在ListView中很了解決,只需要判斷顯示的條目是否為是第一個就行檩奠,但在實際需求中可能只是一個普通的布局需要刷新桩了,而這里我們就無法得知下滑到底是顯示下面的數(shù)據(jù)還是刷新。在這里我用到了ViewDragHelper
來解決這個問題埠戳。
效果實現(xiàn)及ViewDragHelper用法
第一步:寫一個類繼承FrameLayout
使頭與身體疊加.
public class Luffy extends FrameLayout
然后獲取頭和身體及其高度
@Override
protected void onFinishInflate() {
if(getChildCount() != 2)
throw new IllegalStateException("只能有兩個子View");
mContentView = getChildAt(1);
}
------------------------------------------------------------
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
mHeight = getMeasuredHeight();
mMaxDrag = (int) (mHeight * 0.2);
}
onFinishInflate在布局文件加載完畢后調(diào)用井誉,我們可以在這里獲取到子類,onSizeChanged在測量完畢后會回調(diào)乞而,我們在這里獲取高度送悔,并設(shè)置最大拖拽范圍.
第二步:創(chuàng)建ViewDragHelper對象,該對象接收兩個參數(shù),分別為ViewGroup
和Callback
欠啤,注意Callback
是我們實現(xiàn)效果的關(guān)鍵類.并把觸摸事件和攔截事件交給ViewDragHelper.
ViewDragHelper.create(this, new RefurbishCallBack());
-------------------------------------------------------------
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mViewDragHelper.shouldInterceptTouchEvent(ev);
}
-------------------------------------------------------------
@Override
public boolean onTouchEvent(MotionEvent event) {
mViewDragHelper.processTouchEvent(event);
return true; // 返回true觸發(fā)后續(xù)事件.
}
Callback保有一個抽象方法tryCaptureView
荚藻,該返回值決定了我們是否觸發(fā)拖拽事件,在這里我們觸發(fā)拖拽事件的條件有兩種洁段,觸摸View為Content和isRefurbish為true.當(dāng)我們觸摸的View是身體時并且isRefurbish為true時我們才允許刷新其他情況不觸發(fā).
/**
* ViewDragHelper的回調(diào)方法
*/
private class RefurbishCallBack extends ViewDragHelper.Callback{
// 捕獲view
@Override
public boolean tryCaptureView(View child, int pointerId) {
if(child == mContentView && isRefurbish){
return true;
}
return isRefurbish;
}
// 限制view移動方式
// 這里我們重寫的是clampViewPositionVertical方法应狱,當(dāng)我們縱向滑動時該方法調(diào)用
// 并返回觸摸的View及移動大小
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
top = (int) (top * 0.93f); // 阻尼系數(shù)
if(child == mContentView){
if(top < 0) top = 0;
}
return top;
}
// 釋放view時回調(diào),即抬手時
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
if(releasedChild != mContentView) return;
// 判斷是否達(dá)到最大拖拽范圍, 達(dá)到則調(diào)用回調(diào)方法
if(mContentView.getTop() >= mMaxDrag){
onRefurbishListener.onRefurbish();
scrollDown();
}else {
scrollUp();
}
}
// 這個方法比較重要祠丝,當(dāng)子view也需要事件時疾呻,重寫該方法并返回正數(shù),我這里返回的是觸發(fā)點(diǎn)
@Override
public int getViewVerticalDragRange(View child) {
return mMaxDrag;
}
}
核心代碼寫到這里就差不多了最后在提供一個方法改變isRefurbish值就可以達(dá)到動態(tài)刷新的目的.
public void isRefurbish(boolean isRefurbish){
this.isRefurbish = isRefurbish;
}
下面是Activity中的代碼
luffy.setOnRefurbishListener(new RefurbishLayout.OnRefurbishListener() {
@Override
public void onRefurbish() {
//加載數(shù)據(jù)...完畢后調(diào)用方法隱藏頭部
refurbishLayout.setRefurbish(true);
}
};
--------------------------------------------------
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
// 根據(jù)第一個條目是否可見判斷開關(guān)刷新.
refurbishLayout.isRefurbish(firstVisibleItem == 0);
}