Obsługa zaawansowanych funkcji rysika

Rysik umożliwia użytkownikom wygodę i precyzyjną interakcję z aplikacjami. Dzięki niemu można robić notatki, szkicować i pracować z aplikacjami zwiększającymi produktywność, a także odprężyć się i zrelaksować dzięki grom i aplikacjom rozrywkowym.

Android i ChromeOS oferują różne interfejsy API, dzięki którym możesz w pełni wykorzystać możliwości rysika. Zajęcia MotionEvent zawierają informacje o interakcjach użytkownika z ekranem, w tym o wykrywaniu nacisku rysika, orientacji, przechylania, najeżdżania kursorem i wykrywaniu dłoni. Grafika i biblioteki przewidywania ruchu o krótkim czasie oczekiwania ulepszają renderowanie na ekranie rysikiem, zapewniając bardziej naturalne wrażenia jak pióro i papier.

MotionEvent

Klasa MotionEvent reprezentuje interakcje użytkownika, takie jak pozycja i ruch wskaźników dotykowych na ekranie. W przypadku wpisywania rysika MotionEvent ujawnia też dane dotyczące nacisku, orientacji, przechylenia i najechania kursorem.

Dane zdarzenia

Aby uzyskać dostęp do danych MotionEvent w aplikacjach opartych na widoku danych, skonfiguruj onTouchListener:

Kotlin

val onTouchListener = View.OnTouchListener { view, event ->
  // Process motion event.
}

Java

View.OnTouchListener listener = (view, event) -> {
  // Process motion event.
};

Detektor odbiera obiekty MotionEvent z systemu, aby aplikacja mogła je przetworzyć.

Obiekt MotionEvent dostarcza dane związane z tymi aspektami zdarzenia interfejsu:

  • Działania: fizyczna interakcja z urządzeniem – dotykanie ekranu, przesuwanie wskaźnika nad powierzchnią ekranu, najeżdżanie kursorem na powierzchnię ekranu.
  • Wskaźniki – identyfikatory obiektów wchodzących w interakcję z ekranem – palcem, rysika, myszy
  • Oś: typ danych – współrzędne X i Y, ciśnienie, przechylenie, orientacja i utrzymanie (odległość).

działania.

Aby wdrożyć obsługę rysika, musisz wiedzieć, jakie czynności wykonuje użytkownik.

MotionEvent udostępnia szeroki zakres stałych ACTION, które definiują zdarzenia ruchu. Najważniejsze działania związane z rysikiem to:

Działanie Opis
ACTION_DOWN
ACTION_POINTER_DOWN
Wskaźnik dotknął ekranu.
ACTION_MOVE Wskaźnik porusza się po ekranie.
ACTION_UP
ACTION_POINTER_UP
Wskaźnik nie styka się już z ekranem
ACTION_CANCEL Poprzedni lub obecny ruch ma zostać anulowany.

Aplikacja może wykonywać zadania takie jak rozpoczęcie nowego kreski w momencie ACTION_DOWN, rysowanie kreski w formacie ACTION_MOVE, i kończenie kreski po uruchomieniu funkcji ACTION_UP.

Zbiór działań MotionEvent, od ACTION_DOWN do ACTION_UP dla danego wskaźnika, jest nazywany zestawem ruchu.

Wskaźniki

Większość ekranów jest dotykowych: system przypisuje wskaźnik do każdego palca, rysika, myszy lub innego obiektu wskazującego, które styka się z ekranem. Indeks wskaźnika pozwala uzyskać informacje o osi dla określonego wskaźnika, np. położenie pierwszego palca dotykającego ekranu lub drugiego palca.

Indeksy wskaźników mają zakres od 0 do liczby wskaźników zwracanych przez MotionEvent#pointerCount() minus 1.

Dostęp do wartości osi wskaźników można uzyskać za pomocą metody getAxisValue(axis, pointerIndex). W przypadku pominięcia indeksu wskaźnika system zwraca wartość pierwszego wskaźnika – wskaźnika 0 (0).

Obiekty MotionEvent zawierają informacje o typie używanego wskaźnika. Aby uzyskać typ wskaźnika, wykonaj iterację dla indeksów wskaźników i wywołaj metodę getToolType(pointerIndex).

Więcej informacji o wskaźnikach znajdziesz w artykule Obsługa gestów wielodotykowych.

Dane wejściowe rysika

Za pomocą TOOL_TYPE_STYLUS możesz filtrować dane wejściowe rysika:

Kotlin

val isStylus = TOOL_TYPE_STYLUS == event.getToolType(pointerIndex)

Java

boolean isStylus = TOOL_TYPE_STYLUS == event.getToolType(pointerIndex);

Rysik może też zgłosić, że jest używany jako gumka w aplikacji TOOL_TYPE_ERASER:

Kotlin

val isEraser = TOOL_TYPE_ERASER == event.getToolType(pointerIndex)

Java

boolean isEraser = TOOL_TYPE_ERASER == event.getToolType(pointerIndex);

Dane osi rysika

ACTION_DOWN i ACTION_MOVE podają dane na temat rysika, czyli współrzędne X i Y, nacisk, orientację, przechylenie i najechanie kursorem.

Aby umożliwić dostęp do tych danych, interfejs MotionEvent API udostępnia parametr getAxisValue(int), gdzie parametr jest dowolnym z tych identyfikatorów osi:

Axis Zwracana wartość getAxisValue()
AXIS_X Współrzędna X zdarzenia ruchu.
AXIS_Y Współrzędna Y zdarzenia ruchu.
AXIS_PRESSURE W przypadku ekranu dotykowego lub touchpada nacisk wywierany przez palec, rysik lub inny wskaźnik. W przypadku myszy lub kulki ma wartość 1, gdy naciśnięty zostanie przycisk główny, lub 0, jeśli nie jest to możliwe.
AXIS_ORIENTATION W przypadku ekranu dotykowego lub touchpada jest to orientacja palca, rysika lub innego wskaźnika względem pionowej płaszczyzny urządzenia.
AXIS_TILT Kąt nachylenia rysika w radianach.
AXIS_DISTANCE Odległość rysika od ekranu.

Na przykład MotionEvent.getAxisValue(AXIS_X) zwraca współrzędną x dla pierwszego wskaźnika.

Zobacz też Obsługiwanie gestów wielodotykowych.

Pozycja

Współrzędne x i y można pobierać, używając następujących wywołań:

Rysunek rysikiem na ekranie z zmapowanymi współrzędnymi x i y.
Rysunek 1. Współrzędne X i y na ekranie wskaźnika rysika.

Ciśnienie

Presję wskaźnika można pobierać za pomocą tych wywołań:

getAxisValue(AXIS_PRESSURE) lub getPressure() w przypadku pierwszego wskaźnika.

W przypadku ekranów dotykowych i touchpadów wartość ciśnienia jest wartością z zakresu od 0 (bez nacisku) do 1, ale w zależności od kalibracji ekranu mogą być zwracane wyższe wartości.

Styl rysikiem reprezentujący kontinuum od niskiego do wysokiego ciśnienia. Styl jest wąski i słaby po lewej stronie, co wskazuje na niskie ciśnienie. Styl kreski staje się szerszy i ciemniejszy od lewej do prawej, aż jest najszerszy i najciemniejszy po prawej stronie, co wskazuje na najwyższe ciśnienie.
Rysunek 2. Reprezentacja ciśnienia – niskie ciśnienie po lewej, wysokie po prawej.

Orientacja

Orientacja wskazuje kierunek, w którym wskazuje rysik.

Orientację wskaźnika można pobrać za pomocą funkcji getAxisValue(AXIS_ORIENTATION) lub getOrientation() (w przypadku pierwszego wskaźnika).

W przypadku rysika orientacja jest zwracana jako wartość radiana z zakresu od 0 do pi (π) w prawo lub od 0 do pi w lewo.

Orientacja umożliwia zastosowanie prawdziwego pędzla. Jeśli na przykład rysik reprezentuje płaski pędzel, jego szerokość zależy od jego orientacji.

Rysunek 3. Rysik skierowany w lewo około minus 0,57 radianów.

Przechylenie

Pochylenie określa nachylenie rysika względem ekranu.

Pochylenie zwraca dodatni kąt rysika w radianach, gdzie 0 jest prostopadłe do ekranu, a π/2 jest płaska na powierzchni.

Kąt nachylenia można pobrać za pomocą funkcji getAxisValue(AXIS_TILT) (bez skrótu do pierwszego wskaźnika).

Pochylenie pozwala uzyskać model z jak największą liczbą rzeczywistych narzędzi, np. naśladować cieniowanie przechylonym ołówkiem.

Rysik pochylony o około 40 stopni od powierzchni ekranu.
Rysunek 4. Rysik przechylony pod około 0,785 radianów, czyli pod kątem 45 stopni względem prostopadłego.

Najechanie

Odległość rysika od ekranu mierzy się za pomocą getAxisValue(AXIS_DISTANCE). Zwraca wartość od 0, 0 (kontakt z ekranem) na wyższe wartości, gdy rysik przesuwa się od ekranu. Odległość między ekranem a końcówką (punktem) rysika zależy od producenta ekranu i rysika. Implementacje mogą się różnić, dlatego nie polegaj na precyzyjnych wartościach kluczowych funkcji aplikacji.

Najechanie rysikiem pozwala wyświetlić podgląd rozmiaru pędzla lub wskazać przycisk, który zostanie wybrany.

Rysunek 5. Rysik najeżdżający na ekran. Aplikacja reaguje, mimo że rysik nie dotyka powierzchni ekranu.

Uwaga: funkcja tworzenia udostępnia zestaw elementów modyfikujących, które zmieniają stan elementów interfejsu:

  • hoverable: skonfiguruj komponent tak, aby można go najeżdżać kursorem, korzystając ze zdarzeń wejścia i wyjścia wskaźnika.
  • indication: rysuje efekty wizualne związane z tym komponentem w przypadku interakcji.

Odrzucenie dłoni, nawigacja i niechciane dane wejściowe

Czasami ekrany wielodotykowe mogą rejestrować niechciane dotknięcia, np. gdy użytkownik w naturalny sposób opiera rękę o ekranie, aby umożliwić czytanie pisma odręcznego. Odrzucenie urządzenia Palm to mechanizm, który wykrywa takie zachowanie i powiadamia Cię, że ostatni zestaw MotionEvent powinien zostać anulowany.

Dlatego musisz przechowywać historię danych wejściowych użytkownika, aby możliwe było usuwanie niechcianych dotknięć z ekranu i ponowne wyrenderowanie prawidłowych danych wejściowych użytkownika.

ACTION_CANCEL i FLAG_CANCELED

ACTION_CANCEL i FLAG_CANCELED zostały zaprojektowane tak, aby informować, że poprzedni zestaw MotionEvent powinien zostać anulowany od ostatniego ACTION_DOWN. Dzięki temu możesz na przykład cofnąć ostatnią kreskę w aplikacji do rysowania dla danego wskaźnika.

ACTION_CANCEL

Dodano w Androidzie 1.0 (poziom API 1)

ACTION_CANCEL oznacza, że poprzedni zestaw zdarzeń ruchu ma zostać anulowany.

ACTION_CANCEL jest wyzwalany, gdy zostanie wykryty dowolny z tych elementów:

  • Gesty do nawigacji
  • Odrzucenie dłoni

Po wywołaniu funkcji ACTION_CANCEL aktywny wskaźnik należy wskazać za pomocą getPointerId(getActionIndex()). Następnie usuń kreskę utworzoną za pomocą tego wskaźnika z historii danych wejściowych i ponownie wyrenderuj scenę.

OZNACZONE_ANULOWANIE

Dodano w Androidzie 13 (poziom API 33)

FLAG_CANCELED oznacza, że wskaźnik wznoszący się został przypadkowo dotknięty przez użytkownika. Flaga jest zwykle ustawiana, gdy użytkownik przypadkowo dotknie ekranu, na przykład chwytając urządzenie lub przykładając dłoń do ekranu.

Dostęp do wartości flagi możesz uzyskać w ten sposób:

Kotlin

val cancel = (event.flags and FLAG_CANCELED) == FLAG_CANCELED

Java

boolean cancel = (event.getFlags() & FLAG_CANCELED) == FLAG_CANCELED;

Jeśli flaga jest ustawiona, musisz cofnąć ostatnie ustawienie parametru MotionEvent, od ostatniego ustawienia ACTION_DOWN od tego wskaźnika.

Podobnie jak ACTION_CANCEL, wskaźnik można znaleźć razem z elementem getPointerId(actionIndex).

Rysunek 6. Kreska rysikiem i dotyk dłoni tworzą zestawy w MotionEvent. Dotknięcie dłoni zostanie anulowane, a wyświetlacz zostanie wyrenderowany ponownie.

Pełny ekran, od krawędzi do krawędzi i gesty nawigacyjne

Jeśli aplikacja jest wyświetlana na pełnym ekranie i przy jej krawędziach zawiera interaktywne elementy, takie jak obszar roboczy aplikacji do rysowania lub tworzenia notatek, przesunięcie palcem od dołu do dołu w celu wyświetlenia nawigacji lub przeniesienie aplikacji w tle może spowodować niechciane dotknięcie obszaru roboczego.

Rysunek 7. Gest przesuwania pozwala przesunąć aplikację w tle.

Aby zapobiegać niepożądanym kliknięciom w aplikacji za pomocą gestów, możesz stosować wstawki i ACTION_CANCEL.

Zapoznaj się też z sekcją Odrzucenie palca, nawigacja i niechciane dane wejściowe powyżej.

Użyj metody setSystemBarsBehavior() i BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE w WindowInsetsController, aby zapobiec wywoływaniu niepożądanych zdarzeń dotknięcia za pomocą gestów nawigacyjnych:

Kotlin

// Configure the behavior of the hidden system bars.
windowInsetsController.systemBarsBehavior =
    WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE

Java

// Configure the behavior of the hidden system bars.
windowInsetsController.setSystemBarsBehavior(
    WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
);

Więcej informacji o zarządzaniu wstawianiem elementów i gestami znajdziesz tutaj:

Małe opóźnienie

Opóźnienie to czas potrzebny sprzętowi, systemowi i aplikacjom na przetworzenie i wyrenderowanie danych wejściowych użytkownika.

Czas oczekiwania = przetwarzanie danych wejściowych sprzętu i systemu operacyjnego + przetwarzanie aplikacji + komponowanie systemu + renderowanie sprzętowe

Z powodu opóźnienia wyrenderowana kreska jest opóźniona względem położenia rysika. Różnica między wyrenderowanym stylem a położeniem rysika odzwierciedla opóźnienie.
Rysunek 8. Z powodu opóźnienia wyrenderowana kreska jest opóźniona względem położenia rysika.

Źródło opóźnienia

  • Rejestrowanie rysika na ekranie dotykowym (sprzęt): pierwsze połączenie bezprzewodowe, gdy rysik i system operacyjny komunikują się ze sobą w celu zarejestrowania i zsynchronizowania.
  • Częstotliwość próbkowania dotykowego (sprzęt): liczba razy na sekundę ekranu dotykowego sprawdza, czy wskaźnik dotyka powierzchni. Wartość z zakresu od 60 do 1000 Hz.
  • Przetwarzanie danych wejściowych (aplikacja): stosowanie kolorów, efektów graficznych i przekształcania według danych wejściowych użytkownika.
  • Renderowanie graficzne (system operacyjny i sprzęt): zamiana buforów, przetwarzanie sprzętowe.

Grafika z krótkim czasem oczekiwania

Biblioteka grafiki Jetpack o krótkim czasie oczekiwania skraca czas przetwarzania między danymi wprowadzonymi przez użytkownika a renderowaniem na ekranie.

Biblioteka skraca czas przetwarzania dzięki unikaniu renderowania wielobuforowego i wykorzystywaniu techniki renderowania bufora przedniego, która oznacza zapisywanie danych bezpośrednio na ekranie.

Renderowanie przedniego bufora

Przedni bufor to pamięć używana przez ekran do renderowania. To najbliższy obszar, w którym aplikacje mogą rysować bezpośrednio na ekranie. Biblioteka o krótkim czasie oczekiwania umożliwia aplikacjom renderowanie bezpośrednio w przednim buforze. Poprawia to wydajność, ponieważ zapobiega zamianie bufora, która może wystąpić w przypadku zwykłego renderowania z wieloma buforami lub podwójnego buforowania (najczęstszy przypadek).

Aplikacja zapisuje dane w buforze ekranu i odczytuje z niego dane.
Rysunek 9. Renderowanie z przodu bufora.
Aplikacja zapisuje dane w wielu buforach i zamienia się w bufor ekranu. Aplikacja odczytuje dane z bufora ekranu.
Rysunek 10. Renderowanie z użyciem wielu buforów.

Renderowanie z buforem przednim to świetna technika renderowania małego obszaru ekranu, ale nie służy do odświeżania całego ekranu. W przypadku renderowania z buforem przednim aplikacja renderuje treść do bufora, z którego odczytuje wyświetlacz. W rezultacie może dojść do wyrenderowania artefaktów lub rozerania się(patrz poniżej).

Biblioteka o krótkim czasie oczekiwania jest dostępna na urządzeniach z Androidem 10 (poziom interfejsu API 29) i nowszym oraz na urządzeniach z ChromeOS z Androidem 10 (poziom API 29) lub nowszym.

Zależności

Biblioteka o krótkim czasie oczekiwania dostarcza komponenty do implementacji renderowania bufora przedniego. Biblioteka jest dodawana jako zależność w pliku build.gradle modułu aplikacji:

dependencies {
    implementation "androidx.graphics:graphics-core:1.0.0-alpha03"
}

Wywołania zwrotne GLFrontBufferRenderer

Biblioteka o krótkim czasie oczekiwania zawiera interfejs GLFrontBufferRenderer.Callback, który definiuje te metody:

Biblioteka o krótkim czasie oczekiwania nie zależy od typu danych używanych w usłudze GLFrontBufferRenderer.

Biblioteka przetwarza je jako strumień setek punktów danych, więc musisz tak zaprojektować dane, aby zoptymalizować wykorzystanie pamięci i jej przydzielanie.

Wywołania zwrotne

Aby włączyć wywołania zwrotne renderowania, zaimplementuj funkcję GLFrontBufferedRenderer.Callback i zastąp onDrawFrontBufferedLayer() i onDrawDoubleBufferedLayer(). Funkcja GLFrontBufferedRenderer używa wywołań zwrotnych do renderowania danych w najbardziej zoptymalizowany sposób.

Kotlin

val callback = object: GLFrontBufferedRenderer.Callback<DATA_TYPE> {

   override fun onDrawFrontBufferedLayer(
       eglManager: EGLManager,
       bufferInfo: BufferInfo,
       transform: FloatArray,
       param: DATA_TYPE
   ) {
       // OpenGL for front buffer, short, affecting small area of the screen.
   }

   override fun onDrawMultiDoubleBufferedLayer(
       eglManager: EGLManager,
       bufferInfo: BufferInfo,
       transform: FloatArray,
       params: Collection<DATA_TYPE>
   ) {
       // OpenGL full scene rendering.
   }
}

Java

GLFrontBufferedRenderer.Callback<DATA_TYPE> callbacks =
    new GLFrontBufferedRenderer.Callback<DATA_TYPE>() {
        @Override
        public void onDrawFrontBufferedLayer(@NonNull EGLManager eglManager,
            @NonNull BufferInfo bufferInfo,
            @NonNull float[] transform,
            DATA_TYPE data_type) {
                // OpenGL for front buffer, short, affecting small area of the screen.
        }

    @Override
    public void onDrawDoubleBufferedLayer(@NonNull EGLManager eglManager,
        @NonNull BufferInfo bufferInfo,
        @NonNull float[] transform,
        @NonNull Collection<? extends DATA_TYPE> collection) {
            // OpenGL full scene rendering.
    }
};
Deklarowanie instancji GLFrontBufferedRenderer

Przygotuj GLFrontBufferedRenderer, podając utworzone wcześniej zdarzenia SurfaceView i wywołania zwrotne. GLFrontBufferedRenderer optymalizuje renderowanie na początku i podwójny bufor przy użyciu wywołań zwrotnych:

Kotlin

var glFrontBufferRenderer = GLFrontBufferedRenderer<DATA_TYPE>(surfaceView, callbacks)

Java

GLFrontBufferedRenderer<DATA_TYPE> glFrontBufferRenderer =
    new GLFrontBufferedRenderer<DATA_TYPE>(surfaceView, callbacks);
Renderowanie

Renderowanie bufora przedniego rozpoczyna się po wywołaniu metody renderFrontBufferedLayer(), która wywołuje wywołanie zwrotne onDrawFrontBufferedLayer().

Renderowanie z podwójnym buforem jest wznawiane po wywołaniu funkcji commit(), która aktywuje wywołanie zwrotne onDrawMultiDoubleBufferedLayer().

W poniższym przykładzie proces renderuje się w przednim buforze (szybkie renderowanie), gdy użytkownik zacznie rysować na ekranie (ACTION_DOWN), a potem przesunie kursor (ACTION_MOVE). Gdy wskaźnik opuści powierzchnię ekranu (ACTION_UP), proces renderuje się w podwójnym buforze.

Możesz użyć funkcji requestUnbufferedDispatch(), aby poprosić system wprowadzania, by nie grupował zdarzeń ruchu, a zamiast nich wysyłał je, gdy tylko są dostępne:

Kotlin

when (motionEvent.action) {
   MotionEvent.ACTION_DOWN -> {
       // Deliver input events as soon as they arrive.
       view.requestUnbufferedDispatch(motionEvent)
       // Pointer is in contact with the screen.
       glFrontBufferRenderer.renderFrontBufferedLayer(DATA_TYPE)
   }
   MotionEvent.ACTION_MOVE -> {
       // Pointer is moving.
       glFrontBufferRenderer.renderFrontBufferedLayer(DATA_TYPE)
   }
   MotionEvent.ACTION_UP -> {
       // Pointer is not in contact in the screen.
       glFrontBufferRenderer.commit()
   }
   MotionEvent.CANCEL -> {
       // Cancel front buffer; remove last motion set from the screen.
       glFrontBufferRenderer.cancel()
   }
}

Java

switch (motionEvent.getAction()) {
   case MotionEvent.ACTION_DOWN: {
       // Deliver input events as soon as they arrive.
       surfaceView.requestUnbufferedDispatch(motionEvent);

       // Pointer is in contact with the screen.
       glFrontBufferRenderer.renderFrontBufferedLayer(DATA_TYPE);
   }
   break;
   case MotionEvent.ACTION_MOVE: {
       // Pointer is moving.
       glFrontBufferRenderer.renderFrontBufferedLayer(DATA_TYPE);
   }
   break;
   case MotionEvent.ACTION_UP: {
       // Pointer is not in contact in the screen.
       glFrontBufferRenderer.commit();
   }
   break;
   case MotionEvent.ACTION_CANCEL: {
       // Cancel front buffer; remove last motion set from the screen.
       glFrontBufferRenderer.cancel();
   }
   break;
}

Co robić, a czego nie robić

Należy

Niewielkie fragmenty ekranu, pismo odręczne, rysowanie, szkicowanie.

Przeciwwskazania

Aktualizacja na pełnym ekranie, przesunięcie, powiększenie. Może to spowodować rozdarcie.

Łzawienie

Rozdarcie pojawia się, gdy ekran odświeża się, gdy jednocześnie modyfikowany jest bufor ekranu. W jednej części ekranu widoczne są nowe dane, a w innej – stare.

Górne i dolne części obrazu Androida są niewłaściwie wyrównane z powodu rozdarcia przy odświeżaniu ekranu.
Rysunek 11. Podczas odświeżania ekranu z góry na dół

Przewidywanie ruchu

Biblioteka prognoz ruchu jetpacka zmniejsza postrzegane opóźnienie, szacując ścieżkę ruchu użytkownika i dostarczając tymczasowe, sztuczne punkty do mechanizmu renderowania.

Biblioteka prognoz ruchu pobiera dane wejściowe użytkownika jako obiekty MotionEvent. Obiekty zawierają informacje o współrzędnych x i y, ciśnieniu i czasie. Funkcje te są wykorzystywane przez funkcję prognozowania ruchu do przewidywania przyszłych obiektów MotionEvent.

Prognozowane obiekty (MotionEvent) są tylko szacunkowe. Prognozowane zdarzenia mogą zmniejszyć postrzegane czasy oczekiwania, ale dane prognozowane muszą zostać zastąpione rzeczywistymi danymi MotionEvent po ich otrzymaniu.

Biblioteka prognoz ruchu jest dostępna na urządzeniach z Androidem 4.4 (poziom interfejsu API 19) i nowszym oraz na urządzeniach z ChromeOS z Androidem 9 (poziom interfejsu API 28) lub nowszym.

Z powodu opóźnienia wyrenderowana kreska jest opóźniona względem położenia rysika. Przerwa między kreską a rysikiem jest wypełniana punktami prognozy. Pozostała przerwa to postrzegane opóźnienie.
Rysunek 12. Czas oczekiwania skrócony przez prognozę ruchu.

Zależności

Biblioteka prognoz ruchu umożliwia ich implementację. Biblioteka jest dodawana jako zależność w pliku build.gradle modułu aplikacji:

dependencies {
    implementation "androidx.input:input-motionprediction:1.0.0-beta01"
}

Implementacja

Biblioteka prognoz ruchu zawiera interfejs MotionEventPredictor, który definiuje te metody:

  • record(): zapisuje obiekty MotionEvent jako rejestr działań użytkownika
  • predict(): zwraca przewidywaną wartość MotionEvent
Zadeklaruj wystąpienie elementu MotionEventPredictor

Kotlin

var motionEventPredictor = MotionEventPredictor.newInstance(view)

Java

MotionEventPredictor motionEventPredictor = MotionEventPredictor.newInstance(surfaceView);
Podaj dane prognozy

Kotlin

motionEventPredictor.record(motionEvent)

Java

motionEventPredictor.record(motionEvent);
Prognoza

Kotlin

when (motionEvent.action) {
   MotionEvent.ACTION_MOVE -> {
       val predictedMotionEvent = motionEventPredictor?.predict()
       if(predictedMotionEvent != null) {
            // use predicted MotionEvent to inject a new artificial point
       }
   }
}

Java

switch (motionEvent.getAction()) {
   case MotionEvent.ACTION_MOVE: {
       MotionEvent predictedMotionEvent = motionEventPredictor.predict();
       if(predictedMotionEvent != null) {
           // use predicted MotionEvent to inject a new artificial point
       }
   }
   break;
}

Co należy, a czego nie należy robić w przypadku prognozowania ruchu

Należy

Po dodaniu nowego punktu prognozy usuń punkty prognozy.

Przeciwwskazania

Nie używaj punktów prognozy do końcowego renderowania.

Notatki

ChromeOS umożliwia aplikacji deklarowanie niektórych działań związanych z notatkami.

Aby zarejestrować aplikację w ChromeOS jako aplikację do robienia notatek, przeczytaj artykuł Zgodność urządzeń wejściowych.

Aby zarejestrować aplikację na Androidzie jako aplikację do notatek, przeczytaj artykuł Tworzenie aplikacji do notatek.

W Androidzie 14 (poziom interfejsu API 34) wprowadziliśmy intencję ACTION_CREATE_NOTE, która umożliwia aplikacji rozpoczynanie robienia notatek na ekranie blokady.

Cyfrowe rozpoznawanie tuszu za pomocą ML Kit

Dzięki cyfrowemu rozpoznawaniu pisma ML Kit Twoja aplikacja może rozpoznawać w setkach języków tekst odręczny na cyfrowej powierzchni. Możesz też klasyfikować szkice.

ML Kit udostępnia klasę Ink.Stroke.Builder w celu tworzenia obiektów Ink, które mogą być przetwarzane przez modele systemów uczących się w celu konwertowania pisma odręcznego na tekst.

Oprócz rozpoznawania pisma odręcznego model rozpoznaje gesty takie jak usunięcie czy zakreślenie.

Więcej informacji znajdziesz w artykule Cyfrowe rozpoznawanie atramentu.

Dodatkowe materiały

Przewodniki dla programistów

Ćwiczenia z programowania