您的位置:首页 > 移动开发 > Android开发

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.saveLayersaveLayerAlpha创建新的(带有透明度的)图层并且放入到图层栈中,出栈则是通过方法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的基本绘制流程。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息