android自带下拉阻尼动画,android 有阻尼下拉刷新列表的实现方法
本文將會(huì)介紹有阻尼下拉刷新列表的實(shí)現(xiàn),先來(lái)看看效果預(yù)覽:
這是下拉狀態(tài):
這是下拉松開手指后listView回滾到刷新?tīng)顟B(tài)時(shí)的樣子:
1. 如何調(diào)用
雖然效果圖看起來(lái)樣子不太好看,主要是因?yàn)槟莻€(gè)藍(lán)色的背景對(duì)不對(duì),沒(méi)關(guān)系,這只是一個(gè)背景而已,在了解了我們這個(gè)下拉刷新列表的實(shí)現(xiàn)之后,你就可以很輕松地修改這個(gè)背景,從而實(shí)現(xiàn)你想要的UI效果!話不多說(shuō),下面我們先來(lái)講講這個(gè)下拉刷新列表是如何使用的,這也是我們編寫代碼所要實(shí)現(xiàn)的目標(biāo)。
final PullToRefreshListView eListView = (PullToRefreshListView) rootView.findViewById(R.id.profile_listView);
eListView.setOnLoadCallBack(new PullToRefreshListView.OnLoadCallBack() {
@Override
public int whereToLoad() {
return PullToRefreshListView.DEFAULT_WHERE_TO_LOAD;
}
@Override
public void onLoad() {
eListView.postDelayed(new Runnable() {
@Override
public void run() {
eListView.setLoadingFinish();
}
}, 5000);
}
@Override
public void cancelLoad() {
}
@Override
public Drawable refreshDrawable() {
return new ColorDrawable(Color.CYAN);
}
});
eListView.setAdapter(new BaseAdapter() {
@Override
public int getCount() {
return 30;
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView tv;
if (convertView == null) {
tv = new TextView(getActivity());
tv.setGravity(Gravity.CENTER_VERTICAL);
tv.setHeight(200);
tv.setBackgroundColor(Color.WHITE);
} else {
tv = (TextView) convertView;
}
tv.setText(position+"");
return tv;
}
});
在上述代碼中,我們可以看到PullToRefreshListView的使用在adapter上跟ListView是一樣的,這個(gè)當(dāng)然,因?yàn)槲覀儗?shí)現(xiàn)下拉刷新功能并不需要修改數(shù)據(jù)適配器。我們也看到,PullToRefreshListView的實(shí)例需要設(shè)置一個(gè)OnLoadCallBack回調(diào),該回調(diào)需要實(shí)現(xiàn)4個(gè)方法,包括:
/**
* 下拉刷新的回調(diào)
*/
public interface OnLoadCallBack {
/**
* 下拉結(jié)束后將listView定位到哪個(gè)位置等待刷新完成
* @return listView的定位y坐標(biāo)值,in dp
*/
int whereToLoad();
/**
* 下拉結(jié)束后進(jìn)行刷新的回調(diào)
*/
void onLoad();
/**
* 取消刷新
*/
void cancelLoad();
/**
* 下拉刷新的背景
* @return 背景drawable
*/
Drawable refreshDrawable();
}
whereToLoad方法告知PullToRefreshListView對(duì)象下拉刷新時(shí)停留在哪個(gè)位置,具體點(diǎn)說(shuō),也就是上述第二章效果圖中藍(lán)色背景的高度。onLoad方法是下拉刷新的回調(diào),調(diào)用者可以在這里實(shí)現(xiàn)刷新動(dòng)作。cancelLoad方法是取消刷新動(dòng)作的回調(diào),調(diào)用者需要在這里將刷新動(dòng)作取消。
根據(jù)上述方法,我們可以猜測(cè),在onLoad方法中執(zhí)行的應(yīng)該是一個(gè)線程或者AsyncTask,而在cancelLoad方法中要做的就是將這個(gè)線程或者AsyncTask取消掉。最后還有一個(gè)refreshDrawable方法,這個(gè)方法是為修改listView的背景而提供給調(diào)用者的,調(diào)用者可以返回任意一個(gè)喜歡的背景Drawable。
知道如何調(diào)用以后,我們就要一步一步地實(shí)現(xiàn)這個(gè)PullToRefreshListView了。
2. 在dispatchDraw中重畫子View實(shí)現(xiàn)下拉視覺(jué)
PullToRefreshListView實(shí)現(xiàn)的關(guān)鍵在于重畫該listVIew的子View。重畫ViewGroup的子View一般是在dispatchDraw方法中實(shí)現(xiàn)的。因此,我們的PullToRefreshListView繼承自ListView類,重載其dispatchDraw方法。
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (distanceY > 0) {
if (refreshDrawable == null) {
refreshDrawable = onLoadCallBack.refreshDrawable();
}
if (refreshDrawable == null) {
canvas.drawColor(Color.GRAY);
} else {
int left = getPaddingLeft();
int top = getPaddingTop();
refreshDrawable.setBounds(left, top, getWidth()+left, getHeight()+top);
refreshDrawable.draw(canvas);
}
canvas.save();
canvas.translate(getPaddingLeft(), getPaddingTop() + distanceY);
for (int i=0;i
View child = getChildAt(i);
drawChild(canvas, child, getDrawingTime());
}
canvas.restore();
}
}
重畫子View的關(guān)鍵在于這一句代碼:
canvas.translate(getPaddingLeft(), getPaddingTop() + distanceY);
在重畫子View之前,我們需要先將canvas向上移動(dòng)distanceY距離。這是為什么呢?我們先來(lái)看看在canvas畫子View的方法
drawChild方法的文檔是怎么說(shuō)的。
protected boolean drawChild (Canvas canvas, View child, long drawingTime)
Added in API level 1 Draw one child of this View Group. This method is responsible for getting the canvas in the right state. This includes clipping, translating so that the child's scrolled origin is at 0, 0, and applying any animation transformations.
Parameters canvas The canvas on which to draw the child child Who to draw drawingTime The time at which draw is occurring Returns True if an invalidate() was issued
我來(lái)翻譯一下,drawChild方法可以畫出這個(gè)View Group的一個(gè)子View。該方法需要使canvas處于一個(gè)正確的狀態(tài),該狀態(tài)就
是通過(guò)對(duì)canvas進(jìn)行clip裁剪,translate評(píng)議操作等以使得該子View位于canvas的(0,0)位置。
什么意思呢?簡(jiǎn)單來(lái)說(shuō)就是,drawChild方法會(huì)將child view畫在canvas的(0,0)位置,因此為了使得該child view位于
canvas的正確位置,我們需要在重畫之前對(duì)canvas進(jìn)行裁剪平移等操作。舉個(gè)例子,有一個(gè)canvas和一個(gè)child view,本來(lái)
child view要畫在(0,0)位置上,于是呈現(xiàn)在我們眼前的child view就是位于canvas的頂部,但是如果在畫之前我們將
canvas向上移動(dòng)100個(gè)像素單位,然后再將child view畫在(0,0)位置上,那么呈現(xiàn)在我們眼前的child view的位置將會(huì)是
位于canvas的(0,100)位置上。
根據(jù)以上分析,我們可以知道,重畫子View的原理就是:
當(dāng)PullToRefreshListView已經(jīng)滾動(dòng)到頂部的時(shí)候,通過(guò)監(jiān)控滑動(dòng)手勢(shì)來(lái)計(jì)算distanceY,從而確定要將canvas向上移動(dòng)多少再重畫子View,就可以實(shí)現(xiàn)PullToRefreshListView跟隨滑動(dòng)手勢(shì)進(jìn)行下拉的功能了。
3. 計(jì)算下拉距離
實(shí)現(xiàn)了重畫以后,我們需要做的就是如何計(jì)算distanceY。我們的初步想法是,根據(jù)滑動(dòng)的距離來(lái)計(jì)算,考慮到我們要實(shí)現(xiàn)阻尼效果,即隨著滑動(dòng)距離的變長(zhǎng),PullToRefreshListView的下拉距離會(huì)越來(lái)越短。在PullToRefreshListView實(shí)現(xiàn)中,我使用指數(shù)函數(shù)來(lái)實(shí)現(xiàn)這一阻尼效果,具體計(jì)算如下:
distanceY = ev.getY() - pullStartY;
distanceY = (float) (Math.exp(-ev.getY() / pullStartY / 40) * distanceY);
我們知道負(fù)指數(shù)是加速度隨距離變小的單調(diào)遞增函數(shù),我使用手指滑動(dòng)距離計(jì)算負(fù)指數(shù)作為PullToRefreshListView的滑動(dòng)距離的參考標(biāo)準(zhǔn),便可以實(shí)現(xiàn)有阻尼下拉效果。
4. 監(jiān)控手勢(shì)判斷ListView是否進(jìn)入下拉狀態(tài)并更新distanceY
更進(jìn)一步,我們要實(shí)現(xiàn)的就是對(duì)手勢(shì)的監(jiān)控,在PullToRefreshListView中,我們?cè)趏nTouchEvent方法中進(jìn)行處理。
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (lastAction == -1 && ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
// 按下的時(shí)候
lastAction = MotionEvent.ACTION_DOWN;
cancelAnimating();
L.d(TAG, "touch down");
} else if (lastAction == MotionEvent.ACTION_MOVE && ev.getActionMasked() == MotionEvent.ACTION_UP) {
// 放開手指,開始回滾
isPulling = false;
lastAction = -1;
startAnimating();
L.d(TAG, "touch up");
} else if (lastAction == MotionEvent.ACTION_DOWN) {
if (ev.getActionMasked() == MotionEvent.ACTION_MOVE) {
// 在按下手指的基礎(chǔ)上,開始滑動(dòng)
if (isTop && !isPulling) {
// listView在頂部而且不處于下拉刷新?tīng)顟B(tài),開始下拉
pullStartY = ev.getY();
lastAction = MotionEvent.ACTION_MOVE;
isPulling = true;
}
}
} else if (lastAction == MotionEvent.ACTION_MOVE) {
if (isTop) {
// 下拉
distanceY = ev.getY() - pullStartY;
L.d(TAG, distanceY + "");
if (distanceY > 0) {
distanceY = (float) (Math.exp(-ev.getY() / pullStartY / 40) * distanceY);
// 在下拉狀態(tài)時(shí)取消系統(tǒng)對(duì)move動(dòng)作的響應(yīng),完全由本類響應(yīng)
ev.setAction(MotionEvent.ACTION_DOWN);
} else {
distanceY = 0;
// 在下拉過(guò)程中往上拉動(dòng)該listView使得其回到頂部位置,則將該move動(dòng)作交由系統(tǒng)進(jìn)行響應(yīng)
ev.setAction(MotionEvent.ACTION_MOVE);
}
} else {
// 在下拉過(guò)程中往上拉動(dòng)listView使listView往下滾動(dòng)到其沒(méi)有滾動(dòng)到頂部,則取消其下拉狀態(tài),回到手指按下的初始狀態(tài)
lastAction = MotionEvent.ACTION_DOWN;
isPulling = false;
distanceY = 0;
}
}
return super.onTouchEvent(ev);
}
這一段代碼相對(duì)有一點(diǎn)復(fù)雜,我們慢慢解析。首先,我們有一個(gè)lastAction變量來(lái)記錄上一個(gè)手勢(shì)是什么,有一個(gè)isPulling變量來(lái)記錄當(dāng)前PullToRefreshListView是否處于下拉狀態(tài),有一個(gè)isTop變量記錄當(dāng)前PullToRefreshListView是否已經(jīng)滾動(dòng)到頂部。
在onTouchEvent方法的重載實(shí)現(xiàn)中,一開始PullToRefreshListView沒(méi)有接受任何手勢(shì),然后當(dāng)用戶按下手指出發(fā)ACTION_DOWN事件時(shí),我記錄下這個(gè)動(dòng)作,然后當(dāng)用戶進(jìn)行滑動(dòng)時(shí),如果此時(shí)PullToRefreshListView沒(méi)有“滾動(dòng)到頂部”,則不做任何處理,反之則將lastAction更新為ACTION_MOVE狀態(tài),更新isPulling變量,記錄當(dāng)前手指的位置作為計(jì)算下拉距離的起始位置,開始下拉刷新,然后在下拉的過(guò)程中計(jì)算PullToRefreshListView下拉的距離以重畫子View。
在這個(gè)手勢(shì)處理的實(shí)現(xiàn)中,當(dāng)用戶在下拉過(guò)程中突然將PullToRefreshListView往上拉,如果將PullToRefreshListView 拉到不處于“滾動(dòng)到頂部的狀態(tài)”時(shí),則重置下拉狀態(tài),使得:
lastAction = MotionEvent.ACTION_DOWN;
于是PullToRefreshListView接下來(lái)的下滑手勢(shì)響應(yīng)權(quán)被交還給系統(tǒng),知道用戶又將PullToRefreshListView下拉到“滾動(dòng)到頂部”狀態(tài),則又重新執(zhí)行上述操作,使PullToRefreshListView進(jìn)入下拉狀態(tài)。
5. 如何判斷ListView是否已經(jīng)滾動(dòng)到頂部
下一步,我們?nèi)绾闻袛郘istView是否處于“滾動(dòng)到頂部”狀態(tài)呢?這一問(wèn)題我PullToRefreshListView的onScroll中解決。
setOnScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
// 沒(méi)有子view的時(shí)候(沒(méi)有數(shù)據(jù),或者被拉到看不到子view),意味著該listView滾動(dòng)到頂部
if (getChildCount() == 0) {
isTop = true;
return;
}
if (firstVisibleItem == 0) {
View firstView = getChildAt(0);
if (firstView.getTop() + distanceY >= 0) {
// 第一個(gè)view可見(jiàn)且其相對(duì)parent(該listView)的頂部距離大于等于0,意味著該listView也是滾動(dòng)到頂部
isTop = true;
return;
}
}
isTop = false;
}
});
為PullToRefreshListView設(shè)置一個(gè)OnScrollListener回調(diào),并在其onScroll方法中監(jiān)控其滾動(dòng)位置,具體看注釋也已經(jīng)一目了然,我就不多解釋了。
6. 下拉后的回滾動(dòng)畫
最后,當(dāng)下拉結(jié)束松開手指時(shí),我們需要為PullToRefreshListView執(zhí)行一個(gè)回滾的動(dòng)畫,我們?cè)趏nTouchEvent方法中看到:
// ......
else if (lastAction == MotionEvent.ACTION_MOVE && ev.getActionMasked() == MotionEvent.ACTION_UP) {
// 放開手指,開始回滾
isPulling = false;
lastAction = -1;
startAnimating();
L.d(TAG, "touch up");
}
// ......
startAnimating方法的實(shí)現(xiàn)如下:
/**
* 下拉結(jié)束時(shí)進(jìn)行回滾動(dòng)畫并執(zhí)行刷新動(dòng)作
*/
private void startAnimating() {
int whereToLoad = dp2px(onLoadCallBack.whereToLoad());
final boolean toLoad;
if (distanceY <= whereToLoad) {
pullCancelAnimator = ValueAnimator.ofFloat(distanceY, 0);
toLoad = false;
} else {
pullCancelAnimator = ValueAnimator.ofFloat(distanceY, whereToLoad);
toLoad = true;
}
pullCancelAnimator.setDuration((long) (DEFAULT_BASE_ANIMATING_TIME_PER_100DP*px2dp(distanceY)/100));
pullCancelAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
pullCancelAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
distanceY = (float) animation.getAnimatedValue();
ViewCompat.postInvalidateOnAnimation(PullToRefreshListView.this);
}
});
pullCancelAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
post(new Runnable() {
@Override
public void run() {
pullCancelAnimator = null;
if (toLoad) {
onLoadCallBack.onLoad();
}
}
});
}
@Override
public void onAnimationCancel(Animator animation) {
post(new Runnable() {
@Override
public void run() {
pullCancelAnimator = null;
if (toLoad) {
onLoadCallBack.cancelLoad();
}
}
});
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
pullCancelAnimator.start();
}
我使用ValueAnimator來(lái)實(shí)現(xiàn)這一回滾動(dòng)畫,其中為ValueAnimator設(shè)置的回調(diào)中,在動(dòng)畫更新和動(dòng)畫結(jié)束以及動(dòng)畫取消中分別調(diào)用了OnLoadCallBack的3歌回調(diào)方法,從而實(shí)現(xiàn)PullToRefreshListView的下拉刷新動(dòng)作。我們可以看到,onLoad方法是在UI線程執(zhí)行的,因此如果在onLoad方法中執(zhí)行耗時(shí)操作的話,需要在后臺(tái)線程中操作,這與我們前面的解析是對(duì)應(yīng)的。
7. 改進(jìn)和問(wèn)題
(1) 我們可以將onLoad回調(diào)修改成一個(gè)返回一個(gè)異步任務(wù)對(duì)象的方法,然后PullToRefreshListView在下拉結(jié)束后執(zhí)行這個(gè)異步任務(wù),因此我們就可以不需要cancelLoading回調(diào)了,直接就可以在PullToRefreshListView內(nèi)部進(jìn)行取消操作,這樣做可以增強(qiáng)封裝性,但相對(duì)目前的做法自由度就沒(méi)有那么高了。
(2) 回滾動(dòng)畫應(yīng)該也可以進(jìn)行優(yōu)化,具體怎么優(yōu)化我也不清楚。。。各位朋友有好的想法可以在評(píng)論區(qū)提議一下,謝謝~
(3) 下拉的時(shí)候?qū)Χ帱c(diǎn)觸碰的響應(yīng)并不完美,雖然也可以接受,但是做不到像qq客戶端的聊天列表那樣。
8. 源碼
至此,我已經(jīng)解析了如何實(shí)現(xiàn)一個(gè)下拉刷新列表,PullToRefreshListView的源碼如下。
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.AbsListView;
import android.widget.ListView;
import com.ivan.healthcare.healthcare_android.log.L;
/**
* 支持下拉刷新的的listView
* Created by Ivan on 16/2/14.
*/
public class PullToRefreshListView extends ListView {
private final String TAG = "PullToRefreshListView";
private final int DEFAULT_BASE_ANIMATING_TIME_PER_100DP = 150;
public static final int DEFAULT_WHERE_TO_LOAD = 80;
private int lastAction = -1;
private float pullStartY = -1;
private boolean isTop = true;
private float distanceY = 0;
private boolean isPulling = false;
private ValueAnimator pullCancelAnimator;
private Context context;
private Drawable refreshDrawable;
private OnLoadCallBack onLoadCallBack = new OnLoadCallBack() {
@Override
public int whereToLoad() {
return DEFAULT_WHERE_TO_LOAD;
}
@Override
public void onLoad() {
}
@Override
public void cancelLoad() {
}
@Override
public Drawable refreshDrawable() {
return null;
}
};
public PullToRefreshListView(Context context) {
super(context);
initView(context);
}
public PullToRefreshListView(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
private void initView(Context context) {
this.context = context;
setOnScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
// 沒(méi)有子view的時(shí)候(沒(méi)有數(shù)據(jù),或者被拉到看不到子view),意味著該listView滾動(dòng)到頂部
if (getChildCount() == 0) {
isTop = true;
return;
}
if (firstVisibleItem == 0) {
View firstView = getChildAt(0);
if (firstView.getTop() + distanceY >= 0) {
// 第一個(gè)view可見(jiàn)且其相對(duì)parent(該listView)的頂部距離大于等于0,意味著該listView也是滾動(dòng)到頂部
isTop = true;
return;
}
}
isTop = false;
}
});
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (lastAction == -1 && ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
// 按下的時(shí)候
lastAction = MotionEvent.ACTION_DOWN;
cancelAnimating();
L.d(TAG, "touch down");
} else if (lastAction == MotionEvent.ACTION_MOVE && ev.getActionMasked() == MotionEvent.ACTION_UP) {
// 放開手指,開始回滾
isPulling = false;
lastAction = -1;
startAnimating();
L.d(TAG, "touch up");
} else if (lastAction == MotionEvent.ACTION_DOWN) {
if (ev.getActionMasked() == MotionEvent.ACTION_MOVE) {
// 在按下手指的基礎(chǔ)上,開始滑動(dòng)
if (isTop && !isPulling) {
// listView在頂部而且不處于下拉刷新?tīng)顟B(tài),開始下拉
pullStartY = ev.getY();
lastAction = MotionEvent.ACTION_MOVE;
isPulling = true;
}
}
} else if (lastAction == MotionEvent.ACTION_MOVE) {
if (isTop) {
// 下拉
distanceY = ev.getY() - pullStartY;
L.d(TAG, distanceY + "");
if (distanceY > 0) {
distanceY = (float) (Math.exp(-ev.getY() / pullStartY / 40) * distanceY);
// 在下拉狀態(tài)時(shí)取消系統(tǒng)對(duì)move動(dòng)作的響應(yīng),完全由本類響應(yīng)
ev.setAction(MotionEvent.ACTION_DOWN);
} else {
distanceY = 0;
// 在下拉過(guò)程中往上拉動(dòng)該listView使得其回到頂部位置,則將該move動(dòng)作交由系統(tǒng)進(jìn)行響應(yīng)
ev.setAction(MotionEvent.ACTION_MOVE);
}
} else {
// 在下拉過(guò)程中往上拉動(dòng)listView使listView往下滾動(dòng)到其沒(méi)有滾動(dòng)到頂部,則取消其下拉狀態(tài),回到手指按下的初始狀態(tài)
lastAction = MotionEvent.ACTION_DOWN;
isPulling = false;
distanceY = 0;
}
}
return super.onTouchEvent(ev);
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (distanceY > 0) {
if (refreshDrawable == null) {
refreshDrawable = onLoadCallBack.refreshDrawable();
}
if (refreshDrawable == null) {
canvas.drawColor(Color.GRAY);
} else {
int left = getPaddingLeft();
int top = getPaddingTop();
refreshDrawable.setBounds(left, top, getWidth()+left, getHeight()+top);
refreshDrawable.draw(canvas);
}
canvas.save();
canvas.translate(getPaddingLeft(), getPaddingTop() + distanceY);
for (int i=0;i
View child = getChildAt(i);
drawChild(canvas, child, getDrawingTime());
}
canvas.restore();
}
}
/**
* 下拉結(jié)束時(shí)進(jìn)行回滾動(dòng)畫并執(zhí)行刷新動(dòng)作
*/
private void startAnimating() {
int whereToLoad = dp2px(onLoadCallBack.whereToLoad());
final boolean toLoad;
if (distanceY <= whereToLoad) {
pullCancelAnimator = ValueAnimator.ofFloat(distanceY, 0);
toLoad = false;
} else {
pullCancelAnimator = ValueAnimator.ofFloat(distanceY, whereToLoad);
toLoad = true;
}
pullCancelAnimator.setDuration((long) (DEFAULT_BASE_ANIMATING_TIME_PER_100DP*px2dp(distanceY)/100));
pullCancelAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
pullCancelAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
distanceY = (float) animation.getAnimatedValue();
ViewCompat.postInvalidateOnAnimation(PullToRefreshListView.this);
}
});
pullCancelAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
post(new Runnable() {
@Override
public void run() {
pullCancelAnimator = null;
if (toLoad) {
onLoadCallBack.onLoad();
}
}
});
}
@Override
public void onAnimationCancel(Animator animation) {
post(new Runnable() {
@Override
public void run() {
pullCancelAnimator = null;
if (toLoad) {
onLoadCallBack.cancelLoad();
}
}
});
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
pullCancelAnimator.start();
}
private void cancelAnimating() {
if (pullCancelAnimator != null) {
pullCancelAnimator.cancel();
}
}
private float px2dp(float pxvalue) {
return (pxvalue - 0.5f) /context.getResources().getDisplayMetrics().density;
}
private int dp2px(float dpvalue) {
return (int) (dpvalue * context.getResources().getDisplayMetrics().density + 0.5f);
}
/**
* 下拉刷新的回調(diào)
*/
public interface OnLoadCallBack {
/**
* 下拉結(jié)束后將listView定位到哪個(gè)位置等待刷新完成
* @return listView的定位y坐標(biāo)值,in dp
*/
int whereToLoad();
/**
* 下拉結(jié)束后進(jìn)行刷新的回調(diào)
*/
void onLoad();
/**
* 取消刷新
*/
void cancelLoad();
/**
* 下拉刷新的背景
* @return 背景drawable
*/
Drawable refreshDrawable();
}
/**
* 設(shè)置下拉刷新回調(diào)
* @param cb 回調(diào)
*/
public void setOnLoadCallBack(OnLoadCallBack cb) {
this.onLoadCallBack = cb;
}
/**
* 刷新動(dòng)作結(jié)束后調(diào)用該方法結(jié)束刷新,使得listView回滾到頂部
*/
public void setLoadingFinish() {
startAnimating();
}
}
以上這篇android 有阻尼下拉刷新列表的實(shí)現(xiàn)方法就是小編分享給大家的全部?jī)?nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
總結(jié)
以上是生活随笔為你收集整理的android自带下拉阻尼动画,android 有阻尼下拉刷新列表的实现方法的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 福庆板材好在哪里呢?想用质量可靠的品牌。
- 下一篇: 为什么胃胀还想吃东西?