Based on SurfaceView Detailed Android Lucky Wheel, with example app
First of all, let’s talk about the wheel of fortune and SurfaceView after reading the blog which is also a great god. Understand, of course, here is the blog address of this great god: blog address, if you are interested, you can check it out. There are many examples in it. As for why I wrote this blog? One of the reasons: to strengthen one’s own understanding, and the second reason: the blog of the Great God is the blog of the Great God, the jump is too fast, the foundation is not good, and it is difficult to understand. Another day is too boring in the laboratory, nothing to write about . Here I will come to a more basic analysis. Bad writing, forgive me. I hope to point out what is wrong, thank you. Attach [email protected]. Interested parties communicate. Next, let’s get to the main topic, let’s explain this big turntable in detail.
1. First attach the renderings and simple analysis< /h2>
Is this rendering very familiar?, of course this It is the final rendering, which is genuine, not deviated at all, and smoother than this. Because this is intercepted on the virtual machine, you also know that the fluency of the virtual machine is incomparable with the real machine.
1. First analyze, make this big turntable, all need What to achieve.
It’s not difficult to see that making this big turntable requires two controls, one is custom In the SurfaceView, there is of course the button pointer that never moves in the middle. You only need to change the picture every time you click.
2. Talk about the basic idea to achieve this.
Implementation Does this seem difficult? Of course, for some great god-level characters, this is a piece of cake, but the point is that most are not. At first glance, isn’t it just a plate that keeps spinning. There is a button to control the plate in the middle. To achieve the continuous rotation of the plate, you have to rely on this SurfaceView. Check out the official ApiSurfaceView directly. The direct parent class is View. There is an important difference between SurfaceView and other Views. SurfaceView allows non-UI thread modification. This is also listed on SurfaceView. One big advantage, so you don’t have to use a View to notify the UI thread to modify the view every time through a non-UI thread. How troublesome. So pass , SurfaceView, you can open a thread to continuously update the UI in the background, which is much more convenient. ,
2. Commonly used templates for SurfaceView
Although SurfaceView is very convenient to make a small Games, but don’t mess around casually. There is a more commonly used template on the Internet. I believe this can achieve your goals and get twice the result with half the effort. Why not do it. < /span>
public class SurfaceViewTemplate extends SurfaceView implements Callback,Runnable{
//Define a SurfaceHolder to accept the acquired SurfaceHolder
private SurfaceHolder mHolder;
//Used to get the Canvas bound to mHOlder
private Canvas mCanvas;
//Thread used for continuous drawing
private Thread mThread;
//Switch used to control the thread
private boolean isRunning;
//The three-parameter constructor in this era will generally be used This constructor will only be called when customizing attributes.
/**
* @param context
* @param attrs xml file-defined attributes
* @param defStyle custom Attributes
*/
public SurfaceViewTemplate(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
//When this custom View was defined with an xml file, the constructor with two parameters would be called
public SurfaceViewTemplate(Context context, AttributeSet attrs) {
super(context , attrs);
//Get SurfaceHolder
mHolder=getHolder();
//Add callback
mHolder.addCallback(this);
//Set some attributes, focus, the screen is always on< br /> setFocusable(true);
setFocusableInTouchMode(true);
setKeepScreenOn(true);
}
//When the definition is displayed in the code The constructor with one parameter will be called
public SurfaceViewTemplate(Context context) {
//Here we let him call the constructor with two parameters, so that even if it is defined in the code Can also complete some initialization operations
this(context,null);
}
//The main and core tasks are performed in the run method, such as draw()
@Override
public void run() {
try{
//Here through an infinite loop, drawing is continuously performed, giving you a kind of continuous rotation of the disk Illusion
while (isRunning){
draw();
}
}catch(Exception e){
e.printStackTrace( );
}
}
@Override
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
}
//Do some initialization work here and start the thread. . .
@Override
public void surfaceCreated(SurfaceHolder arg0) {
//Instantiate the thread and set isRunning
isRunning=true;
mThread=new Thread(this);
mThread.start();
}
//Close the thread when SurfaceView executes destroy
@Override
public void surfaceDestroyed(SurfaceHolder arg0) {
//To close the thread, just set isRunning
isRunning=false;
}
//The following controls are developed here Width and Height
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}< /blockquote>
3. Next, define the member variables according to the big carousel
< blockquote>
I have to say that there are indeed a lot of member variables. Besides the ones given by the template, they have to be defined. Text, picture, picture address, all kinds. . . I can’t remember, attach the code
// surface
private SurfaceHolder mHolder;
// Canvas bound with surface
private Canvas mCanvas;
// Thread used for drawing
private Thread mThread;
// Thread control switch
private boolean isRunning ;
// text describing the lottery
private String[] mName = new String[] {"SLR camera", "IPAD", "I'm Feeling Lucky", "IPHONE",< br /> "One piece by Zhang Jie", "I'm not feeling lucky" };
// The color of each block
private int deepColor = 0xFFFFC300;
private int lightColor = 0xFFF17E01;
private int[] mColors = new int[] {deepColor, lightColor, deepColor,
lightColor, deepColor, lightColor, };
// pictures corresponding to text
private int[] mImgs = new int[] {R.drawable.danfan, R.drawable.ipad,
R.drawable.f040, R.drawable.iphone, R.drawable.meizi,
R.drawable.f040 };
// Array of pictures corresponding to text
private Bitmap[] mImgsBitmap;
// Number of disk blocks
private final int mItemCount = 6;
// Draw the range of the block
private RectF mRange = new RectF();
/ / The diameter of the circle
private int mRadius;
// The brush for drawing the plate
private Paint mArcPaint;
// The brush for drawing the text< br /> private Paint mTextPaint;
// Scrolling speed
private double mSpeed;
private volatile float mStartAngle = 0;
// Decrease
private int aSpeed = 1;
// Did you click to stop?
private boolean isShouldEnd;
// The center position of the control
private int mCenter;
// control padding
private int mPadding;
// background image
private Bitmap mBgBitmap = BitmapFactory.decodeResource (getResources(),
R.drawable.bg2);
// The size of the text
private float mTextSize = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP, 20 , getResources().getDisplayMetrics());
public LuckyPadView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
These are basically indispensable, of course you can simplify some, for example, The decelerated home speed, as well as the number of blocks you have shown that you know the disk is 6, write it every time you use it Six will do.
4. Write the construction method
This construction method, there are multiple ways of writing, depending on your own If the purpose is only defined in the xml file without custom attributes, you can also ignore the one- and three-parameter constructors and directly write the two-parameter constructors. Of course, for compatibility, it is recommended that the three constructors are all Support, so as to avoid unnecessary trouble., attach the code as usual:
//Do some initialization operations
public LuckyPadView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// Get holder, and Canvans associated with it
mHolder = getHolder();
mHolder.addCallback(this);
// Set the focus to be available and always on
setFocusable(true);
setFocusableInTouchMode(true);
setKeepScreenOn(true);
}
//To call the constructor with three parameters
public LuckyPadView(Context context, AttributeSet attrs) {
this(context,null,0);
}
// A constructor with one parameter to call the constructor with two construction parameters
public Luc kyPadView(Context context) {
this(context, null);
}
< span style="color:#3366FF">5. Write onMeasure method
When writing a custom control, a method that cannot be ignored is that it specifies its own size and the maximum size of the child control. . . Not to mention, one thing worth mentioning is that the size of the control is based on the smallest of the width and height. The purpose of this is to make the control into a square, so that it is convenient to draw a circle in the future to determine the radius, center, etc., so Don’t forget centerInParent when defining in the xml file. Not much nonsense, attach the source code:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// Get the smallest width and height
int width = getMeasuredWidth();
int height = getMeasuredHeight();
int min = width
// Get the diameter of the circle
mRadius = min-getPaddingLeft()-getPaddingRight( );
// Get the padding value, a paddingleft is the reference
mPadding = getPaddingLeft();
// Set the center point
mCenter = min / 2;
setMeasuredDimension(min, min);
}
6. Write surfaceCreated and attach surfaceDestroyed by the way
The effect of these two methods is well understood by the name. When creating, we need to instantiate the thread, start the thread, and initialize some member variables, such as: mRange to determine the drawing area, attach a picture to understand. In the destroy Sometimes we must close the thread, otherwise it will cause a serious consequence, Memory leak. Attach the code:
// Do some initialization work
@Override
public void surfaceCreated(SurfaceHolder arg0) {
// Initialize the brush for drawing the arc, and set the sawtooth and the like
mArcPaint = new Paint();
mArcPaint.setAntiAlias (true);
mArcPaint.setDither(true);
// Initialize the brush for drawing text
mTextPaint = new Paint();
mTextPaint.setColor(0xFFffffff );
mTextPaint.setTextSize(mTextSize);
// The drawing range of the arc, the drawing range is just a square, this I have to make an illustration (1), otherwise I can’t understand it
mRange = new RectF(mPadding, mPadding, mRadius + mPadding, mRadius
+ mPadding);
// Initialize the picture
mImgsBitmap = new Bitmap[mItemCount];
for (int i = 0; imImgsBitmap[i] = BitmapFactory.decodeResource(getResources(),
mImgs[i]);
}
// Start thread
isRunning = true;
mThread = new Thread(this);
mThread.start();
}
// Mainly used to close the thread
@Override
public void surfaceDestroyed(SurfaceHolde r arg0) {
// Notify thread to close
isRunning = false;
}
< h2>7. The highlight run method
Basically all operations are performed in this run method. Of course, in order to make the code readable, we type the various operations in the run method, package them into methods, and put them outside. The main packaging method draw(), drawing operation. Through an endless loop, continue to draw. Give the user a feeling that the turntable is constantly rotating. But because the drawing is executed by the computer, you don’t know what its execution time is, and the performance of the computer at a certain moment is also uncertain, so it will cause the drawing time to be different, which will not cause false The speed is fast and sometimes slow. This doesn’t work. So there is a very clever way to deal with it, is to record the time you draw. Then you give a standard time value for drawing. When it is less than the drawing time, let the thread sleep for insufficient time, which is a good solution. Attach the code:
@Override
public void run() {
// Continue drawing, which gives you the illusion that the turntable is spinning
while (isRunning) {
// Time to start drawing this time
br /> long start = System.currentTimeMillis();
// The real drawing operation
draw();
// The end time of this drawing
long end = System.currentTimeMillis();
// If your phone is too fast and drawing takes minutes, you have to let him wait for the 50 to finish.
try {
if (end-start <50) {
Thread.sleep(50-(end-start));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
8.draw() in the run() method
In this draw() method, work comes, you first need to get the Canvas bound to mHolder, and then draw the background , Draw the background of each block once, and then draw the text on the block, each of which is faster than the picture above. . . Of course, each fast position is calculated based on mStartAngle and speed, sweepAngle, tmpAngle, and it must be judged whether the user has pressed the stop button, and if so, it must be decelerated by a certain acceleration. Of course, the speed cannot be less than zero, so you have to judge at the end, in order to return the speed to 0. By the way, have you noticed all kinds of try{}catch(){}, this is to prevent an exception from unknown when, I don’t want my turntable to crash halfway, attach the code: span>
// Drawing
private void draw() {
try {
mCanvas = mHolder.lockCanvas();
if (mCanvas != null) {
// First draw the background image
drawBg();
// Draw each arc, and the text on each arc, and the picture on each arc
float tmpAngle = mStartAngle;
float sweepAngle = (float) ( 360 / mItemCount);
for (int i = 0; i
// This is the legendary background color
mArcPaint .setColor(mColors[i]);
// There is really a fan shape drawn here, so I will get an illustration here too (4) For details, please refer to
// http://blog.sina.com.cn/s/blog_783ede0301012im3.html
// oval: Specify the rectangular area of the outer contour of the arc.
// startAngle: The starting angle of the arc, in degrees.
// sweepAngle: The angle swept by the arc, clockwise, and the unit is degree.
// useCenter: If True, include the center of the circle when drawing an arc, usually used to draw a fan shape.
// paint: The drawing board attributes for drawing the arc, such as color, whether to fill, etc.
mCanvas.drawArc(mRange, tmpAngle, sweepAngle, true,
mArcPaint);
// draw text
drawText(tmpAngle, sweepAngle, mName[i]) ;
// Draw Icon
drawIcon(tmpAngle, i);
// Convert the angle, you can’t draw in one place all the time,
tmpAngle + = sweepAngle;
}
// When mspeed is not equal to 0, it is equivalent to scrolling
mStartAngle += mSpeed;
/ / When you click Stop, set mspeed to decrease slowly, instead of stopping at one click
if (isShouldEnd) {
mSpeed -= aSpeed;
}
// When mspeed is less than 0, it should stop.
if (mSpeed <0) {
mSpeed = 0;
isShouldEnd = false;
}
// Calculate the current scrolling area according to the currently rotating mStartAngle
callInExactArea(mStartAngle);
}
} catch (Exception e) {
e.printStackTrace( );
} finally {
if (mCanvas != null)
mHolder.unlockCanvasAndPost(mCanvas);
}
}< span id="OSC_h3_28">
There is a background for drawing each block. Please understand the illustration< /h3>
mCanvas.drawArc(mRange, tmpAngle, sweepAngle, true,
mArcPaint) ;
9. drawBg() in the draw() method
First of all, you have to attach a picture or you won’t understand it. Go through this picture I believe you can see the next code very clearly, nothing more than drawing a circle on the outside of mRange for a beautiful background. It is not useful, not much to say, attach the code:
// Draw a background image
private void drawBg() {
// Draw the background based on the currently rotated mStartAngle to calculate the area currently scrolled to. It is not important, it is purely for the sake of beauty.
mCanvas.drawColor(0xFFFFFFFF);
// The drawing here is generally incomprehensible, it has to be an illustration. This seems to be a circle larger than the drawing range of the arc
mCanvas.drawBitmap(mBgBitmap, null, new Rect(mPadding / 2,
mPadding / 2, getMeasuredWidth()-mPadding / 2,
getMeasuredWidth()-mPadding / 2), null);
}blockquote>
The drawText() method in 10.draw() is used to draw the text inside the block
First of all, in the previous picture, drawing this text is indeed a bit troublesome, after all, the text is The location is a bit special.
I have understood this text for a long time. It’s a bit convoluted. To draw this text is to first determine an Arc through the path, which is an arc, and then use the horizontal and vertical offset to co-locate the final position of the text, which is marked in the figure. float hOffset = (float) (mRadius * Math.PI / mItemCount / 2-textWidth / 2); Explain this formula first to get the arc length, and then subtract half of the text width to get the horizontal offset and vertical offset Just to understand it is directly float vOffset = (float) (mRadius / 2 / 6); which is 1/6 of the radius. Attach the code:
span>
/**
* Draw text
*
* @param startAngle
* @param sweepAngle
* @param mName2
*/
private void drawText(float startAngle, float sweepAngle, String mName2) {
// path
Path path = new Path();
// Add the writing area
path.addArc(mRange, startAngle, sweepAngle);
// The width of the text
float textWidth = mTextPaint. measureText(mName2);
// Use horizontal offset and vertical offset to center the text, is it incomprehensible? Me too, draw an illustration, (3)
float hOffset = (float) (mRadius * Math.PI / mItemCount / 2-textWidth / 2);
float vOffset = (float) (mRadius / 2 / 6);
// I have to draw the text up
mCanvas.drawTextOnPath(mName2, path, hOffset, vOffset, mTextPaint);
}
11.draw() inside drawIcon()
First of all, attach a picture for easy understanding. After all, drawing this picture is not so easy to understand. Is the picture Very detailed . To draw this picture, we must first determine a Rect, and this Rect is the shadow part of the illustration, as long as the shadow part is determined, the drawing is very simple. The key is to determine this shaded part. First, we get X and Y through the trigonometric function through the bisecting angle and startAngle, don’t tell me you forgot . Then through mCenter add and subtract, etc., the final shadow part, and finally attach the code:
/**
* Draw Icon
*
* @param startAngle
* @param i
*/
private void drawIcon(float startAngle, int i) {
// Set the width of the picture to 1/8 of the diameter, of course you can change it at will.
int imgWidth = mRadius / 8;
// Converted to radians
float angle = (float) ((30 + startAngle) * (Math.PI / 180));
// x,y ... maybe this Only a picture can be understood, (5)
int x = (int) (mCenter + mRadius / 2/2 * Math.cos(angle));
int y = (int) (mCenter + mRadius / 2/2 * Math.sin(angle));
// Determine where to draw the picture
Rect rect = new Rect(x-imgWidth / 2, y-imgWidth / 2, x + imgWidth
/ 2, y + imgWidth / 2);
// Draw
mCanvas.drawBitmap(mImgsBitmap[i], null, rect, null);
}
12. luckyStart(index) method
This method is to display the final result of the turntable according to the calculation result after the turntable is clicked to start and before the start of the turn. If you really want to scold some e-commerce companies, you know to play with our feelings. The result has long been known. Of course it’s a bit difficult to understand. For many formulas, first attach a picture: As long as the result of the group is controlled within the angle range of 210 to 270, as for what is going on, there is a formula to understand slowly, and the code is attached:< /h3>
/**
* Now I finally see through, all e-commerce conspiracies are deceptive. E-commerce can display the result of setting your turntable
*
* @param luckyIndex
*/
public void luckyStart(int luckyIndex) {
/ / The angle size of each item
float angle = (float) (360 / mItemCount);
// The range of the winning angle, because the pointer is up, the range is 210-270 , I need an illustration to understand (6)
float from = 270-(luckyIndex + 1) * angle;
float to = from + angle;
// Stop is the distance of rotation
float targetFrom = 4 * 360 + from;
/*
*
* It’s a bit winding here, Evaluate it carefully
*/
float v1 = (float) (Math.sqrt(1 * 1 + 8 * targetFrom)-1) / 2;
float targetTo = 4 * 360 + to;
float v2 = (float) (Math.sqrt(1 * 1 + 8 * 1 * targetTo)-1) / 2;
mSpeed = (float) (v1 + Math.ran dom() * (v2-v1));
isShouldEnd = false;
}
13.SurfaceView helper method and MainActivity code
< span style="color:#3366FF">There is nothing to post here. The other is to call some methods. Here I am attaching some additional methods. I believe you will understand at a glance
public void luckyEnd() {
mStartAngle = 0;
isShouldEnd = true;
}
public boolean isStart() {
return mSpeed != 0;
}
public boolean isShouldEnd() {
return isShouldEnd;
}
Here is the code of MainActivity
public class MainActivity extends Activity {
private LuckyPadView id_luckypadview;
private ImageView id_imageview;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
setListener();
}
< br /> private void setListener() {
id_imageview.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
if ( !id_luckypadview.isStart()){
id_imageview.setImageResource(R.drawable.stop);
Random random=new Random();
id_luckypadview.luckyStart(random.nextInt ()%6);
}else {
if (!id_luckypadview.isShouldEnd()){
id_imageview.setImageResource(R.drawable.start);
id_luckypadview.luckyEnd( );
}
}
}
});
}
private void initView() {
id_luckypadview=(LuckyPadView)findViewById(R.id.id_luckypadview);
id_imageview=(ImageView)findViewById(R.id.id_imageview);
}
14. Finally, attach the code in the XML, it is very simple, you can understand it at a glance
xmlns:tools="http: //schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff"
>< br />
android:id="@+id/id_luckypadview"
android:layout_width="match_parent"
android:layout_height=" match_parent"
android:layout_centerInParent="true"
android:padding="30dp"
/>
android:id="@+id/id_imageview "
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/start"
android:layout_centerInParent="true"
/>
Finally, I put the source code on the Internet, which is effective for a long time.
Source code address
p>