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 funkcji Utwórz

Rysowanie interfejsu użytkownika to tylko jeden z etapów tworzenia widoku niestandardowego. Widok musi też reagować na dane wejściowe użytkownika w sposób zbliżony do prawdziwego działania, które naśladujesz.

Spraw, by obiekty w aplikacji działały tak, jak robią to prawdziwe obiekty. Nie można na przykład pozwolić, by obrazy w aplikacji przestały istnieć i pojawiły się w innym miejscu, ponieważ obiekty w świecie rzeczywistym tego nie robią. Zamiast tego przenieś obrazy z jednego miejsca do drugiego.

Użytkownicy wyczuwają nawet subtelne zachowania i działania interfejsu i najlepiej reagują na subtelności naśladujące rzeczywisty świat. Jeśli na przykład użytkownik przesuwa obiekt interfejsu, daj mu poczucie bezwładności, który opóźnia ruch. Pod koniec ruchu daj dziecku poczucie pędu, które przenosi obiekt poza granicę.

Na tej stronie pokazujemy, jak za pomocą funkcji platformy Androida dodać do widoku niestandardowego te rzeczywiste zachowania.

Więcej powiązanych informacji znajdziesz w sekcjach Zdarzenia wejściowe – omówienie i Omówienie animacji usługi.

Uchwyć gesty wprowadzania

Podobnie jak wiele innych platform interfejsu, Android obsługuje model zdarzeń wejściowych. Działania użytkownika zmieniają się w zdarzenia, które wywołują wywołania zwrotne. Możesz zastąpić wywołania zwrotne, aby dostosować sposób, w jaki aplikacja odpowiada użytkownikowi. Najczęstszym zdarzeniem wejściowym w systemie Android jest touch, który wywołuje onTouchEvent(android.view.MotionEvent). Zastąp tę metodę obsługi zdarzenia 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);
   }

Zdarzenia dotknięcia nie są szczególnie przydatne. Nowoczesne interfejsy dotykowe definiują interakcje za pomocą gestów, takich jak dotykanie, przeciąganie, pchanie, przesuwanie i powiększanie. Aby przekonwertować nieprzetworzone zdarzenia dotyku na gesty, Android udostępnia funkcję GestureDetector.

Utwórz GestureDetector, przekazując wystąpienie klasy implementującej GestureDetector.OnGestureListener. Jeśli chcesz przetworzyć tylko kilka gestów, możesz rozwinąć GestureDetector.SimpleOnGestureListener zamiast implementować interfejs GestureDetector.OnGestureListener. Na przykład ten kod 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żywasz GestureDetector.SimpleOnGestureListener, zawsze wdrażaj metodę onDown(), która zwraca true. Jest to konieczne, ponieważ wszystkie gesty zaczynają się od komunikatu onDown(). Jeśli zwrócisz false z onDown() (podobnie jak GestureDetector.SimpleOnGestureListener), system zakłada, że chcesz zignorować resztę gestu. Pozostałe metody metody GestureDetector.OnGestureListener nie są wywoływane. Jeśli chcesz zignorować cały gest, zwróć tylko wartość false z funkcji onDown().

Po zaimplementowaniu GestureDetector.OnGestureListener i utworzeniu wystąpienia GestureDetector możesz za pomocą GestureDetector zinterpretować otrzymywane zdarzenia dotyku w funkcji 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 przekażesz usłudze onTouchEvent() zdarzenie dotyku, którego nie rozpoznaje jako gestu, zwraca wartość false. Następnie można uruchomić własny kod wykrywania gestów.

Utwórz ruch, który może być fizycznie

Gesty to skuteczny sposób sterowania urządzeniami z ekranem dotykowym, ale mogą być nieintuicyjne i trudne do zapamiętania, jeśli nie dają realistycznych rezultatów.

Załóżmy na przykład, że chcesz zaimplementować gest odchylenia w poziomie, który sprawi, że przedmiot narysowany w widoku obraca się wokół osi pionowej. Ten gest ma sens, jeśli interfejs reaguje szybko w kierunku ruchu, a następnie zwalnia, jak gdyby użytkownik naciskał koło zamachowe i zakręcało się.

Dokumentacja animowania gestu przewijania zawiera szczegółowe wyjaśnienie, jak wdrożyć własny mechanizm. Jednak symulowanie działania koła zamachowego nie jest takie proste. Aby model zamachu działał prawidłowo, trzeba zrezygnować z dużej wiedzy z zakresu fizyki i matematyki. Na szczęście Android udostępnia klasy pomocnicze symulujące to i inne zachowania. Klasa Scroller jest podstawą obsługi gestów przesuwania przypominających koło zamachowe.

Aby rozpocząć przesuwanie, wywołaj fling() z prędkością początkową oraz minimalną i maksymalną wartością x i y. 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() powoduje skonfigurowanie modelu fizycznego gestu ucieczki. Następnie zaktualizuj Scroller, wywołując Scroller.computeScrollOffset() w regularnych odstępach czasu. Funkcja computeScrollOffset() aktualizuje wewnętrzny stan obiektu Scroller, odczytując bieżącą godzinę i stosując model fizyki, aby obliczyć w tym momencie położenie x i y. Aby pobrać te wartości, wywołaj getCurrX() i getCurrY().

Większość widoków przekazuje pozycje x i y obiektu Scroller bezpośrednio do scrollTo(). Ten przykład wygląda trochę inaczej: do ustawienia kąta obrotu widoku wykorzystuje się bieżącą pozycję przewijania x.

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 w Twoim widoku. Stosuj nowe współrzędne na tyle często, by przewijanie animacji wyglądało płynnie. Możesz to zrobić na 2 sposoby:

  • Wymuś ponowne renderowanie, wywołując postInvalidate() po wywołaniu fling(). Ta metoda wymaga obliczania przesunięcia przewijania w polu onDraw() i wywoływania metody postInvalidate() przy każdej zmianie przesunięcia przewijania.
  • Skonfiguruj ValueAnimator, aby animacja była animowana na czas trwania przelotu, i dodaj detektor, który będzie przetwarzać aktualizacje animacji przez wywołanie addUpdateListener(). Ta metoda pozwala animować właściwości elementu View.

Płynne przejścia

Użytkownicy oczekują, że nowoczesny interfejs będzie płynnie przechodzić między stanami: elementy interfejsu wyłaniają się i znikają, a nie pojawiają się i znikają, a ruchy zaczynają i kończą płynnie, a nie nagle rozpoczynają i zatrzymują się. Ramka 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, co wpływa na wygląd widoku, nie zmieniaj jej bezpośrednio. Zamiast tego użyj polecenia ValueAnimator, aby wprowadzić zmianę. W poniższym przykładzie zmiana wybranego komponentu podrzędnego w widoku powoduje obrót całego wyrenderowanego widoku w taki sposób, że wskaźnik wyboru jest wyśrodkowany. ValueAnimator zmienia obrót w okresie kilkuset milisekund, a nie od razu i nie ustawia od razu nowej wartości 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 właściwości podstawowych View, tworzenie animacji jest jeszcze łatwiejsze, ponieważ widoki mają wbudowaną funkcję ViewPropertyAnimator, która jest zoptymalizowana pod kątem jednoczesnej animacji wielu usług, jak w tym przykładzie:

Kotlin

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

Java

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