多種姿勢花式實現薄荷Loading動畫(上)
本文屬於Android技術論述文章,閱讀完大致需要五分鐘
原創文章,轉載請註明出處。
沒時間的小夥伴可以直接跳過文章,點選專案地址,如果喜歡的話,順手給個star那是極好的【嬌羞……】
好吧。先說一下為什麼要做這個專案。
事情發生在前幾天的一個夜晚,我開啟薄荷,並點選播放。隨著視訊裡妹子的動作,我的身體也有規律地擺動了起來,不一會兒就開始喘起了粗氣,汗流浹背……
隨著妹子節奏的加快,我也情不自禁加快了頻率,眼看著就要【不可描述的累癱】的時候。我看到了這個畫面。我摔!深蹲還差10個就到600個呢!:
這~~,就是我要的Loading圖!
先上本次最終實現的效果圖吧,顏色當然選擇今年最流行的原諒色:
思路分析
- 1、整個圖形的形狀如何繪製
- 2、如何讓線條動起來
整個圖形的形狀分析
- 好了,首先我們來分析一下這個圖案,如果是靜態的,那麼如何繪製?
很簡單,拆分。我們將圖形拆開分解,然後再看。分析細節和步驟,這是要點。
我這裡將這個圖分成了三份。- 第一個,也就是葉柄。也就是下面那一條小小的豎線。原Loading圖中不甚明顯,但還是有的。葉柄沒什麼說的,直線就可以了。
- 第二個,葉子的左輪廓邊緣和右輪廓邊緣。這是一段下肥上窄的弧線,橢圓擷取感覺不妥,我這裡採用的是貝塞爾二階曲線。有關Android貝塞爾相關的知識大家可以看看這篇文章。
- 第三個,也就是葉片的脈絡,線和線交叉連線,沒什麼可說的。
- 那麼重點其實就是葉子左右輪廓的繪製了,我畫了一張草圖。大家可以看看:
其中黑色的框作為View的邊界。A點是左輪廓曲線的起點,B點事貝塞爾曲線的控制點,我把它定義到了View的左邊框那裡。C點事整個貝塞爾曲線的終點,D點則是實際上曲線的最高點。
右輪廓則和左輪廓是映象存在。
圖有點潦草,不過應該還看得懂。
- 好了,靜態圖形拆解完畢。接著看,如何讓圖動起來。
如何讓線條動起來
整個專案中,如何讓線條真正的動起來才是要點。剛開始在這裡的思路,是想使用canvas.drawCircle
繪製在一張Bitmap上,以點匯面。後面實現起來發現,這種方式特別不靠譜。
為什麼不靠譜呢?因為點連線成線,每次移動的速率和距離都得計算,很麻煩。很容易出現斷點的情況。
最後,我採用的是讓canvas
去繪製一段Path
路徑,然後Path
路徑不停的重新整理改變。這樣做的好處,是Path
更加直觀易於控制。而且還不用多繪製一張Bitmap
。
整個專案中,自定義的View,LeafAnimView
做的工作很少,只是在onDraw
方法內,調起了繪製而已。具體的繪製都交給LeafAtom
了。物件導向嘛。
具體的思路,是我把總時間按比例分成四部分。生成四個屬性動畫,在屬性動畫的監聽裡作
Path
的x和y的變化。在繪製的時候,只需要將這四個動畫依次播放,即可得到每個時間段的具體運動值。而且還是均勻變化的。LeafAnimView
內部作為動畫引擎的是一個ValueAnimator
,使用它來觸發View的onDraw。同時也使用它來控制整個動畫的時間。
mValueAnimator = ValueAnimator.ofFloat(0, 1);
mValueAnimator.setDuration(5000);
mValueAnimator.setRepeatCount(ValueAnimator.INFINITE);
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
invalidate();
}
});
-
LeafAtom
類內部接受到這個總時長,然後將運動總時間分割,根據比例計算出繪製葉柄、左右輪廓、脈絡的動畫時間。
-------在LeafAnimView類內部---------
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (null == mLeafAtom) {
//傳入總時長
mLeafAtom = new LeafAtom(getWidth(), getHeight(), mValueAnimator.getDuration());
}
if (!mValueAnimator.isStarted()) {
mValueAnimator.start();
}
//開始繪製
mLeafAtom.drawGraph(canvas, mPaint);
}
-------在LeafAtome類---------------
public static final float PETIOLE_RATIO = 0.1f;//葉柄所佔比例
public LeafAtom(int width, int height, long duration) {
mWidth = width;
mHeight = height;
mPetioleTime = (long) (duration * PETIOLE_RATIO);//繪製葉柄的時間
mArcTime = (long) (duration * (1 - PETIOLE_RATIO) * 0.4f);//左右輪廓弧線的時間
mLastLineTime = duration - mPetioleTime - mArcTime * 2;//最後一段葉脈的時間
mBezierBottom = new PointF(mWidth * 0.5f, mHeight * (1 - PETIOLE_RATIO));//左側輪廓底部點
mBezierControl = new PointF(0, mHeight * (1 - 3 * PETIOLE_RATIO));//左側輪廓控制點
mBezierTop = new PointF(mWidth * 0.5f, 0);//左側輪廓頂部結束點
mVeinBottomY = mHeight * (1 - PETIOLE_RATIO) - 10;//右側輪廓底部點Y軸座標,稍稍低一點
mOneNodeY = mVeinBottomY * 4 / 5;//第一個節點的Y軸座標
mTwoNodeY = mVeinBottomY * 2 / 5;//第二個節點Y軸座標
initEngine();
setOrginalStatus();
}
- 在
LeafAtom
的建構函式中,得到每一個階段動畫的時間,然後生成四個屬性動畫,在這個屬性動畫的監聽裡去做Path的x和y座標的值變化。
/**
* 初始化path引擎
*/
private void initEngine() {
//葉柄動畫,Y軸變化由底部運動到葉柄高度的地方
mPetioleAnim = ValueAnimator.ofFloat(mHeight, mHeight * (1 - PETIOLE_RATIO)).setDuration(mPetioleTime);
//左右輪廓貝塞爾曲線,只需要只奧時間變化是從0~1的。起點、控制點、結束點都知道了
mArcAnim = ValueAnimator.ofFloat(0, 1.0f).setDuration(mArcTime);
//繪製葉脈的動畫
mLastAnim = ValueAnimator.ofFloat(mVeinBottomY, 0).setDuration(mLastLineTime);
mPetioleAnim.setInterpolator(new LinearInterpolator());
mArcAnim.setInterpolator(new LinearInterpolator());
mLastAnim.setInterpolator(new LinearInterpolator());
mArcRightAnim = mArcAnim.clone();
mPetioleAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mY = (float) animation.getAnimatedValue();
}
});
mArcAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
computeArcPointF(animation, true);
}
});
mArcRightAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
computeArcPointF(animation, false);
}
});
mLastAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mY = (float) animation.getAnimatedValue();
float tan = (float) Math.tan(Math.toRadians(30));
if (mY <= mOneNodeY && mY > mTwoNodeY) {
mOneLpath.moveTo(mX, mOneNodeY);
mOneRpath.moveTo(mX, mOneNodeY);
//這裡的引數x和y代表相對當前位置偏移量,y軸不加偏移量會空一截出來,這裡的15是經驗值
mMainPath.addPath(mOneLpath, 0, EXPRIENCE_OFFSET);
mMainPath.addPath(mOneRpath, 0, EXPRIENCE_OFFSET);
//第一個節點和第二個節點之間
float gapY = mOneNodeY - mY;
mOneLpath.rLineTo(-gapY * tan, -gapY);
mOneRpath.lineTo(mX + gapY * tan, mY);
} else if (mY <= mTwoNodeY) {
mTwoLpath.moveTo(mX, mTwoNodeY);
mTwoRpath.moveTo(mX, mTwoNodeY);
//第二個節點,為避免線超出葉子,取此時差值的一半作計算
float gapY = (mTwoNodeY - mY) * 0.5f;
mMainPath.addPath(mTwoLpath, 0, EXPRIENCE_OFFSET);
mMainPath.addPath(mTwoRpath, 0, EXPRIENCE_OFFSET);
mTwoLpath.rLineTo(-gapY * tan, -gapY);
mTwoRpath.rLineTo(gapY * tan, -gapY);
}
}
});
mEngine = new AnimatorSet();
mEngine.playSequentially(mPetioleAnim, mArcAnim, mArcRightAnim, mLastAnim);
mEngine.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
setOrginalStatus();
}
});
}
- 計算貝塞爾曲線運動過程中的方法。貝塞爾曲線是有一個函式的,我們知道起點、控制點、終點的話,就可以根據時間計算出此時此刻的x和y的座標。而這個時間變化是從0~1變化的。謹記。
private void computeArcPointF(ValueAnimator animation, boolean isLeft) {
float ratio = (float) animation.getAnimatedValue();
//ratio從0~1變化,左右輪廓三個點不一樣
PointF bezierStart = isLeft ? mBezierBottom : mBezierTop;
PointF bezierControl = isLeft ? mBezierControl : new PointF(mWidth, mHeight * (1 - 3 * PETIOLE_RATIO));
PointF bezierEnd = isLeft ? mBezierTop : new PointF(mWidth * 0.5f, mVeinBottomY);
PointF pointF = calculateCurPoint(ratio, bezierStart, bezierControl, bezierEnd);
mX = pointF.x;
mY = pointF.y;
}
private PointF calculateCurPoint(float t, PointF p0, PointF p1, PointF p2) {
PointF point = new PointF();
float temp = 1 - t;
point.x = temp * temp * p0.x + 2 * t * temp * p1.x + t * t * p2.x;
point.y = temp * temp * p0.y + 2 * t * temp * p1.y + t * t * p2.y;
return point;
}
-
葉脈的繪製,在節點一和節點二,分別加上兩個向左和向右伸展開的Path路徑即可。
- 需要說明的是,
lineTo
和rLineTo
的區別,lineTo
的引數代表的就是目標引數,而rLineTo
的引數代表的是,目標引數和起點引數的差值。
- 需要說明的是,
最後在
drawGraph
函式中,啟動這個動畫集合:
public void drawGraph(Canvas canvas, Paint paint) {
if (mEngine.isStarted()) {
canvas.drawPath(mMainPath, paint);
mMainPath.lineTo(mX, mY);
} else {
mEngine.start();
}
}
以上,就是本次專案的主要思路了。相關注釋程式碼裡都寫的很清楚了,專案地址在這裡。仿薄荷Loading動畫,大家走過路過千萬別忘了給個Star啊。
下次還是這個動畫,我會嘗試一種新的方式來實現這個動畫~~
相關文章
- 小程式各種姿勢實現登入
- 開啟Flutter動畫的另一種姿勢——FlareFlutter動畫
- 實現同比、環比計算的N種姿勢
- 解鎖多種JavaScript陣列去重姿勢JavaScript陣列
- 10種CSS3實現的loading動畫,挑一個走吧?CSSS3動畫
- 幀動畫的多種實現方式與效能對比動畫
- CSS 並不簡單–例項帶你領略實現SVG動畫的姿勢CSSSVG動畫
- CSS 並不簡單--例項帶你領略實現SVG動畫的姿勢CSSSVG動畫
- 漲姿勢系列之——核心環境下花式獲得CSRSS程式id
- 巧用 CSS 實現動態線條 Loading 動畫CSS動畫
- PPT中LOADING澆注動畫的實現方法動畫
- 建立 React 元件三種“姿勢”React元件
- 換個姿勢上傳?el-upload + qiniu-js 的實現JS
- 實現Flutter彈窗的正確姿勢..Flutter
- Guava Cache使用的三種姿勢Guava
- PHP 檔案操作的各種姿勢PHP
- 解鎖跨域的九種姿勢跨域
- 程式碼除錯的N種姿勢除錯
- Python爬蟲的N種姿勢Python爬蟲
- 【CSS】實現10種簡單的loading效果CSS
- 強大的CSS:3種姿勢實現26個英文字母的案例CSS
- 【譯】Flutter中的花式背景動畫Flutter動畫
- 實戰小技巧19:List轉Map List的幾種姿勢
- 直播平臺搭建,自定義View實現loading動畫載入View動畫
- 用Vue來實現圖片上傳多種方式Vue
- 這才是實現分散式鎖的正確姿勢!分散式
- 「分散式」實現分散式鎖的正確姿勢?!分散式
- 換一個帥一點姿勢實現DexHunter
- 檔案上傳漏洞(繞過姿勢)
- ProGuard 在 Android 上的使用姿勢Android
- Powershell惡意程式碼的N種姿勢
- 【吐血整理】Git的各種撤銷姿勢Git
- Windwos密碼匯出的幾種姿勢密碼
- 跨頁面通訊的各種姿勢
- Python 連線 MySQL 的幾種姿勢PythonMySql
- 使用CSS3實現超炫的Loading(載入)動畫效果CSSS3動畫
- android實現登入,Login姿勢對不對?Android
- 妙用 background 實現花式文字效果