安卓中Canvas使用方法


更多技术内容请移步:我的个人博客

一、前言

今天是周日,昨天刚刚写完了一篇关于如何搭建LNMP环境,让自己可以DIY有个性的个人主页:

http://blog.csdn.net/jiangwei0910410003/article/details/50929955

那么今天,我们继续来看一篇关于Android中的UI篇,如何自定义视图View的进阶篇,关于前奏篇之前已经写过了,还没有了解的同学可以去看看:http://blog.csdn.net/jiangwei0910410003/article/details/42640665,在这篇文章中我主要介绍了自定义View的一些基础知识,讲解了Paint,Canvas,Path,渐变色等技术。那么隔了半年的时间,我们今天再次看一下如何在上一个层次去使用Canvas自定义更多的我们想要的特效呢?说道这里,我还想多讲两句,就是Android中UI知识点还是很多很高深的,如果你对UI技术了解的非常透彻,那么待遇也是很不错的,现在可以看到一些公司在找Android高级UI工程师,就是叫你去做一些酷炫的UI,同时对性能方面也是要了解很多的,因为我们知道酷炫的UI动画什么的,其实如果处理不好会给整个程序造成很大的性能问题。所以本人对Android中的UI和逆向领域非常感兴趣的。


二、知识点&实现的效果

下面我们就来看看,如何使用Canvas做一些我们经常要用到的效果:

1、修改图片的透明度

2、图层的叠加效果(遮罩层)

3、画布的保存和恢复操作

4、使用矩阵实现平移,旋转,缩放动画

5、通过旋转,平移,缩放画布来实现动画

6、裁剪画布

总共六个知识点,讲完这些知识点,现在市面上的View大部分都可以知道原理,以及自己可以动手去绘制了,后面会再写一篇关于自定义ViewGroup系列(LinearLayout,RelativeLayout等)的相关文章。


三、具体实现

我们下面就用例子一一介绍这上面的六个功能点,首先我们先回顾一下,如何自定义一个View,其实很简单,我们只需要集成View类,然后在onDraw方法中,得到一个画布Canvas对象,然后就可以绘制各种我们想要的效果了。

public void onDraw(Canvas canvas)

下面我们先来看看第一个知识点:

1、修改图片的透明度

/**
 * 修改图片的argb值
 * @param sourceImg
 * @param number
 * @return
 */
public static Bitmap getTransparentBitmap(Bitmap sourceImg, int number){
    int[] argb = new int[sourceImg.getWidth() * sourceImg.getHeight()];
    sourceImg.getPixels(argb, 0, sourceImg.getWidth(), 0, 0, sourceImg
            .getWidth(), sourceImg.getHeight());// 获得图片的ARGB值
    number = number * 255 / 100;
    for (int i = 0; i < argb.length; i++) {
        argb[i] = (number << 24) | (argb[i] & 0x00FFFFFF);
    }
    sourceImg = Bitmap.createBitmap(argb, sourceImg.getWidth(), sourceImg
            .getHeight(), Bitmap.Config.ARGB_8888);
    return sourceImg;
}

我们在onDraw方法绘制出修改之后的图片:

BitmapDrawable drawable = (BitmapDrawable)ctx.getResources().getDrawable(R.drawable.cm_whatsapp_ico_audio);
Bitmap bitmap = drawable.getBitmap();

//修改图片的argb值的使用
bitmap = getTransparentBitmap(bitmap, 10);
canvas.drawBitmap(bitmap, 0, 0, paint);
代码其实很简单,就是拿到原图Bitmap,然后在从新构造一张图片,只是这时候我们会看到这里的技术点:

首先我们可以获取到一张图片的ARGB数组值,数组大小就是图片的像素值,那么每个像素都是一个int类型值:

A代表透明度:8位

RGB代表三基色的颜色值:各8位

那么加起来是32位,正好一个int类型长度。

这个也是在构造图片的时候我们可以看到Bitmap.Config.ARGB_8888的含义,当然这个Config还有其他值,比如:

Bitmap.Config.ALPHA_8:只有透明度的值,8位,那么我们可以使用byte类型就可以了。这张图我们可以想象没有颜色的图片。

Bitmap.Config.ARGB_4444:和ARGB.8888类似,就是占用的位数不一样,这里是4*4=16位,用short类型即可,由此可见ARGB.8888虽然能够显示更高清的图片,但是占用内存会高点。

Bitmap.Config.RGB_565:只有颜色值,没有透明度,占用位数:5+6+5=16位,用short类型即可。

就是在构造一张Bitmap的时候,可以传递新的argb值了,那么意味着我们可以随意的改变图片的透明度,颜色值啥的,我们看一下效果:


下面我们在随便改一下他的颜色值:

for (int i = 0; i < argb.length; i++) {
    argb[i] = (number << 24) | (argb[i] & 0x00FFFFFF);
    argb[i] = 400 + (argb[i] & 0xFF00FFFF);
}

看一下效果:


知识点:

1>、我们可以使用Bitmap的getPixel方法获取图片的像素值

2>、了解到了图片像素值的表示含义和各个配置之间的差异

3>、修改图片的透明度和颜色值


2、图层的叠加效果(遮罩层)

这个知识点我们可能在前面已经介绍过了,比如如何制作圆角图片,就是用遮罩层来做的,其实遮罩层说白了,就是两张图片之间如何进行叠加,类似于ppt中我们制作图片的时候,可以选择至于上层,至于底层等效果。但是这里的遮罩层的效果会更多,下面来看一下代码实现:

//获得圆角图片的方法
/**
 *   android.graphics.PorterDuff.Mode.SRC:只绘制源图像
         android.graphics.PorterDuff.Mode.DST:只绘制目标图像
         android.graphics.PorterDuff.Mode.DST_OVER:在源图像的顶部绘制目标图像
         android.graphics.PorterDuff.Mode.DST_IN:只在源图像和目标图像相交的地方绘制目标图像
         android.graphics.PorterDuff.Mode.DST_OUT:只在源图像和目标图像不相交的地方绘制目标图像
         android.graphics.PorterDuff.Mode.DST_ATOP:在源图像和目标图像相交的地方绘制目标图像,在不相交的地方绘制源图像
         android.graphics.PorterDuff.Mode.SRC_OVER:在目标图像的顶部绘制源图像
         android.graphics.PorterDuff.Mode.SRC_IN:只在源图像和目标图像相交的地方绘制源图像
         android.graphics.PorterDuff.Mode.SRC_OUT:只在源图像和目标图像不相交的地方绘制源图像
         android.graphics.PorterDuff.Mode.SRC_ATOP:在源图像和目标图像相交的地方绘制源图像,在不相交的地方绘制目标图像
         android.graphics.PorterDuff.Mode.XOR:在源图像和目标图像重叠之外的任何地方绘制他们,而在不重叠的地方不绘制任何内容
         android.graphics.PorterDuff.Mode.LIGHTEN:获得每个位置上两幅图像中最亮的像素并显示
         android.graphics.PorterDuff.Mode.DARKEN:获得每个位置上两幅图像中最暗的像素并显示
         android.graphics.PorterDuff.Mode.MULTIPLY:将每个位置的两个像素相乘,除以255,然后使用该值创建一个新的像素进行显示。结果颜色=顶部颜色*底部颜色/255
         android.graphics.PorterDuff.Mode.SCREEN:反转每个颜色,执行相同的操作(将他们相乘并除以255),然后再次反转。结果颜色=255-(((255-顶部颜色)*(255-底部颜色))/255)
 * @param bitmap
 * @param roundPx
 * @return
 */
public static Bitmap getRoundedCornerBitmap(Bitmap bitmap,float roundPx){
    Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap
            .getHeight(), Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(output);

    final int color = 0xff424242;
    final Paint paint = new Paint();
    final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
    final RectF rectF = new RectF(rect);
    paint.setAntiAlias(true);
    canvas.drawARGB(0, 0, 0, 0);
    paint.setColor(color);
    canvas.drawRoundRect(rectF, roundPx, roundPx, paint);
    paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR));
    canvas.drawBitmap(bitmap, rect, rect, paint);
    return output;
}
这里我们看到,我们首先可以创造一个画布,但是画布不能为空,需要一个打底图,我们直接使用配置Bitmap.Config.ARGB_8888配置创造一个和原图一样大小的白色图片,当然我们通过上面说的方法,可以创造一个有透明度和颜色值的图片,然后贴在画布上。这里我们看到也可以直接使用画布来设置ARGB的值,这里全是0,这时候我们可以作画了,我们绘制一个圆角矩形,绘制完成之后,我们需要设置画笔的遮罩层效果,这里是关键点,关于遮罩层有很多种选项:

android.graphics.PorterDuff.Mode.SRC:只绘制源图像
android.graphics.PorterDuff.Mode.DST:只绘制目标图像
android.graphics.PorterDuff.Mode.DST_OVER:在源图像的顶部绘制目标图像
android.graphics.PorterDuff.Mode.DST_IN:只在源图像和目标图像相交的地方绘制目标图像
android.graphics.PorterDuff.Mode.DST_OUT:只在源图像和目标图像不相交的地方绘制目标图像
android.graphics.PorterDuff.Mode.DST_ATOP:在源图像和目标图像相交的地方绘制目标图像,在不相交的地方绘制源图像
android.graphics.PorterDuff.Mode.SRC_OVER:在目标图像的顶部绘制源图像
android.graphics.PorterDuff.Mode.SRC_IN:只在源图像和目标图像相交的地方绘制源图像
android.graphics.PorterDuff.Mode.SRC_OUT:只在源图像和目标图像不相交的地方绘制源图像
android.graphics.PorterDuff.Mode.SRC_ATOP:在源图像和目标图像相交的地方绘制源图像,在不相交的地方绘制目标图像
android.graphics.PorterDuff.Mode.XOR:在源图像和目标图像重叠之外的任何地方绘制他们,而在不重叠的地方不绘制任何内容
android.graphics.PorterDuff.Mode.LIGHTEN:获得每个位置上两幅图像中最亮的像素并显示
android.graphics.PorterDuff.Mode.DARKEN:获得每个位置上两幅图像中最暗的像素并显示
android.graphics.PorterDuff.Mode.MULTIPLY:将每个位置的两个像素相乘,除以255,然后使用该值创建一个新的像素进行显示。结果颜色=顶部颜色*底部颜色/255
android.graphics.PorterDuff.Mode.SCREEN:反转每个颜色,执行相同的操作(将他们相乘并除以255),然后再次反转。结果颜色=255-(((255-顶部颜色)*(255-底部颜色))/255)

这些选项很全的,可以实现两张图片之间叠加的任何效果了。设置完打底图片的画笔遮罩层之后,那么就来绘制第二张图片,也就是原始图片,绘制完成之后,直接返回打底图片即可。

看一下效果:



看到了,这里我们选择的遮罩层选项是:Mode.XOR。就是智慧值他们两交集以外的地方,挺有意思的吧,下面我们在用Mode.SCR_IN来看看,和Mode.XOR相反,只绘制两张图片相交的地方:


好吧,这就实现了图片的圆角效果了。哈哈哈,当然还有其他遮罩的效果,大家都可以去尝试一下,也很好玩的。

知识点:

1>、手动创建一个Bitmap和Canvas

2>、了解遮罩层技术改变两张图片的叠加效果


3、画布的保存和恢复操作

这个技术,我们在自定义View的时候,用到的最多的地方,也是非常重要的一个点,他说白了,就是先在画布上绘制一个图形,然后保存一下,在绘制一个图形,然后在恢复一下,这样就可以在一个画布上绘制出不同的样式了,下面我们来看一下代码:

/**
 * 保留图层技术
 * @param canvas
 */
public static void saveCanvas(Canvas canvas){

    //首先绘制一个线条
    Paint paint = new Paint();
    paint.setColor(Color.BLUE);
    paint.setStrokeWidth(10);
    canvas.drawLine(0,0, 100,100,paint);

    //保存画布,同时设置区域l,t,r,b
    canvas.saveLayerAlpha(10, 10, 300, 300, 0x88, Canvas.ALL_SAVE_FLAG);

    //绘制圆圈
    Paint paint1 = new Paint();
    paint1.setColor(Color.RED);
    paint1.setStrokeWidth(10);
    paint1.setStyle(Paint.Style.FILL);
    canvas.drawCircle(140, 140, 100, paint1);
    canvas.restore();
}
我们看到首先,我们绘制出一个蓝色的线条,然后我们保存画布状态,我们在保存的时候,同时设置一下画布的区域和透明度,然后在绘制一个红色的圆圈,最后恢复画布,我们看一下效果:



看到了,首先红色的圆圈和蓝色的线条重叠的地方,红色图片在上面,其次是我们看到红色的圆圈有透明度的,不是全红的。这样我们就学会了保存画布和恢复画布状态操作,但是在实际的过程中我们不可能这么简单的,我们改一下代码,让画布平移一下:


这里我们使用矩阵来操作一下画布平移,然后我们看一下效果:


好了,看到了吗?圆圈移动了,其实实际上不是圆圈移动了,是画布移动了,所以要理解好画布的保存和恢复的真正含义:

每个画板Canvas创建的时候都有一个默认的图层,我们在使用save方法的时候,会在创建一个图层,然后绘制,在调用restore方法的时候,相当于两个图层的叠加操作,所以看上去是画布移动了,其实不是的,是新建了一个图层,画布不可能移动的。在说的通俗点就是:画布相当于画板,图层相当于画纸。

当然这里我们还看到了圆圈没显示全,这个就是我们上面在保存画布的时候,设置了区域,其实这个区域和透明度就是新建图层的大小和透明度,这个圆圈是绘制在新建的图层上的所以会被裁剪了。

还有一个地方需要注意的是:

我们每次如果要操作画布的时候,一定要记住一个准则:

先操作画布,在绘制图形

如果顺序反了,那么操作是没有效果的,比如上面的平移效果如果放到绘制圆圈的后面,那么就没有任何效果的。

知识点:

1>、使用画布保存技术来绘制想要的特殊效果

2>、理解了画布的平移,缩放等效果不是真正意义上的操作画布,而是新建一个图层,然后进行图层的叠加操作。

3>、使用矩阵来操作画布


4、使用矩阵实现平移,旋转,缩放效果

我们在实现View的简单的平移,旋转,缩放等功能的时候,Android中提供了很多种选择,可以使用传统动画,属性动画等。但是这里我们今天来看一下,在绘制图片的时候我们使用矩阵来实现这些效果,说道矩阵,在学习线性数学的时候,我们知道两个矩阵相乘能够实现各种变化效果,同时A*B=C,A矩阵我们理解为变化前的矩阵,B矩阵理解为变化矩阵,C矩阵理解为变化后的矩阵。当然Android中操作举证的Api不需要我们去手动的计算,而是帮我们已经封装了平移,旋转,缩放这三种功能的效果矩阵,同样我们也可以实现矩阵的手动计算,但是这里就不介绍了,因为矩阵操作还是很复杂的,感兴趣的同学可以去网上搜一下。下面我们来看一下代码:

/**
 * 使用Matrix来实现旋转图片
 * @param bitmap
 * @return
 */
public static Bitmap operateBitmap(Bitmap bitmap){
    Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap
            .getHeight(), Bitmap.Config.RGB_565);
    Canvas canvas = new Canvas(output);
    Matrix matrix = new Matrix();
    matrix.postRotate(300);
    matrix.postScale(0.4f, 0.5f);
    matrix.postTranslate(100, 100);
    canvas.drawBitmap(bitmap, matrix, new Paint());
    return output;
}
这里我们依然是,先创建一个画布和打底图,然后就用矩阵来操作图片了,这里先旋转,缩放,平移,然后我们看一下效果:


看到了,图片变成这个样子了,但是这里我们看到怎么有一块是黑色的,原因是我们在创建打底图的时候设置的图片配置是:

Bitmap.Config.RGB_565

这个样式是没有透明度的,默认是黑色的。

关于Matrix矩阵操作有一个前乘和后乘的区别,因为矩阵的乘法是不支持交换律的,post开头的方法都是后乘,就是我们正常的操作效果,其中pre开头的方法是前乘,改一下代码:


看看效果:


我擦,不知道变成什么鬼了,我这里为什么要提一下前乘和后乘的区别呢?其实想说明一个问题就是:

一定不要把前乘和后乘理解为是相反的操作

如果我们想操作相反的动作,只需要把数值写成负数值即可。

知识点:

1>、学习到了矩阵操作

2>、理解了矩阵的前乘和后乘的区别


5、通过旋转,平移,缩放画布来实现动画

关于这个知识点,我们在前面自定义视图View的基础篇说到了。但是这里我们来使用选择画布的方式来实现动画效果,下面来看一下代码:

/**
 * 通过旋转画布来实现图片的旋转
 * @param canvas
 * @param bitmap
 * @param argee
 */
public void recyleRotateBitmap(Canvas canvas, Bitmap bitmap, int argee){
    //这里需要注意的是一定是先操作完画布,才能绘制,不然没效果的
    canvas.rotate(argee);
    Rect rect1 = new Rect(0,0,bitmap.getWidth(),bitmap.getWidth());
    Rect rect2 = new Rect(0,0,bitmap.getWidth(),bitmap.getHeight());
    canvas.drawBitmap(bitmap, rect1, rect2, new Paint());
    postInvalidateDelayed(200);
}
这里我们可以看到,Canvas本身也有平移,旋转,缩放的方法,可以不需要矩阵就可以实现,这里我们实现的原理很简单:就是在onDraw方法中来改变旋转角度,然后从新绘制图片,然后调用postInvalidateDelayed方法来实现画布的内容更新操作,在onDraw方法中我们的代码:


我们看一下效果:


就是图片沿着左上角的点开始旋转。

知识点:

1>、使用Canvas本身的api来操作画布


6、裁剪画布

有时候我们可能需要裁剪画布,就是把画布上已经画好的图形,我们只想要其中的一部分,那么这时候我们可以使用裁剪技术来实现,比如我们现在想把图片裁剪成不规则形状,我们看一下代码:

/**
 * 裁剪画布
 * @param canvas
 * @param bitmap
 */
public static void clipCanvas(Canvas canvas, Bitmap bitmap){

    canvas.drawColor(Color.GRAY);

    //先裁去完画布,在绘制内容

    //canvas.clipRect(0, 0, 200, 200);

    Path path = new Path();
    path.lineTo(0, 0);
    path.lineTo(400, 0);
    path.lineTo(100, 300);
    path.lineTo(0, 0);
    canvas.clipPath(path);

    Rect rect1 = new Rect(0,0,bitmap.getWidth(),bitmap.getWidth());
    Rect rect2 = new Rect(0,0,bitmap.getWidth(),bitmap.getHeight());
    canvas.drawBitmap(bitmap, rect1, rect2, new Paint());
}
这里我们绘制不规则形状使用Path来实现的,这里有一点需要注意的是,裁剪画布也算是操作画布了,所以还是要遵从一个准则:

先操作画布,在绘制图形

我们可以看一下效果:


擦,被裁成这个吊样了,我们这里还看到了,可以使用path来实现绘制不规则图形的,而且裁剪的api中也支持规则的图形裁剪,比如是:圆形,矩形等裁剪形状。

知识点:

1>、使用Path来绘制不规则图形

2>、随意裁剪图片的形状


代码下载地址:http://download.csdn.net/detail/jiangwei0910410003/9467133


四、知识点总结

到这里我们就介绍完了关于画布Canvas的一些操作,这些操作可以满足我们在日常中想要实现的效果了,下面我们就再来整理我们所学习到的知识点:

1>   我们可以使用Bitmap的getPixel方法获取图片的像素值
2>   了解到了图片像素值的表示含义和各个配置之间的差异
3>   修改图片的透明度和颜色值
4>   手动创建一个Bitmap和Canvas
5>  了解遮罩层技术改变两张图片的叠加效果
6>   使用画布保存技术来绘制想要的特殊效果
7>   理解了画布的平移,缩放等效果不是真正意义上的操作画布,而是新建一个图层,然后进行图层的叠加操作。
8>   使用矩阵来操作画布
9>   学习到了矩阵操作
10> 理解了矩阵的前乘和后乘的区别
11> 使用Canvas本身的api来操作画布
12> 使用Path来绘制不规则图形
13> 随意裁剪图片的形状


五、总结

我们这篇文章主要介绍了如何操作画布来实现我们在自定义视图View的时候想要的效果,这些api我们可以不用记住,但是这些知识点可以记住,因为我们在看到一种UI效果的时候我们可以想到有这些功能就可以了,没事多看看一些UI上的效果和特效,多想想可以怎么去实现它,这样对于我们绘制UI很有帮助,没事的时候脑补一下还是很有用途的。


更多内容:点击这里

关注微信公众号,最新Android技术实时推送


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值