简单自定义View的实现(电子钟表)

先放张效果图
这里写图片描述
我们先来说说自定义View的步骤:

1:首先要继承一个View类,这个View可以是View本身,也可以是View的子类(例如:TextView,Button等)。
2:接下来就要定义自定义属性,在构造方法中遍历这些属性,为自定义的属性添加必要的属性和方法。
3:重写onMeasure()方法,根据自己的需要确定控件的大小。
4:重写onDraw()方法绘制自己想要的图形。

接下来根据上面的四个步骤,我们来创建我们的Watch类。

第一步:创建Watch类并继承View,重写两个参数的构造方法。

public class Watch extends View {
 public Watch(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
}

第二步:创建自定义属性attrs.xml文件。

这里我们的自定义属性比较简单,仅仅是设置一下时、分、秒指针的颜色(attrs.xml文件要放在res/values目录下)。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="watch">
        <attr name="dialColor" format="color" />
        <attr name="scaleColor" format="color" />
        <attr name="hourHandColor" format="color"></attr>
        <attr name="minuteHandColor" format="color"></attr>
        <attr name="secondHandColor" format="color"></attr>
    </declare-styleable>
</resources>

在构造方法中遍历我们的自定义属性

TypedArray a = context.getTheme().obtainStyledAttributes(
                attrs,
                R.styleable.watch,
                0, 0);

        try {
            dialColor = a.getColor(R.styleable.watch_dialColor, getResources().getColor(R.color.colorAccent));
            scaleColor = a.getColor(R.styleable.watch_dialColor, getResources().getColor(R.color.colorAccent));
            hourHandColor = a.getColor(R.styleable.watch_hourHandColor, getResources().getColor(R.color.colorAccent));
            minuteHandColor = a.getColor(R.styleable.watch_minuteHandColor, getResources().getColor(R.color.colorAccent));
            secondHandColor = a.getColor(R.styleable.watch_secondHandColor, getResources().getColor(R.color.colorAccent));
        } finally {
            a.recycle();
        }

在全部获得自定义属性后需要调用TypedArray.recycle()方法,TypedArray对象是共享资源,必须在使用后进行回收。
得到自定义属性后,我们还需要对属性设置一些方法,比如说属性的set()方法,方便我们在代码中直接设置指针颜色。

public void setHourHandColor(int hourHandColor) {
        this.hourHandColor = hourHandColor;
        hourHandPaint.setColor(hourHandColor);
        invalidate(); // 重新绘制
        requestLayout();  // 重新布局
    }

    public void setMinuteHandColor(int minuteHandColor) {
        this.minuteHandColor = minuteHandColor;
        minuteHandPaint.setColor(minuteHandColor);
        invalidate(); // 重新绘制
        requestLayout();  // 重新布局
    }

    public void setSecondHandColor(int secondHandColor) {
        this.secondHandColor = secondHandColor;
        secondHandPaint.setColor(secondHandColor);
        invalidate(); // 重新绘制
        requestLayout();  // 重新布局
    }

当设置属性后视图需要重新绘制我们应调用invalidate()方法,设置的属性如果影响到原来的布局需要调用requestLayout()重新布局。

第三步:我们需要知道我们的控件的大小。

重写View的onMeasure()方法,根据onMeasure()方法中的参数得到我们控件的测量大小。

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int WidthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);

        switch (widthSpecMode) {
            case MeasureSpec.UNSPECIFIED:
                dialWidth = 20;
                break;
            case MeasureSpec.AT_MOST: 
            case MeasureSpec.EXACTLY: 
                dialWidth = WidthSpecSize;
                break;
        }
        switch (heightSpecMode) {
            case MeasureSpec.UNSPECIFIED:
                dialHeight = 20;
                break;
            case MeasureSpec.AT_MOST:
            case MeasureSpec.EXACTLY:
                dialHeight = heightSpecSize;
                break;
        }

        dialLeft = dialPointWidth;
        dialRight = dialWidth - dialPointWidth;
        dialTop = dialPointWidth;
        dialBottom = dialHeight - dialPointWidth;
        circleCenterX = dialWidth / 2;
        circleCenterY = dialHeight / 2;
        setMeasuredDimension(dialWidth, dialHeight);
    }

onMeasure中的两个参数分别对应控件宽度的测量参数和高度的测量参数

MeasureSpec.getMode(widthMeasureSpec)获得具体的测量模式,测量模式一共有三种:
MeasureSpec.UNSPECIFIED 父项没有对子项施加任何约束。 它可以是任何它想要的大小
MeasureSpec.AT_MOST 父母已确定孩子的确切大小。 孩子将被给予那些边界,无论它想要多大

MeasureSpec.getSize(widthMeasureSpec)得到测量的具体数值

第四步:绘制我们的图形,整篇文章的重点就在这里了。

重写onDraw()方法,这个方法中有一个Canvas参数,我们要做的就在这个Canvas上进行画图。要想画图肯定需要Paint画笔,为了提高View的绘制效率,画图需要的所有所有对象都应提前创建并实例化。
创建init()方法,实例化各种Paint对象

public void init() {
        dialPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        dialPaint.setStyle(Paint.Style.STROKE);
        dialPaint.setColor(dialColor);
        dialPaint.setStrokeWidth(dialPointWidth);

        scalePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        scalePaint.setColor(scaleColor);

        hourHandPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        hourHandPaint.setStrokeWidth(hourPointWidth);
        hourHandPaint.setColor(hourHandColor);

        minuteHandPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        minuteHandPaint.setStrokeWidth(minutePointWidth);
        minuteHandPaint.setColor(minuteHandColor);

        secondHandPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        secondHandPaint.setStrokeWidth(secondPointWidth);
        secondHandPaint.setColor(secondHandColor);
    }

在构造方法中调用init()方法。

下面是onDraw()方法的具体内容:

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        RectF dialRectf = new RectF(dialLeft, dialTop, dialRight, dialBottom);
        canvas.drawOval(dialRectf, dialPaint);
        canvas.drawPoint(circleCenterX, circleCenterY, dialPaint);
        for (int i = 1; i <= 60; i++) {
            canvas.rotate(rotateAngle, circleCenterX, circleCenterY); // 旋转画布
            if (i % 5 == 0) {
                canvas.drawLine(circleCenterX, circleCenterX / 4, circleCenterX, dialTop, scalePaint); // 绘制长刻度线
            } else {
                canvas.drawLine(circleCenterX, circleCenterX / 8, circleCenterX, dialTop, scalePaint); // 绘制短刻度线
            }
        }

        drawHourHand(hour, canvas); // 绘制时针
        drawMinuteHand(minute, canvas); // 绘制分针
        drawSecondHand(second, canvas); // 绘制秒针
    }

图形绘制好之后感觉好像缺了点什么,现在我们的指针是不会动的,那么怎样才能让指针动起来呢,我的思路是,每隔一秒获取一下系统时间,然后动态设置指针的位置。

public void setTime(int hour, int minute, int second) {
        this.hour = hour;
        this.minute = minute;
        this.second = second;
        invalidate();
    }

    public void getTime() {
        Date date = new Date(System.currentTimeMillis());
        SimpleDateFormat dateFormat = new SimpleDateFormat("HH");
        hour = Integer.parseInt(dateFormat.format(date));
        dateFormat = new SimpleDateFormat("mm");
        minute = Integer.parseInt(dateFormat.format(date));
        dateFormat = new SimpleDateFormat("ss");
        second = Integer.parseInt(dateFormat.format(date));
    }

创建一个Handler对象,在onSizeChange()方法中获取系统时间,然后循环调用setTime()方法。

Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case REFRESH:
                    getTime();
                    setTime(hour, minute, second);
                    sendEmptyMessageDelayed(REFRESH, 1000);
                    break;
            }
        }
    };
@Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        getTime();
        setTime(hour, minute, second);
        handler.sendEmptyMessageDelayed(REFRESH, 1000);
    }

下面是Watch类的完整代码(直接拷贝就可以使用):

public class Watch extends View {
    private static final String TAG = "watch";
    private static final int REFRESH = 1;
    private int dialColor;
    private int scaleColor;
    private Paint dialPaint;
    private Paint scalePaint;
    private int scaleStartX, scaleStartY, scaleEndX, scaleEndY;
    private float dialLeft, dialTop, dialRight, dialBottom;
    private int dialWidth, dialHeight;
    private int circleCenterX, circleCenterY;
    private int dialPointWidth = 5;
    private int hourPointWidth = 10;
    private int minutePointWidth = 6;
    private int secondPointWidth = 3;
    private int rotateAngle = 6;
    private Paint hourHandPaint, minuteHandPaint, secondHandPaint;
    private int hourHandColor, minuteHandColor, secondHandColor;
    private int hour, minute, second;

    Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case REFRESH:
                    getTime();
                    setTime(hour, minute, second);
                    sendEmptyMessageDelayed(REFRESH, 1000);
                    break;
            }
        }
    };

    public Watch(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray a = context.getTheme().obtainStyledAttributes(
                attrs,
                R.styleable.watch,
                0, 0);

        try {
            dialColor = a.getColor(R.styleable.watch_dialColor, getResources().getColor(R.color.colorAccent));
            scaleColor = a.getColor(R.styleable.watch_dialColor, getResources().getColor(R.color.colorAccent));
            hourHandColor = a.getColor(R.styleable.watch_hourHandColor, getResources().getColor(R.color.colorAccent));
            minuteHandColor = a.getColor(R.styleable.watch_minuteHandColor, getResources().getColor(R.color.colorAccent));
            secondHandColor = a.getColor(R.styleable.watch_secondHandColor, getResources().getColor(R.color.colorAccent));
        } finally {
            a.recycle();
        }

        init();
    }

    public void init() {
        // 带边缘的表盘
        dialPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        dialPaint.setStyle(Paint.Style.STROKE);
        dialPaint.setColor(dialColor);
        dialPaint.setStrokeWidth(dialPointWidth);
        // 刻度
        scalePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        scalePaint.setColor(scaleColor);
        // 时针
        hourHandPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        hourHandPaint.setStrokeWidth(hourPointWidth);
        hourHandPaint.setColor(hourHandColor);
        // 分针
        minuteHandPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        minuteHandPaint.setStrokeWidth(minutePointWidth);
        minuteHandPaint.setColor(minuteHandColor);
        // 秒针
        secondHandPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        secondHandPaint.setStrokeWidth(secondPointWidth);
        secondHandPaint.setColor(secondHandColor);
    }

    public void setHourHandColor(int hourHandColor) {
        this.hourHandColor = hourHandColor;
        hourHandPaint.setColor(hourHandColor);
        invalidate(); // 重新绘制
        requestLayout();  // 重新布局
    }

    public void setMinuteHandColor(int minuteHandColor) {
        this.minuteHandColor = minuteHandColor;
        minuteHandPaint.setColor(minuteHandColor);
        invalidate();
        requestLayout();
    }

    public void setSecondHandColor(int secondHandColor) {
        this.secondHandColor = secondHandColor;
        secondHandPaint.setColor(secondHandColor);
        invalidate();
        requestLayout();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int WidthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);

        switch (widthSpecMode) {
            case MeasureSpec.UNSPECIFIED:// 未确认 测量规范模式:父项没有对子项施加任何约束。 它可以是任何它想要的大小。
                dialWidth = 20;
                break;
            case MeasureSpec.AT_MOST: // 究竟 测量规格模式:父母已确定孩子的确切大小。 孩子将被给予那些边界,无论它想要多大。
            case MeasureSpec.EXACTLY: // 最多 测量规格模式:孩子可以大到它想要达到指定的尺寸。
                dialWidth = WidthSpecSize;
                break;
        }
        switch (heightSpecMode) {
            case MeasureSpec.UNSPECIFIED:
                dialHeight = 20;
                break;
            case MeasureSpec.AT_MOST:
            case MeasureSpec.EXACTLY:
                dialHeight = heightSpecSize;
                break;
        }

        dialLeft = dialPointWidth;
        dialRight = dialWidth - dialPointWidth;
        dialTop = dialPointWidth;
        dialBottom = dialHeight - dialPointWidth;
        circleCenterX = dialWidth / 2;
        circleCenterY = dialHeight / 2;
        setMeasuredDimension(dialWidth, dialHeight);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        RectF dialRectf = new RectF(dialLeft, dialTop, dialRight, dialBottom);
        canvas.drawOval(dialRectf, dialPaint);
        canvas.drawPoint(circleCenterX, circleCenterY, dialPaint);
        for (int i = 1; i <= 60; i++) {
            canvas.rotate(rotateAngle, circleCenterX, circleCenterY); // 旋转画布
            if (i % 5 == 0) {
                canvas.drawLine(circleCenterX, circleCenterX / 4, circleCenterX, dialTop, scalePaint); // 绘制长刻度线
            } else {
                canvas.drawLine(circleCenterX, circleCenterX / 8, circleCenterX, dialTop, scalePaint); // 绘制短刻度线
            }
        }

        drawHourHand(hour, canvas); // 绘制时针
        drawMinuteHand(minute, canvas); // 绘制分针
        drawSecondHand(second, canvas); // 绘制秒针
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        getTime();
        setTime(hour, minute, second);
        handler.sendEmptyMessageDelayed(REFRESH, 1000);
    }

    private void drawHourHand(int hour, Canvas canvas) {
        int angle1 = minute / 2;
        int angle = hour * rotateAngle * 5 + angle1;
        canvas.rotate(angle, circleCenterX, circleCenterY);
        canvas.drawLine(circleCenterX, circleCenterY, circleCenterX, circleCenterY / 2, hourHandPaint);
        canvas.rotate(360 - angle, circleCenterX, circleCenterY); // 将画布旋转到初始位置
    }

    private void drawMinuteHand(int minute, Canvas canvas) {
        int angle = minute * rotateAngle;
        canvas.rotate(angle, circleCenterX, circleCenterY);
        canvas.drawLine(circleCenterX, circleCenterY, circleCenterX, circleCenterY / 4, minuteHandPaint);
        canvas.rotate(360 - angle, circleCenterX, circleCenterY); // 将画布旋转到初始位置
    }

    private void drawSecondHand(int second, Canvas canvas) {
        int angle = second * rotateAngle;
        canvas.rotate(angle, circleCenterX, circleCenterY);
        canvas.drawLine(circleCenterX, circleCenterY, circleCenterX, dialTop, secondHandPaint);
        canvas.rotate(360 - angle, circleCenterX, circleCenterY); // 将画布旋转到初始位置
    }

    public void setTime(int hour, int minute, int second) {
        this.hour = hour;
        this.minute = minute;
        this.second = second;
        invalidate();
    }

    public void getTime() {
        Date date = new Date(System.currentTimeMillis());
        SimpleDateFormat dateFormat = new SimpleDateFormat("HH");
        hour = Integer.parseInt(dateFormat.format(date));
        dateFormat = new SimpleDateFormat("mm");
        minute = Integer.parseInt(dateFormat.format(date));
        dateFormat = new SimpleDateFormat("ss");
        second = Integer.parseInt(dateFormat.format(date));
    }
}

自定义View的使用方法:

在xml中使用

<com.jqk.customizewatch.Watch
        android:id="@+id/watch"
        android:layout_width="200dp"
        android:layout_height="200dp"
        app:hourHandColor="@color/green"
        app:minuteHandColor="@color/red"
        app:secondHandColor="@color/blue"
        android:layout_centerInParent="true" />

在代码中使用

private Watch watch;

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

        watch = (Watch) findViewById(R.id.watch);
//        watch.setTime(5, 30, 40);
        watch.setHourHandColor(getResources().getColor(R.color.green));
        watch.setMinuteHandColor(getResources().getColor(R.color.red));
        watch.setSecondHandColor(getResources().getColor(R.color.blue));
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值