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

Android使用surfaceView自定义抽奖大转盘

程序员文章站 2024-02-28 09:51:34
使用surfaceview自定义抽奖大转盘 话不多说,先上效果图 完整代码地址欢迎start 实现思路以及过程 1、首先了解surfaceview的基本用法,它...

使用surfaceview自定义抽奖大转盘

话不多说,先上效果图

Android使用surfaceView自定义抽奖大转盘

完整代码地址欢迎start

实现思路以及过程

1、首先了解surfaceview的基本用法,它跟一般的view不太一样,采用的双缓存机制,可以在子线程中绘制view,不会因为绘制耗时而失去流畅性,这也是选择使用surfaceview去自定义这个抽奖大转盘的原因,毕竟绘制这个转盘的盘块,奖项的图片和文字以及转动都是靠绘制出来的,是一个比较耗时的绘制过程。

2、使用surfaceview的一般模板样式

一般会用到的成员变量

private surfaceholder msurfaceholder;
private canvas    mcanvas;

初始化常亮

public surfaceviewtemplate(context context,attributeset attrs) {
  super(context, attrs);
  //初始化
  msurfaceholder = getholder();
  msurfaceholder.addcallback(this);
  //设置可获得焦点
  setfocusable(true);
  setfocusableintouchmode(true);
  //这是常亮
  setkeepscreenon(true);
}

给surfaceview添加callback实现其中三个方法

 @override
public void surfacecreated(surfaceholder surfaceholder) {
  //surface创建的时候
  mthread = new thread(this);
  //创建的时候就开启线程
  isrunning = true;
  mthread.start();
}

@override
public void surfacechanged(surfaceholder surfaceholder, int i, int i1, int i2) {
  //变化的时候
}

@override
public void surfacedestroyed(surfaceholder surfaceholder) {
  //销毁的时候 关闭线程
  isrunning = false;
}

在子线程中定义一个死循环不断的进行绘制

 @override
public void run() {
  //在子线程中不断的绘制
  while (isrunning) {
    draw();
  }
}

private void draw() {
  try {
    mcanvas = msurfaceholder.lockcanvas();
    if (null != mcanvas) {
  //避免执行到这里的时候程序已经退出 surfaceview已经销毁那么获取到canvas为null
    }
  } catch (exception e) {
    //异常可以不必处理
  } finally {
    //一定要释放canvas避免泄露
    msurfaceholder.unlockcanvasandpost(mcanvas);
  }
}

3、了解了surfaceview的基本用法之后,接下来实现抽奖转盘

首先测量整个view的范围,设置成正方形

@override
  protected void onmeasure(int widthmeasurespec, int heightmeasurespec) {
    super.onmeasure(widthmeasurespec, heightmeasurespec);
    //直接控制span为正方形
    int width = math.min(getmeasuredwidth(), getmeasuredheight());
    mpadding = getpaddingleft();
    //直径
    mradius = width - mpadding * 2;
    //设置中心点
    mcenter = width / 2;
    //设置成正方形
    setmeasureddimension(width, width);
  }

在surfaceview创建的时候初始化画笔矩形范围等,见代码

public void surfacecreated(surfaceholder surfaceholder) {
    //初始化绘制span的画笔
    mspanpaint = new paint();
    mspanpaint.setantialias(true);
    mspanpaint.setdither(true);
    //初始化绘制文本的画笔
    mtextpaint = new paint();
    mtextpaint.settextsize(mtextsize);
    mtextpaint.setcolor(0xffa58453);
    //绘制圆环的画笔
    mcirclepaint = new paint();
    mcirclepaint.setantialias(true);
    mcirclepaint.setcolor(0xffdfc89c);
    //初始化span的范围
    mrectrange = new rectf(mpadding, mpadding, mpadding + mradius, mpadding + mradius);
    mrectcirclerange = new rectf(mpadding * 3 / 2, mpadding * 3 / 2, getmeasuredwidth() - mpadding * 3 / 2, getmeasuredwidth() - mpadding * 3 / 2);
    //初始化bitmap
    mimgiconbitmap = new bitmap[mspancount];
    //将奖项的icon存储为bitmap
    for (int i = 0; i < mspancount; i++) {
      mimgiconbitmap[i] = bitmapfactory.decoderesource(getresources(), mprizeicon[i]);
    }

    //surface创建的时候
    mthread = new thread(this);
    //创建的时候就开启线程
    isrunning = true;
    mthread.start();
  }

接下来就是在开启的子线程中进行绘制

 @override
  public void run() {
    //在子线程中不断的绘制
    while (isrunning) {
      //保证绘制不低于50毫秒 优化性能
      long start = systemclock.currentthreadtimemillis();
      draw();
      long end = systemclock.currentthreadtimemillis();
      if ((end - start) < 50) {
        //休眠到50毫秒
        systemclock.sleep(50 - (end - start));
      }
    }
  }

重点就在draw()方法中了下面就实现draw方法:

注意:避免mcanvas带来的内存泄漏

 try {
    mcanvas = msurfaceholder.lockcanvas();
    if (null != mcanvas) {
      //避免执行到这里的时候程序已经退出 surfaceview已经销毁那么获取到canvas为null
      //绘制背景
      drawbg();
      //绘制圆环
      mcanvas.drawcircle(mcenter, mcenter, mradius / 2 + mpadding / 20, mcirclepaint);
      drawspan();
    }
  } catch (exception e) {
    //异常可以不必处理
  } finally {
    //一定要释放canvas避免泄露
    msurfaceholder.unlockcanvasandpost(mcanvas);
  }

画背景:

//绘制背景
  private void drawbg() {
    //背景设置为白色
    mcanvas.drawcolor(0xffffffff);
    mcanvas.drawbitmap(mspanbackground, null, new rectf(mpadding / 2, mpadding / 2, getmeasuredwidth() - mpadding / 2, getmeasuredheight() - mpadding / 2), mspanpaint);
  }

参数解释:

  mspanbackground背景图片
  new rectf(mpadding / 2, mpadding / 2, getmeasuredwidth() - mpadding / 2, getmeasuredheight() - mpadding / 2)
  //限制背景在一个矩形范围之类 

绘制内圆环

mcanvas.drawcircle(mcenter, mcenter, mradius / 2 + mpadding / 20, mcirclepaint); 

绘制中间八个盘块

//定义一个变量临时记录开始转动的角度
float tempangle = mstartspanangle;
//每个盘块所占的角度 circle_angle = 360
float sweepangle = circle_angle / mspancount;
//循环绘制八个板块
  for (int i = 0; i < mspancount; i++) {
    //设置每个盘块画笔的颜色
    mspanpaint.setcolor(mspancolor[i]);
    //绘制扇形盘块,第四个参数为true就是扇形否则就是弧形
    mcanvas.drawarc(mrectcirclerange, tempangle, sweepangle, true, mspanpaint);
    //绘制文字
    drawtext(tempangle, sweepangle, mprizename[i]);
    //绘制奖项icon
    drawprizeicon(tempangle, mimgiconbitmap[i]);
      //改变角度
      tempangle += sweepangle;
    }

绘制文字

文字绘制成圆环形状,根据path绘制文字

private void drawtext(float tempangle, float sweepangle, string text) {
  //绘制有弧度的文字 根据path绘制文字的路径
  path path = new path();
  path.addarc(mrectrange, tempangle, sweepangle);
  //让文字水平居中 那绘制文字的起点位子就是 弧度的一半 - 文字的一半
  float textwidth = mtextpaint.measuretext(text);
  float hoval = (float) ((mradius * math.pi / mspancount / 2) - (textwidth / 2));

  float voval = mradius / 15;//竖直偏移量可以自定义

  mcanvas.drawtextonpath(text, path, hoval, voval, mtextpaint); //第三个四个参数是竖直和水平偏移量
}

绘制盘块中的奖品icon图片

private void drawprizeicon(float tempangle, bitmap bitmap) {
  //图片的大小设置成直径的1/8
  int iconwidth = mradius / 20;
  //根据角度计算icon中心点
  //角度计算 1度 == math.pi / 180
  double angle = (tempangle + circle_angle / mspancount / 2) * math.pi / 180;
  //根据三角函数,计算中心点(x,y)
  int x = (int) (mcenter + mradius / 4 * math.cos(angle));
  int y = (int) (mcenter + mradius / 4 * math.sin(angle));
  //定义一个矩形 限制icon位置
  rectf rectf = new rectf(x - iconwidth, y - iconwidth, x + iconwidth, y + iconwidth);
  mcanvas.drawbitmap(bitmap, null, rectf, null);
}

大致的绘制基本完成,重点就是通过改变开始转动的角度让转盘转动起来。

 mstartspanangle += mspeed;//mspeed的数值控制转动的速度
   //声明的一个结束标志
  if (isspanend) {
    mspeed -= 1;
  }
  if (mspeed <= 0) {
    //停止旋转了
    mspeed = 0;
    isspanend = false;
    //定义一个回调,监控转盘停止转动
   mspanrolllistener.onspanrolllistener(mspeed);
  }

定义一个方法, 启动转盘

//抽奖转盘重点就在这里,根据自己传入的index控制抽到的奖品
public void luckystart(int index) {
  //根据index控制停留的位置 angle 是每个奖品所占的角度范围
  float angle = circle_angle / mspancount;
  //计算指针停留在某个index下的角度范围half_circle_angle=180度
  float from = half_circle_angle - (index - 1) * angle;
  float end = from + angle;

  //设置需要停下来的时候转动的距离 保证每次不停留的某个index下的同一个位置
  float targetfrom = 4 * circle_angle + from;
  float targetend = 4 * circle_angle + end;//最终停下来的位置在from-end之间,4 * circle_angle 自定义要多转几圈

  //计算要停留下来的时候速度的范围,这里注意:涉及到等差数列的公式,因为涉及到让转盘停止转动是使mspeed-=1;所以它是从 vfrom--0等差递减的一个过程,所以可以算出来vfrom,同理计算出vend
  float vfrom = (float) ((math.sqrt(1 + 8 * targetfrom) - 1) / 2);
  float vend = (float) ((math.sqrt(1 + 8 * targetend) - 1) / 2);
  //在点击开始转动的时候 传递进来的index值就已经决定停留在那一项上面了
  mspeed = vfrom + math.random() * (vend - vfrom);
  isspanend = false;
}

停止转动

 public void luckstop() {
  //在停止转盘的时候强制吧开始角度赋值为0 因为控制停留指定位置的角度计算是根据开始角度为0计算的
  mstartspanangle = 0;
  isspanend = true;
}

具体实现牵涉到一些数学知识,可能讲述不太清楚,不过上代码就比较好了,直接看代码会更加清晰,代码中注释很详细,防止以后自己再回头看的时候忘记。欢迎访问github地址,查看完整代码,可以根据自己的需求去修改,顺便学习一下自定义view了解一下surfaceview的用法。

地址:完整代码地址欢迎start,如有发现问题请多多指点互相学习交流。

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