Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android自定義View和屬性動畫完美結合,創造出酷炫圓環動畫,帶標尺和進度

Android自定義View和屬性動畫完美結合,創造出酷炫圓環動畫,帶標尺和進度

編輯:關於Android編程

主要是在原來的基礎上添加了如下功能

1.進度圓環的顏色是漸變。 2.添加一個進度標尺,類似與鐘表表盤的樣子,用來顯示刻度。

3.添加一個進度指示器,三角形的樣子,用來顯示進度。

效果圖如下

效果圖

下面,就針對這三個變化來說明一下:

一.漸變的圓環顏色

        /**
         * 設置圓形漸變
         * 【第一個參數】:中心點x坐標
         * 【第二個參數】:中心點y坐標
         * 【第三個參數】:漸變的顏色數組
         * 【第四個參數】:漸變的顏色數組對應的相對位置
         */
        paintCurrent.setShader(new SweepGradient(centerX, centerX, new int[]{getResources().getColor(R.color.start_color), getResources().getColor(R.color.end_color)}, null));

二.畫三角形進度指示器

1.拿到一個資源圖片對象
        /**
         * 通過這個拿到一個資源圖片對象
         */
        bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.arrow);

2.計算三角形移動軌跡的坐標

 /**
  * 為進度設置動畫
  * ValueAnimator是整個屬性動畫機制當中最核心的一個類,屬性動畫的運行機制是通過不斷地對值進行操作來實現的,
  * 而初始值和結束值之間的動畫過渡就是由ValueAnimator這個類來負責計算的。
  * 它的內部使用一種時間循環的機制來計算值與值之間的動畫過渡,
  * 我們只需要將初始值和結束值提供給ValueAnimator,並且告訴它動畫所需運行的時長,
  * 那麼ValueAnimator就會自動幫我們完成從初始值平滑地過渡到結束值這樣的效果。
  *
  * @param last
  * @param current
  */
 @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
 private void setAnimation(float last, float current, int length) {
     ValueAnimator progressAnimator = ValueAnimator.ofFloat(last, current);
     progressAnimator.setDuration(length);
     progressAnimator.setTarget(currentAngleLength);
     progressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
         @Override
         public void onAnimationUpdate(ValueAnimator animation) {
             /**每次要繪制的圓弧角度**/
             currentAngleLength = (float) animation.getAnimatedValue();

             /**要繪制的三角形指示器的半徑**/
             float radius=centerX - borderWidth-bitmap.getHeight();
             /**要繪制的三角形指示器的x坐標**/
             point.x = (float) (centerX +radius * Math.cos((startAngle + currentAngleLength) * Math.PI / 180));
             /**要繪制的三角形指示器的y坐標**/
             point.y = (float) (centerX + radius* Math.sin((startAngle + currentAngleLength) * Math.PI / 180));
             Log.d("stepView", point + "");

             /**要繪制的圓弧多繪制的部分減掉**/
             double subtractionScale = borderWidth/2/(centerX*2*Math.PI);
             double subtractionAngle=subtractionScale*angleLength;
             if(currentAngleLength>subtractionAngle){
                 currentAngleLength-=subtractionAngle;
             }
             invalidate();
         }
     });
     progressAnimator.start();
 }

3.調整旋轉的角度後繪制三角形指示器

/**
  * 5.畫三角形
  *
  * @param canvas
  */
 private void drawBitmap(Canvas canvas) {
     // 定義矩陣對象
     Matrix matrix = new Matrix();
     // 參數為正則向右旋轉
     matrix.postRotate(startAngle + currentAngleLength + 90);
     Bitmap dstbmp = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(),
             matrix, true);
     Paint mBitmapPaint = new Paint(Paint.DITHER_FLAG);//這裡不管怎麼設置都不影響最終圖像
     canvas.drawBitmap(dstbmp, point.x - dstbmp.getWidth() / 2, point.y - dstbmp.getHeight() / 2, mBitmapPaint);
 }

三.繪制進度表尺的鐘表儀表盤

 /**
  * 6. 畫鐘表線
  *
  * @param canvas
  */
 private void drawLine(Canvas canvas) {
     Paint mPaint = new Paint();
     mPaint.setStrokeWidth(5);
     mPaint.setColor(getResources().getColor(R.color.start_color));
     /**要繪制的表盤線的總數**/
     int count = 60;
     /**要繪制的表盤每個間隔線條之間的夾角**/
     int avgAngle = (360 / (count - 1));
     /**要繪制的表盤的最長的半徑**/
     float radius = centerX - borderWidth - bitmap.getHeight() - 20;
     /**要繪制的表盤線條長度**/
     int lineLength = 25;
     /**起始點**/
     PointF point1 = new PointF();
     /**終止點**/
     PointF point2 = new PointF();
     for (int i = 0; i < count; i++) {
         int angle = avgAngle * i;
         /**起始點坐標**/
         point1.x = centerX + (float) Math.cos(angle * (Math.PI / 180)) * radius;
         point1.y = centerX + (float) Math.sin(angle * (Math.PI / 180)) * radius;

         /**終止點坐標**/
         point2.x = centerX + (float) Math.cos(angle * (Math.PI / 180)) * (radius - lineLength);
         point2.y = centerX + (float) Math.sin(angle * (Math.PI / 180)) * (radius - lineLength);

         /**畫線**/
         canvas.drawLine(point1.x, point1.y, point2.x, point2.y, mPaint);
     }
 }

四.完整代碼

package cn.bluemobi.dylan.stepcirclestaffview;

/**
* Created by yuandl on 2016-11-08.
*/

import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.RadialGradient;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.SweepGradient;
import android.graphics.Typeface;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

import java.util.logging.Logger;


/**
* Created by DylanAndroid on 2016/5/26.
* 顯示步數的圓弧
*/
public class StepArcView extends View {

 /**
  * 圓弧的寬度
  */
 private float borderWidth = 38f;
 /**
  * 畫步數的數值的字體大小
  */
 private float numberTextSize = 0;
 /**
  * 步數
  */
 private String stepNumber = "0";
 /**
  * 開始繪制圓弧的角度
  */
 private float startAngle = 90;
 /**
  * 終點對應的角度和起始點對應的角度的夾角
  */
 private float angleLength = 360;
 /**
  * 所要繪制的當前步數的紅色圓弧終點到起點的夾角
  */
 private float currentAngleLength = 0;
 /**
  * 動畫時長
  */
 private int animationLength = 3000;

 private PointF point;
 private float centerX;
 private Bitmap bitmap;
 private int totalStepNum;

 private void init() {
     point = new PointF();
     /**
      * 通過這個拿到一個資源圖片對象
      */
     bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.arrow);


 }

 public StepArcView(Context context) {
     super(context);
     init();

 }


 public StepArcView(Context context, AttributeSet attrs) {
     super(context, attrs);
     init();
 }

 public StepArcView(Context context, AttributeSet attrs, int defStyleAttr) {
     super(context, attrs, defStyleAttr);
     init();
 }


 @Override
 protected void onDraw(Canvas canvas) {
     super.onDraw(canvas);
     /**中心點的x坐標*/
     centerX = (getWidth()) / 2;
     /**指定圓弧的外輪廓矩形區域*/
     RectF rectF = new RectF(0 + borderWidth, borderWidth, 2 * centerX - borderWidth, 2 * centerX - borderWidth);

     /**【第一步】繪制整體的黃色圓弧*/
     drawArcYellow(canvas, rectF);
     /**【第二步】繪制當前進度的紅色圓弧*/
     drawArcRed(canvas, rectF);
     /**【第三步】繪制當前進度的紅色數字*/
     drawTextNumber(canvas, centerX);
     /**【第四步】繪制"步數"的紅色數字*/
     drawTextStepString(canvas, centerX);
     /**【第五步】繪制"步數"進度標尺的三角形*/
     drawBitmap(canvas);
     /**【第六步】繪制"步數"進度標尺類似於鐘表線隔*/
     drawLine(canvas);
 }


 /**
  * 1.繪制總步數的黃色圓弧
  *
  * @param canvas 畫筆
  * @param rectF  參考的矩形
  */
 private void drawArcYellow(Canvas canvas, RectF rectF) {
     Paint paint = new Paint();
     /** 默認畫筆顏色,黃色 */
     paint.setColor(getResources().getColor(R.color.default_color));
     /** 結合處為圓弧*/
     paint.setStrokeJoin(Paint.Join.ROUND);
     /** 設置畫筆的樣式 Paint.Cap.Round ,Cap.SQUARE等分別為圓形、方形*/
     paint.setStrokeCap(Paint.Cap.ROUND);
     /** 設置畫筆的填充樣式 Paint.Style.FILL  :填充內部;Paint.Style.FILL_AND_STROKE  :填充內部和描邊;  Paint.Style.STROKE  :僅描邊*/
     paint.setStyle(Paint.Style.STROKE);
     /**抗鋸齒功能*/
     paint.setAntiAlias(true);
     /**設置畫筆寬度*/
     paint.setStrokeWidth(borderWidth);

     /**繪制圓弧的方法
      * drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)//畫弧,
      參數一是RectF對象,一個矩形區域橢圓形的界限用於定義在形狀、大小、電弧,
      參數二是起始角(度)在電弧的開始,圓弧起始角度,單位為度。
      參數三圓弧掃過的角度,順時針方向,單位為度,從右中間開始為零度。
      參數四是如果這是true(真)的話,在繪制圓弧時將圓心包括在內,通常用來繪制扇形;如果它是false(假)這將是一個弧線,
      參數五是Paint對象;
      */
     canvas.drawArc(rectF, startAngle, angleLength, false, paint);

 }

 /**
  * 2.繪制當前步數的紅色圓弧
  */
 private void drawArcRed(Canvas canvas, RectF rectF) {
     Paint paintCurrent = new Paint();
     paintCurrent.setStrokeJoin(Paint.Join.ROUND);
     paintCurrent.setStrokeCap(Paint.Cap.SQUARE);//圓角弧度
     paintCurrent.setStyle(Paint.Style.STROKE);//設置填充樣式
     paintCurrent.setAntiAlias(true);//抗鋸齒功能
     paintCurrent.setStrokeWidth(borderWidth);//設置畫筆寬度
     /**
      * 設置圓形漸變
      * 【第一個參數】:中心點x坐標
      * 【第二個參數】:中心點y坐標
      * 【第三個參數】:漸變的顏色數組
      * 【第四個參數】:漸變的顏色數組對應的相對位置
      */
     paintCurrent.setShader(new SweepGradient(centerX, centerX, new int[]{getResources().getColor(R.color.start_color), getResources().getColor(R.color.end_color)}, null));
     canvas.drawArc(rectF, startAngle, currentAngleLength, false, paintCurrent);
 }

 /**
  * 3.圓環中心的步數
  */
 private void drawTextNumber(Canvas canvas, float centerX) {
     Paint vTextPaint = new Paint();
     vTextPaint.setTextAlign(Paint.Align.CENTER);
     vTextPaint.setAntiAlias(true);//抗鋸齒功能
     vTextPaint.setTextSize(numberTextSize);
     Typeface font = Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL);
     vTextPaint.setTypeface(font);//字體風格
     vTextPaint.setColor(getResources().getColor(R.color.center_text_color));
     Rect bounds_Number = new Rect();
     vTextPaint.getTextBounds(stepNumber, 0, stepNumber.length(), bounds_Number);
     canvas.drawText(stepNumber, centerX, getHeight() / 2 + bounds_Number.height() / 2, vTextPaint);

 }

 /**
  * 4.圓環中心[步數]的文字
  */
 private void drawTextStepString(Canvas canvas, float centerX) {
     Paint vTextPaint = new Paint();
     vTextPaint.setTextSize(dipToPx(13));
     vTextPaint.setTextAlign(Paint.Align.CENTER);
     vTextPaint.setAntiAlias(true);//抗鋸齒功能
     vTextPaint.setColor(getResources().getColor(R.color.other_text_color));
     String stepString = "目標 "+totalStepNum;
     Rect bounds = new Rect();
     vTextPaint.getTextBounds(stepString, 0, stepString.length(), bounds);
     canvas.drawText(stepString, centerX, getHeight() / 2 + bounds.height() + getFontHeight(numberTextSize), vTextPaint);
     canvas.save();
      stepString = "今天步數";
      bounds = new Rect();
     vTextPaint.getTextBounds(stepString, 0, stepString.length(), bounds);
     canvas.drawText(stepString, centerX, getHeight() / 2  - getFontHeight(numberTextSize), vTextPaint);

 }

 /**
  * 5.畫三角形
  *
  * @param canvas
  */
 private void drawBitmap(Canvas canvas) {
     // 定義矩陣對象
     Matrix matrix = new Matrix();
     // 參數為正則向右旋轉
     matrix.postRotate(startAngle + currentAngleLength + 90);
     Bitmap dstbmp = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(),
             matrix, true);
     Paint mBitmapPaint = new Paint(Paint.DITHER_FLAG);//這裡不管怎麼設置都不影響最終圖像
     canvas.drawBitmap(dstbmp, point.x - dstbmp.getWidth() / 2, point.y - dstbmp.getHeight() / 2, mBitmapPaint);
 }

 /**
  * 6. 畫鐘表線
  *
  * @param canvas
  */
 private void drawLine(Canvas canvas) {
     Paint mPaint = new Paint();
     mPaint.setStrokeWidth(5);
     mPaint.setColor(getResources().getColor(R.color.start_color));
     /**要繪制的表盤線的總數**/
     int count = 60;
     /**要繪制的表盤每個間隔線條之間的夾角**/
     int avgAngle = (360 / (count - 1));
     /**要繪制的表盤的最長的半徑**/
     float radius = centerX - borderWidth - bitmap.getHeight() - 20;
     /**要繪制的表盤線條長度**/
     int lineLength = 25;
     /**起始點**/
     PointF point1 = new PointF();
     /**終止點**/
     PointF point2 = new PointF();
     for (int i = 0; i < count; i++) {
         int angle = avgAngle * i;
         /**起始點坐標**/
         point1.x = centerX + (float) Math.cos(angle * (Math.PI / 180)) * radius;
         point1.y = centerX + (float) Math.sin(angle * (Math.PI / 180)) * radius;

         /**終止點坐標**/
         point2.x = centerX + (float) Math.cos(angle * (Math.PI / 180)) * (radius - lineLength);
         point2.y = centerX + (float) Math.sin(angle * (Math.PI / 180)) * (radius - lineLength);

         /**畫線**/
         canvas.drawLine(point1.x, point1.y, point2.x, point2.y, mPaint);
     }
 }

 /**
  * 獲取當前步數的數字的高度
  *
  * @param fontSize 字體大小
  * @return 字體高度
  */
 public int getFontHeight(float fontSize) {
     Paint paint = new Paint();
     paint.setTextSize(fontSize);
     Rect bounds_Number = new Rect();
     paint.getTextBounds(stepNumber, 0, stepNumber.length(), bounds_Number);
     return bounds_Number.height();
 }

 /**
  * dip 轉換成px
  *
  * @param dip
  * @return
  */

 private int dipToPx(float dip) {
     float density = getContext().getResources().getDisplayMetrics().density;
     return (int) (dip * density + 0.5f * (dip >= 0 ? 1 : -1));
 }

 /**
  * 所走的步數進度
  *
  * @param totalStepNum  設置的步數
  * @param currentCounts 所走步數
  */
 @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
 public void setCurrentCount(int totalStepNum, int currentCounts) {
     this.totalStepNum = totalStepNum;
     stepNumber = currentCounts + "";
     setTextSize(currentCounts);
     /**如果當前走的步數超過總步數則圓弧還是270度,不能成為園*/
     if (currentCounts > totalStepNum) {
         currentCounts = totalStepNum;
     }

     /**所走步數占用總共步數的百分比*/
     float scale = (float) currentCounts / totalStepNum;
     /**換算成弧度最後要到達的角度的長度-->弧長*/
     float currentAngleLength = scale * angleLength;
     /**開始執行動畫*/
     setAnimation(0, currentAngleLength, animationLength);
 }

 /**
  * 為進度設置動畫
  * ValueAnimator是整個屬性動畫機制當中最核心的一個類,屬性動畫的運行機制是通過不斷地對值進行操作來實現的,
  * 而初始值和結束值之間的動畫過渡就是由ValueAnimator這個類來負責計算的。
  * 它的內部使用一種時間循環的機制來計算值與值之間的動畫過渡,
  * 我們只需要將初始值和結束值提供給ValueAnimator,並且告訴它動畫所需運行的時長,
  * 那麼ValueAnimator就會自動幫我們完成從初始值平滑地過渡到結束值這樣的效果。
  *
  * @param last
  * @param current
  */
 @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
 private void setAnimation(float last, float current, int length) {
     ValueAnimator progressAnimator = ValueAnimator.ofFloat(last, current);
     progressAnimator.setDuration(length);
     progressAnimator.setTarget(currentAngleLength);
     progressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
         @Override
         public void onAnimationUpdate(ValueAnimator animation) {
             /**每次要繪制的圓弧角度**/
             currentAngleLength = (float) animation.getAnimatedValue();

             /**要繪制的三角形指示器的半徑**/
             float radius=centerX - borderWidth-bitmap.getHeight();
             /**要繪制的三角形指示器的x坐標**/
             point.x = (float) (centerX +radius * Math.cos((startAngle + currentAngleLength) * Math.PI / 180));
             /**要繪制的三角形指示器的y坐標**/
             point.y = (float) (centerX + radius* Math.sin((startAngle + currentAngleLength) * Math.PI / 180));
             Log.d("stepView", point + "");

             /**要繪制的圓弧多繪制的部分減掉**/
             double subtractionScale = borderWidth/2/(centerX*2*Math.PI);
             double subtractionAngle=subtractionScale*angleLength;
             if(currentAngleLength>subtractionAngle){
                 currentAngleLength-=subtractionAngle;
             }
             invalidate();
         }
     });
     progressAnimator.start();
 }

 /**
  * 設置文本大小,防止步數特別大之後放不下,將字體大小動態設置
  *
  * @param num
  */
 public void setTextSize(int num) {
     String s = String.valueOf(num);
     int length = s.length();
     if (length <= 4) {
         numberTextSize = dipToPx(40);
     } else if (length > 4 && length <= 6) {
         numberTextSize = dipToPx(30);
     } else if (length > 6 && length <= 8) {
         numberTextSize = dipToPx(25);
     } else if (length > 8) {
         numberTextSize = dipToPx(20);
     }
 }

}
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved