SwipeRefreshLayout
是Androidx提供了提供的下拉刷新組件聂儒,具體如何使用就不說了虎锚,相信大家也都經(jīng)常用。
1衩婚,效果
首先看一下SwipeRefreshLayout
的默認(rèn)效果:
為了不耽誤你的時間窜护,先看一下最終效果:
2,常用方法
方法 | 解釋 |
---|---|
setColorSchemeResources(int…colorReslds) | 設(shè)置下拉進(jìn)度條的顏色主題非春,參數(shù)可變柱徙,并且是資源id,最多設(shè)置四種不同的顏色奇昙。 |
setProgressBackgroundSchemeResource(int coloRes) | 設(shè)置下拉進(jìn)度條的背景顏色,默認(rèn)白色护侮。 |
isRefreshing() | 判斷當(dāng)前的狀態(tài)是否是刷新狀態(tài)。 |
setOnRefreshListener(SwipeRefreshLayout.OnRefreshListener listener) | 設(shè)置監(jiān)聽储耐,需要重寫onRefresh()方法羊初,頂部下拉時會調(diào)用這個方法,在里面實現(xiàn)請求數(shù)據(jù)的邏輯什湘,設(shè)置下拉進(jìn)度條消失等等长赞。 |
setRefreshing(boolean refreshing) | 設(shè)置刷新狀態(tài),true表示正在刷新闽撤,false表示取消刷新得哆。 |
盡管SwipeRefreshLayout
提供了setColorSchemeResources()
方法可以設(shè)置幾個'圈圈'的顏色,但效果還是差強(qiáng)人意哟旗,作為高級代碼搬運(yùn)工的我實在看不下去了贩据。
于是幫SwipeRefreshLayout
換個免費的皮膚吧栋操。
3,分析
SwipeRefreshLayout
的代碼很簡單乐设,就三個類
-
SwipeRefreshLayout
就是我們在xml中使用的布局,根據(jù)情況攔截并消費手勢事件绎巨,更新Loading視圖的位置近尚,控制其轉(zhuǎn)動角度等,具體邏輯不再這里展開场勤,有需要的大佬自己去看吧戈锻。
-
CircleImageView
下拉時拉出來的圓圈
-
CircularProgressDrawable
下拉及刷新時轉(zhuǎn)動的圈圈
他們?nèi)齻€關(guān)系非常緊密,在SwipeRefreshLayout
中創(chuàng)建了CircleImageView
和CircularProgressDrawable
和媳,將CircularProgressDrawable
設(shè)置給CircleImageView
格遭,然后又將CircleImageView
添加到了SwipeRefreshLayout
中,代碼如下:
public class SwipeRefreshLayout {
// 1留瞳,聲明一個CircleImageView
CircleImageView mCircleVew;
// 1拒迅,聲明一個CircularProgressDrawable
CircularProgressDrawable mProgress;
public SwipeRefreshLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
...
// 構(gòu)造方法中初始化
createProgressView();
...
}
private void createProgressView() {
// 2,初始化mCircleView
mCircleView = new CircleImageView(getContext());
// 2她倘,初始化mProgress
mProgress = new CircularProgressDrawable(getContext());
// 3璧微,將mProgress設(shè)置為mCircleView的Drawable
mCircleView.setImageDrawable(mProgress);
mCircleView.setVisibility(View.GONE);
// 4,添加mCircleView在當(dāng)前ViewGroup中
addView(mCircleView);
}
}
結(jié)論
1硬梁,由于CircularProgressDrawable
是控制這個'皮膚'的關(guān)鍵前硫,因此需要先定義一個Drawable,并實現(xiàn)相應(yīng)的效果荧止。
2屹电,由于SwipeRefreshLayout
沒有提供擴(kuò)展的接口,無法直接使用自定義的Drawable跃巡,因此需要將SwipeRefreshLayout
拷貝一份危号,修改其中的代碼。
3素邪,CircleImageView
貌似是可以使用的葱色,但是其中添加了一個半透明背景,無法通過設(shè)置的方式去除娘香,因此也需要重寫一下苍狰。
4,擼碼
1烘绽,自定義LoadingDrawable
自定義一個Drawable并實現(xiàn)Animatable
接口淋昭,在其中繪制圖形及動畫,具體如何實現(xiàn)可以參考LoadingDrawable安接,效果如下:
2翔忽,重寫SwipeRefreshLayout
將SwipeRefreshLayout
的代碼拷貝一份,重命名為MySwipeRefreshLayout
,如下:
public class MySwipeRefreshLayout extends ViewGroup implements NestedScrollingParent3,
NestedScrollingParent2, NestedScrollingChild3, NestedScrollingChild2, NestedScrollingParent,
NestedScrollingChild {
...
CircularProgressDrawable mProgress;
...
}
只需要將其中的mProgress
改為我們上面定義的LoadingDrawable
即可歇式,然后刪除一些無用的代碼即可驶悟。看一下效果:
這樣就有了我們想要的效果材失,但是發(fā)現(xiàn)動畫中有一個圓形的陰影背景痕鳍。分析一下,首先這個背景我不是我們定義的Drawable中的龙巨,那就看看Drawable所在的CircleImageView
吧笼呆,代碼如下:
class CircleImageView extends ImageView {
...
CircleImageView(Context context) {
super(context);
// 創(chuàng)建一個OvalShape的ShapeDrawable
ShapeDrawable circle = new ShapeDrawable(new OvalShape());
// 繪制
ViewCompat.setBackground(this, circle);
}
private static class OvalShadow extends OvalShape {
...
@Override
public void draw(Canvas canvas, Paint paint) {
// 繪制圓形
canvas.drawCircle(x, y, x, mShadowPaint);
canvas.drawCircle(x, y, x - mShadowRadius, paint);
}
}
}
可見這個背景是在CircleImageView
中繪制的,由于CircleImageView
沒有提供對應(yīng)的方法控制是否繪制背景旨别,因此要想去掉這個背景就需要自定義一個CircleImageView
诗赌。
3,自定義CircleImageView
由于我們的目的是要去掉背景秸弛,因此這個就非常簡單了铭若,只需要把繪制背景的代碼刪掉即可:
public class MyCircleImageView extends androidx.appcompat.widget.AppCompatImageView {
private Animation.AnimationListener mListener;
public MyCircleImageView(Context context) {
super(context);
}
...
public void setAnimationListener(Animation.AnimationListener listener) {
mListener = listener;
}
@Override
public void onAnimationStart() {
super.onAnimationStart();
if (mListener != null) {
mListener.onAnimationStart(getAnimation());
}
}
@Override
public void onAnimationEnd() {
super.onAnimationEnd();
if (mListener != null) {
mListener.onAnimationEnd(getAnimation());
}
}
}
然后將第二步創(chuàng)建的MySwipeRefreshLayout
中的CircleImageView
換成MyCircleImageView
即可,直接看效果吧:
4递览,進(jìn)一步優(yōu)化
給SwipeRefreshLayout
的換膚已經(jīng)完成奥喻,但是感覺哪里總是不對勁,再往下拖動時讓布局跟著一起動效果會不會更好呢非迹?試試吧
思路也很簡單环鲤,就是再下拉時根據(jù)拉動的距離讓內(nèi)部的子View ScrollTo到指定位置,最后松手時ScrollTo到原來的位置即可憎兽。
具體代碼分兩步:
1冷离,跟著滑動
在onTouchEvent()
的ACTION_MOVE
事件中,如果時Dragged
狀態(tài)就會調(diào)用moveSpinner(overscrollTop)
纯命,在其中添加代碼即可:
private void moveSpinner(float overscrollTop) {
float originalDragPercent = overscrollTop / mTotalDragDistance;
float dragPercent = Math.min(1f, Math.abs(originalDragPercent));
float adjustedPercent = (float) Math.max(dragPercent - .4, 0) * 5 / 3;
float extraOS = Math.abs(overscrollTop) - mTotalDragDistance;
float slingshotDist = mCustomSlingshotDistance > 0
? mCustomSlingshotDistance
: (mUsingCustomStart
? mSpinnerOffsetEnd - mOriginalOffsetTop
: mSpinnerOffsetEnd);
float tensionSlingshotPercent = Math.max(0, Math.min(extraOS, slingshotDist * 2)
/ slingshotDist);
float tensionPercent = (float) ((tensionSlingshotPercent / 4) - Math.pow(
(tensionSlingshotPercent / 4), 2)) * 2f;
float extraMove = slingshotDist * tensionPercent * 2;
int targetY = mOriginalOffsetTop + (int) ((slingshotDist * dragPercent) + extraMove);
...
// 增加以下代碼即可
if (targetY >= 0) {
((ViewGroup) mTarget).getChildAt(0).scrollTo(0, -targetY / 2);
}
}
只需要在targetY>=0
時執(zhí)行scrollTo()
即可西剥。
2,返回原處
在onTouchEvent()
的ACTION_UP
事件中亿汞,在Dragged
狀態(tài)松手時會執(zhí)行finishSpinner()
瞭空,在其中將子View回復(fù)到原位即可:
private void finishSpinner(float overscrollTop) {
// 返回原處
((ViewGroup) mTarget).getChildAt(0).scrollTo(0, 0);
...
}
如果你覺得scrollTo(0, 0)
太突兀,可以搞個屬性動畫讓回彈速度變慢一點疗我,這里就不多說了咆畏,看下效果吧:
5,最后
如果你想進(jìn)一步擴(kuò)展吴裤,比如加上一些文字旧找,最近更新時間等也是可以實現(xiàn)的。
最后安利一波我的開源項目:風(fēng)云天氣(https://github.com/wdsqjq/FengYunWeather)麦牺,以上效果就在這里哦~