Графика в Compose

Многие приложения должны иметь возможность точно контролировать то, что отображается на экране. Это может быть просто размещение рамки или круга на экране в нужном месте, или это может быть тщательно продуманное расположение графических элементов в самых разных стилях.

Базовый рисунок с модификаторами и DrawScope

Основной способ нарисовать что-то собственное в Compose — использовать модификаторы, такие как Modifier.drawWithContent , Modifier.drawBehind и Modifier.drawWithCache .

Например, чтобы нарисовать что-то позади компонуемого объекта, вы можете использовать модификатор drawBehind чтобы начать выполнение команд рисования:

Spacer(
    modifier
= Modifier
       
.fillMaxSize()
       
.drawBehind {
           
// this = DrawScope
       
}
)

Если все, что вам нужно, это составной объект, который рисует, вы можете использовать составной объект Canvas . Составной элемент Canvas — это удобная оболочка Modifier.drawBehind . Вы размещаете Canvas в своем макете так же, как и любой другой элемент пользовательского интерфейса Compose. Внутри Canvas вы можете рисовать элементы, точно контролируя их стиль и расположение.

Все модификаторы рисования предоставляют DrawScope — среду рисования с ограниченной областью действия, которая поддерживает свое собственное состояние. Это позволяет задать параметры для группы графических элементов. DrawScope предоставляет несколько полезных полей, таких как size — объект Size , определяющий текущие размеры DrawScope .

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

Canvas(modifier = Modifier.fillMaxSize()) {
   
val canvasQuadrantSize = size / 2F
    drawRect
(
        color
= Color.Magenta,
        size
= canvasQuadrantSize
   
)
}

На белом фоне нарисован розовый прямоугольник, занимающий четверть экрана.
Рисунок 1 . Прямоугольник, нарисованный с помощью Canvas в Compose.

Дополнительные сведения о различных модификаторах рисования см. в документации по модификаторам графики .

Система координат

Чтобы нарисовать что-то на экране, вам нужно знать смещение ( x и y ) и размер вашего элемента. Во многих методах рисования в DrawScope положение и размер задаются значениями параметров по умолчанию . Параметры по умолчанию обычно размещают элемент в точке [0, 0] на холсте и предоставляют size по умолчанию, который заполняет всю область рисования, как в примере выше — вы можете видеть, что прямоугольник расположен в левом верхнем углу. Чтобы настроить размер и положение вашего элемента, вам необходимо понять систему координат в Compose.

Начало системы координат ( [0,0] ) находится в крайнем левом верхнем пикселе области рисования. x увеличивается при движении вправо, а y увеличивается при движении вниз.

Сетка, показывающая систему координат, показывающую верхний левый [0, 0] и нижний правый [ширину, высоту]
Рисунок 2 . Система координат чертежа/сетка чертежа.

Например, если вы хотите нарисовать диагональную линию из правого верхнего угла области холста в левый нижний угол, вы можете использовать функцию DrawScope.drawLine() и указать начальное и конечное смещение с соответствующим значением x. и позиции y:

Canvas(modifier = Modifier.fillMaxSize()) {
   
val canvasWidth = size.width
   
val canvasHeight = size.height
    drawLine
(
        start
= Offset(x = canvasWidth, y = 0f),
        end
= Offset(x = 0f, y = canvasHeight),
        color
= Color.Blue
   
)
}

Основные преобразования

DrawScope предлагает преобразования, позволяющие изменить место и способ выполнения команд рисования.

Шкала

Используйте DrawScope.scale() , чтобы увеличить размер операций рисования в несколько раз. Такие операции, как scale() применяются ко всем операциям рисования в соответствующей лямбде. Например, следующий код увеличивает scaleX в 10 раз и scaleY в 15 раз:

Canvas(modifier = Modifier.fillMaxSize()) {
    scale
(scaleX = 10f, scaleY = 15f) {
        drawCircle
(Color.Blue, radius = 20.dp.toPx())
   
}
}

Круг масштабирован неравномерно
Рисунок 3 . Применение операции масштабирования к кругу на Canvas.

Переводить

Используйте DrawScope.translate() для перемещения операций рисования вверх, вниз, влево или вправо. Например, следующий код перемещает рисунок на 100 пикселей вправо и на 300 пикселей вверх:

Canvas(modifier = Modifier.fillMaxSize()) {
    translate
(left = 100f, top = -300f) {
        drawCircle
(Color.Blue, radius = 200.dp.toPx())
   
}
}

Круг, сместившийся от центра
Рисунок 4 . Применение операции перевода к кругу на Canvas.

Поворот

Используйте DrawScope.rotate() , чтобы вращать операции рисования вокруг точки поворота. Например, следующий код поворачивает прямоугольник на 45 градусов:

Canvas(modifier = Modifier.fillMaxSize()) {
    rotate
(degrees = 45F) {
        drawRect
(
            color
= Color.Gray,
            topLeft
= Offset(x = size.width / 3F, y = size.height / 3F),
            size
= size / 3F
       
)
   
}
}

Телефон с прямоугольником, повернутым на 45 градусов в центре экрана.
Рисунок 5 . Мы используем rotate() , чтобы применить поворот к текущей области рисования, которая поворачивает прямоугольник на 45 градусов.

Вставка

Используйте DrawScope.inset() чтобы настроить параметры по умолчанию текущего DrawScope , изменяя границы рисунка и соответствующим образом переводя рисунки:

Canvas(modifier = Modifier.fillMaxSize()) {
   
val canvasQuadrantSize = size / 2F
    inset
(horizontal = 50f, vertical = 30f) {
        drawRect
(color = Color.Green, size = canvasQuadrantSize)
   
}
}

Этот код эффективно добавляет отступы к командам рисования:

Прямоугольник, заполненный по всему периметру
Рисунок 6 . Применение вставки к командам рисования.

Множественные преобразования

Чтобы применить к рисункам несколько преобразований, используйте функцию DrawScope.withTransform() , которая создает и применяет одно преобразование, объединяющее все желаемые изменения. Использование withTransform() более эффективно, чем выполнение вложенных вызовов отдельных преобразований, поскольку все преобразования выполняются вместе в одной операции, вместо того, чтобы Compose вычислял и сохранял каждое из вложенных преобразований.

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

Canvas(modifier = Modifier.fillMaxSize()) {
    withTransform
({
        translate
(left = size.width / 5F)
        rotate
(degrees = 45F)
   
}) {
        drawRect
(
            color
= Color.Gray,
            topLeft
= Offset(x = size.width / 3F, y = size.height / 3F),
            size
= size / 3F
       
)
   
}
}

Телефон с повернутым прямоугольником, сдвинутым в сторону экрана
Рисунок 7 . Используйте withTransform , чтобы применить как вращение, так и перемещение, вращая прямоугольник и сдвигая его влево.

Общие операции рисования

Нарисовать текст

Чтобы нарисовать текст в Compose, вы обычно можете использовать компонуемый Text . Однако если вы используете DrawScope или хотите нарисовать текст вручную с настройкой, вы можете использовать метод DrawScope.drawText() .

Чтобы нарисовать текст, создайте TextMeasurer с помощью rememberTextMeasurer и вызовите drawText с измерителем:

val textMeasurer = rememberTextMeasurer()

Canvas(modifier = Modifier.fillMaxSize()) {
    drawText
(textMeasurer, "Hello")
}

Показ приветствия, нарисованного на холсте
Рисунок 8 . Рисование текста на холсте.

Измерение текста

Рисование текста работает немного иначе, чем другие команды рисования. Обычно вы указываете команде рисования размер (ширину и высоту), в котором нужно нарисовать форму/изображение. Что касается текста, то существует несколько параметров, которые управляют размером отображаемого текста, например размер шрифта, шрифт, лигатуры и интервал между буквами.

С помощью Compose вы можете использовать TextMeasurer , чтобы получить доступ к измеренному размеру текста, в зависимости от вышеуказанных факторов. Если вы хотите нарисовать фон позади текста, вы можете использовать измеренную информацию, чтобы получить размер области, которую занимает текст:

val textMeasurer = rememberTextMeasurer()

Spacer(
    modifier
= Modifier
       
.drawWithCache {
           
val measuredText =
                textMeasurer
.measure(
                   
AnnotatedString(longTextSample),
                    constraints
= Constraints.fixedWidth((size.width * 2f / 3f).toInt()),
                    style
= TextStyle(fontSize = 18.sp)
               
)

            onDrawBehind
{
                drawRect
(pinkColor, size = measuredText.size.toSize())
                drawText
(measuredText)
           
}
       
}
       
.fillMaxSize()
)

Этот фрагмент кода создает розовый фон текста:

Многострочный текст, занимающий ⅔ полной площади, с фоновым прямоугольником.
Рисунок 9 . Многострочный текст, занимающий ⅔ полной площади, с фоновым прямоугольником.

Корректировка ограничений, размера шрифта или любого свойства, влияющего на измеренный размер, приводит к получению нового размера. Вы можете установить фиксированный размер как для width , так и height , и тогда текст будет следовать заданному TextOverflow . Например, следующий код отображает текст в ⅓ высоты и ⅓ ширины составной области и устанавливает для TextOverflow значение TextOverflow.Ellipsis :

val textMeasurer = rememberTextMeasurer()

Spacer(
    modifier
= Modifier
       
.drawWithCache {
           
val measuredText =
                textMeasurer
.measure(
                   
AnnotatedString(longTextSample),
                    constraints
= Constraints.fixed(
                        width
= (size.width / 3f).toInt(),
                        height
= (size.height / 3f).toInt()
                   
),
                    overflow
= TextOverflow.Ellipsis,
                    style
= TextStyle(fontSize = 18.sp)
               
)

            onDrawBehind
{
                drawRect
(pinkColor, size = measuredText.size.toSize())
                drawText
(measuredText)
           
}
       
}
       
.fillMaxSize()
)

Текст теперь отображается в ограничениях с многоточием в конце:

Текст нарисован на розовом фоне, многоточие обрезает текст.
Рисунок 10 . TextOverflow.Ellipsis с фиксированными ограничениями на измерение текста.

Нарисовать изображение

Чтобы нарисовать ImageBitmap с помощью DrawScope , загрузите изображение с помощью ImageBitmap.imageResource() , а затем вызовите drawImage :

val dogImage = ImageBitmap.imageResource(id = R.drawable.dog)

Canvas(modifier = Modifier.fillMaxSize(), onDraw = {
    drawImage
(dogImage)
})

Изображение собаки, нарисованное на холсте.
Рисунок 11 . Рисование ImageBitmap на холсте.

Рисуем основные формы

В DrawScope имеется множество функций рисования фигур. Чтобы нарисовать фигуру, используйте одну из предопределенных функций рисования, например drawCircle :

val purpleColor = Color(0xFFBA68C8)
Canvas(
    modifier
= Modifier
       
.fillMaxSize()
       
.padding(16.dp),
    onDraw
= {
        drawCircle
(purpleColor)
   
}
)

Нарисовать путь

Путь — это серия математических инструкций, результатом выполнения которых является рисунок. DrawScope может нарисовать путь с помощью метода DrawScope.drawPath() .

Например, предположим, что вы хотите нарисовать треугольник. Вы можете создать путь с помощью таких функций, как lineTo() и moveTo() используя размер области рисования. Затем вызовите drawPath() с этим вновь созданным путем, чтобы получить треугольник.

Spacer(
    modifier
= Modifier
       
.drawWithCache {
           
val path = Path()
            path
.moveTo(0f, 0f)
            path
.lineTo(size.width / 2f, size.height / 2f)
            path
.lineTo(size.width, 0f)
            path
.close()
            onDrawBehind
{
                drawPath
(path, Color.Magenta, style = Stroke(width = 10f))
           
}
       
}
       
.fillMaxSize()
)

Перевернутый фиолетовый треугольник пути, нарисованный в Compose
Рисунок 12 . Создание и рисование Path в Compose.

Доступ к объекту Canvas

При использовании DrawScope у вас нет прямого доступа к объекту Canvas . Вы можете использовать DrawScope.drawIntoCanvas() чтобы получить доступ к самому объекту Canvas , для которого вы можете вызывать функции.

Например, если у вас есть собственный объект Drawable , который вы хотите нарисовать на холсте, вы можете получить доступ к холсту и вызвать Drawable#draw() , передав объект Canvas :

val drawable = ShapeDrawable(OvalShape())
Spacer(
    modifier
= Modifier
       
.drawWithContent {
            drawIntoCanvas
{ canvas ->
                drawable
.setBounds(0, 0, size.width.toInt(), size.height.toInt())
                drawable
.draw(canvas.nativeCanvas)
           
}
       
}
       
.fillMaxSize()
)

Овальный черный объект ShapeDrawable в полный размер.
Рисунок 13 . Доступ к холсту для рисования Drawable .

Узнать больше

Для получения дополнительной информации о рисовании в Compose посетите следующие ресурсы:

{% дословно %}

Пока рекомендаций нет.

Попытайтесь в свой аккаунт Google.

{% дословно %}