你沒看錯,右上角的那個大圓就是傳說中的太陽,^_^ 這個動畫的難點在於這個“食物”的繪製上吧,不用懷疑,你還是沒看錯,那些小點就是傳說中的食物 首先一步步來,看到這種效果,第一個想到的就是一個普通的小圓,而這個大圓就用貝塞爾繪製,至於為什麼用貝塞爾而不是直接繪製一個半圓呢,因為食物是繞著半圓的,緊貼 ...
你沒看錯,右上角的那個大圓就是傳說中的太陽,^_^
這個動畫的難點在於這個“食物”的繪製上吧,不用懷疑,你還是沒看錯,那些小點就是傳說中的食物
首先一步步來,看到這種效果,第一個想到的就是一個普通的小圓,而這個大圓就用貝塞爾繪製,至於為什麼用貝塞爾而不是直接繪製一個半圓呢,因為食物是繞著半圓的,緊貼著,你需要拿這個半圓做參照物,不然隨便改改佈局就亂套了,
然後是小圓也需要圍著半圓繞圈,這兩個都需要一個已知的point
接下來就開始繪製半圓了,小圓就直接被忽略了
半圓採用 drawPath.cubicTo 來繪製,需要四個點,其中有兩個控制點
具體怎麼繪製看後面的代碼
接下來繪製食物,這下可能一部分人會有點困惑,食物怎麼繪製,怎麼讓食物緊貼著這個半圓去繪製,那我是不是可以照著這個半圓把Y軸的坐標改改,這樣另外一個半圓就出現了,只是需要改變下樣式,因為食物是一個個小點
這裡設置paint的effect就可以改變繪製的樣式了
就這樣食物出現了
然後就是一個難點了,小圓怎麼沿著食物一路吃下去呢,很顯然不能直接按照食物的繪製方法來繪製了,因為小圓是會動的,需要加入動畫,而不是一下子全繪製,這樣就需要知道食物的坐標了,這樣才能讓小圓繞著食物吃下去
這裡需要用到 Evaluator 根據控制點去計算出它本身的一個繪製坐標,貝塞爾是一個公式,可以推算出它的一個坐標點
package com.fragmentapp.view; import android.graphics.PointF; /** * Created by liuzhen on 2018/1/24. */ public class BezierUtil { /** * B(t) = (1 - t)^2 * P0 + 2t * (1 - t) * P1 + t^2 * P2, t ∈ [0,1] * * @param t 曲線長度比例 * @param p0 起始點 * @param p1 控制點 * @param p2 終止點 * @return t對應的點 */ public static PointF CalculateBezierPointForQuadratic(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; } /** * B(t) = P0 * (1-t)^3 + 3 * P1 * t * (1-t)^2 + 3 * P2 * t^2 * (1-t) + P3 * t^3, t ∈ [0,1] * * @param t 曲線長度比例 * @param p0 起始點 * @param p1 控制點1 * @param p2 控制點2 * @param p3 終止點 * @return t對應的點 */ public static PointF CalculateBezierPointForCubic(float t, PointF p0, PointF p1, PointF p2, PointF p3) { PointF point = new PointF(); float temp = 1 - t; point.x = p0.x * temp * temp * temp + 3 * p1.x * t * temp * temp + 3 * p2.x * t * t * temp + p3.x * t * t * t; point.y = p0.y * temp * temp * temp + 3 * p1.y * t * temp * temp + 3 * p2.y * t * t * temp + p3.y * t * t * t; return point; } }View Code
package com.fragmentapp.view; import android.animation.TypeEvaluator; import android.graphics.PointF; /** * Created by liuzhen on 2018/1/24. */ public class PathEvaluator implements TypeEvaluator<PointF> { private PointF mControlPoint1,mControlPoint2; // public PathEvaluator(PointF controlPoint) { // this.mControlPoint1 = controlPoint; // } public PathEvaluator(PointF controlPoint1,PointF controlPoint2) { this.mControlPoint1 = controlPoint1; this.mControlPoint2 = controlPoint2; } @Override public PointF evaluate(float t, PointF startValue, PointF endValue) { return BezierUtil.CalculateBezierPointForCubic(t, startValue, mControlPoint1,mControlPoint2, endValue); } }View Code
有了這些坐標那小圓就可以沿著食物吃下去了,但是隨著吃的動作又會發現怎麼吃這個問題了,這顯然也不是很好操作,本人想了一會,發現這條路行不通,技術還沒達標,於是另闢蹊徑,用另外的辦法去做了,想想是繪製,那就乾脆在繪製一條背景色的線
這樣食物就會出現被吃掉的效果了,所以在上面有個clears.add的操作
在給小圓添加一張嘴吧
控制angle這個參數,小圓終於可以吃了,然後為了追求更高的一個層次,又添加了一個移動的動畫,然後動畫開始的時候先讓半圓有個上升的過程,這樣頓時看起來高大上一些了
最後stop
使用就是兩個方法,一個start動畫,一個開始吃的動畫操作,兩個方法,在合適的時候調用就行了,直接上代碼
package com.fragmentapp.view.refresh; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Canvas; import android.graphics.DashPathEffect; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PathDashPathEffect; import android.graphics.PathEffect; import android.graphics.PointF; import android.graphics.RectF; import android.util.AttributeSet; import android.util.Log; import android.view.View; import android.view.animation.LinearInterpolator; import com.fragmentapp.R; import com.fragmentapp.helper.TimeUtil; import com.fragmentapp.view.PathEvaluator; import java.util.ArrayList; import java.util.List; /** * Created by liuzhen on 2018/1/24. */ public class SunHeadView extends View implements IHeadView{ private int mWidth; private int mHeight; private Paint effectPaint,facePaint,clearPaint,defPaint;//這裡定義多個屬性都是為了能夠自定義不同的樣式 private RectF rectF = null; private float angle,loadAngle = 45; private ValueAnimator faceVa,arcVa; private int left,top; private boolean isDraw = false; private Path path,foodPath; private PointF startPoint = null,movePoint1 = null,movePoint2 = null,endPoint = null; private List<PointF> clears = null; private PathEffect effect = null; private int faceRadius = 30,foodRadius = 3; public SunHeadView(Context context) { this(context, null, 0); } public SunHeadView(Context context, AttributeSet attrs) { this(context, attrs,0); } public SunHeadView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init(){ path = new Path(); foodPath = new Path(); foodPath.addCircle(0, 0, foodRadius, Path.Direction.CCW); effectPaint = new Paint(); effectPaint.setAntiAlias(true); effectPaint.setStyle(Paint.Style.STROKE); effectPaint.setColor(getResources().getColor(R.color.color_a9a05c)); effect = new PathDashPathEffect(foodPath, 12, -1, PathDashPathEffect.Style.ROTATE); effectPaint.setPathEffect(effect); facePaint = new Paint(); facePaint.setAntiAlias(true); facePaint.setStyle(Paint.Style.FILL); facePaint.setColor(getResources().getColor(R.color.color_a9a05c)); defPaint = new Paint(); defPaint.setAntiAlias(true); defPaint.setStyle(Paint.Style.FILL); defPaint.setColor(getResources().getColor(R.color.color_a9a05c)); rectF = new RectF(0,0,0,0); startPoint = new PointF(); movePoint1 = new PointF(); movePoint2 = new PointF(); endPoint = new PointF(); clearPaint = new Paint(); clearPaint.setAntiAlias(true); clearPaint.setStyle(Paint.Style.FILL); clearPaint.setColor(getResources().getColor(R.color.white)); clears = new ArrayList<>(); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); if (changed) { mWidth = getWidth(); mHeight = getHeight(); this.left = mWidth / 2; this.top = mHeight / 3; rectF.set(startPoint.x - faceRadius/2,startPoint.y - faceRadius,startPoint.x + faceRadius/2,startPoint.y); } } @Override protected void onDraw(Canvas canvas) { if (!isDraw) return; //繪製“食物” foodPath.reset(); foodPath.moveTo(startPoint.x,startPoint.y); foodPath.cubicTo(movePoint1.x,movePoint1.y,movePoint2.x,movePoint2.y,endPoint.x,endPoint.y); canvas.drawPath(foodPath, effectPaint); //繪製大球 path.reset(); path.moveTo(startPoint.x + faceRadius/2,startPoint.y); path.cubicTo(movePoint1.x,movePoint1.y + faceRadius/2,movePoint2.x,movePoint2.y + faceRadius/2,endPoint.x - faceRadius/2,endPoint.y); canvas.drawPath(path, defPaint); //吃掉“食物” for (PointF f : clears) { RectF rectF = new RectF(f.x-foodRadius*2,f.y-foodRadius*2,f.x+foodRadius*2,f.y+foodRadius*2); canvas.drawOval(rectF,clearPaint); } //繪製小球,需要在最後面繪製 canvas.drawArc(rectF, angle, 360 - angle * 2, true, facePaint); } @Override public View getView() { return this; } /**開始動畫*/ public void upAnim(){ if (faceVa != null) faceVa.cancel(); faceVa = null; effectPaint.setColor(getResources().getColor(R.color.color_a9a05c)); facePaint.setColor(getResources().getColor(R.color.color_a9a05c)); clearPaint.setColor(getResources().getColor(R.color.white)); defPaint.setColor(getResources().getColor(R.color.color_a9a05c)); startPoint.set(mWidth*1/2 + faceRadius*2,mHeight + faceRadius); movePoint1.set(mWidth*2/3, 0); movePoint2.set(mWidth*5/6, 0); endPoint.set(mWidth - faceRadius*2,mHeight + faceRadius); clears.clear(); PathEvaluator bezierEvaluator = new PathEvaluator(movePoint1,movePoint2); arcVa = ValueAnimator.ofObject(bezierEvaluator, startPoint, endPoint); arcVa.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {//饒球移動 @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { PointF point = (PointF) valueAnimator.getAnimatedValue(); // if (point.x + faceRadius <= endPoint.x) clears.add(new PointF(point.x,point.y));//保存移動的坐標 //faceRadius/2是為了讓小球的中心點剛好在大球的中間 rectF.set(point.x - faceRadius/2,point.y - faceRadius/2,point.x + faceRadius/2,point.y + faceRadius/2); postInvalidate(); } }); arcVa.setInterpolator(new LinearInterpolator()); arcVa.setDuration(2000); arcVa.setRepeatMode(ValueAnimator.RESTART); arcVa.start(); rectF.set(startPoint.x - faceRadius/2,startPoint.y - faceRadius,startPoint.x + faceRadius/2,startPoint.y); angle = loadAngle; faceVa = ValueAnimator.ofFloat(loadAngle , 0); faceVa.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {//吃食物動作 @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { angle = (float)valueAnimator.getAnimatedValue(); postInvalidate(); } }); faceVa.setInterpolator(new LinearInterpolator()); faceVa.setDuration(500); faceVa.setRepeatMode(ValueAnimator.RESTART); faceVa.setRepeatCount(-1); faceVa.start(); } @Override public void startAnim() {//前奏 effectPaint.setColor(getResources().getColor(R.color.transparent)); facePaint.setColor(getResources().getColor(R.color.transparent)); clearPaint.setColor(getResources().getColor(R.color.transparent)); isDraw = true; faceVa = ValueAnimator.ofFloat(0 , mHeight + faceRadius);//大球落下 faceVa.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {//吃食物動作 @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { float val = (float)valueAnimator.getAnimatedValue(); startPoint.set(mWidth*1/2 + faceRadius*2,val); movePoint1.set(mWidth*2/3, 0); movePoint2.set(mWidth*5/6, 0); endPoint.set(mWidth - faceRadius*2,val); postInvalidate(); } }); faceVa.setInterpolator(new LinearInterpolator()); faceVa.setDuration(1000); faceVa.setRepeatMode(ValueAnimator.RESTART); faceVa.start(); } @Override public void stopAnim() { if (arcVa != null) arcVa.cancel(); if (faceVa != null) faceVa.cancel(); arcVa = null; faceVa = null; isDraw = false; } }View Code
最後,歡迎收藏
GitHub:https://github.com/1024477951/FragmentApp