Tworzenie widoku niestandardowego jako interaktywnego

Wypróbuj metodę Compose
Jetpack Compose to zalecany zestaw narzędzi interfejsu na Androida. Dowiedz się, jak pracować z układami w Compose.

Rysowanie interfejsu to tylko jeden z elementów tworzenia widoku niestandardowego. Musisz też sprawić, aby widok reagował na dane wejściowe użytkownika w sposób jak najbardziej zbliżony do rzeczywistego działania, które naśladujesz.

Spraw, aby obiekty w aplikacji zachowywały się jak prawdziwe. Na przykład nie pozwól, aby obrazy w aplikacji znikały i pojawiały się w innych miejscach, ponieważ obiekty w świecie rzeczywistym tak nie działają. Zamiast tego przenieś obrazy z jednego miejsca do drugiego.

Użytkownicy wyczuwają nawet subtelne zachowania lub odczucia w interfejsie i najlepiej reagują na subtelności, które naśladują świat rzeczywisty. Gdy użytkownicy np. przesuwają obiekt interfejsu, na początku daj im poczucie bezwładności, które opóźnia ruch. Na końcu ruchu nadaj im poczucie pędu, który przeniesie obiekt poza rzut.

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

Dodatkowe informacje znajdziesz w artykułach Omówienie zdarzeń wejściowychOmówienie animacji właściwości.

Obsługa gestów wprowadzania

Podobnie jak wiele innych platform interfejsu użytkownika, Android obsługuje model zdarzeń wejściowych. Działania użytkownika są przekształcane w zdarzenia, które wywołują wywołania zwrotne. Możesz zastąpić wywołania zwrotne, aby dostosować sposób, w jaki aplikacja reaguje na działania użytkownika. Najczęstszym zdarzeniem wejściowym w systemie Android jest dotknięcie, które wywołuje onTouchEvent(android.view.MotionEvent). Zastąp tę metodę, aby obsłużyć zdarzenie, 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 w postaci gestów, takich jak dotknięcie, przeciągnięcie, pchnięcie, szybkie przesunięcie i powiększenie. Aby przekształcić surowe zdarzenia dotyku w gesty, Android udostępnia GestureDetector.

Utwórz GestureDetector, przekazując instancję klasy, która implementuje GestureDetector.OnGestureListener. Jeśli chcesz przetwarzać tylko kilka gestów, możesz rozszerzyć klasę GestureDetector.SimpleOnGestureListener zamiast implementować interfejs GestureDetector.OnGestureListener. Na przykład ten kod tworzy klasę, która rozszerza 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 wiadomości onDown(). Jeśli zwrócisz wartość falseonDown(), tak jak GestureDetector.SimpleOnGestureListener, system założy, że chcesz zignorować resztę gestu, i nie wywoła innych metod GestureDetector.OnGestureListener. Zwróć tylko wartość falseonDown(), jeśli chcesz zignorować cały gest.

Po zaimplementowaniu interfejsu GestureDetector.OnGestureListener i utworzeniu instancji interfejsu GestureDetector możesz używać interfejsu GestureDetector do interpretowania zdarzeń dotykowych 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 przekażesz onTouchEvent() zdarzenie dotknięcia, które nie jest rozpoznawane jako część gestu, zwraca false. Następnie możesz uruchomić własny kod wykrywania gestów.

Tworzenie realistycznych ruchów

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

Załóżmy na przykład, że chcesz wdrożyć gest poziomego przesunięcia, który powoduje, że element narysowany w widoku obraca się wokół osi pionowej. Ten gest ma sens, jeśli interfejs reaguje szybkim przesunięciem w kierunku przeciągnięcia, a następnie zwalnia, tak jakby użytkownik popchnął koło zamachowe i wprawił je w ruch.

Dokumentacja dotycząca animowania gestu przewijania zawiera szczegółowe wyjaśnienie, jak zaimplementować własne zachowanie przewijania. Symulowanie działania koła zamachowego nie jest jednak proste. Aby model koła zamachowego działał prawidłowo, potrzebna jest duża wiedza z zakresu fizyki i matematyki. Na szczęście Android udostępnia klasy pomocnicze, które symulują to i inne zachowania. Klasa Scroller jest podstawą obsługi gestów machania w stylu koła zamachowego.

Aby rozpocząć szybkie przesunięcie, wywołaj fling() z prędkością początkową oraz minimalnymi i maksymalnymi wartościami xy szybkiego przesunięcia. W przypadku wartości prędkości możesz użyć wartości obliczonej przez funkcję 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 funkcji fling() konfiguruje model fizyczny dla gestu przesunięcia. Następnie regularnie aktualizuj Scroller, wywołując Scroller.computeScrollOffset(). computeScrollOffset() aktualizuje stan wewnętrzny obiektu Scroller, odczytując bieżący czas i używając modelu fizycznego do obliczenia pozycji xy w tym czasie. Wywołaj funkcje getCurrX() i getCurrY() , aby pobrać te wartości.

Większość widoków przekazuje pozycje xy obiektu Scroller bezpośrednio do scrollTo(). Ten przykład jest nieco inny: wykorzystuje bieżącą pozycję przewijania x do ustawienia kąta obrotu widoku.

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. Nowe współrzędne należy stosować wystarczająco często, aby animacja przewijania wyglądała płynnie. Możesz to zrobić na 2 sposoby:

  • Wymuś ponowne rysowanie, wywołując funkcję postInvalidate() po wywołaniu funkcji fling(). Ta technika wymaga obliczania przesunięć przewijania w onDraw() i wywoływania funkcji postInvalidate() za każdym razem, gdy zmienia się przesunięcie przewijania.
  • Skonfiguruj ValueAnimator, aby animować przez cały czas trwania przesunięcia, i dodaj detektor do przetwarzania aktualizacji animacji przez wywołanie addUpdateListener(). Ta technika umożliwia animowanie 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 będą się pojawiać i znikać stopniowo, a animacje będą się rozpoczynać i kończyć płynnie, a nie nagle. Platforma animacji właściwości Androida ułatwia tworzenie płynnych przejść.

Aby korzystać z systemu animacji, za każdym razem, gdy właściwość zmienia wygląd widoku, nie zmieniaj jej bezpośrednio. Zamiast tego użyj elementu ValueAnimator, aby wprowadzić zmianę. W tym przykładzie zmiana wybranego komponentu podrzędnego w widoku powoduje obrócenie całego renderowanego widoku tak, aby wskaźnik wyboru był wyśrodkowany. ValueAnimator zmienia obrót w ciągu kilkuset milisekund, zamiast natychmiast ustawiać nową wartość obrotu.

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 Viewwłaściwości, animacja jest jeszcze łatwiejsza, ponieważ widoki mają wbudowany ViewPropertyAnimator, który jest zoptymalizowany 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();