基本要求:

  1.  在XML布局中可配置控件的属性。
  2. 遵守基本的安卓规范

View基本绘制原理:

首先计算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)

layout通过调用setFrame(l,t,r,b),子视图在父视图中的具体位置,onLayout一般只会在自定义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 studio绘制扇形统计图 扇形统计图手机制作_ide