文摘摘抄至:株洲新城 IT教育 李赞红老师。非常感谢老师。想过摘抄一边的方式去让自己记住一些知识点。如果不适合,我会立刻删除
第二章 Graphics2D API
2.1、概述
在Android中,2D图形由 AndroidSDK提供。本章我们将会向大家介绍 Graphics2D涉及到的常用类。如基本的数据结构、位图类、绘图类等,并介绍和使用常见的绘图方法。2.2、Point类和 PointF类
在 Graphics2D中,Point类是一种简单的结构,代表一个“点”,实现了 Parcelable接口。支持序列化与反序列化。Point类定义了两个 int成员变量 x 和 y 。代表点的 x 坐标 和 y 坐标,图形坐标系与数学中的平面坐标系有所不同,x 方向向左为负,向右为正,y 方向向上为负,向下为正。图形坐标系的原点在左上角。这一点特别注意。所以,默认情况下,当 x ,y 为正数时该点会显示在屏幕之内(取决于屏幕的大小)。如果为负数就显示在屏幕之外。在 Android中,通过相应的 API是可以对图形坐标系进行平移和旋转的。Point类作为一种最简单的数据结构,提供的也是只有简单的功能,大概如下:
初始化点的 x, y坐标:
public Point(){}
public Point(int x,int y){
this.x=x;
this.y=y;
}
public Point(Point src){
this.x=src.x;
this.y=src.y;
}
可以为 Point提供一个(x,y)坐标值,也可以将一个 Point额坐标值复制给另一个 Point。 改变 x,y的坐标值:Point类提供了三个方法用于改变 Point对象的 x,y值。分别是:
public void set(int x,int y){
this.x=x;
this.y=y;
}
public final void negate(){
x = -x;
y = -y;
}
public final void offset(int dx,int dy){
x += dx;
y += dy;
}
其中:
- set() 方法简单粗暴的为 x,y重新赋值
- negate() 方法将 x,y 取反
- offset() 方法则改变 x,y的偏移量,正负符号决定坐标偏移的方向
序列化.与反序列化:重写了 Parcelable接口的 writeToParcel()、readFromParcel()等相关方法。虽然 Java中也有序列化接口 Serializabl,但是 Parcelable的效率更高、性能更好。 PointF类和 Point类的定义是完全一样的,最大的不同就是成员变量 x,y 的类型不是 int,而是 float。这也是加了后缀“F”的原因。不过,PointF提供了一个很贴心的功能,定义了 length()方法计算坐标原点(0,0)到(x,y)之间的距离。而且有连个版本:静态的 length()和非静态的 length()。代码如下:
public final float length(){
return length(x,y);
}
public static float length(float x,float y){
return FloatMath.sqrt(x*x + y*y);
}
2.3、Rect 类和 RectF类
Rect类定义了一个矩形结构,同样实现了 Parcelable序列化接口。Rect来定义了 left,top,right,bottom四个成员变量。我们需要正确的理解这 4个成员变量的作用 :- left:矩形左边线条离 y轴的距离
- top:矩形上面线条离 x轴的距离
- right:矩形右边线条离 y轴的距离
- bottom:矩形底部线条离 x轴的距离
矩形是一种非常常见的图像结构,并且能衍生出更多的图形。如:椭圆、扇形、弧线等等;矩形还能进行各种图形运算,如:交集、并集等等。所以,与之对应的 Rect类功能也更加复杂。有人会疑惑为什么不直接指定左上角的坐标、宽度和高度来确定一个矩形 ? 因为,指定 top、left、right 和 bottom 更符合坐标系的数学逻辑。也能更好的支持矩形计算。
Rect的主要功能有 :
初始化:主要有两种初始化的方法:一是直接指定 left、top、right、bottom等 4个成员变量的值。而是从另一个 Rect对象中复制。下面是 Rect的三个构造方法:
public Rect(){}
public Rect(int left, int top, int right, int bottom) {
this.left = left;
this.top= top;
this.right= right;
this.bottom= bottom;
}
public Rect(Rect r) {
if (r == null) {
left = top = right = bottom = 0;
}else{
left = r.left;
top = r.top;
right = r.right;
bottom = r.bottom;
}
}
增值计算:根据 left、top、right、bottom等 4个成员变量计算矩形的宽度、高度或中心点的坐标。主要的方法定义如下:
public final boolean isEmpty(){
return left >= right || top >= bottom ;
}
判断 Rect是否为空,也就是矩形区域面积是否为 0,或者为无效矩形。
public final int width(){
return right - left ;
}
返回矩形的宽度
public final int height(){
return bottom - top ;
}
返回矩形的高度
public final int centerX(){
return (left+right)>>1 ;
}
计算矩形中心点的 x 坐标,右移一位相当于除以 2.移位运算比普通的除法运算效率更高
public final int centerY(){
return (top+bottom)>>1;
}
计算矩形中心点的 y 坐标
public final flaot exactCenterX(){
return (left+right)*0.5f ;
}
计算矩形中心点的 x坐标,返回 float类型,结果更精确
public final flaot exactCenterY(){
return (top+bottom)*0.5f ;
}
计算矩形中心的点的 y坐标,返回 float类型,结果更精确 改变矩形的位置和大小,通过修改 let、top、right 和 bottom等 4个成员变量的值。获取矩形位置的平移、放大、缩小等结果
public void setEmpty(){
left = right = top = bottom = 0;
}
将矩形的 left、top、right 和 bottom 置 0
public void set(int left, int top, int right, int bottom) {
this.left = left;
this.top = top;
this.right = right;
this.bottom = bottom;
}
给 left、top、right 和 bottom 重新赋值
public void set(Rect src){
this.left = src.left;
this.top = src.top;
this.right = src.right;
this.bottom = src.bottom;
}
矩形的 left、top、right 和 bottom来自于另一个矩形 src
public void offset(int dx, int dy) {
left += dx ;
top += dy;
right += dx ;
bottom += dy ;
}
矩形的 left 和 right 同时移动相同的距离 dx,矩形的 top 和 bottom同时移动相同的距离 dy。实际就是将矩形移动(dx,dy)距离。正负决定移动的方向。此为 绝对位移
public void offsetTo(int newLeft, int newTop) {
right += newLeft - left ;
bottom += newTop - top ;
left = newLeft ;
top = newTop ;
}
offsetTo()方法遇事位移。和offset()不同的是。offset为决定位移,而offsetTo()为相对位移
public void inset(int dx,int dy){
left += dx;
top += dy;
right -= dx;
bottom -= dy;
}
实现了矩形的缩放功能,缩放中心就是聚集性的中心点。要注意的是 dx,dy为正数时,表示缩小;负数时,表示放大 包含测试:支持一个点是否位于矩形内和一个矩形是否位于另一个矩形内
public boolean contains(int x, int y) {
return left < right && top < bottom // check for empty first
&& x >= left && x < right && y >= top && y < bottom;
}
判断(x,y)是否位于矩形内
public boolean contains(int left, int top, int right, int bottom) {
// check for empty first
return this.left < this.right && this.top < this.bottom
// now check for containment
&& this.left <= left && this.top <= top
&& this.right >= right && this.bottom >= bottom;
}
判断传递过来的矩形是否位于矩形内
public boolean contains(Rect r) {
// check for empty first
return this.left < this.right && this.top < this.bottom
// now check for containment
&& left <= r.left && top <= r.top && right >= r.right && bottom >= r.bottom;
}
判断传递过来的矩形是否位于矩形内 矩形的交集与并集运算:交集是指两个矩形相交的公共部分,并集是指两个矩形所占有的最大面积区域。
主要方法如下:
public boolean intersect(int left, int top, int right, int bottom) {
if (this.left < right && left < this.right && this.top < bottom && top < this.bottom) {
if(this.left < left) this.left = left;
if(this.top < top) this.top = top ;
if(this.right > right) this.right = right;
if(this.bottom > bottom) this.bottom = bottom;
return true ;
}
return false ;
}
传入 Rect的 left、top、right、bottom,并构建的 Rect对象与当前的 Rect对象做交集运算,结果保存在当前的 Rect对象中
public boolean intersect(Rect r) {
return intersect(r.left, r.top, r.right, r.bottom);
}
传入新的 Rect对象,并将该对象与当前 Rect对象做交集运算。结果保存在当前的 Rect对象中。
比如有下面的代码段:
Rect rect1 = new Rect(0, 0, 400, 400);
Rect rect2 = new Rect(200, 200, 600, 600);
rect1.intersect(rect2);
此时,rect1的left、top、right、bottom属性被改变了,分别为 200、200、400、400。如下图所示,黄色部分即使两个矩形的交集:
I/info(1513): rect1 = Rect(0, 0 - 400, 400)
I/info(1513): rect1 = Rect(200, 200 - 400, 400)
public void union(int left, int top, int right, int bottom) {
if ((left < right) && (top < bottom)) {
if ((this.left < this.right) && (this.top < this.bottom)) {
if (this.left > left) this.left = left;
if (this.top > top) this.top = top;
if (this.right < right) this.right = right;
if (this.bottom < bottom) this.bottom = bottom;
} else {
this.left = left;
this.top = top;
this.right = right;
this.bottom = bottom;
}
}
}
public void union(Rect r) {
union(r.left, r.top, r.right, r.bottom);
}
union()方法是计算两个矩形的并集,传入一个新的 Rect,与当前的 Rect进行并集运算,并将结果保存在当前 Rect对象中。比如有下面代码段:
Rect rect1 = new Rect(0, 0, 400, 400);
Rect rect2 = new Rect(200, 200, 600, 600);
rect1.union(rect2);
运行后与交集一样,最终的结果保存在 rect1对象中,rect1的 left、top、right、bottom属性值分别为 :0,0,600,600.也就是说。并集取得是四个方向的最大值。如下图所示中的蓝色区域即为 rect1的最终结果。
I/info(2143): rect1 = Rect(0, 0 - 400, 400)
I/info(2143): rect1 = Rect(0, 0 - 600, 600)
与 Rect类类似的还有 RectF类,Rect类的代码与 Rect如出一辙,主要的不同是 Rect的 left、top、right、bottom四个成员变量为 int类型,而 RectF为 float类型
在开发中,常常会出现 Rect与 RectF相互转换的情况,Rect类中没有定义与 RectF相关的任何信息。但是,在 RectF类中,则定义了二者互相转换的方法
Rectf转化成 Rect。RectF定义了两个名为 round 和 roundOut的方法。round()方法将 RectF类的类型为 float的 left、top、right、bottom属性以四舍五入的方式转换成 int,再通过 Rect类型的参数传回,roundOut()方法虽然和 round()差不过,但是在某些情况下返回的矩形区域要大些,从下面的表格额对比中可以看出端倪来。
public class FastMath {
/**
* Fast round from float to int. This is faster than Math.round()
* thought it may return slightly different results. It does not try to
* handle (in any meaningful way) NaN or infinities.
*/
public static int round(float value) {
long lx = (long) (value * (65536 * 256f));
return (int) ((lx + 0x800000) >> 24);
}
}
I/System.out(3112): (int)FloatMath.floor(12.4) = 12
I/System.out(3112): (int)FloatMath.floor(20.8) = 20
I/System.out(3112): (int)FloatMath.ceil(100.3f) = 101
I/System.out(3112): (int)FloatMath.ceil(202.9f) = 203
left = 12.4 | top = 20.8 | right = 100.3 | bottom = 202.9 | |
---|---|---|---|---|
RectF() | 12.4 | 20.8 | 100.3 | 202.9 |
round() | 12 | 20 | 100 | 202 |
roundOut() | 12 | 20 | 101 | 203 |
public void round(Rect dst) {
dst.set(FastMath.round(left), FastMath.round(top),
FastMath.round(right), FastMath.round(bottom));
}
public void roundOut(Rect dst) {
dst.set((int) FloatMath.floor(left), (int) FloatMath.floor(top),
(int) FloatMath.ceil(right), (int) FloatMath.ceil(bottom));
}
Rect转成 RectF就相对简单了,实例化 RectF时,构造方法支持传递 Rect对象作为参数
public RectF(RectF r) {
if (r == null) {
left = top = right = bottom = 0.0f;
} else {
left = r.left;
top = r.top;
right = r.right;
bottom = r.bottom;
}
}
2.4、Bitmap类 和 BitmapDrawable类
Bitmap译为“位图”,用于存储 png、jpg、gif 等格式的是图片数据,很多时候如果需要在 Android中对图片进行处理。需要先将图片读入 Bitmap对象,接着调用相关的 API对图片进行处理和加工。图片读取操作是有 BitmapFactory类完成的。还类定义了若干方法用于读取图片资源:- public static Bitmap decodeStream(InputStream is)
从输入流中读取图片数据并转成 Bitmap对象 - public static Bitmap decodeByteArray(byte[] data, int offset, int length)
从字节数组中读取图片数据并转成 Bitmap对象 - public static Bitmap decodeResource(Resources res, int id)
从 Anroid的drawable资源目录中读取图片数据并转成 Bitmap对象 - public static Bitmap decodeFile(String pathName)
从图片文件中读取图片数据并转成 Bitmap对象
Bitmap bmp = Bitmap.createBitmap(400, 400, Config. ARGB_8888);
位于 res/drawable目录下的图片读成 Bitmap对象后是无法修改的,若要修改必须复制一张新的图片并设置可修改的标记。Bitmap类的 copy()方法能完成该功能,方法编写如下:
public Bitmap copy(Config config,boolean isMutable)
参数 isMutable为 true表示复制新位图可以修改 Bitmap是一种非常占用资源的对象,不管是什么手机,如果没有处理好很容易导致APP崩溃。所以,即使回收 Bitmap内存是一个好习惯。涉及到的方法有 两个:
- public final boolean isRecycled()
判断是否已回收,返回 true表示内存一被回收 - public void recycle()
回收 Bitmap内存,同一个Bitmap对象不能连续回收多次,所以,在回收之前最好先判断。不过,从源码中发现其实该方法已经自己判断过了
if(bmp != null && !bmp.isRecycled()){
bmp.recycle();
System.gc(); //提醒 JVM释放资源
bmp = null ;
}
绘图中 Bitmap是一个很重要的类,为了提高绘图的性能。通常会使用“双缓存”技术。“双缓存”技术就是先将图绘制在 Bitmap上,在统一显示出来;另外,在绘图软件额开发过程中,Bitmap常用于保存绘制结果。而用户看到的绘制过程是需要和结果分离的。 BitmapDrawable是 Android的一种通用位图格式,我们可以简单粗暴的理解成 Bitmap的另外一种表现形式。但是,和Bitmap相比 BitmapDrawable占用的资源更少、性能更高。 Bitmap 和 BitmapDrawable在一些情况下需要互相转换,BitmapDrawable的构造方法 publicBitmapDrawable(Resources res, Bitmap bitmap)用于将 Bitmap转成 BitmapDrawable,而 getBitmap()方法则用于将 BitmapDrawable转成 Bitmap Bitmap 和 BitmapDrawable都能获得位图的宽度和高,对比如下:
Bitmap | BitmapDrawable | |
---|---|---|
获取宽度 | int getWidth() | int getIntrinsicWidth() |
获取高度 | int getHeight() | int getIntrinsicHeight() |
2.5、Canvas类与 Paint类
2.5.1 绘图概述
画家在作画时,有两样东西必不可少;画笔和宣纸。画家用画笔蘸上颜料,轻重挥洒间衣服栩栩如生的画面跃然纸上。引得啧啧称赞。Android中的图形绘制类是于此。 Canvas翻译为“画布”,可以理解层画家作画时的宣纸。Canvas提供了若干方法用于绘制各种图案——点、线、圆 等等。Paint翻译为“画笔”,为绘图定义各种参数——颜色、线条样式、图案样式 等等。通常的绘图思路是先定义 Paint对象,指定绘图参数,再通过 Canvas 对象进行图形绘制,绘图的结果因 Paint额不同而不同。 掌握好 Graphics绘图是自定义组件的基础。Android给了我们一支笔(Paint)和一张纸(Canvas)。画出什么样的东西取决于我们的想象力以及对 Graphics技术的理解程度。不过,要说明的是,绘图方法定义在 Canvas类中,Paint类用于指定绘图参数。这个跟我们生活中的绘图有稍有不同,其实就是理解的方式不同而已。2.5.2 Paint类
我们首先来了解 Paint类的使用 Paint类用定义绘图时的参数,主要包含了 颜色、文本、图形样式、位图模式、滤镜等几个方面。通过控制这些参数,我们可以设计出简单使用的图片编辑器。 颜色是指绘图时使用的颜色,在 Anroid中颜色可以指定透明度,使用 16进制来表示颜色时,格式通常为 #AARRGGBB。其中,AA,表示透明度、RR,表示红色、GG,表示绿色、BB,表示蓝色;Color类定义了颜色信息,内置了常用的颜色的 int型常量。比如:Color.RED是红色,Color.BLUE是蓝色….,如果您习惯了 16进制的颜色,Color类的静态方法 parseColor(String colorString)可以将 16进制转成 Color类型。需要注意的是,Anroid的颜色都是 int类型的额,Color类值负责颜色的管理而不是代表某种颜色。 下面的代码将 16进制颜色转成 Android认可的颜色值:int color = Color.parseColor("#66FF0000") ;
Paint类中与颜色相关的方法有:
- public native void setColor(int color);
设置颜色 - public native void setAlpha(int a);
设置透明度,a 的范围取值 0~255 之间的整数 - public void setARGB(int a, int r, int g, int b);
指定透明度、红、绿、蓝定义一种颜色
- public native void setTextSize(float textSize)
设置文本的大小,单位 px,这个和我们平时使用的字体单位 sp不同,所以最好进行转换 - public void setTextAlign(Paint.Align align)
设置文本的对其方式,可选值有 static final Align[] sAlignArray = { Align.LEFT, Align.CENTER, Align.RIGHT }; - public native void setTextSkewX(float skewx)
设置文本的倾斜程度,skewx取值与 0~1 之间,正负表示倾斜的方向 - public native void setUnderlineText(boolean underline)
给文本添加下划线,underline 为 true表示添加 - public native void setFakeBoldText(boolean bold)
设置文本的村提样式,bold为 true表示粗体 - public native void setStrikeThruText(boolean strike)
strike为 true时为文本添加删除线
public enum Style {
FILL (0),
STROKE (1),
FILL_AND_STROKE (2);
}
其中,FILL,表示实心样式,对于闭合的图形来说,会用指定的颜色进行填充;STROKE ,表示空心样式,绘制时只有线条而无填充效果;FILL_AND_STROKE,表示同时使用实心样式和空心样式 public void setStrokeJoin(Paint.Join join) 当绘图样式为 STROKE时,该方法用于指定线条连接处的拐角样式,鞥呢绘制的图形更加平滑。可选值如下:
public enum Join {
MITER (0),
ROUND (1),
BEVEL (2);
}
我们通过下图来区别三个枚举值(比较右上角即可),默认为 MITER :
public void setStrokeCap(Paint.Cap cap)
该方法用于设置落笔时的样式,控制我们画笔在离开画板是留下的最后一点图形,可选值如下:
public enum Cap {
BUTT (0),
ROUND (1),
SQUARE (2);
}
下面图示很好的对三个枚举值惊醒区别,默认值为 BUTT :
public native void setStrokeWidth(float width)
设置线条的宽度,注意是float类型,在 Android中最细的线条不是 1,可以比 1 更小更细
通过以下代码可以对绘图产生不同的效果。
public class MainActivity extends Activity {
private ImageView iv ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iv = (ImageView) findViewById(R.id.iv) ;
Bitmap bitmap = Bitmap.createBitmap(400,800,Config.ARGB_8888) ;
Canvas canvas = new Canvas(bitmap) ;
//绘制文字
Paint paint = new Paint() ;
paint.setAntiAlias(true) ;
paint.setStyle(Style.FILL) ;
paint.setTextAlign(Align.LEFT) ;
int sp = 14 ;
paint.setTextSize(sp) ;
paint.setTextSkewX(0.5f);
paint.setUnderlineText(true) ;
paint.setFakeBoldText(true) ;
canvas.drawText("世事无对错,只有立场", 10, 100, paint);
//绘制图形
paint.setStyle(Style.STROKE) ;
paint.setColor(Color.RED) ;
paint.setStrokeWidth(20) ;
paint.setStrokeJoin(Join.MITER) ;
//绘制一个矩形
canvas.drawRect(new Rect(10, 200,350,350),paint) ;
paint.setStrokeJoin(Join.ROUND) ;
//绘制一个矩形
canvas.drawRect(new Rect(10, 400,350,550),paint) ;
paint.setStrokeJoin(Join.BEVEL) ;
//绘制一个矩形
canvas.drawRect(new Rect(10, 600,350,750),paint) ;
iv.setImageBitmap(bitmap) ;
}
}
对于上面,首先创建了一张空白位图,大小为 400*800,同时创建了一个与位图关联的 Canvas对象,通过 Canvas绘图。结果呈现在 Bitmap上。先绘制了一段“世事无对错,只有立场”的文字,与不同 setStrokeJoin()的三个矩形。
2.5.3 Canvas类
Canvas 类封装了大量的绘图方法,绘图的图形受到 Paint对象的影响。一般情况下,在绘制之前,要先创建 Paint对象,定义绘制的颜色、颜色。Paint对象是一个轻量级对象,在程序运行过程中可以创建多个,我们也可以从头到尾只创建一个,最终取决与绘图需求。不过,Paint类有一个 reset()方法。能重置 Paint参数。所以,除非有必要,我们并不推荐大量创建 Paint对象。而是调用 reset() 方法重置后重复使用。不然这对有限的手机资源来说是一种考验。Canvas类定义的绘图方法主要分成以下类型
- 位图
- 点
- 线
- 矩形
- 圆
- 路径
- 文字
2.5.3.1 绘制位图
之所以先学习位图的绘制,主要是考虑到内容编排,我们会先将图形绘制在空白位图上,再将位图画到 ImageView组件中。Canvas中定义了绘制位图的方法有:
public void drawBitmap(Bitmap bitmap, float left, float top, Paint paint)
该方法将Bitmap绘制在画布上,同时指定位图左上角相对于画布的坐标。大小与原位置相同,不能进行任何缩放。
绘制位图时,除非需要进行位图运算,否则,并不需要指定 paint对象。直接传递 null即可
public void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint)
public void drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint)
这两个方法从 bitmap中抠出一块大小区域为 src的图片并绘制到 Canvas的 dst处。src 和 dst 的大小与比例关系影响到最终的绘制效果
我们通过一个案例来说明 drawBitmap()方法来使用,该方案在 ImageView上绘制了两个位图。一个安原始大小绘制,另一个则放大三倍
public class MainActivity extends Activity {
private ImageView iv ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iv = (ImageView) findViewById(R.id.iv) ;
Bitmap bitmap = Bitmap.createBitmap(500,800,Config.ARGB_8888) ;
Canvas canvas = new Canvas(bitmap) ;
Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
//原大小绘制
canvas.drawBitmap(bmp, 0, 0,null) ;
//对图片进行缩放
int bmpWidth = bmp.getWidth() ;
int bmpHeight = bmp.getHeight();
Rect src = new Rect(0, 0, bmpWidth, bmpHeight);
Rect dst = new Rect(0, bmpHeight, bmpWidth * 3, bmpHeight * 3 + bmpHeight);
canvas.drawBitmap(bmp, src, dst, null);
iv.setImageBitmap(bitmap);
}
}
2.5.3.2 绘制点
点的大小取决于 setStrokeWidth()方法的参数,参数值越大。点也就越大。所以,不要以为一个点就是屏幕上的一个像素。如果将 stoke的宽度设置的足够大,我们发现最终绘制出来的点其实是一个正方形绘制点的方法一共有三个:
- public void drawPoint(float x, float y, Paint paint)
该方法在(x,y)处绘制一个点 - public void drawPoints(float[] pts, Paint paint)
该方法的参数 pts是一个数组,从下标 0开始每 2个数确定一个点,连续绘制多个点。多余的元素忽略 - public void drawPoints(float[] pts, int offset, int count, Paint paint)
从 pts数组中的第 offset出开始取出 count个数字,以 2个数为一组确定一个点,连续绘制若干个点。忽略多余的元素
下面的代码演示了这三个方法的使用
public class MainActivity extends Activity {
private ImageView iv ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iv = (ImageView) findViewById(R.id.iv) ;
Bitmap bitmap = Bitmap.createBitmap(500,800,Config.ARGB_8888) ;
Canvas canvas = new Canvas(bitmap) ;
Paint paint = new Paint() ;
paint.setColor(Color.RED);
paint.setStrokeWidth(14);
//画一个红色的点
canvas.drawPoint(120, 20, paint);
paint.setColor(Color.BLUE);
//2个数一组画 4个点
float[] points = new float[]{10,10,50,50,50,100,50,150} ;
canvas.drawPoints(points, paint);
paint.setColor(Color.GREEN);
float[] points2 = new float[]{10,200,50,200,150,200,200,200} ;
canvas.drawPoints(points2, 1, 4, paint);
iv.setImageBitmap(bitmap);
}
}
从 GREEN(绿色点)可以看出 画出两个点的坐标为(200,50)和(200,150);
2.5.3.3 绘制线
两个点确定一条直线,所以,绘制线条时2,需要指定两个点的坐标。同画点一样,绘制线条也有 3个重载的方法:- public void drawLine(float startX, float startY, float stopX, float stopY, Paint paint)
在(startX,startY)和 (stopX,stopY)两点之间绘制一条直线 - public void drawLines(float[] pts, Paint paint)
pts 数组每 4个数绘制一天直线,多余的元素会忽略 - public void drawLines(float[] pts, int offset, int count, Paint paint)
从 pts数组中的 offset索引出开始,取出 count个元素,并以 4个数一组绘制直线,忽略多余的元素
下面代码绘制 5条红色的水平线,水平线之间相隔 50个单位距离,此时中线条的宽度设置为 1
public class MainActivity extends Activity {
private ImageView iv ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iv = (ImageView) findViewById(R.id.iv) ;
Bitmap bitmap = Bitmap.createBitmap(500,800,Config.ARGB_8888) ;
Canvas canvas = new Canvas(bitmap) ;
Paint paint = new Paint() ;
paint.setColor(Color.RED);
paint.setStrokeWidth(1);
final int offsetDy = 50;
for(int i = 0; i < 5; i ++){
canvas.drawLine(10, offsetDy * i, 300, offsetDy * i, paint);
}
iv.setImageBitmap(bitmap);
}
}
2.5.3.4 绘制矩形
矩形包含直角矩形和圆角矩形,正方形也是矩形的一种。所以,Canvas并没有提供绘制正方形的方法。绘制矩形时,参数分为 2种:一种是指定 left、top、right、bottom等 4个参数。另一种直接指定一个 Rect对象或 rectF对象绘制直角矩形的三个重载方法如下:
- public void drawRect(float left, float top, float right, float bottom, Paint paint)
- public void drawRect(Rect r, Paint paint)
- public void drawRect(RectF r, Paint paint)
方法中参数参考章节 2.3,本质这三个绘制直角矩形的方法是完全一样的,只是提供了调用的多种形态。使用哪一个完全由自己决定
圆角矩形的几何形状比直角矩形相对复杂一些,我们需要指定 4个拐角的弧度。 4个角的弧度不能单独的设置,而是统一设置为相同的值。拐角弧度实际上是圆或椭圆的一段弧线。如图 2-24所示:
绘制圆角矩形一共有 2哥重载的方法:
- public void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry,Paint paint)
该方法用于绘制一个圆角矩形,left、top、right、bottom 构建一个矩形,rx,ry 分别是圆角处的水平半径和垂直半径。 rx 和 ry 不一定相同,则是椭圆上的一段弧线 - public void drawRoundRect(RectF rect, float rx, float ry, Paint paint)
该方法是上面方法的另一种重载形式
下面代码绘制两个圆角矩形,一个为空心圆角矩形,另一个是有填充色的实心圆角矩形
public class MainActivity extends Activity {
private ImageView iv ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iv = (ImageView) findViewById(R.id.iv) ;
Bitmap bitmap = Bitmap.createBitmap(500,800,Config.ARGB_8888) ;
Canvas canvas = new Canvas(bitmap) ;
Paint paint = new Paint() ;
paint.setAntiAlias(true);
paint.setColor(Color.RED);
canvas.drawRoundRect(new RectF( 10,10, 400, 300),80,20,paint) ;
paint.setStyle(Paint.Style.STROKE);
canvas.drawRoundRect(new RectF(10, 320, 400, 620), 20, 80, paint);
iv.setImageBitmap(bitmap);
}
}
查看运行效果时,注意个圆角矩形 4个角的弧度的方向,第一个的水平半径为 80,垂直半径为 20.另一个的水平半径为 20,垂直半径为 80
2.5.3.5 绘制圆
在对图形进行分类时,将圆、椭圆、扇形、弧线统一归类到“圆”这一类中,扇形和弧形可以认为是圆或椭圆的一部分椭圆的大小是由他的外切矩形来决定,之二实际上和几何学中是的定义完全一致,如图 2-16所示:
绘制椭圆的方法如下:
- public void drawOval(float left, float top, float right, float bottom, Paint paint)
- public void drawOval(RectF oval, Paint paint)
上面了两个方法的参数定义了一个矩形结构,绘制的结构是该矩形的内切椭圆
绘制弧线和扇形的方法如下:
- public void drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paintpaint)
- public void drawArc(float left, float top, float right, float bottom, float startAngle, floatsweepAngle, boolean useCenter, Paint paint)
- 以上两个方法中,参数 startAngle,表示起始角度 ;sweepAngle,表示扇形或弧线所占的角度。正数,表示顺时针;负数,表示逆时针。参数 useCenter,询问是否使用中心点,为 true,表示扇形,为 false,表示弧线
下面的代码演示了弧线和扇形的绘制方法,采用了 Style。STROKE的图形模式。如果将 Style设置为 Style.FULL。不管是弧线还是扇形,偶可以使用颜色进行填充。
public class MainActivity extends Activity {
private ImageView iv ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iv = (ImageView) findViewById(R.id.iv) ;
Bitmap bitmap = Bitmap.createBitmap(500,950,Config.ARGB_8888) ;
Canvas canvas = new Canvas(bitmap) ;
Paint paint = new Paint() ;
paint.setAntiAlias(true);
paint.setStyle(Style.STROKE) ;
RectF rect = new RectF(10,10,400,200) ;
paint.setColor(Color.GRAY);
canvas.drawRect(rect, paint);
canvas.drawOval(rect, paint) ;
rect.set(10, 260, 400, 550);
canvas.drawRect(rect, paint);
canvas.drawOval(rect, paint);
paint.setColor(Color.RED) ;
paint.setStrokeWidth(3) ;
canvas.drawArc(rect, -30, -30, false, paint) ;
paint.reset() ;
paint.setAntiAlias(true);
paint.setStyle(Style.STROKE) ;
rect.set(10, 610, 400, 900);
canvas.drawRect(rect, paint);
canvas.drawOval(rect, paint);
paint.setColor(Color.RED) ;
paint.setStrokeWidth(3) ;
canvas.drawArc(rect, -30, -30, true, paint) ;
iv.setImageBitmap(bitmap);
}
}
运行效果如下所示。将语句 canvas.drawArc(rect,-30,-30,false,paint)中的第 4个参数变成 true。则表示会利用椭圆的中心绘制一个闭合的扇形
2.5.3.6 绘制路径
Path是 Graphics2D 中一个非常重要的概念。表示“路径”,理解该概念时保持“路径”的本色就好。路径可以是直的、也可以是弯的;可以是闭合的、也可以是非闭合的;可以是圆的、也可以是方形的;可以是单个的、也可以是多个的;可以是简单的、也可以是复杂的….总的来说,路径是基于普通图形但是功能比普通图形更强的一种复杂图形Path是一个类,用于绘制复杂图形,创建之初什么也没有,只有往 Path中添加了具体的形状。Path才会清晰可见。绘制 Path时,所有信息都存储在 path对象中,Canvas根据 Path对象来绘制相应的图形
我们将 Path的功能归纳成一下几类
- 往 Path中添加线条
- 往 Path中添加矩形、椭圆、弧
- 往 Path中添加曲线和贝赛尔曲线
- 将 Path中的图形进行运算
通过 path可以绘制出奇形怪状的线条并能将线条组合在一起变成折线,闭合后就是一个多边形了。这就是 Path的厉害之处。为此,Path类中定义了 5个方法
- public void moveTo(float x, float y)
将画笔移动到点(x,y)的位置,使用的是绝对定位 - public void rMoveTo(float dx, float dy)
讲画笔移动到一个新点,新点在上一个点的基础上偏移(dx,dy),也就是说:“新点的坐标为(x+dx,y+dy)”。这里使用的是相对定位。首字母“r”就是“relative(相对)”的意思 - public void lineTo(float x, float y)
将画笔移动到点(x,y)的位置,并在上一个点与当前点之前画一条直线。使用的是绝对定位 - public void rLineTo(float dx, float dy)
将画笔移动到一个新点,新点在上一个点的基础上偏移(dx,dy),新点的坐标为(x+dx,y+dy),同时,在新点与上一个点之间回一条直线。这里使用的是相对定位 - public void close()
在第一个点和最后一个点之前画一条直线。形成闭合区域
下面使用 Path绘制一个五角星,这不是一个完美的五角星集合图形。因为 五个点的坐标并没有正确的计算出来。只是双流一个大概。首先调用 moveTo(0,150)定义好这次绘图的起点,接下来调用 rLineTo() 方法通过相对定位计算下一个点的坐标,并使用直线连接。最后,调用 close() 方法连接最后一个点和第一个点以形成闭合区域
public class MainActivity extends Activity {
private ImageView iv ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iv = (ImageView) findViewById(R.id.iv) ;
Bitmap bitmap = Bitmap.createBitmap(500,950,Config.ARGB_8888) ;
Canvas canvas = new Canvas(bitmap) ;
Paint paint = new Paint() ;
paint.setAntiAlias(true);
paint.setColor(Color.RED);
paint.setStrokeWidth(4) ;
paint.setStyle(Style.STROKE) ;
Path path = new Path() ;
path.moveTo(0, 150);
path.rLineTo(300, 0);
path.rLineTo(-300, 150);
path.rLineTo(150, -300);
path.rLineTo(150, 300);
path.close() ;
canvas.drawPath(path, paint) ;
path.reset() ;
//同时在 Y轴加上350,表示里上面 50像素
path.moveTo(0, 500);
path.lineTo(300, 500) ;
path.lineTo(0, 650);
path.lineTo(150, 350);
path.lineTo(300, 650);
path.close();
canvas.drawPath(path, paint);
iv.setImageBitmap(bitmap);
}
}
如果要往 Path对象中添加矩形、椭圆、圆和弧,需要调用 Pat类中定义的一组以“add”开头的方法,这组方法有些需要传递一个类型为 Path.Direction的参数。这是一个枚举类型,枚举值 CW,表示顺时针,枚举 CCW,表示逆时针。下一节内容沿着图形绘制文职时,我们帮可以清晰的感受到方向对绘图带来的影响
- public void addRect(RectF rect, Path.Direction dir)
- public void addRect(float left, float top, float right, float bottom, Path.Direction dir)
- 往 Path对象中添加一个矩形
- public void addRoundRect(RectF rect, float[] radii, Path.Direction dir)
- public void addRoundRect(RectF rect, float rx, float ry, Path.Direction dir)
- public void addRoundRect(float left, float top, float right, float bottom, float[] radii,Path.Direction dir)
- 往 Path对象中添加一个圆角矩形。该方法和前面绘制圆角矩形相比,在定义 4个角的弧线大小时功能更强。能对 4个角分别定义不同的弧线弧度
- public void addOval(RectF oval, Path.Direction dir)
- public void addOval(float left, float top, float right, float bottom, Path.Direction dir)
- 往 Path对象中调价一个椭圆
- public void addCircle(float x, float y, float radius, Path.Direction dir)
- 往 Path对象中添加一个圆
- public void addArc(RectF oval, float startAngle, float sweepAngle)
- public void addArc(float left, float top, float right, float bottom, float startAngle, floatsweepAngle)
- 往 Path对象中添加一段弧。本方法并没有指定方法,因为角度的正负已经代表了方向,正数时,为顺时针;负数时,为逆时针
我们在下面的代码中绘制一个 Path对象,对象中同时包含了 矩形、圆角矩形、椭圆、圆、弧线等图形。显然,Path对象绘制出来的图形更加复杂
public class MainActivity extends Activity {
private ImageView iv ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iv = (ImageView) findViewById(R.id.iv) ;
Bitmap bitmap = Bitmap.createBitmap(500,950,Config.ARGB_8888) ;
Canvas canvas = new Canvas(bitmap) ;
Paint paint = new Paint() ;
paint.setAntiAlias(true);
paint.setColor(Color.RED);
paint.setStrokeWidth(4) ;
paint.setStyle(Style.STROKE) ;
Path path = new Path() ;
//矩形
path.addRect(new RectF(10, 10, 300, 100), Direction.CCW);
//圆角矩形,4个角的弧度都不一样, 2个数确定一个弧度
path.addRoundRect(new RectF(10, 120, 300, 220),
new float[]{10,20,20,10,30,40,40,30},Direction.CCW );
//椭圆
path.addOval(new RectF(10, 240, 300, 340), Direction.CCW) ;
//圆
path.addCircle(60, 390, 50, Direction.CCW) ;
//弧线
path.addArc(new RectF(10, 500, 300, 600), -30, -60);
canvas.drawPath(path, paint);
iv.setImageBitmap(bitmap);
}
}
运行结果如下,注意圆角矩形的 4个角的弧度不是一样的:
曲线包括弧线和贝赛尔曲线。与前面将的矩形、圆或弧线不同。绘制曲线时需要确定一个起点,绘制的曲线会与该起点进行连接,形成一个更加复杂的图形
贝赛尔曲线(B..zier curve)是图形开发中的一个重要工具。通过三个点,确定一条平滑的曲线,又称贝兹曲线或贝济埃曲线。是应用于二维图应用程序的数学曲线。一般的矢量图形软件通过它来精确画出曲线,贝兹曲线由线段与节点组成。节点是可拖动的支点,线段像可伸缩的皮筋。我们在绘图工具上看到钢笔工具就是来做这种矢量曲线。贝塞尔曲线时计算机图形学中相当重要的参数曲线,在一些比较成熟的位图软件中也有贝赛尔曲线。
贝塞尔曲线又分为一节贝塞尔曲线、二阶贝赛尔曲线‘三节贝塞尔曲线。一节贝塞尔曲线就是一条线段,Path类支持二阶贝赛尔曲线。’访问网址 :http://www.cnblogs.com/jay-dong/archive/2012/09/26/2704188.html 可以欣赏到贝赛尔曲线效果动画。
前面提到,贝塞尔曲线通过 3个点来绘制一条平滑的曲线。这 3个点分别是起点、控制点 和终点。比如:如果绘制一条二阶贝塞尔曲线,必须调用 moveTo()方法定义起,再调用 public
void quadTo(float x1, float y1, float x2, float y2)方法绘制贝赛尔曲线,其中(x1,y1)是控制点,(x2,y2)是终点。我们通过一段代码演示如何绘制贝塞尔曲线
public class MainActivity extends Activity {
private ImageView iv ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iv = (ImageView) findViewById(R.id.iv) ;
Bitmap bitmap = Bitmap.createBitmap(500,950,Config.ARGB_8888) ;
Canvas canvas = new Canvas(bitmap) ;
Paint paint = new Paint() ;
paint.setStyle(Style.STROKE) ;
Path path = new Path() ;
path.moveTo(100, 100) ;
path.quadTo(200, 10, 300, 300);
canvas.drawPath(path, paint) ;
//画点(100,100),(200,10),(300,300)
paint.setColor(Color.RED);
paint.setStrokeWidth(8);
canvas.drawPoints(new float[]{100,100,200,10,300,300}, paint) ;
iv.setImageBitmap(bitmap);
}
}
运行结果如下,我们看到屏幕撒胡工绘制出了一条曲线。为了加深印象。把起点、控制点 和终点也一起标注
三阶贝塞尔曲线有 1个起点,2个控制点,1和终点,Path类中通过 public void cubicTo(float
x1, float y1, float x2, float y2, float x3, float y3)进行绘制,其中(x1,y1)、(x2,y2)是控制点,(x3,y3)是终点
quadTo() 和 cubicTo() 的控制点和终点利用决定定位来进行确定,其实还有另外两个方法。通过相对定位对各点进行 定义
- public void rQuadTo(float dx1, float dy1, float dx2, float dy2)
- public void rCubicTo(float x1, float y1, float x2, float y2, float x3, float y3)
arcTo()方法可以和 moveTo()配合使用,通过 moveTo()确定一个起点。再通过 arcTo()绘制弧线。弧线是基于矩形的内切圆上的一段,该弧线的起始点会和 moveTo()方法定义的点进行连接
- public void arcTo(RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo)
- public void arcTo(RectF oval, float startAngle, float sweepAngle)
- public void arcTo(float left, float top, float right, float bottom, float startAngle, floatsweepAngle, boolean forceMoveTo)
上面三个方法的参数在前面都有说明,参数 forceMoveTo,为 true时,表示开始一个新的图形,不和上一个点进行连接。为false时,才会和上一点进行连接
我们通过一小段代码来演示 arcTo()方法的使用技巧:
public class MainActivity extends Activity {
private ImageView iv ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iv = (ImageView) findViewById(R.id.iv) ;
Bitmap bitmap = Bitmap.createBitmap(500,950,Config.ARGB_8888) ;
Canvas canvas = new Canvas(bitmap) ;
Paint paint = new Paint() ;
paint.setStyle(Style.STROKE) ;
Path path = new Path() ;
path.moveTo(100, 100) ;
RectF rectF = new RectF(100, 150, 300, 300) ;
path.arcTo(rectF, -30, 60,false);
canvas.drawRect(rectF, paint) ;
paint.setColor(Color.RED) ;
paint.setStrokeWidth(8) ;
canvas.drawPoint(100, 100, paint) ;
paint.setColor(Color.BLUE) ;
paint.setStrokeWidth(4);
canvas.drawPath(path, paint) ;
iv.setImageBitmap(bitmap);
}
}
我们还可以将多个 Path进行图形运算,得到更加复杂和不规则的图形。Path有一个静态内部类 Op,定义了 5种运算规则:
- Path.Op. DIFFERENCE
差集,图形 A 减去与图形 B 重写的区域后 A 余下的区域 - Path.Op. INTERSECT
交集,图形 A 和图形 B 的重叠区域 - Path.Op. UNION
并集,包含了图形 A 和图形 B 的所有区域 - Path.Op.XOR
补集,即图形 A 和 图形 B 的所有区域减去他们重叠区域后余下的区域 - Path.Op. REVERSE_DIFFERENCE
反差集,图形 B 减去与图形 A 重叠的区域后 B 余下的区域
2.5.3.7 绘制文字
绘制图形也包括文字的绘制,Canvas为我们提供了两组方法,一组是直接从指定的位置开始绘制文字;另一种是沿着 Path绘制文字:- public void drawText(char[] text, int index, int count, float x, float y, Paint paint)
- public void drawText(String text, float x, float y, Paint paint)
- public void drawText(String text, int start, int end, float x, float y, Paint paint)
- public void drawText(CharSequence text, int start, int end, float x, float y, Paint paint)
- 上面的一组方法是从指定的位置(坐标)开始绘制文字,虽然都是字符串。但是,提供了三种形式:char[]、String 和 CharSequence,本质上并没有什么不同。参数 index 和 count;start 和 end 可以从字符串中取出子串。而参数 x,y 就是文字绘制的坐标位置,其中 y 是文字 baseline的值
- public void drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint)
- public void drawTextOnPath(char[] text, int index, int count, Path path, float hOffset, floatvOffset, Paint paint)
- 这两个重载的 drawTextOnPath()方法沿用了 Path定义好的路径绘制文字,这是一个很有趣的功能。文字在 path的带领下龙飞凤舞,灵活多变。参数 和Offset 和 vOffsert 用于定义文字离 Path的水平偏移量和垂直偏移量。正数和负数影响文字与路径的相对位置。同样,也支持绘制从字符数组中截取的子串。index ,表示起始的索引;count,表示要截取的长度
下面的代码中绘制 4个字符串,一个绘制所有的字符串,中间两个截取子串进行绘制,最后一个沿着 Path绘制所有的文字。为了理解,把对应的路径也绘制出
public class MainActivity extends Activity {
private ImageView iv ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iv = (ImageView) findViewById(R.id.iv) ;
Bitmap bitmap = Bitmap.createBitmap(500,950,Config.ARGB_8888) ;
Canvas canvas = new Canvas(bitmap) ;
Paint paint = new Paint() ;
paint.setAntiAlias(true) ;
paint.setStyle(Style.FILL) ;
String text = "文本的绘制,文本的绘制!文本的绘制!!" ;
//绘制 全部文字
canvas.drawText(text, 10, 50, paint) ;
//截取 从 0开始,截取 4个
canvas.drawText(text, 0, 4, 10, 100, paint) ;
//截取第 5-10 的文字
canvas.drawText(text.toCharArray(), 5, 10, 10, 150, paint) ;
//延 Path绘制
Path path = new Path() ;
path.moveTo(10, 200) ;
path.quadTo(100, 100, 300, 300) ;
canvas.drawTextOnPath(text, path, 15, 15, paint) ;
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.STROKE);
canvas.drawPath(path, paint);
iv.setImageBitmap(bitmap);
}
}
运行如下: