Przycinanie obiektów Canvas

To ćwiczenie programowania jest częścią kursu „Android dla zaawansowanych w Kotlinie”. Korzyści z tego kursu będą dla Ciebie najbardziej wartościowe, jeśli wykonasz je w sekwencjach ćwiczeń z programowania, ale nie jest to obowiązkowe. Wszystkie ćwiczenia z kursu są wymienione na stronie Zaawansowane ćwiczenia z programowania na Androida w Kotlin.

Wstęp

Na potrzeby tych ćwiczeń z programowania przycinanie umożliwia definiowanie regionów obrazu, obszaru roboczego lub bitu obrazu, które są selektywnie rysowane lub niewidoczne na ekranie. Jednym z zadań jest zmniejszenie obrysu. Rysowanie dotyczy sytuacji, w której piksel na ekranie zostanie wyświetlony więcej niż raz, aby wyświetlić końcowy obraz. Minimalizowanie przerysowań pozwala zminimalizować liczbę pikseli lub regionów wyświetlania, co pozwala zmaksymalizować skuteczność rysowania. Możesz go też używać do tworzenia ciekawych efektów podczas projektowania i animowania interfejsu.

Gdy na przykład rysujesz stos nakładających się kart, jak pokazano poniżej, zamiast rysować całą kartę od dołu, efektywniej jest wyświetlić tylko widoczne fragmenty. "Zazwyczaj wiążą się z tym również koszty operacji przycinania, a system Android wykonuje bardzo dużo operacji rysowania.

Aby narysować tylko widoczne części kart, musisz określić region przycinania dla każdej karty. Jeśli na przykład na diagramie poniżej przycięcie jest przypisane do obrazu, wyświetli się tylko jego część.

Obszar przycinania jest zazwyczaj prostokątem, ale może to być dowolny kształt lub kombinacja kształtów, a nawet tekst. Możesz też określić, czy chcesz, aby region wewnątrz obszaru przycinania został uwzględniony lub wykluczony. Możesz na przykład utworzyć okrągły region przycinania i wyświetlać tylko okręgi poza tym okręgiem.

W tym ćwiczeniu zmierzysz różne sposoby tworzenia klipów.

Co musisz wiedzieć

Pamiętaj:

  • Jak utworzyć aplikację w Activity i uruchomić ją przy użyciu Android Studio.
  • Tworzenie i rysowanie na Canvas
  • Jak utworzyć niestandardowy View i zastąpić onDraw() oraz onSizeChanged().

Czego się nauczysz

  • Jak przycinać obiekty, aby narysować coś na Canvas.
  • Zapisywanie i przywracanie stanów rysowania obszaru roboczego.
  • Stosowanie przekształceń do przestrzeni roboczej i tekstu.

Jakie zadania wykonasz:

  • Aplikacja, która rysuje na ekranie przycięte kształty, ukazując różne sposoby przycięcia i wynik tych widocznych efektów.
  • Narysujesz także tekst przetłumaczony i zniekształcony.

Aplikacja ClippingExample pokazuje, jak za pomocą kombinacji i kształtów określić, które części obszaru roboczego mają być wyświetlane w widoku. Ostateczna wersja aplikacji będzie wyglądać tak jak na zrzucie ekranu poniżej.

Będziesz tworzyć tę aplikację od zera, więc musisz skonfigurować projekt, zdefiniować wymiary i ciągi tekstowe oraz zadeklarować niektóre zmienne.

Krok 1. Utwórz projekt ClippingExample

  1. Utwórz projekt Kotlin o nazwie ClippingExample i szablon Pusta aktywność. Użyj prefiksu com.example.android jako prefiksu nazwy pakietu.
  2. Otwórz aplikację MainActivity.kt.
  3. W metodzie onCreate() zastąp domyślny widok treści i ustaw jego widok na nowe wystąpienie ClippedView. To będzie Twój widok niestandardowych przykładów przycinania, który 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 ClippedView o rozszerzeniu View. Wpisz podpis widoczny poniżej. Pozostałe zadania będą się odbywać w ClippedView. Adnotacja @JvmOverloads instruuje kompilatorowi Kotlin generowanie przeciążeń tej funkcji, która zastępuje wartości parametrów domyślnych.
class ClippedView @JvmOverloads constructor(
   context: Context,
   attrs: AttributeSet? = null,
   defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
}

Krok 2. Dodaj wymiary i zasoby typu ciąg znaków

  1. Określ wymiary, których będziesz używać w przyciętych widokach w nowym pliku zasobów w res/values/dimens.xml. Domyślne wymiary są zakodowane na stałe i mieszczą się na niewielkim ekranie.
<?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 wyglądała dobrze na większym ekranie (i łatwiej ją zobaczyć), 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 Nowy > Wartość plik zasobów.
  2. W oknie Nowy plik zasobu wywołaj plik dimens. W sekcji Dostępne kwalifikatory wybierz Mała szerokość ekranu i kliknij przycisk >>, aby dodać go do Kwalifikatorów wybierz. Wpisz wartość 480 w polu Mała szerokość ekranu i kliknij OK.

  1. Plik powinien pojawić się w folderze wartości w sposób przedstawiony poniżej.

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

  1. Zastąp domyślną zawartość pliku values-sw480dp/dimens.xml wymiarami opisanymi 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 strings.xml dodaj następujące ciągi znaków. Służą one do wyświetlania tekstu w 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 obiekt Paint i Ścieżka

  1. Wróć do widoku Androida projektu.
  2. W ClippedView określ zmienną Paint, która ma zostać wykorzystana. Włącz antyaliasing, a także szerokość kreski i rozmiar tekstu zdefiniowane 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. W ClippedView utwórz i zainicjuj Path, aby przechowywać lokalnie ścieżkę narysowanego ciągu. Importuj: android.graphics.Path.
private val path = Path()

Krok 4. Skonfiguruj kształty

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

Wszystkie one mają te same cechy:

  • Duży prostokąt (kwadrat), który pełni rolę kontenera
  • Przekątna biegnąca wzdłuż dużego prostokąta
  • Okrąg
  • Krótki ciąg tekstu

Na tym etapie konfigurujesz wymiary dla tych kształtów z zasobów, więc pobierasz wymiary tylko raz.

  1. W ClippedView pod 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 odstępów między prostokątem a odsunięciem 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 koła. To promień okręgu narysowanego wewnątrz prostokąta.
private val circleRadius = resources.getDimension(R.dimen.circleRadius)
  1. Dodaj przesunięcie i rozmiar tekstu tekstu rysowanego w ramach 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 wyświetlają się w dwóch kolumnach i czterech wierszach na podstawie wartości skonfigurowanych powyżej wymiarów. Matematyka nie jest częścią tego ćwiczenia z programowania, ale popatrz na to, jak kopiujesz kod do tego kroku.

  1. Skonfiguruj współrzędne dwóch kolumn.
private val columnOne = rectInset
private val columnTwo = columnOne + rectInset + clipRectRight
  1. Dodaj współrzędne każdego wiersza, w tym ostatni wiersz 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ę. Aplikacja powinna się otworzyć przy pustym białym ekranie pod nazwą aplikacji.

W onDraw() wywołujesz metody rysujące siedem różnych przyciętych prostokątów, tak jak pokazano na zrzucie ekranu poniżej aplikacji. Prostokąty są rysowane w ten sam sposób. Jedyną różnicą są zdefiniowane regiony przycinania i lokalizacji na ekranie.

Algorytm używany do rysowania prostokątów działa na diagramie i wyjaśnieniu poniżej. W skrócie rysujesz serię prostokątów, przemieszczając punkt początkowy elementu Canvas. Trasa obejmuje takie etapy:

(1) Najpierw przetłumacz Canvas na miejsce, w którym chcesz narysować prostokąt. Oznacza to, że zamiast obliczać miejsce, w którym trzeba narysować następny prostokąt i wszystkie pozostałe kształty, przenosisz punkt początkowy Canvas, czyli jego układ współrzędnych.

(2) Następnie rysujesz prostokąt w nowym miejscu obszaru roboczego. Oznacza to, że kształty rysujesz w tej samej lokalizacji w przetłumaczonym systemie współrzędnych. Jest to dużo łatwiejsze i nieco skuteczniejsze.

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

Algorytm, w którym wdrożysz ten algorytm:

  1. W funkcji onDraw() wywołaj funkcję, aby wypełnić element Canvas szarym kolorem tła i narysować pierwotne kształty.
  2. Wywołaj funkcję każdego przyciętego prostokąta i tekst, który chcesz narysować.

W przypadku każdego prostokąta lub tekstu:

  1. Zapisz bieżący stan Canvas, aby móc go przywrócić.
  2. Przetłumacz Origin obszaru roboczego na miejsce, w którym chcesz rysować.
  3. Zastosuj przycinanie kształtów i ścieżek.
  4. Narysuj prostokąt lub tekst.
  5. Przywróć stan Canvas.

Krok: Zastąp onDraw()

  1. Zastąp wartość onDraw() zgodnie z poniższym kodem. Wywołujesz funkcję dla każdego tworzonego kształtu, który wdrożysz 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 wycinki dla każdej funkcji rysowania, aby kod nadal był kompilowany. Możesz skopiować poniższy kod.
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 następnie 6 razy z różnymi ścieżkami przycinania. Metoda drawClippedRectangle() rozróżnia kod do rysowania jednego prostokąta, jak pokazano poniżej.

Krok 1. Utwórz metodę pullClippedRectangle()

  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 do rysowania tylko kwadrat.
canvas.clipRect(
       clipRectLeft,clipRectTop,
       clipRectRight,clipRectBottom
)

Metoda Canvas.clipRect(...) ogranicza obszar ekranu, na którym w przyszłości będą mogły zapisywać się operacje rysowania. Ustawia przycięcia na przycięcia w przecinku bieżącego prostokąta przycinania oraz prostokąta przekazywanego do clipRect(). Jest wiele wariantów metody clipRect(), które przyjmują różne formularze i umożliwiają wykonywanie różnych działań na prostokątnym polu przycinania.

  1. Wypełnij canvas białym kolorem. Tak. Cały obszar roboczy, ponieważ nie rysujesz prostokątów, został przycięty. Ze względu na prostokąt przycinania, tylko obszar zdefiniowany przez prostokąt zostanie przycięty, tworząc biały prostokąt. Pozostałe części powierzchni pozostaną szare.
canvas.drawColor(Color.WHITE)
  1. Zmień kolor na czerwony i narysuj ukośną linię 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 wewnątrz prostokąta 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 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ę pullBackAndUnclippedRectangle()

  1. Aby zobaczyć, jak działa metoda drawClippedRectangle(), narysuj pierwszy nieprzycięty prostokąt, stosując metodę drawBackAndUnclippedRectangle() zgodnie z opisem poniżej. Zapisz atrybut canvas, przetłumacz go na pierwszy wiersz i w kolumnie, rysuj, wywołując element drawClippedRectangle(), a potem przywróć canvas poprzedni stan.
private fun drawBackAndUnclippedRectangle(canvas: Canvas){
   canvas.drawColor(Color.GRAY)
   canvas.save()
   canvas.translate(columnOne,rowOne)
   drawClippedRectangle(canvas)
   canvas.restore()
}
  1. Uruchom aplikację. Pierwszy biały prostokąt z kółkiem, czerwoną linią i tekstem powinien być widoczny na szarym tle.

W poniższych przykładowych metodach przycinania stosujesz różne kombinacje przycinających obszarów, aby uzyskać efekt graficzny. Dowiedz się też, jak łączyć regiony przycinania, aby uzyskać odpowiedni kształt.

Każda z tych metod ma ten sam wzorzec.

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

Kontekst aktywności zawiera zestaw stanów rysowania. Stany rysowania składają się z bieżącej tablicy transformacji i bieżącego regionu przycinania. Możesz zapisać bieżący stan, wykonać działania, które zmienią ten stan (np. przetłumaczyć lub obrócić obszar roboczy), a następnie go przywrócić. (Uwaga: działa to jak w przypadku polecenia „&sht;stash"” w git!).

Gdy rysunek zawiera przekształcenia, łańcuchy i cofanie przekształceń poprzez ich odwrócenie jest podatne na błędy. Jeśli np. przetłumaczysz, rozciągniesz i obrócisz tekst, staje się on bardzo złożony. Zamiast tego zapisz stan obszaru roboczego, zastosuj przekształcenia, rysunki i przywróć poprzedni stan.

Możesz na przykład zdefiniować region przycinania i zapisać ten stan. Następnie przetłumacz obszar roboczy, dodaj obszar przycinania i obróć. Po zakończeniu rysowania możesz przywrócić pierwotny stan przycinania, a także kontynuować translację i przekształcenie, jak pokazano na diagramie.

  1. Przetłumacz punkt początkowy obszaru roboczego na współrzędne wiersza/kolumny: canvas.translate()

Dużo prościej jest przenieść punkt odniesienia i narysować to samo w nowym układzie współrzędnych niż przesunąć wszystkie elementy. (Wskazówka: tej samej metody możesz używać do obracania elementów).

  1. Zastosuj przekształcenia do path (jeśli występują).
  2. Zastosuj przycięcie: canvas.clipPath(path)
  3. Narysuj kształty: drawClippedRectangle() or drawText()
  4. Przywróć poprzedni stan obszaru roboczego: canvas.restore()

Krok 1. Zaimplementuj przykład rysowania różnicy(canvaClip) (Canvas)

Dodaj kod, aby narysować drugi prostokąt. Wykorzystuje różnicę między dwoma prostokątami przycinania, aby utworzyć efekt ramki zdjęcia.

Użyj tego kodu, który umożliwia:

  1. Zapisz odbitkę na płótnie.
  2. Zamień punkt początkowy obszaru roboczego na pierwszy, drugi wiersz, na prawo od pierwszego prostokąta.
  3. Zastosuj 2 proste prostokąty. Operator DIFFERENCE odejmuje drugi prostokąt od pierwszego.
  1. Wywołaj metodę drawClippedRectangle(), by narysować zmodyfikowany obszar roboczy.
  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ę, a powinna wyglądać tak.

Krok 2. Zaimplementuj przykładrysowanieCircularClipping(Canvas)

Następnie dodaj kod, aby narysować prostokąt z okrągłym obszarem przycinania utworzonym na podstawie okrągłej ścieżki. Zasadniczo usuniesz (nie rysujesz) okrąg, tym samym wyświetlając 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. Zaimplementuj instrukcję pullIntersectionClippingExample(canvas)

Następnie dodaj kod, aby narysować przecięcie dwóch prostokątów przycinania w drugim wierszu i kolumnie.

Pamiętaj, że wygląd interfejsu może się różnić w zależności od rozdzielczości ekranu. Poeksperymentuj z wymiarem smallRectOffset, aby zmienić rozmiar widocznego obszaru. Mniejszy smallRectOffset oznacza większy region 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. Zaimplementuj rysowaniapołączonedopasowujące(na przykład)

Następnie połącz kształty, koło i prostokąt, a następnie narysuj dowolną ścieżkę, by zdefiniować region.

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. Zaimplementuj element rysowaniaRoundedRectangleClipping(Canvas)

Następnie dodaj zaokrąglony prostokąt, który jest często używany do przycinania.

  1. Na najwyższym poziomie utwórz i zainicjuj zmienną prostokątną. RectF to klasa zawierająca współrzędne prostokąta w postaci zmiennoprzecinkowej.
private var rectF = RectF(
   rectInset,
   rectInset,
   clipRectRight - rectInset,
   clipRectBottom - rectInset
)
  1. Zaimplementuj funkcję drawRoundedRectangleClippingExample(). Funkcja addRoundRect() przyjmuje prostokąty w promieniu x i y promienia narożnika i kieruje na półokrągły prostokąt. Path.Direction określa, jak są kształtowane zamknięte kształty (np. prostokąty lub owale) podczas dodawania ich do ścieżki. CCW oznacza ruch 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. Zaimplementuj pullOutsideClippingExample(canvas)

Przytnij zewnętrznie wokół prostokąta, podwajając w nim odstępy prostokąta.

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. Zaimplementuj rysowaniarysowane_teksty(przykład)

Rysowanie tekstu tak naprawdę nie różni się od innych kształtów. Możesz zastosować do niego przekształcenia. Możesz na przykład przetłumaczyć tekst, tłumacząc obszar roboczy i rysując go.

  1. Zaimplementuj tę 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: Implementacja rysowaniaSkewedTextExample(canvas)

Tekst można zniekształcić. czyli zniekształcać je na różne sposoby.

  1. Utwórz poniższą funkcję w 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ć zniekształcony tekst przed przetłumaczonym tekstem.

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

Metoda quickReject() jest niezwykle przydatna przy tworzeniu bardziej złożonych rysunków, dlatego musisz je wykonywać tak szybko, jak to możliwe. Dzięki quickReject() możesz skutecznie wybierać obiekty, których w ogóle nie chcesz rysować. Nie musisz pisać własnych algorytmów skrzyżowania.

  • Metoda quickReject() zwraca true, jeśli prostokąt lub ścieżka w ogóle nie byłyby widoczne na ekranie. W przypadku częściowego pokrywania się musisz jeszcze samodzielnie przeprowadzić weryfikację.
  • EdgeType ma wartość AA (Antyaliasowany: krawędzie są zaokrąglane, ponieważ mogą być wygładzane), lub BW (czarno-biały: krawędzie są zaokrąglane do najbliższej krawędzi piksela), co jest zaokrąglane do najbliższego piksela.

Istnieje kilka wersji aplikacji quickReject(). Znajdziesz je również w dokumentacji.

boolean

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

boolean

quickOdrzuć(RectF rect, Canvas.EdgeType type)

boolean

quickOdrzuć(Ścieżka path, Canvas.EdgeType type)

W tym ćwiczeniu będziesz rysować w nowym wierszu, pod tekstem i w części clipRect, tak jak wcześniej.

  • Najpierw wywołujesz funkcję quickReject() z prostokątem inClipRectangle, który nakłada się na clipRect. quickReject() zwraca wartość false (fałsz), clipRect jest wypełnione wartością BLACK i rysowany jest prostokąt inClipRectangle.

  • Następnie zmień kod i zadzwoń do firmy quickReject(), używając operatora notInClipRectangle. quickReject() zwraca wartość „prawda”, a pole clipRect jest wypełnione wartością WHITE, a pole notInClipRectangle nie jest rysowane.

Jeśli masz skomplikowane rysunki, możesz szybko stwierdzić, które kształty znajdują się całkowicie poza regionem przycinania, a w związku z tym konieczne może być wykonanie dodatkowych obliczeń i rysowanie, ponieważ znajdują się one w części obszaru przycinania.

Krok: eksperyment z funkcją QuickOdrzuć()

  1. Na najwyższym poziomie utwórz zmienną dla współrzędnych y dodatkowego wiersza.
   private val rejectRow = rowFour + rectInset + 2*clipRectBottom
  1. Dodaj poniższą funkcję drawQuickRejectExample() do ClippedView. Przeczytaj ten kod, ponieważ zawiera on wszystko, co musisz wiedzieć o korzystaniu 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 onDraw() usuń komentarz w wywołaniu metody drawQuickRejectExample().
  2. Uruchom aplikację, a pojawi się czarny prostokąt (wypełniony obszar przycinania) i części inClipRectangle, ponieważ te dwa prostokąty się nakładają, więc quickReject() zwraca false i inClipRectangle.

  1. W pliku drawQuickRejectExample() zmień kod w celu uruchomienia quickReject()notInClipRectangle. teraz quickReject() zwraca true, a region przycinania jest biały

Pobierz kod ukończonych ćwiczeń z programowania.

$  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 aplikację Zip

  • Context aktywności utrzymuje stan, który zachowuje przekształcenia i segmenty przycinania dla Canvas.
  • Użyj opcji canvas.save() i canvas.restore(), aby narysować obraz i wrócić do pierwotnego stanu odbitki na płótnie.
  • Aby narysować wiele kształtów na płótnie, możesz obliczyć jej lokalizację lub przesunąć (przetłumaczyć) punkt początkowy powierzchni rysowania. Może on ułatwić tworzenie metod powtarzania sekwencji rysowania.
  • Obszary przycinania mogą mieć dowolny kształt, kombinację kształtów lub ścieżek.
  • Możesz dodawać, odejmować i nakładać na siebie regiony przycięcia, aby uzyskać dokładnie ten region, którego potrzebujesz.
  • Możesz zastosować przekształcenia do tekstu, przekształcając obszar roboczy.
  • Metoda quickReject() Canvas umożliwia sprawdzenie, czy określony prostokąt lub ścieżka znajdują się w całości poza aktualnie widocznymi obszarami.

Kurs Udacity:

Dokumentacja dla programistów Androida:

Szczegółowe informacje o tym, jak platforma Android jest prezentowana na ekranie, znajdziesz też w serii artykułów Graphics Architecture (Architektura graficzna).

Ta sekcja zawiera listę możliwych zadań domowych dla uczniów, którzy pracują w ramach tego ćwiczenia w ramach kursu prowadzonego przez nauczyciela. To nauczyciel może wykonać te czynności:

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

Nauczyciele mogą wykorzystać te sugestie tak długo, jak chcą lub chcą, i mogą przypisać dowolne zadanie domowe.

Jeśli samodzielnie wykonujesz te ćwiczenia z programowania, możesz sprawdzić swoją wiedzę w tych zadaniach domowych.

Odpowiedz na te pytania

Pytanie 1

Jak wywoływać efektywne wykluczanie rysowania kształtów?

excludeFromDrawing()

quickReject()

onDraw()

clipRect()

Pytanie 2

Canvas.save() i Canvas.restore() zapiszą i przywrócą informacje?

▢ Kolor, szerokość linii itp.

▢ Tylko bieżące transformacje

▢ Bieżące przekształcenia i region przycinania

▢ Tylko obecny region przycinania

Pytanie 3

Paint.Align określa:

▢ Jak wyrównać następujące kształty rysunku

▢ Wskazuje, skąd pochodzi tekst

▢ Kraj i miejsce przycięcia

▢ bok tekstu do wyrównania

Linki do innych ćwiczeń z programowania znajdziesz w kursie dotyczącym programowania na Androida dla zaawansowanych w Kotlin.