這是一個很簡單的動畫效果,使用屬性動畫便可實現,但願對讀者學習動畫能達到拋磚引玉的效果
一.自定義動畫效果——Loading效果
如上是咱們須要作的一個Loading動畫。Loading效果是很常見的一種動畫,最簡單的實現讓設計畫個動態圖便可,或者畫個靜態圖而後使用幀動畫也能夠實現。可是今天咱們用純代碼實現,不用任何圖片資源。面試
大體思路
咱們自定義一個View,繼承View類,而後畫兩個不一樣半徑的弧形,轉動不一樣的角度便可實現。
繪製兩個不一樣半徑的弧形
首先初始化外圓和內園的Recf();canvas
private RectF mOuterCircleRectF = new RectF(); private RectF mInnerCircleRectF = new RectF();
而後在onDraw方法繪製圓弧:性能優化
//獲取View的中心 float centerX = getWidth() / 2; float centerY = getHeight() / 2; if (lineWidth > centerX) { throw new IllegalArgumentException("lineWidth值太大了"); } //外圓半徑,由於咱們的弧形是有寬度的,因此計算半徑的時候應該把這部分減去,否則會有切割的效果 float outR = centerX - lineWidth; //小圓半徑 float inR = outR * 0.6f - lineWidth; //設置弧形的距離上下左右的距離,也就是包圍園的矩形。 mOuterCircleRectF.set(centerX - outR, centerY - outR, centerX + outR, centerY + outR); mInnerCircleRectF.set(centerX - inR, centerY - inR, centerX + inR, centerY + inR); //繪製外圓 canvas.drawArc(mOuterCircleRectF, mRotateAngle % 360, OUTER_CIRCLE_ANGLE, false, mStrokePaint); //繪製內圓 canvas.drawArc(mInnerCircleRectF, 270 - mRotateAngle % 360, INTER_CIRCLE_ANGLE, false, mStrokePaint);
代碼很簡單,就像註釋同樣:架構
繪製圓的過程應該放在onDraw方法中,這樣咱們能夠不斷的重繪,也能夠獲取view的真實的寬高
固然,咱們還需設置一個畫筆來畫咱們的圓ide
mStrokePaint = new Paint(); mStrokePaint.setStyle(Paint.Style.STROKE); mStrokePaint.setStrokeWidth(lineWidth); mStrokePaint.setColor(color); mStrokePaint.setAntiAlias(true); mStrokePaint.setStrokeCap(Paint.Cap.ROUND); mStrokePaint.setStrokeJoin(Paint.Join.ROUND);
這個設置很簡單,設置值得範圍,這是無線循環,設置動畫執行的時間,這隻動畫循環時延遲的時間,設置插值器。性能
弧形動起來
讓弧形動起來的原理,就是監聽值屬性動畫的值變化,而後在這個變化的過程當中不斷的改變弧形的角度,而後讓它重繪便可。
咱們讓咱們的loadview實現ValueAnimator.AnimatorUpdateListener接口,而後在onAnimationUpdate監聽動畫的變化。咱們初始化值屬性動畫的時候設置了值得範圍爲float型,因此這裏能夠獲取這個變化的值。而後利用這個值能夠改變繪製圓的角度大小,再調用重繪方法,便可實現:學習
@Override public void onAnimationUpdate(ValueAnimator animation) { mRotateAngle = 360 * (float)animation.getAnimatedValue(); invalidate(); }
整個思路大體就是這樣。優化
完整代碼以下:動畫
public class LoadingView extends View implements Animatable, ValueAnimator.AnimatorUpdateListener { private static final long ANIMATION_START_DELAY = 200; private static final long ANIMATION_DURATION = 1000; private static final int OUTER_CIRCLE_ANGLE = 270; private static final int INTER_CIRCLE_ANGLE = 90; private ValueAnimator mFloatValueAnimator; private Paint mStrokePaint; private RectF mOuterCircleRectF; private RectF mInnerCircleRectF; private float mRotateAngle; public LoadingView (Context context) { this(context, null); } public LoadingView (Context context, @Nullable AttributeSet attrs) { this(context, attrs, -1); } public LoadingView (Context context, @Nullable AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, -1); } public LoadingView (Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); initView(context, attrs); } float lineWidth; private void initView(Context context, AttributeSet attrs) { TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyCustomLoadingView); lineWidth = typedArray.getFloat(R.styleable.MyCustomLoadingView_lineWidth, 10.0f); int color = typedArray.getColor(R.styleable.MyCustomLoadingView_viewColor, context.getColor(R.color.colorAccent)); typedArray.recycle(); initAnimators(); mOuterCircleRectF = new RectF(); mInnerCircleRectF = new RectF(); //初始化畫筆 initPaint(lineWidth, color); //旋轉角度 mRotateAngle = 0; } private void initAnimators() { mFloatValueAnimator = ValueAnimator.ofFloat(0.0f, 1.0f); mFloatValueAnimator.setRepeatCount(Animation.INFINITE); mFloatValueAnimator.setDuration(ANIMATION_DURATION); mFloatValueAnimator.setStartDelay(ANIMATION_START_DELAY); mFloatValueAnimator.setInterpolator(new AccelerateDecelerateInterpolator()); } /** * 初始化畫筆 */ private void initPaint(float lineWidth, int color) { mStrokePaint = new Paint(); mStrokePaint.setStyle(Paint.Style.STROKE); mStrokePaint.setStrokeWidth(lineWidth); mStrokePaint.setColor(color); mStrokePaint.setAntiAlias(true); mStrokePaint.setStrokeCap(Paint.Cap.ROUND); mStrokePaint.setStrokeJoin(Paint.Join.ROUND); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); float centerX = getWidth() / 2; float centerY = getHeight() / 2; //最大尺寸 if (lineWidth > centerX) { throw new IllegalArgumentException("lineWidth值太大了"); } float outR = centerX - lineWidth; //小圓尺寸 float inR = outR * 0.6f; mOuterCircleRectF.set(centerX - outR, centerY - outR, centerX + outR, centerY + outR); mInnerCircleRectF.set(centerX - inR, centerY - inR, centerX + inR, centerY + inR); //先保存畫板的狀態 canvas.save(); //外圓 canvas.drawArc(mOuterCircleRectF, mRotateAngle % 360, OUTER_CIRCLE_ANGLE, false, mStrokePaint); //內圓 canvas.drawArc(mInnerCircleRectF, 270 - mRotateAngle % 360, INTER_CIRCLE_ANGLE, false, mStrokePaint); //恢復畫板的狀態 canvas.restore(); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); startLoading(); } public void startLoading() { start(); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); stopLoading(); } public void stopLoading() { stop(); } @Override public void onAnimationUpdate(ValueAnimator animation) { mRotateAngle = 360 * (float)animation.getAnimatedValue(); invalidate(); } protected void computeUpdateValue(float animatedValue) { mRotateAngle = (int) (360 * animatedValue); } @Override public void start() { if (mFloatValueAnimator.isStarted()) { return; } mFloatValueAnimator.addUpdateListener(this); mFloatValueAnimator.setRepeatCount(Animation.INFINITE); mFloatValueAnimator.setDuration(ANIMATION_DURATION); mFloatValueAnimator.start(); } @Override public void stop() { mFloatValueAnimator.removeAllUpdateListeners(); mFloatValueAnimator.removeAllListeners(); mFloatValueAnimator.setRepeatCount(0); mFloatValueAnimator.setDuration(0); mFloatValueAnimator.end(); } @Override public boolean isRunning() { return mFloatValueAnimator.isRunning(); } }
attr文件代碼以下:this
<declare-styleable name="LoadingView"> <attr name="lineWidth" format="float" /> <attr name="viewColor" format="color" /> </declare-styleable>
最後送福利了,如今關注我而且加入羣聊能夠獲取包含源碼解析,自定義View,動畫實現,架構分享等。
內容難度適中,篇幅精煉,天天只需花上十幾分鍾閱讀便可。
你們能夠跟我一塊兒探討,歡迎加羣探討,有flutter—性能優化—移動架構—資深UI工程師 —NDK相關專業人員和視頻教學資料,還有更多面試題等你來拿~
羣號:925019412