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

Android打造流畅九宫格抽奖活动效果

程序员文章站 2024-03-01 22:00:10
因为company项目中需要做九宫格抽奖活动,以前都没有做过类似的功能,虽然之前在浏览大神们的博客中,无意中也看到了好多关于抽奖的项目,但因为项目中没有需要,一直都没有点击...

因为company项目中需要做九宫格抽奖活动,以前都没有做过类似的功能,虽然之前在浏览大神们的博客中,无意中也看到了好多关于抽奖的项目,但因为项目中没有需要,一直都没有点击进去看。这次不去看估计不行。直到公司计划要做抽奖功能,才迫不得已上网查找demo

网上找了大半天,好不容易找到了几个demo,下载下来,解压缩包发现竟然里面空空如也,只有几张九宫格的图片,害我白白浪费了几个csdn积分。后面在eoe网站那发现了一个demo,于是好开心,下载下来后马上导入到工程中,运行看了效果,九宫格是出来了,但效果真不敢恭维,主要是运行不流畅。但我还是进去稍微看了一下demo,基本思路是这样的:定义好九宫格界面,然后开启子线程不断循环修改状态,再通过handler发送消息到主线程中修改界面(子线程不能直接修改界面)。

这个demo虽然功能上实现了,但不是我想要的效果,因为我这一关都不能通过,到了产品那边更加不用说了。那怎么办呢?

于是我想到了一个控件,叫做surfaceview,做游戏开发的同志们,应该对这个控件不陌生吧?首先介绍一下这个控件:
1.surfaceview继承于view,多用于游戏开发中
2.可以直接在子线程中运行(其他ui控件都必须在主线程中运行的)。
3.一般的ui控件自定义时都是重写ondraw方法,但在surfaceview中是通过surfaceholder获取canvas来绘制图形的

好了,来吧各位,先来看看效果图:

Android打造流畅九宫格抽奖活动效果

这样,下面我开始根据我的想法,把自定义九宫格的步骤说一下。

步骤:

1.计算各位方块的位置
2.绘制每个奖品的方块(主要让界面更加好看)
3.绘制奖品图
4.计算旋转方块的下一步位置
5.绘制旋转方块
6.监听点击开始按钮事件

主要核心技术:
surfaceview,surfaceholder

ok,有了基本步骤,接下来就是根据步骤一步一步来进行了。
在开始绘制九宫格之前,我们先重写onmeasure方法,主要是为了让九宫格成为一个正方形,这样看起来体验更好,基本代码如下:

public class lotteryview extends surfaceview{

  /**
   * holder
   */
  private surfaceholder mholder;


  private list<prize>prizes;
  private boolean flags;  //抽奖开关

  private int lottery=6;  //设置中奖号码

  private int current=2;  //抽奖开始的位置

  private int count=0;  //旋转次数累计

  private int countdown;  //倒计次数,快速旋转完成后,需要倒计多少次循环才停止

  //旋转抽奖的方块默认颜色
  private int transfer= 0xffff0000;

  private int max=50;  //最大旋转次数
  /** 
   * 重新测量
   */ 
  @override 
  protected void onmeasure(int widthmeasurespec, int heightmeasurespec) 
  { 
    super.onmeasure(widthmeasurespec, heightmeasurespec); 
    int width = math.min(getmeasuredwidth(), getmeasuredheight()); 
    setmeasureddimension(width, width); 
  }
}


surfaceview一般不是通过重写ondraw方法来绘制控件的,那么怎么获取到canvas呢?主要是通过surfaceholder监听callback事件来获取的
基本代码如下:

/**
   * holder
   */
  private surfaceholder mholder;
  public lotteryview(context context, attributeset attrs) {
    super(context, attrs);
    mholder = this.getholder();
    //监听callback
    mholder.addcallback(this);
  }

  public lotteryview(context context) {
    this(context,null);
  }


现在有了对象surfaceholder对象,我们就可以获取到canvas对象了,下面开始真正的绘制工作。

1.计算方块的具体显示位置
2.绘制每个奖品的方块

  //绘制背景
  private void drawbg(canvas canvas) {
    //清除已绘制的图形
    canvas.drawcolor(color.white, mode.clear);
    //获取控件的宽度,因为要绘制九宫格,所以要平局分成三列
    int width = getmeasuredwidth()/3;
    int x1=0;
    int y1=0;

    int x2=0;
    int y2=0;

    int len = (int) math.sqrt(prizes.size());

    for(int x=0;x<len*len;x++){

      prize prize = prizes.get(x);

      int index=x;
      x1=getpaddingleft()+width*(math.abs(index)%len);
      y1=getpaddingtop()+width*(index/len);

      x2=x1+width;
      y2=y1+width;
      rect rect=new rect(x1,y1,x2,y2);

      paint paint=new paint();
      //绘制方块
      canvas.drawrect(rect, paint);
    }
  }

解析:prizes 是一个集合,里面封装了奖品的一些基本信息,x1,y1,x2,y2分别是绘制奖品容器正方形的左上顶点和右下顶点,

Android打造流畅九宫格抽奖活动效果

通过观察发现,每一个方块位置都有一定的关系,即 x1=getpaddingleft()+width*(math.abs(index)%len);

y1=getpaddingtop()+width*(index/len); 
x2=x1+width; 
y2=y1+width; 

有了这些点的关系,就可以通过canvas.drawrect(rect, paint);绘制出方块了

3.绘制奖品图

  //绘制奖品
  private void drawprize(canvas canvas) {
    int width = getmeasuredwidth()/3;
    int x1=0;
    int y1=0;

    int x2=0;
    int y2=0;

    int len = (int) math.sqrt(prizes.size());

    for(int x=0;x<len*len;x++){

      prize prize = prizes.get(x);

      int index=x;
      x1=getpaddingleft()+width*(math.abs(index)%len);
      y1=getpaddingtop()+width*(index/len);

      x2=x1+width;
      y2=y1+width;
      rect rect=new rect(x1+width/6,y1+width/6,x2-width/6,y2-width/6);
      prize.setrect(rect);
      canvas.drawbitmap(prize.geticon(), null, rect, null);

    }
  }

通过了步骤1,2知道了方块的位置关系,就可以轻松的根据这些关系绘制出奖品来,rect rect=new rect(x1+width/6,y1+width/6,x2-width/6,y2-width/6);是让奖品比方块缩小一些,这样看起来会更自然一点。

4.计算旋转方块的下一步位置

  //下一步
  public int next(int position,int len){
    int current=position;
    if(current+1<len){
      return ++current;
    }

    if((current+1)%len==0&&current<len*len-1){
      return current+=len;
    }

    if(current%len==0){
      return current-=len;
    }

    if(current<len*len){
      return --current;
    }

    return current;
  }

position是当前旋转方块的位置,len是3

5.绘制旋转方块

  //绘制旋转的方块
  private void drawtransfer(canvas canvas) {
    int width = getmeasuredwidth()/3;
    int x1;
    int y1;

    int x2;
    int y2;
    int len = (int) math.sqrt(prizes.size());
    //得到下一步方块的位置
    current=next(current, len);
    x1=getpaddingleft()+width*(math.abs(current)%len);
    y1=getpaddingtop()+width*((current)/len);

    x2=x1+width;
    y2=y1+width;

    rect rect=new rect(x1,y1,x2,y2);
    paint paint=new paint();
    paint.setcolor(transfer);
    canvas.drawrect(rect, paint);
  }

6.监听点击开始按钮事件

  private ontransferwinninglistener listener;

  public void setontransferwinninglistener(ontransferwinninglistener listener){
    this.listener=listener;
  }

  public interface ontransferwinninglistener{
    /**
     * 中奖回调
     * @param position
     */
    void onwinning(int position);
  }
    @override
  public boolean ontouchevent(motionevent event) {
    handletouch(event);
    return super.ontouchevent(event);
  }
  /**
   * 触摸
   * @param event
   */
  public void handletouch(motionevent event) {


    point touchpoint=new point((int)event.getx()-getleft(),(int)event.gety());
    switch(event.getaction()){
    case motionevent.action_down:
      prize prize = prizes.get(math.round(prizes.size())/2);
      if(prize.isclick(touchpoint)){
        if(!flags){
          setstartflags(true);
          prize.click();
        }
      }
      break ;
    default:
      break ;
    }
  }

//控制旋转
  private void controllertransfer() {
    if(count>max){
      countdown++;
      systemclock.sleep(count*5);
    }else{
      systemclock.sleep(count*2);
    }

    count++;
    if(countdown>2){
      if(lottery==current){
        countdown=0;
        count=0;
        setstartflags(false);

        if(listener!=null){
          //切换到主线程中运行
          post(new runnable() {

            @override
            public void run() {
              listener.onwinning(current);
            }
          });

        }
      }
    }
  }

至此,基本的自定义工作已经差不多了,使用demo如下:

<?xml version="1.0" encoding="utf-8"?>
<relativelayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="vertical" >

  <com.example.test.lotteryview
    android:id="@+id/nl"
     android:layout_width="match_parent"
    android:layout_height="match_parent"
    />


</relativelayout>

public class homeactivity extends activity {

  lotteryview nl;
  @override
  protected void oncreate(bundle savedinstancestate) {
    super.oncreate(savedinstancestate);

    setcontentview(r.layout.act_home);

    nl=(lotteryview) findviewbyid(r.id.nl);

    int[]prizesicon={r.drawable.danfan,r.drawable.meizi,r.drawable.iphone,r.drawable.f015,r.drawable.arrow,r.drawable.f040,r.drawable.ipad,r.drawable.spree_icon,r.drawable.spree_success_icon};
    final list<prize>prizes=new arraylist<prize>();
    for(int x=0;x<9;x++){
      prize lottery=new prize();
      lottery.setid(x+1);
      lottery.setname("lottery"+(x+1));
      bitmap bitmap = bitmapfactory.decoderesource(getresources(), prizesicon[x]);
      lottery.seticon(bitmap);
      if((x+1)%2==0){
        lottery.setbgcolor(0xff4fccee);
      }else if(x==4){
        lottery.setbgcolor(0xffffffff);
      }else{
        lottery.setbgcolor(0xff00ff34);
      }

      prizes.add(lottery);
    }
    nl.setprizes(prizes);
    nl.setontransferwinninglistener(new ontransferwinninglistener() {

      @override
      public void onwinning(int position) {
        toast.maketext(getapplicationcontext(), prizes.get(position).getname(), toast.length_short).show();
      }
    });
  }
}

运行效果非常流畅

lotteryview整体demo:

package com.example.test;

import java.util.list;
import java.util.random;
import java.util.concurrent.executorservice;
import java.util.concurrent.executors;

import android.content.context;
import android.graphics.canvas;
import android.graphics.color;
import android.graphics.paint;
import android.graphics.point;
import android.graphics.porterduff;
import android.graphics.porterduff.mode;
import android.graphics.rect;
import android.os.systemclock;
import android.util.attributeset;
import android.view.motionevent;
import android.view.surfaceholder;
import android.view.surfaceholder.callback;
import android.view.surfaceview;


public class lotteryview extends surfaceview implements callback{

 /**
 * holder
 */
 private surfaceholder mholder;


 private list<prize>prizes;
 private boolean flags;

 private int lottery=6; //设置中奖号码

 private int current=2; //抽奖开始的位置

 private int count=0; //旋转次数累计

 private int countdown; //倒计次数,快速旋转完成后,需要倒计多少次循环才停止


 private int transfer= 0xffff0000;

 private int max=50; //最大旋转次数

 private ontransferwinninglistener listener;

 public void setontransferwinninglistener(ontransferwinninglistener listener){
 this.listener=listener;
 }

 public interface ontransferwinninglistener{
 /**
  * 中奖回调
  * @param position
  */
 void onwinning(int position);
 }


 /**
 * 设置中奖号码
 * @param lottery
 */
 public void setlottery(int lottery) {
 if(prizes!=null&&math.round(prizes.size()/2)==0){
  throw new runtimeexception("开始抽奖按钮不能设置为中奖位置!");
 }
 this.lottery = lottery;
 }

 /**
 * 设置转盘颜色
 * @param transfer
 */
 public void settransfer(int transfer) {
 this.transfer = transfer;
 }

 /**
 * 设置奖品集合
 * @param prizes
 */
 public void setprizes(list<prize>prizes){
 this.prizes=prizes;
 }


 @override
 public boolean ontouchevent(motionevent event) {
 handletouch(event);
 return super.ontouchevent(event);
 }

 /**
 * 触摸
 * @param event
 */
 public void handletouch(motionevent event) {


 point touchpoint=new point((int)event.getx()-getleft(),(int)event.gety());
 switch(event.getaction()){
 case motionevent.action_down:
  prize prize = prizes.get(math.round(prizes.size())/2);
  if(prize.isclick(touchpoint)){
  if(!flags){
   setstartflags(true);
   prize.click();
  }
  }
  break ;
 default:
  break ;
 }
 }
 private class surfacerunnable implements runnable{
 @override
 public void run() {
  while(flags){
  canvas canvas=null;
  try {
   canvas = mholder.lockcanvas();

   drawbg(canvas);

   drawtransfer(canvas);

   drawprize(canvas);

   controllertransfer();
  } catch (exception e) {
   e.printstacktrace();
  }finally{
   //涓轰簡璁╂瘡娆$粯鍒跺浘褰㈡椂鑳藉椤哄埄杩涜锛屾渶濂藉皢瑙i攣鏀惧埌寮傚父涓繘琛屽鐞嗭紝涔熷氨鏄锛屽鏋渃anvas涓嶄负绌猴紝閮藉皢鍏跺叧闂紝璁╀笅涓�娆″惊鐜兘澶熼『鍒╄繘琛岀粯鍒�
   if(canvas!=null)
   mholder.unlockcanvasandpost(canvas); 
  }
  }
 }
 }

 //绘制背景
 private void drawbg(canvas canvas) {
 canvas.drawcolor(color.white, mode.clear);
 int width = getmeasuredwidth()/3;
 int x1=0;
 int y1=0;

 int x2=0;
 int y2=0;

 int len = (int) math.sqrt(prizes.size());

 for(int x=0;x<len*len;x++){

  prize prize = prizes.get(x);

  int index=x;
  x1=getpaddingleft()+width*(math.abs(index)%len);
  y1=getpaddingtop()+width*(index/len);

  x2=x1+width;
  y2=y1+width;
  rect rect=new rect(x1,y1,x2,y2);

  paint paint=new paint();
  paint.setcolor(prize.getbgcolor());
  canvas.drawrect(rect, paint);
 }
 }

 //绘制旋转的方块
 private void drawtransfer(canvas canvas) {
 int width = getmeasuredwidth()/3;
 int x1;
 int y1;

 int x2;
 int y2;
 int len = (int) math.sqrt(prizes.size());
 current=next(current, len);
 x1=getpaddingleft()+width*(math.abs(current)%len);
 y1=getpaddingtop()+width*((current)/len);

 x2=x1+width;
 y2=y1+width;

 rect rect=new rect(x1,y1,x2,y2);
 paint paint=new paint();
 paint.setcolor(transfer);
 canvas.drawrect(rect, paint);
 }

 //控制旋转
 private void controllertransfer() {
 if(count>max){
  countdown++;
  systemclock.sleep(count*5);
 }else{
  systemclock.sleep(count*2);
 }

 count++;
 if(countdown>2){
  if(lottery==current){
  countdown=0;
  count=0;
  setstartflags(false);

  if(listener!=null){
   //切换到主线程中运行
   post(new runnable() {

   @override
   public void run() {
    listener.onwinning(current);
   }
   });

  }
  }
 }
 }

 public void setstartflags(boolean flags){
 this.flags=flags;
 }

 //绘制奖品
 private void drawprize(canvas canvas) {
 int width = getmeasuredwidth()/3;
 int x1=0;
 int y1=0;

 int x2=0;
 int y2=0;

 int len = (int) math.sqrt(prizes.size());

 for(int x=0;x<len*len;x++){

  prize prize = prizes.get(x);

  int index=x;
  x1=getpaddingleft()+width*(math.abs(index)%len);
  y1=getpaddingtop()+width*(index/len);

  x2=x1+width;
  y2=y1+width;
  rect rect=new rect(x1+width/6,y1+width/6,x2-width/6,y2-width/6);
  prize.setrect(rect);
  canvas.drawbitmap(prize.geticon(), null, rect, null);

 }
 }


 public void start() {
 setlottery(getrandom());
 executorservice service = executors.newcachedthreadpool();
 service.execute(new surfacerunnable());
 }

 //获取随机中奖数,实际开发中一般中奖号码是服务器告诉我们的
 private int getrandom(){
 random r=new random();
 int nextint =r.nextint(prizes.size());
 if(nextint%(math.round(prizes.size()/2))==0){
  //随机号码等于中间开始位置,需要继续摇随机号
  return getrandom();
 }
 return nextint;
 }

 //下一步
 public int next(int position,int len){
 int current=position;
 if(current+1<len){
  return ++current;
 }

 if((current+1)%len==0&&current<len*len-1){
  return current+=len;
 }

 if(current%len==0){
  return current-=len;
 }

 if(current<len*len){
  return --current;
 }

 return current;
 }


 public lotteryview(context context, attributeset attrs) {
 super(context, attrs);
 mholder = this.getholder();
 mholder.addcallback(this);
 }

 public lotteryview(context context) {
 this(context,null);
 }

 @override
 public void surfacechanged(surfaceholder holder, int format, int width,
  int height) {
 }

 @override
 public void surfacecreated(surfaceholder holder) {
 canvas canvas=null;
 try {
  canvas = mholder.lockcanvas();
  drawbg(canvas);
  drawprize(canvas);

  prize prize = prizes.get(math.round(prizes.size()/2));
  prize.setlistener(new prize.onclicklistener() {

  @override
  public void onclick() {
   start();
  }
  });
 } catch (exception e) {
  e.printstacktrace();
 }finally{
  if(canvas!=null)
  mholder.unlockcanvasandpost(canvas); 
 }
 }

 @override
 public void surfacedestroyed(surfaceholder holder) {
 setstartflags(false);
 }

 /** 
 * 重新测量
 */ 
 @override 
 protected void onmeasure(int widthmeasurespec, int heightmeasurespec) 
 { 
 super.onmeasure(widthmeasurespec, heightmeasurespec); 
 int width = math.min(getmeasuredwidth(), getmeasuredheight()); 
 setmeasureddimension(width, width); 
 }
}

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