Android进阶——自定义View的必修课之关于Canvas绘图与Android坐标系的总结
2017-05-05 17:21
621 查看
引言
Canvas相信大家都不会陌生,虽然看来很简单,也知道各种API的用法和作用,但是很多人觉得自定义View很难,很大一部分原因就是对于Canvas不够熟悉,或许看教材和视频只是教你要移动translate、rotate、save、restore等,很少告知你为什么这样做,导致你只能照敲不能灵活应用。所以知其然更要知其所以然,授人以鱼不如授人以渔,这篇文章我争取把Android 2D绘画的一些相关知识点总结出来。一、Android系统坐标系
把Android绘画当成现实中的画家作画,Canvas自然就是画家笔下的画板,而画家自然就是Android系统本身,在现实生活中画家可以自主决定从哪个点开始起笔,又延伸到哪点,而在机器世界里都是需要去一系列的逻辑计算的,因而坐标系应运而生,在Android中主要有两大坐标系:Android坐标系和视图坐标系。1、Android坐标系
Android坐标系可以看成是物理存在的坐标系,也可以理解为绝对坐标,以屏幕为参照物,就是以屏幕的左上角是坐标系统原点(0,0),原点向右延伸是X轴正方向,原点向下延伸是Y轴正方向。比如系统的getLocationOnScreen(int[] location)实际上获取Android坐标系中位置(即该View左上角在Android坐标系中的坐标),还有getRawX()、getRawY()获取的坐标也是Android坐标系的坐标。2、视图坐标系
视图坐标系是相对坐标系,是以父视图为参照物,以父视图的左上角为坐标原点(0,0),原点向右延伸是X轴正方向,原点向下延伸是Y轴正方向,getX()、getY()就是获取视图坐标系下的坐标。3、两种坐标系在Android的应用
3.1、子View获取自身尺寸信息
getHeight():获取View自身高度getWidth():获取View自身宽度
3.2、子View获取自身坐标信息
子View的存在是依附于父View的,所以用的是相对坐标来表示,如下方法可以获得子View到其父View(ViewGroup)的距离:getLeft():获取子View自身左边到其父View左边的距离
getTop():获取子View自身顶边到其父View顶边的距离
getRight():获取子View自身右边到其父View左边的距离
getBottom():获取子View自身底边到其父View顶边的距离
getMargingXxxx:获取子View的边框距离父ViewGroup边框的距离即外边距,Xxxx代表Left、Right、Top、Bootom。
getPaddingXxxx:获取子View内部的内容的边框距离子View的边框的距离即内边距,Xxxx代表Left、Right、Top、Bootom。
3.3、获取MotionEvent中对应坐标信息
无论是View还是ViewGroup,Touch事件都会经由onTouchEvent(MotionEvent event)方法来处理,通过MotionEvent实例event可以获取相关坐标信息。getX():获取Touch事件当前触摸点距离控件左边的距离,即视图坐标下对应的X轴的值
getY():获取Touch事件距离控件顶边的距离,即视图坐标系下对应的Y轴的值
getRawX():获取Touch事件距离整个屏幕左边距离,即绝对坐标系下对应的X轴的值
getRawY():获取Touch事件距离整个屏幕顶边的的距离,即绝对坐标系下对应的Y轴的值
3.4、获取view在屏幕中的位置
如果在Activity的OnCreate()事件调用这些方法,那么输出那些参数全为0,必须要等UI控件都加载完了才能获取到。- getLocalVisibleRect() :返回一个填充的Rect对象, 所有的View都是以一块矩形内存空间存在的
getGlobalVisibleRect() :获取Android坐标系的一个视图区域, 返回一个填充的Rect对象且该Rect是基于总整个屏幕的
getLocationOnScreen :计算该视图在Android坐标系中的x,y值,获取在当前屏幕内的绝对坐标
(这个值是要从屏幕顶端算起,当然包括了通知栏和状态栏的高度)
getLocationInWindow ,计算该视图在它所在的widnow的坐标x,y值,获取在整个window的绝对坐标
int[] location = new int[2]; view.getLocationOnScreen(location); int x = location[0]; int y = location[1];
二、Paint
Paint即画笔,在绘图过程中起到了极其重要的作用,画笔主要保存了颜色, 样式等绘制信息,指定了如何绘制文本和图形,画笔对象有很多设置方法,大体上可以分为两类,一类与图形绘制相关,一类与文本绘制相关。方法 | 说明 |
---|---|
图形绘制相关 | |
setARGB(int a,int r,int g,int b); | 用于绘制图形,设置绘制的颜色,a代表透明度,r,g,b代表颜色值。 |
setAlpha(int a) | 设置绘制图形的透明度 |
setColor(int color) | 设置绘制的颜色,使用颜色值来表示,该颜色值包括透明度和RGB颜色 |
setAntiAlias(boolean aa) | 设置是否使用抗锯齿功能,会消耗较大资源,绘制图形速度会变慢 |
setDither(boolean dither) | 设定是否使用图像抖动处理,会使绘制出来的图片颜色更加平滑和饱满,图像更加清晰 |
setFilterBitmap(boolean filter) | 如果该项设置为true,则图像在动画进行中会滤掉对Bitmap图像的优化操作,加快显示速度,本设置项依赖于dither和xfermode的设置 |
setMaskFilter(MaskFilter maskfilter) | 设置MaskFilter,可以用不同的MaskFilter实现滤镜的效果,如滤化,立体等 |
setColorFilter(ColorFilter colorfilter) | 设置颜色过滤器,可以在绘制颜色时实现不用颜色的变换效果 |
setPathEffect(PathEffect effect) | 设置绘制路径的效果,如点画线 |
setShader(Shader shader) | 设置图像效果,使用Shader可以绘制出各种渐变效果 |
setShadowLayer(float radius ,float dx,float dy,int color) | 在图形下面设置阴影层,产生阴影效果,radius为阴影的角度,dx和dy为阴影在x轴和y轴上的距离,color为阴影的颜色 |
setStyle(Paint.Style style) | 设置画笔的样式,为FILL,FILL_OR_STROKE,或STROKE |
setStrokeCap(Paint.Cap cap) | 当画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的图形样式,如圆形样式Cap.ROUND,或方形样式Cap.SQUARE |
setSrokeJoin(Paint.Join join) | 设置绘制时各图形的结合方式,如平滑效果等 |
setStrokeWidth(float width) | 当画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的粗细度 |
setXfermode(Xfermode xfermode) | 设置图形重叠时的处理方式,如合并,取交集或并集,经常用来制作橡皮的擦除效果 |
文本绘制相关 | |
setFakeBoldText(boolean fakeBoldText) | 模拟实现粗体文字,但设置在小字体上效果会非常差 |
setSubpixelText(boolean subpixelText) | 设置该项为true,将有助于文本增强在LCD屏幕上的显示效果 |
setTextAlign(Paint.Align align) | 设置绘制文字的对齐方向 |
setTextScaleX(float scaleX) | 设置绘制文字x轴的缩放比例,可以实现文字的拉伸的效果 |
setTextSize(float textSize) | 设置绘制文字的字号大小 |
setTextSkewX(float skewX) | 设置斜体文字,skewX为倾斜弧度 |
setTypeface(Typeface typeface) | 设置Typeface对象,即字体风格,包括粗体,斜体以及衬线体,非衬线体等 |
setUnderlineText(boolean underlineText) | 设置带有下划线的文字效果 |
setStrikeThruText(boolean strikeThruText) | 设置带有删除线的效果 |
三、Canvas绘图
1、canvas.translate(x,y)
translate其实是把圆心坐标移动,比如说canvas.translate(200,200),则是把圆心移动到原来(200,200)处,若是移动canvas.translate(200,0)也是如此,相当于改变圆心的x坐标,y坐标不变。2、canvas.rotate(degree)
rotate(float degrees)这个方法的旋转中心是坐标的原点3、translate和rotate
4、onMeasure方法详解及实现
在自定义View时重写onDraw方法是为了绘制控件或者UI,而要想能在布局文件中正确的使用,往往还需要重写onMeasure方法计算位置 ,要想计算位置就得先测量自身的尺寸大小。至于实现请关注下篇文章。5、测试代码
package crazymo.com.drawcanvas; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.View; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(new CustomView(this)); } class CustomView extends View { Paint paint; public CustomView(Context context) { super(context); //初始化画笔 paint = new Paint(Paint.ANTI_ALIAS_FLAG);//在画图的时候,图片如果旋转或缩放之后,总是会出现那些华丽的锯齿。给Paint加上抗锯齿标志 paint .setAntiAlias(true); paint.setColor(Color.GREEN); paint.setStrokeJoin(Paint.Join.ROUND); paint.setStrokeCap(Paint.Cap.ROUND); paint.setStrokeWidth(16); } //在这里我们将测试canvas提供的绘制图形方法 @Override protected void onDraw(Canvas canvas) { //canvas.translate(200,200); //canvas.rotate(30); //drawCircle(50,50,90,canvas,paint); drawLine(canvas); paint.setColor(0xFFDD0000); drawALine(250+30, 250, 400+30, 400,canvas, paint); drawCircle(0,0,90,canvas,paint); } /** * 画圆 * @param cx 圆心x坐标 * @param cy 圆心y坐标 * @param radius 半径 * @param paint paint对象 */ private void drawCircle(int cx,int cy,float radius,Canvas canvas,Paint paint){ canvas.drawCircle(cx,cy,radius,paint); } private void drawLine(Canvas canvas){ //画一条线 canvas.drawLine(250, 250, 400, 400, paint); } private void drawALine(float startX,float startY,float stopX,float stopY,Canvas canvas,Paint paint){ //画一条线 canvas.drawLine( startX, startY, stopX, stopY,paint); } } }
6、Layer图层
Android中的绘图机制很多都是借鉴了Photoshop的概念,在Photoshop中一张原始的素材可能是由很多图层叠加而成,Android也借鉴了这一机制,所谓Layer图层其本质就是内存中一块矩形的区域,在Android中图层是基于栈的数据结果进行管理的,通过方法canvas.saveLayer或saveLayerAlpha来创建新的(带有透明度的)图层并且放入到图层栈中,出栈则是通过方法restore、restoreToCount,出入栈造成的操作区别是:入栈时所有的绘制操作都发生在当前这个图层,而出栈之后则会把操作绘制到上一个图层。@Override protected void onDraw(Canvas canvas) { //相当于是默认绘制白色背景、蓝色圆在整个画布上,可以看成PS中的背景 canvas.drawColor(Color.WHITE); paintOutSide.setColor(Color.BLUE); canvas.drawCircle(100,100,100,paintOutSide); canvas.saveLayerAlpha(0,0,400,400,125,ALL_SAVE_FLAG);//执行saveLayerAlpha 相当于是创建了一个新的图层绘制红色圆,其中125代表alpha值0~255,你可以尝试着修改透明值进行测试可以加深对于图层的理解 paintOutSide.setColor(Color.RED); canvas.drawCircle(150,150,100,paintOutSide); canvas.restore(); }
7、canvas.save()和canvas.restore()
save和restore通俗解释就是:save的作用就是将之前所有的绘制操作保存到内存中,让后续的操作在新的内存空间操作,就像是Photoshop中的前面的操作保存到一个图层中,save之后的操作再保存到新的图层;而restore的话就相当于是Photoshop中的合并图层的操作比如你可以先保存目前画纸的位置(save),然后旋转90度,向下移动100像素后画一些图形,画完后调用restore方法合并图层。/** * Auther: Crazy.Mo * DateTime: 2017/4/28 16:34 * Summary: */ public class ClockView extends View { private Context context; private Paint paintOutSide,paintDegree; private float outWidth,outHeight; public ClockView(Context context) { this(context, null); init(context); } public ClockView(Context context, AttributeSet attrs) { this(context, attrs,0); init(context); } public ClockView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } private void init(Context context){ this.context=context; initOutSize(); } private void initOutSize(){ WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);//获取WM对象 DisplayMetrics dm = new DisplayMetrics(); manager.getDefaultDisplay().getMetrics(dm); outHeight=(float) dm.heightPixels;//获取真实屏幕的高度以px为单位 outWidth=(float)dm.widthPixels; } /** * 画外圈圆 * @param canvas */ private void drawOutCircle(Canvas canvas){ paintOutSide=new Paint(); paintOutSide.setColor(Color.GREEN); paintOutSide.setStyle(Paint.Style.STROKE); paintOutSide.setAntiAlias(true); paintOutSide.setDither(true); paintOutSide.setStrokeWidth(6f); canvas.drawCircle(outWidth/2.0f,outHeight/2.0f,outWidth/2.0f,paintOutSide); } /** * 画刻度 */ private void drawDegree(Canvas canvas){ paintDegree=new Paint(); paintDegree.setColor(Color.RED); paintDegree.setStyle(Paint.Style.STROKE); paintDegree.setAntiAlias(true); paintDegree.setDither(true); paintDegree.setStrokeWidth(3f); for(int i=0;i<24;i++){ if(i==0||i==6||i==12||i==18){ paintDegree.setStrokeWidth(6f); paintDegree.setTextSize(30); canvas.drawLine(outWidth/2.0f,(outHeight/2.0f-outWidth/2.0f),outWidth/2.0f,(outHeight/2.0f-outWidth/2.0f+60),paintDegree); String degreeTxt=String.valueOf(i); canvas.drawText(degreeTxt,(outWidth/2-paintDegree.measureText(degreeTxt)/2),(outHeight/2-outWidth/2+90),paintDegree); }else { paintDegree.setStrokeWidth(4f); paintDegree.setTextSize(20); canvas.drawLine(outWidth/2.0f,(outHeight/2.0f-outWidth/2.0f),outWidth/2.0f,(outHeight/2.0f-outWidth/2.0f+40),paintDegree); String degreeTxt=String.valueOf(i); canvas.drawText(degreeTxt,(outWidth/2-paintDegree.measureText(degreeTxt)/2)+20,(outHeight/2-outWidth/2+40),paintDegree); } canvas.rotate(15,outWidth/2,outHeight/2); } } private void drawPointor(Canvas canvas){ Paint paintHour=new Paint(); paintHour.setColor(Color.RED); paintHour.setStyle(Paint.Style.STROKE); paintHour.setAntiAlias(true); paintHour.setDither(true); paintHour.setStrokeWidth(12f); Paint paintMin=new Paint(); paintMin.setColor(Color.RED); paintMin.setStyle(Paint.Style.STROKE); paintMin.setAntiAlias(true); paintMin.setDither(true); paintMin.setStrokeWidth(8f); canvas.save(); canvas.translate(outWidth/2,outHeight/2); canvas.drawLine(0,0,100,100,paintHour); canvas.drawLine(0,0,100,150,paintMin); canvas.restore(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); drawOutCircle(canvas); drawDegree(canvas); drawPointor(canvas); } }
下一篇总结下onMeasure的实现和View的基本绘制流程。
相关文章推荐
- 0916Android基础自定义View(Canvas绘图)
- Android 绘图基础:Canvas画布——自定义View基础(绘制表盘、矩形、圆形、弧、渐变)
- Android进阶学习-使用Canvas自定义简单TextView(1)
- 关于这一周学习Android自定义View的经验总结
- Android进阶学习-使用Canvas自定义ImageTextView(2)
- Android 绘图基础:Canvas画布——自定义View基础(绘制表盘、矩形、圆形、弧、渐变)
- Android自定义View和Canvas绘图解析
- Android中自定义视图View之---进阶篇(Canvas的使用)
- Android进阶学习-使用Canvas自定义ProgressView(3)
- Android 绘图进阶(四):自定义View属性(灰常重要)
- Android进阶学习总结-自定义ViewGroup和属性
- Android高手进阶教程(三)之 ----Android 中自定义View的应用
- Android高手进阶教程(二十七)之---基于ViewFlipper实现的自定义新手指引控件.
- Android学习:自定义ViewGroup方法总结
- Android高手进阶教程(三)之----Android 中自定义View的应用.
- Android 高手进阶之自定义View,自定义属性(带进度的圆形进度条)
- Android高手进阶教程(三)之----Android 中自定义View的应用.
- Android自定义View研究(八)--自定义View总结
- Android:自定义View、Paint、Canvas、attrs、customview、path