Эта кодовая лаборатория является частью курса Advanced Android in Kotlin. Вы получите максимальную отдачу от этого курса, если будете последовательно работать с лабораториями кода, но это не обязательно. Все кодовые лаборатории курса перечислены на целевой странице Advanced Android in Kotlin codelabs .
Введение
Для целей этой кодовой лаборатории отсечение — это способ определить области изображения, холста или растрового изображения, которые выборочно рисуются или не рисуются на экране. Одной из целей отсечения является уменьшение перерисовки . Перерисовка — это когда пиксель на экране отрисовывается более одного раза для отображения окончательного изображения. Когда вы уменьшаете перерисовку, вы минимизируете количество отрисовок пикселя или области дисплея, чтобы максимизировать производительность отрисовки. Вы также можете использовать отсечение для создания интересных эффектов в дизайне пользовательского интерфейса и анимации.
Например, когда вы рисуете стопку перекрывающихся карт, как показано ниже, вместо того, чтобы полностью рисовать каждую карту снизу вверх, обычно более эффективно рисовать только видимые части. «Обычно», потому что операции отсечения также имеют стоимость, и в целом система Android много оптимизирует отрисовку.
Чтобы рисовать только видимые части карточек, вы указываете область отсечения для каждой карточки. Например, на диаграмме ниже, когда к изображению применяется прямоугольник отсечения , отображается только часть внутри этого прямоугольника.
Область отсечения обычно представляет собой прямоугольник, но может быть любой формой или комбинацией фигур, даже текстом. Вы также можете указать, хотите ли вы, чтобы область внутри области отсечения была включена или исключена. Например, вы можете создать круглую область отсечения и отображать только то, что находится за пределами круга.
В этой кодлабе вы собираетесь поэкспериментировать с различными способами отсечения.
Что вы уже должны знать
Вы должны быть знакомы с:
- Как создать приложение с
Activity
и запустить его с помощью Android Studio. - Как создавать и рисовать на
Canvas
. - Как создать собственное
View
и переопределитьonDraw()
иonSizeChanged()
.
Что вы узнаете
- Как обрезать объекты для рисования на
Canvas
. - Как сохранять и восстанавливать состояния рисования холста.
- Как применять преобразования к холсту и к тексту.
Что ты будешь делать
- Создайте приложение, которое рисует обрезанные фигуры на экране, демонстрируя различные способы отсечения и его результат в отношении видимости этих фигур.
- Вы также нарисуете переведенный и искаженный текст.
Приложение ClippingExample демонстрирует, как можно использовать и комбинировать фигуры, чтобы указать, какие части холста отображаются в представлении. Ваше окончательное приложение будет выглядеть так, как показано на скриншоте ниже.
Вы собираетесь создать это приложение с нуля, поэтому вам нужно будет настроить проект, определить размеры и строки, а также объявить некоторые переменные.
Шаг 1: Создайте проект ClippingExample
- Создайте проект Kotlin под названием
ClippingExample
с шаблоном Empty Activity . Используйтеcom.example.android
в качестве префикса имени пакета. - Откройте
MainActivity.kt
. - В
onCreate()
замените представление содержимого по умолчанию и задайте для представления содержимого новый экземплярClippedView
. Это будет ваш пользовательский вид для примеров отсечения, которые вы создадите дальше.
setContentView(ClippedView(this))
- На том же уровне, что и
MainActivity.kt
, создайте новый файл Kotlin и класс для пользовательского представления с именемClippedView
, которое расширяетView
. Дайте ему подпись, показанную ниже. Остальная часть вашей работы будет внутри этогоClippedView
. Аннотация@JvmOverloads
указывает компилятору Kotlin генерировать перегрузки для этой функции, которые заменяют значения параметров по умолчанию.
class ClippedView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
}
Шаг 2. Добавьте размеры и строковые ресурсы
- Определите размеры, которые вы будете использовать для обрезанных представлений, в новом файле ресурсов в
res/values/dimens.xml
. Эти размеры по умолчанию жестко закодированы и рассчитаны на довольно маленький экран.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="clipRectRight">90dp</dimen>
<dimen name="clipRectBottom">90dp</dimen>
<dimen name="clipRectTop">0dp</dimen>
<dimen name="clipRectLeft">0dp</dimen>
<dimen name="rectInset">8dp</dimen>
<dimen name="smallRectOffset">40dp</dimen>
<dimen name="circleRadius">30dp</dimen>
<dimen name="textOffset">20dp</dimen>
<dimen name="strokeWidth">4dp</dimen>
<dimen name="textSize">18sp</dimen>
</resources>
Чтобы приложение хорошо выглядело на большом экране (и для более легкого просмотра деталей), вы можете создать файл dimens
с большими значениями, которые применимы только к большим экранам.
- В Android Studio щелкните правой кнопкой мыши папку значений и выберите « Создать» > «Файл ресурсов значений ».
- В диалоговом окне New Resource File назовите файл
dimens
. В Доступных квалификаторах выберите Наименьшая ширина экрана и нажмите кнопку >> , чтобы добавить ее к выбранным квалификаторам . Введите 480 в поле « Наименьшая ширина экрана » и нажмите « ОК ».
- Файл должен отображаться в папке значений, как показано ниже.
- Если вы не видите файл, переключитесь в представление « Файлы проекта» приложения. Полный путь к новому файлу показан ниже:
ClippingExample/app/src/main/res/values-sw480dp/dimens.xml
.
- Замените содержимое файла
values-sw480dp/dimens.xml
по умолчанию указанными ниже размерами.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="clipRectRight">120dp</dimen>
<dimen name="clipRectBottom">120dp</dimen>
<dimen name="rectInset">10dp</dimen>
<dimen name="smallRectOffset">50dp</dimen>
<dimen name="circleRadius">40dp</dimen>
<dimen name="textOffset">25dp</dimen>
<dimen name="strokeWidth">6dp</dimen>
</resources>
- В
strings.xml
добавьте следующие строки. Они будут использоваться для отображения текста на холсте.
<string name="clipping">Clipping</string>
<string name="translated">translated text</string>
<string name="skewed">"Skewed and "</string>
Шаг 3: Создайте и инициализируйте объекты Paint и Path
- Вернитесь к Android -представлению вашего проекта.
- В
ClippedView
определите переменнуюPaint
для рисования. Включите сглаживание и используйте ширину обводки и размер текста, указанные в размерах, как показано ниже.
private val paint = Paint().apply {
// Smooth out edges of what is drawn without affecting shape.
isAntiAlias = true
strokeWidth = resources.getDimension(R.dimen.strokeWidth)
textSize = resources.getDimension(R.dimen.textSize)
}
- В
ClippedView
создайте и инициализируйтеPath
для локального хранения пути того, что было нарисовано. Импортируйтеandroid.graphics.Path
.
private val path = Path()
Шаг 4: Настройте фигуры
В этом приложении вы отображаете несколько строк и два столбца фигур, обрезанных различными способами.
Все они имеют общее:
- Большой прямоугольник (квадрат), который действует как контейнер
- Диагональная линия через большой прямоугольник
- Круг
- Короткая строка текста
На этом шаге вы устанавливаете размеры для этих фигур из ресурсов, так что вам нужно будет получить размеры только один раз, когда вы будете использовать их позже.
- В
ClippedView
подpath
добавьте переменные для размеров обрезающего прямоугольника вокруг всего набора фигур.
private val clipRectRight = resources.getDimension(R.dimen.clipRectRight)
private val clipRectBottom = resources.getDimension(R.dimen.clipRectBottom)
private val clipRectTop = resources.getDimension(R.dimen.clipRectTop)
private val clipRectLeft = resources.getDimension(R.dimen.clipRectLeft)
- Добавьте переменные для вставки прямоугольника и смещения маленького прямоугольника.
private val rectInset = resources.getDimension(R.dimen.rectInset)
private val smallRectOffset = resources.getDimension(R.dimen.smallRectOffset)
- Добавьте переменную для радиуса круга. Это радиус окружности, нарисованной внутри прямоугольника.
private val circleRadius = resources.getDimension(R.dimen.circleRadius)
- Добавьте смещение и размер текста для текста, нарисованного внутри прямоугольника.
private val textOffset = resources.getDimension(R.dimen.textOffset)
private val textSize = resources.getDimension(R.dimen.textSize)
Шаг 4. Настройте расположение строк и столбцов
Формы для этого приложения отображаются в двух столбцах и четырех строках в зависимости от значений параметров, установленных выше. Математика для этого не является частью этой лаборатории кода, но взгляните на нее, копируя код, указанный на этом шаге.
- Задайте координаты для двух столбцов.
private val columnOne = rectInset
private val columnTwo = columnOne + rectInset + clipRectRight
- Добавьте координаты для каждой строки, включая последнюю строку для преобразованного текста.
private val rowOne = rectInset
private val rowTwo = rowOne + rectInset + clipRectBottom
private val rowThree = rowTwo + rectInset + clipRectBottom
private val rowFour = rowThree + rectInset + clipRectBottom
private val textRow = rowFour + (1.5f * clipRectBottom)
- Запустите свое приложение. Приложение должно открыться с пустым белым экраном под названием приложения.
В onDraw()
вы вызываете методы для рисования семи разных обрезанных прямоугольников, как показано на снимке экрана ниже. Все прямоугольники нарисованы одинаково; единственная разница заключается в их определенных областях отсечения и расположении на экране.
Алгоритм, используемый для рисования прямоугольников, работает, как показано на диаграмме и в пояснении ниже. Таким образом, вы рисуете серию прямоугольников, перемещая начало координат Canvas
. Концептуально это состоит из следующих шагов:
(1) Во-первых, вы переводите Canvas
туда, где вы хотите, чтобы прямоугольник был нарисован. То есть вместо того, чтобы вычислять, где нужно нарисовать следующий прямоугольник и все остальные фигуры, вы перемещаете начало координат Canvas
, то есть его систему координат.
(2) Затем вы рисуете прямоугольник в новом начале координат холста. То есть вы рисуете фигуры в одном и том же месте в переведенной системе координат. Это намного проще и немного эффективнее.
(3) Наконец, вы восстанавливаете Canvas
до его исходного Origin
.
Вот алгоритм, как вы будете его реализовывать:
- В
onDraw()
вызовите функцию, чтобы заполнитьCanvas
серым цветом фона и нарисовать исходные фигуры. - Вызовите функцию для каждого обрезанного прямоугольника и текста для рисования.
Для каждого прямоугольника или текста:
- Сохраните текущее состояние
Canvas
, чтобы вы могли вернуться к исходному состоянию. -
Origin
начало холста в то место, где вы хотите рисовать. - Примените обтравочные формы и контуры.
- Нарисуйте прямоугольник или текст.
- Восстановите состояние
Canvas
.
Шаг: переопределить onDraw()
- Переопределите
onDraw()
, как показано в коде ниже. Вы вызываете функцию для каждой рисуемой фигуры, которую реализуете позже.
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
drawBackAndUnclippedRectangle(canvas)
drawDifferenceClippingExample(canvas)
drawCircularClippingExample(canvas)
drawIntersectionClippingExample(canvas)
drawCombinedClippingExample(canvas)
drawRoundedRectangleClippingExample(canvas)
drawOutsideClippingExample(canvas)
drawSkewedTextExample(canvas)
drawTranslatedTextExample(canvas)
// drawQuickRejectExample(canvas)
}
- Создайте заглушки для каждой из функций рисования, чтобы код продолжал компилироваться. Вы можете скопировать код ниже.
private fun drawBackAndUnclippedRectangle(canvas: Canvas){
}
private fun drawDifferenceClippingExample(canvas: Canvas){
}
private fun drawCircularClippingExample(canvas: Canvas){
}
private fun drawIntersectionClippingExample(canvas: Canvas){
}
private fun drawCombinedClippingExample(canvas: Canvas){
}
private fun drawRoundedRectangleClippingExample(canvas: Canvas){
}
private fun drawOutsideClippingExample(canvas: Canvas){
}
private fun drawTranslatedTextExample(canvas: Canvas){
}
private fun drawSkewedTextExample(canvas: Canvas){
}
private fun drawQuickRejectExample(canvas: Canvas){
}
Приложение рисует один и тот же прямоугольник и фигуры семь раз, сначала без отсечения, а затем шесть раз с применением различных контуров отсечения. Метод drawClippedRectangle()
выделяет код для рисования одного прямоугольника, как показано ниже.
Шаг 1: Создайте метод drawClippedRectangle()
- Создайте метод
drawClippedRectangle()
, который принимает в качестве аргументаcanvas
типаCanvas
.
private fun drawClippedRectangle(canvas: Canvas) {
}
- Внутри
drawClippedRectangle()
установите границы прямоугольника отсечения для всей фигуры. Примените обтравочный прямоугольник, ограничивающий рисование только квадрата.
canvas.clipRect(
clipRectLeft,clipRectTop,
clipRectRight,clipRectBottom
)
Метод Canvas.clipRect(...)
уменьшает область экрана, в которую могут записывать будущие операции рисования. Он устанавливает границы отсечения как пространственное пересечение текущего прямоугольника отсечения и прямоугольника, переданного в clipRect()
. Существует множество вариантов метода clipRect()
, которые принимают различные формы для областей и позволяют выполнять различные операции с прямоугольником отсечения.
- Залейте
canvas
белым цветом. Да! Весь холст, потому что вы рисуете не прямоугольники, а вырезаете! Из-за прямоугольника отсечения заполняется только область, определяемая прямоугольником отсечения, создавая белый прямоугольник. Остальная поверхность остается серой.
canvas.drawColor(Color.WHITE)
- Измените цвет на красный и нарисуйте диагональную линию внутри обтравочного прямоугольника.
paint.color = Color.RED
canvas.drawLine(
clipRectLeft,clipRectTop,
clipRectRight,clipRectBottom,paint
)
- Установите зеленый цвет и нарисуйте круг внутри обтравочного прямоугольника.
paint.color = Color.GREEN
canvas.drawCircle(
circleRadius,clipRectBottom - circleRadius,
circleRadius,paint
)
- Установите синий цвет и нарисуйте текст, выровненный по правому краю прямоугольника обрезки. Используйте
canvas.drawText()
для рисования текста.
paint.color = Color.BLUE
// Align the RIGHT side of the text with the origin.
paint.textSize = textSize
paint.textAlign = Paint.Align.RIGHT
canvas.drawText(
context.getString(R.string.clipping),
clipRectRight,textOffset,paint
)
Шаг 2: Реализуйте метод drawBackAndUnclippedRectangle()
- Чтобы увидеть метод
drawClippedRectangle()
в действии, нарисуйте первый необрезанный прямоугольник, реализуя методdrawBackAndUnclippedRectangle()
, как показано ниже. Сохранитеcanvas
, переведите его в первую строку и столбец, нарисуйте, вызвавdrawClippedRectangle()
, а затем восстановитеcanvas
в его предыдущее состояние.
private fun drawBackAndUnclippedRectangle(canvas: Canvas){
canvas.drawColor(Color.GRAY)
canvas.save()
canvas.translate(columnOne,rowOne)
drawClippedRectangle(canvas)
canvas.restore()
}
- Запустите свое приложение. Вы должны увидеть первый белый прямоугольник с кругом, красной линией и текстом на сером фоне.
В следующих примерах методов отсечения вы применяете различные комбинации областей отсечения для достижения графических эффектов и узнаете, как можно комбинировать области отсечения для создания любой формы, которая вам нужна.
Каждый из этих методов следует одной и той же схеме.
- Сохраните текущее состояние холста:
canvas.
save(
)
Контекст действия поддерживает стек состояний рисования. Состояния рисования состоят из текущей матрицы преобразования и текущей области отсечения. Вы можете сохранить текущее состояние, выполнить действия, изменяющие состояние рисования (например, перемещение или поворот холста), а затем восстановить сохраненное состояние рисования. (Примечание: это похоже на команду «stash» в git!).
Когда ваш рисунок включает в себя преобразования, объединение и отмена преобразований путем их реверсирования подвержены ошибкам. Например, если вы переводите, растягиваете, а затем поворачиваете, это быстро усложняется. Вместо этого сохраните состояние холста, примените преобразования, нарисуйте, а затем восстановите предыдущее состояние.
Например, вы можете определить область отсечения и сохранить это состояние. Затем переместите холст, добавьте область отсечения и поверните. Сделав некоторый рисунок, вы можете восстановить исходное состояние отсечения и приступить к другому перемещению и преобразованию с наклоном, как показано на диаграмме.
- Переведите начало холста в координаты строки/столбца:
canvas.
translate
()
Гораздо проще переместить начало холста и нарисовать то же самое в новой системе координат, чем перемещать все элементы для рисования. (Совет: вы можете использовать ту же технику для вращения элементов.)
- Примените преобразования к
path
, если они есть. - Применить отсечение:
canvas.clipPath(path)
- Нарисуйте фигуры:
drawClippedRectangle() or drawText()
- Восстановить предыдущее состояние холста:
canvas.restore()
Шаг 1: Реализуйте drawDifferenceClippingExample(canvas)
Добавьте код для рисования второго прямоугольника, который использует разницу между двумя обрезающими прямоугольниками для создания эффекта рамки изображения.
Используйте код ниже, который делает следующее:
- Сохраните холст.
- Перенесите начало холста в открытое пространство в первую строку, второй столбец, правее первого прямоугольника.
- Примените два обтравочных прямоугольника. Оператор
DIFFERENCE
вычитает второй прямоугольник из первого.
- Вызовите метод
drawClippedRectangle()
, чтобы нарисовать измененный холст. - Восстановите состояние холста.
private fun drawDifferenceClippingExample(canvas: Canvas) {
canvas.save()
// Move the origin to the right for the next rectangle.
canvas.translate(columnTwo,rowOne)
// Use the subtraction of two clipping rectangles to create a frame.
canvas.clipRect(
2 * rectInset,2 * rectInset,
clipRectRight - 2 * rectInset,
clipRectBottom - 2 * rectInset
)
// The method clipRect(float, float, float, float, Region.Op
// .DIFFERENCE) was deprecated in API level 26. The recommended
// alternative method is clipOutRect(float, float, float, float),
// which is currently available in API level 26 and higher.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O){
canvas.clipRect(
4 * rectInset,4 * rectInset,
clipRectRight - 4 * rectInset,
clipRectBottom - 4 * rectInset,
Region.Op.DIFFERENCE
)
} else {
canvas.clipOutRect(
4 * rectInset,4 * rectInset,
clipRectRight - 4 * rectInset,
clipRectBottom - 4 * rectInset
)
}
drawClippedRectangle(canvas)
canvas.restore()
}
- Запустите ваше приложение, и оно должно выглядеть так.
Шаг 2: Реализуйте drawCircularClippingExample(canvas)
Затем добавьте код для рисования прямоугольника, который использует круглую область отсечения, созданную из кругового пути, по существу удаляя (не рисуя) круг и, таким образом, отображая вместо этого серый фон.
private fun drawCircularClippingExample(canvas: Canvas) {
canvas.save()
canvas.translate(columnOne, rowTwo)
// Clears any lines and curves from the path but unlike reset(),
// keeps the internal data structure for faster reuse.
path.rewind()
path.addCircle(
circleRadius,clipRectBottom - circleRadius,
circleRadius,Path.Direction.CCW
)
// The method clipPath(path, Region.Op.DIFFERENCE) was deprecated in
// API level 26. The recommended alternative method is
// clipOutPath(Path), which is currently available in
// API level 26 and higher.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
canvas.clipPath(path, Region.Op.DIFFERENCE)
} else {
canvas.clipOutPath(path)
}
drawClippedRectangle(canvas)
canvas.restore()
}
Шаг 3: Реализуйте drawIntersectionClippingExample(canvas)
Затем добавьте код для рисования пересечения двух прямоугольников отсечения во второй строке и столбце.
Обратите внимание, что в зависимости от разрешения экрана внешний вид этой области может различаться. Поэкспериментируйте с smallRectOffset
, чтобы изменить размер видимой области. Меньшее smallRectOffset
приводит к увеличению области на экране.
private fun drawIntersectionClippingExample(canvas: Canvas) {
canvas.save()
canvas.translate(columnTwo,rowTwo)
canvas.clipRect(
clipRectLeft,clipRectTop,
clipRectRight - smallRectOffset,
clipRectBottom - smallRectOffset
)
// The method clipRect(float, float, float, float, Region.Op
// .INTERSECT) was deprecated in API level 26. The recommended
// alternative method is clipRect(float, float, float, float), which
// is currently available in API level 26 and higher.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
canvas.clipRect(
clipRectLeft + smallRectOffset,
clipRectTop + smallRectOffset,
clipRectRight,clipRectBottom,
Region.Op.INTERSECT
)
} else {
canvas.clipRect(
clipRectLeft + smallRectOffset,
clipRectTop + smallRectOffset,
clipRectRight,clipRectBottom
)
}
drawClippedRectangle(canvas)
canvas.restore()
}
Шаг 4: Реализуйте drawCombinedClippingExample(canvas)
Затем объедините фигуры, круг и прямоугольник и нарисуйте любой путь, чтобы определить область отсечения.
private fun drawCombinedClippingExample(canvas: Canvas) {
canvas.save()
canvas.translate(columnOne, rowThree)
path.rewind()
path.addCircle(
clipRectLeft + rectInset + circleRadius,
clipRectTop + circleRadius + rectInset,
circleRadius,Path.Direction.CCW
)
path.addRect(
clipRectRight / 2 - circleRadius,
clipRectTop + circleRadius + rectInset,
clipRectRight / 2 + circleRadius,
clipRectBottom - rectInset,Path.Direction.CCW
)
canvas.clipPath(path)
drawClippedRectangle(canvas)
canvas.restore()
}
Шаг 5: Реализуйте drawRoundedRectangleClippingExample(canvas)
Затем добавьте прямоугольник со скругленными углами, который является часто используемой формой обрезки.
- На верхнем уровне создайте и инициализируйте переменную прямоугольника.
RectF
— это класс, который хранит координаты прямоугольника с плавающей запятой.
private var rectF = RectF(
rectInset,
rectInset,
clipRectRight - rectInset,
clipRectBottom - rectInset
)
-
drawRoundedRectangleClippingExample()
. ФункцияaddRoundRect()
принимает прямоугольник, значения x и y для углового радиуса и направление наматывания контура скругленного прямоугольника.Path.Direction
указывает, как замкнутые формы (например, прямоугольники, овалы) ориентированы при добавлении к контуру.CCW
означает против часовой стрелки.
private fun drawRoundedRectangleClippingExample(canvas: Canvas) {
canvas.save()
canvas.translate(columnTwo,rowThree)
path.rewind()
path.addRoundRect(
rectF,clipRectRight / 4,
clipRectRight / 4, Path.Direction.CCW
)
canvas.clipPath(path)
drawClippedRectangle(canvas)
canvas.restore()
}
Шаг 6: Реализуйте drawOutsideClippingExample(canvas)
Обрежьте внешнюю часть прямоугольника, удвоив вставки обрезающего прямоугольника.
private fun drawOutsideClippingExample(canvas: Canvas) {
canvas.save()
canvas.translate(columnOne,rowFour)
canvas.clipRect(2 * rectInset,2 * rectInset,
clipRectRight - 2 * rectInset,
clipRectBottom - 2 * rectInset)
drawClippedRectangle(canvas)
canvas.restore()
}
Шаг 7: Реализуйте drawTranslatedTextExample(canvas)
Рисование текста на самом деле не отличается от любых других фигур, и к тексту можно применять преобразования. Например, вы можете перевести текст, переведя холст и нарисовав текст.
- Реализуйте функцию ниже.
private fun drawTranslatedTextExample(canvas: Canvas) {
canvas.save()
paint.color = Color.GREEN
// Align the RIGHT side of the text with the origin.
paint.textAlign = Paint.Align.LEFT
// Apply transformation to canvas.
canvas.translate(columnTwo,textRow)
// Draw text.
canvas.drawText(context.getString(R.string.translated),
clipRectLeft,clipRectTop,paint)
canvas.restore()
}
- Запустите приложение, чтобы увидеть переведенный текст.
Шаг 8: Реализуйте drawSkewedTextExample(canvas)
Вы также можете наклонять текст. То есть искажать его различными способами.
- Создайте приведенную ниже функцию в
ClippedView
.
private fun drawSkewedTextExample(canvas: Canvas) {
canvas.save()
paint.color = Color.YELLOW
paint.textAlign = Paint.Align.RIGHT
// Position text.
canvas.translate(columnTwo, textRow)
// Apply skew transformation.
canvas.skew(0.2f, 0.3f)
canvas.drawText(context.getString(R.string.skewed),
clipRectLeft, clipRectTop, paint)
canvas.restore()
}
- Запустите приложение, чтобы увидеть перекошенный текст, нарисованный перед переведенным текстом.
Метод quickReject()
Canvas
позволяет проверить, будет ли указанный прямоугольник или путь находиться полностью за пределами видимых в данный момент областей после применения всех преобразований.
Метод quickReject()
невероятно полезен, когда вы создаете более сложные чертежи и вам нужно сделать это как можно быстрее. С помощью quickReject()
вы можете эффективно решить, какие объекты вам вообще не нужно рисовать, и нет необходимости писать собственную логику пересечения.
- Метод
quickReject()
возвращает значениеtrue
, если прямоугольник или контур вообще не будут видны на экране. Для частичных перекрытий вам все равно придется выполнять собственную проверку. -
EdgeType
— либоAA
( сглаживание : обработка краев путем скругления, поскольку они могут быть сглажены), либоBW
(черно-белый: обработка краев путем простого округления до границы ближайшего пикселя) для простого округления до ближайшего пикселя.
Существует несколько версий quickReject()
, и вы также можете найти их в документации.
| quickReject |
| quickReject |
| quickReject |
В этом упражнении вы будете рисовать новую строку под текстом и внутри clipRect
, как и раньше.
- Сначала вы вызываете
quickReject()
с прямоугольникомinClipRectangle
, который перекрывается сclipRect
. ИтакquickReject()
возвращает false,clipRect
заполняетсяBLACK
, и рисуется прямоугольникinClipRectangle
.
- Затем измените код и вызовите
quickReject()
сnotInClipRectangle
.quickReject()
теперь возвращает true, аclipRect
заполняетсяWHITE
, аnotInClipRectangle
не рисуется.
Когда у вас есть сложные рисунки, это может быстро сказать вам, какие фигуры полностью находятся за пределами области отсечения, а для каких вам, возможно, придется выполнять дополнительные вычисления и рисовать, потому что они частично или полностью находятся внутри области отсечения.
Шаг: поэкспериментируйте с quickReject()
- На верхнем уровне создайте переменную для координат y дополнительной строки.
private val rejectRow = rowFour + rectInset + 2*clipRectBottom
- Добавьте следующую
drawQuickRejectExample()
вClippedView
. Прочтите код, так как он содержит все, что вам нужно знать для использованияquickReject()
.
private fun drawQuickRejectExample(canvas: Canvas) {
val inClipRectangle = RectF(clipRectRight / 2,
clipRectBottom / 2,
clipRectRight * 2,
clipRectBottom * 2)
val notInClipRectangle = RectF(RectF(clipRectRight+1,
clipRectBottom+1,
clipRectRight * 2,
clipRectBottom * 2))
canvas.save()
canvas.translate(columnOne, rejectRow)
canvas.clipRect(
clipRectLeft,clipRectTop,
clipRectRight,clipRectBottom
)
if (canvas.quickReject(
inClipRectangle, Canvas.EdgeType.AA)) {
canvas.drawColor(Color.WHITE)
}
else {
canvas.drawColor(Color.BLACK)
canvas.drawRect(inClipRectangle, paint
)
}
canvas.restore()
}
- В
onDraw()
раскомментируйте вызовdrawQuickRejectExample()
. - Запустите ваше приложение, и вы увидите черный прямоугольник, который представляет собой заполненную область отсечения, и части
inClipRectangle
, потому что два прямоугольника перекрываются, поэтомуquickReject()
возвращаетfalse
, аinClipRectangle
рисуется.
- В
drawQuickRejectExample()
измените код для запускаquickReject()
противnotInClipRectangle.
ТеперьquickReject()
возвращаетtrue
, а область отсечения заполняется белым цветом.
Скачайте код для готовой кодлабы..
$ git clone https://github.com/googlecodelabs/android-kotlin-drawing-clipping
Кроме того, вы можете загрузить репозиторий в виде Zip-файла, разархивировать его и открыть в Android Studio.
-
Context
действия поддерживает состояние, в котором сохраняются преобразования и области отсечения дляCanvas
. - Используйте
canvas.save()
иcanvas.restore()
для рисования и возврата к исходному состоянию вашего холста. - Чтобы нарисовать несколько фигур на холсте, вы можете либо вычислить их местоположение, либо переместить (переместить) исходную точку поверхности рисования. Последнее может упростить создание служебных методов для повторяющихся последовательностей отрисовки.
- Области отсечения могут иметь любую форму, комбинацию форм или путь.
- Вы можете добавлять, вычитать и пересекать области обрезки, чтобы получить именно ту область, которая вам нужна.
- Вы можете применять преобразования к тексту, трансформируя холст.
- Метод
quickReject()
Canvas
позволяет вам проверить, будет ли указанный прямоугольник или путь находиться полностью за пределами видимых в данный момент областей.
Удасити курс:
Документация для разработчиков Android:
-
Canvas
класс - Класс
Bitmap
-
View
класс - Класс
Paint
- Конфигурации
Bitmap.config
- Операторы
Region.Op
- Класс
Path
-
Canvas
класс - Класс
Bitmap
-
View
класс - Класс
Paint
- Конфигурации
Bitmap.config
- Операторы
Region.Op
- Класс
Path
- графические инструменты
android.graphics
- Конфигурации
Canvas
Bitmap.Config
- Холст и чертежи
- Что делает canvas.translate()
- Понимание save() и restore() для контекста Canvas
- вырезка
- перерасход .
-
@JvmOverloads
Также см. серию статей « Графическая архитектура » для подробного объяснения того, как инфраструктура Android отрисовывается на экране.
В этом разделе перечислены возможные домашние задания для студентов, которые работают с этой кодовой лабораторией в рамках курса, проводимого инструктором. Инструктор должен сделать следующее:
- При необходимости задайте домашнее задание.
- Объясните учащимся, как сдавать домашние задания.
- Оценивайте домашние задания.
Преподаватели могут использовать эти предложения так мало или так часто, как они хотят, и должны свободно давать любые другие домашние задания, которые они считают подходящими.
Если вы работаете с этой кодовой лабораторией самостоятельно, не стесняйтесь использовать эти домашние задания, чтобы проверить свои знания.
Ответьте на эти вопросы
Вопрос 1
Какой метод вы используете для эффективного исключения фигур из рисования?
▢ исключить из excludeFromDrawing()
▢ быстрый quickReject()
▢ при onDraw()
▢ clipRect()
вопрос 2
Canvas.save()
и Canvas.restore()
какую информацию сохраняют и восстанавливают?
▢ Цвет, ширина линии и т. д.
▢ Только текущие преобразования
▢ Текущие преобразования и область отсечения
▢ Только текущая область отсечения
Вопрос 3
Paint.Align
указывает:
▢ Как выровнять следующие фигуры на чертеже
▢ С какой стороны исходной точки нарисован текст
▢ Где в области отсечения он выровнен
▢ Какую сторону текста выровнять по исходной точке
Ссылки на другие лаборатории кода в этом курсе см. на целевой странице Advanced Android in Kotlin codelabs.