Android Studio实现刮刮卡效果

代码和刮刮乐图片参考网络

实现效果

MainActivity

java 复制代码
import android.app.Activity;
import android.os.Bundle;

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

ScratchCardView

java 复制代码
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
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.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;


public class ScratchCardView extends View {

    //类成员变量
    private Paint mPaint;//画笔
    private Path mPath;//手指滑动的路径
    private Canvas mCanvas;//临时画布

    private Bitmap mBackGroundBitmap;//未刮奖前背景
    private Bitmap mForeGroundBitmap;//前景图(灰色)

    private int mLastX, mLastY;//滑动结束点的坐标

    public ScratchCardView(Context context) {
        this(context, null);
    }

    public ScratchCardView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ScratchCardView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    /**
     * 初始化操作
     */
    private void init() {

        mPaint = new Paint();//初始化画笔
        mPaint.setAlpha(0);//设置alpha不透明度,范围为0~255
        mPaint.setAntiAlias(true);//消除锯齿边,给画笔设置平滑的属性
        mPaint.setStyle(Paint.Style.STROKE);//描边效果
        mPaint.setStrokeCap(Paint.Cap.ROUND);//圆角效果
        mPaint.setStrokeJoin(Paint.Join.ROUND);//设置圆角
        mPaint.setStrokeWidth(20);//设置画笔宽度
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));//设置图层混合模式

        mPath = new Path();// 实例化路径
        //未刮奖前背景 图片资源转化为Bitmap
        mBackGroundBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.background);

        //创建一个和背景图大小一致的Bitmap对象作为装载画布
        mForeGroundBitmap = Bitmap.createBitmap(mBackGroundBitmap.getWidth(), mBackGroundBitmap.getHeight(), Config.ARGB_8888);

        //与Canvas进行绑定  //画涂层的画布,传一个Bitmap进去,所画的信息都存在Bitmap上
        mCanvas = new Canvas(mForeGroundBitmap);

        //涂成灰色
        mCanvas.drawColor(Color.BLUE);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //先把底层的画画到View的画布上
        canvas.drawBitmap(mBackGroundBitmap, 0, 0, null);
        //绘制前景层
        canvas.drawBitmap(mForeGroundBitmap, 0, 0, null);
    }

    /**
     * 手指滑动事件处理,把手指移动的轨迹保存在Path中.
     * 不停的移动,就不停的回调View的更新UI的方法:invalidate();
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            //当用户按下屏幕时,会执行MotionEvent.ACTION_DOWN的case分支,
            // 记录下当前的坐标,并将路径(Path)移动到该点
            case MotionEvent.ACTION_DOWN:
                mLastX = (int) event.getX();
                mLastY = (int) event.getY();
                mPath.moveTo(mLastX, mLastY);
                break;

            //当用户在屏幕上滑动时,会执行MotionEvent.ACTION_MOVE的case分支,
            // 记录下当前的坐标,并将路径绘制到该点
            case MotionEvent.ACTION_MOVE:
                mLastX = (int) event.getX();
                mLastY = (int) event.getY();
                mPath.lineTo(mLastX, mLastY);
                break;

            //当用户松开屏幕时,会执行MotionEvent.ACTION_UP的case分支,不做任何操作。
            case MotionEvent.ACTION_UP:
                break;
            default:
                break;
        }

        mCanvas.drawPath(mPath, mPaint);//将路径绘制到画布上
        invalidate();//调用invalidate()方法刷新视图
        return true;//表示已经处理了触摸事件
    }
}

ScratchCardView2

java 复制代码
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
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.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class ScratchCardView2 extends View {

    //处理文字
    private String mText = "恭喜您中奖啦!!";//刮奖文本信息
    private Paint mTextPaint;//文字画笔
    private Rect mRect;//用于表示坐标系中的一块矩形区域

    //处理图层
    private Paint mForePaint;//画笔
    private Path mPath;//手指滑动的路径

    private Bitmap mBitmap;//加载资源文件
    private Canvas mForeCanvas;//前景图Canvas
    private Bitmap mForeBitmap;//前景图Bitmap

    //记录位置
    private int mLastX;
    private int mLastY;

    private volatile boolean isClear;//标志是否被清除


    public ScratchCardView2(Context context) {
        this(context, null);
    }

    public ScratchCardView2(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ScratchCardView2(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }


    private void init() {

        mRect = new Rect();//实例化矩形区域
        mPath = new Path();//实例化画笔的路径

        //文字画笔
        mTextPaint = new Paint();//初始化画笔
        mTextPaint.setAntiAlias(true);//消除锯齿边,给画笔设置平滑的属性
        mTextPaint.setColor(Color.BLACK);//文字颜色
        mTextPaint.setStyle(Paint.Style.FILL_AND_STROKE);//描边效果
        mTextPaint.setTextSize(50);//字体大小

        //用于测量文本边界的方法。这个方法接受四个参数:
        //mText 是要测量的文本字符串,0 是文本开始的位置,mText.length() 是文本的长度,mRect 是用于存储测量结果的矩形。
        mTextPaint.getTextBounds(mText, 0, mText.length(), mRect);

        //擦除画笔
        mForePaint = new Paint();
        mForePaint.setAntiAlias(true);  //消除锯齿边,给画笔设置平滑的属性
        mForePaint.setAlpha(0); //设置alpha不透明度,范围为0~255
        mForePaint.setStrokeCap(Paint.Cap.ROUND);//圆角效果
        mForePaint.setStrokeJoin(Paint.Join.ROUND);//设置圆角
        mForePaint.setStyle(Paint.Style.STROKE);//描边效果
        mForePaint.setStrokeWidth(50);//设置画笔宽度
        mForePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));//设置图层混合模式
        //在相交时利用源图像的透明度来改变目标图像的透明度和饱和度的,也就是当源图像透明度为0时,目标图像完全不显示

        //通过资源文件创建Bitmap对象  图片资源转化为Bitmap
        mBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.background);

        //创建一个和背景图大小一致的Bitmap对象作为装载画布
        mForeBitmap = Bitmap.createBitmap(mBitmap.getWidth(), mBitmap.getHeight(), Bitmap.Config.ARGB_8888);

        //双缓冲,装载画布
        //与Canvas进行绑定  //画涂层的画布,传一个Bitmap进去,所画的信息都存在Bitmap上
        mForeCanvas = new Canvas(mForeBitmap);

        //将前景图画到View的画布上
        mForeCanvas.drawBitmap(mBitmap, 0, 0, null);

    }


    @Override
    protected void onDraw(Canvas canvas) {
        //canvas.drawText()方法绘制文本,这个方法接收四个参数:
        // 要绘制的文本字符串 mText,
        // 文本的水平位置 mForeBitmap.getWidth() / 2 - mRect.width() / 2,
        // 文本的垂直位置 mForeBitmap.getHeight() / 2 + mRect.height() / 2,
        // 以及用于绘制文本的画笔对象 mTextPaint
        canvas.drawText(mText, mForeBitmap.getWidth() / 2 - mRect.width() / 2, mForeBitmap.getHeight() / 2 + mRect.height() / 2, mTextPaint);
        //如果 isClear 为 false,则使用 canvas.drawBitmap() 方法绘制位图。
        //方法接收三个参数:要绘制的位图对象 mForeBitmap,位图在画布上的水平位置 0,位图在画布上的垂直位置 0
        if (!isClear) {
            canvas.drawBitmap(mForeBitmap, 0, 0, null);
        }
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            //当用户按下屏幕时,会执行MotionEvent.ACTION_DOWN的case分支,
            // 记录下当前的坐标,并将路径(Path)移动到该点
            case MotionEvent.ACTION_DOWN:
                mLastX = (int) event.getX();
                mLastY = (int) event.getY();
                mPath.moveTo(mLastX, mLastY);
                break;

            //当用户在屏幕上滑动时,会执行MotionEvent.ACTION_MOVE的case分支,
            // 记录下当前的坐标,并将路径绘制到该点
            case MotionEvent.ACTION_MOVE:
                mLastX = (int) event.getX();
                mLastY = (int) event.getY();
                mPath.lineTo(mLastX, mLastY);
                break;

            //当用户松开屏幕时,会执行MotionEvent.ACTION_UP的case分支,不做任何操作。
            case MotionEvent.ACTION_UP:
                new Thread(mRunnable).start();
                break;
            default:
                break;
        }

        mForeCanvas.drawPath(mPath, mForePaint);//将路径绘制到画布上
        invalidate();//调用invalidate()方法刷新视图
        return true;//表示已经处理了触摸事件
    }


    /**
     * 开启子线程计算被擦除的像素点
     */
    private Runnable mRunnable = new Runnable() {
        int[] pixels;


        // 这段代码的作用是计算位图中透明像素的擦拭面积,
        // 并根据擦拭面积占总面积的比例判断是否达到清除条件,如果达到则刷新视图。
        @Override
        public void run() {

            //获取mForeBitmap的宽和高
            int w = mForeBitmap.getWidth();
            int h = mForeBitmap.getHeight();

            float wipeArea = 0;//擦拭面积
            float totalArea = w * h;//总面积


            pixels = new int[w * h];
            /**
             * pixels      接收位图颜色值的数组
             * offset      写入到pixels[]中的第一个像素索引值
             * stride      pixels[]中的行间距个数值(必须大于等于位图宽度)。可以为负数
             * x           从位图中读取的第一个像素的x坐标值。
             * y           从位图中读取的第一个像素的y坐标值
             * width      从每一行中读取的像素宽度
             * height    读取的行数
             */
            //获取位图像素数据存储到数组中,mForeBitmap 是一个位图对象,
            //pixels 是一个用于存储像素数据的数组。w 和 h 分别表示要获取的像素数据的宽度和高度。这个方法将指定区域的位图像素数据存储到 pixels 数组中。
            mForeBitmap.getPixels(pixels, 0, w, 0, 0, w, h);

            //使用两层循环遍历位图的每个像素
            for (int i = 0; i < w; i++) {
                for (int j = 0; j < h; j++) {
                    int index = i + j * w;
                    //判断像素的颜色值是否为0(即透明像素),如果是,则将擦拭面积wipeArea加1。
                    if (pixels[index] == 0) {
                        wipeArea++;
                    }
                }
            }

            //在循环结束后,通过判断擦拭面积和总面积是否大于0,计算出擦拭面积占总面积的百分比。
            //如果擦拭面积百分比大于50%,则将变量isClear置为true,表示达到了清除条件。
            //最后调用postInvalidate()方法刷新视图。

            if (wipeArea > 0 && totalArea > 0) {
                int percent = (int) (wipeArea * 100 / totalArea);
                if (percent > 50) {
                    isClear = true;
                    postInvalidate();
                }
            }

        }
    };
}

activity_main.xml

java 复制代码
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.example.guaguale.ScratchCardView2
        android:id="@+id/scratchCardView"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true" />
</RelativeLayout>

遮盖图

相关推荐
黄林晴2 小时前
如何判断手机是否是纯血鸿蒙系统
android
火柴就是我2 小时前
flutter 之真手势冲突处理
android·flutter
法的空间2 小时前
Flutter JsonToDart 支持 JsonSchema
android·flutter·ios
循环不息优化不止2 小时前
深入解析安卓 Handle 机制
android
恋猫de小郭3 小时前
Android 将强制应用使用主题图标,你怎么看?
android·前端·flutter
jctech3 小时前
这才是2025年的插件化!ComboLite 2.0:为Compose开发者带来极致“爽”感
android·开源
用户2018792831673 小时前
为何Handler的postDelayed不适合精准定时任务?
android
叽哥3 小时前
Kotlin学习第 8 课:Kotlin 进阶特性:简化代码与提升效率
android·java·kotlin
Cui晨3 小时前
Android RecyclerView展示List<View> Adapter的数据源使用View
android
氦客3 小时前
Android Doze低电耗休眠模式 与 WorkManager
android·suspend·休眠模式·workmanager·doze·低功耗模式·state_doze