angular js点击图片使图片覆盖到相应画布js canvas清除画布里,怎么做阿

如何实现Canvas图像的拖拽、点击等操作 - Darly - 推酷
如何实现Canvas图像的拖拽、点击等操作 - Darly
上一篇Canvas的博文写完后,有位朋友希望能对Canvas绘制出来的图像进行点击、拖拽等操作,因为Canvas绘制出的图像能很好的美化。好像是想做炉石什么的游戏,我也没玩过。
Canvas在我的理解中就好像在一张画布上绘制图像,它只能看到却“摸”不到,那要如何进行操作呢。我不知道网上是怎么做的,这里用自己的想法做了个DEMO分享给大家。
虽然Canvas不能拖拽,但div可以拖拽,那怎么把二者结合起来呢。初步想法是将一个与Canvas图像大小差不多的div覆盖在其上,在拖拽div时将获取的光标坐标修正后传给Canvas绘制函数并刷新图像的位置。
既然要覆盖,先做些准备工作:
1.将div与Canvas画布均position:absolute,否则无法重叠。
2.将div的z-index值设置大点,保证其在Canvas画面之上。
准备工作完成后,我们先来看div的拖拽:
var divObj=document.getElementById(&cover&);
var moveFlag=false;
divObj.onmousedown=function(e){
moveFlag=true;
var clickEvent=window.event||e;
var mwidth=clickEvent.clientX-divObj.offsetL
var mheight=clickEvent.clientY-divObj.offsetT
document.onmousemove=function(e){
var moveEvent=window.event||e;
if(moveFlag){
divObj.style.left=moveEvent.clientX-mwidth+&px&;
divObj.style.top=moveEvent.clientY-mheight+&px&;
divObj.onmouseup=function(){
moveFlag=false;
来解读下这段代码:首先获取div对象,设置拖拽标志moveFlage,当onmousedown时为true表示可以拖动,当onmouseup时为false表示不能拖动了。
var clickEvent=window.event||e;
var mwidth=clickEvent.clientX-divObj.offsetL
var mheight=clickEvent.clientY-divObj.offsetT
这三行代码是为了修正光标位置。当点击时,记录下光标在div上的位置。mwidth和mheight表示光标落点相对于div左边和上边的距离。如果不加修正:
这就是不加修正的结果,当光标点下时,div的坐标即左上角会与光标坐标一致。
点击时光标总会“粘”在div某点上。
接下来绘制图片:
首先定义全局变量X和Y,它们是为了实时更新图像的绘制坐标。
var ctx=document.getElementById(&myCanvas&).getContext(&2d&);
var img=document.getElementById(&myImg&);
function drawImg(){
ctx.clearRect(0,0,);
ctx.beginPath();
ctx.drawImage(img,X,Y);
ctx.closePath();
ctx.stroke();
window.onload=function(){
setInterval(drawImg,1);
获取“画笔”,获取图片对象。这里setInterval循环执行绘制图片的函数,以刷新图片的位置,setInterval的间隔值越小,拖拽起来越“流畅”。
同时别忘了clearRect,当图片移动到下一个位置时,清除上一个位置的图片,参数为Canvas画布的坐标和尺寸。
在拖拽时将修正后的光标坐标传给X、Y:
X=moveEvent.clientX-
Y=moveEvent.clientY-
最后加上div和图像的活动范围:
if(moveEvent.clientX&=mwidth){
divObj.style.left=0+&px&;
if(parseInt(divObj.style.left)+divObj.offsetWidth &=1000){
divObj.style.left=1000 - divObj.offsetWidth+&px&;
X=1000 - divObj.offsetW
if(moveEvent.clientY&=mheight){
divObj.style.top=0+&px&;
if(parseInt(divObj.style.top)+divObj.offsetHeight&=500){
divObj.style.top=500-divObj.offsetHeight+&px&;
Y=500-divObj.offsetH
这个就看个人的要求了,注意是要同时限定div和图片的活动范围。为本例的画布大小,如果是在整个页面里活动就换成innerWidth或innerHeight。
彻底隐藏div看看效果:
最后说下点击事件,这里要注意的是在拖拽的过程中onmousedown与onmouseup二者就构成了一个click过程,但我们不希望在拖拽结束后触发点击事件。
这里有个比较简单的办法,定义一个clickFlag默认为false,当onmousedown时设为true,若进行了onmousemove事件时设为false。
在最后onmouseup时判断clickFlag的值,为true时才触发点击事件。也就是说当你按下鼠标时,只有不发现移动,松开鼠标时才会触发点击事件。
整理后的JS代码:
// 绘制图片坐标
var divObj=document.getElementById(&cover&);
var moveFlag=false;
//区别moueseup与click的标志
var clickFlag=false;
// 拖拽函数
divObj.onmousedown=function(e){
moveFlag=true;
clickFlag=true;
var clickEvent=window.event||e;
var mwidth=clickEvent.clientX-divObj.offsetL
var mheight=clickEvent.clientY-divObj.offsetT
document.onmousemove=function(e){
clickFlag=false;
var moveEvent=window.event||e;
if(moveFlag){
divObj.style.left=moveEvent.clientX-mwidth+&px&;
divObj.style.top=moveEvent.clientY-mheight+&px&;
将鼠标坐标传给Canvas中的图像
X=moveEvent.clientX-
Y=moveEvent.clientY-
下面四个条件为限制div以及图像的活动边界
if(moveEvent.clientX&=mwidth){
divObj.style.left=0+&px&;
if(parseInt(divObj.style.left)+divObj.offsetWidth &=1000){
divObj.style.left=1000 - divObj.offsetWidth+&px&;
X=1000 - divObj.offsetW
if(moveEvent.clientY&=mheight){
divObj.style.top=0+&px&;
if(parseInt(divObj.style.top)+divObj.offsetHeight&=500){
divObj.style.top=500-divObj.offsetHeight+&px&;
Y=500-divObj.offsetH
divObj.onmouseup=function(){
moveFlag=false;
if(clickFlag){
alert(&点击生效&);
本例到此结束,更多功能大家有兴趣可以自己开发,感谢您的浏览,也感谢每个对我这菜鸟提意见的人。
已发表评论数()
&&登&&&录&&
已收藏到推刊!
请填写推刊名
描述不能大于100个字符!
权限设置: 公开
仅自己可见下载源码 - 93.5 KB
简介在这个例子中,我引用了包括AngularJS在内的一些JavaScript库,实现了一个很简单的名片生成器。&尽管在这个小应用中,AngularJS库相较于其他JavaScript库来说做的事不多,然而,这个小而强大的AngularJS却是该应用的全部灵感之源。背景在该应用中,我们需要做些简单工作。首先,我们需要用CSS设计名片。然后,我们需要让用户实时的输入和编辑数据,这个地方AngularJS就不可或缺了。再然后,我们需要将名片的HTML div容器转化为canvas画布,并以PNG图片形式下载即可。就这么简单!
代码的使用这里,我来解释下下面的代码块。&!DOCTYPE html&&html&&head& &title&vCard Creator demo&/title& &link rel=&stylesheet& type=&text/css& href=&main.css&&&/head&&body&&div id=&wrapper& ng-app& &h1&Real time vCard Creator&/h1&&div id=&editor&& &input placeholder=&Company name& ng-model=&cname&/& &input placeholder=&Company tag line& ng-model=&tagline&/& &input placeholder=&Your full name& ng-model=&name&/& &input placeholder=&Your designation& ng-model=&desig&/& &input placeholder=&Phone number& ng-model=&phone&/& &input placeholder=&Email address& ng-model=&email&/& &input placeholder=&Website url& ng-model=&url&/& &button id=&saveBut&&Download vCard as PNG&/button& &/div&
&div id=&card&&
&h4&{{cname}}&/h4&
&p&{{tagline}}&/p& &/header& &div id=&theBody&&
&div id=&theLeft&&
&h2&{{name}}&/h2&
&h5&{{desig}}&/h5&
&div id=&theRight&&
&p&{{phone}}&/p&
&p&{{email}}&/p&
&p&{{url}}&/p&
&/div& &/div&
&/div& &/div&&script type=&text/javascript& src=&angular.min.js&&&/script&&script type=&text/javascript& src=&jquery.min.js&&&/script&&script type=&text/javascript& src=&html2canvas.js&&&/script&&script type=&text/javascript& src=&canvas2image.js&&&/script&&script type=&text/javascript& src=&&&&/script&&/body&&/html&这个是该应用的HTML结构。本结构包括了两部分。一个是id为editor的div,一个是id为card的div。前一个用于让用户输入信息,后一个的作用是用来在名片上显示信息。&这俩div又被一个id为wrapper的div给包裹起来。这个id为wrapper的div里面,我们会添加 ng-app属性,因为就是在这个div容器里,我们就要使用angular了。我们可以添加ng-app到HTML的标签里,这样一来,我们就能在该网页的任何地方使用angular了。&下一步,我们创建一些输入框来接收用户的输入信息。确保每个输入框都有ng-model 这么个属性,用于传递输入框里相应的值。我们把ng-model属性放在这里,主要是因为我们想要实时的更新id为card的div里的值。现在,在id为card的div内部,确保我们已经放置了一些卖相古怪的双括弧,并且在双括弧里我们放了来自ng-model的值。&基本上,我们在输入框中输入内容后,双括弧里的值立马就随之改变了。所以对名片的编辑到此结束。我们的目标是,当一个用户点击了下载按钮,当前的名片将被转化为一张图片,并被下载到用户电脑里。#editor{ width:350 background: margin:0 margin-top:20 padding-top:10 padding-bottom:10}input{ width:90%; margin-left:5}button{ margin-left:5}#card{ width:350 height:200 background: box-shadow: 0 0 2px #323232; margin:0 margin-top:20}header{ background:#323232; padding:5}header h4{ color: line-height:0; font-family: margin:7 margin-top:20 text-shadow: 1px 1 text-transform:}header p{ line-height:0; color: font-size:10 margin:11 margin-bottom:20}#theBody{ background: width:100%; height:}#theLeft{ width:50%; float: text-align:}#theLeft h2{ margin-right:10 margin-top:40 font-family: margin-bottom:0; color:#323232;}#theLeft h5{ margin-right:10 font-family: margin-top:5 line-height:0; font-weight: color:#323232;}#theRight{ width:50%; float: padding-top:42}#theRight p{ line-height:0; font-size:12 color:#323232; font-family:C margin-left:15}这是该应用的CSS样式。在这地方我们模拟了一个名片的设计,并创建了让用户输入信息的编辑面板。&script&$(function() { $(&#saveBut&).click(function() {
html2canvas($(&#card&), {
onrendered: function(canvas) {
theCanvas = Canvas2Image.saveAsPNG(canvas);
} });});}); &/script&
最后,在HTML页面的body结束标签之前插入这段script脚本。这段脚本的包含了下载按钮对应的事件响应,也就是说 html2canvas 函数的作用是将id为card的div转化为HTML的canvas画布,并在对canvas画布完成渲染之后,以PNG文件的形式保存该canvas画布。添加完了这个script脚本之后,该做的就做完了。注意事项这个canvas2image.js脚本代码里默认没有在生成的文件名称结尾使用扩展名.png。所以如果你无法打开图片的时候,请重命名该文件名,在文件名结尾加上.png这个扩展名即可。在线调试 jsFiddle用JavaScript将Canvas内容转化成图片的方法 – WEB骇客网页设计教程与开发
提供各种常见网页效果
提供各种各样的设计教程
装扮QQ,让QQ变得更酷
设计参考,提高自升水平
学习服务器和操作系统
提供各种素材和工具
收藏学习资料
您现在的位置:&&>>&&>>&&>>&&>>&正文
angularjs客户端实现压缩图片文件并上传实例
主要是利用html5的canvas来进行图片的压缩,然后转化为dataURL,再有dataURL转化为Blob文件,Blob对象可以直接赋值给Formdata.
app.service('Util', function($q) {
var dataURItoBlob = function(dataURI) {
// convert base64/URLEncoded data component to raw binary data held in a string
if (dataURI.split(',')[0].indexOf('base64') &= 0)
byteString = atob(dataURI.split(',')[1]);
byteString = unescape(dataURI.split(',')[1]);
// separate out the mime component
var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
// write the bytes of the string to a typed array
var ia = new Uint8Array(byteString.length);
for (var i = 0; i & byteString. i++) {
ia[i] = byteString.charCodeAt(i);
return new Blob([ia], {
type: mimeString
var resizeFile = function(file) {
var deferred = $q.defer();
var img = document.createElement("img");
var reader = new FileReader();
reader.onload = function(e) {
img.src = e.target.
//resize the image using canvas
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
var MAX_WIDTH = 800;
var MAX_HEIGHT = 800;
var width = img.
var height = img.
if (width & height) {
if (width & MAX_WIDTH) {
height *= MAX_WIDTH /
width = MAX_WIDTH;
if (height & MAX_HEIGHT) {
width *= MAX_HEIGHT /
height = MAX_HEIGHT;
canvas.width =
canvas.height =
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, width, height);
//change the dataUrl to blob data for uploading to server
var dataURL = canvas.toDataURL('image/jpeg');
var blob = dataURItoBlob(dataURL);
deferred.resolve(blob);
reader.readAsDataURL(file);
} catch (e) {
deferred.resolve(e);
return deferred.
resizeFile: resizeFile
由于目前angualrjs暂不支持通过multiform data上传文件,所以利用如下的代码可以上传formdata里的文件
app.controller('CompanyCtrl', function($http, Util) {
Util.resizeFile(input.files[0]).then(function(blob_data) {
var fd = new FormData();
fd.append("imageFile", blob_data);
$http.post('/upload', fd, {
headers: {'Content-Type': undefined },
transformRequest: angular.identity
.success(function(data) {
$pany_pict = data[0];
.error(function() {
console.log("uploaded error...")
}, function(err_reason) {
console.log(err_reason);
转载请注明:破洛洛(谢谢合作)
上一篇文章: 下一篇文章:
网友评论:
[][][][][][][][][][]Android 画布绘图 - (Never Give Up)--(我的知识点滴) - ITeye技术网站
博客分类:
我们已经介绍了Canvas,在那里,已经学习了如何创建自己的View。在第7章中也使用了Canvas来为MapView标注覆盖。
画布(Canvas)是图形编程中一个很普通的概念,通常由三个基本的绘图组件组成:
提供了绘图方法,可以向底层的位图绘制基本图形。
也称为"刷子",Paint可以指定如何将基本图形绘制到位图上。
绘图的表面。
Android绘图API支持透明度、渐变填充、圆边矩形和抗锯齿。遗憾的是,由于资源限制,它还不支持矢量图形,它使用的是传统光栅样式的重新绘图。
这种光栅方法的结果是提高了效率,但是改变一个Paint对象不会影响已经画好的基本图形,它只会影响新的元素。
如果你拥有Windows开发背景,那么Android的2D绘图能力大致相当于GDI+的能力。
1. 可以画什么?
Canvas类封装了用作绘图表面的位图;它还提供了draw*方法来实现设计。
下面的列表提供了对可用的基本图形的简要说明,但并没有深入地探讨每一个draw方法的详细内容:
drawARGB / drawRGB / drawColor
使用单一的颜色填充画布。
在一个矩形区域的两个角之间绘制一个弧。
drawBitmap
在画布上绘制一个位图。可以通过指定目标大小或者使用一个矩阵来改变目标位图的外观。
drawBitmapMesh
使用一个mesh(网)来绘制一个位图,它可以通过移动网中的点来操作目标的外观。
drawCircle
以给定的点为圆心,绘制一个指定半径的圆。
drawLine(s)
在两个点之间画一条(多条)直线。
以指定的矩形为边界,画一个椭圆。
使用指定的Paint填充整个Canvas
绘制指定的Path。Path对象经常用来保存一个对象中基本图形的集合。
drawPicture
在指定的矩形中绘制一个Picture对象。
drawPosText
绘制指定了每一个字符的偏移量的文本字符串。
绘制一个矩形。
drawRoundRect
绘制一个圆角矩形。
在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将会决定如何处理剩余的区域:
使用Shader的边界颜色来填充剩余的空间。
在水平和垂直方向上拉伸Shader图像,这样每一个图像就都能与上一个缝合了。
在水平和垂直方向上重复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);
3. 使用抗锯齿效果提高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 ),如图所示。
由于上面的图片是黑白的,所以需要实际动手创建这个控件来看到完全的效果。
(1) 首先,通过修改colors.xml资源文件来包含边界、表盘阴影以及天空和地面的颜色值。同时还要更新边界和盘面标记所使用的颜色。
java代码:
&?xml version="1.0" encoding="utf-8"?&
&resources&
&color name="text_color"&#FFFF&/color&
&color name="background_color"&#F000&/color&
&color name="marker_color"&#FFFF&/color&
&color name="shadow_color"&#7AAA&/color&
&color name="outer_border"&#FF444444&/color&
&color name="inner_border_one"&#FF323232&/color&
&color name="inner_border_two"&#FF414141&/color&
&color name="inner_border"&#FFFFFFFF&/color&
&color name="horizon_sky_from"&#FFA52A2A&/color&
&color name="horizon_sky_to"&#FFFFC125&/color&
&color name="horizon_ground_from"&#FF5F9EA0&/color&
&color name="horizon_ground_to"&#FF00008B&/color&
&/resources&
(2) 用作航空地平仪的天空和地面的Paint和Shader对象是根据当前View的大小创建的,所以它们不能像你在创建的Paint对象那样,是静态的。因此,不再创建Paint对象,取而代之的是构造它们所使用的渐变数组和颜色。java代码:
int[] borderGradientC
float[] borderGradientP
int[] glassGradientC
float[] glassGradientP
int skyHorizonColorF
int skyHorizonColorTo;
int groundHorizonColorF
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 - markW int endX = center.x + markW
(9) 要让水平值更易于读取,应该保证俯仰角刻度总是从当前值开始。下面的代码计算了天空和地面的接口在水平面上的位置:java代码:
double h = innerRadius*Math.cos(Math.toRadians(90-tiltDegree)); double justTiltY = center.y -
(10) 找到表示每一个倾斜角的像素的数目。java代码:
float pxPerDegree = (innerBoundingBox.height()/2)/45f;
(11) 现在遍历180度,以当前的倾斜值为中心,给出一个可能的俯仰角的滑动刻度。java代码:
for (int i = 90; i &= -90; i -= 10) {
double ypos = justTiltY + i*pxPerD
// 只显示内表盘的刻度
if ((ypos & (innerBoundingBox.top + textHeight)) || (ypos & innerBoundingBox.bottom - textHeight))
// 为每一个刻度增加画一个直线和一个倾斜角
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);
canvas.restore();
浏览 25210
浏览: 483559 次
来自: 广州
写的不错,作为笔记了
好文章是要顶的!
理解了许多。解释的不错。3Q.

我要回帖

更多关于 canvas画布大小 的文章

 

随机推荐