Przycinanie obiektów Canvas

Te warsztaty są częścią kursu Zaawansowany Android w Kotlinie. Najwięcej korzyści z tego kursu uzyskasz, jeśli przejdziesz wszystkie ćwiczenia w kolejności, ale nie jest to obowiązkowe. Wszystkie ćwiczenia z tego kursu znajdziesz na stronie docelowej ćwiczeń z zaawansowanego Androida w Kotlinie.

Wprowadzenie

Na potrzeby tego laboratorium przycinanie to sposób definiowania obszarów obrazu, płótna lub mapy bitowej, które są selektywnie rysowane lub nie rysowane na ekranie. Jednym z celów przycinania jest zmniejszenie nadmiernego rysowania. Nadmierne rysowanie to sytuacja, w której piksel na ekranie jest rysowany więcej niż raz, aby wyświetlić ostateczny obraz. Zmniejszając nadmierne rysowanie, minimalizujesz liczbę rysowań piksela lub obszaru wyświetlania, aby zmaksymalizować wydajność rysowania. Przycinanie możesz też wykorzystać do tworzenia ciekawych efektów w projektowaniu interfejsu i animacji.

Na przykład podczas rysowania stosu nakładających się na siebie kart, jak pokazano poniżej, zamiast rysować każdą kartę od dołu do góry, zwykle bardziej efektywne jest rysowanie tylko widocznych części. „Zazwyczaj”, ponieważ operacje przycinania też mają swój koszt, a system Android przeprowadza wiele optymalizacji rysowania.

Aby rysować tylko widoczne części kart, dla każdej z nich określasz obszar przycinania. Na przykład na poniższym diagramie po zastosowaniu prostokąta przycinania do obrazu wyświetlana jest tylko część znajdująca się w tym prostokącie.

Obszar przycinania jest zwykle prostokątem, ale może mieć dowolny kształt lub kombinację kształtów, a nawet tekst. Możesz też określić, czy obszar wewnątrz regionu przycinania ma być uwzględniony czy wykluczony. Możesz na przykład utworzyć okrągły obszar przycinania i wyświetlać tylko to, co znajduje się poza nim.

W tym laboratorium kodu poeksperymentujesz z różnymi sposobami przycinania.

Co warto wiedzieć

Musisz znać:

  • Jak utworzyć aplikację z Activity i uruchomić ją w Android Studio.
  • Jak utworzyć i narysować Canvas.
  • Jak utworzyć niestandardowy View i zastąpić onDraw() oraz onSizeChanged().

Czego się nauczysz

  • Jak przycinać obiekty, aby rysować na Canvas.
  • Jak zapisywać i przywracać stany rysowania na kanwie.
  • Jak stosować przekształcenia na obszarze roboczym i tekście.

Jakie zadania wykonasz

  • Utwórz aplikację, która rysuje na ekranie przycięte kształty, pokazując różne sposoby przycinania i ich wpływ na widoczność tych kształtów.
  • Narysujesz też przetłumaczony i przekrzywiony tekst.

Aplikacja ClippingExample pokazuje, jak używać i łączyć kształty, aby określać, które części obszaru rysowania mają być wyświetlane w widoku. Gotowa aplikacja będzie wyglądać jak na zrzucie ekranu poniżej.

Aplikację utworzysz od podstaw, więc musisz skonfigurować projekt, zdefiniować wymiary i ciągi znaków oraz zadeklarować niektóre zmienne.

Krok 1. Utwórz projekt ClippingExample

  1. Utwórz projekt w Kotlinie o nazwie ClippingExample na podstawie szablonu Pusta aktywność. Użyj com.example.android jako prefiksu nazwy pakietu.
  2. Otwórz pokój MainActivity.kt.
  3. W metodzie onCreate() zastąp domyślny widok treści i ustaw widok treści na nową instancję ClippedView. Będzie to Twój widok niestandardowy dla przykładów wycinków, które utworzysz w następnym kroku.
setContentView(ClippedView(this))
  1. Na tym samym poziomie co MainActivity.kt utwórz nowy plik i klasę Kotlin dla widoku niestandardowego o nazwie ClippedView, który rozszerza View. Nadaj mu podpis widoczny poniżej. Pozostała część pracy będzie wykonywana w tym ClippedView. Adnotacja @JvmOverloads informuje kompilator języka Kotlin, aby wygenerował przeciążenia tej funkcji, które zastępują domyślne wartości parametrów.
class ClippedView @JvmOverloads constructor(
   context: Context,
   attrs: AttributeSet? = null,
   defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
}

Krok 2. Dodaj wymiary i zasoby tekstowe

  1. Zdefiniuj wymiary, których będziesz używać w przypadku przyciętych widoków, w nowym pliku zasobów w res/values/dimens.xml. Te domyślne wymiary są zakodowane na stałe i dopasowane do dość małego ekranu.
<?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>

Aby aplikacja dobrze wyglądała na większym ekranie (i aby łatwiej było zobaczyć szczegóły), możesz utworzyć plik dimens z większymi wartościami, który będzie stosowany tylko na większych ekranach.

  1. W Android Studio kliknij prawym przyciskiem myszy folder values i wybierz New > Values resource file (Nowy > Plik zasobów wartości).
  2. W oknie Nowy plik zasobów nadaj plikowi nazwę dimens. W sekcji Dostępne kwalifikatory wybierz Najmniejsza szerokość ekranu i kliknij przycisk >>, aby dodać go do sekcji Wybrane kwalifikatory. Wpisz 480 w polu Najmniejsza szerokość ekranu i kliknij OK.

  1. Plik powinien być widoczny w folderze wartości, jak pokazano poniżej.

  1. Jeśli nie widzisz pliku, przełącz się na widok Pliki projektu aplikacji. Pełna ścieżka do nowego pliku jest taka jak poniżej: ClippingExample/app/src/main/res/values-sw480dp/dimens.xml.

  1. Zastąp domyślną zawartość pliku values-sw480dp/dimens.xml wymiarami podanymi poniżej.
<?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. W sekcji strings.xml dodaj te ciągi znaków. Będą one używane do wyświetlania tekstu na obszarze roboczym.
<string name="clipping">Clipping</string>
<string name="translated">translated text</string>
<string name="skewed">"Skewed and "</string>

Krok 3. Utwórz i zainicjuj obiekty Paint i Path

  1. Wróć do widoku Android w projekcie.
  2. W sekcji ClippedView zdefiniuj zmienną Paint, której chcesz używać do rysowania. Włącz wygładzanie i użyj szerokości linii oraz rozmiaru tekstu zdefiniowanych w wymiarach, jak pokazano poniżej.
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 utwórz i zainicjuj Path, aby lokalnie przechowywać ścieżkę narysowanego kształtu. Importuj android.graphics.Path.
private val path = Path()

Krok 4. Skonfiguruj kształty

W tej aplikacji wyświetlasz kilka wierszy i 2 kolumny kształtów przyciętych na różne sposoby.

Wszystkie te usługi mają wspólne cechy:

  • Duży prostokąt (kwadrat) pełniący funkcję kontenera
  • linia ukośna przecinająca duży prostokąt,
  • Okrąg
  • krótki ciąg tekstu,

W tym kroku skonfigurujesz wymiary tych kształtów z zasobów, aby później pobierać je tylko raz.

  1. ClippedView poniżej path dodaj zmienne wymiarów prostokąta przycinania wokół całego zestawu kształtów.
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. Dodaj zmienne dla wcięcia prostokąta i przesunięcia małego prostokąta.
private val rectInset = resources.getDimension(R.dimen.rectInset)
private val smallRectOffset = resources.getDimension(R.dimen.smallRectOffset)
  1. Dodaj zmienną dla promienia okręgu. Jest to promień okręgu narysowanego wewnątrz prostokąta.
private val circleRadius = resources.getDimension(R.dimen.circleRadius)
  1. Dodaj przesunięcie i rozmiar tekstu, który ma być rysowany wewnątrz prostokąta.
private val textOffset = resources.getDimension(R.dimen.textOffset)
private val textSize = resources.getDimension(R.dimen.textSize)

Krok 4. Skonfiguruj lokalizacje wierszy i kolumn

Kształty tej aplikacji są wyświetlane w 2 kolumnach i 4 wierszach, co zależy od wartości wymiarów skonfigurowanych powyżej. Obliczenia matematyczne nie są częścią tych ćwiczeń, ale przyjrzyj się im podczas kopiowania kodu podanego w tym kroku.

  1. Skonfiguruj współrzędne dla 2 kolumn.
private val columnOne = rectInset
private val columnTwo = columnOne + rectInset + clipRectRight
  1. Dodaj współrzędne dla każdego wiersza, w tym ostatniego wiersza przekształconego tekstu.
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. Uruchom aplikację. Powinna się otworzyć z pustym białym ekranem pod nazwą aplikacji.

onDraw() wywołujesz metody rysowania 7 różnych przyciętych prostokątów, jak pokazano na zrzucie ekranu aplikacji poniżej. Wszystkie prostokąty są rysowane w ten sam sposób. Różnią się tylko zdefiniowanymi regionami przycinania i położeniem na ekranie.

Algorytm używany do rysowania prostokątów działa zgodnie ze schematem i wyjaśnieniem poniżej. Podsumowując, rysujesz serię prostokątów, przesuwając punkt początkowy Canvas. W teorii obejmuje to te kroki:

(1) Najpierw przesuń Canvas w miejsce, w którym chcesz narysować prostokąt. Oznacza to, że zamiast obliczać, gdzie należy narysować kolejny prostokąt i wszystkie inne kształty, przesuwasz Canvaspunkt początkowy, czyli jego układ współrzędnych.

(2) Następnie narysuj prostokąt w nowym punkcie początkowym obszaru roboczego. Oznacza to, że rysujesz kształty w tym samym miejscu w przetłumaczonym układzie współrzędnych. Jest to znacznie prostsze i nieco bardziej wydajne.

(3) Na koniec przywróć Canvas do pierwotnego Origin.

Oto algorytm, który musisz wdrożyć:

  1. onDraw() wywołaj funkcję, aby wypełnić Canvas szarym kolorem tła i narysować oryginalne kształty.
  2. Wywołaj funkcję dla każdego wyciętego prostokąta i tekstu do narysowania.

W przypadku każdego prostokąta lub tekstu:

  1. Zapisz bieżący stan Canvas, aby móc przywrócić go do stanu początkowego.
  2. Przesuń Origin na płótnie w miejsce, w którym chcesz rysować.
  3. Stosowanie kształtów i ścieżek przycinania.
  4. Narysuj prostokąt lub tekst.
  5. Przywróć stan urządzenia Canvas.

Krok: zastąpienie metody onDraw()

  1. Zastąp onDraw(), jak pokazano w poniższym kodzie. Dla każdego rysowanego kształtu wywołujesz funkcję, którą zaimplementujesz później.
 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. Utwórz stuby dla każdej funkcji rysowania, aby kod nadal się kompilował. Kod możesz skopiować poniżej.
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){
}

Aplikacja rysuje ten sam prostokąt i kształty 7 razy: najpierw bez przycinania, a potem 6 razy z zastosowaniem różnych ścieżek przycinania. Metoda drawClippedRectangle() wyodrębnia kod rysowania jednego prostokąta, jak pokazano poniżej.

Krok 1. Utwórz metodę drawClippedRectangle()

  1. Utwórz metodę drawClippedRectangle(), która przyjmuje argument canvas typu Canvas.
private fun drawClippedRectangle(canvas: Canvas) {
}
  1. W metodzie drawClippedRectangle() ustaw granice prostokąta przycinania dla całego kształtu. Zastosuj prostokąt przycinania, który ogranicza rysowanie tylko do kwadratu.
canvas.clipRect(
       clipRectLeft,clipRectTop,
       clipRectRight,clipRectBottom
)

Metoda Canvas.clipRect(...) zmniejsza obszar ekranu, na którym można wykonywać kolejne operacje rysowania. Ustawia granice przycinania jako przecięcie przestrzenne bieżącego prostokąta przycinania i prostokąta przekazanego do clipRect(). Istnieje wiele wariantów metody clipRect(), które akceptują różne formy regionów i umożliwiają różne operacje na prostokącie przycinania.

  1. Wypełnij canvas kolorem białym. Tak. Całe płótno, ponieważ nie rysujesz prostokątów, tylko przycinasz. Ze względu na prostokąt przycinania wypełniany jest tylko obszar zdefiniowany przez ten prostokąt, co powoduje utworzenie białego prostokąta. Pozostała część powierzchni pozostanie szara.
canvas.drawColor(Color.WHITE)
  1. Zmień kolor na czerwony i narysuj linię po przekątnej wewnątrz prostokąta przycinania.
paint.color = Color.RED
canvas.drawLine(
   clipRectLeft,clipRectTop,
   clipRectRight,clipRectBottom,paint
)
  1. Ustaw kolor na zielony i narysuj okrąg w prostokącie przycinania.
paint.color = Color.GREEN
canvas.drawCircle(
   circleRadius,clipRectBottom - circleRadius,
   circleRadius,paint
)
  1. Ustaw kolor na niebieski i narysuj tekst wyrównany do prawej krawędzi prostokąta przycinania. Użyj ikony canvas.drawText(), aby narysować tekst.
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
)

Krok 2. Zaimplementuj metodę drawBackAndUnclippedRectangle()

  1. Aby zobaczyć działanie metody drawClippedRectangle(), narysuj pierwszy nieprzycięty prostokąt, implementując metodę drawBackAndUnclippedRectangle() w sposób pokazany poniżej. Zapisz canvas, przetłumacz na pozycję pierwszego wiersza i pierwszej kolumny, narysuj, wywołując drawClippedRectangle(), a następnie przywróć canvas do poprzedniego stanu.
private fun drawBackAndUnclippedRectangle(canvas: Canvas){
   canvas.drawColor(Color.GRAY)
   canvas.save()
   canvas.translate(columnOne,rowOne)
   drawClippedRectangle(canvas)
   canvas.restore()
}
  1. Uruchom aplikację. Powinien pojawić się pierwszy biały prostokąt z okręgiem, czerwoną linią i tekstem na szarym tle.

W przykładach przycinania poniżej zastosowano różne kombinacje regionów przycinania, aby uzyskać efekty graficzne. Dowiesz się też, jak łączyć regiony przycinania, aby tworzyć dowolne kształty.

Każda z tych metod jest oparta na tym samym wzorcu.

  1. Zapisz bieżący stan obszaru roboczego: canvas.save()

Kontekst aktywności utrzymuje stos stanów rysowania. Stan rysowania obejmuje bieżącą macierz przekształcenia i bieżący region przycinania. Możesz zapisać bieżący stan, wykonać działania, które zmieniają stan rysunku (np. przesunąć lub obrócić obszar roboczy), a następnie przywrócić zapisany stan rysunku. (Uwaga: to polecenie jest podobne do polecenia „stash” w systemie git).

Jeśli rysunek zawiera przekształcenia, łączenie i cofanie przekształceń przez ich odwracanie jest podatne na błędy. Jeśli na przykład przetłumaczysz, rozciągniesz, a następnie obrócisz element, szybko stanie się to skomplikowane. Zamiast tego zapisz stan obszaru roboczego, zastosuj przekształcenia, narysuj element, a następnie przywróć poprzedni stan.

Możesz na przykład zdefiniować region przycinania i zapisać ten stan. Następnie przetłumacz obszar roboczy, dodaj region przycinania i obróć go. Po narysowaniu czegoś możesz przywrócić pierwotny stan wycinania i wykonać inne przekształcenie przez przesunięcie i przekształcenie przez pochylenie, jak pokazano na diagramie.

  1. Przetłumacz początek obszaru rysowania na współrzędne wiersza i kolumny: canvas.translate()

Przesunięcie początku obszaru rysowania i narysowanie tego samego w nowym układzie współrzędnych jest dużo prostsze niż przesuwanie wszystkich elementów do narysowania. (Wskazówka: możesz użyć tej samej techniki do obracania elementów).

  1. W razie potrzeby zastosuj przekształcenia do elementu path.
  2. Zastosuj przycinanie: canvas.clipPath(path)
  3. Narysuj kształty: drawClippedRectangle() or drawText()
  4. Przywróć poprzedni stan obszaru roboczego: canvas.restore()

Krok 1. Zaimplementuj funkcję drawDifferenceClippingExample(canvas)

Dodaj kod, aby narysować drugi prostokąt, który wykorzystuje różnicę między dwoma prostokątami przycinania do utworzenia efektu ramki obrazu.

Użyj poniższego kodu, który wykonuje te czynności:

  1. Zapisz obszar roboczy.
  2. Przesuń początek obszaru rysowania do otwartej przestrzeni w pierwszym wierszu, drugiej kolumnie, po prawej stronie pierwszego prostokąta.
  3. Zastosuj 2 prostokąty przycinania. Operator DIFFERENCE odejmuje drugi prostokąt od pierwszego.
  1. Aby narysować zmodyfikowany obszar rysowania, wywołaj metodę drawClippedRectangle().
  2. Przywróć stan obszaru roboczego.
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. Uruchom aplikację. Powinna wyglądać tak:

Krok 2. Wdróż funkcję drawCircularClippingExample(canvas)

Następnie dodaj kod, który narysuje prostokąt z okrągłym obszarem przycinania utworzonym na podstawie okrągłej ścieżki. W ten sposób usuniesz (nie narysujesz) okrąg, a zamiast niego pojawi się szare tło.

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()
}

Krok 3. Wdróż funkcję drawIntersectionClippingExample(canvas)

Następnie dodaj kod, który narysuje przecięcie 2 prostokątów przycinających w drugim wierszu i kolumnie.

Pamiętaj, że wygląd tego obszaru będzie się różnić w zależności od rozdzielczości ekranu. Eksperymentuj z wymiarem smallRectOffset, aby zmieniać rozmiar widocznego obszaru. Mniejsza wartość smallRectOffset powoduje powiększenie regionu na ekranie.

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()
}

Krok 4. Wdróż funkcję drawCombinedClippingExample(canvas)

Następnie połącz kształty, okrąg i prostokąt, i narysuj dowolną ścieżkę, aby zdefiniować obszar przycinania.

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()
}

Krok 5. Wdróż funkcję drawRoundedRectangleClippingExample(canvas)

Następnie dodaj zaokrąglony prostokąt, który jest często używanym kształtem przycinania.

  1. Na najwyższym poziomie utwórz i zainicjuj zmienną prostokąta. RectF to klasa, która zawiera współrzędne prostokąta w postaci liczb zmiennoprzecinkowych.
private var rectF = RectF(
   rectInset,
   rectInset,
   clipRectRight - rectInset,
   clipRectBottom - rectInset
)
  1. Zaimplementuj funkcję drawRoundedRectangleClippingExample(). Funkcja addRoundRect() przyjmuje prostokąt, wartości x i y promienia zaokrąglenia rogu oraz kierunek, w którym ma być rysowany kontur zaokrąglonego prostokąta. Path.Direction określa, jak zamknięte kształty (np. prostokąty, owale) są orientowane po dodaniu do ścieżki. CCW oznacza obrót w lewo.
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()
}

Krok 6. Wdróż funkcję drawOutsideClippingExample(canvas)

Przytnij obszar na zewnątrz prostokąta, podwajając wcięcia prostokąta przycinania.

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()
}

Krok 7. Wdróż funkcję drawTranslatedTextExample(canvas)

Tekst rysowany nie różni się od innych kształtów i możesz stosować do niego przekształcenia. Możesz na przykład przetłumaczyć tekst, tłumacząc obszar roboczy i rysując tekst.

  1. Zaimplementuj funkcję poniżej.
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. Uruchom aplikację, aby zobaczyć przetłumaczony tekst.

Krok 8. Wdróż drawSkewedTextExample(canvas)

Możesz też pochylić tekst. czyli zniekształcać je na różne sposoby.

  1. Utwórz poniższą funkcję w języku 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. Uruchom aplikację, aby zobaczyć przekrzywiony tekst narysowany przed przetłumaczonym tekstem.

Metoda quickReject() Canvas umożliwia sprawdzenie, czy po zastosowaniu wszystkich przekształceń określony prostokąt lub ścieżka znajdzie się całkowicie poza aktualnie widocznymi regionami.

Metoda quickReject() jest niezwykle przydatna, gdy tworzysz bardziej złożone rysunki i musisz to zrobić jak najszybciej. Dzięki quickReject() możesz skutecznie decydować, których obiektów nie musisz w ogóle rysować, i nie musisz pisać własnej logiki przecięcia.

  • Metoda quickReject() zwraca wartość true, jeśli prostokąt lub ścieżka nie byłyby w ogóle widoczne na ekranie. W przypadku częściowego pokrywania się treści nadal musisz samodzielnie sprawdzić, czy nie doszło do naruszenia.
  • Wartość EdgeType to AA (Wygładzanie: krawędzie są zaokrąglane, ponieważ mogą być wygładzone) lub BW (Czarno-białe: krawędzie są zaokrąglane do najbliższej granicy piksela).

Istnieje kilka wersji quickReject(), które znajdziesz też w dokumentacji.

boolean

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

boolean

quickReject(RectF rect, Canvas.EdgeType type)

boolean

quickReject(Path path, Canvas.EdgeType type)

W tym ćwiczeniu narysujesz nowy wiersz poniżej tekstu i wewnątrz znaku clipRect, tak jak wcześniej.

  • Najpierw dzwonisz do quickReject(), rysując prostokąt inClipRectangle, który nakłada się na clipRect. Zatem quickReject() zwraca wartość fałszywą, clipRect jest wypełniony znakiem BLACK, a prostokąt inClipRectangle jest narysowany.

  • Następnie zmień kod i wywołaj funkcję quickReject(), używając notInClipRectangle. quickReject() zwraca teraz wartość „prawda”, a clipRect jest wypełniony wartością WHITE, a notInClipRectangle nie jest rysowany.

W przypadku złożonych rysunków możesz szybko sprawdzić, które kształty znajdują się całkowicie poza obszarem przycinania, a w przypadku których konieczne będzie wykonanie dodatkowych obliczeń i rysowania, ponieważ znajdują się one częściowo lub całkowicie w obszarze przycinania.

Krok: Eksperymentowanie z funkcją quickReject()

  1. Na najwyższym poziomie utwórz zmienną dla współrzędnych y dodatkowego wiersza.
   private val rejectRow = rowFour + rectInset + 2*clipRectBottom
  1. Dodaj do pliku ClippedView tę funkcję drawQuickRejectExample(). Przeczytaj kod, ponieważ zawiera on wszystko, co musisz wiedzieć, aby korzystać z 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. W pliku onDraw() odkomentuj wywołanie funkcji drawQuickRejectExample().
  2. Uruchom aplikację. Zobaczysz czarny prostokąt, który jest wypełnionym obszarem przycinania, oraz fragmenty znaku inClipRectangle, ponieważ oba prostokąty się nakładają, więc funkcja quickReject() zwraca wartość false i rysuje znak inClipRectangle.

  1. drawQuickRejectExample() zmień kod, aby uruchomić quickReject() na notInClipRectangle.. Teraz quickReject() zwraca true, a obszar przycinania jest wypełniony białym kolorem.

Pobierz kod ukończonego ćwiczenia.

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


Możesz też pobrać repozytorium jako plik ZIP, rozpakować go i otworzyć w Android Studio.

Pobierz plik ZIP

  • Context aktywności zachowuje stan, który przechowuje przekształcenia i obszary przycinania dla Canvas.
  • Użyj narzędzi canvas.save()canvas.restore(), aby rysować i wrócić do pierwotnego stanu obszaru roboczego.
  • Aby narysować kilka kształtów na obszarze roboczym, możesz obliczyć ich lokalizację lub przenieść (przesunąć) punkt początkowy powierzchni rysowania. To drugie rozwiązanie może ułatwić tworzenie metod narzędziowych dla powtarzających się sekwencji rysowania.
  • Obszary przycinania mogą mieć dowolny kształt, kombinację kształtów lub ścieżkę.
  • Możesz dodawać, odejmować i przecinać regiony przycinania, aby uzyskać dokładnie ten region, którego potrzebujesz.
  • Możesz przekształcać tekst, przekształcając obszar roboczy.
  • Metoda quickReject() Canvas umożliwia sprawdzenie, czy określony prostokąt lub ścieżka znajdują się całkowicie poza aktualnie widocznymi regionami.

Kurs Udacity:

Dokumentacja dla deweloperów aplikacji na Androida:

Szczegółowe wyjaśnienie, jak framework Androida rysuje obraz na ekranie, znajdziesz w serii artykułów Architektura grafiki.

W tej sekcji znajdziesz listę możliwych zadań domowych dla uczniów, którzy wykonują ten moduł w ramach kursu prowadzonego przez instruktora. Nauczyciel musi:

  • W razie potrzeby przypisz pracę domową.
  • Poinformuj uczniów, jak przesyłać projekty.
  • Oceń zadania domowe.

Instruktorzy mogą korzystać z tych sugestii w dowolnym zakresie i mogą zadawać inne zadania domowe, które uznają za odpowiednie.

Jeśli wykonujesz ten kurs samodzielnie, możesz użyć tych zadań domowych, aby sprawdzić swoją wiedzę.

Odpowiedz na te pytania

Pytanie 1

Jakiej metody używasz, aby skutecznie wykluczyć kształty z rysowania?

▢ excludeFromDrawing()

▢ quickReject()

▢ onDraw()

▢ clipRect()

Pytanie 2

Canvas.save()Canvas.restore() zapisują i przywracają które informacje?

▢ Kolor, szerokość linii itp.

▢ Tylko bieżące transformacje

▢ Aktualne przekształcenia i obszar przycinania

▢ Tylko bieżący region przycinania

Pytanie 3

Paint.Align określa:

▢ Jak wyrównać te kształty rysunku

▢ Po której stronie punktu początkowego jest rysowany tekst

▢ Miejsce wyrównania w regionie przycinania

▢ Po której stronie tekstu ma być wyrównanie do punktu początkowego

Linki do innych ćwiczeń z tego kursu znajdziesz na stronie docelowej ćwiczeń z zaawansowanego Androida w Kotlinie.