点击打开链接,下载项目代码。。。。。。。。。。。。
显示的是两个图形一圆一方通过一定的计算产生不同的组合效果,其中圆形是底部的目标图像,方形是上方的源图像。
Xfermode国外有大神称之为过渡模式,这种翻译比较贴切但恐怕不易理解,大家也可以直接称之为图像混合模式,因为所谓的“过渡”其实就是图像混合的一种,这个方法跟我们上面讲到的setColorFilter蛮相似的,首先它与set一样没有公开的实现的方法:
同理可得其必然有一定的子类去实现一些方法供我们使用,查看API文档发现其果然有三个子类:AvoidXfermode, PixelXorXfermode和PorterDuffXfermode
PorterDuffXfermode当大家看到上面API DEMO给出的效果时一定会觉得PorterDuffXfermode其实就是简单的图形交并集计算,比如重叠的部分删掉或者叠加等等,事实上呢!PorterDuffXfermode的计算绝非是根据于此!上面我们也说了PorterDuffXfermode的计算是要根据具体的Alpha值和RGB值的
PS:Src为源图像,意为将要绘制的图像;Dis为目标图像,意为我们将要把源图像绘制到的图像
下面的图片人物的实体部分大小一致(自己截取失误了)
图片从左到右依次是a3_mask a3
activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#FFFFFF" android:orientation="vertical" > <com.aigestudio.customviewdemo.views.DisInView android:id="@+id/main_cv" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>
package com.aigestudio.customviewdemo.utils; import android.app.Activity; import android.util.DisplayMetrics; /** * 测绘工具类 */ public final class MeasureUtil { /** * 获取屏幕尺寸 * * @param activity * Activity * @return 屏幕尺寸像素值,下标为0的值为宽,下标为1的值为高 */ public static int[] getScreenSize(Activity activity) { DisplayMetrics metrics = new DisplayMetrics(); activity.getWindowManager().getDefaultDisplay().getMetrics(metrics); return new int[] { metrics.widthPixels, metrics.heightPixels }; } }
package com.aigestudio.customviewdemo.activities; import android.app.Activity; import android.os.Bundle; import com.aigestudio.customviewdemo.R; /** * 主界面 * */ public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } }
package com.aigestudio.customviewdemo.views; import android.app.Activity; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.util.AttributeSet; import android.view.View; import com.aigestudio.customviewdemo.R; import com.aigestudio.customviewdemo.utils.MeasureUtil; /** * 测试DisIn模式的View */ public class DisInView extends View { private Paint mPaint;// 画笔 private Bitmap bitmapDis, bitmapSrc;// 位图 private PorterDuffXfermode porterDuffXfermode;// 图形混合模式 private int x, y;// 位图绘制时左上角的起点坐标 private int screenW, screenH;// 屏幕尺寸 public DisInView(Context context) { this(context, null); } public DisInView(Context context, AttributeSet attrs) { super(context, attrs); // 实例化混合模式 porterDuffXfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN); // 初始化画笔 initPaint(); // 初始化资源 initRes(context); } /** * 初始化画笔 */ private void initPaint() { // 实例化画笔 mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); } /** * 初始化资源 */ private void initRes(Context context) { // 目标位图 bitmapDis = BitmapFactory.decodeResource(context.getResources(), R.drawable.a3); // 源位图 bitmapSrc = BitmapFactory.decodeResource(context.getResources(), R.drawable.a3_mask); // 获取包含屏幕尺寸的数组 int[] screenSize = MeasureUtil.getScreenSize((Activity) context); // 获取屏幕尺寸 screenW = screenSize[0]; screenH = screenSize[1]; /* * 计算位图绘制时左上角的坐标使其位于屏幕中心 * 屏幕坐标x轴向左偏移位图一半的宽度 * 屏幕坐标y轴向上偏移位图一半的高度 */ x = screenW / 2 - bitmapDis.getWidth() / 2; y = screenH / 2 - bitmapDis.getHeight() / 2; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //先绘制一层白色 canvas.drawColor(Color.RED); /* * 将绘制操作保存到新的图层(更官方的说法应该是离屏缓存) * (float left, float top, float right, float bottom, Paint paint, int saveFlags) */ int sc = canvas.saveLayer(0, 0, screenW, screenH, null, Canvas.ALL_SAVE_FLAG); // 先绘制dis目标图 canvas.drawBitmap(bitmapDis, x, y, mPaint); // 设置混合模式 mPaint.setXfermode(porterDuffXfermode); // 再绘制src源图 canvas.drawBitmap(bitmapSrc, x, y, mPaint); // 还原混合模式 mPaint.setXfermode(null); // 还原画布--给一个人物头像 canvas.restoreToCount(sc); } }
DisOutView
package com.aigestudio.customviewdemo.views; import android.app.Activity; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.util.AttributeSet; import android.view.View; import com.aigestudio.customviewdemo.R; import com.aigestudio.customviewdemo.utils.MeasureUtil; /** * 测试DisOut模式的View */ public class DisOutView extends View { private Paint mPaint;// 画笔 private Bitmap bitmapSrc;// 位图 private PorterDuffXfermode porterDuffXfermode;// 图形混合模式 private int x, y;// 位图绘制时左上角的起点坐标 private int screenW, screenH;// 屏幕尺寸 public DisOutView(Context context) { this(context, null); } public DisOutView(Context context, AttributeSet attrs) { super(context, attrs); // 实例化混合模式 porterDuffXfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_OUT); // 初始化画笔 initPaint(); // 初始化资源 initRes(context); } /** * 初始化画笔 */ private void initPaint() { // 实例化画笔 mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); } /** * 初始化资源 */ private void initRes(Context context) { // 获取位图 bitmapSrc = BitmapFactory.decodeResource(context.getResources(), R.drawable.a3_mask); // 获取包含屏幕尺寸的数组 int[] screenSize = MeasureUtil.getScreenSize((Activity) context); // 获取屏幕尺寸 screenW = screenSize[0]; screenH = screenSize[1]; /* * 计算位图绘制时左上角的坐标使其位于屏幕中心 * 屏幕坐标x轴向左偏移位图一半的宽度 * 屏幕坐标y轴向上偏移位图一半的高度 */ x = screenW / 2 - bitmapSrc.getWidth() / 2; y = screenH / 2 - bitmapSrc.getHeight() / 2; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //先绘制一层白色 canvas.drawColor(Color.BLUE); /* * 将绘制操作保存到新的图层(更官方的说法应该是离屏缓存)我们将在1/3中学习到Canvas的全部用法这里就先follow me */ int sc = canvas.saveLayer(0, 0, screenW, screenH, null, Canvas.ALL_SAVE_FLAG); // 先绘制一层颜色红色背景 canvas.drawColor(Color.YELLOW); // 设置混合模式 mPaint.setXfermode(porterDuffXfermode); // 再绘制src源图 canvas.drawBitmap(bitmapSrc, x, y, mPaint); // 还原混合模式 mPaint.setXfermode(null); // 还原画布 canvas.restoreToCount(sc); } }
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
源图像在运算时,只是在源图像所在区域与对应区域的目标图像做运算。所以目标图像与源图像不相交的地方是不会参与运算的!这一点非常重要!
* 不相交的地方不会参与运算,所以不相交的地方的图像也不会是脏数据,也不会被更新,所以不相交地方的图像也永远显示的是目标图像。
package com.example.porterduffmodesrcin; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.RectF; import android.util.AttributeSet; import android.view.View; public class MyView extends View { private int width = 400; private int height = 400; private Bitmap dstBmp; private Bitmap srcBmp; private Paint mPaint; /** * 构造函数 * * @param context * @param attrs */ public MyView(Context context, AttributeSet attrs) { super(context, attrs); dstBmp = makeDst(width, height); srcBmp = makeSrc(width, height); mPaint = new Paint(); } /** * 源图像在运算时,只是在源图像所在区域与对应区域的目标图像做运算。所以目标图像与源图像不相交的地方是不会参与运算的!这一点非常重要! * 不相交的地方不会参与运算,所以不相交的地方的图像也不会是脏数据,也不会被更新,所以不相交地方的图像也永远显示的是目标图像。 */ @SuppressLint("DrawAllocation") @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int layerID = canvas.saveLayer(0, 0, width, height, mPaint, Canvas.ALL_SAVE_FLAG); canvas.drawBitmap(dstBmp, 0, 0, mPaint); mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); canvas.drawBitmap(srcBmp, width / 2, height / 2, mPaint); mPaint.setXfermode(null); canvas.restoreToCount(layerID); } static Bitmap makeDst(int w, int h) { Bitmap bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); Canvas c = new Canvas(bm); Paint p = new Paint(Paint.ANTI_ALIAS_FLAG); p.setColor(0xFFFFCC44); c.drawOval(new RectF(0, 0, w, h), p); return bm; } static Bitmap makeSrc(int w, int h) { Bitmap bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); Canvas c = new Canvas(bm); Paint p = new Paint(Paint.ANTI_ALIAS_FLAG); p.setColor(0xFF66AAFF); c.drawRect(0, 0, w, h, p); return bm; } }