前言:
不应该一路失望
又一路等待
时间它说
世界还有不同的海
但不要告诉我
现实它很坏
我想看看
自己的能耐
——莫文蔚《境外》
图片摘自《google官方文档:硬件加速》
我再重复一遍,上面我们涉及了两个API等级,在API 11以后,在程序集中加入了对GPU加速的支持,在API 14之后,硬件加速是默认开启的!也就是说在API 11——API 13虽然是支持硬件加速的,但是默认是关闭的。
1.在AndroidManifest.xml文件为application标签添加如下的属性即可为整个应用程序开启/关闭硬件加速:
<application android:hardwareAccelerated="true" ...>2.在Activity 标签下使用 hardwareAccelerated 属性开启或关闭硬件加速:
<activity android:hardwareAccelerated="false" />3. 在Window 层级使用如下代码开启硬件加速:(Window层级不支持关闭硬件加速)
getWindow().setFlags( WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);4.View 级别如下关闭硬件加速:(view 层级上不支持开启硬件加速)
setLayerType(View.LAYER_TYPE_SOFTWARE, null);或者使用android:layerType=”software”来关闭硬件加速:比如
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" android:paddingLeft="2dp" android:layerType="software" android:paddingRight="2dp" >
setLayerType(View.LAYER_TYPE_SOFTWARE, null);2、使用离屏绘制
//新建图层 int layerID = canvas.saveLayer(0,0,width,height,mPaint,Canvas.ALL_SAVE_FLAG); //TODO 核心绘制代码 //还原图层 canvas.restoreToCount(layerID);有关离屏绘制的原因,这节就先不给大家引申了,后面会单独拉出来两篇文章讲离屏绘制,大家只需要知道,我们需要把绘制的核心代码放在saveLayer()和restoreToCount()之间即可。
public AvoidXfermode(int opColor, int tolerance, Mode mode)当Mode取Mode.TARGET时,它的意义表示将opColor参数所指定的颜色替换成画笔的颜色。
然后我们把小狗身上白色的地方换成红色
效果图如下:
看下代码实现:
public class MyView extends View { private Paint mPaint; private Bitmap mBmp; public MyView(Context context, AttributeSet attrs) { super(context, attrs); mPaint = new Paint(); mBmp = BitmapFactory.decodeResource(getResources(),R.drawable.dog); setLayerType(View.LAYER_TYPE_SOFTWARE, null); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int width = 500; int height = width * mBmp.getHeight()/mBmp.getWidth(); mPaint.setColor(Color.RED); int layerID = canvas.saveLayer(0,0,width,height,mPaint,Canvas.ALL_SAVE_FLAG); canvas.drawBitmap(mBmp,null,new Rect(0,0,width,height),mPaint); mPaint.setXfermode(new AvoidXfermode(Color.WHITE,100, AvoidXfermode.Mode.TARGET)); canvas.drawRect(0,0,width,height,mPaint); canvas.restoreToCount(layerID); } }除了禁用硬件加速和离屏绘制,最关键的代码就是在离屏绘制中间的部分:
canvas.drawBitmap(mBmp,null,new Rect(0,0,width,height),mPaint); mPaint.setXfermode(new AvoidXfermode(Color.WHITE,100, AvoidXfermode.Mode.TARGET)); canvas.drawRect(0,0,width,height,mPaint);这段代码只有三句话,看起来很容易理解的样子,其实不然……下面我们就来看看它是怎么来做的吧。
canvas.drawBitmap(mBmp,null,new Rect(0,0,width,height),mPaint);所以此时的屏幕应该是这样子的:
mPaint.setXfermode(new AvoidXfermode(Color.WHITE,100, AvoidXfermode.Mode.TARGET));这一点与Photoshop是类似的,就是以白色为目标色,容差为100找到对应的选区;
canvas.drawRect(0,0,width,height,mPaint);这句的意思就是把这个纯红色对应选区的图片截取后,覆盖到原图片上面
最后我们再来对比下我们代码产生的效果图:
看起来差不多,但是使用photoshop选中的区域会更多一点。这是为什么呢?
这是因为android中计算颜色差值的算法与photoshop的不一样。
前面我们讲了颜色差异的最大值是255,所以当容差为255时,选区应该是整个小狗图片;所以做出来的效果应该是红色会把整个图片覆盖,效果图应该是如下的:
使用photoshop来演示这个过程如下:
而使用代码出来的情况却是这样的:
对应的代码如下
protected void onDraw(Canvas canvas) { super.onDraw(canvas); int width = 500; int height = width * mBmp.getHeight()/mBmp.getWidth(); mPaint.setColor(Color.RED); int layerID = canvas.saveLayer(0,0,width,height,mPaint,Canvas.ALL_SAVE_FLAG); canvas.drawBitmap(mBmp,null,new Rect(0,0,width,height),mPaint); mPaint.setXfermode(new AvoidXfermode(Color.WHITE,255, AvoidXfermode.Mode.TARGET)); canvas.drawRect(0,0,width,height,mPaint); canvas.restoreToCount(layerID); }明显可以看出,红色并没有完全覆盖整个图片,仍然也只是一部分;这就是Android比较蛋疼的地方,做出来的效果与Photoshop不一致,这就因为android中计算颜色差值的算法与photoshop的不一样,photoshop中当容差为255时表示选中所有颜色,而在android中却不是!所以选区的大小也只能靠猜……至于Android是如何来计算两个颜色之间差异的,我也懒得去找了,在现实使用中的地方比较少,一般会用来用一个图片替换另一个图片中的一部分,比如上面的,我们需要把两张图片溶合
然后利用代码将第二张图片替换小狗身上的白色位置:
protected void onDraw(Canvas canvas) { super.onDraw(canvas); int width = 500; int height = width * mBmp.getHeight()/mBmp.getWidth(); mPaint.setColor(Color.RED); int layerID = canvas.saveLayer(0,0,width,height,mPaint,Canvas.ALL_SAVE_FLAG); canvas.drawBitmap(mBmp,null,new Rect(0,0,width,height),mPaint); mPaint.setXfermode(new AvoidXfermode(Color.WHITE,100, AvoidXfermode.Mode.TARGET)); canvas.drawBitmap(BitmapFactory.decodeResource(getResources(),R.drawable.flower_2),null,new Rect(0,0,width,height),mPaint); canvas.restoreToCount(layerID); }效果图如下:
就效果图来看,替换效果还是可以的。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#0000ff" > <com.harvic.BlogXMode.MyView android:layout_width="wrap_content" android:layout_height="wrap_content"/> </LinearLayout>(2)、然后将选区填充为纯透明
public class MyView extends View { private Paint mPaint; private Bitmap mBmp; public MyView(Context context, AttributeSet attrs) { super(context, attrs); mPaint = new Paint(); mBmp = BitmapFactory.decodeResource(getResources(),R.drawable.dog); setLayerType(View.LAYER_TYPE_SOFTWARE, null); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int width = 500; int height = width * mBmp.getHeight()/mBmp.getWidth(); int layerID = canvas.saveLayer(0,0,width,height,mPaint,Canvas.ALL_SAVE_FLAG); canvas.drawBitmap(mBmp,null,new Rect(0,0,width,height),mPaint); mPaint.setXfermode(new AvoidXfermode(Color.WHITE,100, AvoidXfermode.Mode.TARGET)); //将画笔设置为纯透明 mPaint.setARGB(0x00,0xff,0xff,0xff); canvas.drawRect(0,0,width,height,mPaint); canvas.restoreToCount(layerID); } }效果图如下:
这段代码不难理解,所以正是由于把选区改为了全透明,所以才露出底部Activity的背景色。
所以这也证明了,我们提到的canvas的脏区域更新原理:
如果没有设置Xfermode,那么直接将绘制的图形覆盖Canvas对应位置原有的像素;如果设置了Xfermode,那么会按照Xfermode具体的规则来更新Canvas中对应位置的像素颜色。
所以对于AvoidXfermode而言,这个规则就是先把把目标区域(选区)中的颜色值先清空,然后再把目标颜色给替换上;