1、首先说一下canvas类:
Class Overview
The Canvas class holds the “draw” calls. To draw something, you need 4 basic components: A Bitmap to hold the pixels, a Canvas to host the draw calls (writing into the bitmap), a drawing primitive (e.g. Rect, Path, text, Bitmap), and a paint (to describe the colors and styles for the drawing).
这个类相当于一个画布,你可以在里面画很多东西;
我们可以把这个Canvas理解成系统提供给我们的一块内存区域(但实际上它只是一套画图的API,真正的内存是下面的Bitmap),而且它还提供了一整套对这个内存区域进行操作的方法,所有的这些操作都是画图API。也就是说在这种方式下我们已经能一笔一划或者使用Graphic来画我们所需要的东西了,要画什么要显示什么都由我们自己控制。
这种方式根据环境还分为两种:一种就是使用普通View的canvas画图,还有一种就是使用专门的SurfaceView的canvas来画图。两种的主要是区别就是可以在SurfaceView中定义一个专门的线程来完成画图工作,应用程序不需要等待View的刷图,提高性能。前面一种适合处理量比较小,帧率比较小的动画,比如说象棋游戏之类的;而后一种主要用在游戏,高品质动画方面的画图。
下面是Canvas类常用的方法:
drawRect(RectF rect, Paint paint) //绘制区域,参数一为RectF一个区域
drawPath(Path path, Paint paint) //绘制一个路径,参数一为Path路径对象
drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) //贴图,参数一就是我们常规的Bitmap对象,参数二是源区域(这里是bitmap),参数三是目标区域(应该在canvas的位置和大小),参数四是Paint画刷对象,因为用到了缩放和拉伸的可能,当原始Rect不等于目标Rect时性能将会有大幅损失。
drawLine(float startX, float startY, float stopX, float stopY, Paintpaint) //画线,参数一起始点的x轴位置,参数二起始点的y轴位置,参数三终点的x轴水平位置,参数四y轴垂直位置,最后一个参数为Paint 画刷对象。
drawPoint(float x, float y, Paint paint) //画点,参数一水平x轴,参数二垂直y轴,第三个参数为Paint对象。
drawText(String text, float x, floaty, Paint paint) //渲染文本,Canvas类除了上面的还可以描绘文字,参数一是String类型的文本,参数二x轴,参数三y轴,参数四是Paint对象。
drawOval(RectF oval, Paint paint)//画椭圆,参数一是扫描区域,参数二为paint对象;
drawCircle(float cx, float cy, float radius,Paint paint)// 绘制圆,参数一是中心点的x轴,参数二是中心点的y轴,参数三是半径,参数四是paint对象;
drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)//画弧,
参数一是RectF对象,一个矩形区域椭圆形的界限用于定义在形状、大小、电弧,参数二是起始角(度)在电弧的开始,
参数三扫描角(度)开始顺时针测量的,参数四是如果这是真的话,包括椭圆中心的电弧,并关闭它,如果它是假这将是一个弧线,参数五是Paint对象;
还要理解一个paint类:
Class Overview
The Paint class holds the style and color information about how to draw geometries, text and bitmaps.
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) // 设置下划线
2、直接看案例
看一下效果图:
Android利用canvas画各种图形(点、直线、弧、圆、椭圆、文字、矩形、多边形、曲线、圆角矩形)
在此案例中我们用到的是自定义view类;
CustomActivity.java
public class CustomActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
init();
}
private void init() {
LinearLayout layout=(LinearLayout) findViewById(R.id.root);
final DrawView view=new DrawView(this);
view.setMinimumHeight(500);
view.setMinimumWidth(300);
//通知view组件重绘
view.invalidate();
layout.addView(view);
}
}
重要的类自定义View组件要重写View组件的onDraw(Canvase)方法,接下来是在该 Canvas上绘制大量的几何图形,点、直线、弧、圆、椭圆、文字、矩形、多边形、曲线、圆角矩形,等各种形状!
DrawView.java
public class DrawView extends View {
public DrawView(Context context) {
super(context);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 创建画笔
Paint p = new Paint();
p.setColor(Color.RED);// 设置红色
canvas.drawText("画圆:", 10, 20, p);// 画文本
canvas.drawCircle(60, 20, 10, p);// 小圆
p.setAntiAlias(true);// 设置画笔的锯齿效果。 true是去除,大家一看效果就明白了
canvas.drawCircle(120, 20, 20, p);// 大圆
canvas.drawText("画线及弧线:", 10, 60, p);
p.setColor(Color.GREEN);// 设置绿色
canvas.drawLine(60, 40, 100, 40, p);// 画线
canvas.drawLine(110, 40, 190, 80, p);// 斜线
//画笑脸弧线
p.setStyle(Paint.Style.STROKE);//设置空心
RectF oval1=new RectF(150,20,180,40);
canvas.drawArc(oval1, 180, 180, false, p);//小弧形
oval1.set(190, 20, 220, 40);
canvas.drawArc(oval1, 180, 180, false, p);//小弧形
oval1.set(160, 30, 210, 60);
canvas.drawArc(oval1, 0, 180, false, p);//小弧形
canvas.drawText("画矩形:", 10, 80, p);
p.setColor(Color.GRAY);// 设置灰色
p.setStyle(Paint.Style.FILL);//设置填满
canvas.drawRect(60, 60, 80, 80, p);// 正方形
canvas.drawRect(60, 90, 160, 100, p);// 长方形
canvas.drawText("画扇形和椭圆:", 10, 120, p);
Shader mShader = new LinearGradient(0, 0, 100, 100,
new int[] { Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW,
Color.LTGRAY }, null, Shader.TileMode.REPEAT); // 一个材质,打造出一个线性梯度沿著一条线。
p.setShader(mShader);
// p.setColor(Color.BLUE);
RectF oval2 = new RectF(60, 100, 200, 240);// 设置个新的长方形,扫描测量
canvas.drawArc(oval2, 200, 130, true, p);
// 画弧,第一个参数是RectF:该类是第二个参数是角度的开始,第三个参数是多少度,第四个参数是真的时候画扇形,是假的时候画弧线
//画椭圆,把oval改一下
oval2.set(210,100,250,130);
canvas.drawOval(oval2, p);
canvas.drawText("画三角形:", 10, 200, p);
// 绘制这个三角形,你可以绘制任意多边形
Path path = new Path();
path.moveTo(80, 200);// 此点为多边形的起点
path.lineTo(120, 250);
path.lineTo(80, 250);
path.close(); // 使这些点构成封闭的多边形
canvas.drawPath(path, p);
// 你可以绘制很多任意多边形,比如下面画六连形
p.reset();//重置
p.setColor(Color.LTGRAY);
p.setStyle(Paint.Style.STROKE);//设置空心
Path path1=new Path();
path1.moveTo(180, 200);
path1.lineTo(200, 200);
path1.lineTo(210, 210);
path1.lineTo(200, 220);
path1.lineTo(180, 220);
path1.lineTo(170, 210);
path1.close();//封闭
canvas.drawPath(path1, p);
//画圆角矩形
p.setStyle(Paint.Style.FILL);//充满
p.setColor(Color.LTGRAY);
p.setAntiAlias(true);// 设置画笔的锯齿效果
canvas.drawText("画圆角矩形:", 10, 260, p);
RectF oval3 = new RectF(80, 260, 200, 300);// 设置个新的长方形
canvas.drawRoundRect(oval3, 20, 15, p);//第二个参数是x半径,第三个参数是y半径
//画贝塞尔曲线
canvas.drawText("画贝塞尔曲线:", 10, 310, p);
p.reset();
p.setStyle(Paint.Style.STROKE);
p.setColor(Color.GREEN);
Path path2=new Path();
path2.moveTo(100, 320);//设置Path的起点
path2.quadTo(150, 310, 170, 400); //设置贝塞尔曲线的控制点坐标和终点坐标
canvas.drawPath(path2, p);//画出贝塞尔曲线
//画点
p.setStyle(Paint.Style.FILL);
canvas.drawText("画点:", 10, 390, p);
canvas.drawPoint(60, 390, p);//画一个点
canvas.drawPoints(new float[]{60,400,65,400,70,400}, p);//画多个点
//画图片,就是贴图
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
canvas.drawBitmap(bitmap, 250,360, p);
}
}
ok!哈哈!
http://hi.baidu.com/chenjiaping10/item/e6b42cdd9b84130dd68ed05b
今天要给大家介绍的是在android中画饼图:
画扇形:
RectF oval2 = new RectF(60, 100, 200, 240);
// 设置个新的长方形,60为左上点的x坐标,100为左上点的y坐标;200为右下点的 x坐标,240为右下点的y坐标。
// 画弧,第一个参数是RectF:该类是第二个参数是角度的开始,第三个参数是多少度,第四个参数是真的时候画扇形,是假的时候画弧 线
canvas.drawArc(oval2, 200, 130, true, p);
一、 无网情况下:
由于Android 画图API为提供直接画饼图的方法,采用了比较原始的方法,画扇形,然后拼接在一起,由于为了显示立体效果,程序画了20次,每次改变上下的位置,结果看起来就会有立体感(如果谁有更好的方式,非常愿意学习)
canvas.drawArc(new RectF(0, 0, 300, 100), 0,60, true, paint);
会画一个宽300,高100,水平 顺时针方向的60度的扇形,程序中就是用这种方式拼接成整个饼图的.
二、有网情况下:
使用Google API实现:
WebView webView = new WebView(this);
String url = “http://chart.apis.google.com/chart?cht=p3&chs=350x180&chd=t:30,30,40“;
webView.loadUrl(url);
setContentView(webView);
给Google API传递URL形式的参数,其中cht=p3表示图形类型为饼图,chs=350x180表示宽和高,chd=t:30,30,40表示饼图分为三份,每一份所占的显示比例;
返回结果是一个包含根据参数生成饼图图片的网页,所以以Android控件WebView接收进行显示。
下面为效果图。
Android利用canvas画各种图形(点、直线、弧、圆、椭圆、文字、矩形、多边形、曲线、圆角矩形)
==========================================================
http://my.oschina.net/taisha/blog/71630
我们已经介绍了Canvas,在那里,已经学习了如何创建自己的View。在第7章中也使用了Canvas来为MapView标注覆盖。
画布(Canvas)是图形编程中一个很普通的概念,通常由三个基本的绘图组件组成:
Canvas 提供了绘图方法,可以向底层的位图绘制基本图形。
Paint 也称为”刷子”,Paint可以指定如何将基本图形绘制到位图上。
Bitmap 绘图的表面。
Android绘图API支持透明度、渐变填充、圆边矩形和抗锯齿。遗憾的是,由于资源限制,它还不支持矢量图形,它使用的是传统光栅样式的重新绘图。
这种光栅方法的结果是提高了效率,但是改变一个Paint对象不会影响已经画好的基本图形,它只会影响新的元素。
提示:
如果你拥有Windows开发背景,那么Android的2D绘图能力大致相当于GDI+的能力。
1. 可以画什么?
Canvas类封装了用作绘图表面的位图;它还提供了draw*方法来实现设计。
下面的列表提供了对可用的基本图形的简要说明,但并没有深入地探讨每一个draw方法的详细内容:
drawARGB / drawRGB / drawColor 使用单一的颜色填充画布。
drawArc 在一个矩形区域的两个角之间绘制一个弧。
drawBitmap 在画布上绘制一个位图。可以通过指定目标大小或者使用一个矩阵来改变目标位图的外观。
drawBitmapMesh 使用一个mesh(网)来绘制一个位图,它可以通过移动网中的点来操作目标的外观。
drawCircle 以给定的点为圆心,绘制一个指定半径的圆。
drawLine(s) 在两个点之间画一条(多条)直线。
drawOval 以指定的矩形为边界,画一个椭圆。
drawPaint 使用指定的Paint填充整个Canvas
drawPath 绘制指定的Path。Path对象经常用来保存一个对象中基本图形的集合。
drawPicture 在指定的矩形中绘制一个Picture对象。
drawPosText 绘制指定了每一个字符的偏移量的文本字符串。
drawRect 绘制一个矩形。
drawRoundRect 绘制一个圆角矩形。
drawText 在Canvas上绘制一个文本串。文本的字体、大小和渲染属性都设置在用来渲染文本的Paint对象中。
drawTextOnPath 在一个指定的path上绘制文本。
drawVertices 绘制一系列三角形面片,通过一系列顶点来指定它们。
这些绘图方法中的每一个都需要指定一个Paint对象来渲染它。在下面的部分中,将学习如何创建和修改Paint对象,从而在绘图中完成大部分工作。
2. 从Paint中完成工作
Paint类相当于一个笔刷和调色板。它可以选择如何使用上面描述的draw方法来渲染绘制在画布上的基本图形。通过修改Paint对象,可以在绘图的时候控制颜色、样式、字体和特殊效果。最简单地,setColor可以让你选择一个Paint的颜色,而Paint对象的样式(使用setStyle控制)则可以决定是绘制绘图对象的轮廓(STROKE),还是只填充每一部分(FILL),或者是两者都做(STROKE_AND_FILL)
除了这些简单的控制之外,Paint类还支持透明度,另外,它也可以通过使用各种各样的阴影、过滤器和效果进行修改,从而提供由更丰富的、复杂的画笔和颜料组成的调色板。
Android SDK包含了一些非常好的实例,它们说明了Paint类中可用的大部分功能。你可以在API demos的graphics子目录中找到它们:
sdk root folder]\samples\ApiDemos\src\com\android\samples\graphics
在下面的部分中,将学习和使用其中的部分功能。这些部分只是简单地罗列了它们能实现的效果(例如渐变和边缘浮雕),而没有详细地列出所有可能的情况。
使用透明度
Android中的所有颜色都包含了一个不透明组件(alpha通道)。
当创建一个颜色的时候,可以使用argb或者parseColor方法来定义它的alpha值,如下所示:
Java代码:
// 使用红色,并让它50%透明
int opacity = 127;
int intColor = Color.argb(opacity, 255, 0, 0);
int parsedColor = Color.parseColor(“#7FFF0000”);
复制代码
或者,也可以使用setAlpha方法来设置已存在的Paint对象的透明度:
Java代码:
// 让颜色50%透明
int opacity = 127;
myPaint.setAlpha(opacity);
复制代码
创建一个不是100%透明的颜色意味着,使用它绘制的任何基本图形都将是部分透明的--也就是说,在它下面绘制的所有基本图形都是部分可见的。
可以在任何使用了颜色的类或者方法中使用透明效果,包括Paint、Shader和Mask Filter。
Shader介绍
Shader类的派生类可以创建允许使用多种固体颜色填充绘图对象的Paint。
对Shader最常见的使用是定义渐变填充;渐变是在2D图像中添加深度和纹理的最佳方式之一。Android包含了一个Bitmap Shader和一个Compose Shader,同时,还包含了三个渐变的Shader。
试图用语言来描述绘图的效果本来就是没有意义的,所以看一下图就应该可以知道每一种Shader是如何工作的。图中从左到右依次代表的是LinearGradient、RadialGradient和 SweepGradient.
提示:
没有包含的是ComposerShader,它可以创建多个Shader和BitmapShader的组合,从而可以在一个位图图像的基础上创建一个绘图刷。
要在绘图的时候使用一个Shader,可以使用setShader方法将其应用到一个Paint中,如下面的代码所示:
Paint shaderPaint = new Paint();
shaderPaint.setShader(myLinearGradient);
你使用这个Paint所绘制的任何东西都将使用你指定的Shader进行填充,而不是使用Paint本身的颜色进行填充。
定义渐变Shader
如上所示,使用渐变Shader可以让你使用交替改变的颜色来填充图片;你可以将颜色渐变定义为两种颜色的简单交替,如下所示:
java代码:
int colorFrom = Color.BLACK;
int colorTo = Color.WHITE;
LinearGradient linearGradientShader = new LinearGradient(x1, y1, x2, y2, colorFrom, colorTo, TileMode.CLAMP);
复制代码
或者,你还可以定义更复杂的按照设定比例进行分布的颜色序列,如下面的RadialGradientShader例子所示:
java代码:
int[] gradientColors = new int[3];
gradientColors[0] = Color.GREEN;
gradientColors[1] = Color.YELLOW;
gradientColors[2] = Color.RED;
float[] gradientPositions = new float[3];
gradientPositions[0] = 0.0f;
gradientPositions[1] = 0.5f;
gradientPositions[2] = 1.0f;
RadialGradient radialGradientShader=new RadialGradient(centerX,centerY, radius, gradientColors, gradientPositions, TileMode.CLAMP);
复制代码
每一种渐变Shader(线性的、辐射形的和扫描状的)都可以使用以上这两种技术来定义渐变填充。
使用Shader TileModes
渐变Shader的画刷大小既可以显式地使用有边界的矩形来定义,也可以使用中心点和半径长度来定义。Bitmap Shader可以通过它的位图大小来决定它的画刷大小。
如果Shader画刷所定义的区域比要填充的区域小,那么TileMode将会决定如何处理剩余的区域:
CLAMP 使用Shader的边界颜色来填充剩余的空间。
MIRROR 在水平和垂直方向上拉伸Shader图像,这样每一个图像就都能与上一个缝合了。
REPEAT 在水平和垂直方向上重复Shader图像,但不拉伸它。
使用MaskFilter
MaskFilter类可以为Paint分配边缘效果。
对MaskFilter的扩展可以对一个Paint边缘的alpha通道应用转换。Android包含了下面几种MaskFilter:
BlurMaskFilter 指定了一个模糊的样式和半径来处理Paint的边缘。
EmbossMaskFilter 指定了光源的方向和环境光强度来添加浮雕效果。
要应用一个MaskFilter,可以使用setMaskFilter方法,并传递给它一个MaskFilter对象。下面的例子是对一个已经存在的Paint应用一个EmbossMaskFilter:
java代码:
// 设置光源的方向
float[] direction = new float[]{ 1, 1, 1 };
//设置环境光亮度
float light = 0.4f;
// 选择要应用的反射等级
float specular = 6;
// 向mask应用一定级别的模糊
float blur = 3.5f;
EmbossMaskFilter emboss=new EmbossMaskFilter(direction,light,specular,blur);
// 应用mask myPaint.setMaskFilter(emboss);
复制代码
SDK中包含的FingerPaint API demo是说明如何使用MaskFilter的一个非常好的例子。它展示了这两种filter的效果。
使用ColorFilter
MaskFilter是对一个Paint的alpha通道的转换,而ColorFilter则是对每一个RGB通道应用转换。所有由ColorFilter所派生的类在执行它们的转换时,都会忽略alpha通道。
Android包含三个ColorFilter:
ColorMatrixColorFilter 可以指定一个4×5的ColorMatrix并将其应用到一个Paint中。ColorMatrixes通常在程序中用于对图像进行处理,而且由于它们支持使用矩阵相乘的方法来执行链接转换,所以它们很有用。
LightingColorFilter 乘以第一个颜色的RGB通道,然后加上第二个颜色。每一次转换的结果都限制在0到255之间。
PorterDuffColorFilter 可以使用数字图像合成的16条Porter-Duff 规则中的任意一条来向Paint应用一个指定的颜色。
使用setColorFilter方法应用ColorFilter,如下所示:
myPaint.setColorFilter(new LightingColorFilter(Color.BLUE, Color.RED));
API中的ColorMatrixSample是说明如何使用ColorFilter和Color Matrix的非常好的例子。
使用PathEffect
到目前为止,所有的效应都会影响到Paint填充图像的方式;PathEffect是用来控制绘制轮廓(线条)的方式。
PathEffect对于绘制Path基本图形特别有用,但是它们也可以应用到任何Paint中从而影响线条绘制的方式。
使用PathEffect,可以改变一个形状的边角的外观并且控制轮廓的外表。Android包含了多个PathEffect,包括:
CornerPathEffect 可以使用圆角来代替尖锐的角从而对基本图形的形状尖锐的边角进行平滑。
DashPathEffect 可以使用DashPathEffect来创建一个虚线的轮廓(短横线/小圆点),而不是使用实线。你还可以指定任意的虚/实线段的重复模式。
DiscretePathEffect 与DashPathEffect相似,但是添加了随机性。当绘制它的时候,需要指定每一段的长度和与原始路径的偏离度。
PathDashPathEffect 这种效果可以定义一个新的形状(路径)并将其用作原始路径的轮廓标记。
下面的效果可以在一个Paint中组合使用多个Path Effect。
SumPathEffect 顺序地在一条路径中添加两种效果,这样每一种效果都可以应用到原始路径中,而且两种结果可以结合起来。
ComposePathEffect 将两种效果组合起来应用,先使用第一种效果,然后在这种效果的基础上应用第二种效果。
对象形状的PathEffect的改变会影响到形状的区域。这就能够保证应用到相同形状的填充效果将会绘制到新的边界中。
使用setPathEffect方法可以把PathEffect应用到Paint对象中,如下所示:
java代码:
borderPaint.setPathEffect(new CornerPathEffect(5));
复制代码
PathEffect API示例给出了如何应用每一种效果的指导说明。
修改Xfermode
可以通过修改Paint的Xfermode来影响在Canvas已有的图像上面绘制新的颜色的方式。
在正常的情况下,在已有的图像上绘图将会在其上面添加一层新的形状。如果新的Paint是完全不透明的,那么它将完全遮挡住下面的Paint;如果它是部分透明的,那么它将会被染上下面的颜色。
下面的Xfermode子类可以改变这种行为:
AvoidXfermode 指定了一个颜色和容差,强制Paint避免在它上面绘图(或者只在它上面绘图)。
PixelXorXfermode 当覆盖已有的颜色时,应用一个简单的像素XOR操作。
PorterDuffXfermode 这是一个非常强大的转换模式,使用它,可以使用图像合成的16条Porter-Duff规则的任意一条来控制Paint如何与已有的Canvas图像进行交互。
要应用转换模式,可以使用setXferMode方法,如下所示:
java代码:
AvoidXfermode avoid = new AvoidXfermode(Color.BLUE, 10, AvoidXfermode.Mode. AVOID); borderPen.setXfermode(avoid);
复制代码
使用抗锯齿效果提高Paint质量
在绘制一个新的Paint对象时,可以通过传递给它一些标记来影响它被渲染的方式。ANTI_ALIAS_FLAG是其中一种很有趣的标记,它可以保证在绘制斜线的时候使用抗锯齿效果来平滑该斜线的外观。
在绘制文本的时候,抗锯齿效果尤为重要,因为经过抗锯齿效果处理之后的文本非常容易阅读。要创建更加平滑的文本效果,可以应用SUBPIXEL_TEXT_FLAG,它将会应用子像素抗锯齿效果。
也可以手工地使用setSubpixelText和setAntiAlias方法来设置这些标记,如下所示:
java代码:
myPaint.setSubpixelText(true);
myPaint.setAntiAlias(true);
复制代码
2D图形的硬件加速
在当前这个到处都是2D图形爱好者的时代,Android允许你使用硬件加速来渲染你的应用程序。
如果设备可以使用硬件加速,那么通过设置这个标记可以让活动中的每一个View都能使用硬件渲染。尽管减少了系统处理程序的负载,但在极大地提高了图像处理速度的同时,硬件加速也带来了相应的负面效果。
使用requestWindowFeature方法,可以在你的活动中应用Window.FEATURE_OPENGL标记来打开硬件加速,如下所示:
java代码:
myActivity.requestWindowFeature(Window.FEATURE_OPENGL);
复制代码
并不是Android中所有的2D绘图基本图形都被硬件支持(特别是前面描述的大部分PathEffect)。
与此同时,由于整个活动实际上是作为一个Canvas进行渲染的,所以对任何View的无效请求都将会导致整个活动被重新绘制。
Canvas绘图最佳实践经验
2D自绘操作是非常耗费处理程序资源的;低效的绘图方法会阻塞GUI线程,并且会对应用程序的响应造成不利的影响。对于那些只有一个处理程序的资源受限的环境来说,这一点就更加现实了。
这里需要注意onDraw方法的资源消耗以及CPU周期的耗费,这样才能保证不会把一个看起来很吸引人的应用程序变得完全没有响应。
目前有很多技术可以帮助将与自绘控件相关的资源消耗最小化。我们关心的不是一般的原则,而是某些Android特定的注意事项,从而保证你可以创建外观时尚、而且能够保持交互的活动(注意,以下这个列表并不完整):
考虑硬件加速 OpenGL硬件加速对2D图形的支持是非常好的,所以你总是应该考虑它是否适合你的活动。另一种比较优秀的方法是只用一个单独的View和迅速的、耗时的更新来组成活动。一定要保证你使用的基本图形能够被硬件支持。
考虑大小和方向 当在设计View和布局的时候,一定要保证考虑(和测试)它们在不同的分辨率和大小下的外观。
只创建一次静态对象 在Android中对象的创建是相当昂贵的。因此,在可能的地方,应用只创建一次像Paint对象、Path和Shader这样的绘图对象,而不是在View每次无效的时候都重新创建它们。
记住onDraw是很消耗资源的 执行onDraw方法是很消耗资源的处理,它会强制Android执行多个图片组合和位图构建操作。下面有几点建议可以让你修改Canvas的外观,而不用重新绘制它:
使用Canvas转换 可以使用像rotate和translate这样的转换,来简化Canvas中元素复杂的相关位置。例如,相比放置和旋转一个表盘周围的每一个文本元素,你可以简单地将canvas旋转22.5?,然后在相同的位置绘制文本。
使用动画 可以考虑使用动画来执行View的预设置的转换,而不是手动地重新绘制它。在活动的View中可以执行缩放、旋转和转换动画,并可以提供一种能够有效利用资源的方式来提供缩放、旋转或者抖动效果。
考虑使用位图和9 Patch 如果View使用了静态背景,那么你应该考虑使用一个图片,如位图或者9 patch,而不是手动地重新绘制。
高级指南针表盘的例子
已经创建了一个简单的指南针。而在上一章,你又回到了这个例子,对它进行了扩展从而使它够使用加速计硬件来显示横向和纵向方向。
那些例子中的UI都很简单,从而保证了那些章节中的代码都尽可能地清晰。
在下面的例子中,将对CompassView的onDraw方法做一些重要的改动,从而把它从一个简单的、平面的指南针,变成一个动态的航空地平仪(artificial horizon ),如图所示。
Android利用canvas画各种图形(点、直线、弧、圆、椭圆、文字、矩形、多边形、曲线、圆角矩形)
由于上面的图片是黑白的,所以需要实际动手创建这个控件来看到完全的效果。
(1) 首先,通过修改colors.xml资源文件来包含边界、表盘阴影以及天空和地面的颜色值。同时还要更新边界和盘面标记所使用的颜色。
java代码:
#FFFF
#F000
#FFFF
#7AAA
#FF444444
#FF323232
#FF414141
#FFFFFFFF
#FFA52A2A
#FFFFC125
#FF5F9EA0
#FF00008B
复制代码
(2) 用作航空地平仪的天空和地面的Paint和Shader对象是根据当前View的大小创建的,所以它们不能像你在创建的Paint对象那样,是静态的。因此,不再创建Paint对象,取而代之的是构造它们所使用的渐变数组和颜色。
java代码:
int[] borderGradientColors;
float[] borderGradientPositions;
int[] glassGradientColors;
float[] glassGradientPositions;
int skyHorizonColorFrom;
int skyHorizonColorTo;
int groundHorizonColorFrom;
int groundHorizonColorTo;
复制代码
(3) 更新CompassView的initCompassView方法,来使用第(1)步中所创建的资源来初始化第(2)步中所创建的变量。现存的方法代码大部分可以保留,而只需要对textPaint、circlePaint和markerPaint变量做些许改动,如下所示:
java代码:
protected void initCompassView() {
setFocusable(true);
// 获得外部资源
Resources r = this.getResources();
circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
circlePaint.setColor(R.color.background_color);
circlePaint.setStrokeWidth(1);
circlePaint.setStyle(Paint.Style.STROKE);
northString = r.getString(R.string.cardinal_north);
eastString = r.getString(R.string.cardinal_east);
southString = r.getString(R.string.cardinal_south);
westString = r.getString(R.string.cardinal_west);
textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
textPaint.setColor(r.getColor(R.color.text_color));
textPaint.setFakeBoldText(true);
textPaint.setSubpixelText(true);
textPaint.setTextAlign(Align.LEFT);
textHeight = (int)textPaint.measureText("yY");
markerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
markerPaint.setColor(r.getColor(R.color.marker_color));
markerPaint.setAlpha(200);
markerPaint.setStrokeWidth(1);
markerPaint.setStyle(Paint.Style.STROKE);
markerPaint.setShadowLayer(2, 1, 1, r.getColor(R.color.shadow_color));
复制代码
a. 创建径向Shader用来绘制外边界所使用的颜色和位置数组。
java代码:
borderGradientColors = new int[4];
borderGradientPositions = new float[4];
borderGradientColors[3] = r.getColor(R.color.outer_border);
borderGradientColors[2] = r.getColor(R.color.inner_border_one);
borderGradientColors[1] = r.getColor(R.color.inner_border_two);
borderGradientColors[0] = r.getColor(R.color.inner_border);
borderGradientPositions[3] = 0.0f;
borderGradientPositions[2] = 1-0.03f;
borderGradientPositions[1] = 1-0.06f;
复制代码
b. 现在创建径向渐变的颜色和位置数组,它们将用来创建半透明的"glass dome"(玻璃圆顶),它放置在View的上面,从而使人产生深度的幻觉。
java代码:
glassGradientColors = new int[5];
glassGradientPositions = new float[5];
int glassColor = 245;
glassGradientColors[4]=Color.argb(65,glassColor,glassColor, glassColor);
glassGradientColors[3]=Color.argb(100,glassColor,glassColor,glassColor);
glassGradientColors[2]=Color.argb(50,glassColor,glassColor, glassColor);
glassGradientColors[1]=Color.argb(0,glassColor,glassColor, glassColor);
glassGradientColors[0]=Color.argb(0,glassColor,glassColor, glassColor);
glassGradientPositions[4] = 1-0.0f;
glassGradientPositions[3] = 1-0.06f;
glassGradientPositions[2] = 1-0.10f;
glassGradientPositions[1] = 1-0.20f;
glassGradientPositions[0] = 1-1.0f;
复制代码
c. 最后,获得创建线性颜色渐变所使用的颜色,它们将用来表示航空地平仪中的天空和地面。
java代码:
skyHorizonColorFrom = r.getColor(R.color.horizon_sky_from);
skyHorizonColorTo = r.getColor(R.color.horizon_sky_to);
groundHorizonColorFrom = r.getColor(R.color.horizon_ground_from);
groundHorizonColorTo = r.getColor(R.color.horizon_ground_to);
复制代码
(6) 创建用来填充圆的每个部分(地面和天空)的路径。每一部分的比例应该与形式化之后的俯仰值有关。
java代码:
Path skyPath = new Path();
skyPath.addArc(innerBoundingBox, -tiltDegree, (180 + (2 * tiltDegree)));
复制代码
(7) 将Canvas围绕圆心,按照与当前翻转角相反的方向进行旋转,并且使用在第(4)步中所创建的Paint来绘制天空和地面路径。
java代码:
canvas.rotate(-rollDegree, px, py);
canvas.drawOval(innerBoundingBox, groundPaint);
canvas.drawPath(skyPath, skyPaint);
canvas.drawPath(skyPath, markerPaint);
复制代码
(8) 接下来是盘面标记,首先计算水平的水平标记的起止点。
java代码:
int markWidth = radius / 3; int startX = center.x - markWidth; int endX = center.x + markWidth;
复制代码
(9) 要让水平值更易于读取,应该保证俯仰角刻度总是从当前值开始。下面的代码计算了天空和地面的接口在水平面上的位置:
java代码:
double h = innerRadius*Math.cos(Math.toRadians(90-tiltDegree)); double justTiltY = center.y - h;
复制代码
(10) 找到表示每一个倾斜角的像素的数目。
java代码:
float pxPerDegree = (innerBoundingBox.height()/2)/45f;
复制代码
(11) 现在遍历180度,以当前的倾斜值为中心,给出一个可能的俯仰角的滑动刻度。
java代码:
for (int i = 90; i >= -90; i -= 10) {
double ypos = justTiltY + i*pxPerDegree;
// 只显示内表盘的刻度
if ((ypos < (innerBoundingBox.top + textHeight)) || (ypos > innerBoundingBox.bottom - textHeight)) continue;
// 为每一个刻度增加画一个直线和一个倾斜角
canvas.drawLine(startX, (float)ypos, endX, (float)ypos, markerPaint);
t displayPos = (int)(tiltDegree - i);
String displayString = String.valueOf(displayPos);
float stringSizeWidth = textPaint.measureText(displayString);
canvas.drawText(displayString, (int)(center.x-stringSizeWidth/2), (int)(ypos)+1, textPaint);
}
复制代码
(12) 现在,在大地/天空接口处绘制一条更粗的线。在画线之前,改变markerPaint对象的线条粗度(然后把它设置回以前的值)。
java代码:
markerPaint.setStrokeWidth(2);
canvas.drawLine(center.x - radius / 2, (float)justTiltY, center.x + radius / 2, (float)justTiltY, markerPaint);
markerPaint.setStrokeWidth(1);
复制代码
(13) 要让用户能够更容易地读取精确的翻转值,应该画一个箭头,并显示一个文本字符串来表示精确值。
创建一个新的Path,并使用moveTo/lineTo方法构建一个开放的箭头,它指向直线的前方。然后绘制路径和一个文本字符串来展示当前的翻转。
java代码:
// 绘制箭头
Path rollArrow = new Path();
rollArrow.moveTo(center.x - 3, (int)innerBoundingBox.top + 14);
rollArrow.lineTo(center.x, (int)innerBoundingBox.top + 10);
rollArrow.moveTo(center.x + 3, innerBoundingBox.top + 14);
rollArrow.lineTo(center.x, innerBoundingBox.top + 10);
canvas.drawPath(rollArrow, markerPaint);
// 绘制字符串
String rollText = String.valueOf(rollDegree);
double rollTextWidth = textPaint.measureText(rollText);
canvas.drawText(rollText, (float)(center.x - rollTextWidth / 2), innerBoundingBox.top + textHeight + 2, textPaint);
复制代码
(14) 将Canvas旋转到正上方,这样就可以绘制其他的盘面标记了。
java代码:
canvas.restore();
复制代码
(15) 每次将Canvas旋转10度,然后画一个标记或者一个值,直到画完翻转值表盘为止。当完成表盘之后,把Canvas恢复为正上方的方向。
java代码:
canvas.save();
canvas.rotate(180, center.x, center.y);
for (int i = -180; i < 180; i += 10) {
// 每30度显示一个数字值
if (i % 30 == 0) {
String rollString = String.valueOf(i*-1);
float rollStringWidth = textPaint.measureText(rollString);
PointF rollStringCenter = new PointF(center.x-rollStringWidth / 2, innerBoundingBox.top+1+textHeight);
canvas.drawText(rollString, rollStringCenter.x, rollStringCenter.y, textPaint);
}
// 否则,绘制一个标记直线
else { canvas.drawLine(center.x, (int)innerBoundingBox.top, center.x, (int)innerBoundingBox.top + 5, markerPaint);
}
canvas.rotate(10, center.x, center.y);
}
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2012/1224/738.html
Android绘图:绘制直线的 drawLine方法
【功能说明】该方法用于在画布上绘制直线,通过指定直线的两个端点坐标来绘制。该方法只能绘制单条直线;如果需要同时绘制多条直线,则可以使用drawLines方法。
【基本语法】public void drawLine (float startX, float startY, float stopX, float stopY, Paint paint)
参数说明
startX:起始端点的X坐标。
startY:起始端点的Y坐标。
stopX:终止端点的X坐标。
stopY:终止端点的Y坐标。
paint:绘制直线所使用的画笔。
【实例演示】下面通过代码来演示如何在画布上绘制直线。
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
mPaint.setColor(Color.BLACK); //设置画笔颜色
canvas.drawColor(Color.WHITE); //设置背景颜色
mPaint.setStrokeWidth((float) 1.0); //设置线宽
canvas.drawLine(50, 50, 450, 50, mPaint); //绘制直线
mPaint.setStrokeWidth((float) 5.0); //设置线宽
canvas.drawLine(50, 150, 450, 150, mPaint); //绘制直线
mPaint.setStrokeWidth((float) 10.0); //设置线宽
canvas.drawLine(50, 250, 450, 250, mPaint); //绘制直线
mPaint.setStrokeWidth((float) 15.0); //设置线宽
canvas.drawLine(50, 350, 450, 350, mPaint); //绘制直线
mPaint.setStrokeWidth((float) 20.0); //设置线宽
canvas.drawLine(50, 450, 450, 450, mPaint); //绘制直线
}
每条直线的线宽都不一样。