android使用Ultra-PullToRefresh實(shí)現(xiàn)下拉刷新自定義代碼
下拉刷新中Ultra-Pull-To-Refresh一直是我最喜歡用的了,這里自定義一個(gè)HeaderView的樣式。和普通的樣式略微有些區(qū)別。先看效果圖

一眼看上去和普通下拉刷新樣式?jīng)]啥區(qū)別,但仔細(xì)看會(huì)發(fā)現(xiàn)下拉時(shí)的頭部是蓋在內(nèi)容上的(為了簡(jiǎn)便,這里整個(gè)布局內(nèi)容就一張圖片)。而PtrFrameLayout默認(rèn)布局樣式是將header放置在內(nèi)容上方,下拉時(shí)從上到下逐漸顯示。要實(shí)現(xiàn)這種頭部覆蓋在屏幕內(nèi)容上的效果就需要我們另外想辦法了。
方案1:修改庫文件的,將headerView的顯示位置放置在內(nèi)容上方。由于PtrFrameLayout本身自己是一個(gè)ViewGroup,修改其中的onLayout的代碼即可實(shí)現(xiàn)該樣式

但是,這里考慮到這里L(fēng)ayout修改后可能會(huì)導(dǎo)致的下拉刷新原本功能的一系列問題,想想還是直接放棄。
方案2:不修改庫文件,HeaderView的位置不變,只是將headerView的內(nèi)容顯示到content上面。這樣的話HeaderView的內(nèi)容顯示就超出了其自身邊界,聽說在布局上加上一句神奇的代碼可以實(shí)現(xiàn),于是自己去嘗試了下,確實(shí)真的可以。所以就選擇方案2繼續(xù)研究。
<in.srain.cube.views.ptr.PtrFrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/ptr_layout_activity" android:layout_width="match_parent" android:layout_height="match_parent" android:clipChildren="false">
確定方案2后剩下的就是和普通自定義頭部差不多的步驟。自定義一個(gè)View實(shí)現(xiàn)PtrUIHandler的回調(diào)。其中用到的幾張圖片
首先觀察下拉刷新的過程可以知道,整個(gè)下拉刷新過程中的幾種狀態(tài)。

狀態(tài)1:開始下拉時(shí)底部顯示弧線,黃色小人眼睛閉著(左1圖片),此時(shí)下拉的高度不足以觸發(fā)刷新操作;
狀態(tài)2:下拉到可以觸發(fā)刷新操作的高度后眼睛睜開(左2圖片);
狀態(tài)3:松手后刷新過程中的動(dòng)作,動(dòng)作由后面5張圖輪播切換顯示。
下拉刷新的距離以及狀態(tài)判斷處理在onUIPositionChange回調(diào)方法中
@Override
public void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, PtrIndicator ptrIndicator) {
//下拉距離
posY = ptrIndicator.getCurrentPosY();
if (!isRefresh) {
if (isComplete) {
//剛剛完成了下拉刷新操作,還沒有重置事件。使用圖片2.保持上下邊距,下拉上推底部弧線不顯示
drawable = ResourcesUtils.getDrawable(R.drawable.home_loading_1);
flag = 4;
} else {
//未觸發(fā)下拉刷新時(shí)拉著玩
if (posY < turning) {
//使用圖片1
drawable = ResourcesUtils.getDrawable(R.drawable.home_loading_0);
flag = 0;
} else if (posY < measureHeight * RATIO_TO_REFRESH) {
//使用圖片1
//顯示下面的弧線
drawable = ResourcesUtils.getDrawable(R.drawable.home_loading_0);
flag = 1;
} else {
//下拉距離已經(jīng)達(dá)到了可以觸發(fā)下拉刷新的位置。使用圖片2
drawable = ResourcesUtils.getDrawable(R.drawable.home_loading_1);
flag = 2;
}
}
} else {
//當(dāng)前正在下拉刷新的時(shí)候.自己手動(dòng)去滑動(dòng)圖片不做變化
flag = 3;
if (!animation.isHasStart()) {
startAnimation(animation);
animation.setHasStart(true);
}
}
invalidate();
}
因?yàn)樵诘却⑿逻^程中也可以繼續(xù)滑動(dòng),為了刷新的正常顯示,這里添加了isRefresh(是否正在刷新)以及isComplete(是否刷新完成)的判斷。另外,由于最后刷新時(shí)保持顯示的是后面5張圖,因此控件高度的measureHeight需要與后面圖的大小有關(guān),但是后面圖片小黃人的上下邊距太小,看上去視覺效果不太好,在設(shè)置measureHeight的時(shí)候特地增加了上下邊距
Drawable animationDrawable = ResourcesUtils.getDrawable(R.drawable.home_loading_2); measureHeight = padding * 2 + animationDrawable.getIntrinsicHeight();
準(zhǔn)備工作就緒,接下來就是重點(diǎn)onDraw中的方法。根據(jù)不同的狀態(tài)繪制,但是這里有個(gè)麻煩的地方,上面7張圖中,小黃人大小是一樣的,但是后面5張圖周圍有了云朵背景,圖片整體比前兩張要大,所以在狀態(tài)切換時(shí),圖片的繪制范圍需要格外注意。
1.繪制弧線階段,flag=1和2
switch (flag) {
case 1:
case 2:
controlY = (int) ((posY - turning) * RATIO_TO_REFRESH) > dragDistance * 2
? dragDistance * 2 + measureHeight : (int) ((posY - turning) * RATIO_TO_REFRESH)
+ measureHeight;
//下拉弧度
mPath.reset();
mPath.moveTo(0, measureHeight);
mPath.quadTo(getWidth() / 2, controlY, getWidth(), measureHeight);
mPath.lineTo(getWidth(), 0);
mPath.lineTo(0, 0);
mPath.close();
mDrawableRect.set((getWidth() - drawable.getIntrinsicWidth()) / 2,
getBsrPositionY(controlY) - drawable.getIntrinsicHeight() * 2 / 3,
(canvas.getWidth() + drawable.getIntrinsicWidth()) / 2,
getBsrPositionY(controlY) + drawable.getIntrinsicHeight() / 3);
//繪制弧線
mPaint.setXfermode(null);
canvas.drawPath(mPath, mPaint);
canvas.save();
canvas.clipPath(mPath);
drawable.setBounds(mDrawableRect);
drawable.draw(canvas);
canvas.restore();
break;
其中弧線是一條二階貝塞爾曲線。

代碼中controlY為控制點(diǎn)P1的Y坐標(biāo),turning值表示下拉多少距離后開始繪制弧線(可以修改值來看看效果)。在這里我們的控制點(diǎn)X坐標(biāo)在屏幕的中心(t=0.5),P0和P2的X坐標(biāo)也是確定的,只需要求得對(duì)應(yīng)的曲線Y軸最高點(diǎn)即可。又因?yàn)镻0和P2Y軸坐標(biāo)相同,都為measureHeight,所以這里二階曲線的最高點(diǎn)左邊簡(jiǎn)化計(jì)算為
/**
* 獲取貝塞爾曲線最高點(diǎn)位置
*
* @param y 中間控制點(diǎn)的y坐標(biāo)
* @return
*/
private int getBsrPositionY(int y) {
//起點(diǎn)和終點(diǎn)確定的
return measureHeight + (y - measureHeight) / 2;
}
采用clipPath方式裁剪畫布,使得圖片按弧線顯示部分。
2.放手后開始刷新階段,flag = 3
圖片循環(huán)輪播,計(jì)算好圖片位置與時(shí)間間隔,定時(shí)切換圖片
mDrawableRect.set((getWidth() - drawable.getIntrinsicWidth()) / 2,
padding,
(getWidth() + drawable.getIntrinsicWidth()) / 2,
padding + drawable.getIntrinsicHeight());
if (drawable != null) {
drawable.setBounds(mDrawableRect);
drawable.draw(canvas);
}
if (SystemClock.elapsedRealtime() - lastTime > DURATION) {
//超過間隔后刷新動(dòng)畫
changeDrawable();
lastTime = SystemClock.elapsedRealtime();
}
但是在這里顯示上如果松手,弧線會(huì)立馬消失,顯示上不太友好。不過PtrFrameLayout自身帶有一個(gè)參數(shù)mDurationToClose,可以理解為放手后界面回彈到刷新高度所預(yù)留的時(shí)間,可以在這個(gè)時(shí)間內(nèi)對(duì)顯示做些優(yōu)化。在這里我根據(jù)這個(gè)時(shí)間值做了弧線緩慢上彈的動(dòng)畫。
class MyAnimation extends Animation {
boolean hasStart;
public boolean isHasStart() {
return hasStart;
}
public void setHasStart(boolean hasStart) {
this.hasStart = hasStart;
}
@Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
setDuration(mDurationToClose);
//設(shè)置動(dòng)畫結(jié)束后保留效果
setInterpolator(new AccelerateDecelerateInterpolator());
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
//從0-1.逐漸變化(弧線回彈動(dòng)畫),位置從controlY到0變化
flag = 3;
proportion = interpolatedTime;
invalidate();
}
}
在onDraw中對(duì)應(yīng)的顯示
case 3:
//正在刷新時(shí),執(zhí)行彈上去的動(dòng)畫
if (proportion < 1.0f) {
mPath.reset();
mPath.moveTo(0, measureHeight);
mPath.quadTo(getWidth() / 2, (controlY - measureHeight) * (1 - proportion) + measureHeight, getWidth(), measureHeight);
mPath.lineTo(getWidth(), 0);
mPath.lineTo(0, 0);
mPath.close();
canvas.drawPath(mPath, mPaint);
mDrawableRect.set((getWidth() - drawable.getIntrinsicWidth()) / 2,
(int) ((getBsrPositionY((int) controlY) - drawable.getIntrinsicHeight() - padding) * (1 - proportion)) + padding,
(getWidth() + drawable.getIntrinsicWidth()) / 2,
(int) ((getBsrPositionY((int) controlY) - (padding + drawable.getIntrinsicHeight())) * (1 - proportion)) + (padding + drawable.getIntrinsicHeight()));
if (drawable != null) {
drawable.setBounds(mDrawableRect);
drawable.draw(canvas);
}
} else {..}
具體效果如果看上面gif圖不清晰的話可以將代碼下載下來自己運(yùn)行,可以將該部分注釋后對(duì)比兩種效果,對(duì)比還是蠻明顯的。
3.刷新完成后還原的過程
case 4:
//刷新完成后,圖片此時(shí)換成了1,變小了。也要保持圖片的居中
mDrawableRect.set((getWidth() - drawable.getIntrinsicWidth()) / 2,
(measureHeight - drawable.getIntrinsicHeight()) / 2,
(getWidth() + drawable.getIntrinsicWidth()) / 2,
(measureHeight + drawable.getIntrinsicHeight()) / 2);
if (drawable != null) {
drawable.setBounds(mDrawableRect);
drawable.draw(canvas);
}
break;
4.初始狀態(tài),未下拉或者下拉高度未達(dá)到繪制弧線的高度
case 0:
default:
//圖片位置
mDrawableRect.set((getWidth() - drawable.getIntrinsicWidth()) / 2,
measureHeight - drawable.getIntrinsicHeight(),
(getWidth() + drawable.getIntrinsicWidth()) / 2,
measureHeight);
if (drawable != null) {
drawable.setBounds(mDrawableRect);
drawable.draw(canvas);
}
break;
到這里整個(gè)onDraw方法就完成了,其中關(guān)于圖片繪制與顯示位置的計(jì)算費(fèi)了不少腦細(xì)胞。然后在代碼中添加上PtrFrameLayout的配置即可使用

這些配置屬性也可以寫在xml中,下拉刷新的自定義基本就完成了。不過別高興太早,在繪制弧線的時(shí)候封閉區(qū)域采用了顏色填充,這個(gè)填充顏色就是paint的顏色,這個(gè)顏色要和跟布局顏色保持一致,不然自己試試看,這里我沒有給PtrFrameLayout設(shè)置背景色,而是采用了Theme,設(shè)置windowBackground的顏色。具體的代碼里面也有,就不繼續(xù)貼了,反正如果不設(shè)一樣的話你看上去會(huì)有bug。
代碼下載地址:TestUltraPullToRefresh_jb51.rar
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- android使用PullToRefresh框架實(shí)現(xiàn)ListView下拉刷新上拉加載更多
- android使用PullToRefresh實(shí)現(xiàn)下拉刷新和上拉加載
- Android使用PullToRefresh完成ListView下拉刷新和左滑刪除功能
- Android開源項(xiàng)目PullToRefresh下拉刷新功能詳解2
- Android開源項(xiàng)目PullToRefresh下拉刷新功能詳解
- Android下拉刷新控件PullToRefresh實(shí)例解析
- Android使用PullToRefresh實(shí)現(xiàn)上拉加載和下拉刷新效果的代碼
- Android實(shí)現(xiàn)簡(jiǎn)單的下拉刷新pulltorefresh
- Android程序開發(fā)之使用PullToRefresh實(shí)現(xiàn)下拉刷新和上拉加載
- Android PullToRefreshLayout下拉刷新控件的終結(jié)者
- Android帶刷新時(shí)間顯示的PullToRefresh上下拉刷新
相關(guān)文章
Android編程實(shí)現(xiàn)二維碼的生成與解析
這篇文章主要介紹了Android編程實(shí)現(xiàn)二維碼的生成與解析方法,結(jié)合實(shí)例分析了Android二維碼的生成與讀取二維碼的相關(guān)技巧,并提供了二維碼jar包供讀者下載,需要的朋友可以參考下2015-11-11
Android RelativeLayout相對(duì)布局屬性簡(jiǎn)析
在Android應(yīng)用開發(fā)過程中,為了界面的美觀考慮,經(jīng)常會(huì)使用到布局方面的屬性,本文就以此問題深入解析,詳解一下Android RelativeLayout相對(duì)布局屬性在實(shí)際開發(fā)中的應(yīng)用,需要的朋友可以參考下2012-11-11
android編程實(shí)現(xiàn)對(duì)話框的封裝實(shí)例
這篇文章主要介紹了android編程實(shí)現(xiàn)對(duì)話框的封裝,以實(shí)例形式分析了Android針對(duì)對(duì)話框的相關(guān)操作技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-11-11
Android編程實(shí)現(xiàn)通訊錄中聯(lián)系人的讀取,查詢,添加功能示例
這篇文章主要介紹了Android編程實(shí)現(xiàn)通訊錄中聯(lián)系人的讀取,查詢,添加功能,涉及Android權(quán)限控制及通訊錄相關(guān)操作技巧,需要的朋友可以參考下2017-07-07
Android中ImageView.src設(shè)置圖片拉伸、填滿控件的方法
最近公司有個(gè)需求,要展示客戶公司的企業(yè)形象,用一張圖片放在ImageView中實(shí)現(xiàn),但是發(fā)現(xiàn)圖片并沒有填滿,而是在上下邊上留出了一點(diǎn)空白,下面這篇文章主要跟大家介紹了Android中ImageView.src設(shè)置圖片拉伸、填滿控件的方法,需要的朋友可以參考下。2017-06-06
Android實(shí)現(xiàn)手機(jī)攝像頭的自動(dòng)對(duì)焦
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)手機(jī)攝像頭的自動(dòng)對(duì)焦的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11
Flutter之Timer實(shí)現(xiàn)短信驗(yàn)證碼獲取60s倒計(jì)時(shí)功能的代碼
這篇文章主要介紹了Flutter之Timer實(shí)現(xiàn)短信驗(yàn)證碼獲取60s倒計(jì)時(shí)功能,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07
Android開發(fā)簡(jiǎn)單實(shí)現(xiàn)搖動(dòng)動(dòng)畫的方法
這篇文章主要介紹了Android開發(fā)簡(jiǎn)單實(shí)現(xiàn)搖動(dòng)動(dòng)畫的方法,結(jié)合實(shí)例形式分析了Android搖動(dòng)動(dòng)畫的布局與功能簡(jiǎn)單實(shí)現(xiàn)方法,需要的朋友可以參考下2017-10-10

