В последней версии IntelliJ IDEA (2021.1 на момент публикации) есть классный экран-заставка, основанный на сетке, содержащей различные цветные формы, как показано ниже:

Цель этого поста - реализовать составной компонент Jetpack Compose, имитирующий этот шаблон.

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

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

Разделение холста

Чтобы Canvas вел себя как сетка, мы должны параметризовать функцию обертывания для Canvas количеством строк и столбцов для рисования:

@Composable
fun IntelliJSplashScreen(rows: Int, columns: Int, modifier: Modifier) = TODO()

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

Canvas(modifier = modifier) {
    val cellSize =
        min(this.size.width / columns, this.size.height / rows)
    for (row in 0 until rows) {
        for (column in 0 until columns) {
            translate(
                left = column * cellSize,
                top = row * cellSize
            ) {
                // TODO Draw cell
                drawIntelliJCell(cellSize)
            }
        }
    }
}

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

Типы клеток

Как мы уже говорили ранее, существует 5 различных типов клеток:

Радиус круга равен половине cellSize. Квадранты представляют собой дугу окружности под углом 90 градусов с радиусом, равным cellSize. Кажется разумным смоделировать все это в закрытом классе:

sealed class IntelliJCell {
    object Circle : IntelliJCell()
    sealed class Quadrant(val startAngle: Float, val topLeftOffset: Offset) :
        IntelliJCell() {
        object TopLeft : Quadrant(180f, Offset.Zero)
        object TopRight : Quadrant(270f, -Offset(1f, 0f))
        object BottomLeft : Quadrant(90f, -Offset(0f, 1f))
        object BottomRight : Quadrant(0f, -Offset(1f, 1f))
    }
}

Обратите внимание, что Quadrant также является закрытым классом для группировки всех 4 квадрантов. Класс параметризован двумя свойствами:

  • startAngle: представляет начальный угол дуги. 0 соответствует 3 часам, поэтому для нижнего правого угла установлен 0. SweepAngle (размер дуги, измеренный по часовой стрелке) для каждого квадранта равен 90.
  • topLeftOffset: дополнительное смещение множителя (относительно cellSize), которое нужно добавить для вычисления левого верхнего угла дуги. Для верхнего левого квадранта легко видеть, что это дополнительное смещение равно 0.

Рисование клеток

С учетом всего вышесказанного мы теперь можем определить функцию расширения drawIntelliJCell через интерфейс DrawScope для обработки рисования ячейки заданного размера:

fun DrawScope.drawIntelliJCell(cellSize: Float) = TODO()

В этом примере мы просто собираемся рандомизировать тип и цвет ячейки при каждом вызове этой функции:

val cellTypes = listOf(
    IntelliJCell.Circle,
    IntelliJCell.Quadrant.TopLeft,
    IntelliJCell.Quadrant.TopRight,
    IntelliJCell.Quadrant.BottomLeft,
    IntelliJCell.Quadrant.BottomRight
)

val cellColors = listOf(
    Color(0xFFFF7000),
    Color(0xFF007EFF),
    Color(0xFFFF0058)
)
val cell = cellTypes.random()
val color = cellColors.random()
when (cell) {
    is IntelliJCell.Circle -> // TODO Draw circle
    is IntelliJCell.Quadrant -> // TODO Draw quadrant
}

Нарисовать круговую ячейку просто, радиус составляет половину размера ячейки, а его центр расположен в середине ячейки:

drawCircle(
    color = color,
    radius = cellSize / 2,
    center = Offset(cellSize, cellSize).div(2f)
)

Для квадрантов startAngle предоставляется конкретным квадрантом, а точка topLeft - это дополнительное смещение, обеспечиваемое конкретными квадрантами. Размер в этом случае в два раза больше размера ячейки:

drawArc(
    color = color,
    startAngle = cell.startAngle,
    sweepAngle = 90f,
    useCenter = true,
    topLeft = cell.topLeftOffset.times(cellSize),
    size = Size(cellSize, cellSize).times(2f)
)

Все это вместе есть в этом Гисте. Глядя на предварительный просмотр примера компонента с 8 строками и 13 столбцами, мы получаем что-то вроде:

Следующие шаги

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

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