Mobile Development 12 min read

How to Create a Rotating Color‑Changing Rounded Triangle in Android

This article walks through the mathematical analysis, coordinate calculations, and step‑by‑step Android code needed to draw an inverted equilateral triangle, animate a red/black tracing line, and rotate the shape with rounded corners, while addressing common pitfalls such as frame drops and aliasing.

Sohu Tech Products
Sohu Tech Products
Sohu Tech Products
How to Create a Rotating Color‑Changing Rounded Triangle in Android

In modern UI design, animated effects greatly improve user perception, but implementing them efficiently on Android can be challenging when the supplied assets exceed memory limits or cause performance issues. This guide demonstrates how to build a rotating, color‑changing, rounded‑corner triangle from scratch using custom drawing and property animation.

Step 1 – Draw the Inverted Equilateral Triangle

The triangle is defined by three points on its circumcircle. Using basic geometry, the coordinates are derived as follows (with the origin at the center of the view, side length a, and circumradius r = √3/3 * a):

Point A: ( -r/2, -a/2)

Point B: ( r, 0)

Point C: ( -r/2, a/2)

The following code creates the necessary fields and measures the view size (100 dp in this example):

private Paint mPaint;
private Path mPath;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
                         getDefaultSize(0, heightMeasureSpec));
    int size = DisplayUtils.dipToPx(getContext(), 100);
    widthMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
    heightMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

Coordinate initialization uses the derived formulas:

private void initCoordinate() {
    x0 = -(float) (Math.sqrt(3) * mWidth / 6);
    y0 = -mWidth / 2;
    x1 = (float) (Math.sqrt(3) * mWidth / 3);
    y1 = 0;
    x2 = -(float) (Math.sqrt(3) * mWidth / 6);
    y2 = mWidth / 2;
}

The paint is configured for anti‑aliasing, stroke style, a 2 px width, and a CornerPathEffect(10) to round the corners:

private void initPaint() {
    mPaint = new Paint();
    mPaint.setAntiAlias(true);
    mPaint.setStyle(Paint.Style.STROKE);
    mPaint.setStrokeWidth(DisplayUtils.dipToPx(mContext, 2));
    mPaint.setPathEffect(new CornerPathEffect(10));
    mColorBlack = mContext.getResources().getColor(R.color.bg_333333);
    mPaint.setColor(mColorBlack);
}

The path connects the three points and closes the shape:

private void initPath() {
    mPath = new Path();
    mPath.moveTo(x0, y0);
    mPath.lineTo(x1, y1);
    mPath.lineTo(x2, y2);
    mPath.close();
}

Step 2 – Animate the Red/Black Tracing Line

The animation progresses along the three edges sequentially, each covering one‑third of the total animation time. A dynamic point (x, y) records the current position on the path. The following logic maps the global progress (0‑1) to the appropriate segment and interpolates the coordinates:

if (progress < 0.33333333f) { // first segment
    float segmentProgress = progress / 0.33333333f;
    path.lineTo(x + Math.abs(x1 - x) * segmentProgress,
                y + Math.abs(y1 - y) * segmentProgress);
} else if (progress < 0.66666666f) { // second segment
    float segmentProgress = (progress - 0.33333333f) / 0.33333333f;
    path.lineTo(x1, y1);
    path.lineTo(x1 - Math.abs(x2 - x1) * segmentProgress,
                y1 + Math.abs(y2 - y1) * segmentProgress);
} else if (progress < 1f) { // third segment
    float segmentProgress = (progress - 0.66666666f) / 0.33333333f;
    path.lineTo(x1, y1);
    path.lineTo(x2, y2);
    path.lineTo(x2, y2 - Math.abs(y2 - y0) * segmentProgress);
} else { // complete
    path.lineTo(x1, y1);
    path.lineTo(x2, y2);
    path.lineTo(x0, y0);
    path.close();
}

Because the corners are rounded, the actual drawing distance is shortened. The offset caused by a 10 px corner radius is compensated using trigonometric calculations:

float offsetX = (float) (10 * Math.sin(Math.PI * 60 / 180));
float offsetY = (float) (10 * Math.cos(Math.PI * 60 / 180));

The segment logic is then adjusted to start and end points shifted by offsetX and offsetY, ensuring the line follows the visual edge of the rounded triangle.

Step 3 – Rotate the Triangle While Drawing

Since an equilateral triangle repeats every 120°, rotating the shape by 40° per animation fraction aligns the next corner with the previous one. The rotation angle is computed as:

float mDegree = 40 * progress; // applied to the black line

In onDraw, the canvas is translated to the view centre, rotated, and the path is rendered:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.translate(getMeasuredWidth() / 2, getMeasuredHeight() / 2);
    canvas.rotate(mDegree);
    canvas.drawPath(mPath, mPaint);
}

The final result is a smooth, rotating triangle whose outline changes color as the animated line traverses each side, with rounded corners that avoid sharp artifacts.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

animationGraphicsAndroidCanvasUI designCustom Viewpath
Sohu Tech Products
Written by

Sohu Tech Products

A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.