美文网首页Code
android自定义控件

android自定义控件

作者: 5391db121647 | 来源:发表于2017-03-16 22:31 被阅读35次

    1.概述
    大家在做自定义控件时,可以把自己想像成一名艺术家。你在创作自己的艺术品。那么作为一名画家,你肯定得需要至少两样工具:画笔和画布。这两样是作画的基础,缺一不可。那么Android有这两样东西吗,答案是肯定的。在Android中Paint就是我们的画笔,Canvas就是我们的画布。那么这两样东西该如何去用呢?其实也很简单,Paint提供了很多方法,我们通过这些方法可以对这只笔进行设置,比如笔的颜色,画出来线条的粗细等等。而Canvas则负责具体要画的东西,比如点,线,矩形,圆形等等

    Paste_Image.png Paste_Image.png

    2.准备工具

    public class TimeView extends View {
           private Context mContext;
           private Paint mPaint;
    
           public TimeView(Context context) {
               super(context);
               this.mContext = context;
               initPaint();
           }
    
           public TimeView(Context context, AttributeSet attrs) {
               super(context, attrs);
               this.mContext = context;
               initPaint();
           }
    
           /**
            * 初始化画笔
            */
           private void initPaint() {
               mPaint = new Paint();
               //抗锯齿
               mPaint.setAntiAlias(true);
               mPaint.setColor(Color.BLACK);
               mPaint.setStyle(Paint.Style.STROKE);
               mPaint.setStrokeWidth(0);
           }
    
           @Override
           protected void onDraw(Canvas canvas) {
               //画具体内容
           }
    

    3.画边框

    我们的边框就是一个简单的圆:

       @Overrideprotected
            void onDraw(Canvas canvas) {
                //圆形边框 
                canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 3, mPaint);
            }
    

    我们可以看到,要想画一个圆,只用调用canvas的drawCircle(float x,float y,float radius,Paint mPaint)方法,它接受四个参数,其中想x、y为圆的圆心。这里我要说一下Android的坐标系,它的坐标原点默认在屏幕的左上角,向右为为X轴正方向,向下为Y轴正方向



    这里我们圆心选在控件的中心,即宽高的一半。第三个参数是圆的半径,这里我们就取控件宽的三分之一,第四个为之前我们创建的画笔。我们来运行看一下效果:



    怎么样还不错吧,总算有点东西了,这里我们的Style设的是Paint.Style.STROKE,我们换成Paint.Style.FILL试下:

    可以看到圆内被填充了,这下你应该知道FILL和STROKE的区别了吧。好了我们来看下一个
    3.画中心点

    有了外面的边框我们还可以再给它一个中心点,当然你觉得没必要,不加也可以。不过我们还是来看一下在Android中是如何画一个点的。其实也很简单,你只需调用

    canvas.drawPoint(float x,float y,Paint mPaint)
    

    方法,我想这方法也不用在过多的解释了,x,y为中心点的坐标,mPaint为之前的画笔。



    4.画刻度线

    时钟自然是少不了刻度线啦,所以我们来看看刻度线是如何画的。刻度线说白了就是一条条的直线。那么在Canvas
    中有画直线的方法吗?答案是必须的。画布给我们提供一个叫canvas.drawLine(float fromX,float fromY,float stopX,float stopY,Paint mPaint)
    的方法;我想大家应该在初中就知道两点决定一条直线,所以这个方法中一二两个参数分别为起始点的x、y坐标,三四两个参数为终点坐标,第五个自然为我们的画笔啦。好了有了这个方法,只要求出起点坐标和终点坐标,理论上我们能画出任意的直线。不过这里可能有人要坐不住了:你扯独自呢,这么多刻度线,怎么求啊?确实,这么多刻度线,要想一条一条求出起点坐标和终点坐标,确实不太现实。那么有没有简单点的方法呢?先别急,在回答这个问题之前我们先来看一下Canvas
    的操作坐标系的几个方法:

    canvas.translate(float x,float y);
    canvas.rotate(float degree);
    canvas.rotate(float degree,float x,float y);
    

    这里我简单说一下这几个方法,第一个是坐标系的平移,传入的两个参数,分别为平移后坐标原点的X、Y坐标,说白了就是你想把坐标原点移到哪个点就传入哪个点;第二个方法是把坐标系旋转一定角度,传入正数则顺时针转,负数则相反。第三个方法是绕着传入的(X,Y)点旋转一定度数。好了,知道了这几个方法现在再画刻度线是不是有点思路了呢。我们知道,要想求出所有刻度的起始与终点坐标很复杂,也不太现实。但求一条刻度的坐标还是好求的。为了坐标表示方便我们移动一下坐标系,即调用

    canvas.translate(getWidth()/2,getHeight()/2)
    

    将坐标原点移到圆心处。



    如上图所示,我们把坐标原点移到圆心,这样如果我们要画图中绿色刻度线,其实就很简单了。起始坐标和终点坐标的Y轴坐标均为0,起始坐标的X轴坐标为半径减去刻度线长度,而终点坐标的X轴坐标就是半径。怎么样,这样画一条刻度线是不是挺简单的,相信你一定能画好。好,接下来我们再画一条,不过在画之前,我们得做一个小小的动作,就是把坐标系旋转一下。如下图:



    我们把原来的红色坐标系顺时针旋转了a角度得到了黄色坐标系,也就是调用了canvas.rotate(a)
    ,我们之前说过顺时针转,要传入正值,所以这里的a

    是一个正数。好了,这样我们再来求一下黄色X’轴上的刻度线,会发现它的坐标和第一条刻度线的坐标是一样的。是不是问题变得很简单了。这样不管你要画几条刻度线,不管你想画在哪,只要旋转你的坐标系,而不用反复的计算刻度线的坐标。比如,我们都知道圆是360度,你想每隔一度,就画一条刻度线,那么你就每次旋转一度,然后画一条线。这样不断循环后,就画出了360条刻度线。当然你可以根据自己的需求画任意条。代码如下:

     @Overrideprotected
            void onDraw(Canvas canvas) {
                //圆形边框 
                mPaint.setStrokeWidth(2);
                canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 3, mPaint);
                //圆心 mPaint.setStrokeWidth(5); canvas.drawPoint(getWidth() / 2, getHeight() / 2, mPaint); 
                //设置刻度线线宽
                mPaint.setStrokeWidth(1);
                //将坐标原点移到圆心处 
                canvas.translate(getWidth() / 2, getHeight() / 2);
                for (int i = 0; i < 360; i++) {
                    //这里刻度线长度我设置为25 
                    canvas.drawLine(getWidth() / 3 - 25, 0, getWidth() / 3, 0, mPaint);
                    canvas.rotate(1);
                }
            }
    

    效果如下:我是每隔1度画了一条刻度线。为便于观看,我放大了整个图片,可以看到我们的刻度线分布的还是很均匀、整齐的。



    当然如果你觉得刻度线的长度都一样长,太单调了你也可以进行适当的改变。比如你可以每秒钟设置一个中等长度,每五秒钟设置一个最长的长度,然后其他的刻度线都设置一个最小的长度。我们知道圆是360度,并且秒针转一圈为60秒,所以一秒就对应360度/60秒=6度,那么五秒也就是5*6 = 30度。得到这两个关键的角度我们就可以写代码了:

    @Override
    protected void onDraw(Canvas canvas) {
        //圆形边框
        mPaint.setStrokeWidth(2);
        canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 3, mPaint);
        //圆心
        mPaint.setStrokeWidth(5);
        canvas.drawPoint(getWidth() / 2, getHeight() / 2, mPaint); 
        //设置刻度线线宽
        mPaint.setStrokeWidth(1); 
        //将坐标原点移到圆心处
        canvas.translate(getWidth()/2,getHeight()/2);
        for (int i = 0; i < 360; i++) {      
           //这里刻度线长度我设置为25
           canvas.drawLine(getWidth() / 3-25, 0,getWidth() /3, 0, mPaint);
           canvas.rotate(1); 
        }
    }
    

    效果如下:



    4.画数字

    接下来我们在时钟上画上1-12的数字,有关写字Canvas给我提供了这样一个方法:drawText(String text,float x,float y,Paint mPaint)
    ;其中text指我们要写的字,mPaint是我们的画笔,那么x,y是什么呢?很显然x和y是用来给文字定位用的,x指的文字最左边的X坐标,那么y呢,难道是文字最下边的Y坐标吗。其实不是的。我们来看下图:



    上图给出个文字的一些尺寸参数,我们可以看到其中那条黑线,即Baseline,上文的y其实就是这条线的Y坐标。Baseline到文字顶部距离叫做ascent,Baseline到文字底部叫做descent,我们知道一般文字上部和下部会有一点padding,所以top和bottom的距离会略大于ascent,descent。如果有两行文字,那么上一行的descent到下一行的ascent的距离就叫做leading,即行间距。那么我们如何能得到这些参数呢。其实很简单,在调用drawText方法之前,我们一般会通过mPaint.setTextSize(float size);来设置字体大小,设完以后,我们就可以通过mPaint.getFontMetrics()方法来得到一个Paint.FontMetrics对象,这个对象封装了上述我们要的文字尺寸信息。代码如下:

            Paint mPaint = new Paint();
            mPaint.setTextSize(50);
            Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
            float ascent = fontMetrics.ascent;
            float bottom = fontMetrics.bottom;
            float descent = fontMetrics.descent;
            float leading = fontMetrics.leading;
            float top = fontMetrics.top;
    

    注意:上述这些参数大小与具体是什么文字无关,只与字体大小和字体格式有关。并且,在Baseline上方的尺寸为负,下方为正。也就是top、ascent都是负数,bottom和descent为正数。
    好了,知道了如何写文字后,我们就可以在时钟上写上我们要的十二个数字了,一共12个数字,一个圆360度,所以每个30度写一个字。这样我们就可以用之前的方法,没写完一个数,就将坐标系旋转30度。代码如下:

    mPaint.setTextSize(25);
    mPaint.setStyle(Paint.Style.FILL);
    Rect textBound = new Rect();//创建一个矩形
    for (int i = 0; i <12; i++) {
        if (i == 0){
            //将文字装在上面创建的矩形中,即这个矩形就是文字的边框
            mPaint.getTextBounds(12+"",0,(12+"").length(),textBound);
            canvas.drawText(12+"",-textBound.width()/2,-(getWidth()/3-50),mPaint);
            canvas.rotate(30);
        }else{
            mPaint.getTextBounds(i+"",0,(i+"").length(),textBound);
            canvas.drawText(i+"",-textBound.width()/2,-(getWidth()/3-50),mPaint);
            canvas.rotate(30); 
       }
    }
    

    上面的代码还是好理解的,我们创建一个循环,每循环一次就写个文字,并且将坐标系顺时针旋转30度,其中我们可以看到,我们创建了一个矩形,然后我们调用mPaint.getTextBounds(String text,int start,int end,Rect textBound)将文字的边框存入其中,这个方法传入四个参数,第一个为我们要画的字符串,第二三个参数分别为这个字符串的开始角标和结束叫角标,最后一个为矩形。这样我们就可以把这个矩形理解为这个字符串的边框,有了边框我们就可以知道这个字符串的很多参数,比如上下左右的坐标,以及字符串的宽高等。这样当我们画数字时,它的X坐标就是文字宽度的一半,注意别忘了负号。好了我们来看下效果如何:



    没错正如你所料,虽然数字是有了,而且还挺整齐的,不过文字也跟着旋转了。看来简单的旋转坐标系是不行了。那还有其他办法吗,有的人可能会说了,直接算出每个数字的具体坐标然后在画。这样当然可以,只要你够耐心,而三角函数还不错的话,可以尝试下。不过我还是劝你不要这么干,因为这样计算既麻烦而且算的准确度也不高。那么还有什么更好的办法呢。这里我想到了一个好办法,可以给大家参考一下。其实我们每一次画数字的时候可以提取出一个动作,举个例子,比如我们要画数字“1”,如下图所示,我们知道“12”和“1”之间为30度,那么我们可以先将图中黑色坐标系顺时针旋转30度,得到蓝色坐标系,然后我们将蓝色坐标系沿着Y轴反方向移动合适的距离,得到红色坐标系,然后再将坐标系逆时针转30度得到绿色坐标系,我们的目标就是在绿色坐标系的中心画上数字,具体怎么画,我想也不用多说了。画完后,再将坐标系原路返回。也就是,将绿色坐标系顺时针旋转30度,回到红色坐标系,然后将红色坐标系沿着Y轴正方向移动和之前平移时同样的距离,得到蓝色坐标系,最后将蓝色坐标系逆时针旋转30度回到原来的黑色坐标,即刚开始的坐标系。这样经过一系列的动作,画完一个数字,我们的坐标系还是和原来没画数字时的一样。这样我们就可把这一系列动作写成一个方法,在每次画数字之前调用它就行。



    这系列动作我们可以写成如下方法:
     private void drawNum(Canvas canvas, int degree, String text, Paint paint) { 
            Rect textBound = new Rect();
            paint.getTextBounds(text, 0, text.length(), textBound);
            canvas.rotate(degree);
            canvas.translate(0, 50 - getWidth() / 3);//这里的50是坐标中心距离时钟最外边框的距离,当然你可以根据需要适当调节
            canvas.rotate(-degree);
            canvas.drawText(text, -textBound.width() / 2, 
                   textBound.height() / 2, paint); 
            canvas.rotate(degree);
            canvas.translate(0, getWidth() / 3 - 50);  
            canvas.rotate(-degree);
        }
    

    这个方法,我们传入四个参数,分别为画布,要画数字与12点之间的夹角,要画的数字以及画笔。接下来,在我们每次画数字是调用这个方法就行了:

    mPaint.setTextSize(25);
    mPaint.setStyle(Paint.Style.FILL);
    for (int i = 0; i < 12; i++) { 
       if (i == 0) { 
           drawNum(canvas, i * 30, 12 + "", mPaint);
        } else {
           drawNum(canvas, i * 30, i + "", mPaint); 
       }
    }
    

    代码还是挺直观的,我就不过多解释了。我们来看一下效果:



    只能用两字形容“完美”。
    3.画指针

    好了,数字也总算画好了,接下来就只剩下指针了,指针分秒针、分针和时针,知道一种怎么画就可以。其实很简单,这里我直接调用drawLine()方法,代码如下:
    //秒针

    //秒针
    canvas.save()
    mPaint.setColor(Color.RED);
    mPaint.setStyle(Paint.Style.STROKE);
    mPaint.setStrokeWidth(2);
    //其实坐标点(0,0)终点坐标(0,-190),这里的190为秒针长度
    canvas.drawLine(0, 0, 0, 
           -190, mPaint);
    canvas.restore();
    //分针
    canvas.save();
    mPaint.setColor(Color.BLACK);
    mPaint.setStyle(Paint.Style.STROKE);
    mPaint.setStrokeWidth(4);
    canvas.rotate(30);
    canvas.drawLine(0, 0, 0,
            -130, mPaint);
    canvas.restore();
    //时针
    canvas.save();
    mPaint.setStyle(Paint.Style.STROKE);
    mPaint.setStrokeWidth(7);
    canvas.rotate(90);
    canvas.drawLine(0, 0, 0,
            -90, mPaint);
    canvas.restore();
    

    因为我们每个指针的旋转角度都不同,所以为了避免相互影响,我们把每个指针画在canvas.save()和canvas.restore()之间,相当每个指针都画在不同的图层上,最后合并为一张图。


    http://www.cnblogs.com/zxxiaoxia/archive/2015/05/16/4508400.html

    相关文章

      网友评论

        本文标题:android自定义控件

        本文链接:https://www.haomeiwen.com/subject/yfdsnttx.html