Android自定义ViewAndroid 自定义控件

用最接近官网的例子做个刮奖

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;
    }
}

总结:


上一篇 下一篇

猜你喜欢

热点阅读