android 自定義帶動畫的統計餅圖

VIjolie發表於2019-03-04

  閒來無事,發現市面上好多app都有餅圖統計的功能,得空自己實現以下,菜鳥一隻,求指教,輕噴!效果圖如下:

android 自定義帶動畫的統計餅圖

基本要求:

  1. 在XML佈局中可配置控制元件的屬性。
  2. 遵守基本的安卓規範

View基本繪製原理:

1.計算View的大小,測量View的大小主要有三個:

public final void measure(int widthMeasureSpec, int heightMeasureSpec)  
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)  
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) 

複製程式碼

measure()呼叫onMeasure(),onMeasure取得寬高然後呼叫setMeasureDimension()儲存測量結果,nMeasure在view的子類中重寫。

注意MeasureSpec這個幫助類:

(1) UPSPECIFIED :父容器對於子容器沒有任何限制,子容器想要多大就多大.如wrap_content

(2) EXACTLY父容器已經為子容器設定了尺寸,子容器應當服從這些邊界,不論子容器想要多大的空間.如match_parent或者具體的值50dp

(3) AT_MOST子容器可以是宣告大小內的任意大小.

2.View的位置

public void layout(int l, int t, int r, int b)
protected boolean setFrame(int left, int top, int right, int bottom)
protected void onLayout(boolean changed, int left, int top, int right, int bottom)
複製程式碼

通過呼叫draw函式進行檢視繪製,在View類中onDraw函式是個空函式,最終的繪製需求需要在自定義的onDraw函式中進行實現,比如ImageView完成圖片的繪製,如果自定義ViewGroup這個函式則不需要過載。

3.繪製

public void draw(Canvas canvas)
protected void onDraw(Canvas canvas)複製程式碼

通過呼叫draw函式進行檢視繪製,在View類中onDraw函式是個空函式,最終的繪製需求需要在自定義的onDraw函式中進行實現,比如ImageView完成圖片的繪製,如果自定義ViewGroup這個函式則不需要過載。

具例項如下:

package com.example.customview.view;

import java.util.Timer;
import java.util.TimerTask;

import com.example.customview.R;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.RectF;
import android.support.v4.app.TaskStackBuilder;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

public class CirclePercentView extends View {
    private final static String TAG = CirclePercentView.class.getSimpleName();
    private Paint mPaint;
    private RectF oval;
    // 總數
    private int max;

    private int value;
    // 背景圓的顏色
    private int backColor;
    // 圓環的顏色
    private int frontColor;

    private float tempValue;
    // 畫圓環的速度
    private int step;
    private float maxAngle;
    private Timer timer;
    // 字型大小
    private float textSize;
    // 字型顏色
    private int textColor;
    // 統計數值與統計描述的上下間距
    private float margin;
    // 園環寬度
    private float borderWidth;
    // 統計描述文字
    private String descripe;

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

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

    private void init(Context context, AttributeSet attrs) {
        // 自定義屬性
        TypedArray ta = context.obtainStyledAttributes(attrs,
                R.styleable.CirclePercentView);
        max = ta.getInt(R.styleable.CirclePercentView_maxValue, 0);
        value = ta.getInt(R.styleable.CirclePercentView_value, 0);
        backColor = ta.getColor(R.styleable.CirclePercentView_backgroudColor,
                Color.GRAY);
        frontColor = ta
                .getColor(R.styleable.CirclePercentView_frontColor, Color.BLUE);
        textColor = ta.getColor(R.styleable.CirclePercentView_textColor, Color.BLACK);
        textSize = ta.getDimension(R.styleable.CirclePercentView_textFont, 16);
        margin = ta.getDimension(R.styleable.CirclePercentView_textMargin, 16);
        borderWidth = ta.getDimension(R.styleable.CirclePercentView_borderWidth, 8);
        descripe = ta.getString(R.styleable.CirclePercentView_descripe);
        ta.recycle();
        // 計算角度
        maxAngle = value * 360f / max;
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        oval = new RectF();
        timer = new Timer();
        startAnim(100, 5000);
    }

    /**
     * 
     * @param t1
     *            間隔時長
     * @param t2
     *            動畫總時長
     */
    private void startAnim(long t1, long t2) {
        step = (int) (maxAngle / t2 * t1);
        startAnim();
    }

    private void startAnim() {
        timer.scheduleAtFixedRate(new TimerTask() {

            @Override
            public void run() {
                Log.e("tempValuetempValuetempValue", tempValue + "maxAngle"
                        + maxAngle + "maxAngle" + maxAngle);
                if (tempValue + step >= maxAngle) {
                    tempValue = maxAngle;
                    timer.cancel();
                } else {
                    tempValue += step;
                }
                // 注意此處postInvalidate()與invalidate()的區別
                postInvalidate();

            }
        }, 100, 100);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        Log.e(TAG, "onMeasure--widthMode-->" + widthMode);
        switch (widthMode) {
        case MeasureSpec.EXACTLY:
            Log.e(TAG, "EXACTLY-->EXACTLY" + widthSize);
            setMeasuredDimension(widthSize, heightSize);
            break;
        case MeasureSpec.AT_MOST:
            Log.e(TAG, "AT_MOST-->AT_MOST" + widthSize);
            break;
        case MeasureSpec.UNSPECIFIED:
            Log.e(TAG, "UNSPECIFIED-->UNSPECIFIED" + widthSize);
            break;
        }
        Log.e(TAG, "onMeasure--widthSize-->" + widthSize);
        Log.e(TAG, "onMeasure--heightMode-->" + heightMode);
        Log.e(TAG, "onMeasure--heightSize-->" + heightSize);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right,
            int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        Log.e(TAG, "onLayout");
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        mPaint.setColor(backColor);
        // FILL填充, STROKE描邊,FILL_AND_STROKE填充和描邊
        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        int with = getWidth();
        int height = getHeight();
        // 取最小值作為圓的直徑
        int size = Math.min(with, height);
        Log.e(TAG, "onDraw---->" + with + "*" + height);
        // 計算圓的半徑
        float radius = (size - 2 * borderWidth) / 2;
        // 畫背景圓
        canvas.drawCircle(size / 2, size / 2, radius, mPaint);
        // 畫文字
        mPaint.setStrokeWidth(0);
        mPaint.setColor(textColor);
        mPaint.setTextSize(textSize);
        canvas.drawText(descripe, radius - mPaint.measureText(descripe) * 0.5f,
                size / 2 + textSize + margin, mPaint);
        float textHalfWidth = mPaint
                .measureText((int) (tempValue / 360 * 100 + 0.5f) + "%") * 0.5f;
        canvas.drawText((int) (tempValue / 360 * 100 + 0.5f) + "%", radius
                - textHalfWidth, size / 2, mPaint);
        // 畫圓環
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setColor(frontColor);
        mPaint.setStrokeWidth(borderWidth);
        // 放圓的矩形
        oval.set(size / 2 - radius, size / 2 - radius, size / 2 + radius, size
                / 2 + radius);
        // 注意第三個引數
        canvas.drawArc(oval, 0, tempValue, false, mPaint); // 根據進度畫圓弧

    }
}
複製程式碼

attrs.xml如下:

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="CirclePercentView">
        <attr name="maxValue" format="integer" />
        <attr name="value" format="integer" />
        <attr name="backgroudColor" format="color|reference" />
        <attr name="frontColor" format="color|reference" />
        <attr name="textFont" format="dimension|reference" />
        <attr name="textColor" format="color|reference" />
        <attr name="textMargin" format="dimension|reference" />
        <attr name="borderWidth" format="dimension|reference" />
        <attr name="descripe" format="string|reference" />
    </declare-styleable>

</resources>
複製程式碼

具體使用如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <com.example.customview.view.CirclePercentView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="10dp"
        app:backgroudColor="#cccccc"
        app:borderWidth="12dp"
        app:frontColor="#ff00ff"
        app:maxValue="360"
        app:textColor="#ececcc"
        app:textFont="24sp"
        app:textMargin="0dp"
        app:value="270" 
        app:descripe="參與人數"/>

</LinearLayout>
複製程式碼

也可以在程式碼中通過暴露方法對各個屬性的值進行設定,這裡就不舉例了

碼字不易,期待各位的讚賞!!!

android 自定義帶動畫的統計餅圖    android 自定義帶動畫的統計餅圖

相關文章