深入理解Xfermode,使用时要注意以及顺便膜拜下saveLayer的强大
来源:互联网 发布:c4d r16 mac 注册机 编辑:程序博客网 时间:2024/05/16 16:19
前言
Android的Xfermode可以做出很多神奇的效果,例如ios锁屏的扫光效果,刮奖卡刮开的效果,相框相片合成效果等等。相信很多人都用过Xfermode,网上也有很多现成的效果实例,但是我们真的了解它吗?
基本用法
关于Xfermode的使用可以看看Android官方提供的ApiDemos工程看看源码,如何创建并运行ApiDemos可看这:http://my.oschina.net/libralzy/blog/151856或者http://blog.csdn.net/liu_zhen_wei/article/details/6924017。
它的基本用法看下ApiDemos的源码就懂了,源码就一百多行,其中核心代码就几行,实现上手比较容易,或者也可以看看这两篇文章:http://blog.csdn.net/t12x3456/article/details/10432935,http://blog.csdn.net/lmj623565791/article/details/42094215。
下面的ApiDemos中Xfermode的运行截图,我借用下上面文章博主的图:
从图片我们可以看到,通过Xfermode我们可以把Src和Dst两张图片做一定的合成渲染效果处理,用到实例上会更加神奇。
简单例子:文字上部分区域加上光效
下面我先写一个简单的例子,后面会用到。该例子实现的效果就是仿ios锁屏文字的扫光效果,只不过光不会动,加上动画修改样式就会跟ios十分类似。此处写的是其重要原理。
直接上代码,我写得比较简单:
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(new MainView(this), new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); } class MainView extends View { /** * 文字图片 */ private Bitmap mTextBitmap = null; /** * 文字Canvas */ private Canvas mTextCanvas = null; /** * 光效图片 */ private Bitmap mLightBitmap = null; /** * 光效Canvas */ private Canvas mLightCanvas = null; private boolean mHasCreated = false; private Paint mTextPaint = null; private Paint mLightPaint = null; private Paint mPaint = null; private Xfermode mXfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN); public MainView(Context context) { super(context); mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mTextPaint.setTextSize(40); mTextPaint.setColor(Color.BLACK); // 文字是黑色的 mLightPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mLightPaint.setColor(Color.RED); // 光是红色的 mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); if (!mHasCreated) { // 为了简单,这里创建的图片都是整个屏幕那么大 mTextBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); mTextCanvas = new Canvas(mTextBitmap); // 在中间画一段文字 String text = "红红火火恍恍惚惚"; float textSize = mTextPaint.measureText(text); mTextCanvas.drawText(text, (w - textSize) / 2, h / 2, mTextPaint); mLightBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); mLightCanvas = new Canvas(mLightBitmap); // 画光效,其实就是一个红色的圆 mLightCanvas.drawCircle(w / 2, h / 2, 70, mLightPaint); mHasCreated = true; } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // 先画一次原文字 canvas.drawBitmap(mTextBitmap, 0, 0, mPaint); // 保存画布 int sc = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.MATRIX_SAVE_FLAG | Canvas.CLIP_SAVE_FLAG | Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.FULL_COLOR_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG); // 画光效的文字 canvas.drawBitmap(mTextBitmap, 0, 0, mPaint);// mPaint.setXfermode(mXfermode); canvas.drawBitmap(mLightBitmap, 0, 0, mPaint);// mPaint.setXfermode(null); canvas.restoreToCount(sc); } }}
直接看效果图,先看看没有用Xfermode时的效果,上面的代码已经把Xfermode注释了:
然后我们看看用了Xfermode的效果,需要把下面注释的代码打开:
// 画光效的文字 canvas.drawBitmap(mTextBitmap, 0, 0, mPaint);// mPaint.setXfermode(mXfermode); canvas.drawBitmap(mLightBitmap, 0, 0, mPaint);// mPaint.setXfermode(null);
效果图:
只要上面红色区域慢慢左右移动,最后形成的效果就是类似ios锁屏文字的效果了。
问题出现
上面的代码很简单,其核心代码就是onDraw方法里面的代码,其中
canvas.drawBitmap(mTextBitmap, 0, 0, mPaint); mPaint.setXfermode(mXfermode); canvas.drawBitmap(mLightBitmap, 0, 0, mPaint); mPaint.setXfermode(null);
就是使用Xfermode的地方。
现在如果想换个颜色背景,然后我在这代码上面加一行画背景色的代码,就是如下:
canvas.drawColor(Color.BLUE); // 画一个蓝色的背景色canvas.drawBitmap(mTextBitmap, 0, 0, mPaint);mPaint.setXfermode(mXfermode);canvas.drawBitmap(mLightBitmap, 0, 0, mPaint);mPaint.setXfermode(null);
然后问题就出现了,请看效果图:
咦?说好的蓝色背景呢?怎么不见了?再看看代码明明是已经把蓝色画在画布上,怎么一点蓝色都没有呢?
此时确实很有疑惑,一时也摸不着头脑。我们尝试下把mXfermode换个相反的模式,把原本的PorterDuff.Mode.SRC_IN改成PorterDuff.Mode.DST_IN,也就是:
private Xfermode mXfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
看看效果图:
蓝色终于出现了!不过为啥不是整个屏幕呢?!而且文字效果也不对!好有疑惑。
我的猜想
以前我一直以为Xfermode合成的是使用Xfermode前后的两个图片,也就是mTextBitmap和mLightBitmap这两张图片:
canvas.drawBitmap(mTextBitmap, 0, 0, mPaint); mPaint.setXfermode(mXfermode); canvas.drawBitmap(mLightBitmap, 0, 0, mPaint); mPaint.setXfermode(null);
但是现在效果明显告诉我不是这样的。
我认为Xfermode合成的应该是当前Canvas与setXfermode之后画的那张图片。回到上面的第一次画蓝色背景的例子:
canvas.drawColor(Color.BLUE); // 画一个蓝色的背景色 ①canvas.drawBitmap(mTextBitmap, 0, 0, mPaint); ②mPaint.setXfermode(mXfermode);canvas.drawBitmap(mLightBitmap, 0, 0, mPaint); ③mPaint.setXfermode(null);
假如我们不画蓝色背景,跳过①,我们来到②的位置,此处画了整个文字,此时Canvas上的有像素值的地方仅仅是文字的地方;然后执行③后,将Canvas上的像素和mLightBitmap的像素合成,因此就会形成正确的效果,就是部分文字出现红色;
但是如果我们先执行了①,由于画了整个Canvas,此时整个Canvas都有像素值,所以执行③,将Canvas上的像素和mLightBitmap的像素合成后,形成的效果就是如上面的图所示。
以上是我的猜想,由于Canvas的源码都是调用Native层的代码实现,最终是调用Skia图库实现,这部分我不熟悉,所以无法从代码上验证。但是对于该猜想把握十足。
侧面验证,问题的解决方法
解决方法一:
我们可以从该解决方法侧面验证我的猜想,这个解决方法比较简单,就是把画蓝色背景色的部分移到saveLayer之前,也就是:
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawColor(Color.BLUE); // 画一个蓝色的背景色 // 先画一次原文字 canvas.drawBitmap(mTextBitmap, 0, 0, mPaint); // 保存画布 int sc = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.MATRIX_SAVE_FLAG | Canvas.CLIP_SAVE_FLAG | Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.FULL_COLOR_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG); // 画光效的文字 canvas.drawBitmap(mTextBitmap, 0, 0, mPaint); mPaint.setXfermode(mXfermode); canvas.drawBitmap(mLightBitmap, 0, 0, mPaint); mPaint.setXfermode(null); canvas.restoreToCount(sc); }
先看效果图:
效果非常正确,这正是我想要的。
上面的代码改了之后,因为在使用Xfermode已经saveLayer了,导致后面所有操作都是在另一个图层所做的,因此此时Canvas非常干净,所以该Layer层上用Xfermode合成时就是文字和圆形红光,然后在restoreToCount之后,该Layer就会绘制在原有的Canvas上,因此效果就是上图,非常正确。这也侧面验证了猜想。
解决方法二:
将所有操作都放在mTextCanvas上,也就是:
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawColor(Color.BLUE); // 画一个蓝色的背景色 // 先画一次原文字 String text = "红红火火恍恍惚惚"; float textSize = mTextPaint.measureText(text); canvas.drawText(text, (getWidth() - textSize) / 2, getHeight() / 2, mTextPaint);// canvas.drawBitmap(mTextBitmap, 0, 0, mPaint); // 画光效的文字 mPaint.setXfermode(mXfermode); mTextCanvas.drawBitmap(mLightBitmap, 0, 0, mPaint); mPaint.setXfermode(null); canvas.drawBitmap(mTextBitmap, 0, 0, mPaint); }
这个解决方法就是把所有操作都放到了画Text的Canvas上了,因为mTextCanvas没有背景色像素干扰,所以同样十分干净,有像素值的地方仅仅是文本的地方,所以合成效果也十分正确。
延伸理解
上面的代码里用到了canvas.saveLayer的方法,此处也是多亏该方法,才能让效果完全实现,当然解决方法二不需要如此。
以前不怎么理解saveLayer,一直觉得跟save好像,但是现在来看两者差远了,saveLayer强大很多。
我们看看源码对两者的注释:
/** * Saves the current matrix and clip onto a private stack. * <p> * Subsequent calls to translate,scale,rotate,skew,concat or clipRect, * clipPath will all operate as usual, but when the balancing call to * restore() is made, those calls will be forgotten, and the settings that * existed before the save() will be reinstated. * * @return The value to pass to restoreToCount() to balance this save() */ public int save() { return native_save(mNativeCanvasWrapper, MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG); } /** * This behaves the same as save(), but in addition it allocates and * redirects drawing to an offscreen bitmap. * <p class="note"><strong>Note:</strong> this method is very expensive, * incurring more than double rendering cost for contained content. Avoid * using this method, especially if the bounds provided are large, or if * the {@link #CLIP_TO_LAYER_SAVE_FLAG} is omitted from the * {@code saveFlags} parameter. It is recommended to use a * {@link android.view.View#LAYER_TYPE_HARDWARE hardware layer} on a View * to apply an xfermode, color filter, or alpha, as it will perform much * better than this method. * <p> * All drawing calls are directed to a newly allocated offscreen bitmap. * Only when the balancing call to restore() is made, is that offscreen * buffer drawn back to the current target of the Canvas (either the * screen, it's target Bitmap, or the previous layer). * <p> * Attributes of the Paint - {@link Paint#getAlpha() alpha}, * {@link Paint#getXfermode() Xfermode}, and * {@link Paint#getColorFilter() ColorFilter} are applied when the * offscreen bitmap is drawn back when restore() is called. * * @param bounds May be null. The maximum size the offscreen bitmap * needs to be (in local coordinates) * @param paint This is copied, and is applied to the offscreen when * restore() is called. * @param saveFlags see _SAVE_FLAG constants, generally {@link #ALL_SAVE_FLAG} is recommended * for performance reasons. * @return value to pass to restoreToCount() to balance this save() */ public int saveLayer(@Nullable RectF bounds, @Nullable Paint paint, @Saveflags int saveFlags) { if (bounds == null) { bounds = new RectF(getClipBounds()); } return saveLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, paint, saveFlags); }
save方法可以保存当前的matrix and clip,并且在restore把它恢复,一些平移,旋转,缩放等操作都会影响Canvas的matrix,所以save操作一般可以保存这些信息以及clip信息;
而saveLayer则强大很多,它相当于另外起一张干净图层,并在上面进行绘制操作,然后在restoreToCount的时候,把刚才所绘制的重新绘制在原本的Canvas上。当时正如所知的那样,它会绘制两次,所以消耗是十分巨大,对此,官方注释也进行了很长的说明和建议,请自行翻译。
小结
就是上面的猜想:我认为Xfermode合成的是当前Canvas与setXfermode之后画的那张图片。
- 深入理解Xfermode,使用时要注意以及顺便膜拜下saveLayer的强大
- Canvas的saveLayer理解
- canvas的saveLayer理解
- Paint:xfermode的使用
- 深入理解java强大的注解
- junit4 的使用 顺便理解ClassPathXmlApplicationContext的使用
- 理解POST和PUT的区别,顺便提下RESTful
- 关于Paint 的 setXfermode(Xfermode xfermode) 的理解
- 安卓Canvas的save以及saveLayer简单总结
- xFermode的原理及使用
- iBatis下使用like查询,以及需要注意的问题
- 深入理解JavaScript系列--------强大的原型和原型链
- linux下网络服务器模型以及使用时应该注意的问题
- v4 Simple下的XferMode学习.
- Paint的Xfermode的使用和经验总结
- 使用redis缓存数据需要注意的问题以及个人的一些思考和理解
- ~ 使用redis缓存数据需要注意的问题以及个人的一些思考和理解
- 使用redis缓存数据需要注意的问题以及个人的一些思考和理解
- Xen I/O虚拟化原理——Xen下总线、设备和驱动
- IOS 的 plist方法 好记性不如看博客
- 利用VPN实现android免root防火墙的方法
- android svg
- 错误:Error Domain=NSCocoaErrorDomain Code=3840 "Garbage at end." UserInfo={NSDebugDescription=Garbage
- 深入理解Xfermode,使用时要注意以及顺便膜拜下saveLayer的强大
- 简易易懂的android回调的实现
- Play 2, Scala, postgresql and Squeryl 整合
- 根据radio值设置是否选中状态
- Linux进程间通信
- 一句话总结C++,Java,PHP
- Managed Direct3D开发经验浅析
- ANT教程之三 Ant构建文件
- centos6.5环境下redis3.0集群搭建和配置