自定义控件的实现

来源:互联网 发布:工业设计软件分类 编辑:程序博客网 时间:2024/05/19 20:57

本来应该继续写Spring的学习技术博客的,今天下午帮助同学简单写了一个自定义控件,自己也熟悉回忆了一下自定义控件的绘制流程。

首先有这么一个需求,要制作一块精致华丽的挂表,怎么实现呢?

我们来分析:
1.表是圆的,是不是要先画一个圆出来呢。

2.表是有刻度的,那么怎么给这个圆上面画出刻度来呢,其实也很简单,我们先给12点画一个刻度,出来,在设置或测量出控件的大小来后,通过getWidth()可以获取空间的宽度,这样就能得到12点的X坐标getWidth()/2,12点在整个控件的顶部,他的Y坐标显而易见是0,这样通过canvas.drawLine(),方法轻易就能画出刻度来。

3.接下来我们再来分析,按小时总共有12小时,那就是,每两个时间中间的角度是360/12=30°,这就好办了,每画完一次刻度,就让画布旋转30就好了。这样就可以画出刻度来(分秒的刻度道理一样的);

4.在接下来就是如何绘制,时分秒的指针了这个更简单,起始点都是这个圆表盘的圆心,画出不同的直线来。

5.最后就是让时分秒跟着时间动态走了,其实也很简单,就是不断重回时分秒之间就可以了,invalidate();调用这个方法;

就上面这五步就基本实现这个需求了,是不是很简单,下面就把代码贴上来,供大家鉴赏,我会在代码中加上注释的:

package com.fly.customview.customclock.view;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.icu.util.Calendar;import android.text.format.Time;import android.util.AttributeSet;import android.util.DisplayMetrics;import android.util.Log;import android.view.View;/** * 类名: * 类描述: * 创建人:fei.wang * 创建日期: 2017/2/2. * 版本:V1.0 * */public class ClockView extends View {    private static final String TAG = ClockView.class.getSimpleName();    /**创建画圆的画笔*/    private Paint circlePaint;    /**创建画圆的画笔颜色*/    private int circleColor = Color.parseColor("#dddd89");    /**创建画刻度的画笔*/    private Paint scalePaint;    /**创建画刻度的画笔颜色*/    private int scaleColor = Color.parseColor("#000000");    /**创建画时、分、秒的画笔*/    private Paint mPaint;    /**距离圆内部边缘的距离*/    private int padding = 5;    public ClockView(Context context) {        super(context);        initCirclePaint();    }    public ClockView(Context context, AttributeSet attrs) {        super(context, attrs);        initCirclePaint();    }    public ClockView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        initCirclePaint();    }    private void initCirclePaint() {        circlePaint = new Paint();        circlePaint.setColor(circleColor);        circlePaint.setStyle(Paint.Style.STROKE); // 设置画笔的样式,STROKE为空心,FILL为实心        circlePaint.setStrokeWidth(5);  // 设置空心的边框宽度        circlePaint.setAntiAlias(true); // 设置画笔无锯齿        scalePaint = new Paint();        scalePaint.setStyle(Paint.Style.FILL);        scalePaint.setColor(scaleColor);        scalePaint.setStrokeWidth(5);        scalePaint.setAntiAlias(true);        mPaint = new Paint();        mPaint.setStyle(Paint.Style.FILL);        mPaint.setAntiAlias(true);    }    //--------------------------------- 接下来开始最重要的三个方法啦 ----------------------------------------------------    /**     * 对这个方法的详细了解http://www.jianshu.com/p/ba2e73899cc7     * @param widthMeasureSpec    从父控件传递过来的宽度(包括模式和大小)     * @param heightMeasureSpec   从父控件传递过来的高度(包括模式和大小)     * 通过这个方法可以获取该控件的宽(getMeasuredWidth)高(getMeasuredHeight)     */    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        // 把要设置控件的宽高告诉父控件        setMeasuredDimension(measureWidth(widthMeasureSpec),measureHeight(heightMeasureSpec));    }    /**     *     * @param measureSpec 从父控件传递过来的     * @return  最终告诉父控件子控件需要的宽度     */    private int measureWidth(int measureSpec){        int result = 0;        int mode = MeasureSpec.getMode(measureSpec);        int size = MeasureSpec.getSize(measureSpec);        switch (mode){            case MeasureSpec.EXACTLY:  // 表示父控件已经把大小限制死了                result = size;                break;            case MeasureSpec.AT_MOST:  // 表示父控件允许子控件设置大小,但是设置的大小一定要在父控件的范围之内                result = getWidth()/2;                break;            case MeasureSpec.UNSPECIFIED: // 随意设,没限制,但是不管你设置多少(值大于父控件了已经),最后展示出来的,都是浮空间的大小                result = 200;                break;        }        return result;    }    /**     *     * @param measureSpec 从父控件传递过来的     * @return  最终告诉父控件子控件需要的宽度     */    private int measureHeight(int measureSpec){        int result = 0;        int mode = MeasureSpec.getMode(measureSpec);        int size = MeasureSpec.getSize(measureSpec);        switch (mode){            case MeasureSpec.EXACTLY:  // 表示父控件已经把大小限制死了                result = size;                Log.e(TAG,"************EXACTLY*************");                break;            case MeasureSpec.AT_MOST:  // 表示父控件允许子控件设置大小,但是设置的大小一定要在父控件的范围之内                result = getHeight()/4;                Log.e(TAG,"************AT_MOST*************");                break;            case MeasureSpec.UNSPECIFIED: // 随意设,没限制,但是不管你设置多少(值大于父控件了已经),最后展示出来的,都是浮空间的大小                result = 200;                Log.e(TAG,"************UNSPECIFIED*************");                break;        }        return result;    }    //--------------------------------- 好了经过上面的测量已经知道所绘控件的大小了,接下来开始固定控件所在的位置 ---------------------------------    /**     * 这个方法的详细讲解http://blog.csdn.net/liangde123/article/details/51839188     * @param changed     * @param left    控件左边距离父控件左边的距离     * @param top     控件顶部距离父控件顶部的距离     * @param right   控件右边距离父控件左边的距离     * @param bottom  控件底部距离父控件顶部的距离     * 通过这个方法,可以最终获取在这个位置上控件的宽(getWidth)高(getHeight)     */    @Override    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {        super.onLayout(changed, left, top, right, bottom);        // 把控件要展示到父控件的位置传递给父控件//        layout(50,50,getMeasuredWidth() + 50,getMeasuredHeight() + 50);    }    /**获取屏幕的宽高*/    private void getScreenSize(){        DisplayMetrics metrics = new DisplayMetrics();        int widthPixels = metrics.widthPixels;        int heightPixels = metrics.heightPixels;    }    //---------------------------------接下来是重头戏OnDraw()方法的实现-------------------------------------------------    /**     * 通过这个方法可以画出我们所需要的控件     * @param canvas 画布     */    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        drawCircle(canvas);        drawScale(canvas);        drawPointer(canvas);    }    /**画出圆*/    private void drawCircle(Canvas canvas){        // 分别是X轴坐标,Y轴坐标,半径,画笔        canvas.drawCircle(getWidth()/2,getHeight()/2,getHeight()/2 - padding,circlePaint);    }    /**画出刻度*/    private void drawScale(Canvas canvas){        /**         * startX:起始端点的X坐标。         * startY:起始端点的Y坐标。         * stopX:终止端点的X坐标。         * stopY:终止端点的Y坐标。         * paint:绘制直线所使用的画笔。         *         * 至于为什么要这么设置坐标:         * 控件的宽度getWidth()除以2就可以获取到12点的X坐标,         * 因为限定(到这一步知道)了控件的高度,12点的Y坐标本身应该为0,但是上面画圆的时候画笔的宽度为5,和padding是一样的         * 并且画的时候把画笔的宽度减去了,所以这里设置是padding,是让从远的外边距算起,有点绕,需要理解         *///        canvas.drawLine(getWidth()/2,padding,getWidth()/2,padding + 14,scalePaint); // 这一步只是为了演示划出的刻度        /**         * 既然可以画出刻度,那么接下来我们又要分析, 表的刻度(这里只是计算是真的刻度,分针秒针原理一样的)         * 时针的刻度在一个表盘里有12个,并且3、6、9、12点的时候刻度会略长         * 并且每画出一个刻度的时候画布应该旋转360/12的角度,继续画下一个刻度         * 好了分析清楚后,我们就开始画了         */        for (int i = 0; i < 12; i++) {            if (i%3 == 0) { // 可以获取到3的整数倍的点(3、6、9、12)                canvas.drawLine(getWidth()/2,padding,getWidth()/2,padding + 20,scalePaint);            }else{                canvas.drawLine(getWidth()/2,padding,getWidth()/2,padding + 14,scalePaint);            }            /**             * degrees 旋转的角度             * px 以某个点来旋转的点的x坐标             * py 以某个点来旋转的点的y坐标             */            canvas.rotate(30,getWidth()/2,getHeight()/2);        }    }    /**     * 接下来开始绘制时分秒的指针     * canvas.save();与canvas.restore();的详细参考下面的链接     * http://www.cnblogs.com/xirihanlin/archive/2009/07/24/1530246.html     * @param canvas 画布     * 分析:     *     时分秒的指针都是直线canvas.drawLine();     *     时的指针旋转角度为 360/12     *     分的指针旋转的角度 360/120     *     秒的指针旋转的角度 360/1200     */    private void drawPointer(Canvas canvas){        Time t=new Time(); // or Time t=new Time("GMT+8"); 加上Time Zone资料。        t.setToNow(); // 取得系统时间。        int hour = 1; // 0-23        if (t.hour > 12) {            hour = t.hour - 12;        }else{            hour = t.hour;        }        int minute = t.minute;        int second = t.second;        // 旋转的角度        float degrees = hour*30;        mPaint.setColor(Color.BLACK);        mPaint.setStrokeWidth(5);        canvas.save();        canvas.rotate(degrees,getWidth()/2,getHeight()/2);        canvas.drawLine(getWidth()/2,getHeight()/2,getWidth()/2,getHeight()/2 - 90,mPaint);        canvas.restore();        // 分        degrees = minute*3;        mPaint.setColor(Color.BLUE);        mPaint.setStrokeWidth(4);        canvas.save();        canvas.rotate(degrees,getWidth()/2,getHeight()/2);        canvas.drawLine(getWidth()/2,getHeight()/2,getWidth()/2,getHeight()/2 - 70,mPaint);        canvas.restore();        // 分        degrees = (float) (minute*0.3);        mPaint.setColor(Color.BLUE);        mPaint.setStrokeWidth(3);        canvas.save();        canvas.rotate(degrees,getWidth()/2,getHeight()/2);        canvas.drawLine(getWidth()/2,getHeight()/2,getWidth()/2,getHeight()/2 - 40,mPaint);        canvas.restore();        invalidate();// view的刷新操作,重绘    }}

到此为止,是不是很简单,自定义控件其实也没这么神奇,慢慢也变成基础 了。

0 0