Android 自定义View (五)

在必要时候需要弯一弯,转一转,因为太坚强容易折断,我们需要更多的柔软,才能战胜挫折。


本讲内容:PorterDuffXfermode图形混合模式类只有一个含参的构造方法

<span style="font-size:18px;">PorterDuffXfermode(PorterDuff.Mode mode)</span>

在API中Android为我们提供了18种图形混合模式:


Sa全称为Source alpha表示源图的Alpha通道;Sc全称为Source color表示源图的颜色;Da全称为Destination alpha表示目标图的Alpha通道;Dc全称为Destination color表示目标图的颜色


比上图多了少种ADD和OVERLAY)模式:(上层( src源图 )、下层(dis目标图) 

1、PorterDuff.Mode.CLEAR                所绘制不会提交到画布上。

2、PorterDuff.Mode.SRC                    显示上层绘制图片
3、PorterDuff.Mode.DST                    显示下层绘制图片
4、PorterDuff.Mode.SRC_OVER        正常绘制显示,上下层绘制叠盖。
5、PorterDuff.Mode.DST_OVER        上下层都显示。下层居上显示。
6、PorterDuff.Mode.SRC_IN               取两层绘制交集。显示上层。
7、PorterDuff.Mode.DST_IN               取两层绘制交集。显示下层。
8、PorterDuff.Mode.SRC_OUT          取上层绘制非交集部分。
9、PorterDuff.Mode.DST_OUT          取下层绘制非交集部分。
10、PorterDuff.Mode.SRC_ATOP      取下层非交集部分与上层交集部分
11、PorterDuff.Mode.DST_ATOP      取上层非交集部分与下层交集部分
12.PorterDuff.Mode.XOR                    取两层绘制非交集。两层绘制非交集。
13、PorterDuff.Mode.DARKEN          上下层都显示。变暗
14、PorterDuff.Mode.LIGHTEN         上下层都显示。变量
15、PorterDuff.Mode.MULTIPLY       取两层绘制交集
16、PorterDuff.Mode.SCREEN         上下层都显示。

17、PorterDuff.Mode.ADD                上下层饱和度相加

18、PorterDuff.Mode.OVERLAY      上下层叠加


示例一:DST_IN  取两层绘制交集。显示下层。

          

     dis目标图                                src源图                        混合模式效果图(文字不见了)

下面是自定义CustomTitleView.java文件:

public class CustomView extends View {
	private Paint mPaint;// 画笔
	private Bitmap bitmapDis, bitmapSrc;// 位图
	private PorterDuffXfermode porterDuffXfermode;// 图形混合模式

	private int x, y;// 位图绘制时左上角的起点坐标
	private int screenW, screenH;// 屏幕尺寸

	public CustomView(Context context) {
		super(context, null);
	}

	public CustomView(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.a1);
		bitmapSrc = BitmapFactory.decodeResource(context.getResources(), R.drawable.a2);

		// 获取包含屏幕尺寸的数组
		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;
	}

	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		canvas.drawColor(Color.WHITE);

		// 将绘制操作保存到新的图层(离屏缓存)
		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);
	}

}

示例二:DST_OUT      取下层绘制非交集部分。

           

            src源图                              效果图

下面是自定义CustomTitleView.java文件:

public class CustomView extends View {
	private Paint mPaint;// 画笔
	private Bitmap bitmapSrc;// 位图
	private PorterDuffXfermode porterDuffXfermode;// 图形混合模式

	private int x, y;// 位图绘制时左上角的起点坐标
	private int screenW, screenH;// 屏幕尺寸

	public CustomView(Context context) {
		super(context, null);
	}

	public CustomView(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.c5);

		// 获取包含屏幕尺寸的数组
		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;
	}

	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		canvas.drawColor(Color.WHITE);

		// 将绘制操作保存到新的图层(离屏缓存)
		int sc = canvas.saveLayer(0, 0, screenW, screenH, null,
				Canvas.ALL_SAVE_FLAG);

		// 先绘制一层颜色(先绘制dis目标图)
		canvas.drawColor(0xFFFF04DA);

		// 设置混合模式
		mPaint.setXfermode(porterDuffXfermode);

		// 再绘制src源图
		canvas.drawBitmap(bitmapSrc, x, y, mPaint);

		// 还原混合模式
		mPaint.setXfermode(null);

		// 还原画布
		canvas.restoreToCount(sc);
	}

}

示例三:橡皮檫效果

我们可以通过手指不断地触摸屏幕绘制Path,再以Path作遮罩遮掉填充的色块显示下层的图像:



public class CustomView 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 CustomView(Context context) {
		super(context, null);
	}

	public CustomView(Context context, AttributeSet attrs) {
		super(context, attrs);

		// 计算参数
		cal(context);

		// 初始化对象
		init(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);
	}

	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的事件
	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;  
    }  

}

Take your time and enjoy it 路过的、学习过的请留个言,顶个呗~~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值