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


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


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


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

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

这样,下面我开始根据我的想法,把自定义九宫格的步骤说一下。
步骤:
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();//监听CallBackmHolder.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分别是绘制奖品容器正方形的左上顶点和右下顶点,

通过观察发现,每一个方块位置都有一定的关系,即 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);}@Overridepublic 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() {@Overridepublic 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;@Overrideprotected 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() {@Overridepublic 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;}@Overridepublic 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{@Overridepublic 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() {@Overridepublic 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);}@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width,int height) {}@Overridepublic 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() {@Overridepublic void onClick() {start();}});} catch (Exception e) {e.printStackTrace();}finally{if(canvas!=null)mHolder.unlockCanvasAndPost(canvas);    }}@Overridepublic 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);  }
}

下载地址

打造流畅九宫格抽奖活动相关推荐

  1. Android打造流畅九宫格抽奖

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

  2. 原生js实现运维小姐姐的九宫格抽奖活动、心跳快了

    背景 带着我的小耳机坐在电脑前正YY着自己的女神(哦不,像我这么认真,负责,肯定是在狂飞乱舞的码着代码) 肩膀突然被人拍了一下,吓得我第一反应是Alt+Tab键切换了屏幕,屏幕正前方是显示着一行行代码 ...

  3. android 百度轻量输入法,百度发布Android手机输入法 打造流畅体验

    日前,百度无线宣布正式推出Android版本手机输入法,凭借其拥有独家专利的多样输入.点划结合等创新技术,秉承百度一贯的"简单可依赖"的产品设计原则,将为Android用户带来最便 ...

  4. uni-app 九宫格抽奖活动

    一.效果图 二.代码 <template><view class="container"><view class="container_ci ...

  5. 微信小程序九宫格抽奖活动制作

    首先安装npm包 npm install @lucky-canvas/mini@latest 然后构建 npm 微信开发者工具 -> 找到左上角点击 -> 工具 点击 -> 构建 n ...

  6. 基于产生式的动物识别专家系统_钇鑫智通打造“极致流畅人脸识别闸机”

    现在,人脸识别通道闸机越来越普及,但是由于技术的差异,导致人脸识别闸机的性能也有差异,人脸识别闸机其核心在人脸识别设备上,人脸机的配置,决定人脸识别的速度和准确率.人脸识别系统成功关键在于是否拥有尖端 ...

  7. 华为应用市场 AppGallery Connect 研习社·直播:百万开发者打造优质应用的奥秘

    如何快速构建移动应用后端? 不升级应用,如何修改应用的行为和外观? 如何快速发现.定位并解决应用崩溃? 2月4日19点,华为应用市场AppGallery Connect专家团队将在线为大家带来丰富干货 ...

  8. 华为应用市场AppGallery Connect研习社·直播:百万开发者打造优质应用的奥秘

    如何快速构建移动应用后端? 不升级应用,如何修改应用的行为和外观? 如何快速发现.定位并解决应用崩溃? 2月4日19点,华为应用市场AppGallery Connect专家团队将在线为大家带来丰富干货 ...

  9. 一本讲 Android 流畅性的书,应该有什么内容?

    写在前面 最近读了一本新书:<打造流畅的 Android App>,京东链接:https://item.jd.com/10035215362170.html .因为书名所以买了这本书,读完 ...

最新文章

  1. lsa五类_OSPF外部路由4类5类LSA
  2. 黑白青春-纪念那年我的秋天
  3. python查询mysql表名字动态日期_Python之路day11作业-MySQL表查询
  4. php + mssql乱码
  5. Python 2.x 与Python 3.x的差别总结
  6. GUI编程与CLI编程
  7. IBASE write buffer
  8. linux视音频解码教程,音视频编解码:NVIDIA Jetson Linux Multimedia API(总结)
  9. VMware卸载有残留,再安装时报错提示MSI Failed
  10. 码农不得不做的P图辨识能力
  11. STM32学习心得三十一:485通信原理及实验
  12. 常见的协议号和端口号
  13. 查看电脑连接的wifi密码
  14. 非空子集的生成(二进制法)
  15. [位压] 高精度加法
  16. 让你的工作事半功倍的语音转文字转换器
  17. 微信公共服务平台开发(.Net 的实现)2-------获得ACCESSTOKEN
  18. 安服仔10大灵魂拷问
  19. 数学建模之SPSS的使用
  20. IPHONE 设置 铃声 方法

热门文章

  1. 用计算机解决问题时 为什么要用计算思维,在亲历计算机解决问题的全过程中发展计算思维...
  2. 怎么从微信群聊中引流?如何从群聊进行引流?微信群怎么引流
  3. 抖音广告营销被处罚限流,减少作品推荐该怎么办丨国仁网络资讯
  4. 使用java在后台将数据导出为excel文件
  5. c语言错误c2198,这段哪里错了。。。
  6. 线程安全的ArrayList——CopyOnWriteArrayList
  7. SSH介绍与神器Tabby
  8. 图文详解 HBase 的读写流程
  9. 硬核又实用,4款人气爆棚的电脑软件,每一款都堪称黑科技
  10. android创建以及使用SDcard镜像文件