一、前言
今天是周日,昨天刚刚写完了一篇关于如何搭建LNMP环境,让自己可以DIY有个性的个人主页: 点击进入
那么今天,我们继续来看一篇关于Android中的UI篇,如何自定义视图View的进阶篇,关于前奏篇之前已经写过了,还没有了解的同学可以去看看:点击进入,在这篇文章中我主要介绍了自定义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技术实时推送