[1] Introduction (full code at the end)
This project aims to implement a simple "Coin Toss" function that allows the user to simulate the process of tossing a coin by clicking on the gopher icon on the screen. The application will record and display the number of times the coin appears on the front side (gopher side) and the back side (number 100 side). To enhance the user experience, we also added animation effects to make the coin tossing process more vivid and interesting.
[2] Environmental Preparation
Computer system: windows 10
Development Tools: DevEco Studio NEXT Beta1 Build Version: 5.0.3.806
Engineering version: API 12
The real one: mate60 pro
Languages: ArkTS, ArkUI
[3] Application Structure
The application consists of two main parts: the gopher component (Hamster) and the main page component (CoinTossPage).
Gopher component (Hamster)
The gopher component is one of the core visual elements of the application and is responsible for displaying the image of the gopher. The component is defined through the @Component decorator and receives a property cellWidth, which is used to control the size of the component.
Main page component (CoinTossPage)
The main page component is the entry point to the entire application and is responsible for organizing and managing the various UI elements. This component is also defined through the @Component decorator and contains several state variables for tracking the state of the coins and the progress of the animation.
[4] Function Analysis
1. Gopher components:
- A gopher image was created by combining multiple graphic elements through a Stack layout.
- Each graphic element is set with a specific size, color, border, and other styles, and its position is adjusted with the margin attribute.
2. Main page components:
- At the top is a "coin flip" header, and below that is a row layout showing the gopher components and the number of times they appear heads and tails.
- The gopher component is placed in a circular area with a linear gradient color background.
- When you click on the gopher, a series of animation effects are triggered, simulating the process of a coin being tossed up and then falling down.
- By calculating the final angle, determine whether it is front or back facing up and update the count accordingly.
[complete code]
// Define the gopher component @Component struct Hamster { @Prop cellWidth: number // Cell width build() { Stack() { // Create a stacked layout // Body Text() .width(`${ / 2}lpx`)// The width is half the width of the cell .height(`${ / 3 * 2}lpx`)// Height is 2/3 of the cell height .backgroundColor("#b49579")// background color .borderRadius({ topLeft: '50%', topRight: '50%' })// Rounded corners .borderColor("#2a272d")// border color .borderWidth(1) // Border width // Mouth Ellipse() .width(`${ / 4}lpx`)// Width of the mouth .height(`${ / 5}lpx`)// Height of the mouth .fillOpacity(1)// Fill opacity .fill("#e7bad7")// Fill color .stroke("#563e3f")// border color .strokeWidth(1)// Border width .margin({ top: `${ / 6}lpx` }) // Top margin // Left eye Ellipse() .width(`${ / 9}lpx`)// Width of the left eye .height(`${ / 6}lpx`)// Left eye height .fillOpacity(1)// Fill opacity .fill("#313028")// Fill color .stroke("#2e2018")// border color .strokeWidth(1)// Border width .margin({ bottom: `${ / 3}lpx`, right: `${ / 6}lpx` }) // Bottom and right margins // Right eye Ellipse() .width(`${ / 9}lpx`)// Width of the right eye .height(`${ / 6}lpx`)// Right eye height .fillOpacity(1)// Fill opacity .fill("#313028")// Fill color .stroke("#2e2018")// border color .strokeWidth(1)// Border width .margin({ bottom: `${ / 3}lpx`, left: `${ / 6}lpx` }) // Bottom and left margins // Left eye pupil Ellipse() .width(`${ / 20}lpx`)// Pupil width of the left eye .height(`${ / 15}lpx`)// Height of the left pupil .fillOpacity(1)// Fill opacity .fill("#fefbfa")// Fill color .margin({ bottom: `${ / 2.5}lpx`, right: `${ / 6}lpx` }) // Bottom and right margins // Right pupil Ellipse() .width(`${ / 20}lpx`)// Pupil width of the right eye .height(`${ / 15}lpx`)// Pupil height of the right eye .fillOpacity(1)// Fill opacity .fill("#fefbfa")// Fill color .margin({ bottom: `${ / 2.5}lpx`, left: `${ / 6}lpx` }) // Bottom and left margins }.width(`${}lpx`).height(`${}lpx`) // Set the width and height of the component } } // Define page components @Entry @Component struct CoinTossPage { @State cellWidth: number = 50 // Cell width @State headsCount: number = 0 // Number of times face up @State tailsCount: number = 0 // Number of times the reverse side is up @State rotationAngle: number = 0 // Rotation angle @State verticalOffset: number = 0 // Longitudinal displacement @State isAnimRun: boolean = false // Whether the animation is executing or not build() { Column() { // Page title Text('Flip a coin') .height(50)// Height set to 50 .width('100%')// Width set to 100% .textAlign()// Center-align text .fontColor("#fefefe")// font color .fontSize(20); // Font size // Gopher display and count Row({ space: 20 }) { Stack() { Hamster({ cellWidth: }) // Create the gopher component } .borderRadius('50%') // Setting rounded corners .width(`${}lpx`) // Set the width .height(`${}lpx`) // Set the height .linearGradient({ // Set a linear gradient background direction: , colors: [['#ebcf2f', 0.0], ['#fef888', 0.5], ['#ebcf2f', 1.0]] }); // Number of times reverse face-up is displayed Text(`${}`) .fontSize(20) .fontColor("#fefefe"); Stack() { // Display 100 Text("100") .fontColor("#9f7606") .fontSize(`${ / 2}lpx`); } .borderRadius('50%') // Setting rounded corners .width(`${}lpx`) // Set the width .height(`${}lpx`) // Set the height .linearGradient({ // Set a linear gradient background direction: , colors: [['#ebcf2f', 0.0], ['#fef888', 0.5], ['#ebcf2f', 1.0]] }); // Show the number of times the front side is facing up Text(`${}`) .fontSize(20) .fontColor("#fefefe"); }.width('100%').justifyContent(); // Set the width and center the content Stack() { Stack() { // Create a magnified version of the gopher component Hamster({ cellWidth: * 3 }) .visibility(() ? : ); // Show or hide according to status // Display 100 Text("100") .fontColor("#9f7606")// font color .fontSize(`${ / 2 * 3}lpx`)// Font size .visibility(!() ? : )// Show or hide according to status .rotate({ // Rotate 180 degrees x: 1, y: 0, z: 0, angle: 180 }); } .borderRadius('50%') // Setting rounded corners .width(`${ * 3}lpx`) // Set the width .height(`${ * 3}lpx`) // Set the height .linearGradient({ // Set a linear gradient background direction: , colors: [['#ebcf2f', 0.0], ['#fef888', 0.5], ['#ebcf2f', 1.0]] }) .rotate({ // Rotate according to the current angle x: 1, y: 0, z: 0, angle: }) .translate({ x: 0, y: }) // Setting the longitudinal displacement .onClick(() => { // Click event handling if () { return; } = true let maxAnimationSteps = 2 * (10 + (() * 10)); // Calculate the maximum number of animations let totalAnimationDuration = 2000; // Total length of animation // First animation, throw up animateToImmediately({ duration: totalAnimationDuration / 2, // Animation is half of the total duration onFinish: () => { // Callback when the animation is complete // Second animation, falling animateToImmediately({ duration: totalAnimationDuration / 2, onFinish: () => { = % 360; // Make sure the angle is between 0 and 360 // Determine which face is currently displayed if (()) { // If it's gopher noodles ++; // Add 1 to the number of times the reverse side is up } else { // If it's the other way around ++; // Add 1 to the number of times you face up } = false } }, () => { = 0; // Reset longitudinal displacement }); } }, () => { // Set the vertical displacement to simulate the effect of a coin flip = -100 * (1 + (() * 5)); // Randomly set upward displacement }); // Looping animation to add a rotational effect for (let i = 0; i < maxAnimationSteps; i++) { animateToImmediately({ delay: i * totalAnimationDuration / maxAnimationSteps, // Set the delay for each animation duration: 100, // Duration of each animation onFinish: () => { // Callback when the animation is complete } }, () => { += 90; // Increase rotation by 90 degrees at a time }); } }); }.width('100%').layoutWeight(1).align().padding({ bottom: 80 }); // Set the component's width, weight, alignment, and bottom inner margins } .height('100%') // Set the height of the entire page .width('100%') // Set the width of the entire page .backgroundColor("#0b0d0c"); // set the background color } // Determine if the gopher face is currently displayed isHeadsFaceUp() { let normalizedAngle = % 360; // Normative perspective // Determine the angle range to show or not show the gopher surface. if (normalizedAngle >= 0 && normalizedAngle < 90 || normalizedAngle >= 270 && normalizedAngle <= 360) { return true; // show gopher face } return false; // Show reverse } }