其实在前面的文章中也都用到了画笔(Paint),了解了一些常用的属性,比如抗锯齿,带边框,空心,宽度等,这些都是最基本的属性,下面来说一个高级属性『PorterDuffXfermode』
PorterDuffXfermode
首先看一张图
PoerterDuffXfermode设置的是两个图层交集区域的显示方式,其中dst是先画的图形,src是后画的图形。
这里列举了16种,当然有的都不怎么经常使用,最常用的就是DST_IN、SRC_IN来将矩形图片变成圆角图片或者圆形图片了。下面就来进行一个实例CircleImageView(好久之前就看过鸿神的这个View,一直不怎么理解,书看到这一章节就忽然想起来了,自己重新写一遍),完成图如下:
分如下几步
- 获得图片
- 测量宽高
- 创建画布
- 画一个圆
- 使用PerterDuffXfermode来控制下一个图片和刚才的圆交集
- 画图片
- 完事
首先我们在构造函数中获得图片:
public CircleImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyleAttr, 0);
try {
mBitmap = BitmapFactory.decodeResource(getResources(), a.getResourceId(R.styleable.CircleImageView_src, -1));
if (mBitmap == null) {
throw new RuntimeException("src Null!");
}
} finally {
a.recycle();
}
}
然后我们在onMeasure()中控制图片的宽高
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidth = measureWidthAndHeight(MeasureSpec.getSize(widthMeasureSpec));
mHeight = measureWidthAndHeight(MeasureSpec.getSize(heightMeasureSpec));
setMeasuredDimension(mWidth, mHeight);
}
private int measureWidthAndHeight(int size) {
int mode = MeasureSpec.getMode(size);
switch (mode) {
case MeasureSpec.UNSPECIFIED:
case MeasureSpec.AT_MOST:
return 200;
case MeasureSpec.EXACTLY:
default:
return size;
}
}
这里我们给wrap_content设置了一个200的值,然后我们在onDraw()方法中完成我们剩下的工作:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(createBitmap(), 0, 0, null);
}
private Bitmap createBitmap() {
/**
* 获取宽高最小值,重新生成一个Bitmap
*/
int min = Math.min(mHeight, mWidth);
mBitmap = Bitmap.createScaledBitmap(mBitmap, min, min, false);
/**
* 根据原有的Bitmap再生成一个Bitmap,当做Canvas的参数
* 如果在Canvas的构造中带入一个Bitmap的话,那么后续在画布上画的东西就等于在Bitmap上画的
*/
Bitmap b = Bitmap.createBitmap(min, min, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(b);
/**
* 初始化画笔,设置抗锯齿
*/
mPaint = new Paint();
mPaint.setAntiAlias(true);
/**
* 首先画一个圆,和画布一样大
*/
canvas.drawCircle(min / 2, min / 2, min / 2, mPaint);
/**
* 设置PoerterDuffXfermode参数,使后面画的和前面画的交集
* 这样就等于在一个圆上画我们的图片,所以看到的就是一个圆形的图片了
*/
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(mBitmap, 0, 0, mPaint);
return b;
}
这样就好啦,运行程序就可以看到我们开始的效果啦~
感谢鸿洋大神
原文地址
第二个实例 刮刮卡
相信大家都玩过刮刮卡,记得没错好像是支付宝也弄过这个效果(好像是在支付成功后)
刮刮卡有两个图层,上面一层和下面的图片,上面的主要用来被刮掉,在初始状态下,上面的图层会掩盖住下面的图层,当用手刮上面的图层时,下面的会慢慢显示出来,这就需要用到DST_IN了。效果图如下:
分如下几步
- 初始化Paint和图片等
- 在onDraw()方法中绘制两个图片,首先绘制背景图,然后绘制遮罩层
- 在onTouchEvent()方法中绘制路径
- 使用DST_IN模式绘制在上图层就ok
首先初始化,代码如下:
private void init() {
mPaint = new Paint();
/**
* 设置画笔透明度为0,设置PoerterDuffXfermode为DST_IN模式
*/
mPaint.setAlpha(0);
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); //设置宽度
mPath = new Path();
/**
* 获取背景图片
*/
mBgBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.a1);
/**
* 创建遮罩层Bitmap和背景图片一样大小,画上灰色
*/
mFgBitmap = Bitmap.createBitmap(mBgBitmap.getWidth(), mBgBitmap.getHeight(), Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mFgBitmap);
mCanvas.drawColor(Color.GRAY);
}
然后绘制两个图层:
protected void onDraw(Canvas canvas) {
/**
* 首先绘制背景图,然后绘制遮罩层,这样遮罩层才会在背景图上面
*/
canvas.drawBitmap(mBgBitmap, 0, 0, null);
canvas.drawBitmap(mFgBitmap, 0, 0, null);
}
最后在onTouchEvent()方法中绘制路径:
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
/**
* 重置一下路径,移动到该点
*/
case MotionEvent.ACTION_DOWN:
mPath.reset();
mPath.moveTo(event.getX(), event.getY());
break;
/**
* 路径的点
*/
case MotionEvent.ACTION_MOVE:
mPath.lineTo(event.getX(), event.getY());
break;
}
/**
* 绘制路径,其实是在mFgBitmap上绘制
* 并且由于DST_IN模式,取交集,透明度为0,所以就能实现刮刮卡的效果
*/
mCanvas.drawPath(mPath, mPaint);
/**
* 通知重绘
*/
invalidate();
return true;
}
这样一个刮刮卡的效果就完成了,是不是很简单。
需要注意的一点:在使用PorterDuffXfermode时,最好把硬件加速关闭,因为有的模式不支持硬件加速
最后
爱生活,爱小丽,爱Android