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łaniufling()
. Ta metoda wymaga obliczania przesunięcia przewijania w poluonDraw()
i wywoływania metodypostInvalidate()
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łanieaddUpdateListener()
. Ta metoda pozwala animować właściwości elementuView
.
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();