前言
除了使用已有的图片之外,Android应用常常需要在运行时根据场景动态生成2D图片,比如手机游戏,这就需要借助于Android2D绘图的支持。本篇博客主要讲解一下Android下使用Canvas进行绘图的相关操作。最后将以一个简单的Demo演示如何使用Canvas在ImageView上画图并保存。
本篇博客的主要内容:
画布Canvas
在Android下进行2D绘图需要Canvas类的支持,它位于"android.graphics.Canvas"包下,直译过来为画布的意思,用于完成在View上的绘图。
Canvas为提供了两个构造函数:
- Canvas():创建一个空的Canvas对象。
- Canvas(Bitmap bitmap):创建一个以bitmap位图为背景的Canvas。
既然Canvas主要用于2D绘图,那么它也提供了很多相应的drawXxx()方法,方便我们在Canvas对象上画画,drawXxx()具有多种类型,可以画出:点、线、矩形、圆形、椭圆、文字、位图等的图形,这里就不再一一介绍了,只介绍几个Canvas中常用的方法:
- void drawBitmap(Bitmap bitmap,float left,float top,Paint paint):在指定坐标绘制位图。
- void drawLine(float startX,float startY,float stopX,float stopY,Paint paint):根据给定的起始点和结束点之间绘制连线。
- void drawPath(Path path,Paint paint):根据给定的path,绘制连线。
- void drawPoint(float x,float y,Paint paint):根据给定的坐标,绘制点。
- void drawText(String text,int start,int end,Paint paint):根据给定的坐标,绘制文字。
- int getHeight():得到Canvas的高度。
- int getWidth():得到Canvas的宽度。
画笔Paint
从上面列举的几个Canvas.drawXxx()的方法看到,其中都有一个类型为paint的参数,可以把它理解为一个"画笔",通过这个画笔,在Canvas这张画布上作画。 它位于"android.graphics.Paint"包下,主要用于设置绘图风格,包括画笔颜色、画笔粗细、填充风格等。
Paint中提供了大量设置绘图风格的方法,这里仅列出一些常用的,高级的内容有时间再详细讲解:
- setARGB(int a,int r,int g,int b):设置ARGB颜色。
- setColor(int color):设置颜色。
- setAlpha(int a):设置透明度。
- setPathEffect(PathEffect effect):设置绘制路径时的路径效果。
- setShader(Shader shader):设置Paint的填充效果。
- setAntiAlias(boolean aa):设置是否抗锯齿。
- setStrokeWidth(float width):设置Paint的笔触宽度。
- setStyle(Paint.Style style):设置Paint的填充风格。
- setTextSize(float textSize):设置绘制文本时的文字大小。
使用Canvas绘图Demo
既然已经简单讲解了Android下2D绘图的两个重要类,Canvas和Paint,那么下面通过一个简单的Demo来演示一下,这样加深大家的理解。
在这个Demo中,将实现一个画图板的功能,当用户在触摸屏上移动的时候,即可在屏幕上绘制任意的图形。实现手绘功能其实是一种假象:表面上看起来可以随着用户在触摸屏上*地画线,实际上依然利用的是Canvas的drawLine()方法画直线,每条直线都是从上一个移动事件发生点画到本次移动事件的的发生点。当用户在触摸屏上连续移动的时候,每次移动点之间的距离很小,多此极短的连线,肉眼看起来就是一个依照手指触摸移动的轨迹的连线。
需要指出的是,如果程序每次都只是从上次移动事件的发生点绘制一条直线到本次拖动事件的发生点,那么当用户手指一旦离开触摸屏,再次引发触摸移动事件的时候,会导致前面绘制的内容被丢失。为了保留用户之前绘制的内容,程序需要借助于一个"双缓冲"的机制。
之前讲解SurfaceView的时候,有讲到SurfaceView会自己维护一个双缓冲的缓冲区,但是在这里使用ImageView来展示绘图效果,它需要我们去维护双缓冲的机制。当用户在ImageView上进行"绘制"的时候,程序并不直接"绘制"到该ImageView组件上,而是先绘制到一个内存中的Bitmap对象(缓冲)上,等到内存中的Bitmap绘制好之后,再一次性的将Bitmap对象"绘制"到ImageView上。
在这个Demo中,会监听ImageView的View.OnTouchListener事件的发生,它主要用户监听在View上的触摸事件。其中需要重写onTouch()方法,当用户触摸View的时候会调用这个方法,以下是它的完整签名:
boolean onTouch(View v,MotionEvent event)
它的返回值用于指定是否连续捕获触摸事件,而在它的参数中,View为当前引发触摸事件的View,而MotionEvent是当前引发触摸事件一些属性,这个类中定义了一系列的静态常量,用于表示当前触摸的动作,比如:
- MotionEvent.ACTION_DOWN:手指触摸屏幕。
- MotionEvent.ACTION_MOVE:手指移动。
- MotionEvent.ACTION_UP:手指离开屏幕。
Demo中的主要内容已经讲解清楚,下面直接上代码了,代码中注释比较详细,就不再赘述了。
布局代码:activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" > <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" > <Button
android:id="@+id/btn_resume"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="重新画图" /> <Button
android:id="@+id/btn_save"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="保存图片" />
</LinearLayout> <ImageView
android:id="@+id/iv_canvas"
android:layout_width="match_parent"
android:layout_height="match_parent" /> </LinearLayout>
activity_main.xml
实现代码:MainActivity.java
package cn.bgtx.canvasdemo; import java.io.File;
import java.io.FileOutputStream;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast; public class MainActivity extends Activity {
private Button btn_save, btn_resume;
private ImageView iv_canvas;
private Bitmap baseBitmap;
private Canvas canvas;
private Paint paint; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); // 初始化一个画笔,笔触宽度为5,颜色为红色
paint = new Paint();
paint.setStrokeWidth(5);
paint.setColor(Color.RED); iv_canvas = (ImageView) findViewById(R.id.iv_canvas);
btn_save = (Button) findViewById(R.id.btn_save);
btn_resume = (Button) findViewById(R.id.btn_resume); btn_save.setOnClickListener(click);
btn_resume.setOnClickListener(click);
iv_canvas.setOnTouchListener(touch);
} private View.OnTouchListener touch = new OnTouchListener() {
// 定义手指开始触摸的坐标
float startX;
float startY; @Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
// 用户按下动作
case MotionEvent.ACTION_DOWN:
// 第一次绘图初始化内存图片,指定背景为白色
if (baseBitmap == null) {
baseBitmap = Bitmap.createBitmap(iv_canvas.getWidth(),
iv_canvas.getHeight(), Bitmap.Config.ARGB_8888);
canvas = new Canvas(baseBitmap);
canvas.drawColor(Color.WHITE);
}
// 记录开始触摸的点的坐标
startX = event.getX();
startY = event.getY();
break;
// 用户手指在屏幕上移动的动作
case MotionEvent.ACTION_MOVE:
// 记录移动位置的点的坐标
float stopX = event.getX();
float stopY = event.getY(); //根据两点坐标,绘制连线
canvas.drawLine(startX, startY, stopX, stopY, paint); // 更新开始点的位置
startX = event.getX();
startY = event.getY(); // 把图片展示到ImageView中
iv_canvas.setImageBitmap(baseBitmap);
break;
case MotionEvent.ACTION_UP: break;
default:
break;
}
return true;
}
};
private View.OnClickListener click = new OnClickListener() { @Override
public void onClick(View v) { switch (v.getId()) {
case R.id.btn_save:
saveBitmap();
break;
case R.id.btn_resume:
resumeCanvas();
break;
default:
break;
}
}
}; /**
* 保存图片到SD卡上
*/
protected void saveBitmap() {
try {
// 保存图片到SD卡上
File file = new File(Environment.getExternalStorageDirectory(),
System.currentTimeMillis() + ".png");
FileOutputStream stream = new FileOutputStream(file);
baseBitmap.compress(CompressFormat.PNG, 100, stream);
Toast.makeText(MainActivity.this, "保存图片成功", 0).show(); // Android设备Gallery应用只会在启动的时候扫描系统文件夹
// 这里模拟一个媒体装载的广播,用于使保存的图片可以在Gallery中查看
Intent intent = new Intent();
intent.setAction(Intent.ACTION_MEDIA_MOUNTED);
intent.setData(Uri.fromFile(Environment
.getExternalStorageDirectory()));
sendBroadcast(intent);
} catch (Exception e) {
Toast.makeText(MainActivity.this, "保存图片失败", 0).show();
e.printStackTrace();
}
} /**
* 清除画板
*/
protected void resumeCanvas() {
// 手动清除画板的绘图,重新创建一个画板
if (baseBitmap != null) {
baseBitmap = Bitmap.createBitmap(iv_canvas.getWidth(),
iv_canvas.getHeight(), Bitmap.Config.ARGB_8888);
canvas = new Canvas(baseBitmap);
canvas.drawColor(Color.WHITE);
iv_canvas.setImageBitmap(baseBitmap);
Toast.makeText(MainActivity.this, "清除画板成功,可以重新开始绘图", 0).show();
}
}
}
效果展示(随手涂鸦了个笑脸,发现还是蛮有喜感的,大家将就着看看吧):
Android--使用Canvas绘图的更多相关文章
-
Android中Canvas绘图基础详解(附源码下载) (转)
Android中Canvas绘图基础详解(附源码下载) 原文链接 http://blog.csdn.net/iispring/article/details/49770651 AndroidCa ...
-
【转】Android Canvas绘图详解(图文)
转自:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2012/1212/703.html Android Canvas绘图详解(图文) 泡 ...
-
Android Canvas绘图详解(图文)
编辑推荐:稀土掘金,这是一个针对技术开发者的一个应用,你可以在掘金上获取最新最优质的技术干货,不仅仅是Android知识.前端.后端以至于产品和设计都有涉猎,想成为全栈工程师的朋友不要错过! Andr ...
-
【转】Android Canvas绘图详解
转自:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2012/1212/703.html Android中使用图形处理引擎,2D部分是an ...
-
HTML5 学习总结(四)——canvas绘图、WebGL、SVG
一.Canvas canvas是HTML5中新增一个HTML5标签与操作canvas的javascript API,它可以实现在网页中完成动态的2D与3D图像技术.<canvas> 标记和 ...
-
canvas绘图、WebGL、SVG
目录 一.Canvas 1.1.创建canvas元素 1.2.画线 1.3.绘制矩形 1.4.绘制圆弧 1.5.绘制图像 1.6.绘制文字 1.7.随机颜色与简单动画 二.WebGL 2.1.HTML ...
-
HTML5 学习笔记(四)——canvas绘图、WebGL、SVG
一.Canvas canvas是HTML5中新增一个HTML5标签与操作canvas的javascript API,它可以实现在网页中完成动态的2D与3D图像技术.<canvas> 标记和 ...
-
Android之canvas详解
首先说一下canvas类: Class Overview The Canvas class holds the "draw" calls. To draw something, y ...
-
Android自定义View绘图实现拖影动画
前几天在"Android绘图之渐隐动画"一文中通过画线实现了渐隐动画,但里面有个问题,画笔较粗(大于1)时线段之间会有裂隙,我又改进了一下.这次效果好多了. 先看效果吧: 然后我们 ...
随机推荐
-
UWP开发必备以及常用知识点总结
一直在学UWP,一直在写Code,自己到达了什么水平?还有多少东西需要学习才能独挡一面?我想对刚接触UWP的开发者都有这种困惑,偶尔停下来总结分析一下还是很有收获的! 以下内容是自己开发中经常遇到的一 ...
-
Windows 10 下安装 npm 后全局 node_modules 和 npm-cache 文件夹的设置
npm 指 Node Package Manager,是 Node.js 中一个流行的包管理和分发工具.Node.js 在某个版本的 Windows 安装包开始已经加入了 npm,现在可以进入 htt ...
-
Linux快速入门02-文件系统管理
继续进入Linux文件系统的学习,加油,早日突破MS压在自己身上的那道束缚. Linux系列文章 快速入门系列--Linux--01基础概念 快速入门系列--Linux--02文件系统管理 快速入门系 ...
-
Python XML解析(转载)
Python XML解析 什么是XML? XML 指可扩展标记语言(eXtensible Markup Language). 你可以通过本站学习XML教程 XML 被设计用来传输和存储数据. XML是 ...
-
004.CleanUp方法
定义: Delphi procedure CleanUp; 类型 可见性 所在源码 所在单元 父类 procedure protected System.RegularExpressionsCore. ...
-
BZOJ_1588_&;_Codevs_1296_[HNOI2002]_营业额统计(平衡树/set)
描述 http://www.lydsy.com/JudgeOnline/problem.php?id=1588 给出每一天的营业值,求出之前的天当中与它相差最小的营业值与它的差的绝对值(第一天的差值为 ...
-
Qt 的内部进程通信机制
Qt 的内部进程通信机制 续欣 (xxin76@hotmail.com), 博士.大学讲师 2004 年 4 月 01 日 Qt 作为一种跨平台的基于 C++ 的 GUI 系统,能够提供给用户构造图形 ...
-
设为首页/加入收藏JS代码
传统网站常用的设为首页/加入收藏js代码 <a href="javascript:void(0)" onclick="sethome(this,window.loc ...
-
python中读写excel并存入mysql
为了一个突如其来的想法:用python简单解决就好.现在算是把这个项目需要的基础功能坑都填完了.剩下就是AI和数据展示方面的坑了. 今天遇到的坑是: 1.从excel读出的中文是乱码 2.中文写入my ...
-
控制结构(1): 分枝/叶子(branch/leaf)
// 下一篇:卫语句(guard clause) 典型代码: function doSomething1(){ // ... } function doSomething2(){ // ... } f ...