Обрезка объектов холста

Эта практическая работа входит в курс «Advanced Android in Kotlin». Вы получите максимальную пользу от этого курса, выполняя задания последовательно, но это не обязательно. Все практическая работа курса перечислены на целевой странице практической работы «Advanced Android in Kotlin» .

Введение

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

Например, когда вы рисуете стопку перекрывающихся карт, как показано ниже, вместо того, чтобы полностью рисовать каждую карту снизу вверх, обычно эффективнее рисовать только видимые части. «Обычно» — потому что операции обрезки также имеют свои затраты, и в целом система Android выполняет значительную оптимизацию отрисовки.

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

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

В этой лабораторной работе вам предстоит поэкспериментировать с различными способами обрезки.

Что вам уже следует знать

Вам должно быть знакомо:

  • Как создать приложение с Activity и запустить его с помощью Android Studio.
  • Как создать и рисовать на Canvas .
  • Как создать пользовательское View и переопределить onDraw() и onSizeChanged() .

Чему вы научитесь

  • Как обрезать объекты для рисования на Canvas .
  • Как сохранять и восстанавливать состояния рисования холста.
  • Как применять преобразования к холсту и тексту.

Что ты будешь делать?

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

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

Вы собираетесь создать это приложение с нуля, поэтому вам придется настроить проект, определить измерения и строки, а также объявить некоторые переменные.

Шаг 1: Создайте проект ClippingExample

  1. Создайте проект Kotlin под названием ClippingExample с шаблоном Empty Activity . Используйте префикс имени пакета com.example.android .
  2. Откройте MainActivity.kt .
  3. В методе onCreate() замените представление содержимого по умолчанию и установите его на новый экземпляр ClippedView . Это будет ваше пользовательское представление для примеров обрезки, которые вы создадите далее.
setContentView(ClippedView(this))
  1. На том же уровне, что и 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: Добавьте измерения и строковые ресурсы

  1. Определите размеры, которые вы будете использовать для обрезанных видов, в новом файле ресурсов 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 с большими значениями, который будет применяться только к большим экранам.

  1. В Android Studio щелкните правой кнопкой мыши папку значений и выберите «Создать» > «Файл ресурсов значений» .
  2. В диалоговом окне «Новый файл ресурсов» назовите файл dimens . В разделе «Доступные квалификаторы» выберите «Наименьшая ширина экрана» и нажмите кнопку >> , чтобы добавить его в список «Выбранные квалификаторы» . Введите значение 480 в поле «Наименьшая ширина экрана» и нажмите кнопку «ОК» .

  1. Файл должен отображаться в папке значений, как показано ниже.

  1. Если файл не отображается, переключитесь в представление «Файлы проекта» приложения. Полный путь к новому файлу указан ниже: ClippingExample/app/src/main/res/values-sw480dp/dimens.xml .

  1. Замените содержимое файла 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>
  1. В strings.xml добавьте следующие строки. Они будут использоваться для отображения текста на холсте.
<string name="clipping">Clipping</string>
<string name="translated">translated text</string>
<string name="skewed">"Skewed and "</string>

Шаг 3: Создайте и инициализируйте объекты Paint и Path.

  1. Вернитесь к представлению вашего проекта в версии Android .
  2. В 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)
}
  1. В ClippedView создайте и инициализируйте Path для локального хранения пути нарисованного объекта. Импортируйте android.graphics.Path .
private val path = Path()

Шаг 4: Расставляем фигуры

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

Все они имеют общие черты:

  • Большой прямоугольник (квадрат), который выполняет функцию контейнера.
  • Диагональная линия, пересекающая большой прямоугольник
  • Круг
  • Короткая строка текста

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

  1. В 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)
  1. Добавьте переменные для вставки прямоугольника и смещения небольшого прямоугольника.
private val rectInset = resources.getDimension(R.dimen.rectInset)
private val smallRectOffset = resources.getDimension(R.dimen.smallRectOffset)
  1. Добавьте переменную для радиуса окружности. Это радиус окружности, нарисованной внутри прямоугольника.
private val circleRadius = resources.getDimension(R.dimen.circleRadius)
  1. Добавьте смещение и размер текста, отображаемого внутри прямоугольника.
private val textOffset = resources.getDimension(R.dimen.textOffset)
private val textSize = resources.getDimension(R.dimen.textSize)

Шаг 4: Настройте расположение строк и столбцов

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

  1. Задайте координаты для двух столбцов.
private val columnOne = rectInset
private val columnTwo = columnOne + rectInset + clipRectRight
  1. Добавьте координаты для каждой строки, включая последнюю строку для преобразованного текста.
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)
  1. Запустите приложение. Приложение должно открыться с пустым белым экраном под названием приложения.

В onDraw() вызываются методы для рисования семи различных отсеченных прямоугольников, как показано на снимке экрана приложения ниже. Все прямоугольники рисуются одинаково; единственное отличие заключается в заданных областях отсечения и расположении на экране.

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

(1) Сначала вы перемещаете Canvas в то место, где хотите нарисовать прямоугольник. То есть, вместо того, чтобы вычислять, где нужно нарисовать следующий прямоугольник и все остальные фигуры, вы перемещаете начало координат Canvas , то есть его систему координат.

(2) Затем вы рисуете прямоугольник в новой точке начала координат холста. То есть, вы рисуете фигуры в том же месте в сдвинутой системе координат. Это гораздо проще и немного эффективнее.

(3) Наконец, вы восстанавливаете Canvas в его первоначальное Origin .

Вот алгоритм, который вы будете реализовывать:

  1. В onDraw() вызовите функцию для заполнения Canvas серым фоновым цветом и нарисуйте исходные фигуры.
  2. Вызовите функцию для каждого обрезанного прямоугольника и текста, который нужно отобразить.

Для каждого прямоугольника или текста:

  1. Сохраните текущее состояние Canvas , чтобы можно было вернуться к исходному состоянию.
  2. Переместите Origin холста в то место, где вы хотите рисовать.
  3. Применение обтравочных фигур и контуров.
  4. Нарисуйте прямоугольник или текст.
  5. Восстановить состояние Canvas .

Шаг: переопределить onDraw()

  1. Переопределите 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)
    }
  1. Создайте заглушки для каждой функции рисования, чтобы код продолжал компилироваться. Вы можете скопировать код ниже.
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()

  1. Создайте метод drawClippedRectangle() , который принимает аргумент canvas типа Canvas .
private fun drawClippedRectangle(canvas: Canvas) {
}
  1. В методе drawClippedRectangle() задайте границы прямоугольника обрезки для всей фигуры. Примените прямоугольник обрезки, ограничивающий рисование только квадрата.
canvas.clipRect(
       clipRectLeft,clipRectTop,
       clipRectRight,clipRectBottom
)

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

  1. Заполните canvas белым цветом. Да! Весь холст, ведь вы не рисуете прямоугольники, а обрезаете их! Из-за обрезающего прямоугольника заполняется только область, определяемая им, создавая белый прямоугольник. Остальная поверхность остаётся серой.
canvas.drawColor(Color.WHITE)
  1. Измените цвет на красный и нарисуйте диагональную линию внутри прямоугольника обрезки.
paint.color = Color.RED
canvas.drawLine(
   clipRectLeft,clipRectTop,
   clipRectRight,clipRectBottom,paint
)
  1. Установите зеленый цвет и нарисуйте круг внутри прямоугольника обрезки.
paint.color = Color.GREEN
canvas.drawCircle(
   circleRadius,clipRectBottom - circleRadius,
   circleRadius,paint
)
  1. Установите синий цвет и нарисуйте текст, выровненный по правому краю прямоугольника обрезки. Для этого используйте 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()

  1. Чтобы увидеть метод drawClippedRectangle() в действии, нарисуйте первый необрезанный прямоугольник, реализовав метод drawBackAndUnclippedRectangle() как показано ниже. Сохраните canvas , переместите его в положение первой строки и первого столбца, выполните рисование, вызвав метод drawClippedRectangle() , а затем верните canvas в предыдущее состояние.
private fun drawBackAndUnclippedRectangle(canvas: Canvas){
   canvas.drawColor(Color.GRAY)
   canvas.save()
   canvas.translate(columnOne,rowOne)
   drawClippedRectangle(canvas)
   canvas.restore()
}
  1. Запустите приложение. Вы увидите первый белый прямоугольник с кругом, красной линией и текстом на сером фоне.

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

Каждый из этих методов следует одному и тому же шаблону.

  1. Сохраните текущее состояние холста: canvas. save( )

Контекст активности хранит стек состояний отрисовки. Состояния отрисовки состоят из текущей матрицы преобразования и текущей области отсечения. Вы можете сохранить текущее состояние, выполнить действия, изменяющие состояние отрисовки (например, перемещение или поворот холста), а затем восстановить сохранённое состояние отрисовки. (Примечание: это похоже на команду «stash» в Git!)

Если ваш рисунок содержит преобразования, объединение преобразований в цепочку и их отмена путём их обращения подвержены ошибкам. Например, если вы перемещаете, растягиваете, а затем поворачиваете, рисунок быстро становится сложным. Вместо этого сохраните состояние холста, примените преобразования, нарисуйте и восстановите предыдущее состояние.

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

  1. Перевести начало координат холста в координаты строки/столбца: canvas. () translate

Гораздо проще переместить начало координат холста и нарисовать то же самое в новой системе координат, чем перемещать все элементы для рисования. (Совет: вы можете использовать ту же технику для вращения элементов.)

  1. Применить преобразования к path , если таковые имеются.
  2. Применить обрезку: canvas.clipPath(path)
  3. Нарисуйте фигуры: drawClippedRectangle() or drawText()
  4. Восстановить предыдущее состояние холста: canvas.restore()

Шаг 1: Реализуйте drawDifferenceClippingExample(canvas)

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

Используйте код ниже, который выполняет следующие действия:

  1. Сохраните холст.
  2. Перенесите начало координат холста в открытое пространство в первую строку, второй столбец, справа от первого прямоугольника.
  3. Применяем два отсекающих прямоугольника. Оператор DIFFERENCE вычитает второй прямоугольник из первого.
  1. Вызовите метод drawClippedRectangle() чтобы нарисовать измененный холст.
  2. Восстановите состояние холста.
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()
}
  1. Запустите приложение, и оно должно выглядеть так.

Шаг 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)

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

  1. На верхнем уровне создайте и инициализируйте переменную прямоугольника. RectF — это класс, хранящий координаты прямоугольника в формате с плавающей точкой.
private var rectF = RectF(
   rectInset,
   rectInset,
   clipRectRight - rectInset,
   clipRectBottom - rectInset
)
  1. Реализуйте функцию 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)

Рисование текста ничем не отличается от рисования любых других фигур, и к нему можно применять преобразования. Например, можно перевести текст, переместив холст и нарисовав текст.

  1. Реализуйте функцию ниже.
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()
}
  1. Запустите приложение, чтобы увидеть переведенный текст.

Шаг 8: Реализуйте drawSkewedTextExample(canvas)

Вы также можете искажать текст, то есть деформировать его различными способами.

  1. Создайте функцию ниже в 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()
}
  1. Запустите приложение, чтобы увидеть перекошенный текст, отображённый перед переведенным текстом.

Метод quickReject() Canvas позволяет проверить, будет ли указанный прямоугольник или контур полностью лежать за пределами текущих видимых областей после применения всех преобразований.

Метод quickReject() невероятно полезен, когда вы создаёте сложные чертежи и хотите сделать это как можно быстрее. С помощью quickReject() вы можете эффективно решать, какие объекты вообще не нужно рисовать, и вам не придётся писать собственную логику пересечения.

  • Метод quickReject() возвращает true если прямоугольник или контур вообще не будут видны на экране. В случае частичного перекрытия вам всё равно придётся проводить проверку самостоятельно.
  • EdgeType может быть либо AA ( сглаживание : обрабатывать края путем скругления, поскольку они могут быть сглажены), либо BW (черно-белый: обрабатывать края путем простого округления до ближайшей границы пикселя) для простого округления до ближайшего пикселя.

Существует несколько версий quickReject() , и вы также можете найти их в документации.

boolean

quickReject (float left, float top, float right, float bottom, type) Canvas.EdgeType )

boolean

quickReject ( RectF rect, Canvas.EdgeType type)

boolean

quickReject ( Path path, тип Canvas.EdgeType type)

В этом упражнении вы нарисуете новую строку под текстом и внутри clipRect , как и раньше.

  • Сначала вы вызываете quickReject() с прямоугольником inClipRectangle , который перекрывается с clipRect . В результате quickReject() возвращает false, clipRect заполняется BLACK , и отрисовывается прямоугольник inClipRectangle .

  • Затем измените код и вызовите quickReject() с notInClipRectangle . quickReject() теперь возвращает true, clipRect заполняется WHITE , а notInClipRectangle не отрисовывается.

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

Шаг: Эксперимент с quickReject()

  1. На верхнем уровне создайте переменную для координаты y дополнительной строки.
   private val rejectRow = rowFour + rectInset + 2*clipRectBottom
  1. Добавьте следующую функцию 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()
}
  1. В onDraw() раскомментируйте вызов drawQuickRejectExample() .
  2. Запустите приложение, и вы увидите черный прямоугольник, который является заполненной областью отсечения, и части inClipRectangle , поскольку два прямоугольника перекрываются, поэтому quickReject() возвращает false и отрисовывается inClipRectangle .

  1. В drawQuickRejectExample() измените код так, чтобы он запускал quickReject() для notInClipRectangle. Теперь quickReject() возвращает true , а область обрезки заполняется белым цветом.

Загрузите код для готовой лабораторной работы.

$  git clone https://github.com/googlecodelabs/android-kotlin-drawing-clipping


Кроме того, вы можете загрузить репозиторий в виде ZIP-файла, распаковать его и открыть в Android Studio.

Загрузить ZIP-архив

  • Context действия поддерживает состояние, которое сохраняет преобразования и области обрезки для Canvas .
  • Используйте canvas.save() и canvas.restore() для рисования и возврата в исходное состояние холста.
  • Чтобы нарисовать несколько фигур на холсте, можно либо рассчитать их местоположение, либо переместить (перенести) начало координат поверхности рисования. Последнее может упростить создание вспомогательных методов для повторяющихся последовательностей рисования.
  • Области отсечения могут иметь любую форму, комбинацию форм или путь.
  • Вы можете добавлять, вычитать и пересекать области отсечения, чтобы получить именно ту область, которая вам нужна.
  • Вы можете применять преобразования к тексту, преобразуя холст.
  • Метод quickReject() Canvas позволяет проверить, будет ли указанный прямоугольник или путь полностью лежать за пределами видимых в данный момент областей.

Курс Udacity:

Документация для разработчиков Android:

Также ознакомьтесь с серией статей «Графическая архитектура» для подробного объяснения того, как фреймворк Android отображает данные на экране.

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

  • При необходимости задавайте домашнее задание.
  • Объясните учащимся, как следует сдавать домашние задания.
  • Оцените домашние задания.

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

Если вы работаете с этой лабораторной работой самостоятельно, можете использовать эти домашние задания для проверки своих знаний.

Ответьте на эти вопросы

Вопрос 1

Какой метод следует использовать для эффективного исключения фигур из рисования?

excludeFromDrawing()

quickReject()

onDraw()

clipRect()

Вопрос 2

Какую информацию сохраняют и восстанавливают Canvas.save() и Canvas.restore() ?

▢ Цвет, толщина линии и т. д.

▢ Только текущие преобразования

▢ Текущие преобразования и область отсечения

▢ Только текущая область обрезки

Вопрос 3

Paint.Align определяет:

▢ Как выровнять следующие фигуры чертежа

▢ С какой стороны от источника взят текст?

▢ Где в области отсечения он выравнивается

▢ Какую сторону текста выравнивать относительно начала координат?

Ссылки на другие практические занятия по этому курсу см. на целевой странице практических занятий по курсу Advanced Android in Kotlin.