Android给我们提供了大量的View控件,但这还是远远满足不了我们的要求,有时候开发所需要的控件形式是在Android提供的控件中是不存在,这就需要我们自己去定义一个。那么如何自定义控件?
  学习自定义控件,首先要先掌握Canvas类的使用。
  

Canvas

  Canvas, 我们称之为“画布“,主要适用于绘制View的。
Canvas中提供了大量绘制图形的方法:

绘制扇形:

  • drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint):
      第一个参数RectF对象,指定扇形的区域;第二个参数是起始角度;第三个参数是旋转角度,顺时针旋转;第四个参数是是否填充,true为填充,false为不填充,也就是为一条弧线;第五个参数是绘制图形的画笔对象Paint。
    RectF:
      通过RectF(float left, float top, float right, float bottom)构造器创建RectF对象,我们通过下图理解各个参数的含义,RectF对象指代的就是一个矩形区域。我们通过这四个参数构建矩形区域。

    Paint:
      是绘制所有图形所用到的一个画笔,我们在稍后讲解。
  • drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, Paint paint)
       这个是将扇形区域左,上,右,下边的坐标直接输入,而不是通过RectF对象。其他参数同上。

绘制圆形:

  • drawCircle(float cx, float cy, float radius, Paint paint)
      第一、二个参数是指圆形的x, y坐标; 第三个参数是半径; 第四个参数是画笔Paint对象。

绘制直线:

  • drawLine(float startX, float startY, float stopX, float stopY, Paint paint)
      两点确定一条直线,第一、二参数是起始点的坐标;第三、四参数是结束点的坐标;第五个参数画笔Paint对象。
  • drawLines(float[] pts, Paint paint)
      多个点确定一条直线,第一个参数是点的数组;第二个参数是画笔Paint对象。
  • drawLines(float[] pts, int offset, int count, Paint paint)

绘制椭圆:

  • drawOval(float left, float top, float right, float bottom, Paint paint)
      前四个参数是椭圆的左,上,右,下边的坐标,第五个是画笔Paint对象。
  • drawOval(RectF oval, Paint paint) 
      第一个参数是RectF对象, 第二个参数是画笔Paint对象。

绘制矩形:

  • drawRect(RectF rect, Paint paint)
      第一个参数是RectF对象, 第二个参数是画笔Paint对象。

绘制点:

  • drawPoint(float x, float y, Paint paint)
      第一、二个参数点的坐标,第三个参数为Paint对象。

渲染文本:

  • drawText(String text, float x, floaty, Paint paint)
  • drawText(CharSequence text, int start, int end, float x, float y, Paint paint)
  • drawText(char[] text, int index, int count, float x, float y, Paint paint)
  • drawText(String text, int start, int end, float x, float y, Paint paint)

      Canvas中还给我们提供了很多绘制其他图形的方法,这里我们不在一一列举。我们来看一下Paint”画笔“。

Paint

   Paint是用于绘制的画笔,Canvas就像是我们的画纸,我们需要笔才可以完成一整幅图。Paint中为我们提供了很多设置的方法(我们这里只列举常用的方法):

  • setARGB(int a, int r, int g, int b)
      设置 Paint对象颜色,参数一为alpha透明值
  • setAlpha(int a)
      设置alpha不透明度,范围为0~255
  • setAntiAlias(boolean aa)
       是否抗锯齿,这个一般是都要设置的。
  • setColor(int color)
      设置颜色,这里Android内部定义的有Color类包含了一些常见颜色定义
  • setTextScaleX(float scaleX)
      设置文本缩放倍数,1.0f为原始
  • setTextSize(float textSize)
      设置字体大小
  • setUnderlineText(booleanunderlineText)
      设置下划线
  • setStrokeCap(Paint.Cap cap)
      当画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的图形样式,如圆形样式 Cap.ROUND,或方形样式Cap.SQUARE
  • setSrokeJoin(Paint.Join join)
      设置绘制时各图形的结合方式,如平滑效果等

自定义View

  现在我们来使用Canvas类自定义一个View控件。自定义控件步骤如下:
1. 自定义View,首先定义一个MyView类继承View类。
2. 重写View的两个构造器。
  View是包含四个构造器的,我们必须重写MyWidgetView(Context context, AttributeSet attrs)构造器,因为该构造器的第二个参数是与xml布局文件相联系的,如果没有重写该构造器,将不能在布局中使用该控件。这里我们重写他的两个构造器:

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

3. 重写onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法,定义控件的尺寸:宽度和高度。在布局中使用该控件时会会传入控件的尺寸,只有当传入尺寸之后且调用onMesure之后,控件才会有宽度和高度。

    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        width = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec);        height = getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec);        setMeasuredDimension(width, height);//设置宽和高    }
  1. 重写onDraw(Canvas canvas)方法,我们在该方法中定义绘制View,当我们在Activity或其他地方使该控件时, UI主线程会调用onDraw方法绘制。
    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);    }

  onDraw(Canvas canvas)方法中传入了一个Canvas对象,我们在定义控件时,使用Canvas绘制。

绘制时钟

  这里我们通过绘制一个时钟来巩固一下自定义控件以及Canvas类的使用。
1. 定义一个MyView继承View。
2. 重写MyView(Context context, AttributeSet attrs)构造器。
3. 在重写onMeasureh和onDraw方法。
4. 在onDraw方法中绘制。
  大体步骤就是这样,我们先贴代码,逐步讲解:

public class MyView extends View {    private int width;//设置高    private int height;//设置高    private Paint mPaintLine;//定义一个绘制直线的画笔    private Paint mPaintSecondLine;//定义一个绘制直线的画笔    private Paint mPaintInterCircle;//定义一个绘制圆的画笔    private Paint mPaintOutSideCircle;//定义一个绘制圆的画笔    private Paint mPaintText;//定义一个绘制文字的画笔    private Calendar mCalendar;//创建一个时间类    private static final int NEED_INVALIDATE=0X6666;    //操作UI主线程    private Handler handler = new Handler() {        @Override        public void handleMessage(Message msg) {            super.handleMessage(msg);            switch (msg.what){                case NEED_INVALIDATE:                    //跟新时间                    mCalendar=Calendar.getInstance();                    invalidate();                    sendEmptyMessageDelayed(NEED_INVALIDATE,1000);                    break;            }        }    };    public MyView(Context context) {        super(context);    }    public MyView(Context context, AttributeSet attrs) {        super(context, attrs);        //初始化画直线的画笔        mPaintLine = new Paint();        mPaintLine.setAntiAlias(true);//消除锯齿        mPaintLine.setColor(Color.GRAY);//设置画笔颜色        mPaintLine.setStyle(Paint.Style.STROKE);//设置为空心        mPaintLine.setStrokeWidth(10);//设置宽度        // 初始化秒针的画笔        mPaintSecondLine = new Paint();        mPaintSecondLine.setAntiAlias(true);//消除锯齿        mPaintSecondLine.setColor(Color.GRAY);//设置画笔颜色        mPaintSecondLine.setStyle(Paint.Style.STROKE);//设置为空心        mPaintSecondLine.setStrokeWidth(7);//设置宽度        //初始化内圆的画笔        mPaintInterCircle = new Paint();        mPaintInterCircle.setAntiAlias(true);//消除锯齿        mPaintInterCircle.setColor(Color.BLACK);        mPaintInterCircle.setStyle(Paint.Style.STROKE);//设置为空心        mPaintInterCircle.setStrokeWidth(5);        //初始化外圆的画笔        mPaintOutSideCircle = new Paint();        mPaintOutSideCircle.setAntiAlias(true);//消除锯齿        mPaintOutSideCircle.setColor(Color.BLACK);        mPaintOutSideCircle.setStyle(Paint.Style.STROKE);//设置为空心        mPaintOutSideCircle.setStrokeWidth(10);        //绘制文字的画笔        mPaintText = new Paint();        mPaintText.setAntiAlias(true);//消除锯齿        mPaintText.setColor(Color.GRAY);        mPaintText.setStyle(Paint.Style.STROKE);//设置为空心        mPaintText.setTextAlign(Paint.Align.CENTER);        mPaintText.setTextSize(40);        mPaintText.setStrokeWidth(6);        //初始化日历        mCalendar = Calendar.getInstance();        //发送一个消息给UI主线程        handler.sendEmptyMessageDelayed(NEED_INVALIDATE,2000);    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        width = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec);        height = getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec);        setMeasuredDimension(width, height);//设置宽和高    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        // 主线程自动调用        canvas.drawCircle(width / 2, height / 2, 300, mPaintInterCircle);        canvas.drawCircle(width / 2, height / 2, 320, mPaintOutSideCircle);        for(int i=1; i<=12;i++){            canvas.save();//保存当前状态            canvas.rotate(360 / 12 * i, width / 2, height / 2);            canvas.drawLine(width / 2, height / 2 - 300, width / 2, height / 2 - 270, mPaintLine);            canvas.drawText("" + i, width / 2, height / 2 - 240, mPaintText);            canvas.restore();//回到save()方法保存的状态        }        //绘制分针        int minute=  mCalendar.get(Calendar.MINUTE);        float minuteDegree =  minute/60f*360;        canvas.save();        canvas.rotate(minuteDegree, width / 2, height / 2);        canvas.drawLine(width / 2, height / 2 - 200, width / 2, height / 2 + 40, mPaintLine);        canvas.restore();        //绘制时针        int hour=  mCalendar.get(Calendar.HOUR);        float hourDegree = (hour*60+minute)//(12f*60)*360;        canvas.save();        canvas.rotate(hourDegree, width / 2, height / 2);        canvas.drawLine(width / 2, height / 2 - 170, width / 2, height / 2 + 30, mPaintLine);        canvas.restore();        //绘制秒针        int second =  mCalendar.get(Calendar.SECOND);        float secondDegree = second*6;//一秒是6度。        canvas.save();        canvas.rotate(secondDegree, width / 2, height / 2);        canvas.drawLine(width / 2, height / 2 - 220, width / 2, height / 2 + 50, mPaintSecondLine);        canvas.restore();    }}

  绘制两个圆形嵌套这个很简单,不用多说。然后绘制刻度,是使用了Canvas画布的旋转,这个很好理解,我们在画画的时候,有时候要画一些比较难的角度,我们都是将画板旋转而不是我们自己换角度。在绘制时钟时,我们要绘制时钟每个时间的刻度,我们可以将要绘制刻度的位置旋转到竖直方向,然后绘制。

        for(int i=1; i<=12;i++){            canvas.save();//保存当前状态            canvas.rotate(360 / 12 * i, width / 2, height / 2);            canvas.drawLine(width / 2, height / 2 - 300, width / 2, height / 2 - 270, mPaintLine);            canvas.drawText("" + i, width / 2, height / 2 - 240, mPaintText);            canvas.restore();//回到save()方法保存的状态        }

  然后绘制时针,分针和秒针,他们都是绘制一条线。然后通过获取当前的时间将这条线指向对得时间点,也就是偏转对得角度。这里的偏转我们依然使用画布的偏转。
  以上完成后,我们的时钟就差不多完成了,但是,我们发现我们的表并没有走。实现我们绘制的时钟转动的方式就是然我们的时钟每一秒onDraw一次,这样我们的时钟正常了。onDraw是UI主线程不断调用重绘界面的,因此我们需要使用到Handler,通过发送一个消息给Handler对象,让Handler对象在每一秒重绘一次MyView控件。这里重绘不能调用onDraw()方法额,而要调用的是invalidate()方法,invalidate()方法中调用了onDraw()方法。

      //操作UI主线程    private Handler handler = new Handler() {        @Override        public void handleMessage(Message msg) {            super.handleMessage(msg);            switch (msg.what){                case NEED_INVALIDATE:                    //跟新时间                    mCalendar=Calendar.getInstance();                    invalidate();                    sendEmptyMessageDelayed(NEED_INVALIDATE,1000);                    break;            }        }    };

  这样我们的时钟也转动了……

更多相关文章

  1. Android图形框架简介
  2. Android实现图表绘制和展示
  3. Android的startActivityForResult()与onActivityResult()与setRe
  4. Android逆向之旅---Hook神器家族的Frida工具使用详解
  5. android (java) 网络发送get/post请求参数设置
  6. Android实现图表绘制和展示
  7. Android之怎么使用SQLite数据库(增、删、改、查、分页等)以及Lis
  8. mybatisplus的坑 insert标签insert into select无参数问题的解决
  9. Python技巧匿名函数、回调函数和高阶函数

随机推荐

  1. Android识别模拟器,判断是模拟器还是真机
  2. 从三流外包摇身变成阿里P6(Android开发岗),
  3. 年末呼声最高的 Android 中高级面试笔记,
  4. Android自定义View的实现方法,带你一步步
  5. Android 如何改变应用图标
  6. Android 针对ListActivity中ListView 点
  7. Android 如何自定义一个简单的组件和自定
  8. Flutter底部导航栏BottomNavigationBar
  9. (20120722)(笔记004)android开发应用程序资源
  10. android两种方式中自己画一个圆,实现单点