Tworzenie widoku niestandardowego jako interaktywnego

Wypróbuj sposób tworzenia wiadomości
Jetpack Compose to zalecany zestaw narzędzi UI na Androida. Dowiedz się, jak korzystać z układów w sekcji Utwórz

Rysowanie interfejsu to tylko jeden z elementów tworzenia widoku niestandardowego. Musisz też zadbać o to, aby widok reagował na działania użytkowników w sposób bardzo zbliżony do rzeczywistego działania, które naśladujesz.

Spraw, aby obiekty w aplikacji działały tak, jak robią to prawdziwe obiekty. Na przykład nie pozwól, aby obrazy w aplikacji wyskakiwały i pojawiały się w innym miejscu, ponieważ obiekty w świecie rzeczywistym tak nie robią. Zamiast tego przenoś obrazy z jednego miejsca w drugie.

Użytkownicy wyczuwają nawet subtelne działanie interfejsu i jego działanie i najlepiej reagują na subtelności naśladujące rzeczywisty świat. Na przykład, gdy użytkownik przesuwa obiekt interfejsu, daj im na początku wrażenie bezwładności, która opóźnia ruch. Na końcu ruchu daj mu wrażenie pędu, który pozwala unieść obiekt.

Na tej stronie pokazujemy, jak za pomocą funkcji platformy Androida dodać do widoku niestandardowego dane o rzeczywistych zachowaniach.

Dodatkowe powiązane informacje znajdziesz w artykułach Omówienie zdarzeń wejściowych i Omówienie animacji usługi.

Obsługa gestów wprowadzania

Podobnie jak wiele innych platform interfejsu, Android obsługuje model zdarzeń wejściowych. Działania użytkownika przekształcają się w zdarzenia, które aktywują wywołania zwrotne. Możesz je zastąpić, aby dostosować sposób reakcji aplikacji na użytkownika. Najczęstsze zdarzenie wejściowe w systemie Android to dotknięcie, które uruchamia onTouchEvent(android.view.MotionEvent). Aby obsługiwać zdarzenie, zastąp tę metodę w ten sposób:

Kotlin

override fun onTouchEvent(event: MotionEvent): Boolean {
    return super.onTouchEvent(event)
}

Java

@Override
   public boolean onTouchEvent(MotionEvent event) {
    return super.onTouchEvent(event);
   }

Same zdarzenia dotknięcia nie są szczególnie przydatne. Nowoczesne interfejsy dotykowe definiują interakcje za pomocą gestów, takich jak klikanie, ściąganie, pchnięcie, przesuwanie i powiększanie. Do konwertowania nieprzetworzonych zdarzeń kliknięcia na gesty Android udostępnia interfejs GestureDetector.

Utwórz GestureDetector, przekazując instancję klasy, która implementuje GestureDetector.OnGestureListener. Jeśli chcesz przetworzyć tylko kilka gestów, możesz rozszerzyć pole GestureDetector.SimpleOnGestureListener, zamiast implementować interfejs GestureDetector.OnGestureListener. Ten kod na przykład tworzy klasę, która rozszerza zakres GestureDetector.SimpleOnGestureListener i zastępuje onDown(MotionEvent).

Kotlin

private val myListener =  object : GestureDetector.SimpleOnGestureListener() {
    override fun onDown(e: MotionEvent): Boolean {
        return true
    }
}

private val detector: GestureDetector = GestureDetector(context, myListener)

Java

class MyListener extends GestureDetector.SimpleOnGestureListener {
   @Override
   public boolean onDown(MotionEvent e) {
       return true;
   }
}
detector = new GestureDetector(getContext(), new MyListener());

Niezależnie od tego, czy użyjesz właściwości GestureDetector.SimpleOnGestureListener, zawsze musisz zaimplementować metodę onDown(), która zwraca wartość true. Jest to konieczne, ponieważ wszystkie gesty zaczynają się od wiadomości onDown(). Jeśli zwracasz metodę false z metody onDown(), tak jak robi to GestureDetector.SimpleOnGestureListener, system zakłada, że chcesz zignorować resztę gestu, a inne metody GestureDetector.OnGestureListener nie są wywoływane. Zwracaj wartość false z działania onDown() tylko wtedy, gdy chcesz zignorować cały gest.

Po zaimplementowaniu GestureDetector.OnGestureListener i utworzeniu wystąpienia GestureDetector możesz za pomocą GestureDetector interpretować zdarzenia dotknięcia otrzymywanych w interfejsie onTouchEvent().

Kotlin

override fun onTouchEvent(event: MotionEvent): Boolean {
    return detector.onTouchEvent(event).let { result ->
        if (!result) {
            if (event.action == MotionEvent.ACTION_UP) {
                stopScrolling()
                true
            } else false
        } else true
    }
}

Java

@Override
public boolean onTouchEvent(MotionEvent event) {
   boolean result = detector.onTouchEvent(event);
   if (!result) {
       if (event.getAction() == MotionEvent.ACTION_UP) {
           stopScrolling();
           result = true;
       }
   }
   return result;
}

Gdy przekazujesz onTouchEvent() zdarzenie dotknięcia, którego nie rozpoznaje w ramach gestu, zwraca wartość false. Następnie możesz uruchomić własny niestandardowy kod wykrywania gestów.

Twórz miarodajne ruchy fizyczne

Gesty to skuteczny sposób sterowania urządzeniami z ekranem dotykowym, ale mogą być sprzeczne z intuicją i trudne do zapamiętania, chyba że dają miarodajne wyniki.

Załóżmy np., że chcesz zastosować gest przechylenia w poziomie, który powoduje, że element narysowany w widoku obraca się wokół swojej osi pionowej. Ten gest ma sens, jeśli interfejs reaguje szybko w kierunku przesunięcia, a potem zwalnia, jakby użytkownik popychał koło zamachowe i obrócił się w tę stronę.

Dokumentacja dotycząca animowania gestu przewijania zawiera szczegółowe objaśnienie, jak wdrożyć własne zachowanie czasu. Jednak symulowanie działania koła zamachowego nie jest proste. Aby model koła zamachowego działał prawidłowo, potrzebna jest spora część fizyki i matematyki. Na szczęście Android udostępnia klasy pomocnicze, które symulują takie zachowania i inne zachowania. Klasa Scroller jest podstawą obsługi gestów rzucania w kształcie koła zamachowego.

Aby rozpocząć rzut, wywołaj fling() z prędkością początkową oraz minimalną i maksymalną wartością x i y przesuwania. Jako wartości prędkości możesz użyć wartości obliczonej przez GestureDetector.

Kotlin

fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
    scroller.fling(
            currentX,
            currentY,
            (velocityX / SCALE).toInt(),
            (velocityY / SCALE).toInt(),
            minX,
            minY,
            maxX,
            maxY
    )
    postInvalidate()
    return true
}

Java

@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
   scroller.fling(currentX, currentY, velocityX / SCALE, velocityY / SCALE, minX, minY, maxX, maxY);
   postInvalidate();
    return true;
}

Wywołanie fling() konfiguruje model fizyczny na potrzeby gestu przesuwania. Następnie zaktualizuj Scroller, wywołując w regularnych odstępach czasu polecenie Scroller.computeScrollOffset(). computeScrollOffset() aktualizuje stan wewnętrzny obiektu Scroller, odczytując bieżącą godzinę i używając modelu fizycznego do obliczenia pozycji x i y w tym momencie. Wywołaj funkcje getCurrX() i getCurrY(), aby pobrać te wartości.

Większość widoków przekazuje pozycje x i y obiektu Scroller bezpośrednio do scrollTo(). Ten przykład jest nieco inny: do określenia kąta obrotu widoku używana jest obecna pozycja x przewijania.

Kotlin

scroller.apply {
    if (!isFinished) {
        computeScrollOffset()
        setItemRotation(currX)
    }
}

Java

if (!scroller.isFinished()) {
    scroller.computeScrollOffset();
    setItemRotation(scroller.getCurrX());
}

Klasa Scroller oblicza pozycje przewijania, ale nie stosuje ich automatycznie do widoku. Często wprowadzaj nowe współrzędne, by zapewnić płynność przewijania. Można to zrobić na 2 sposoby:

  • Wymuś ponowne rysowanie, wywołując postInvalidate() po wywołaniu funkcji fling(). Ta metoda wymaga obliczania przesunięcia czasowego w funkcji onDraw() i wywoływania funkcji postInvalidate() po każdej zmianie przesunięcia.
  • Skonfiguruj element ValueAnimator, który będzie animowany w czasie trwania flingu, i dodaj odbiornik, który będzie przetwarzać aktualizacje animacji za pomocą wywołania addUpdateListener(). Ta metoda pozwala animować właściwości obiektu View.

Płynne przejścia

Użytkownicy oczekują, że nowoczesny interfejs płynnie przełącza się między stanami: elementy interfejsu pojawiają się i znikają zamiast się pojawiać i znikają, a ruchy na początku i kończeniu płynnie się rozpoczynają i nie zatrzymują. Platforma animacji właściwości na Androidzie ułatwia płynne przejścia.

Jeśli chcesz korzystać z systemu animacji, za każdym razem, gdy właściwość zmieni to, co wpływa na wygląd widoku, nie zmieniaj jej bezpośrednio. Zamiast tego użyj ValueAnimator, aby wprowadzić zmianę. W poniższym przykładzie zmiana wybranego komponentu podrzędnego w widoku powoduje obrócenie całego renderowanego widoku w taki sposób, że wskaźnik wyboru jest wyśrodkowany. ValueAnimator zmienia rotację na kilkaset milisekund zamiast od razu ustawić nową wartość rotacji.

Kotlin

autoCenterAnimator = ObjectAnimator.ofInt(this, "Rotation", 0).apply {
    setIntValues(targetAngle)
    duration = AUTOCENTER_ANIM_DURATION
    start()
}

Java

autoCenterAnimator = ObjectAnimator.ofInt(this, "Rotation", 0);
autoCenterAnimator.setIntValues(targetAngle);
autoCenterAnimator.setDuration(AUTOCENTER_ANIM_DURATION);
autoCenterAnimator.start();

Jeśli wartość, którą chcesz zmienić, jest jedną z podstawowych właściwości View, tworzenie animacji jest jeszcze łatwiejsze, ponieważ widoki danych mają wbudowaną ViewPropertyAnimator zoptymalizowaną pod kątem jednoczesnej animacji wielu właściwości, jak w tym przykładzie:

Kotlin

animate()
    .rotation(targetAngle)
    .duration = ANIM_DURATION
    .start()

Java

animate().rotation(targetAngle).setDuration(ANIM_DURATION).start();