当前位置: 移动技术网 > IT编程>移动开发>Android > Android开发之自定义刮刮卡实现代码

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

2019年07月24日  | 移动技术网IT编程  | 我要评论

魔染梦土,索命dv,营销软件918站

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



所涉及的知识点:

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,它有点想数学里的交并集,是用来控制两个图像之间的混合显示模式。

在这里它会先去绘制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的布局中外层都是一个矩形的,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循环可能有的朋友会看的有点懵,没着急,我画一张图,你就能懂。

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

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

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网