[Android] 모서리 스타일 변경하기

uuranus·2024년 7월 21일
0
post-thumbnail

지금까지는 모서리가 둥근 경우만 만들어봤다.
그러나 컷팅된 모양이나 안쪽으로 둥근 모서리 스타일을 만들고 싶다면?

모서리 스타일 클래스 만들기

원하는 모서리 스타일을 설정할 수 있도록 enum class를 만들었다.

enum class CornerStyle {
    ROUNDED,
    INNER_ROUNDED,
    CUT
}

모서리 스타일에 따른 그리기 함수 나누기

둥근 모서리일 때는 cornerRadius가 0일 경우는 뾰족한 형태로 그려지기 때문에 같은 코드를 공유했다.
그러나 CUT이나 Inner_rounded 같은 경우는 arc 각도나 시작 포인트가 다르기 때문에 모서리 스타일 별로 함수를 구분하였다.

class RectangleShape(
    private val cornerStyle: CornerStyle,
    private val topStart: CornerSize,
    private val topEnd: CornerSize,
    private val bottomEnd: CornerSize,
    private val bottomStart: CornerSize,
) : Shape {
    override fun createOutline(
        size: Size,
        layoutDirection: LayoutDirection,
        density: Density,
    ): Outline {

        val path = when (cornerStyle) {
            CornerStyle.ROUNDED -> {
                drawRoundedRectangleShape(
                    size = size,
                    topStart = topStart.toPx(size, density),
                    topEnd = topEnd.toPx(size, density),
                    bottomStart = bottomStart.toPx(size, density),
                    bottomEnd = bottomEnd.toPx(size, density),
                )
            }

            CornerStyle.INNER_ROUNDED -> {
                drawInnerRoundedRectangleShape(
                    size = size,
                    topStart = topStart.toPx(size, density),
                    topEnd = topEnd.toPx(size, density),
                    bottomStart = bottomStart.toPx(size, density),
                    bottomEnd = bottomEnd.toPx(size, density),
                )
            }

            CornerStyle.CUT -> {
                drawCutRectangleShape(
                    size = size,
                    topStart = topStart.toPx(size, density),
                    topEnd = topEnd.toPx(size, density),
                    bottomStart = bottomStart.toPx(size, density),
                    bottomEnd = bottomEnd.toPx(size, density),
                )
            }

        }

        return Outline.Generic(path)
    }
}

InnerCorner

InnerCorner같은 경우는 바깥에서 그려야 하고 원의 뾰족한 모서리 지점에서 원을 그리면 그게 cornerRadius만큼 자른 게 되므로 모서리 지점이 원의 중심이 된다.

그리고 arc를 그릴 때 반시계방향으로 그려야 시작과 끝 지점이 유지가 된다.
sweepAngle을 음수값으로 지정하면 반시계방향으로 회전한다.

arcTo(
    rect = Rect(
        offset = Offset(
            -topStart,
            -topStart
        ),
        size = Size(topStart * 2, topStart * 2)
    ),
    startAngleDegrees = 90f,
    sweepAngleDegrees = -90f,
    forceMoveTo = false
)

Cut

사각형 도형인 경우

모서리의 각도를 이용해 sin, cos으로 빼고 더해주는 방식으로 시작과 끝 지점을 계산해줬다.

lineTo(
    width - skewedWidth + bottomEnd * cos(topRightAngle),
    height - bottomEnd * sin(topRightAngle)
)

star polygon의 경우

이전과 현재, 다음 지점의 좌표값을 이용하여 이전~현재 지점 or 현재~다음 지점의 거리와 cornerRadius의 길이 비율로 시작과 끝 지점을 계산해줬다.

val startPoint = if (i % 2 == 0) {
    Point(
        x - prevDx * outerCornerSize / polygonDist,
        y - prevDy * outerCornerSize / polygonDist
    )
} else {
    Point(
        x - prevDx * innerCornerSize / polygonDist,
        y - prevDy * innerCornerSize / polygonDist
    )
}

val endPoint = if (i % 2 == 0) {
    Point(
        x + nextDx * outerCornerSize / polygonDist,
        y + nextDy * outerCornerSize / polygonDist
    )
} else {
    Point(
        x + nextDx * innerCornerSize / polygonDist,
        y + nextDy * innerCornerSize / polygonDist
    )
}

최종결과

RoundedInner_roundedCut

전체코드 - InnerCorner

최종결과에 있던 사다리꼴 코드이다.

private fun drawInnerRoundedTrapezoidShape(
    size: Size,
    startSkewed: Float,
    endSkewed: Float,
    topStart: Float,
    topEnd: Float,
    bottomEnd: Float,
    bottomStart: Float,
): Path {
    val path = Path()

    val width = size.width
    val height = size.height

    val startSkewedWidth = width * startSkewed
    val endSkewedWidth = width * endSkewed

    val bottomStartAngle = atan(height / startSkewedWidth)
    val bottomEndAngle = atan(height / endSkewedWidth)
    val bottomStartAngleDegree = bottomStartAngle.toDegree()
    val bottomEndAngleDegree = bottomEndAngle.toDegree()

    path.apply {
        arcTo(
            rect = Rect(
                offset = Offset(
                    startSkewedWidth - topStart,
                    -topStart
                ),
                size = Size(topStart * 2, topStart * 2),
            ),
            startAngleDegrees = 180f - bottomStartAngleDegree,
            sweepAngleDegrees = bottomStartAngleDegree - 180f,
            forceMoveTo = false
        )

        arcTo(
            rect = Rect(
                offset = Offset(
                    width - endSkewedWidth - topEnd,
                    -topEnd
                ),
                size = Size(topEnd * 2, topEnd * 2),
            ),
            startAngleDegrees = 180f,
            sweepAngleDegrees = bottomEndAngleDegree - 180f,
            forceMoveTo = false
        )

        arcTo(
            rect = Rect(
                offset = Offset(
                    width - bottomEnd,
                    height - bottomEnd
                ),
                size = Size(bottomEnd * 2, bottomEnd * 2),
            ),
            startAngleDegrees = 180f + bottomEndAngleDegree,
            sweepAngleDegrees = -bottomEndAngleDegree,
            forceMoveTo = false
        )

        arcTo(
            rect = Rect(
                offset = Offset(
                    -bottomStart,
                    height - bottomStart
                ),
                size = Size(bottomStart * 2, bottomStart * 2),
            ),
            startAngleDegrees = 0f,
            sweepAngleDegrees = -bottomStartAngleDegree,
            forceMoveTo = false
        )

        close()
    }

    return path
}

전체코드 - Cut

최종결과에 있던 마름모 코드이다.

private fun drawCutRhombusShape(
    size: Size,
    top: Float,
    start: Float,
    end: Float,
    bottom: Float,
): Path {
    val path = Path()
    val width = size.width
    val height = size.height
    val centerX = width / 2
    val centerY = height / 2

    val dist = sqrt(centerX * centerX + centerY * centerY)

    path.apply {
        moveTo(
            centerX - top * centerX / dist,
            0f + top * centerY / dist
        )

        lineTo(
            centerX + top * centerX / dist,
            0f + top * centerY / dist
        )

        lineTo(
            width - end * centerX / dist,
            centerY - top * centerY / dist
        )

        lineTo(
            width - end * centerX / dist,
            centerY + end * centerY / dist
        )

        lineTo(
            centerX + bottom * centerX / dist,
            height - bottom * centerY / dist
        )

        lineTo(
            centerX - bottom * centerX / dist,
            height - bottom * centerY / dist
        )

        lineTo(
            start * centerX / dist,
            centerY + start * centerY / dist
        )

        lineTo(
            start * centerX / dist,
            centerY - start * centerY / dist
        )

        close()
    }

    return path
}

깃헙링크

https://github.com/uuranus/compose-shapes

profile
Frontend Developer

0개의 댓글