美文网首页Android自定义ViewAndroid 自定义控件
用最接近官网的例子做个刮奖

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

作者: 我的阿福 | 来源:发表于2020-06-16 10:57 被阅读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。

相关文章

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

    PorterDuffXfermode可以实现很多自定义view效果,如圆角,刮奖等,网上很多人都做了 ,都实现了效...

  • Kotlin Extension — Declaring Ext

    由于官网的例子很全面,因此这里直接套用Kotlin官网的例子: 例子很简单,无非就是Extension的声明和如何...

  • Flutter 官网例子

  • vue使用简单的store模式

    小型项目不必使用vuex,使用store模式就够了,官网例子 store 模式。 按官网照例子写好store.js...

  • antd select选择器远程数据选择+手输标签

    基础部分请参考antd官网教程,该需求基于官网例子修改antd官网[https://ant-design.gite...

  • Antd 4 Form表单后面跟一段文字提示

    官网有例子 记录一下

  • Seaborn入门指南

    一、官网 官网绝对是最权威的,并且存在大量通俗易懂的例子,非常适合学习seaborn官网 二、来自DataCamp...

  • 刮奖

    需求 刮开浮层显示奖品没有中奖:显示遗憾没有中奖中奖:显示奖品名字刮开后与后台进行数据交互,告知后台是否中奖 原理...

  • 刮奖

    昨天傍晚因下雨,没去广场转。于是就去郑辉逛超市,楼上楼下逛了一会儿,买点日常生活用品、蔬菜等。结帐走到出口...

  • 刮奖

    今天中午天气很热,想喝酸奶,我迫不及待的打开冰箱,发现冰箱里只有最后一大包了,我比姐姐睇先拿出那一大包酸...

网友评论

    本文标题:用最接近官网的例子做个刮奖

    本文链接:https://www.haomeiwen.com/subject/qdbxxktx.html