用最接近官网的例子做个刮奖
2020-06-16 本文已影响0人
我的阿福
pic.gif
PorterDuffXfermode可以实现很多自定义view效果,如圆角,刮奖等,网上很多人都做了 ,都实现了效果,但关于刮奖其实现方法和描述让我感觉很困惑,(只有一篇感觉合理的。)大多数的实现方法千篇一律,和官方的原理描述并不一致,下面来个自认为理解最正确的方式。
实现步骤:
1.先用onDraw(Canvas ca)方法中的画布画上文字。
2. 自建一个Bitmap,然后画上全灰色,充当dst,绘制到onDraw画布上。
3.自建一个Bitmap,充当src。先不绘制任何东西,所以相当于这张bitmap全透明;然后在上面绘制手势滑动的路径Path,画path的画笔Alpha要不透明,在画path的时候,src的bitmap上就会产生不透明的部分,这时候和dst重叠,参照PorterDuff.Mode的效果图中,最符合想要效果的是DstOut:因为,从DstOut图上可以看出,当dst和src重叠部分,src不透明的时候,dst和src都会被清除,也就是说,当src透明的时候,重叠部分的dst不会被清除。这里一开始dst和src就是完全相同大小的两张bitmap,所以是全重合的。所以只会在src上产生不透明路径的时候清除掉重合的部分。最重要的是2、3步,也是关键的地方。
4.需要注意的时候关于画笔的使用,在绘制dst的时候可以不用画笔,但绘制src的时候一定要用画笔,而且这时候画笔是设置的XferMode模式的。
下面上代码:
package com.h.anthony.widgets;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
/**
* Created by hf on 2020-06-15.
* 這個原理是這樣的:
* 1.首先,還是根據官方的例子來,把重合的兩個部分劃到兩個不同的bitmap上。
* 2.假如一個mask層是灰色的maskBitmap,還有一個空的Bitmap專門用來畫手勢路徑pathBitmap。
* 繪畫的步驟:
* a、普通畫布上直接繪製文字。
* b、將灰色的maskBitmap繪製在普通畫布上
* c、將手勢Path繪製在pathBitmap上。
* d、啟用XferMode模式,用此模式構造的畫筆將pathBitmap繪製在普通畫布上。
* 說明:關鍵的地方是參照16張圖。還沒有繪製的時候,src是一張空的bitmap(當然也就是透明的),而dst是一張灰色的bitmap,
* 在手勢滑動的時候,繪製path導致src上會產生不為空的一些路徑,所以要找到二者重合時,src不透明導致dst被
* 清除的效果圖,發現滿足此效果的有DstOut,Xor。
* 同理,还可以根据图的效果,反选做出修复的效果。
*/
public class GuaguaCardView extends View {
private static final String TAG = "GuaguaCardView";
private int mWidth, mHeight;
private Bitmap maskBitmap, mPathBitmap;
private String mText = "今天天气还不错";
private Paint mTextPaint, mXFermodePaint, mPathPaint;
private Canvas mPathCanvas;
private Path mPath;
public GuaguaCardView(Context context) {
super(context);
}
public GuaguaCardView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initView();
}
private void initView() {
mTextPaint = new Paint();
mTextPaint.setColor(Color.BLACK);
mTextPaint.setTextSize(50);
mXFermodePaint = new Paint();
mPathPaint = new Paint();
mPathPaint.setStyle(Paint.Style.STROKE);
mPathPaint.setStrokeWidth(20);
mPath = new Path();
}
private Bitmap createMaskBitmap() {
Bitmap bitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawColor(Color.GRAY);
return bitmap;
}
private Bitmap createPathBitmap() {
Bitmap bitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
mPathCanvas = new Canvas(bitmap);
return bitmap;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
mWidth = getWidth();
mHeight = getHeight();
maskBitmap = createMaskBitmap();
mPathBitmap = createPathBitmap();
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawText(mText, 0, mHeight / 2, mTextPaint);
int re = canvas.saveLayer(0, 0, mWidth, mHeight, null);
//mask
canvas.drawBitmap(maskBitmap, 0, 0, null);
//
mXFermodePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
//path bitmap
mPathCanvas.drawPath(mPath, mPathPaint);
canvas.drawBitmap(mPathBitmap, 0, 0, mXFermodePaint);
mXFermodePaint.setXfermode(null);
canvas.restoreToCount(re);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mPath.moveTo(event.getX(), event.getY());
break;
case MotionEvent.ACTION_MOVE:
mPath.lineTo(event.getX(), event.getY());
break;
}
postInvalidate();
return true;
}
}
总结:
-
把最重要的两张图放这里:
image.png
image.png
- 要实现效果的时候,多参照文档给出的那张图片。
- 要注意理解每个模式中的计算公式,如果一个bitmap有一部分是中被扣了的(或者自建一个bitmap还没画内容),那么这部分是没有像素的,所以在计算重叠的时候不会计算,当做0,这一点相当重要,比如,用一张有形状扣好了的图片来制作loading就是依据这个,不然是无法确定这个图片的具体边界形状的,相应的,默认情况下重叠的没有被扣掉的部分在计算时就是1。