详解与重构hyman《Android SurfaceView实战 打造抽奖转盘》
详解与重构hyman《Android SurfaceView实战 打造抽奖转盘》
作者:邵励治
一、概述——关于SurfaceView您不得不知道的二三事
1.SurfaceView是干什么的?
答:SurfaceView就像一块画板,我们可以在上面即时的绘画。
2.SurfaceView的优势?
答:相信您注意到了上文中“即时”二字被重点标出。如果您写过自定义View的话,会发现想让画面动起来需要调用如下两个方法让界面重绘:
- invalidate:于UI线程中使用
- postinvalidate:于非UI线程中使用
当上面两个方法使用太频繁的时候,我们的Android系统的UI界面就有可能阻塞。而您肯定不想让您的UI界面阻塞————毕竟卡死五秒就崩出去了(而且我一秒都不想卡)
而我们伟大的SurfaceView,可以做到随着数据被更新即时的改变UI界面,而不需要调用上面两个方法,因而也就不会造成UI阻塞。
(注:你们没发现上面的这些话其实什么都没说吗?原因是我并不知道为什么不会造成UI阻塞,但我也不想深究,嘻嘻)
3.SurfaceView的周边知识
- “深度优先遍历文章学习法”————我就是用我自创的这个学习法遍历Hyman这篇文章的。
优点:
(1) 可以了解作者所说的全部内容
(2) 可以迅速构建起一个知识体系
缺点:
(1) 效率低下(老板需要你迅速写出一个抽奖转盘用来救他落水的女儿,那你用我这招肯定不行)
(2) 民科感及其、超级、很浓重————主要赖我起的这名儿 - Android中UI框架的基本概念
您听说过DecorView吗?您了解PhoneWindow吗?您关心Activity#setContentView是如何工作的吗?
如果您感兴趣,请到文末列出的三十多篇参考文献中寻找您想要的内容。 - Android自定义View
如果您是一个Android初学者,那您一定不应该错过这里的内容,去学习一下吧。也许学习完了您就能感受到了SurfaceView的厉害之处了。(正如我写了三叠子数学作业纸的电磁学题后才能感受到麦克斯韦方程的美一样。)
二、新建Java文件——LotteryTurntable(中文意思:抽奖转盘)
1. LotterTurntable类继承关系
extends:SurfaceView
implements SurfaceHolder.Callback
2. LotterTurntable的构造方法
原因:
(1) 若想在XML布局文件中引用我们的自定义View——LotteryTurntable的话,LotteryTurntable至少需要两个参数的构造方法。
(2) 为了实现无论LotteryTurntable的哪一个构造方法被调用都会执行同样操作的伟大梦想
3. 重写(Override)SurfaceHolder.Callback接口中的三个抽象方法
原因:
(1) 您就是想不重写(Override)接口的抽象方法也不行呀
(2) 为什么不行的原因在《Java编程思想》接口定义的章节中
三、SurfaceView起手式
1.何为SurfaceView的起手式?
答:如同下象棋讲究当头炮,打篮球讲究三威胁。我们在使用SurfaceView的时候也需要起手式————即代码上的起手式。
虽然我认为这种类似于“起手式”的形式在代码上运用属于一种重复劳作————既然每个人写的时候都需要写这些代码,那么为什么不把这些代码封装起来呢?
但是像这么做无异于获得了更好的灵活性————就像我们公司最灵活的程序员编程只用0、1两个按键。
2.起手式的具体内容
字段的声明:
构造方法:获取surfaceHolder,并添加回调结构
surfaceCreated方法:打开绘图开关,开启线程
BUG提示:
我上次把在surfaceCreated中做初始化工作的代码写在了上述两行代码的下面,结果我的程序成了六脉神剑————时灵时不灵。
原因是,当上述两行代码执行后,子线程就开始工作,程序就成为了多线程的————一个线程负责初始化工作(也就是主线程),一个线程负责绘图(也就是子线程),它们是同时进行的。
那么,是先完成初始化工作,还是先进行绘图操作可就说不准了。当先完成初始化工作的时候,程序就跟正常的表现一样。但当先完成绘图操作时,程序就会因为传入未初始化的实例而崩溃————程序成了定时炸弹,不知道什么时候会炸。
所以,我们一定要先让主线程跑完————执行完初始化操作之后,再执行开启子线程的代码drawingSwitch=true;和thread.start();。这样,绘图时就一定不会因为传入未初始化的实例而让程序崩溃了。
历史遗留问题:
值得一提的是,我当时是使用drawBitmap(bitmap,null,rect,null);这条语句时,bitmap未初始化造成的程序崩溃。但是,Android Studio却没有弹出任何的错误信息,这就让我感到十分难受,无从Debug。最后我用的单元测试大法————一个一个变量的Sytem.out.println + 注释,才定位到原来是bitmap=null造成的问题。我目前还不知道这是什么原因造成的不提示错误信息。surfaceDestroyed方法:关闭绘图开关(并不关闭线程,浅显原因是Thread.stop方法由于安全原因被禁用了,深刻原因请自行查询)
子线程的声明与代码体(很关键,因为您需要在此进行使用canvas绘画)
3.关于起手式的周边问题
Surface是什么?
答:Surface是原始图像缓冲区的一个句柄,而原始图像是由屏幕图像合成器管理的。当得到一个Surface对象时,同时会得到一个Canvas对象。因为Surface定义了一个Canvas成员变量(Surface.java的90行)句柄是什么?
答:个人理解(此处个人指邵励治本人),句柄可以理解为遥控器、方向盘等等。简单说就是你用句柄可以操作该句柄对应的对象。Surface实现的Parcelable接口是做什么的?
答:一两句说不清楚,请看《Android开发之Parcelable使用详解》与《深入理解Java对象序列化》两篇文章,我没记错的话,都是CSDN上的。SurfaceView与Surface的联系?
答:surface是管理显示内容的数据,SurfaceView就是用来把这些数据显示出来到屏幕上面的窗口。SurfaceHolder是什么?
答:SurfaceHolder是surface的一个抽象接口,可以通过SurfaceHolder来控制Surface。SurfaceHolder.Callback是什么?
答:SurfaceHolder.Callback是监听Surface改变的一个接口,里面有四个重要方法:
(1) SurfaceHolder = surfaceView.getHolder();
(2) Surface = SurfaceHolder.getSurface();
(3) Canvas = SurfaceHolder.lockCanvas(Rect rect);
(4) Canvas = Surface.lockCanvas(Rect rect);
四、开始您的绘画吧————设计绘画工具有:Canvas、Paint、Bitmap、Rect、RectF等
1.来绘图吧
- 原型图
- 原型图分析
原型图由以下几个元素构成
(1)五个扇形(特点:只用黑笔描出边框)
(2)五段文字(特点:文字是随扇形的曲率而弯曲的,并且文字的位置相对于扇形是居中的
(3)五张图片(特点:五张图片始终是正方向的)
(4)一个向上的箭头(点击它整个圆盘会旋转)
2.绘图的原理
我们基于什么理念画扇形?
答:我们基于单位圆坐标轴画扇形(高中数学中经常提到的单位圆诸位还记得吗?就是学三角函数、弧度制那会儿介绍的),只不过这单位圆跟咱们学过的那个是反着的
函数:canvas.drawArc(RectF, 初始角度(float,角度值),末尾角度(float,角度值),是否滑出圆弧与圆心连线(Boolean),Paint)
介绍:
(1)RectF类表示坐标系中的一块矩形区域,需要用左上和右下两个坐标点表示。
(2)Paint类国内普遍理解为画笔,但我觉得它更像染料,canvas更像画笔,SurfaceView像画布canvas.drawArc函数参数与函数作用详解
(1)外圈的正方形就是RectF——每个正方形可以唯一确定一个内接圆(RectF是长方形时内接圆怎么确定,有兴趣的请自行研究)
(2)drawArc画出的扇形(或弧形)是沿着内接圆顺时针画的
(3)从哪儿画到哪儿则是由初始角度和弧形圆心角两个参数决定的
比如drawArc(rectf,90,90,true,paint)就是说,从坐标轴的90度开始,顺时针画出一个圆心角为90度的扇形。
(4)第四个参数要是true的话,会画出一个扇形。若是false的话,会画出一个弧形。
(5)Paint参数决定了画出的扇形(弧形)的颜色,线条粗细,样子等属性
3.由图片到动画
如何让我们的图片动起来呢?
答:关键点还是在于drawArc的几个参数。不难发现,除了初始角度这一参数决定了扇形的位置外,其他的参数好像都是在决定它的大小、颜色、外形等内容。于是我写出了公式:
y = f(x)————y是扇形,x是扇形的初始角度。那么,假如我们有一个循环体,每一次都给扇形的角度+v(这里我将其看成圆盘的速度),并且重新绘制,当这个循环体跑起来的时候,我们就相当于看到一个转动的扇形了。那当我们原型图中的五个扇形同时以相同速度v转动时,我们就获得了一个转动的圆盘。如何让转动的转盘停下来?
答:我们让v=0就会让转盘停下来。如何让转动的转盘不那么突兀的停下来?
答:我们再设立一个a作为加速度,在每次循环的时候先给扇形转动的速度+a,再给扇形的角度+v。这样的话,我们就能让转盘慢慢停下来,有一个减速的过程。
4.关于SurfaceView、Canvas、Paint等绘图工具类的一些重要方法
android.view.SurfaceView#setZOrderOnTop
把SurfaceView置于Activity显示窗口的最顶层android.view.SurfaceHolder#setFormat
设置surace所需的像素格式,默认是DRAG_FLAG_OPAQUE,也就是“不透明”的意思android.view.View#setFocusable
将此方法设置为false将确保此时图在触摸模式下不可对焦
若设置为true,则次视图可以接收焦点android.view.View#setFocusableInTouchMode
设置在触摸模式下,此视图是否可以接收焦点。此属性设置为true还将确保此视图是可聚焦的。(关于上面两个方法的区别,可以看参考文献《setFocusable与setFocusableInTouchMode的差异以及clickable》)android.graphics.Paint#setAntiAlias
设置画笔的抗锯齿效果android.graphics.Paint#setDither
设置图像防抖动
5.周边知识
Bitmap是什么?
答:Bitmap叫位图,扩展名可以使.bmp或者.dib。位图是Windows标准格式图形文件,它将图像定义为由点组成,每个点可以由多种色彩表示,包括2、4、8、16、24和32位色彩。例如,一幅1024×768分辨率的32位真彩图片,其所占存储字节数为1024×768×32÷8÷1024KB=3072KB=3MB
位图文件效果好,但是需要较大的存储空间,因为它是未经压缩的。Android中的Bitmap类有什么用?
答:Bitmap是Android系统中的图像处理最重要的类之一,用它可以获取图像文件信息,进行图像的剪切、旋转、缩放等操作,并可以制定格式保存图像文件。
详情请看:《Android图像处理之Bitmap类》和《Bitmap类用法详细说明》RectF是什么?
答:表示坐标系中的一块矩形区域,并可以对其做一些简单操作。这块矩形区域,需要左上和右下两个坐标点表示。它和Rect的区别是精度,Rect的输入值是int类型的,Rectf的输入值是float类型的。还有,二者提供得方法也不完全一致。volatile是什么?
答:volatile是一个跟生产者-消费者问题相关的Java修饰符。它主要有一下两个特性:- 可见性:对一个volatile变量的读取,总能看到任意线程对这个volatile变量最后的写入
- 原子性:对任意单个volatile的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性
Android中的焦点是什么?
答:当我们有两个输入框时,我们点击上面的那个输入框,就可以说上面的输入框获得乐脚点,因为光标在它那里。故在此给出解释:即当前光标被激活的位置
五、与我一起重构hyman的代码
乔布斯一直坚持要让芯片整齐地排列在电路板上,即使它们不会被人看到也要这么做。我认为我们写代码也应该有这样的精神,这虽然耗费时间,但我认为可以让我们受益匪浅。
如果您认同我的这种观点,请继续往下看。如果您不认同我的观点,请直接去读《Android SurfaceView实战 打造抽奖转盘》原文————下面的内容基本上是基于hyman的基本思想的复制与改进。
1.全部字段
由onMeasure初始化的字段:
抽奖圆盘的直径: private int diameter;
抽奖圆盘的中心: private int circleCenter;
外接矩形RectF与SurfaceView的距离: private int padding;
由surfaceCreated初始化的字段:
转盘上的文字: private String[] stringsOfGifts;
存储图片用的Bitmap: private Bitmap[] bitmapOfPictures;
盘块的个数: private int itemCounts;
弧形的圆心角: private float centralAngle;
抽奖转盘的外接矩形: private RectF externalRectangle;
绘图的笔: private Paint paintingPen;
写字的笔: private Paint writingPen;
由“起手式”初始化的字段:
private SurfaceHolder surfaceHolder;
private boolean drawingSwitch;
private Canvas canvas;
2.核心方法
我的核心代码是下面三个方法:
- void drawSector(float angle) :画扇形
- void drawText(float angle, String string) :画文字
- void drawPicture(float angle, Bitmap bitmap) : 画图片
不难发现,它们三个都可以看成随单一变量angle变化而变化的函数。
故,我设立下面这个方法:
void drawLotteryTurntable(float angle) :画抽奖转盘
private void drawLotteryTurntable(float angle) {for (int i = 0; i < itemCounts; i++) {drawSector(angle);drawPicture(angle, bitmapOfPictures[i]);drawText(angle, stringsOfGifts[i]);angle += centralAngle;//centralAngle是在surfaceCreated中算出的扇形圆心角}
}
3.效果图
- 当传入angle=0时
- 当传入angle=90时,可以看出,转盘顺时针转动了90度
4.位置x,速度v,加速度a
根据上面的设计,我们可以发现。整个转盘只因一个传入参数angle而改变它的“位置”,那么我是不是可以创造一个字段x代表整个圆盘的“位置”。
SurfaceView其实一直在重绘————看我们线程里的那个循环体while(drawingSwitch){},它每执行一遍,整个界面就重绘一遍。
那么我引进一个变量v,每次重绘时我让x+=v,于是整个转盘就会转起来了。
那么我再引进一个变量a,每次重绘时我先让v+=a,再让x+=v,于是整个转盘就能非匀速的转了。
其实这个理念非常像近代物理中的一个概念————即时间是量子的,不连续的。程序中每循环一次我们的加速度、速度、位置在离散的变化,让我不禁幻想:现实世界是不是也是如此呢?
按照这个思想的话,下面三行代码都是我们的“转盘世界”的本质:
- if(v<=0){a=0;v=0;} 当速度等于0时,滑动摩擦力变为0,物体不再拥有加速度。速度不会降到负值
- x+=v; 每个时间单位,位置都随速度变化
- v+=a; 每个时间单位,速度都随加速度变化
5.添加点击按钮,监听点击事件
按照hyman的思路,转盘有以下三个状态:
1. 静止状态(特点:v=0;a=0; 用户点击后,进入”转动状态”)
2. 转动状态(特点:v>0;a=0; 用户点击后,进入”逐渐停止状态”)
3. 逐渐停止状态(特点:v>0;a<0; 用户点击无效)
代码段:(这块有点像数字逻辑的Verilog硬件描述语言的程序设计中常用的思想————即“现在状态->下一状态”编程法)
public void controller(View button) {button.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View view) {if (a == 0) {if (v == 0) {//静止状态//在静止状态点击按钮,我会给转盘一个初速度v=8让转盘转起来v = 8;} else {//转动状态//在转动状态点击按钮,我会给转盘一个摩擦力,导致a=-0.05,让转盘逐渐减速a = (float) -0.05;}}}});
}
6.上帝是个程序员?
诸位,请仔细看上面这循环中的代码。
这就好像是我在程序中创建了一个“转盘世界”,这个世界有自己的自然法则(即上述代码,关于速度,位移,加速度以及滑动摩擦力在速度为零时会消失),而且在这个世界里,“循环”这一概念简直和“普朗克时间”也就是“量子时间”有着惊人的相似度————离散,不可察觉。
世界会不会是一段大程序呢?人类是一个类,而我们是一个个属性不同的实例?而造物主就像我在“转盘世界”中随意敲下几行代码一样在现实世界也敲下了几行代码,设置了一些final修饰的常量如c=3.0×10^8?
哈哈,这真是本末倒置的思想。因为好像不是“我们的世界就好像一段面向对象程序”,而是“面向对象程序在模拟真实世界时做的真好”。
7.课后作业:控制概率
hyman在控制概率时用了一个很不优雅的方法,每当我们按下停止键时,转盘会先回到初始位置,之后再走固定的时间到指定位置,利用了转盘转的很快时,用户看不清“回到初始位置”这一过程的现象。
而我则不想这样做,我给出一个思路,诸位有兴趣的自己去实现看看:
1. 获取当前的x(当前角度),并对360度求模
2. 计算目标角度(360度以内)
3. 设定多少时间单位停下来(一个循环为一个时间单位)
4. 根据“初始角度与目标角度的差值”,“设定好的所需时间”这两个值,求出加速度a
公式:
是“初始角度与目标角度的差值”
是“初速度”
是“设定好的所需时间”
有兴趣的同学可以自行实现
六、源代码
LotteryTurntable.java
package shaolizhi.mymusiclife;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.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;/*
* Created by 邵励治 on 2017/2/28.
*/public class LotteryTurntable extends SurfaceView implements SurfaceHolder.Callback {private SurfaceHolder surfaceHolder;private boolean drawingSwitch;private Canvas canvas;//抽奖圆盘的直径private int diameter;//抽奖圆盘的中心private int circleCenter;//外接矩形RectF与SurfaceView的距离private int padding;//转盘上的文字private String[] stringsOfGifts;//存储图片用的Bitmapprivate Bitmap[] bitmapOfPictures;//盘块的个数private int itemCounts;//弧形的圆心角private float centralAngle;//抽奖转盘的外接矩形private RectF externalRectangle;//绘图的笔private Paint paintingPen;//写字的笔private Paint writingPen;//圆盘世界的速度private float v;//圆盘世界的加速度private float a;//圆盘世界的位移private float x;private Thread thread = new Thread(new Runnable() {@Overridepublic void run() {while (drawingSwitch) {//锁定画布,得到Canvas对象canvas = surfaceHolder.lockCanvas();//Start Drawing//绘制白色背景canvas.drawColor(Color.WHITE);//画抽奖转盘drawLotteryTurntable(x);//下面三行代码,是转盘世界的世界法则,我们的世界是不是也是由某个人写下的几行代码制定的呢?if (v <= 0) {a = 0;v = 0;}x += v;v += a;//Stop Drawingif (canvas != null) {//解除锁定,并提交修改内容,更新屏幕surfaceHolder.unlockCanvasAndPost(canvas);}}}});public void controller(View button) {button.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View view) {if (a == 0) {if (v == 0) {//静止状态//在静止状态点击按钮,我会给转盘一个初速度v=8让转盘转起来v = 8;} else {//转动状态//在转动状态点击按钮,我会给转盘一个摩擦力,导致a=-0.05,让转盘逐渐减速a = (float) -0.05;}}}});}private void drawLotteryTurntable(float angle) {for (int i = 0; i < itemCounts; i++) {drawSector(angle);drawPicture(angle, bitmapOfPictures[i]);drawText(angle, stringsOfGifts[i]);angle += centralAngle;}}private void drawSector(float angle) {canvas.drawArc(externalRectangle, angle, centralAngle, true, paintingPen);}private void drawText(float angle, String string) {Path path = new Path();//添加一个圆弧最为路径path.addArc(externalRectangle, angle, centralAngle);//测量传入字符串的宽度float textWidth = writingPen.measureText(string);float hOffset = (float) (diameter * Math.PI / 5 / 2 - textWidth / 2);// 水平偏移float vOffset = diameter / 12;// 垂直偏移canvas.drawTextOnPath(string, path, hOffset, vOffset, writingPen);}private void drawPicture(float angle, Bitmap bitmap) {//设置图片的宽度为半径的1/4int imageWidth = diameter / 8;float alpha = (float) ((180 / itemCounts + angle) * (Math.PI / 180));int x = (int) (circleCenter + diameter / 2 / 2 * Math.cos(alpha));int y = (int) (circleCenter + diameter / 2 / 2 * Math.sin(alpha));// 确定绘制图片的位置Rect rect = new Rect(x - imageWidth / 2, y - imageWidth / 2, x + imageWidth/ 2, y + imageWidth / 2);canvas.drawBitmap(bitmap, null, rect, null);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);//从系统测量出的SurfaceView的宽和高中取一个较小的作为SurfaceView这个正方形的边长int sideLength = Math.min(getMeasuredWidth(), getMeasuredHeight());//中间变量,为了在surfaceCreated中初始化RectF——Android不建议在onMeasure中进行内存分配操作padding = getPaddingLeft();//获取圆形的直径diameter = sideLength - padding - padding;//获取原型的中心点circleCenter = sideLength / 2;setMeasuredDimension(sideLength, sideLength);}public LotteryTurntable(Context context) {this(context, null);}public LotteryTurntable(Context context, AttributeSet attrs) {this(context, attrs, 0);}public LotteryTurntable(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);//通过SurfaceView获得SurfaceHolder对象surfaceHolder = this.getHolder();//为SurfaceHolder添加回调结构SurfaceHolder.CallbacksurfaceHolder.addCallback(this);//屏幕常亮setFocusable(true);setFocusableInTouchMode(true);setKeepScreenOn(true);}@Overridepublic void surfaceCreated(SurfaceHolder holder) {v = (float) 0;a = (float) 0;x = (float) 0;//初始化礼品文字、礼品的图片资源stringsOfGifts = new String[]{"LG 34UC88-B", "iPhone 7", "Alienware R4 M17","Dog", "Boot"};int[] picturesIdOfGifts = new int[]{R.drawable.lg_34uc88_b, R.drawable.iphone_7,R.drawable.alienware_r4_m17, R.drawable.dog, R.drawable.cat};//初始化盘块的个数itemCountsitemCounts = Math.min(stringsOfGifts.length, picturesIdOfGifts.length);//初始化圆心角centralAnglecentralAngle = (float) 360 / itemCounts;//初始化绘图的笔paintingPen = new Paint();paintingPen.setColor(Color.BLACK);paintingPen.setStrokeWidth((float) 3.0);paintingPen.setStyle(Paint.Style.STROKE);paintingPen.setDither(true);paintingPen.setAntiAlias(true);//初始化写字的笔writingPen = new Paint();writingPen.setAntiAlias(true);writingPen.setDither(true);writingPen.setColor(Color.BLACK);writingPen.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 20, getResources().getDisplayMetrics()));//初始化转盘的外接矩形externalRectangleexternalRectangle = new RectF(padding, padding, diameter + padding, diameter + padding);//初始化BitmapbitmapOfPictures = new Bitmap[itemCounts];for (int i = 0; i < itemCounts; i++) {bitmapOfPictures[i] = BitmapFactory.decodeResource(getResources(), picturesIdOfGifts[i]);}//打开线程开关,开启线程drawingSwitch = true;thread.start();}@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {drawingSwitch = false;}}
LotteryTurntableActivity.java
package shaolizhi.mymusiclife;import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ImageView;public class LotteryTurntableActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_lottery_turntable);LotteryTurntable lotteryTurntable = (LotteryTurntable) findViewById(R.id.lottery_turntable);ImageView startButton = (ImageView)findViewById(R.id.imageButton);lotteryTurntable.controller(startButton);}
}
activity_lottery_turntable.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/activity_lottery_turntable"android:layout_width="match_parent"android:layout_height="match_parent"android:paddingBottom="@dimen/activity_vertical_margin"android:paddingLeft="@dimen/activity_horizontal_margin"android:paddingRight="@dimen/activity_horizontal_margin"android:paddingTop="@dimen/activity_vertical_margin"tools:context="shaolizhi.mymusiclife.LotteryTurntableActivity"><shaolizhi.mymusiclife.LotteryTurntable
android:id="@+id/lottery_turntable"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_centerInParent="true"android:paddingLeft="5dp" /><ImageView
android:id="@+id/imageButton"android:layout_width="200dp"android:layout_height="150dp"android:layout_centerInParent="true"android:background="@drawable/a" />
</RelativeLayout>
源代码:https://github.com/shaolizhi1234/MusicLife/
详解与重构hyman《Android SurfaceView实战 打造抽奖转盘》相关推荐
- Android SurfaceView实战 打造抽奖转盘
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/41722441 ,本文出自:[张鸿洋的博客] 1.概述 今天给大家带来Surfac ...
- Android SurfaceView实战 带你玩转flabby bird (上)
分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 转载请标 ...
- Android SurfaceView实战 带你玩转flabby bird
分析 仔细观察游戏,需要绘制的有:背景.地板.鸟.管道.分数: 游戏开始时: 地板给人一种想左移动的感觉: 管道与地板同样的速度向左移动: 鸟默认下落: 当用户touch屏幕时,鸟上升一段距离后,下落 ...
- 详解Shell编程之if语句实战(小结)
本篇文章主要介绍了详解Shell编程之if语句实战(小结),小编觉得挺不错的,现在分享给大家,也给大家做个参考.一起跟随小编过来看看吧 对于if语句,其实很多人都肯定的听说过,那么if语句到底是什么, ...
- 『矩阵论笔记』详解最小二乘法(矩阵形式求导)+Python实战
详解最小二乘法(矩阵形式求导)+Python实战! 文章目录 一. 矩阵的迹 1.1. 转置矩阵 1.2. 迹的定义 1.3. 七大定理 二. 最小二乘法 2.1. 求解介绍 2.2. 另一角度 2. ...
- Android SurfaceView实战 带你玩转flabby bird (下)
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/43063331,本文出自:[张鸿洋的博客] 1.概述 在Android Surfa ...
- android自定义抽奖,Android自定义view制作抽奖转盘
本文实例为大家分享了Android自定义view制作抽奖转盘的具体代码,供大家参考,具体内容如下 效果图 TurntableActivity package com.bawei.myapplicati ...
- flutter 获取android 还是ios_Flutter完整开发实战详解(二十、 Android PlatformView 和键盘问题)...
作为系列文章的第二十篇,本篇将结合官方的技术文档科普 Android 上 PlatformView 的实现逻辑,并且解释为什么在 Android 上 PlatformView 的键盘总是有问题. 为什 ...
- 今晚8点直播 | 详解聊天机器人落地及进阶实战
近年来,聊天机器人技术及产品得到了快速的发展.聊天机器人作为人工智能技术的杀手级应用,发展得如火如荼,各种智能硬件层出不穷. 本次公开课中,AI科技大本营联合电子工业出版社博文视点邀请到上海瓦歌智能科 ...
最新文章
- 为何而生、What I have Lived for
- 【Power Automate】如何自动生成Word与PDF文件[上]
- 前端学习(1952)vue之电商管理系统电商系统之级联选择器
- 使用 gradle 在编译时动态设置 Android resValue / BuildConfig / Manifes中lt;meta-datagt;变量的值...
- [转]Win XP常遇网络故障分析:局域网问题
- Oracle→表、表字段数据类型、表DDL语句、数据DML语句、约束、case...when、decode
- python鸡兔同笼编程运行结果_Python解决鸡兔同笼问题的方法
- PDF怎么翻译成中文?这些方法值得收藏
- 使用SpringAOP拦截Feign请求动态添加参数
- python查看微信撤回消息_Python查看微信撤回消息代码
- OpenHarmony代码操作总结
- 小米商城网页版(js+css)
- C#代码审计实战+前置知识
- Linux服务器搭建--NTP服务器的搭建与配置
- 想要的资源百度搜不到?6个只有老师傅才知道的网站,悄悄领走
- python 人工智能编程_最适合人工智能开发的5种编程语言
- 大数据背后的神秘公式:贝叶斯公式(万字长文)
- 脚本计算后台程序消耗资源
- MCD19A2 MAIAC AOD 数据处理(三)均值+同日镶嵌+重投影(Grid转经纬度)
- 全国计算机表格试题及答案,全国计算机等级考试四、Excel电子表格操作试题.doc...