一个钟表带你进入Android绘图的世界

前言

顾名思义,就是在Android手机屏幕中绘制我们需要的内容,根据绘制内容的大小(measure),布局(layout)来讲具体内容展示在屏幕中,通过绘制(draw)来实现我们需要的效果.

绘图原理(三部曲)

Measure

measure操作主要用于计算视图的大小,即视图的宽度和长度。在view中定义为final类型,要求子类不能修改。measure()函数中又会调用下面的函数:

(1)onMeasure(),视图大小的将在这里最终确定,也就是说measure只是对onMeasure的一个包装,子类可以覆写onMeasure()方法实现自己的计算视图大小的方式,并通过setMeasuredDimension(width,
height)保存计算结果。

Layout

layout操作用于设置视图在屏幕中显示的位置。在view中定义为final类型,要求子类不能修改。layout()函数中有两个基本操作:

(1)setFrame(l,t,r,b),l,t,r,b即子视图在父视图中的具体位置,该函数用于将这些参数保存起来;

(2)onLayout(),在View中这个函数什么都不会做,提供该函数主要是为viewGroup类型布局子视图用的;

Draw

draw操作利用前两部得到的参数,将视图显示在屏幕上,到这里也就完成了整个的视图绘制工作。子类也不应该修改该方法,因为其内部定义了绘图的基本操作:

(1)绘制背景;

(2)如果要视图显示渐变框,这里会做一些准备工作;

(3)绘制视图本身,即调用onDraw()函数。在view中onDraw()是个空函数,也就是说具体的视图都要覆写该函数来实现自己的显示(比如TextView在这里实现了绘制文字的过程)。而对于ViewGroup则不需要实现该函数,因为作为容器是“没有内容“的,其包含了多个子view,而子View已经实现了自己的绘制方法,因此只需要告诉子view绘制自己就可以了,也就是下面的dispatchDraw()方法;

(4)绘制子视图,即dispatchDraw()函数。在view中这是个空函数,具体的视图不需要实现该方法,它是专门为容器类准备的,也就是容器类必须实现该方法;

(5)如果需要(应用程序调用了setVerticalFadingEdge或者setHorizontalFadingEdge),开始绘制渐变框;

(6)绘制滚动条;

从上面可以看出自定义View需要最少覆写onMeasure()和onDraw()两个方法。

参考:http://blog.csdn.net/xu_fu/article/details/7829721

分类

2D绘图

基于Android SDK内部自己提供,也是我们学习的主要内容,2D绘图的api大部分是android.graphics和android.graphics.drawable包中,他们提供了Canvas,ColorFilter,Point和RetcF等,以及一些动画相关的:AnimationDrawable,BitmapDrawable和TransitionDrawable等.

3D绘图

用Open GL ES 1.0

参考:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2012/1212/703.html

2D绘图

内容

通过canvas对象可以绘制的[弧线],[Bitmap],[圆],[点],[矩形],[圆角矩形],[文本],[路径]等图形,其次还提供了旋转(rotate),缩放(scale),渐变(translate)和扭曲(skew)等转换方法.

正文

今天就以2D我学习例子,来完成对钟表的绘制实现.

知识点

1.Paint的创建于使用

2.Canvas的使用

3.Handler使用

4.onMeasure使用

5.Canvas.restore()和Canvas.save的使用

效果图:

实现步骤:

1.准备工作,初始化画笔工具,初始化时间,初始化圆环半径

2.画圆环

3.画刻度

4.移动坐标中心到画布中心

5.画指针(画时针,画分针,画秒针)

6.画时间文字

源码解析

第一步|准备工作

private Paint mPaintRing;// 圆环画笔
private Paint mPaintDegree;// 刻度/圆心画笔
private Paint mPaintText;// 文字画笔
private float strokeWidthText = 2;// 文字画笔厚度
private float strokeWidthRing = 4;// 圆环画笔厚度
private float radius = 0;// 圆环半径

//用于初始化时间的角度设置
private float hourDegree = 0;// 时针角度
private float minuteDegree = 0;// 分针角度
private float secondDegree = 0;// 秒针角度

private Date mCurrentDate=null;// 用户设置时间,默认为当前时间
private void initPaint() {
    mCurrentDate=new Date();
    setDate(mCurrentDate);
    // 初始化圆环画笔
    mPaintRing = new Paint();
    mPaintRing.setColor(Color.RED);
    mPaintRing.setStyle(Paint.Style.STROKE);
    mPaintRing.setStrokeWidth(strokeWidthRing);
    mPaintRing.setAntiAlias(true);
    // 初始化刻度画笔
    mPaintDegree = new Paint();
    mPaintDegree.setColor(Color.RED);
    mPaintDegree.setStyle(Paint.Style.FILL);
    mPaintDegree.setAntiAlias(true);
    // 初始化文字画笔
    mPaintText = new Paint();
    mPaintText.setColor(Color.RED);
    mPaintText.setStyle(Paint.Style.FILL);
    mPaintText.setAntiAlias(true);
    mPaintText.setStrokeWidth(strokeWidthText);
}

第二步|绘图

//初始化半径,由于圆环的厚度也要占据一定的宽度,因此需要减除厚度值,这样才能保证圆环
radius = Math.min(getWidth() / 2, getHeight() / 2) - strokeWidthRing;
// 1.画圆环
canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius, mPaintRing);
// 2.画刻度
drawMark(canvas);
// 3.画圆心
canvas.drawCircle(getWidth() / 2, getHeight() / 2, 5, mPaintDegree);
// 4.移动坐标中心到画布中心
canvas.save();
canvas.translate(getWidth() / 2, getHeight() / 2);
canvas.save();
// 5.画时针
drawHourMark(hourDegree, canvas);
// 6.画分针
drawMinuteMark(minuteDegree, canvas);
// 7.画秒针
drawSecondMark(secondDegree, canvas);
// 8.画时间文字
mPaintText.setTextSize(30);
mPaintText.setTextAlign(Paint.Align.CENTER);
canvas.drawText(XDate.fmtDate(mCurrentDate,"HH:mm:ss"), 0, -20, mPaintText);

完整代码

/**
 * 绘图基础-时钟<br>
 * 博客:<a href="http://blog.csdn.net/qq243223991">安前松博客</a>
 */
public class ClockView extends View {

    private Paint mPaintRing;// 圆环画笔
    private Paint mPaintDegree;// 刻度/圆心画笔
    private Paint mPaintText;// 文字画笔
    private float strokeWidthText = 2;// 文字画笔厚度
    private float strokeWidthRing = 4;// 圆环画笔厚度
    private float radius = 0;// 圆环半径

    //用于初始化时间的角度设置
    private float hourDegree = 0;// 时针角度
    private float minuteDegree = 0;// 分针角度
    private float secondDegree = 0;// 秒针角度

    private Date mCurrentDate=null;// 用户设置时间,默认为当前时间

    private  Handler handler=new Handler(){
        @Override
        public void handleMessage(Message msg) {
            if(msg.what==0){
                long times=mCurrentDate.getTime();
                mCurrentDate.setTime(times+1000);
               setDate(mCurrentDate);
                handler.sendEmptyMessageDelayed(0,1000);
            }
        }
    };

    public ClockView(Context context) {
        super(context);
        initPaint();
    }

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

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int min = Math.min(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));// 防止圆形变形,宽高度必须相等
        setMeasuredDimension(min, min);// 重新设置宽高
    }

    /**
     * 根据默认宽度测量宽度
     *
     * @param measureSpec
     * @return
     */
    private int measureWidth(int measureSpec) {
        int result = 0;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
        if (specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        } else {
            result = 200;
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize);
            }
        }
        return result;
    }

    /**
     * 根据默认高度测量高度
     *
     * @param measureSpec
     * @return
     */
    private int measureHeight(int measureSpec) {
        int result = 0;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
        if (specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        } else {
            result = 200;
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize);
            }
        }
        return result;
    }

    private void initPaint() {
        mCurrentDate=new Date();
        setDate(mCurrentDate);
        // 初始化圆环画笔
        mPaintRing = new Paint();
        mPaintRing.setColor(Color.RED);
        mPaintRing.setStyle(Paint.Style.STROKE);
        mPaintRing.setStrokeWidth(strokeWidthRing);
        mPaintRing.setAntiAlias(true);
        // 初始化刻度画笔
        mPaintDegree = new Paint();
        mPaintDegree.setColor(Color.RED);
        mPaintDegree.setStyle(Paint.Style.FILL);
        mPaintDegree.setAntiAlias(true);
        // 初始化文字画笔
        mPaintText = new Paint();
        mPaintText.setColor(Color.RED);
        mPaintText.setStyle(Paint.Style.FILL);
        mPaintText.setAntiAlias(true);
        mPaintText.setStrokeWidth(strokeWidthText);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //初始化半径,由于圆环的厚度也要占据一定的宽度,因此需要减除厚度值,这样才能保证圆环
        radius = Math.min(getWidth() / 2, getHeight() / 2) - strokeWidthRing;
        // 1.画圆环
        canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius, mPaintRing);
        // 2.画刻度
        drawMark(canvas);
        // 3.画圆心
        canvas.drawCircle(getWidth() / 2, getHeight() / 2, 5, mPaintDegree);
        // 4.移动坐标中心到画布中心
        canvas.save();
        canvas.translate(getWidth() / 2, getHeight() / 2);
        canvas.save();
        // 5.画时针
        drawHourMark(hourDegree, canvas);
        // 6.画分针
        drawMinuteMark(minuteDegree, canvas);
        // 7.画秒针
        drawSecondMark(secondDegree, canvas);
        // 8.画时间文字
        mPaintText.setTextSize(30);
        mPaintText.setTextAlign(Paint.Align.CENTER);
        canvas.drawText(XDate.fmtDate(mCurrentDate,"HH:mm:ss"), 0, -20, mPaintText);

    }

    /**
     * 画时针
     *
     * @param degree
     * @param canvas
     */
    private void drawHourMark(float degree, Canvas canvas) {
        canvas.rotate(degree);// 旋转度数
        mPaintDegree.setStrokeWidth(6);
        canvas.drawLine(0, 0, 0, -radius / 2, mPaintDegree);
        canvas.restore();
        canvas.save();
    }

    /**
     * 画分针
     *
     * @param degree
     * @param canvas
     */
    private void drawMinuteMark(float degree, Canvas canvas) {
        canvas.rotate(degree);// 旋转度数
        mPaintDegree.setStrokeWidth(3);
        canvas.drawLine(0, 0, 0, -radius / 2 - 25, mPaintDegree);
        canvas.restore();
        canvas.save();
    }

    /**
     * 画秒针
     *
     * @param degree
     * @param canvas
     */
    private void drawSecondMark(float degree, Canvas canvas) {
        canvas.rotate(degree);// 旋转度数
        mPaintDegree.setStrokeWidth(2);
        canvas.drawLine(0, 0, 0, -radius / 2 - 40, mPaintDegree);
        canvas.restore();
        canvas.save();
    }

    /**
     * 画刻度
     */
    private void drawMark(Canvas canvas) {
        for (int i = 0; i < 60; i++) {
            if (i % 5 == 0) {
                mPaintDegree.setStrokeWidth(strokeWidthRing);
                canvas.drawLine(getWidth() / 2, strokeWidthRing, getWidth() / 2, 40, mPaintDegree);
                mPaintText.setTextAlign(Paint.Align.CENTER);
                mPaintText.setTextSize(20);
                if (i / 5 == 0) {
                    canvas.drawText("12", getWidth() / 2, 60, mPaintText);
                } else {
                    canvas.drawText(i / 5 + "", getWidth() / 2, 60, mPaintText);
                }
            } else {
                mPaintDegree.setStrokeWidth(2);
                canvas.drawLine(getWidth() / 2, strokeWidthRing, getWidth() / 2, 30, mPaintDegree);
            }
            canvas.rotate(6, getWidth() / 2, getHeight() / 2);
        }
    }

    /**
     * 设置时间
     *
     * @param date
     */
    public void setDate(@NonNull Date date) {
        mCurrentDate=date;
        float hourDegree = getHourDegree(getHours(date), getMinutes(date), getSeconds(date));
        float minuteDegree = getMinuteDegree(getMinutes(date), getSeconds(date));
        float secondDegree = getSecondDegree(getSeconds(date));
        rotate(hourDegree, minuteDegree, secondDegree);
    }

    /**
     * 设置时间戳
     *
     * @param timeMills
     */
    public void setTimeMills(long timeMills) {
        Date date = new Date();
        date.setTime(timeMills);
        setDate(date);
    }

    /**
     * 开始时间旋转
     */
    public void start(){
        handler.sendEmptyMessageDelayed(0,1000);
    }

    /**
     * 旋转
     */
    private  void rotate(float hourDegree, float minuteDegree, float secondDegree) {
        this.hourDegree = hourDegree;
        this.minuteDegree = minuteDegree;
        this.secondDegree = secondDegree;
        invalidate();
    }

    /**
     * 根据小时获取角度
     *
     * @param hour
     * @return
     */
    private float getHourDegree(int hour, int minute, int second) {
        float hourDegree = (hour + minute / 60.0f + second / 3600.0f) * 30;
        return hourDegree;
    }

    /**
     * 根据分钟获取角度
     *
     * @param minute
     * @return
     */
    private float getMinuteDegree(int minute, int second) {
        float minuteDegree = (minute + second / 60.0f) * 6;
        return minuteDegree;
    }

    /**
     * 根据秒钟获取角度
     *
     * @param second
     * @return
     */
    private float getSecondDegree(int second) {
        int secondDegree = second * 6;
        return secondDegree;
    }

    /**
     * 获取时间信息
     *
     * @param date
     * @return
     */
    private HashMap<Integer, Integer> getTime(Date date) {
        HashMap<Integer, Integer> time = new HashMap<>();
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        time.put(Calendar.HOUR, calendar.get(Calendar.HOUR));//12小时制
        time.put(Calendar.MINUTE, calendar.get(Calendar.MINUTE));
        time.put(Calendar.SECOND, calendar.get(Calendar.SECOND));
        return time;
    }

    /**
     * 获取当天中小时
     *
     * @param date
     * @return
     */
    private int getHours(Date date) {
        return getTime(date).get(Calendar.HOUR);
    }

    /**
     * 获取分钟
     *
     * @param date
     * @return
     */
    private int getMinutes(Date date) {
        return getTime(date).get(Calendar.MINUTE);
    }

    /**
     * 获取秒钟
     *
     * @param date
     * @return
     */
    private int getSeconds(Date date) {
        return getTime(date).get(Calendar.SECOND);
    }

}

觉得有用就支持一下,谢谢!

时间: 2024-08-07 20:40:44

一个钟表带你进入Android绘图的世界的相关文章

进阶篇-用户界面:5.android绘图api自定义View(视图)

1.自定义视图并为其添加属性     我们平时用的Button啊 TextView啊都是安卓中系统自带的控件供开发者使用,但是,这些事远远不够的,有时候我们需要自定义控件. (1)新建一个类MyView使其继承View 类 import android.content.Context; import android.content.res.TypedArray; import android.util.AttributeSet; import android.view.View; /** * C

Android绘图那些事儿(上)

(一)概述 虽然,已经学过了Android绘图的内容,但是总是觉得很模糊,今天就好好梳理下思路吧!纯粹就是一个读书笔记,整理下自己以前不知道的内容,好了开始:(本节主要介绍一些Drawable的常用方法及其xml定义,如果你已经很熟悉了,就跳过吧) (二)Android屏幕适配问题 Android屏幕适配和兼容,一直都是非常头疼的问题,如何才能在不同的屏幕尺寸的手机上图片效果显示不失真!现在看看屏幕这个渣渣的相关参数吧: NO.1----屏幕相关的参数 1)屏幕大小 指的是屏幕对角线的长度,通常

第三章 Android绘图机制与处理技巧

1.屏幕尺寸信息 屏幕大小:屏幕对角线长度,单位“寸”:分辨率:手机屏幕像素点个数,例如720x1280分辨率:PPI(Pixels Per Inch):即DPI(Dots Per Inch),它是对角线的像素点数除以屏幕大小得到的:系统屏幕密度:android系统定义了几个标准的DPI值作为手机的固定DPI.(注,最后一个有笔误,正确的是1080x1920)独立像素密度(DP):android系统使用mdpi屏幕作为标准,在这个屏幕上1dp=1px,其他屏幕可以通过比例进行换算.在hdpi中,

Android绘图

Android绘图方法主要有两个步骤: (1)实现一个继承于View组件的类,并重写它的onDraw(Canavas canvas)方法; (2)显示定义的View子类,有两种方法:a.使用一个Activity来显示View子类,即 setContentView(new MyView(this, null));b.在Acitviy的布局文件中增加"包名.View子类"元素,Activiyty通过setContentView方法来使用该布局文件.下面我们来学习下Android绘制图形的三

Android 绘图(一) Paint

了解Android绘图或者自定义View的同学,都知道Canvas 类.Paint类等.今天就来看看Paint的有关描述. 首先看看官网的定义: The Paint class holds the style and color information about how to draw geometries, text and bitmaps. 翻译:Paint类拥有如何绘制几何图形.文本.位图的颜色和样式等信息. Android系统提供了设置画笔属性的Api,接下来,就来看一些常用Api的使

带你了解Android常见的内存缓存算法

带你了解Android常见的内存缓存算法 本片博客主要简介以下两个问题 介绍一下常见的内存缓存算法 怎样实现这些算法 大家应该对ImageLoader这个框架都不陌生吧,一个很强大的图片加载框架,虽然作者去年的时候已经停止维护了,但里面的许多东西还是值得我们去学习的.本篇博客讲解的内存缓存算法正是基于ImageLoader的实现基础之上的 常见的几种缓存算法 (1)LRU即Least RecentlyUsed,近期最少使用算法. 也就是当内存缓存达到设定的最大值时将内存缓存中近期最少使用的对象移

android 绘图之Canvas,Paint类

Canvas,Paint 1.在android 绘图但中经常要用到Canvas和Paint类,Canvas好比是一张画布,上面已经有你想绘制图画的轮廓了,而Paint就好比是画笔,就要给Canvas进行添色等操作. 这两个类通常都是在onDraw(Canvas canvas)方法中用的. 2.Bitmap:代表一张位图,BitmapDrawable里封装的突变就是一个Bitmao对象 3.Canvas里面有一些例如: drawArc(参数) 绘制弧 drawBitmao(Bitmap bitma

Android绘图机制(一) View类

对android绘图机制的理解,在Android学习中可谓至关重要,包括自定义控件也是使用非常频繁的内容.最近在项目中遇到一个比较棘手的问题,项目中好几个模块都用到ListView或者GridView的”下拉刷新,上拉加载更多“功能 .一开始在网上找了大牛写的作品,用在项目中后发现时不时会出现卡壳的现象,改进以后会有所改善,不过还是感觉有所欠缺.无奈我是个处女座菜鸟,尝试着找出这些问题的根本原因却发现无从下手,所以先补补基础.( 纯文字看着确实很费劲,所以顺便引用下其他的文章)  概述 View

从Handler+Message+Looper源码带你分析Android系统的消息处理机制

引言 [转载请注明出处:从Handler+Message+Looper源码带你分析Android系统的消息处理机制 CSDN 废墟的树] 作为Android开发者,相信很多人都使用过Android的Handler类来处理异步任务.那么Handler类是怎么构成一个异步任务处理机制的呢?这篇 博客带你从源码分析Android的消息循环处理机制,便于深入的理解. 这里不得不从"一个Bug引发的思考"开始研究Android的消息循环处理机制.说来话长,在某一次的项目中,原本打算开启一个工作线