zoukankan      html  css  js  c++  java
  • 自己定义控件事实上非常easy1/6

    尊重原创转载请注明:From AigeStudio(http://blog.csdn.net/aigestudio)Power by Aige 侵权必究!

    炮兵镇楼

    上一节我们粗略地讲了下怎样去实现我们的View并概述了View形成动画的基本原理,这一节我们紧跟上一节的步伐来深挖怎样去绘制更复杂的View!

    通过上一节的学习我们了解到什么是画布Canvas什么是画笔Paint,而且学习了怎样去设置画笔的属性怎样在画布上画一个圆,然而,画笔的属性并不是只就设置个颜色、大小那么简单而画布呢肯定也不单单不过能画一个圆那么无趣。工欲善其事必先利其器,既然想画好图那必须学会画笔和画布的使用,那么今天我们就来看看Android的画笔跟我们现实中的画笔有什么不同呢?

    如上节所说我们能够通过Paint中大量的setter方法来为画笔设置属性:


    这些属性大多我们都能够见名知意。非常好理解,即便如此,哥还是带大家过一遍逐个剖析其使用方法,当中会不定穿插各种画图类比方Canvas、Xfermode、ColorFilter等等的使用方法。

    set(Paint src)

    顾名思义为当前画笔设置一个画笔,说白了就是把还有一个画笔的属性设置Copy给我们的画笔,不累赘了

    setARGB(int a, int r, int g, int b)

    不扯了,别跟我说不懂

    setAlpha(int a)

    同上

    setAntiAlias(boolean aa)

    这个上一节我们用到了,打开抗锯齿。只是我要说明一点,抗锯齿是依赖于算法的。算法决定抗锯齿的效率,在我们绘制棱角分明的图像时,比方一个矩形、一张位图,我们不须要打开抗锯齿。

    setColor(int color)

    不扯

    setColorFilter(ColorFilter filter)

    设置颜色过滤,什么意思呢?就像拿个筛子把颜色“滤”一遍获取我们想要的色彩结果,感觉像是扯蛋白说一样是不是?没事我们慢慢说你一定会懂,这种方法须要我们传入一个ColorFilter參数相同也会返回一个ColorFilter实例,那么ColorFilter类是什么呢?追踪源代码进去你会发现其里面非常easy差点儿没有:

    public class ColorFilter {
        int native_instance;
    
        /**
         * @hide
         */
        public int nativeColorFilter;
    
        protected void finalize() throws Throwable {
            try {
                super.finalize();
            } finally {
                finalizer(native_instance, nativeColorFilter);
            }
        }
    
        private static native void finalizer(int native_instance, int nativeColorFilter);
    }
    压根没有和图像处理相关的方法对吧。那么说明该类必然是个父类或者说其必有一定的子类去实现一些方法,查看API文档发现果然有三个子类:

    ColorMatrixColorFilter、LightingColorFilter和PorterDuffColorFilter。也就是说我们在setColorFilter(ColorFilter filter)的时候能够直接传入这三个子类对象作为參数,那么这三个子类又是什么东西呢?首先我们来看看

    ColorMatrixColorFilter

    中文直译为色彩矩阵颜色过滤器,要明确这玩意你得先了解什么是色彩矩阵。

    在Android中图片是以RGBA像素点的形式载入到内存中的,改动这些像素信息须要一个叫做ColorMatrix类的支持,其定义了一个4x5的float[]类型的矩阵:

    ColorMatrix colorMatrix = new ColorMatrix(new float[]{
    		1, 0, 0, 0, 0,
    		0, 1, 0, 0, 0,
    		0, 0, 1, 0, 0,
    		0, 0, 0, 1, 0,
    });
    当中。第一行表示的R(红色)的向量。第二行表示的G(绿色)的向量,第三行表示的B(蓝色)的向量,最后一行表示A(透明度)的向量,这一顺序必需要正确不能混淆!

    这个矩阵不同的位置表示的RGBA值,其范围在0.0F至2.0F之间,1为保持原图的RGB值。每一行的第五列数字表示偏移值,何为偏移值?顾名思义当我们想让颜色更倾向于红色的时候就增大R向量中的偏移值,想让颜色更倾向于蓝色的时候就增大B向量中的偏移值,这是最最朴素的理解,可是其实色彩偏移的概念是基于白平衡来理解的,什么是白平衡呢?说得简单点就是白色是什么颜色!假设大家是个单反爱好者或者会些PS就会非常easy理解这个概念,在单反的设置參数中有个色彩偏移,其定义的就是白平衡的色彩偏移值,就是当你去拍一张照片的时候白色是什么颜色的,在正常情况下白色是(255, 255, 255, 255)可是现实世界中我们是无法找到这种纯白物体的。所以在我们用单反拍照之前就会拿一个我们觉得是白色的物体让相机记录这个物体的颜色作为白色。然后拍摄时整张照片的颜色都会根据这个定义的白色来偏移!而这个我们定义的“白色”(比方:255, 253, 251, 247)和纯白(255, 255, 255, 255)之间的偏移值(0, 2, 4, 8)我们称之为白平衡的色彩偏移。假设你不理解不要紧。自己定义控件系列完结后紧接着就是设计色彩基础~~~~在这你就像我开头说的那样朴素地理解下就好。

    那么说了这么多,这玩意究竟有啥用呢?我们来做个test!还是接着昨天那个圆环,只是我们今天把它改成绘制一个圆而且去掉线程动画的效果由于我们不须要:

    public class CustomView extends View {
    	private Paint mPaint;// 画笔
    	private Context mContext;// 上下文环境引用
    
    	public CustomView(Context context) {
    		this(context, null);
    	}
    
    	public CustomView(Context context, AttributeSet attrs) {
    		super(context, attrs);
    		mContext = context;
    
    		// 初始化画笔
    		initPaint();
    	}
    
    	/**
    	 * 初始化画笔
    	 */
    	private void initPaint() {
    		// 实例化画笔并打开抗锯齿
    		mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    
    		/*
    		 * 设置画笔样式为描边,圆环嘛……当然不能填充不然就么意思了
    		 * 
    		 * 画笔样式分三种: 
    		 * 1.Paint.Style.STROKE:描边 
    		 * 2.Paint.Style.FILL_AND_STROKE:描边并填充
    		 * 3.Paint.Style.FILL:填充
    		 */
    		mPaint.setStyle(Paint.Style.FILL);
    
    		// 设置画笔颜色为自己定义颜色
    		mPaint.setColor(Color.argb(255, 255, 128, 103));
    
    		/*
    		 * 设置描边的粗细,单位:像素px 注意:当setStrokeWidth(0)的时候描边宽度并不为0而是仅仅占一个像素
    		 */
    		mPaint.setStrokeWidth(10);
    	}
    
    	@Override
    	protected void onDraw(Canvas canvas) {
    		super.onDraw(canvas);
    
    		// 绘制圆形
    		canvas.drawCircle(MeasureUtil.getScreenSize((Activity) mContext)[0] / 2, MeasureUtil.getScreenSize((Activity) mContext)[1] / 2, 200, mPaint);
    	}
    }
    执行下是一个橙红色的圆~~是不是有点萝卜头国旗帜的感脚?

    以下我们为Paint设置一个色彩矩阵:

    // 生成色彩矩阵
    ColorMatrix colorMatrix = new ColorMatrix(new float[]{
    		1, 0, 0, 0, 0,
    		0, 1, 0, 0, 0,
    		0, 0, 1, 0, 0,
    		0, 0, 0, 1, 0,
    });
    mPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
    再次执行发现没变化啊!

    。!草。!。是不是感觉被我坑了?假设你真的那么觉得我仅仅能说你压根就没认真看上面的文字,我说过什么?值为1时表示什么?表示不改变原色彩的值。!

    这时我们改变色彩矩阵:

    // 生成色彩矩阵
    ColorMatrix colorMatrix = new ColorMatrix(new float[]{
    		0.5F, 0, 0, 0, 0,
    		0, 0.5F, 0, 0, 0,
    		0, 0, 0.5F, 0, 0,
    		0, 0, 0, 1, 0,
    });
    mPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
    再次执行:

    是不是明显不一样了?颜色变深了便淳厚了!

    我们通过色彩矩阵与原色彩的计算得出的色彩就是这种。

    那它们是怎样计算的呢?事实上说白了就是矩阵之间的运算乘积:


    矩阵ColorMatrix的一行乘以矩阵MyColor的一列作为矩阵Result的一行,这里MyColor的RGBA值我们须要转换为[0, 1]。那么我们根据此公式来计算下我们得到的RGBA值是否跟我们计算得出来的圆的RGBA值一样:


    我们计算得出最后的RGBA值应该为:0.5, 0.25, 0.2, 1;有兴趣的童鞋能够去PS之类的画图软件里试试看正不对对不对~~~这里就不演示了!看完这里有朋友又会说了,这玩意有毛线用啊!改个颜色还这么复杂!劳资直接setColor多爽。!没错。你这样想是对的,由于毕竟我们仅仅是一个颜色,但是假设是一张图片呢????一张图片可有还几十万色彩呢!。。你麻痹你跟我说setColor?那么我们换张图片来试试呗!看看是什么样的效果:

    public class CustomView extends View {
    	private Paint mPaint;// 画笔
    	private Context mContext;// 上下文环境引用
    	private Bitmap bitmap;// 位图
    	
    	private int x,y;// 位图绘制时左上角的起点坐标
    
    	public CustomView(Context context) {
    		this(context, null);
    	}
    
    	public CustomView(Context context, AttributeSet attrs) {
    		super(context, attrs);
    		mContext = context;
    
    		// 初始化画笔
    		initPaint();
    		
    		//初始化资源
    		initRes(context);
    	}
    
    	/**
    	 * 初始化画笔
    	 */
    	private void initPaint() {
    		// 实例化画笔
    		mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    	}
    	
    	/**
    	 * 初始化资源
    	 */
    	private void initRes(Context context) {
    		// 获取位图
    		bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.a);
    		
    		/*
    		 * 计算位图绘制时左上角的坐标使其位于屏幕中心
    		 * 屏幕坐标x轴向左偏移位图一半的宽度
    		 * 屏幕坐标y轴向上偏移位图一半的高度
    		 */
    		x = MeasureUtil.getScreenSize((Activity) mContext)[0] / 2 - bitmap.getWidth() / 2;
    		y = MeasureUtil.getScreenSize((Activity) mContext)[1] / 2 - bitmap.getHeight() / 2;
    	}
    
    	@Override
    	protected void onDraw(Canvas canvas) {
    		super.onDraw(canvas);
    
    		// 绘制位图
    		canvas.drawBitmap(bitmap, x, y, mPaint);
    	}
    }
    如代码所看到的我们清除了全部的画笔属性设置由于不是必需。从资源获取一个Bitmap绘制在画布上:

    一张灰常美丽的风景图,好!如今我们来为我们的画笔加入一个颜色过滤:

    // 生成色彩矩阵
    ColorMatrix colorMatrix = new ColorMatrix(new float[]{
    		0.5F, 0, 0, 0, 0,
    		0, 0.5F, 0, 0, 0,
    		0, 0, 0.5F, 0, 0,
    		0, 0, 0, 1, 0,
    });
    mPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
    大家看到还是刚才那个色彩矩阵,执行下看看什么效果呢:

    变暗了对吧!

    没意思。我们来点更刺激的,改下ColorMatrix矩阵:

    ColorMatrix colorMatrix = new ColorMatrix(new float[]{
    		0.33F, 0.59F, 0.11F, 0, 0,
    		0.33F, 0.59F, 0.11F, 0, 0,
    		0.33F, 0.59F, 0.11F, 0, 0,
    		0, 0, 0, 1, 0,
    });

    噢!变灰了!还是没意思!

    继续改:

    ColorMatrix colorMatrix = new ColorMatrix(new float[]{
    		-1, 0, 0, 1, 1,
    		0, -1, 0, 1, 1,
    		0, 0, -1, 1, 1,
    		0, 0, 0, 1, 0,
    });

    哟呵!

    。是不是有点类似PS里反相的效果?我们常看到的图片都是RGB的,颠覆一下思维,看看BGR的试试:

    ColorMatrix colorMatrix = new ColorMatrix(new float[]{
    		0, 0, 1, 0, 0,
    		0, 1, 0, 0, 0,
    		1, 0, 0, 0, 0,
    		0, 0, 0, 1, 0,
    });

    这样红色的变成了蓝色而蓝色的就变成了红色,继续改:

    ColorMatrix colorMatrix = new ColorMatrix(new float[]{
    		0.393F, 0.769F, 0.189F, 0, 0,
    		0.349F, 0.686F, 0.168F, 0, 0,
    		0.272F, 0.534F, 0.131F, 0, 0,
    		0, 0, 0, 1, 0,
    });

    是不是有点类似于老旧照片的感脚?继续:

    ColorMatrix colorMatrix = new ColorMatrix(new float[]{
    		1.5F, 1.5F, 1.5F, 0, -1,
    		1.5F, 1.5F, 1.5F, 0, -1,
    		1.5F, 1.5F, 1.5F, 0, -1,
    		0, 0, 0, 1, 0,
    });

    类似去色后高对照度的效果,继续:

    ColorMatrix colorMatrix = new ColorMatrix(new float[]{
    		1.438F, -0.122F, -0.016F, 0, -0.03F,
    		-0.062F, 1.378F, -0.016F, 0, 0.05F,
    		-0.062F, -0.122F, 1.483F, 0, -0.02F,
    		0, 0, 0, 1, 0,
    });

    饱和度对照度加强,好了不演示了……累死我了!截图粘贴上传!!

    这些各种各样的图像效果在哪见过?PS?

    对的!还有各种拍照软件拍摄后的特效处理!

    大致原理都是这么来的!有人会问爱哥你傻逼么!这么多參数怎么玩!

    谁记得!并且TMD用參数调颜色?我映像中都是直接在各种画图软件(比方PS)里拖进度条的!

    这怎么玩!

    淡定!

    如我所说非常多时候你压根不须要了解太多原理,仅仅需站在巨人的丁丁上就可以,所以稍安勿躁!

    再下一个系列教程“设计色彩”中爱哥教你玩转色彩并且让设计和开发无缝结合!

    ColorMatrixColorFilter和ColorMatrix就是这么个东西。ColorMatrix类里面也提供了一些实在的方法,比方setSaturation(float sat)设置饱和度,并且ColorMatrix每一个方法都用了阵列的计算。假设大家感兴趣能够自己去深挖来看只是我是真心不推荐的~~~

    以下我们来看看ColorFilter的还有一个子类

    LightingColorFilter

    顾名思义光照颜色过滤,这肯定是跟光照是有关的了~~该类有且仅仅有一个构造方法:

    LightingColorFilter (int mul, int add)
    这种方法很很地简单。mul全称是colorMultiply意为色彩倍增,而add全称是colorAdd意为色彩加入。这两个值都是16进制的色彩值0xAARRGGBB。

    这种方法使用也是很的简单。还是拿上面那张图片来说吧,比方我们想要去掉绿色:

    public class CustomView extends View {
    	private Paint mPaint;// 画笔
    	private Context mContext;// 上下文环境引用
    	private Bitmap bitmap;// 位图
    
    	private int x, y;// 位图绘制时左上角的起点坐标
    
    	public CustomView(Context context) {
    		this(context, null);
    	}
    
    	public CustomView(Context context, AttributeSet attrs) {
    		super(context, attrs);
    		mContext = context;
    
    		// 初始化画笔
    		initPaint();
    
    		// 初始化资源
    		initRes(context);
    	}
    
    	/**
    	 * 初始化画笔
    	 */
    	private void initPaint() {
    		// 实例化画笔
    		mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    
    		// 设置颜色过滤
    		mPaint.setColorFilter(new LightingColorFilter(0xFFFF00FF, 0x00000000));
    	}
    
    	/**
    	 * 初始化资源
    	 */
    	private void initRes(Context context) {
    		// 获取位图
    		bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.a);
    
    		/*
    		 * 计算位图绘制时左上角的坐标使其位于屏幕中心
    		 * 屏幕坐标x轴向左偏移位图一半的宽度
    		 * 屏幕坐标y轴向上偏移位图一半的高度
    		 */
    		x = MeasureUtil.getScreenSize((Activity) mContext)[0] / 2 - bitmap.getWidth() / 2;
    		y = MeasureUtil.getScreenSize((Activity) mContext)[1] / 2 - bitmap.getHeight() / 2;
    	}
    
    	@Override
    	protected void onDraw(Canvas canvas) {
    		super.onDraw(canvas);
    
    		// 绘制位图
    		canvas.drawBitmap(bitmap, x, y, mPaint);
    	}
    }
    执行后你会发现绿色确实是没了可是原来偏绿的部分如今竟然成了红色。为毛。敬请关注下一系列设计色彩文章。。哈哈哈!!当LightingColorFilter(0xFFFFFFFF, 0x00000000)的时候原图是不会有不论什么改变的,假设我们想添加红色的值,那么LightingColorFilter(0xFFFFFFFF, 0x00XX0000)就好。当中XX取值为00至FF。那么这种方法有什么存在的意义呢?存在必然合理。这种方法存在一定是有它可用之处的,前些天有个盆友在群里问点击一个图片怎样直接改变它的颜色而不是为他多准备还有一张点击效果的图片。这样的情况下该方法就派上用场了。例如以下图一个灰色的星星,我们点击后让它变成黄色

    代码例如以下,凝视非常清楚我就不再多说了:

    public class CustomView extends View {
    	private Paint mPaint;// 画笔
    	private Context mContext;// 上下文环境引用
    	private Bitmap bitmap;// 位图
    
    	private int x, y;// 位图绘制时左上角的起点坐标
    	private boolean isClick;// 用来标识控件是否被点击过
    
    	public CustomView(Context context) {
    		this(context, null);
    	}
    
    	public CustomView(Context context, AttributeSet attrs) {
    		super(context, attrs);
    		mContext = context;
    
    		// 初始化画笔
    		initPaint();
    
    		// 初始化资源
    		initRes(context);
    
    		setOnClickListener(new OnClickListener() {
    			@Override
    			public void onClick(View v) {
    				/*
    				 * 推断控件是否被点击过
    				 */
    				if (isClick) {
    					// 假设已经被点击了则点击时设置颜色过滤为空还原本色
    					mPaint.setColorFilter(null);
    					isClick = false;
    				} else {
    					// 假设未被点击则点击时设置颜色过滤后为黄色
    					mPaint.setColorFilter(new LightingColorFilter(0xFFFFFFFF, 0X00FFFF00));
    					isClick = true;
    				}
    
    				// 记得重绘
    				invalidate();
    			}
    		});
    	}
    
    	/**
    	 * 初始化画笔
    	 */
    	private void initPaint() {
    		// 实例化画笔
    		mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    	}
    
    	/**
    	 * 初始化资源
    	 */
    	private void initRes(Context context) {
    		// 获取位图
    		bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.a2);
    
    		/*
    		 * 计算位图绘制时左上角的坐标使其位于屏幕中心
    		 * 屏幕坐标x轴向左偏移位图一半的宽度
    		 * 屏幕坐标y轴向上偏移位图一半的高度
    		 */
    		x = MeasureUtil.getScreenSize((Activity) mContext)[0] / 2 - bitmap.getWidth() / 2;
    		y = MeasureUtil.getScreenSize((Activity) mContext)[1] / 2 - bitmap.getHeight() / 2;
    	}
    
    	@Override
    	protected void onDraw(Canvas canvas) {
    		super.onDraw(canvas);
    
    		// 绘制位图
    		canvas.drawBitmap(bitmap, x, y, mPaint);
    	}
    }
    执行后点击星星就可以变成黄色再点击变回灰色。当我们不想要颜色过滤的效果时,setColorFilter(null)并重绘视图就可以。那么为什么要叫光照颜色过滤呢?原因非常easy,由于它所呈现的效果就像有色光照在物体上染色一样~~~哎,不说这方法了,看下一个也是最后一个ColorFilter的子类。


    PorterDuffColorFilter

    PorterDuffColorFilter跟LightingColorFilter一样。仅仅有一个构造方法:

    PorterDuffColorFilter(int color, PorterDuff.Mode mode)
    这个构造方法也接受两个值,一个是16进制表示的颜色值这个非常好理解,而还有一个是PorterDuff内部类Mode中的一个常量值。这个值表示混合模式。那么什么是混合模式呢?混合混合必然是有两种东西混才行,第一种就是我们设置的color值而另外一种当然就是我们画布上的元素了!

    。比方这里我们把Color的值设为红色,而模式设为PorterDuff.Mode.DARKEN变暗:

    public class CustomView extends View {
    	private Paint mPaint;// 画笔
    	private Context mContext;// 上下文环境引用
    	private Bitmap bitmap;// 位图
    
    	private int x, y;// 位图绘制时左上角的起点坐标
    
    	public CustomView(Context context) {
    		this(context, null);
    	}
    
    	public CustomView(Context context, AttributeSet attrs) {
    		super(context, attrs);
    		mContext = context;
    
    		// 初始化画笔
    		initPaint();
    
    		// 初始化资源
    		initRes(context);
    	}
    
    	/**
    	 * 初始化画笔
    	 */
    	private void initPaint() {
    		// 实例化画笔
    		mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    
    		// 设置颜色过滤
    		mPaint.setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.DARKEN));
    	}
    
    	/**
    	 * 初始化资源
    	 */
    	private void initRes(Context context) {
    		// 获取位图
    		bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.a);
    
    		/*
    		 * 计算位图绘制时左上角的坐标使其位于屏幕中心
    		 * 屏幕坐标x轴向左偏移位图一半的宽度
    		 * 屏幕坐标y轴向上偏移位图一半的高度
    		 */
    		x = MeasureUtil.getScreenSize((Activity) mContext)[0] / 2 - bitmap.getWidth() / 2;
    		y = MeasureUtil.getScreenSize((Activity) mContext)[1] / 2 - bitmap.getHeight() / 2;
    	}
    
    	@Override
    	protected void onDraw(Canvas canvas) {
    		super.onDraw(canvas);
    
    		// 绘制位图
    		canvas.drawBitmap(bitmap, x, y, mPaint);
    	}
    }
    我们尝试在画布上Draw刚才的那张图片看看:

    变暗了……也变红了……这就是PorterDuff.Mode.DARKEN模式给我们的效果。当然PorterDuff.Mode还有其它非常多的混合模式,大家能够尝试。可是这里要注意一点。PorterDuff.Mode中的模式不不过应用于图像色彩混合。还应用于图形混合。比方PorterDuff.Mode.DST_OUT就表示裁剪混合图,假设我们在PorterDuffColorFilter中强行设置这些图形混合的模式将不会看到不论什么相应的效果。关于图形混合我们将在以下具体解释。

    为了提升大家的学习兴趣也算是扩展,我跟大家说说PS中的图层混合模式。在PS中图层的混合模式跟我们PorterDuff.Mode提供的事实上是差点儿相同的可是更霸气强大,相同我们还是使用上面那张图来说明:


    如图所看到的,Layer 1我们填充了一层红色而Layer 0是我们的图片。这时我们选择混合模式为Darken(默觉得Normal)是不是连名字都跟Android的一样:


    效果也必须是一样的:


    PS的图层混合模式比Android很多其它更广泛,但两者同名混合模式所产生的效果是一样的,也基于相同的算法原理这里就不多说了。


    以下我们来看还有一个跟setColorFilter有几分相似的方法。

    setXfermode(Xfermode xfermode)

    Xfermode国外有大神称之为过渡模式,这样的翻译比較贴切但恐怕不易理解,大家也能够直接称之为图像混合模式。由于所谓的“过渡”事实上就是图像混合的一种,这种方法跟我们上面讲到的setColorFilter蛮相似的。首先它与set一样没有公开的实现的方法:

    public class Xfermode {
    
        protected void finalize() throws Throwable {
            try {
                finalizer(native_instance);
            } finally {
                super.finalize();
            }
        }
    
        private static native void finalizer(int native_instance);
    
        int native_instance;
    }
    同理可得其必定有一定的子类去实现一些方法供我们使用。查看API文档发现其果然有三个子类:AvoidXfermode, PixelXorXfermode和PorterDuffXfermode,这三个子类实现的功能要比setColorFilter的三个子类复杂得多,主要是是涉及到图像处理的一些知识可能对大家来说会比較难以理解,只是我会尽量以通俗的方式阐述它们的作用,那好先来看看我们的第一个子类

    AvoidXfermode

    首先我要告诉大家的是这个API由于不支持硬件加速在API 16已经过时了(大家能够在HardwareAccel查看那些方法不支持硬件加速)……假设想在高于API 16的机子上測试这玩意。必须如今应用或手机设置中关闭硬件加速。在应用中我们能够通过在AndroidManifest.xml文件里设置application节点下的android:hardwareAccelerated属性为false来关闭硬件加速:

    <application
        android:allowBackup="true"
        android:hardwareAccelerated="false"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen" >
        <activity
            android:name="com.aigestudio.customviewdemo.activities.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
    
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

    AvoidXfermode仅仅有一个含參的构造方法AvoidXfermode(int opColor, int tolerance, AvoidXfermode.Mode mode),其详细实现和ColorFilter一样都被封装在C/C++内,它怎么实现我们无论我们仅仅要知道这玩意怎么用即可对吧。

    AvoidXfermode有三个參数,第一个opColor表示一个16进制的能够带透明通道的颜色值比如0x12345678,第二个參数tolerance表示容差值。那么什么是容差呢?你能够理解为一个能够标识“精确”或“模糊”的东西,待会我们细讲。最后一个參数表示AvoidXfermode的详细模式,其可选值仅仅有两个:AvoidXfermode.Mode.AVOID或者AvoidXfermode.Mode.TARGET。两者的意思也很easy,我们先来看

    AvoidXfermode.Mode.TARGET

    在该模式下Android会推断画布上的颜色是否会有跟opColor不一样的颜色,比方我opColor是红色。那么在TARGET模式下就会去推断我们的画布上是否有存在红色的地方。假设有。则把该区域“染”上一层我们画笔定义的颜色,否则不“染”色,而tolerance容差值则表示画布上的像素和我们定义的红色之间的区别该是多少的时候才去“染”的。比方当前画布有一个像素的色值是(200, 20, 13),而我们的红色值为(255, 0, 0)。当tolerance容差值为255时,即便(200, 20, 13)并不等于红色值也会被“染”色。容差值越大“染”色范围越广反之则反。空说无凭我们来看看详细的实现和效果:

    public class CustomView extends View {
    	private Paint mPaint;// 画笔
    	private Context mContext;// 上下文环境引用
    	private Bitmap bitmap;// 位图
    	private AvoidXfermode avoidXfermode;// AV模式
    
    	private int x, y, w, h;// 位图绘制时左上角的起点坐标
    
    	public CustomView(Context context) {
    		this(context, null);
    	}
    
    	public CustomView(Context context, AttributeSet attrs) {
    		super(context, attrs);
    		mContext = context;
    
    		// 初始化画笔
    		initPaint();
    
    		// 初始化资源
    		initRes(context);
    	}
    
    	/**
    	 * 初始化画笔
    	 */
    	private void initPaint() {
    		// 实例化画笔
    		mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    
    		/*
    		 * 当画布中有跟0XFFFFFFFF色不一样的地方时候才“染”色
    		 */
    		avoidXfermode = new AvoidXfermode(0XFFFFFFFF, 0, AvoidXfermode.Mode.TARGET);
    	}
    
    	/**
    	 * 初始化资源
    	 */
    	private void initRes(Context context) {
    		// 获取位图
    		bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.a);
    
    		/*
    		 * 计算位图绘制时左上角的坐标使其位于屏幕中心
    		 * 屏幕坐标x轴向左偏移位图一半的宽度
    		 * 屏幕坐标y轴向上偏移位图一半的高度
    		 */
    		x = MeasureUtil.getScreenSize((Activity) mContext)[0] / 2 - bitmap.getWidth() / 2;
    		y = MeasureUtil.getScreenSize((Activity) mContext)[1] / 2 - bitmap.getHeight() / 2;
    		w = MeasureUtil.getScreenSize((Activity) mContext)[0] / 2 + bitmap.getWidth() / 2;
    		h = MeasureUtil.getScreenSize((Activity) mContext)[1] / 2 + bitmap.getHeight() / 2;
    	}
    
    	@Override
    	protected void onDraw(Canvas canvas) {
    		super.onDraw(canvas);
    
    		// 先绘制位图
    		canvas.drawBitmap(bitmap, x, y, mPaint);
    
    		// “染”什么色是由我们自己决定的
    		mPaint.setARGB(255, 211, 53, 243);
    
    		// 设置AV模式
    		mPaint.setXfermode(avoidXfermode);
    
    		// 画一个位图大小一样的矩形
    		canvas.drawRect(x, y, w, h, mPaint);
    	}
    }
    在高于API 16的測试机上会得到一个矩形的色块(API 16+的都类似。改ROM和关闭了硬件加速的除外):

    我们再用低于API 16(或高于API 16但关闭了硬件加速)的測试机执行就会得到还有一个不同的效果:


    大家能够看到,在我们的模式为TARGET容差值为0的时候此时仅仅有当图片中像色颜色值为0XFFFFFFFF的地方才会被染色,而其它地方不会有改变

    AvoidXfermode(0XFFFFFFFF, 0, AvoidXfermode.Mode.TARGET):


    而当容差值为255的时候仅仅要是跟0XFFFFFFFF有点接近的地方都会被染色

    而第二种模式

    AvoidXfermode.Mode.AVOID

    则与TARGET恰恰相反,TARGET是我们指定的颜色是否与画布的颜色一样,而AVOID是我们指定的颜色是否与画布不一样。其它的都与TARGET类似

    AvoidXfermode(0XFFFFFFFF, 0, AvoidXfermode.Mode.AVOID):


    当模式为AVOID容差值为0时,仅仅有当图片中像素颜色值与0XFFFFFFFF全然不一样的地方才会被染色

    AvoidXfermode(0XFFFFFFFF, 255, AvoidXfermode.Mode.AVOID):


    当容差值为255时,仅仅要与0XFFFFFFFF略微有点不一样的地方就会被染色

    那么这玩意到底有什么用呢?比方说当我们仅仅想在白色的区域画点东西或者想把白色区域的地方替换为还有一张图片的时候就能够採取这样的方式!

    Xfermode的第二个子类

    PixelXorXfermode

    与AvoidXfermode一样也在API 16过时了。该类也提供了一个含參的构造方法PixelXorXfermode(int opColor),该类的计算实现非常easy。从官方给出的计算公式来看就是:op ^ src ^ dst,像素色值的按位异或运算。假设大家感兴趣,能够自己用一个纯色去尝试,并自己计算异或运算的值是否与得出的颜色值一样。这里我就不讲了。Because it was deprecated and useless。

    Xfermode的最后一个子类也是惟一一个没有过时且沿用至今的子类

    PorterDuffXfermode

    该类相同有且仅仅有一个含參的构造方法PorterDuffXfermode(PorterDuff.Mode mode)。这个PorterDuff.Mode大家看后是否会有些面熟,它跟上面我们讲ColorFilter时候用到的PorterDuff.Mode是一样的!麻雀虽小五脏俱全,虽说构造方法的签名列表里仅仅有一个PorterDuff.Mode的參数,可是它能够实现非常多酷毙的图形效果。!而PorterDuffXfermode就是图形混合模式的意思,其概念最早来自于SIGGRAPH的Tomas Proter和Tom Duff,混合图形的概念极大地推动了图形图像学的发展,延伸到计算机图形图像学像Adobe和AutoDesk公司著名的多款设计软件都能够说一定程度上受到影响。而我们PorterDuffXfermode的名字也来源于这俩人的人名组合PorterDuff,那PorterDuffXfermode能做些什么呢?我们先来看一张API DEMO里的图片:


    这张图片从一定程度上形象地说明了图形混合的作用,两个图形一圆一方通过一定的计算产生不同的组合效果,在API中Android为我们提供了18种(比上图多了两种ADD和OVERLAY)模式:


    来定义不同的混合效果,这18种模式Android还为我们提供了它们的计算方式比方LIGHTEN的计算方式为[Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)],当中Sa全称为Source alpha表示源图的Alpha通道。Sc全称为Source color表示源图的颜色。Da全称为Destination alpha表示目标图的Alpha通道;Dc全称为Destination color表示目标图的颜色。细心的朋友会发现“[……]”里分为两部分,当中“,”前的部分为“Sa + Da - Sa*Da”这一部分的值代表计算后的Alpha通道而“,”后的部分为“Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)”这一部分的值代表计算后的颜色值,图形混合后的图片依靠这个矢量来计算ARGB的值,假设大家感兴趣能够查看维基百科中对Alpha合成的解释:http://en.wikipedia.org/wiki/Alpha_compositing。作为一个猿。我们不须要知道复杂的图形学计算可是一定要知道这些模式会为我们提供如何的效果,当大家看到上面API DEMO给出的效果时一定会认为PorterDuffXfermode其实就是简单的图形交并集计算,比方重叠的部分删掉或者叠加等等,其实呢!PorterDuffXfermode的计算绝非是依据于此!上面我们也说了PorterDuffXfermode的计算是要依据详细的Alpha值和RGB值的,既然如此。我们就来看一个比API DEMO略微复杂的样例来更有力地说明PorterDuffXfermode是怎样工作而我们又能用它做些什么,在这个样例中我将用到两个带有Alpha通道的渐变图形Bitmap:


    我们将在不同的模式下混合这两个Bitmap来看看这两个渐变色的颜色值在不同的混合模式下到底发生了什么?先看看我们的測试代码:

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public class PorterDuffView extends View {
    	/*
    	 * PorterDuff模式常量
    	 * 能够在此更改不同的模式測试
    	 */
    	private static final PorterDuff.Mode MODE = PorterDuff.Mode.ADD;
    
    	private static final int RECT_SIZE_SMALL = 400;// 左右上方演示样例渐变正方形的尺寸大小
    	private static final int RECT_SIZE_BIG = 800;// 中间測试渐变正方形的尺寸大小
    
    	private Paint mPaint;// 画笔
    
    	private PorterDuffBO porterDuffBO;// PorterDuffView类的业务对象
    	private PorterDuffXfermode porterDuffXfermode;// 图形混合模式
    
    	private int screenW, screenH;// 屏幕尺寸
    	private int s_l, s_t;// 左上方正方形的原点坐标
    	private int d_l, d_t;// 右上方正方形的原点坐标
    	private int rectX, rectY;// 中间正方形的原点坐标
    
    	public PorterDuffView(Context context, AttributeSet attrs) {
    		super(context, attrs);
    
    		// 实例化画笔并设置抗锯齿
    		mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    
    		// 实例化业务对象
    		porterDuffBO = new PorterDuffBO();
    
    		// 实例化混合模式
    		porterDuffXfermode = new PorterDuffXfermode(MODE);
    
    		// 计算坐标
    		calu(context);
    	}
    
    	/**
    	 * 计算坐标
    	 * 
    	 * @param context
    	 *            上下文环境引用
    	 */
    	private void calu(Context context) {
    		// 获取包括屏幕尺寸的数组
    		int[] screenSize = MeasureUtil.getScreenSize((Activity) context);
    
    		// 获取屏幕尺寸
    		screenW = screenSize[0];
    		screenH = screenSize[1];
    
    		// 计算左上方正方形原点坐标
    		s_l = 0;
    		s_t = 0;
    
    		// 计算右上方正方形原点坐标
    		d_l = screenW - RECT_SIZE_SMALL;
    		d_t = 0;
    
    		// 计算中间方正方形原点坐标
    		rectX = screenW / 2 - RECT_SIZE_BIG / 2;
    		rectY = RECT_SIZE_SMALL + (screenH - RECT_SIZE_SMALL) / 2 - RECT_SIZE_BIG / 2;
    	}
    
    	@Override
    	protected void onDraw(Canvas canvas) {
    		super.onDraw(canvas);
    		// 设置画布颜色为黑色以便我们更好地观察
    		canvas.drawColor(Color.BLACK);
    
    		// 设置业务对象尺寸值计算生成左右上方的渐变方形
    		porterDuffBO.setSize(RECT_SIZE_SMALL);
    
    		/*
    		 * 画出左右上方两个正方形
    		 * 当中左边的的为src右边的为dis
    		 */
    		canvas.drawBitmap(porterDuffBO.initSrcBitmap(), s_l, s_t, mPaint);
    		canvas.drawBitmap(porterDuffBO.initDisBitmap(), d_l, d_t, mPaint);
    
    		/*
    		 * 将绘制操作保存到新的图层(更官方的说法应该是离屏缓存)我们将在1/3中学习到Canvas的所实使用方法这里就先follow me
    		 */
    		int sc = canvas.saveLayer(0, 0, screenW, screenH, null, Canvas.ALL_SAVE_FLAG);
    
    		// 又一次设置业务对象尺寸值计算生成中间的渐变方形
    		porterDuffBO.setSize(RECT_SIZE_BIG);
    
    		// 先绘制dis目标图
    		canvas.drawBitmap(porterDuffBO.initDisBitmap(), rectX, rectY, mPaint);
    
    		// 设置混合模式
    		mPaint.setXfermode(porterDuffXfermode);
    
    		// 再绘制src源图
    		canvas.drawBitmap(porterDuffBO.initSrcBitmap(), rectX, rectY, mPaint);
    
    		// 还原混合模式
    		mPaint.setXfermode(null);
    
    		// 还原画布
    		canvas.restoreToCount(sc);
    	}
    }
    代码中我们使用到了View的离屏缓冲,也通俗地称之为层,这个概念非常easy,我们在画图的时候新建一个“层”,所有的绘制操作都在该层上而不影响该层以外的图像。比方代码中我们在绘制了画布颜色和左右上方两个方形后就新建了一个图层来绘制中间的大正方形,这个方形和左右上方的方形是在两个不同的层上的:

    注:图中所显示色彩效果与我们的代码不同,上图仅仅为演示图层概念

    当我们绘制完毕后要通过restore将全部缓冲(层)中的绘制操作还原到画布以结束绘制,详细关于画布的知识在自己定义控件事实上非常easy1/3,这里就不多说了,以下我们看详细各种模式的计算效果

    PS:Src为源图像,意为将要绘制的图像。Dis为目标图像,意为我们将要把源图像绘制到的图像……是不是感脚非常拗口 = = !Fuck……意会意会~~

    PorterDuff.Mode.ADD

    计算方式:Saturate(S + D);Chinese:饱和相加


    从计算方式和显示的结果我们能够看到。ADD模式简单来说就是对图像饱和度进行相加,这个模式在应用中不经常使用。我唯一一次使用它是通过代码控制RGB通道的融合生成图片。


    PorterDuff.Mode.CLEAR

    计算方式:[0, 0];Chinese:清除

    清除图像,非常好理解不扯了。

    PorterDuff.Mode.DARKEN

    计算方式:[Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)]。Chinese:变暗


    这个模式计算方式目測非常复杂,事实上效果非常好理解,两个图像混合。较深的颜色总是会覆盖较浅的颜色。假设两者深浅同样则混合,如图。黄色覆盖了红色而蓝色和青色由于是跟透明混合所以不变。

    细心的朋友会发现青色和黄色之间有一层类似橙色的过渡色,这就是混合的结果。

    在实际的測试中源图和目标图的DARKEN混合偶尔会有相反的结果比方红色覆盖了黄色,这源于Android对颜色值“深浅”的定义,我临时没有在官方查到有关资料不知道是否与图形图像学一致。DARKEN模式的应用在图像色彩方面比較广泛我们能够利用其特性来获得不同的成像效果。这点与之前介绍的ColorFilter有点类似。

    PorterDuff.Mode.DST

    计算方式:[Da, Dc];Chinese:仅仅绘制目标图像


    如Chinese所说,非常好理解。


    PorterDuff.Mode.DST_ATOP

    计算方式:[Sa, Sa * Dc + Sc * (1 - Da)];Chinese:在源图像和目标图像相交的地方绘制目标图像而在不相交的地方绘制源图像


    PorterDuff.Mode.DST_IN

    计算方式:[Sa * Da, Sa * Dc];Chinese:仅仅在源图像和目标图像相交的地方绘制目标图像


    最常见的应用就是蒙板绘制,利用源图作为蒙板“抠出”目标图上的图像。这里我讲一个非常简单的样例。假设大家用过PS就非常easy理解,我这里有两张图:


    一张是一个非常美丽的手绘古典美女:


    而还有一张是一张仅仅有黑色和透明通道的遮罩图:


    我们把这张美女图画在我们的屏幕上:

    public class DisInView extends View {
    	private Paint mPaint;// 画笔
    	private Bitmap bitmapDis;// 位图
    
    	private int x, y;// 位图绘制时左上角的起点坐标
    	private int screenW, screenH;// 屏幕尺寸
    
    	public DisInView(Context context) {
    		this(context, null);
    	}
    
    	public DisInView(Context context, AttributeSet attrs) {
    		super(context, attrs);
    
    		// 初始化画笔
    		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);
    
    		// 获取包括屏幕尺寸的数组
    		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.drawBitmap(bitmapDis, x, y, mPaint);
    	}
    }
    执行后例如以下:

    美女脑袋上有个文字标识巨恶心并且由于图片画质问题美图周围另一片淡黄色的不好看。那我们就通过刚才那个黑色的透明通道图把美女“抠”出来:

    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.WHITE);
    
    		/*
    		 * 将绘制操作保存到新的图层(更官方的说法应该是离屏缓存)我们将在1/3中学习到Canvas的所实使用方法这里就先follow me
    		 */
    		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);
    	}
    }
    看!

    仅仅剩米女了~~~~:

    当然该混合模式的使用方法绝不止这么简单。这仅仅是阐述了一个原理,更棒的使用方法就看你怎么用了~~~~

    PorterDuff.Mode.DST_OUT

    计算方式:[Da * (1 - Sa), Dc * (1 - Sa)];Chinese:仅仅在源图像和目标图像不相交的地方绘制目标图像

    上面那个样例呢我们把米女抠了出来,而这次我们将从一个色块中把米女的轮廓挖出来~~~~啦啦啦:

    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.WHITE);
    
    		/*
    		 * 将绘制操作保存到新的图层(更官方的说法应该是离屏缓存)我们将在1/3中学习到Canvas的所实使用方法这里就先follow me
    		 */
    		int sc = canvas.saveLayer(0, 0, screenW, screenH, null, Canvas.ALL_SAVE_FLAG);
    
    		// 先绘制一层颜色
    		canvas.drawColor(0xFF8f66DA);
    
    		// 设置混合模式
    		mPaint.setXfermode(porterDuffXfermode);
    
    		// 再绘制src源图
    		canvas.drawBitmap(bitmapSrc, x, y, mPaint);
    
    		// 还原混合模式
    		mPaint.setXfermode(null);
    
    		// 还原画布
    		canvas.restoreToCount(sc);
    	}
    }
    看看美女那动人的轮廓~~~~~么么哒:

    PorterDuff.Mode.DST_OVER

    计算方式:[Sa + (1 - Sa)*Da, Rc = Dc + (1 - Da)*Sc]。Chinese:在源图像的上方绘制目标图像


    这个就不说啦,就是两个图片谁在上谁在下的意思

    PorterDuff.Mode.LIGHTEN

    计算方式:[Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)];Chinese:变亮


    与DARKEN相反,不多说了

    PorterDuff.Mode.MULTIPLY

    计算方式:[Sa * Da, Sc * Dc]。Chinese:正片叠底


    该模式通俗的计算方式非常easy,源图像素颜色值乘以目标图像素颜色值除以255即得混合后图像像素的颜色值,该模式在设计领域应用广泛,由于其特性黑色与不论什么颜色混合都会得黑色。在手绘的上色、三维动画的UV贴图绘制都有应用。详细效果大家自己尝试我就不说了

    PorterDuff.Mode.OVERLAY

    计算方式:未给出;Chinese:叠加


    这个模式没有在官方的API DEMO中给出。谷歌也没有给出其计算方式,在实际效果中其对亮色和暗色不起作用,也就是说黑白色无效,它会将源色与目标色混合产生一种中间色。这样的中间色生成的规律也非常easy,假设源色比目标色暗,那么让目标色的颜色倍增否则颜色递减。


    PorterDuff.Mode.SCREEN

    计算方式:[Sa + Da - Sa * Da, Sc + Dc - Sc * Dc]。Chinese:滤色


    计算方式我不解释了,滤色产生的效果我觉得是Android提供的几个色彩混合模式中最好的。它能够让图像焦媃幻化,有一种色调均和的感觉:

    public class ScreenView extends View {
    	private Paint mPaint;// 画笔
    	private Bitmap bitmapSrc;// 位图
    	private PorterDuffXfermode porterDuffXfermode;// 图形混合模式
    
    	private int x, y;// 位图绘制时左上角的起点坐标
    	private int screenW, screenH;// 屏幕尺寸
    
    	public ScreenView(Context context) {
    		this(context, null);
    	}
    
    	public ScreenView(Context context, AttributeSet attrs) {
    		super(context, attrs);
    
    		// 实例化混合模式
    		porterDuffXfermode = new PorterDuffXfermode(PorterDuff.Mode.SCREEN);
    
    		// 初始化画笔
    		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);
    
    		// 获取包括屏幕尺寸的数组
    		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.WHITE);
    
    		/*
    		 * 将绘制操作保存到新的图层(更官方的说法应该是离屏缓存)我们将在1/3中学习到Canvas的所实使用方法这里就先follow me
    		 */
    		int sc = canvas.saveLayer(0, 0, screenW, screenH, null, Canvas.ALL_SAVE_FLAG);
    
    		// 先绘制一层带透明度的颜色
    		canvas.drawColor(0xcc1c093e);
    
    		// 设置混合模式
    		mPaint.setXfermode(porterDuffXfermode);
    
    		// 再绘制src源图
    		canvas.drawBitmap(bitmapSrc, x, y, mPaint);
    
    		// 还原混合模式
    		mPaint.setXfermode(null);
    
    		// 还原画布
    		canvas.restoreToCount(sc);
    	}
    }
    它比原图多一层蓝紫色调给人感觉更古典~~

    PorterDuff.Mode.SRC

    计算方式:[Sa, Sc]。Chinese:显示源图

    仅仅绘制源图。SRC类的模式跟DIS的事实上差点儿相同就不多说了,大家多动手自己试试。我已经写不动了快………………………………………………………………………………………………………………

    PorterDuff.Mode.SRC_ATOP

    计算方式:[Da, Sc * Da + (1 - Sa) * Dc];Chinese:在源图像和目标图像相交的地方绘制源图像,在不相交的地方绘制目标图像

    PorterDuff.Mode.SRC_IN

    计算方式:[Sa * Da, Sc * Da];Chinese:仅仅在源图像和目标图像相交的地方绘制源图像


    PorterDuff.Mode.SRC_OUT

    计算方式:[Sa * (1 - Da), Sc * (1 - Da)]。Chinese:仅仅在源图像和目标图像不相交的地方绘制源图像


    PorterDuff.Mode.SRC_OVER

    计算方式:[Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc];Chinese:在目标图像的顶部绘制源图像


    PorterDuff.Mode.XOR

    计算方式:[Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc]。Chinese:在源图像和目标图像重叠之外的不论什么地方绘制他们,而在不重叠的地方不绘制不论什么内容


    XOR我们在将PixelXorXfermode的时候提到过,不了解的话上去看看~~~实在写不动了

    那么这些混合模式到底有什么用?可以给我们带来什么优点呢?如果我们要画一个闹钟,例如以下:


    注:该闹钟图标为已投入执行项目文件并已有商标,请大家不要以不论什么盈利手段为目的盗用,当然做练习是没问题的

    构思一下怎么做,我们须要画一个圆圈做钟体。两个Path(Path为路径,我们将会在1/3具体学习到,这里就先follow me)作为指针,问题是两个铃铛~~~~假设我们不会混合模式,一定会想怎么计算坐标啊去绘制曲线然后闭合然后填充啊之类………………实际上有必要吗?这个闹铃不就是一个小圆再用一个大圆去遮罩吗:


    问题是不是一下子就变简单了?假设等你去计算怎么画路径怎么闭合曲线填充颜色还有多屏幕的匹配………………哥已经死了又活过来又死了…………在学完1/2的View尺寸计算和布局后我会教大家怎样做类似的View并匹配在全部的屏幕上~~~~这里就先缓一缓。大家一定要有这种思维,当你想要去画一个View的时候一定要想想看这个View的图形是不是能够通过主要的几何图形混合来生成,假设能够,那么恭喜你。使用PorterDuffXfermode的混合模式你能够事半功倍!

    PorterDuffXfermode的还有一个比較常见的应用就是橡皮檫效果,我们能够通过手指不断地触摸屏幕绘制Path,再以Path作遮罩遮掉填充的色块显示下层的图像:

    public class EraserView extends View {
    	private static final int MIN_MOVE_DIS = 5;// 最小的移动距离:假设我们手指在屏幕上的移动距离小于此值则不会绘制
    
    	private Bitmap fgBitmap, bgBitmap;// 前景橡皮擦的Bitmap和背景我们底图的Bitmap
    	private Canvas mCanvas;// 绘制橡皮擦路径的画布
    	private Paint mPaint;// 橡皮檫路径画笔
    	private Path mPath;// 橡皮擦绘制路径
    
    	private int screenW, screenH;// 屏幕宽高
    	private float preX, preY;// 记录上一个触摸事件的位置坐标
    
    	public EraserView(Context context, AttributeSet set) {
    		super(context, set);
    
    		// 计算參数
    		cal(context);
    
    		// 初始化对象
    		init(context);
    	}
    
    	/**
    	 * 计算參数
    	 * 
    	 * @param context
    	 *            上下文环境引用
    	 */
    	private void cal(Context context) {
    		// 获取屏幕尺寸数组
    		int[] screenSize = MeasureUtil.getScreenSize((Activity) context);
    
    		// 获取屏幕宽高
    		screenW = screenSize[0];
    		screenH = screenSize[1];
    	}
    
    	/**
    	 * 初始化对象
    	 */
    	private void init(Context context) {
    		// 实例化路径对象
    		mPath = new Path();
    
    		// 实例化画笔并开启其抗锯齿和抗抖动
    		mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
    
    		// 设置画笔透明度为0是关键!我们要让绘制的路径是透明的。然后让该路径与前景的底色混合“抠”出绘制路径
    		mPaint.setARGB(128, 255, 0, 0);
    
    		// 设置混合模式为DST_IN
    		mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
    
    		// 设置画笔风格为描边
    		mPaint.setStyle(Paint.Style.STROKE);
    
    		// 设置路径结合处样式
    		mPaint.setStrokeJoin(Paint.Join.ROUND);
    
    		// 设置笔触类型
    		mPaint.setStrokeCap(Paint.Cap.ROUND);
    
    		// 设置描边宽度
    		mPaint.setStrokeWidth(50);
    
    		// 生成前景图Bitmap
    		fgBitmap = Bitmap.createBitmap(screenW, screenH, Config.ARGB_8888);
    
    		// 将其注入画布
    		mCanvas = new Canvas(fgBitmap);
    
    		// 绘制画布背景为中性灰
    		mCanvas.drawColor(0xFF808080);
    
    		// 获取背景底图Bitmap
    		bgBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.a4);
    
    		// 缩放背景底图Bitmap至屏幕大小
    		bgBitmap = Bitmap.createScaledBitmap(bgBitmap, screenW, screenH, true);
    	}
    
    	@Override
    	protected void onDraw(Canvas canvas) {
    		// 绘制背景
    		canvas.drawBitmap(bgBitmap, 0, 0, null);
    
    		// 绘制前景
    		canvas.drawBitmap(fgBitmap, 0, 0, null);
    
    		/*
    		 * 这里要注意canvas和mCanvas是两个不同的画布对象
    		 * 当我们在屏幕上移动手指绘制路径时会把路径通过mCanvas绘制到fgBitmap上
    		 * 每当我们手指移动一次均会将路径mPath作为目标图像绘制到mCanvas上。而在上面我们先在mCanvas上绘制了中性灰色
    		 * 两者会由于DST_IN模式的计算仅仅显示中性灰,可是由于mPath的透明,计算生成的混合图像也会是透明的
    		 * 所以我们会得到“橡皮擦”的效果
    		 */
    		mCanvas.drawPath(mPath, mPaint);
    	}
    
    	/**
    	 * View的事件将会在7/12具体解释
    	 */
    	@Override
    	public boolean onTouchEvent(MotionEvent event) {
    		/*
    		 * 获取当前事件位置坐标
    		 */
    		float x = event.getX();
    		float y = event.getY();
    
    		switch (event.getAction()) {
    		case MotionEvent.ACTION_DOWN:// 手指接触屏幕重置路径
    			mPath.reset();
    			mPath.moveTo(x, y);
    			preX = x;
    			preY = y;
    			break;
    		case MotionEvent.ACTION_MOVE:// 手指移动时连接路径
    			float dx = Math.abs(x - preX);
    			float dy = Math.abs(y - preY);
    			if (dx >= MIN_MOVE_DIS || dy >= MIN_MOVE_DIS) {
    				mPath.quadTo(preX, preY, (x + preX) / 2, (y + preY) / 2);
    				preX = x;
    				preY = y;
    			}
    			break;
    		}
    
    		// 重绘视图
    		invalidate();
    		return true;
    	}
    }

    执行效果例如以下:

    啊啊啊啊啊啊啊啊啊啊啊啊~~~~~PorterDuffXfermode算是暂告一段落。大家一定要学会PorterDuffXfermode各个混合模式的使用,这是android图形绘制的重点之中的一个!

    再次强调:PorterDuffXfermode是重点~~~~一定要学会怎样灵活使用

    源代码地址:传送门

    温馨提示:自己定义控件事实上非常easy系列文章每周一、周四更新一篇~

    下集精彩预告:你知道Shader是什么吗?Xfermode和Colorfilter给我们带来了炫酷的图像混合效果,然而不过这样肯定是体现不出Android在图形图像处理上的逼格。锁定本台敬请关注:自己定义控件事实上非常easy1/4

    自己定义控件事实上非常easy1/4已更新

  • 相关阅读:
    hdu 2137
    hdu 2059
    hdu 2175
    hdu 1297
    hdu 1702
    hdu 1212
    hdu 1397
    [转]常见的碱性食品有哪些?
    [转]C#反射
    每个人都有自己的未来
  • 原文地址:https://www.cnblogs.com/yfceshi/p/6862397.html
Copyright © 2011-2022 走看看