4.9 HarmonyOS Custom Components – Lucky Draw (source code included)

Author: Han Ru

Company: Program Ka (Beijing) Technology Co., LTD

Hong Meng Bus columnist

I. Project introduction

When the components provided by the system cannot meet design requirements, you can create custom components, customize their properties and response events, and draw components based on design requirements. A customized component is drawn on two customized layers reserved for the component. The addDrawTask method is used to add the drawing task, and the component is combined with other layers to display in the interface.

Implementation idea:

  1. Create a custom Component class that inherits Component or its subclasses, adding constructors.
  2. Implement component. DrawTask interface, draw in onDraw method.
  3. Select the interface to implement according to the function that the custom component needs to accomplish. Response measurement events, such as Component can be realized. EstimateSizeListener Component. TouchEventListener respond to touch events, Component. ClickedListener response to the click event, Component. L OngClickedListener response long press event, Component. DoubleClickedListener response double-click events, etc.
  4. This tutorial function of round draw rotary table to implement the following interface: a) need to implement for the screen width height and center coordinates, so implementation Component. EstimateSizeListener interface, rewrite onEstimateSize method. B) need to click the center disk area position began to draw functions, so the implementation Component. TouchEventListener, rewrite onTouchEvent method.

Note: use a custom Component implementation Component. EstimateSizeListener interface need HarmonyOS SDK version in 2.1.0.13 or above.

Ii. Project Presentation

The code engineering structure of the custom circular lottery turntable sample project is described as follows:

  • Customcomponent: LuckyCirclePanComponent CustomComponent class that draws the circular lottery wheel and implements the lottery effect.
  • Slice: MainAbilitySlice This sample tutorial start page, provides interface entry.
  • Utils: utility class
    • ColorUtils color utility class that encapsulates the RGB colors needed to draw the disk.
    • The LogUtils log printing class encapsulates HiLog logs.
    • PixelMapUtils image tool class, mainly load the local image resource, through the local image resource resourceId, the image into PixelMap type.
    • ToastUtils popup tool class, after the end of the lottery, popup the lottery result information.
  • MainAbility: Main program entry, DevEco Studio generated, no logic added, no change required.
  • MyApplication: Generated automatically by DevEco Studio without change.
  • Resources: Stores resource files used by projects
    • Resources \ Base \ Element holds the configuration file String. json automatically generated by DevEco Studio, no changes required.
    • Resources \ Base \ Graphic holds the page style file. This example tutorial is done with a custom component. No page style is defined and no changes are required.
    • Layout files in Resources base Layout. This sample tutorial is done with custom components. No page layout is defined and no changes are required.
    • This example tutorial uses 5. PNG images, which are used to set up corresponding images for prizes. Developers can prepare by themselves. Icon.png is automatically generated by DevEco Studio without change.
  • Config. json: indicates the configuration file.

Three, implementation steps

Create a package customcomponent, create a LuckyCirclePanComponent class for your customcomponent, and extend Component or its subclasses to add constructors.

/** * LuckyCirclePanComponent class, which implements custom components, draws round lotteries, and implements lotteries. * /
public class LuckyCirclePanComponent extends Component implements Component.DrawTask.Component.EstimateSizeListener.Component.TouchEventListener { 
	public LuckyCirclePanComponent(Context context) { 
		super(context); 
		this.context = context; 
		// Initialize the brush
		initPaint(); 
		// Get the width and center of the screen, and call onEstimateSize
		setEstimateSizeListener(this); 
		// Add a drawing task and call onDraw
		addDrawTask(this); 
		// Start the lottery by clicking the location of the central disk area and calling onTouchEvent
		setTouchEventListener(this); }}Copy the code

2, implement component. DrawTask interface, draw in onDraw method.

@Override 
public void onDraw(Component component, Canvas canvas) { 
	// Move the canvas a specified distance along the X and Y axes
	canvas.translate(centerX, centerY); 
	// Draw the petals of the outer disk
	drawFlower(canvas); 
	// Draw the outer disk, the small circle, the five-pointed star
	drawOutCircleAndFive(canvas); 
	// Draw the inner sector lottery area
	drawInnerArc(canvas); 
	// Draw the text in the inner sector
	drawArcText(canvas); 
	// Draw the pictures corresponding to the prizes in the inner sector
	drawImage(canvas); 
	// Draw the central disk and pointer
	drawCenter(canvas); 
}

Copy the code

3. Get the screen size and center point

Implementation Component. EstimateSizeListener interface, rewrite onEstimateSize method, to obtain the width of the screen height width, height and center coordinates centerX, centerY.

@Override 
public boolean onEstimateSize(int widthEstimateConfig, int heightEstimateConfig) { 
        int componentWidth = EstimateSpec.getSize(widthEstimateConfig); 
        int componentHeight = EstimateSpec.getSize(heightEstimateConfig); 
        this.width = componentWidth; 
        this.height = componentHeight; 
        centerX = this.width / TWO; 
        centerY = this.height / TWO; 
        setEstimatedSize( 
        
        EstimateSpec.getChildSizeWithMode(componentWidth, componentWidth, EstimateSpec.PRECISE), 
        
        EstimateSpec.getChildSizeWithMode(componentHeight, componentHeight, EstimateSpec.PRECISE) 
        ); 
        return true; 
}
Copy the code

4. Draw the outer disk

A. Draw the petals of the outer disk: Call the rotate() method of the Canvas to rotate the Canvas at A specified Angle. Make the Canvas hold the latest drawing state by calling the Canvas’s save() and restore() methods. Depending on the number of petals you want to draw, change the rotation Angle and cycle through the petals.

/** * Outer disk petals *@param canvas
 */
private void drawFlower(Canvas canvas) { 
	float beginAngle = startAngle + avgAngle; 
	float radius = centerX - padding; 
	for (int i = 0; i < COUNT; i++) { 
		canvas.save(); 
		canvas.rotate(beginAngle, 0F.0F); paintFlower.setColor(ColorUtils.PAINT_FLOWER_YELLOW); canvas.drawCircle(-radius / TWO, radius / TWO, radius / TWO, paintFlower); paintFlower.setColor(ColorUtils.PAINT_FLOWER_PINK); canvas.drawCircle(-radius / TWO, radius / TWO, (radius - padding) / TWO, paintFlower); beginAngle += avgAngle; canvas.restore(); }}Copy the code

B. Draw the outer disk: At the specified X and Y (0F, 0F) coordinates, draw a circle with radius Centerx-padding (actually draw a red disk).

paintOutCircle.setColor(ColorUtils.PAINT_OUT_CIRCLE); 
canvas.drawCircle(0F.0F, centerX - padding, paintOutCircle);

Copy the code

C. Draw small circles and five-pointed stars on the edge of the outer disk: then a for loop with increasing angles (avgAngle/THREE) will draw small circles and five-pointed stars on the circle. Because the pentagram and the circle are drawn alternately, a conditional statement is used to draw.

float beginAngle = startAngle + avgAngle / THREE; 
for (int i = 0; i < COUNT * THREE; i++) { 
	canvas.save(); 
	canvas.rotate(beginAngle, 0F.0F); 
	if (0 == i % TWO) { 
		paintOutCircle.setColor(Color.WHITE); 
		canvas.drawCircle(centerX - padding - padding / TWO, 0F, vp2px(FIVE), paintOutCircle); 
	} else { 
		paintFiveStart(canvas); 
	} 
	beginAngle += avgAngle / THREE; 
	canvas.restore(); 
}

Copy the code

D. Draw pentagram: Obtain the position of five vertices of pentagram by calculating (the Angle of each vertex of pentagram is 36°, and then calculate the coordinates of each point according to trigonometric function), then use Canvas, Path and Paint to connect the five vertices together by drawing lines, and complete the drawing of pentagram.

/** * draw the pentacle *@param canvas
     */
    private void paintFiveStart(Canvas canvas) { 
        // Draw the path of the pentacle
        Path path = new Path();
        float[] points = fivePoints(centerX - padding - padding / TWO, 0F, padding); 
        for (int i = 0; i < points.length - 1; i = i + TWO) { 
            path.lineTo(points[i], points[i + 1]); 
        } 
        path.close(); 
        canvas.drawPath(path, paintFive); 
    }
    /** * fivePoints retrieves five vertices of a pentagram **@paramPointXa Absolute X-axis position of starting point A *@paramPointYa Absolute y position of starting point A *@paramSideLength the sideLength of the pentacle *@returnPentagram 5 vertex coordinates */
    private static float[] fivePoints(float pointXa, float pointYa, float sideLength) {
        final int eighteen = 18;
        float pointXb = pointXa + sideLength / TWO;
        double num = sideLength * Math.sin(Math.toRadians(eighteen));
        float pointXc = (float) (pointXa + num);
        float pointXd = (float) (pointXa - num);
        float pointXe = pointXa - sideLength / TWO;
        float pointYb = (float) (pointYa + Math.sqrt(Math.pow(pointXc - pointXd, TWO)
                - Math.pow(sideLength / TWO, TWO)));
        float pointYc = (float) (pointYa + Math.cos(Math.toRadians(eighteen)) * sideLength);
        float pointYd = pointYc;
        float pointYe = pointYb;
        float[] points = new float[]{pointXa, pointYa, pointXd, pointYd, pointXb, pointYb,
                pointXe, pointYe, pointXc, pointYc, pointXa, pointYa};
        return points;
    }
Copy the code

5. Draw the inner fan lottery area

A. Draw A fan for the lucky draw area: Draw arcs using RectFloat and Arc objects. Rect represents the coordinates of the top left and bottom right corners of the Arc surrounding the rectangle. The parameters new Arc(startAngle, avgAngle, true) represent Arc parameters, such as starting Angle, sweep Angle, and whether a line is drawn from the two ends of the Arc to its center.

		/** * Draw the lottery area sector *@param canvas
     */
    private void drawInnerArc(Canvas canvas) { 
        float radius = Math.min(centerX, centerY) - padding * TWO; 
        RectFloat rect = new RectFloat(-radius, -radius, radius, radius);
        for (int i = 0; i < COUNT; i++) { 
            paintInnerArc.setColor(colors[i]); 
            canvas.drawArc(rect, new Arc(startAngle, avgAngle, true), paintInnerArc); startAngle += avgAngle; }}Copy the code

B. Draw lottery area text: Using Path, create draw Path, add Arc, and set the horizontal and vertical offsets. Radius/FIVE is how far the current Arc moves toward the center of the circle; Horizontal offset, i.e. clockwise rotation, horizontal offset (math.sin (avgAngle/CIRCLE * math.pi) * radius) – measureWidth/TWO, is used to center text in the current arc range. Finally, use path to draw the text.

		/** * Draw the text of the lucky draw area *@param canvas
     */
    private void drawArcText(Canvas canvas) { 
        for (int i = 0; i < COUNT; i++) { 
            // Create a draw path
            Path circlePath = new Path(); 
            float radius = Math.min(centerX, centerY) - padding * TWO; 
            RectFloat rect = new RectFloat(-radius, -radius, radius, radius); 
            circlePath.addArc(rect, startAngle, avgAngle); 
            float measureWidth = paintArcText.measureText(textArrs[i]); 
            / / the offset
            float advance = (float) ((Math.sin(avgAngle / CIRCLE * Math.PI) * radius) - measureWidth / TWO); canvas.drawTextOnPath(paintArcText, textArrs[i], circlePath, advance, radius / FIVE); startAngle += avgAngle; }}Copy the code

C. Draw the corresponding picture of the words in the lucky draw area: PixelMaps represents the array of images that the text corresponds to, ResourceId, and PixelMap. PixelMapHolderList represents the List of PixelMapHolder images that the text corresponds to. DST represents the coordinates of the PixelMapHolder object’s upper-left corner (-imageheight/TWO, imageHeight/TWO) and lower-right corner (centerX/THREE + imageWidth, centerX/THREE).

/** * Draw the text of the lucky draw area corresponding to the picture *@param canvas
     */
    private void drawImage(Canvas canvas) { 
        float beginAngle = startAngle + avgAngle / TWO; 
        for (int i = 0; i < COUNT; i++) { 
            int imageWidth = pixelMaps[i].getImageInfo().size.width; 
            int imageHeight = pixelMaps[i].getImageInfo().size.height; 
            canvas.save(); 
            canvas.rotate(beginAngle, 0F.0F); 
            // Specify the area of the image to display on the screen
            RectFloat dst = newRectFloat(centerX / THREE, -imageHeight / TWO, centerX / THREE + imageWidth, imageHeight / TWO); canvas.drawPixelMapHolderRect(pixelMapHolderList.get(i), dst, paintImage); beginAngle += avgAngle; canvas.restore(); }}// Convert pixelMap to PixelMapHolder
    private void pixelMapToPixelMapHolder(a) { 
        pixelMapHolderList = new ArrayList<>(pixelMaps.length);
        for (PixelMap pixelMap : pixelMaps) { 
            pixelMapHolderList.add(newPixelMapHolder(pixelMap)); }}Copy the code

6. Draw the central disk and pointer

A. Draw the large pointer to the center disk: Use Path to determine the coordinates of the THREE points to move (-centerx/nine, 0F), (centerX/nine, 0F), (0F, -centerx/THREE) to draw the pointer.

		/** * draw the central disk and pointer *@param canvas
     */
    private void drawCenter(Canvas canvas) { 
        final int nine = 9; 
        final int seven = 7; 
        final int eighteen = 18; 
        // Draw a large pointer
        Path path = new Path(); 
        path.moveTo(-centerX / nine, 0F); 
        path.lineTo(centerX / nine, 0F); 
        path.lineTo(0F, -centerX / THREE); 
        path.close(); 
        canvas.drawPath(path, paintPointer); 
    }
Copy the code

B. Draw the inner large circle and small circle: At the center of the circle, draw TWO central disks with radii centerX/seven + padding/TWO and centerX/seven respectively.

// Draw the inner circle
paintCenterCircle.setColor(ColorUtils.PAINT_POINTER); 
canvas.drawCircle(0F.0F, centerX / seven + padding / TWO, paintCenterCircle); 
// Draw the inner circle
paintCenterCircle.setColor(Color.WHITE); 
canvas.drawCircle(0F.0F, centerX / seven, paintCenterCircle);

Copy the code

C. Draw the small pointer to the central disk: Use Path to draw the small pointer to the central disk, similar to drawing the large pointer to the central disk in Step 1.

Path smallPath = new Path(); 
smallPath.moveTo(-centerX / eighteen, 0F); 
smallPath.lineTo(centerX / eighteen, 0F); 
smallPath.lineTo(0F, -centerX / THREE + padding / TWO); 
smallPath.close(); 
canvas.drawPath(smallPath, paintSmallPoint);

Copy the code

D. Draw center arc text: Use the getFontMetrics() method in Paint to get the recommended line spacing for the font and then draw the text according to the recommended line spacing.

Paint.FontMetrics fontMetrics = paintCenterText.getFontMetrics(); 
float textHeight = (float) Math.ceil(fontMetrics.leading - fontMetrics.ascent); 
canvas.drawText(paintCenterText, "Start".0F, textHeight / THREE);

Copy the code

7. Realize the lottery function

A. implementation Component. TouchEventListener interface, rewrite onTouchEvent method, click on the screen coordinates, when click in the center of the range of disc area, circular turntable around to draw.

@Override 
public boolean onTouchEvent(Component component, TouchEvent touchEvent) { 
	final int seven = 7; 
	switch (touchEvent.getAction()) { 
		case TouchEvent.PRIMARY_POINT_DOWN: 
			// Get the coordinates of the click on the screen
			float floatX = touchEvent.getPointerPosition(touchEvent.getIndex()).getX(); 
			float floatY = touchEvent.getPointerPosition(touchEvent.getIndex()).getY(); 
			float radius = centerX / seven + padding / TWO; 
			boolean isScopeX = centerX - radius < floatX && centerX + radius > floatX; 
			boolean isScopeY = centerY - radius < floatY && centerY + radius > floatY; 
			if(isScopeX && isScopeY && ! animatorVal.isRunning()) { startAnimator(); }break; 
		case TouchEvent.PRIMARY_POINT_UP: 
			// Release cancel
			invalidate(); 
			break; 
		default: 
			break; 
	} 
	return true; 
}

Copy the code

2. The round turntable starts to rotate: Assign a randomAngle to the rotation wheel to ensure that the Angle of each rotation is random (i.e., every prize drawn is also random), and then set the curve type of animation movement. Lottery is set here is that the Animator. CurveType. DECELERATE said animation curve of quick start and then gradually to slow down. After the end of the animation, the turntable stops turning (that is, the end of the lottery), will pop up the prize tips in the lottery.

		/** * The round wheel starts to spin */
    private void startAnimator(a) { 
        final int angle = 270; 
        startAngle = 0; 
        // Animation length
        final long animatorDuration = 4000L; 
        // Random Angle
        int randomAngle = new SecureRandom().nextInt(CIRCLE);
        animatorVal.setCurveType(Animator.CurveType.DECELERATE);
        animatorVal.setDuration(animatorDuration); 
        animatorVal.setValueUpdateListener((AnimatorValue animatorValue, float value) -> { 
            startAngle = value * (CIRCLE * FIVE - randomAngle + angle); 
            invalidate(); 
        }); 
        stateChangedListener(animatorVal, randomAngle); 
        animatorVal.start(); 
    } 
Copy the code

Four, run,

With the source code

More:

1. Community: Hongmeng Bus www.harmonybus.net/

2. HarmonyBus

3, technical exchange QQ group: 714518656

4. Video courses: www.chengxuka.com