1. 程式人生 > >Android 自定義數字圓環

Android 自定義數字圓環

最近專案中寫了一個數字圓環的樣式,只能通過自定義View完成,對於自定義View我還比較薄弱,查了一些資料,我們參考下:

看下效果圖:

自定義View:DoughnutView.java

/**
 * on 15/12/16.
 * 自定義數字圓環
 * 參考:http://www.jianshu.com/p/84df2466e26a
 */
public class DoughnutView extends View {
    //View預設最小寬度
    private static final int DEFAULT_MIN_WIDTH = 400;
    //圓環顏色
//    private int[] doughnutColors = new int[]{Color.GREEN, Color.YELLOW, Color.RED};//多種顏色
    private int[] doughnutColors = new int[]{Color.rgb(57, 160, 242)};//單一顏色

    private int width;
    private int height;
    private float currentValue = 0f;
    private Paint paint = new Paint();

    public DoughnutView(Context context) {
        super(context);
    }

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

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

    private void resetParams() {
        width = getWidth();
        height = getHeight();
    }

    private void initPaint() {
        paint.reset();
        paint.setAntiAlias(true);
    }

    //動畫效果實現
    public void setValue(float value) {
        ValueAnimator valueAnimator = ValueAnimator.ofFloat(currentValue, value);
        valueAnimator.setDuration(2000);
        //自己實現一個簡單的插值器
//        valueAnimator.setInterpolator(new Interpolator() {
//            @Override
//            public float getInterpolation(float v) {
//                return 1-(1-v)*(1-v)*(1-v);
//            }
//        });
        // 使用ValueAnimator來實現動畫效果。還可以設定不同的插值器來實現不同的動畫效果:
        valueAnimator.setInterpolator(new LinearInterpolator());//勻速
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                currentValue = (float) valueAnimator.getAnimatedValue();
                invalidate();
            }
        });
        valueAnimator.start();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        resetParams();
        //畫背景白色圓環
        initPaint();
        float doughnutWidth = Math.min(width, height) / 2 * 0.20f;//圓環寬
        paint.setStrokeWidth(doughnutWidth);
        paint.setStyle(Paint.Style.STROKE);
//        paint.setColor(Color.WHITE);
        paint.setColor(Color.rgb(243, 243, 243));
        paint.setAntiAlias(true);
        RectF rectF = new RectF((width > height ? Math.abs(width - height) / 2 : 0) + doughnutWidth / 2, (height > width ? Math.abs(height - width) / 2 : 0) + doughnutWidth / 2, width - (width > height ? Math.abs(width - height) / 2 : 0) - doughnutWidth / 2, height - (height > width ? Math.abs(height - width) / 2 : 0) - doughnutWidth / 2);
        canvas.drawArc(rectF, 0, 360, false, paint);

        //畫彩色圓環
        //使用SweepGradient來實現圓環漸變的效果,這裡有個判斷當設定的顏色陣列只有一個顏色的時候,
        //直接'setColor',有多個顏色才使用SweepGradient實現漸變色。這樣就能既支援漸變色又支援單色。
        //這裡還有一點要注意,SweepGradient預設是從3點鐘位置開始漸變的,為了能讓它從12點鐘位置開始漸變所以將畫布旋轉了-90°。
        initPaint();
        canvas.rotate(-90, width / 2, height / 2);
        paint.setStrokeWidth(doughnutWidth);
        paint.setStyle(Paint.Style.STROKE);
        if (doughnutColors.length > 1) {
            paint.setShader(new SweepGradient(width / 2, height / 2, doughnutColors, null));
        } else {
            paint.setColor(doughnutColors[0]);
        }
        canvas.drawArc(rectF, 0, currentValue, false, paint);

        //畫中間數值的背景
        int fontSize = 50;
        initPaint();
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(Color.WHITE);
        canvas.drawCircle(width / 2, height / 2, fontSize * 2, paint);

        //畫中間數值
        canvas.rotate(90, width / 2, height / 2);
        initPaint();
        paint.setColor(ColorUtils.getCurrentColor(currentValue / 360f, doughnutColors));
        paint.setTextSize(fontSize);
        paint.setTextAlign(Paint.Align.CENTER);
        float baseLine = height / 2 - (paint.getFontMetrics().descent + paint.getFontMetrics().ascent) / 2;
        canvas.drawText((int) (currentValue / 360f * 100) + "%", width / 2, baseLine, paint);
    }

    /**
     * 當佈局為wrap_content時設定預設長寬
     *
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measure(widthMeasureSpec), measure(heightMeasureSpec));
    }

    private int measure(int origin) {
        int result = DEFAULT_MIN_WIDTH;
        int specMode = MeasureSpec.getMode(origin);
        int specSize = MeasureSpec.getSize(origin);
        if (specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        } else {
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize);
            }
        }
        return result;
    }

}

ColorUtils.java
/**
 15/12/17.
 */
public class ColorUtils {
    /**
     * 顏色漸變演算法
     * 獲取某個百分比下的漸變顏色值
     *
     * @param percent
     * @param colors
     * @return
     */
    public static int getCurrentColor(float percent, int[] colors) {
        float[][] f = new float[colors.length][3];
        for (int i = 0; i < colors.length; i++) {
            f[i][0] = (colors[i] & 0xff0000) >> 16;
            f[i][1] = (colors[i] & 0x00ff00) >> 8;
            f[i][2] = (colors[i] & 0x0000ff);
        }
        float[] result = new float[3];
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < f.length; j++) {
                if (f.length == 1 || percent == j / (f.length - 1f)) {
                    result = f[j];
                } else {
                    if (percent > j / (f.length - 1f) && percent < (j + 1f) / (f.length - 1)) {
                        result[i] = f[j][i] - (f[j][i] - f[j + 1][i]) * (percent - j / (f.length - 1f)) * (f.length - 1f);
                    }
                }
            }
        }
        return Color.rgb((int) result[0], (int) result[1], (int) result[2]);
    }

}

MainActivity.java
public class MainActivity extends AppCompatActivity {
    private DoughnutView doughnutView;
    private Button actionBtn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        doughnutView = (DoughnutView) findViewById(R.id.doughnutView);
        actionBtn = (Button) findViewById(R.id.actionBtn);

        //隨機
        actionBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                doughnutView.setValue(new Random().nextInt(360));
            }
        });


        findViewById(R.id.p20Btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                doughnutView.setValue(360 * 0.2f);
            }
        });
        findViewById(R.id.p50Btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                doughnutView.setValue(360 * 0.5f);
            }
        });
        findViewById(R.id.p80Btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                doughnutView.setValue(360 * 0.8f);
            }
        });
        findViewById(R.id.p100Btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                doughnutView.setValue(360 * 1.0f);
            }
        });
    }
}