Android系统上绘图功能的实现
像BufferedImage,Graphics2D以及ImageIO等这些类,在Android SDK中都是没有的,但可以使用android.graphics的一些子类,如canvas,paint等来实现这些绘图功能。按照惯例,先睹demo效果吧:
下面来描述实现过程。
库文件主要包括两个类文本:FontProperty和PrintGraphics。其中FontProperty用于定义字体的各个属性,如粗体、斜体、下划线等等;PrintGraphics主要定义各种图形的绘制功能。
先看FontProperty.java:
import android.content.Context;
import android.graphics.Typeface;
public class FontProperty{
boolean bBold;
boolean bItalic;
boolean bUnderLine;
boolean bStrikeout;
int iSize;
Typeface sFace;
/**
* 设置字体各个属性函数,参数:<br/>
* boolean bBold - 是否粗体,取值为true/false <br/>
* boolean bItalic - 是否斜体,取值为true/false <br/>
* boolean bUnderLine - 是否下划线,取值为true/false <br/>
* boolean bStrikeout - 是否删除线,取值为true/false <br/>
* int iSize - 字体大小,取值为一整数 <br/>
* Typeface sFace - 字体类型,一般设置为null,表示使用系统默认字体 <br/><br/>
*
* 示例: <br/>
* FontProperty fp = new FontProperty(); <br/>
* fp.setFont(false, true, false, false, 18, null); <br/>
*
*/
public void setFont( boolean bBold,
boolean bItalic,
boolean bUnderLine,
boolean bStrikeout,
int iSize,
Typeface sFace){
this.bBold = bBold;
this.bItalic = bItalic;
this.bUnderLine = bUnderLine;
this.bStrikeout = bStrikeout;
this.iSize = iSize;
this.sFace = sFace;
}
/**
* 通过指定ttf字体库路径初始化sFace字体类型 <br/>
* 第一个参数:是一个context对象 <br/>
* 第二个参数:ttf字体库文件的绝对路径 <br/><br/>
*
* 示例:<br/>
* FontProperty fp = new FontProperty(); <br/>
* fp.setFont(false, false, false, false, 12, null); <br/>
* fp.initTypeface(getContext(),"/sdcard/a.ttf"); <br/>
*
*/
public void initTypeface(Context mContext,String path){
this.sFace = Typeface.createFromAsset (mContext.getAssets(), path);
}
/**
* 通过指定face name初始化sFace字体类型 <br/>
* 第一个参数如:"DroidSerif-Bold" <br/>
* 第二个参数取值如下: <br/>
* Typeface.NORMAL 普通 <br/>
* Typeface.BOLD 粗体 <br/>
* Typeface.ITALIC 斜体 <br/>
* Typeface.BOLD_ITALIC 粗体加斜体 <br/><br/>
*
* 示例: <br/>
* FontProperty fp = new FontProperty(); <br/>
* fp.setFont(false, false, false, false, 12, null); <br/>
* fp.initTypefaceToString("DroidSerif-Bold", Typeface.BOLD); <br/>
*/
public void initTypefaceToString(String familyName, int style){
this.sFace = Typeface.create(familyName, style);
}
}
除了设置字体属性的方法之外,还提供了两个使用指定字体的函数。
接着看PrintGraphics.java文件:
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
/**
* PrintGraphics类定义
*/
public class PrintGraphics{
//画布
public Canvas canvas = null;
//画笔
public Paint paint = null;
//位图
public Bitmap bm = null;
//画布宽度
public int width;
//实际使用的画布长度
public float length = 0;
/**
* 获取实际使用的画布长度,并在此基础上增加20px作底边使用
*/
public int getLength(){
return (int)this.length+20;
}
/**
* 初始化画布,参数为画布的宽度,高度预置为宽度的10倍。<br/><br/>
* 画布默认定义为白色背景 <br/>
*/
public void initCanvas(int w){
int h = 10*w;
//this.bm = Bitmap.createBitmap(w, h, Config.ARGB_8888);
//this.bm = Bitmap.createBitmap(w, h, Config.ALPHA_8);
this.bm = Bitmap.createBitmap(w, h, Config.ARGB_4444);
this.canvas = new Canvas(this.bm);
//设置画布颜色为白色
this.canvas.drawColor(Color.WHITE);
this.width = w;
}
/**
* 初始化画笔,默认画笔的属性为: <br/>
* 1、消除锯齿 <br/>
* 2、设置画笔颜色为黑色 <br/>
* 3、设置图形为空心模式 <br/>
*
*/
public void initPaint(){
this.paint = new Paint();
//消除锯齿
this.paint.setAntiAlias(true);
//设置画笔颜色为黑色
this.paint.setColor(Color.BLACK);
//设置图形为空心模式
this.paint.setStyle(Paint.Style.STROKE);
}
/**
* 根据指定的字体属性初始值设置画笔,参数为FontProperty类型
*/
public void setFontProperty(FontProperty fp) {
//字体face不为null时,设置使用指定的face;如果给定的face不存在,则使用系统默认
if(fp.sFace != null){
try {
this.paint.setTypeface(fp.sFace);
} catch (Exception e) {
this.paint.setTypeface(Typeface.DEFAULT);
}
}
//字体face指定为null时,使用系统默认(推荐将该参数指定为null)
else{
this.paint.setTypeface(Typeface.DEFAULT);
}
//设置是否粗体
if (fp.bBold) {
this.paint.setFakeBoldText(true);
}
else{
this.paint.setFakeBoldText(false);
}
//设置是否斜体
if (fp.bItalic) {
this.paint.setTextSkewX(-0.5f);
}
else{
this.paint.setTextSkewX(0);
}
//设置是否下划线
if (fp.bUnderLine) {
this.paint.setUnderlineText(true);
}
else{
this.paint.setUnderlineText(false);
}
//设置是否删除线(中划线)
if (fp.bStrikeout) {
this.paint.setStrikeThruText(true);
}
else{
this.paint.setStrikeThruText(false);
}
//设置字体大小
this.paint.setTextSize(fp.iSize);
}
/**
* 设置线条宽度
*/
public void setLineWidth(float w){
this.paint.setStrokeWidth(w);
}
/**
* 绘文本字符串函数,其中(x,y)是指插入文本的左下坐标,所以y不能等于0
*/
public void drawText(float x, float y, String nStr){
this.canvas.drawText(nStr, x, y, this.paint);
//计算实际使用的画布长度,下同
if(this.length < y){
this.length = y;
}
}
/**
* 绘直线函数,其中(x1,y1)表示起点坐标,(x2,y2)表示终点坐标
*/
public void drawLine(float x1, float y1, float x2, float y2){
this.canvas.drawLine(x1, y1, x2, y2, this.paint);
float max = 0;
max = y1>y2?y1:y2;
if(this.length < max){
this.length = max;
}
}
/**
* 绘矩形或方形函数,其中(x1,y1)是矩形或方型的左上点坐标,(x2,y2)是矩形或方型的右下点坐标
*/
public void drawRectangle(float x1, float y1, float x2, float y2){
this.canvas.drawRect(x1, y1, x2, y2, this.paint);
float max = 0;
max = y1>y2?y1:y2;
if(this.length < max){
this.length = max;
}
}
/**
* 绘椭圆或正圆函数,其中(x1,y1)是圆外切矩形或方型的左上点坐标,(x2,y2)是圆外切矩形或方型的右下点坐标
*/
public void drawEllips(float x1, float y1, float x2, float y2){
RectF re=new RectF(x1, y1, x2, y2);
this.canvas.drawOval(re, this.paint);
float max = 0;
max = y1>y2?y1:y2;
if(this.length < max){
this.length = max;
}
}
/**
* 插入指定路径的图片函数,其中(x,y)是指插入图片的左上顶点坐标
*/
public void drawImage(float x, float y, String path){
try{
Bitmap btm = BitmapFactory.decodeFile(path);
this.canvas.drawBitmap(btm, x, y, null);
if(this.length < y+btm.getHeight()){
this.length = y+btm.getHeight();
}
}catch (Exception e) {
e.printStackTrace();
}
}
/**
* 将画布上的图片输出成png图片,路径是/sdcard/0.png
*/
public void printPng(){
File f = new File("/sdcard/0.png");
FileOutputStream fos = null;
//根据图像实际长度截取画布
Bitmap nbm = Bitmap.createBitmap(this.bm, 0, 0, this.width, this.getLength());
try {
fos = new FileOutputStream(f);
//第二个参数为压缩率,当格式为PNG时该参数被忽略,其它格式如JPG时会有效,取值范围是0~100,值越大图像越大。
nbm.compress(Bitmap.CompressFormat.PNG, 50, fos);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
程序里面都有详细的注释信息,所以不难看懂的。
最后写一个测试demo测试程序了,因为Android部署还涉及到其它文件,所以这里仅给出测试函数,用户可以自己放到一个Activity里面调用测试即可:
public void myPaint(){
//获取画图实例
PrintGraphics pg = new PrintGraphics();
//初始化画布,宽度为576px,则长度为5760px
pg.initCanvas(576);
//初始化画笔
pg.initPaint();
//插入一张图片,根据自己的图片位置进行更改即可
pg.drawImage(30, 5, "/sdcard/logo.gif");
//获取字体实例
FontProperty fp = new FontProperty();
//初始化字体,黑体,24号
fp.setFont(true, false, false, false, 24, null);
pg.setFontProperty(fp);
//绘制文本
pg.drawText(215, 45, "北京威控睿博科技有限公司");
//绘制直线
pg.setLineWidth(5.0f);
pg.drawLine(0, 70, 576, 70);
//绘制各种格式的文本
//注意:绘制文本时一定要将线宽恢复到0,特别是开启了下划线和删除线属性
pg.setLineWidth(0);
fp.setFont(false, false, false, false, 18, null);
pg.setFontProperty(fp);
pg.drawText(120, 95, "这是一行普通文体-Android绘图测试");
fp.setFont(true, false, false, false, 18, null);
pg.setFontProperty(fp);
pg.drawText(120, 120, "这是一行粗体文体-Android绘图测试");
fp.setFont(false, true, false, false, 18, null);
pg.setFontProperty(fp);
pg.drawText(120, 145, "这是一行斜体文体-Android绘图测试");
fp.setFont(false, false, true, false, 18, null);
pg.setFontProperty(fp);
pg.drawText(120, 170, "这是一行下划线文体-Android绘图测试");
fp.setFont(false, false, false, true, 18, null);
pg.setFontProperty(fp);
pg.drawText(120, 195, "这是一行删除线文体-Android绘图测试");
fp.setFont(true, true, true, true, 18, null);
pg.setFontProperty(fp);
pg.drawText(120, 220, "这是一行混合格式文体-Android绘图测试");
pg.setLineWidth(2.0f);
pg.drawLine(0, 230, 576, 230);
//绘制矩形
pg.drawRectangle(10, 250, 370, 430);
//绘制方形
pg.setLineWidth(4.0f);
pg.drawRectangle(380, 250, 560, 430);
//绘制椭圆
pg.setLineWidth(3.0f);
pg.drawEllips(10, 450, 370, 630);
//绘制正圆
pg.setLineWidth(6.0f);
pg.drawEllips(380, 450, 560, 630);
//绘制上述图形的说明文本
pg.setLineWidth(0);
fp.setFont(false, false, false, false, 15, null);
pg.setFontProperty(fp);
pg.drawText(135, 340, "线宽2.0的矩形");
pg.drawText(420, 340, "线宽4.0的方形");
pg.drawText(135, 540, "线宽3.0的椭圆");
pg.drawText(420, 540, "线宽6.0的正圆");
pg.setLineWidth(4.0f);
pg.drawLine(0, 650, 576, 650);
//输出图片到/sdcard/0.png
pg.printPng();
}
Demo文件的注释也很详细的,都是调用前面两个库文件定义的功能函数,最后会将图像通过窗口显示出来,同时会保存到/sdcard/0.png。显示效果见文档开始。
部署程序需要注意的地方:
0. 开启/sdcard写权限,将下面语句添加到Androidmanifest.xml里面的<manifest>标签中即可:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
1. 绘图中会插入一张图片/sdcard/logo.gif,在手机上当然可以很容易的拷贝到/sdcard目录了,但要通过模拟器测试则同样需要将图片上传到/sdcard:
$ adb push /path/to/your/logo.gif /sdcard/
注意:要保证你的模拟器是开启的,且创建模拟器时已经创建了虚拟sdcard,虚拟sdcard的真实位置是:/home/zwang/.android/avd/android2.3.avd/sdcard.img
2.
因为是调用库函数进行绘图的,所以无法通过view直接展示到屏幕上(或者是我还没找到方法吧,原因如下:如果直接将图绘制到canvas画布上当然是可
以展示出来的,但这样的话就无法对图像进行操控了,所以一般都是先创建一个bitmap缓存,然后被作为参数初始化canvas,这样图像最后都画到
bitmap了,然后就可以对图像进行转换或是保存之类的操作。而通过view展示时,被bitmap初始化的canvas没法作为参数使用,知道解决方
法的请通知我:zwang@ucrobotics.com)
但保存到/sdcard/0.png之后浏览起来也是非常方便的,每次运行完后执行:
$ adb push /sdcard/0.png /tmp/
就会将生成到虚拟sdcard里面图像下载到本地的/tmp目录下了。
3. 每次改动保存之后,Eclipse会自动编译更新apk文件的,所以无需再执行一遍“Run As Android Application”。
4. Android开发过程中,可以使用logcat进行调试。
当然了,熟悉eclipse debug使用方法的可能不需要了,对于习惯将信息通过System.out.println()输出进行调试的,就可以通过"$ adb logcat"查看你的输出了。
5. Eclipse导出jar文件:选中需要导出的java文件(ctrl可多选),右键 Export -> Java -> Jar
File,然后Next,输入路径及名称,然后Finish即可(当然jar文件的源文件开始都会包含package xx.xx.xx声明的)。
6. Eclipse导出说明文档:如果注释写的足够详细和标准,就可以通过eclipse直接超出说明文档,选中project,右键 Export
-> Java ->
Javadoc,然后Next,输入路径,然后Finish即可。(默认导出的位置是project/doc目录,文档为HTML格式,通过浏览器打开后
如果出现乱码,请选择UTF-8编码浏览)
可以输出文档的标准注释格式为(可通过添加“<br/>”达到在文档里的分行效果):
/**
* comment text <br/><br/>
* comment more
*/
整型转换成字节型:
byte[] b = new byte[2];
b[0] = (byte)12;
b[1] = (byte)0x16;
如果小于128也可以直接赋值,无需使用byte进行强制类型转换的。
更多绘图示例参见:
[1] apps.hi.baidu.com/share/detail/18622525
[2] blog.sina.com.cn/s/blog_61ef49250100qw9x.html
[3] www.planet-source-code.com/vb/scripts/Sh...Id=4126&lngWId=2