微博 App 的用戶頭像有一個圓形旋轉進度條的加載效果,看上去效果很是不錯,如圖所示:java
聽說 Instagram 也採用了這種效果。最近抽空研究了一下,最後實現的效果是這樣:android
基本上能模擬出個大概,代碼量不大,來說講實現思路吧。git
模擬一種動畫效果,首先須要仔細分析其運做過程,找到其中的物理規律,從而肯定實現方案。像這種運動速度較快的動畫,通常不是很容易看清。能夠先經過錄屏軟件,錄取動畫運做的過程,而後藉助一些輔助工具放慢放大,好比 PS,反覆重複播放幾遍,基本上就能看出動畫的運做規律了。程序員
回到這裏的加載效果,拆分開來,能夠理解爲畫筆上的兩層繪製和時間上的兩段過程。時間上,很明顯能夠看出分爲前 360 度和後 360 度,主要在畫筆上:github
1,單一完整的圓弧繪製。前 360 度,從 360 度的圓弧到 0 度圓弧的遞減過程;後 360 度,從 0 度圓弧到 360 度圓弧的遞增過程。canvas
2,重複片斷的圓弧繪製。前 360 度,從零開始,逐漸遞增,直到多段填滿圓周;後 360 度,反過來,逐漸遞減,直到數量爲零。微信
其餘的就是細節的處理,後面具體實現時再說起,咱們先來看看如何實現這兩個核心流程的繪製。app
因爲這兩個流程使用的畫筆屬性相同,因此使用一個 Paint 對象便可,這段代碼沒什麼好講的,就是一些初始化工做:ide
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(mArcWidth);
mPaint.setStrokeCap(Paint.Cap.ROUND);複製代碼
注意,與微博原圖效果不一樣的是,個人效果圖中使用到了漸變色圓環,這樣效果更好看一些。使用 setShader() 方法能夠給畫筆設置漸變色,實現方式是:函數
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
......
Shader shader = new LinearGradient(0f, 0f, mWidth, mHeight,
mStartColor, mEndColor, Shader.TileMode.CLAMP);
mPaint.setShader(shader);
}複製代碼
Shader 子類有不少,這裏使用的是線性漸變類 LinearGradient,因爲須要使用 View 寬高,因此放在了 onSizeChanged() 函數裏面。
核心計算和繪製工做都在 onDraw() 方法裏面,看下這裏的代碼:
@Override
protected void onDraw(Canvas canvas) {
if (maxAngle <= 360) {
float angle = 0;
canvas.rotate(mRatio * maxAngle / 360, mWidth / 2, mHeight / 2);
canvas.drawArc(mRectF, maxAngle, 360 - maxAngle, false, mPaint);
while (angle <= maxAngle) {
float length = mArcRadian * angle / maxAngle;
canvas.drawArc(mRectF, 0, length, false, mPaint);
canvas.rotate(mArcSpacing, mWidth / 2, mHeight / 2);
angle += mArcSpacing;
}
} else {
float angle = 0;
canvas.rotate(mRatio + mRatio * (maxAngle - 360) / 360, mWidth / 2, mHeight / 2);
canvas.drawArc(mRectF, 0, maxAngle - 360, false, mPaint);
canvas.rotate(maxAngle - 360, mWidth / 2, mHeight / 2);
while (angle <= 720 - maxAngle) {
float length = mArcRadian * angle / (720 - maxAngle);
canvas.drawArc(mRectF, 0, length, false, mPaint);
canvas.rotate(mArcSpacing, mWidth / 2, mHeight / 2);
angle += mArcSpacing;
}
}
if (maxAngle <= 720) {
maxAngle += mArcSpacing;
postInvalidateDelayed(30);
}
}複製代碼
前面提到,動畫在時間上分先後 360 度的兩段過程,因此這裏定義了一個 maxAngle 變量來定義時間的變化。能夠看到,先後 360 度的繪製代碼看上去差很少,但仍是有很大區別的。須要理解的地方有:
1,多個小段圓弧能夠利用畫布旋轉的方式輕鬆實現,也就是 canvas.rotate() 方法,上面代碼中的 while 循環部分。這裏有個細節處理,就是每段圓弧的弧度有個遞增變化。
2,整個 View 給人的感受有一種旋轉的效果,爲了實現這個效果,在每次繪製前,都增長了旋轉的步驟,也就是這行代碼:
canvas.rotate(mRatio * maxAngle / 360, mWidth / 2, mHeight / 2);複製代碼
其中 mRatio 表示整個動畫結束時,View 相比初始狀態時總體旋轉的角度。 這裏我設置的默認值是 60,值越大,動畫執行時呈現出越快的旋轉效果。你們能夠修改源碼,本身嘗試一下。
3,還有一點就是,maxAngle 變量每次增量值的設置,必定要設置爲相鄰兩段片斷圓弧的間距弧度。這樣作的目的是,保證每次繪製,片斷圓弧都能有一個完整的遞增或遞減。不然,動畫執行時,看上會發生視覺上的抖動效果。
基本上就是這樣,代碼量雖然很少,可是各類細節的處理仍是耗費了不少時間調整。原本想使用一些圖例展現一下每行代碼的做用,可是太費時間了。若是感興趣的,徹底能夠本身下載一下源碼,修改試試看。
通過簡單地封裝,提取出一部分屬性:
<declare-styleable name="CircleLoadingView">
<attr name="circleStartColor" format="color|reference"/>
<attr name="circleEndColor" format="color|reference"/>
<attr name="circleArcWidth" format="integer|reference"/>
<attr name="circleArcRadian" format="integer|reference"/>
<attr name="circleArcSpacing" format="integer|reference"/>
</declare-styleable>複製代碼
注意:中間的頭像部分不是這裏自定義 View 的內容,使用時能夠自由填充內容。好比,看下我這裏的使用方式,仍是比較自由的:
<RelativeLayout android:layout_width="200dp" android:layout_height="200dp" android:layout_centerInParent="true">
<com.yifeng.view.view.CircleLoadingView android:layout_width="match_parent" android:layout_height="match_parent" app:circleStartColor="#ffff00" app:circleEndColor="#ff0000" app:circleArcRadian="5" app:circleArcWidth="10" app:circleArcSpacing="10"/>
<ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="10dp" android:src="@mipmap/ic_avatar_default"/>
</RelativeLayout>複製代碼
源代碼仍是老樣子,統一在 GitHub 上的自定義 View 集錦庫裏,地址以下。:
關於我:亦楓,博客地址:yifeng.studio/,新浪微博:IT亦楓
微信掃描二維碼,歡迎關注個人我的公衆號:安卓筆記俠
不只分享個人原創技術文章,還有程序員的職場遐想
![]()