欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android开发之自定义刮刮卡实现代码

程序员文章站 2023-11-17 22:06:58
关于刮刮卡的实现效果不需要做太多解释,特别是在电商app中,每当做活动的时候都会有它的身影存在,趁着美好周末,来实现下这个效果,也算是对零碎知识点的一个整合...

关于刮刮卡的实现效果不需要做太多解释,特别是在电商app中,每当做活动的时候都会有它的身影存在,趁着美好周末,来实现下这个效果,也算是对零碎知识点的一个整合。

Android开发之自定义刮刮卡实现代码

Android开发之自定义刮刮卡实现代码

所涉及的知识点:

1、自定义view的一些流程
2、双缓冲绘图机制
3、paint的绘图模式
4、触摸事件的一些流程
5、bitmap的相关知识

实现思路:

其实非常简单,首先我们需要确定所要绘图的区域,然后对这块区域进行多层的绘图(背景层,前景层),然后去监听触摸事件,把手指触摸的区域的前景层给消除即可。

首先我们先来实现一个简单版的:

步骤:

1、绘制图片作为背景层
2、绘制一张和背景层大小一致的灰色图层作为前景层
3、监听手指的触摸区域,把对应区域的前景层消除

1、首先绘制图片作为背景层,这个太简单了,我们把资源文件转成bitmap对象,然后利用ondraw(canvas canvas)里的canvas画出来即可。

//背景图
mbackgroundbitmap = bitmapfactory.decoderesource(getresources(), r.mipmap.background);
  @override
  protected void ondraw(canvas canvas) {
    //绘制背景层
    canvas.drawbitmap(mbackgroundbitmap, 0, 0, null);
  }

2、再来绘制一张和背景层大小一致的灰色图层作为前景层,这里我们需要用到绘图的双缓冲机制(这里的缓冲区指bitmap对象)。

双缓冲机制:先将要绘制的图形以对象的形式存放在内存中,作为绘制缓冲区,然后在这个对象上进行一系列的操作,然后再将其绘制到屏幕,避免过多的操作使得在绘制的过程中出现屏幕闪烁现象。

    //背景图
    mbackgroundbitmap = bitmapfactory.decoderesource(getresources(), r.mipmap.background);
    //创建一个和背景图大小一致的bitmap对象作为装载画布
    mforegroundbitmap = bitmap.createbitmap(mbackgroundbitmap.getwidth(), mbackgroundbitmap.getheight(), config.argb_8888);
    //与canvas进行绑定
    mcanvas = new canvas(mforegroundbitmap);
    //涂成灰色
    mcanvas.drawcolor(color.gray);
  @override
  protected void ondraw(canvas canvas) {
    //绘制背景层
    canvas.drawbitmap(mbackgroundbitmap, 0, 0, null);
    //绘制前景层
    canvas.drawbitmap(mforegroundbitmap, 0, 0, null);
  }

运行此时的代码,你会发现背景层已经和前景层融为一体(其实是2个图层,类似于ps里的图层叠加)

3、监听手指的触摸区域,把对应区域的前景层消除,这里我们需要用到一个技巧,在paint画笔api中给我们提供了一个porterduffxfermode,它有点想数学里的交并集,是用来控制两个图像之间的混合显示模式。

Android开发之自定义刮刮卡实现代码

在这里它会先去绘制dst层再绘制src层,那么对应着下来就是背景层(dst)和前景层(src),那么在这个图像我们怎么去选择模式呢?

这里我们需要取的是背景层的内容,也就是dst和 src的交集,然后内容区域显示dst,那么也就是dstin模式,来看下关于画笔paint的设置。

    mpaint = new paint();
    mpaint.setalpha(0);
    mpaint.setantialias(true);
    mpaint.setstyle(paint.style.stroke);
    mpaint.setstrokecap(paint.cap.round);
    mpaint.setstrokejoin(paint.join.round);
    mpaint.setstrokewidth(80);
    mpaint.setxfermode(new porterduffxfermode(porterduff.mode.dst_in));

然后我们重写ontouchevent在手指按下屏幕和滑动屏幕的时候利用path去记录我们想要擦除的路径即可。

  @override
  public boolean ontouchevent(motionevent event) {
    switch (event.getaction()) {
      case motionevent.action_down:
        mlastx = (int) event.getx();
        mlasty = (int) event.gety();
        mpath.moveto(mlastx, mlasty);
        break;
      case motionevent.action_move:
        mlastx = (int) event.getx();
        mlasty = (int) event.gety();
        mpath.lineto(mlastx, mlasty);
        break;
      case motionevent.action_up:
        break;
      default:
        break;
    }

    mcanvas.drawpath(mpath, mpaint);
    invalidate();

    return true;
  }

接下来我们来实现一个完整版的刮刮卡:

步骤:

1、绘制中奖信息作为背景层
2、绘制一张和中奖信息同等大小的刮奖封面作为前景层
3、监听手指的触摸区域,把对应区域的前景层消除
4、在消除大部分区域的时候,讲中奖信息完整展示

步骤1、2、3和前面大体一致,这里我就不详细说了,来讲一下需要注意的几个点:

1、在绘制中奖信息(文本)的时候,如何确定绘制的位置:

Android开发之自定义刮刮卡实现代码

关于文字位置的确定

首先我们需要知道任何的控件在android的布局中外层都是一个矩形的,a代表刮刮卡绘制区域,b代表中奖信息绘制区域,所以在这里我们绘制文本信息的起始点应该是a布局宽的一半减去b布局宽的一半,同理,高也应该是a布局高的一半减去b布局高的一半,这里我们把b布局,也就是文字控件的大小信息用一个rect对象来存储,而这里的a布局即为bitmap背景图的大小。

    //文字画笔
    mtextpaint = new paint();
    mtextpaint.setantialias(true);
    mtextpaint.setcolor(color.green);
    mtextpaint.setstyle(paint.style.fill);
    mtextpaint.settextsize(30);
    mtextpaint.gettextbounds(mtext, 0, mtext.length(), mrect);
@override
  protected void ondraw(canvas canvas) {
    canvas.drawtext(mtext, mbitmap.getwidth() / 2 - mrect.width() / 2, mbitmap.getheight() / 2 + mrect.height() / 2, mtextpaint);
  }

这样我们就绘制好了背景层的中奖信息,再来就是前景层,和上面一样我们利用资源文件转bitmap对象然后绑定canvas并绘制上刮刮卡图案

    //通过资源文件创建bitmap对象
    mbitmap = bitmapfactory.decoderesource(getresources(), r.mipmap.background);
    //新建同等大小的bitmap对象
    mforebitmap = bitmap.createbitmap(mbitmap.getwidth(), mbitmap.getheight(), bitmap.config.argb_8888);
    //双缓冲,装载画布
    mforecanvas = new canvas(mforebitmap);
    mforecanvas.drawbitmap(mbitmap, 0, 0, null);

剩下的利用path来记录用户手指触摸路径就是一样的了,这里我们额外来添加一个功能,使得当用户在刮刮卡上刮的区域范围超过50%后,自动消除刮刮卡前景层。

我们通过bitmap的getpixels方法就可以拿到bitmap的像素信息,由于这里涉及到了计算,这是个耗时操作,所以这里我们开启一个子线程来执行任务

private runnable mrunnable = new runnable() {
    int[] pixels;

    @override
    public void run() {

      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.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;
          if (pixels[index] == 0) {
            wipearea++;
          }
        }
      }


      if (wipearea > 0 && totalarea > 0) {
        int percent = (int) (wipearea * 100 / totalarea);
        if (percent > 50) {
          isclear = true;
          postinvalidate();
        }
      }

    }
  };

首先我们声明一个数组来记录像素点信息,数组的大小即为像素总数的大小也就是bitmap的宽高,然后我们在ontouchevent里的action_up中去计算被擦除的像素值,这里的for循环可能有的朋友会看的有点懵,没着急,我画一张图,你就能懂。

Android开发之自定义刮刮卡实现代码

bitmap像素点

我们第一层for循环i指的是bitmap的宽,第二次层for循环j指的是bitmap的高,那么index=i+jw,假设这个bitmap的像素大小是3*3,那么index的值就是0,3,6,1,4,7,2,5,8,是不是有感觉了?我们遍历像素点是按照纵向下来的,当pixels的值为0的时候,证明已经是被用户擦除掉的像素点。

当被擦除的区域超出50%,我们就在ondraw里去控制不让canvas绘制前景图即可。

  @override
  protected void ondraw(canvas canvas) {
    canvas.drawtext(mtext, mforebitmap.getwidth() / 2 - mrect.width() / 2, mforebitmap.getheight() / 2 + mrect.height() / 2, mtextpaint);
    if (!isclear) {
      canvas.drawbitmap(mforebitmap, 0, 0, null);
    }
  }

下面贴一下完整版的代码:

package com.lcw.view;

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;

/**
 * 刮刮卡(完善版)
 * create by: chenwei.li
 * date: 2017/7/22
 * time: 下午7:25
 */

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.green);
    mtextpaint.setstyle(paint.style.fill);
    mtextpaint.settextsize(30);
    mtextpaint.gettextbounds(mtext, 0, mtext.length(), mrect);

    //擦除画笔
    mforepaint = new paint();
    mforepaint.setantialias(true);
    mforepaint.setalpha(0);
    mforepaint.setstrokecap(paint.cap.round);
    mforepaint.setstrokejoin(paint.join.round);
    mforepaint.setstyle(paint.style.stroke);
    mforepaint.setstrokewidth(30);
    mforepaint.setxfermode(new porterduffxfermode(porterduff.mode.dst_in));

    //通过资源文件创建bitmap对象
    mbitmap = bitmapfactory.decoderesource(getresources(), r.mipmap.background);
    mforebitmap = bitmap.createbitmap(mbitmap.getwidth(), mbitmap.getheight(), bitmap.config.argb_8888);
    //双缓冲,装载画布
    mforecanvas = new canvas(mforebitmap);
    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);
    if (!isclear) {
      canvas.drawbitmap(mforebitmap, 0, 0, null);
    }
  }


  @override
  public boolean ontouchevent(motionevent event) {
    switch (event.getaction()) {
      case motionevent.action_down:
        mlastx = (int) event.getx();
        mlasty = (int) event.gety();
        mpath.moveto(mlastx, mlasty);
        break;
      case motionevent.action_move:
        mlastx = (int) event.getx();
        mlasty = (int) event.gety();
        mpath.lineto(mlastx, mlasty);
        break;
      case motionevent.action_up:
        new thread(mrunnable).start();
        break;
      default:
        break;
    }

    mforecanvas.drawpath(mpath, mforepaint);
    invalidate();
    return true;
  }


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

    @override
    public void run() {

      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.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;
          if (pixels[index] == 0) {
            wipearea++;
          }
        }
      }


      if (wipearea > 0 && totalarea > 0) {
        int percent = (int) (wipearea * 100 / totalarea);
        if (percent > 50) {
          isclear = true;
          postinvalidate();
        }
      }

    }
  };
}

源码下载:

这里附上源码地址:源码下载 https://github.com/lichenwei-dev/scratchcardview

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。