Site icon AppTractor

Рисуя звезды: рисование с помощью Compose и Canvas

Я всегда испытывала трудности с рисованием в Canvas, причем не только в Canvas с Compose, но и с любой другой технологией. Я понимала достаточно, чтобы выполнять необходимые задачи, но в то же время старался избегать работы с технологией.

В одно из воскресений мне захотелось заняться чем-то творческим. Мне также хотелось программировать, поэтому я начала с идеи создания загрузочного спиннера с помощью Canvas. Одно за другим, и я создала симпатичную анимацию, которая больше похожа на иллюстрацию, чем на загрузочный спиннер:

В этой и следующей статьях блога я расскажу о том, как я это сделала, и о некоторых вещах, которые я узнала на этом пути. Первая статья посвящена рисованию элементов на холсте, а следующая — анимации.

Идея

Первоначальная идея иллюстрации на космическую тематику возникла благодаря футболке, которая у меня есть. Это футболка I Need Space от компании Spark. Сначала я подумала, что могу попытаться воспроизвести ту же картинку, но когда я закончила с «Сатурном», то решила пойти дальше.

Я решила добавить другие элементы и сделать звезды по-другому. Я в восторге от того, что получилось! Давайте перейдем к созданию иллюстрации. Полный код для этой статьи вы можете найти в этом сниппете.

Компоненты

Прежде чем говорить о компонентах, я хочу отметить следующее: точные цифры, которые я здесь показываю, подходят для этого конкретного рисунка и по большей части не могут быть обобщены. Я рассчитала позиции и размеры на основе определенного холста, а не для холста с возможностью изменения размеров.

Первый компонент — это фон и сам холст. В качестве родительского компонента используется Column, а в качестве дочернего — Canvas. Это позволяет нам определить анимацию и другие свойства, которые нельзя определить в Canvas.

Для рисования фона мы используем модификатор drawBehind в родительском Column, нарисуем круглый прямоугольник и зададим радиальную градиентную кисть с некоторыми цветами:

Modifier.drawBehind {
    drawRoundRect(
        size = size,
        cornerRadius = CornerRadius(44f),
        brush =
        Brush.radialGradient(
            colors = listOf(
                Color(0xFF02010a),
                Color(0xFF04052e),
                Color(0xFF140152),
                Color(0xFF22007c),
                Color.Transparent,
            ),
            radius = size.width * 0.75f,
        ),
    )
},

На этот момент рисунок выглядит следующим образом:

Сатурн

Первая планета, которую мы добавим — это Сатурн. Чтобы нарисовать его, нам понадобятся три элемента: контур планеты, пояс и линия внутри планеты. Давайте определим функцию расширения drawSaturn, которая принимает смещение центра планеты и стиль контура и рисует контур:

fun DrawScope.drawSaturn(
    center: Offset,
    outlineStyle: Stroke,
) {
    drawCircle(
        center = center,
        color = Colors.white,
        radius = 100f,
        style = outlineStyle,
    )
}

Это круг, который использует координаты центра и стиль контура (который, кстати, представляет собой обводку шириной 4f). Мы также задаем радиус, в данном случае это жестко закодированное значение 100f.

Далее мы рисуем линию внутри планеты:

drawArc(
    topLeft = Offset(
        center.x - 80f, 
        center.y - 80f
    ),
    color = Colors.white,
    startAngle = 180f,
    sweepAngle = 90f,
    useCenter = false,
    style =
        Stroke(
            width = 2f,
        ),
    size = Size(160f, 160f),
)

Для этого мы используем функцию drawArc. Она немного отличается от функции drawCircle, которая принимает координаты центра и радиус. Для дуги нам нужно определить левую верхнюю координату для прямоугольной области вокруг дуги и размер дуги.

Угол startAngle определяет угол начала рисования, а угол sweepAngle — продолжительность рисования. Позиция 0 для углов находится в положении «три часа», поэтому мы хотим начать с угла 180f, а угол развертки — 90f, чтобы добиться нужного нам вида.

Мы также должны установить значение useCenter в true; в противном случае будет нарисована дополнительная линия через центр.

Последний элемент, завершающий Сатурн, — это пояс астероидов. Поскольку он не является полным кругом, мы снова используем drawArc. Код выглядит следующим образом:

rotate(40f, center) {
    drawArc(
        color = Colors.white,
        startAngle = 217f,
        sweepAngle = 285f,
        useCenter = false,
        topLeft = Offset(
            center.x - 50f, 
            center.y - 150f
        ),
        style = outlineStyle,
        size =
            Size(100f, 300f),
    )
}

Обратите внимание, что размер области, где нарисована дуга, не квадратный — из-за формы обода нам нужно использовать неквадратный размер, чтобы сделать ее больше похожей на эллипс, чем на круг.

На этом этапе рисунок выглядит следующим образом:

Планета

Следующий элемент, который нужно добавить — это еще одна планета. Я не хотела давать ей название и хотела сделать ее более общей, поэтому мы будем называть ее просто планетой.

Планета состоит из двух элементов: сама планета и луна, вращающаяся вокруг нее. Давайте начнем с контура планеты, который представляет собой круг:

drawCircle(
    center = center,
    color = Colors.white,
    radius = 80f,
    style = outlineStyle,
)

Этот код прост: мы рисуем окружность в позиции, заданной центром-переменной, и с радиусом 80. Затем мы рисуем линии на планете. Для этого мы используем три пути, два из которых имеют эффект пунктира, а один — сплошная линия. Код для первой линии (самой верхней) выглядит следующим образом:

drawPath(
    path =
        Path().apply {
            moveTo(center.x - 82f, center.y)
            quadraticTo(
                center.x - 45f, center.y + 5f, 
                center.x, center.y
            )
        },
    color = Colors.white,
    style =
        Stroke(
            width = 3f,
            pathEffect = PathEffect.dashPathEffect(
                floatArrayOf(60f, 10f, 50f, 10f), 
                0f
            ),
        ),
)

Остальные похожи, только числа и эффекты путей немного отличаются. Ссылку на полный код вы можете найти в начале статьи.

Итак, мы рисуем путь от края круга к центру. Поскольку линия должна быть слегка изогнутой, мы используем quadraticTo для достижения этого эффекта.

Другой компонент планеты — луна. Сначала луна рисуется в виде круга:

drawCircle(
    center = Offset(
            center.x - 100f, 
            center.y + 80f
     ),
    color = Colors.white,
    radius = 15f,
    style = outlineStyle,
)

А для линии, которая создает впечатление, что луна вращается вокруг планеты, мы используем drawArc:

drawArc(
    topLeft = Offset(center.x - 125f, center.y - 125f),
    color = Colors.white,
    startAngle = 160f,
    sweepAngle = 200f,
    useCenter = false,
    style =
        Stroke(
            width = 2f,
            pathEffect =
                PathEffect.dashPathEffect(
                    floatArrayOf(160f, 70f, 50f, 80f, 40f, 40f),
                    0f,
                ),
        ),
    size = Size(250f, 250f),
)

В дуге нет ничего нового — она берет левую верхнюю координату относительно Луны. Начальный угол составляет 160 градусов, и далее она крутится на 200 градусов. Она не использует центр, чтобы избежать третьей линии, проходящей через центр, и имеет эффект dashedPathEffect для придания внешнего вида.

На данный момент рисунок выглядит следующим образом:

Звезды

Последними элементами иллюстрации являются звезды. Давайте создадим функцию расширения для DrawScope, чтобы нарисовать звезду, а затем используем ее для рисования всех звезд.

Функция принимает размер звезды, смещение от центра, цвет звезды и стиль контура. Давайте определим ее:

fun DrawScope.drawStar(
    starSize: Size,
    center: Offset,
    color: Color,
    outlineStyle: Stroke,
) {
   ...
}

Мы используем путь с квадратичными кривыми Безье от одной начальной точки к другой. У звезды четыре точки, и если представить пространство для звезды в виде квадрата, то точки находятся в середине внешних контуров. Начнем с середины верхней стороны:

val path = Path()

path.moveTo(center.x, center.y - starSize.height * 0.5f)

После этого мы хотим нарисовать квадратичную кривую Безье от одной точки к другой и использовать центр области звезды в качестве точки кривой. Для линии от верхней стороны до правой стороны это будет выглядеть следующим образом:

path.quadraticTo(
    center.x,
    center.y,
    center.x + starSize.width / 2,
    center.y,
)

Первые два параметра — это координаты контрольной точки, а два последних — точка, в которой заканчивается линия. Остальные три линии выглядят так же — просто координаты конца меняются в зависимости от целевых координат.

После рисования этих четырех линий нам остается нарисовать путь:

drawPath(
    path = path,
    color = color,
    style = outlineStyle,
)

Теперь, когда у нас есть функция drawStar, мы можем нарисовать звезды. Мы хотим разместить их вокруг планет, и мы хотим, чтобы они были разного цвета. Нам также нужно хранить размер звезды. Давайте определим класс данных, который будет всем этим заниматься:

data class Star(
    val size: Float,
    val topLeft: Offset,
    val color: Color,
)

Затем мы можем определить список звезд. Это будет выглядеть примерно так, с примером одной звезды в списке:

val starsList =
    listOf(
        Star(
            size = 30f,
            topLeft = Offset(size.width * 0.5f, size.height * 0.5f),
            color = Colors.stars[0],
        ),
    ...
)

Мы можем задать разные размеры звезд и несколько разных цветов. Положение каждой звезды рассчитывается вручную.

Затем мы можем использовать список звезд и нарисовать их с помощью функции:

starsList.forEach { (starSize, offset, color) ->
    drawStar(
        starSize = Size(starSize, starSize),
        color = color,
        center = offset,
        outlineStyle =
            Stroke(
                width = 2f,
            ),
    )
}

После этих изменений иллюстрация выглядит следующим образом:

Итоги

В этой статье мы создали иллюстрацию с помощью Canvas. В настоящее время она статична, но в следующей статье мы анимируем элементы на иллюстрации.

Как вы оцениваете свои навыки работы с Canvas? Уверены ли вы в себе или избегаете этого?

Источник

Продолжение: Паря в космосе: анимации с помощью Compose и Canvas

Exit mobile version