Apps entwickeln

Mit Sammlungen den Überblick behalten Sie können Inhalte basierend auf Ihren Einstellungen speichern und kategorisieren.

Das folgende Material ist für App-Entwickler.

Damit Ihre App Rotary unterstützt, MÜSSEN Sie:

  1. Platzieren Sie eine FocusParkingView im jeweiligen Aktivitätslayout.
  2. Stellen Sie sicher, dass die Ansichten fokussierbar (oder nicht) sind.
  3. Verwenden Sie FocusArea s , um alle Ihre fokussierbaren Ansichten mit Ausnahme von FocusParkingView zu umschließen.

Jede dieser Aufgaben wird unten detailliert beschrieben, nachdem Sie Ihre Umgebung eingerichtet haben, um rotationsfähige Apps zu entwickeln.

Richten Sie einen Drehregler ein

Bevor Sie mit der Entwicklung drehfähiger Apps beginnen können, benötigen Sie entweder einen Drehregler oder einen Stellvertreter. Sie haben die unten beschriebenen Möglichkeiten.

Emulator

source build/envsetup.sh && lunch car_x86_64-userdebug
m -j
emulator -wipe-data -no-snapshot -writable-system

Sie können auch aosp_car_x86_64-userdebug .

So greifen Sie auf den emulierten Drehregler zu:

  1. Tippen Sie auf die drei Punkte unten in der Symbolleiste:

    Greifen Sie auf den emulierten Drehregler zu
    Abbildung 1. Zugriff auf emulierten Drehregler
  2. Wählen Sie im Fenster „Erweiterte Steuerung“ die Option „ Auto-Rotation “ aus:

    Wählen Sie Autorotation
    Abbildung 2. Wählen Sie Car Rotary aus

USB-Tastatur

  • Schließen Sie eine USB-Tastatur an Ihr Gerät an, auf dem Android Automotive OS (AAOS) ausgeführt wird. In einigen Fällen kann dies verhindern, dass die Bildschirmtastatur angezeigt wird.
  • Verwenden Sie einen userdebug oder eng -Build.
  • Schlüsselereignisfilterung aktivieren:
    adb shell settings put secure android.car.ROTARY_KEY_EVENT_FILTER 1
    
  • In der folgenden Tabelle finden Sie die entsprechende Taste für jede Aktion:
    Taste Rotationsaktion
    Q Gegen den Uhrzeigersinn drehen
    E Im Uhrzeiger sinn drehen
    EIN Nach links schubsen
    D Richtig schubsen
    W Stups dich an
    S Stups nach unten
    F oder Komma Mittlere Taste
    R oder Esc Zurück-Button

ADB-Befehle

Sie können car_service Befehle verwenden, um Rotationseingangsereignisse einzufügen. Diese Befehle können auf Geräten mit Android Automotive OS (AAOS) oder auf einem Emulator ausgeführt werden.

car_service-Befehle Dreheingang
adb shell cmd car_service inject-rotary Gegen den Uhrzeigersinn drehen
adb shell cmd car_service inject-rotary -c true Im Uhrzeiger sinn drehen
adb shell cmd car_service inject-rotary -dt 100 50 Mehrmals gegen den Uhrzeigersinn drehen (vor 100 ms und vor 50 ms)
adb shell cmd car_service inject-key 282 Nach links schubsen
adb shell cmd car_service inject-key 283 Richtig schubsen
adb shell cmd car_service inject-key 280 Stups dich an
adb shell cmd car_service inject-key 281 Stups nach unten
adb shell cmd car_service inject-key 23 Klicken Sie auf die mittlere Schaltfläche
adb shell input keyevent inject-key 4 Klicken Sie auf die Zurück-Schaltfläche

OEM-Drehregler

Wenn Ihre Drehregler-Hardware betriebsbereit ist, ist dies die realistischste Option. Es ist besonders nützlich, um schnelle Rotation zu testen.

FocusParkingView

FocusParkingView ist eine transparente Ansicht in der Car UI Library (car-ui-library) . RotaryService verwendet es zur Unterstützung der Drehregler-Navigation. FocusParkingView muss die erste fokussierbare Ansicht im Layout sein. Es muss außerhalb aller FocusArea s platziert werden. Jedes Fenster muss über eine FocusParkingView . Wenn Sie bereits das Basislayout car-ui-library verwenden, das eine FocusParkingView enthält, müssen Sie keine weitere FocusParkingView hinzufügen. Unten sehen Sie ein Beispiel für FocusParkingView in RotaryPlayground .

<FrameLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="match_parent">
   <com.android.car.ui.FocusParkingView
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"/>
   <FrameLayout
       android:layout_width="match_parent"
       android:layout_height="match_parent"/>
</FrameLayout>

Hier sind die Gründe, warum Sie eine FocusParkingView benötigen:

  1. Android löscht den Fokus nicht automatisch, wenn der Fokus in einem anderen Fenster gesetzt wird. Wenn Sie versuchen, den Fokus im vorherigen Fenster zu löschen, fokussiert Android eine Ansicht in diesem Fenster neu, was dazu führt, dass zwei Fenster gleichzeitig fokussiert werden. Durch Hinzufügen einer FocusParkingView zu jedem Fenster kann dieses Problem behoben werden. Diese Ansicht ist transparent und ihre standardmäßige Fokushervorhebung ist deaktiviert, sodass sie für den Benutzer unsichtbar ist, unabhängig davon, ob sie fokussiert ist oder nicht. Es kann den Fokus übernehmen, damit RotaryService den Fokus darauf parken kann, um die Fokushervorhebung zu entfernen.
  2. Wenn im aktuellen Fenster nur eine FocusArea vorhanden ist, bewirkt das Drehen des Controllers in der FocusArea , dass RotaryService den Fokus von der Ansicht rechts auf die Ansicht links verschiebt (und umgekehrt). Das Hinzufügen dieser Ansicht zu jedem Fenster kann das Problem beheben. Wenn RotaryService feststellt, dass das Fokusziel ein FocusParkingView ist, kann es feststellen, dass ein Umlauf bevorsteht, und an diesem Punkt den Umlauf vermeiden, indem es den Fokus nicht bewegt.
  3. Wenn der Drehregler eine App startet, fokussiert Android die erste fokussierbare Ansicht, die immer die FocusParkingView ist. Die FocusParkingView bestimmt die optimale Ansicht, auf die fokussiert werden soll, und wendet dann den Fokus an.

Fokussierbare Ansichten

RotaryService baut auf dem bestehenden Konzept der Ansichtsfokussierung des Android-Frameworks auf, das auf die Zeit zurückgeht, als Telefone physische Tastaturen und D-Pads hatten. Das vorhandene android:nextFocusForward -Attribut wird für Rotation umfunktioniert (siehe FocusArea-Anpassung ), android:nextFocusLeft , android:nextFocusRight , android:nextFocusUp und android:nextFocusDown jedoch nicht.

RotaryService konzentriert sich nur auf Ansichten, die fokussierbar sind. Einige Ansichten, wie z. B. Button , sind normalerweise fokussierbar. Andere, wie z. B. TextView s und ViewGroup s, sind dies normalerweise nicht. Anklickbare Ansichten sind automatisch fokussierbar und Ansichten sind automatisch anklickbar, wenn sie über einen Klick-Listener verfügen. Wenn diese automatische Logik zur gewünschten Fokussierbarkeit führt, müssen Sie die Fokussierbarkeit der Ansicht nicht explizit festlegen. Wenn die automatische Logik nicht zur gewünschten Fokussierbarkeit führt, legen Sie das Attribut android:focusable auf true oder false fest oder legen Sie die Fokussierbarkeit der Ansicht programmgesteuert mit View.setFocusable(boolean) . Damit RotaryService sich darauf konzentrieren kann, MUSS eine Ansicht die folgenden Anforderungen erfüllen:

  • Fokussierbar
  • Ermöglicht
  • Sichtbar
  • Haben Sie Werte ungleich Null für Breite und Höhe

Wenn eine Ansicht nicht alle diese Anforderungen erfüllt, z. B. ein fokussierbarer, aber deaktivierter Button, kann der Benutzer den Drehregler nicht verwenden, um darauf zu fokussieren. Wenn Sie sich auf deaktivierte Ansichten konzentrieren möchten, ziehen Sie die Verwendung eines benutzerdefinierten Status anstelle von android:state_enabled in Betracht, um zu steuern, wie die Ansicht angezeigt wird, ohne anzugeben, dass Android sie als deaktiviert betrachten soll. Ihre App kann den Benutzer darüber informieren, warum die Ansicht beim Antippen deaktiviert wird. Wie das geht, wird im nächsten Abschnitt erklärt.

Benutzerdefinierter Zustand

So fügen Sie einen benutzerdefinierten Status hinzu:

  1. So fügen Sie Ihrer Ansicht ein benutzerdefiniertes Attribut hinzu. Um beispielsweise einen benutzerdefinierten Zustand CustomView zur Ansichtsklasse state_rotary_enabled hinzuzufügen, verwenden Sie:
    <declare-styleable name="CustomView">
        <attr name="state_rotary_enabled" format="boolean" />
    </declare-styleable>
    
  2. Um diesen Zustand zu verfolgen, fügen Sie Ihrer Ansicht eine Instanzvariable zusammen mit Zugriffsmethoden hinzu:
    private boolean mRotaryEnabled;
    public boolean getRotaryEnabled() { return mRotaryEnabled; }
    public void setRotaryEnabled(boolean rotaryEnabled) {
        mRotaryEnabled = rotaryEnabled;
    }
    
  3. Um den Wert Ihres Attributs zu lesen, wenn Ihre Ansicht erstellt wird:
    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomView);
    mRotaryEnabled = a.getBoolean(R.styleable.CustomView_state_rotary_enabled);
    
  4. Überschreiben Sie in Ihrer Ansichtsklasse die Methode onCreateDrawableState() und fügen Sie dann gegebenenfalls den benutzerdefinierten Zustand hinzu. Zum Beispiel:
    @Override
    protected int[] onCreateDrawableState(int extraSpace) {
        if (mRotaryEnabled) extraSpace++;
        int[] drawableState = super.onCreateDrawableState(extraSpace);
        if (mRotaryEnabled) {
            mergeDrawableStates(drawableState, { R.attr.state_rotary_enabled });
        }
        return drawableState;
    }
    
  5. Lassen Sie den Klick-Handler Ihrer Ansicht je nach Zustand unterschiedlich funktionieren. Beispielsweise kann der Click-Handler nichts tun oder einen Toast anzeigen, wenn mRotaryEnabled false ist.
  6. Um die Schaltfläche deaktiviert erscheinen zu lassen, verwenden Sie im Hintergrund Ihrer Ansicht app:state_rotary_enabled anstelle von android:state_enabled . Wenn Sie es noch nicht haben, müssen Sie Folgendes hinzufügen:
    xmlns:app="http://schemas.android.com/apk/res-auto"
    
  7. Wenn Ihre Ansicht in einem beliebigen Layout deaktiviert ist, ersetzen Sie android:enabled="false" durch app:state_rotary_enabled="false" und fügen Sie dann den app -Namespace wie oben hinzu.
  8. Wenn Ihre Ansicht programmgesteuert deaktiviert ist, ersetzen Sie Aufrufe von setEnabled() durch Aufrufe von setRotaryEnabled() .

Fokusbereich

Verwenden Sie FocusAreas , um die fokussierbaren Ansichten in Blöcke zu unterteilen, um die Navigation zu vereinfachen und mit anderen Apps konsistent zu sein. Wenn Ihre App beispielsweise über eine Symbolleiste verfügt, sollte sich die Symbolleiste in einem separaten FocusArea vom Rest Ihrer App befinden. Auch Tab-Bars und andere Navigationselemente sollten vom Rest der App getrennt sein. Große Listen sollten generell eine eigene FocusArea haben. Ist dies nicht der Fall, müssen Benutzer die gesamte Liste durchlaufen, um auf einige Ansichten zugreifen zu können.

FocusArea ist eine Unterklasse von LinearLayout in der car-ui-library. Wenn diese Funktion aktiviert ist, zeichnet eine FocusArea eine Hervorhebung, wenn einer ihrer Nachkommen fokussiert ist. Weitere Informationen finden Sie unter Anpassen der Fokushervorhebung .

Wenn Sie beim Erstellen eines Navigationsblocks in der Layoutdatei beabsichtigen, ein LinearLayout als Container für diesen Block zu verwenden, verwenden Sie stattdessen eine FocusArea . Wickeln Sie andernfalls den Block in eine FocusArea ein.

Verschachteln Sie eine FocusArea NICHT in einer anderen FocusArea . Dies führt zu einem undefinierten Navigationsverhalten. Stellen Sie sicher, dass alle fokussierbaren Ansichten in einer FocusArea verschachtelt sind.

Ein Beispiel für eine FocusArea in RotaryPlayground ist unten dargestellt:

<com.android.car.ui.FocusArea
       android:layout_margin="16dp"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:orientation="vertical">
       <EditText
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:singleLine="true">
       </EditText>
   </com.android.car.ui.FocusArea>

FocusArea funktioniert wie folgt:

  1. Bei der Handhabung von Rotations- und Nudge-Aktionen sucht RotaryService nach Instanzen von FocusArea in der Ansichtshierarchie.
  2. Beim Empfang eines Rotationsereignisses verschiebt RotaryService den Fokus auf eine andere Ansicht, die den Fokus in derselben FocusArea übernehmen kann.
  3. Beim Empfang eines Nudge-Ereignisses verschiebt RotaryService den Fokus auf eine andere Ansicht, die den Fokus in einer anderen (normalerweise angrenzenden) FocusArea kann.

Wenn Sie keine FocusAreas in Ihr Layout aufnehmen, wird die Stammansicht als impliziter Fokusbereich behandelt. Der Benutzer kann nicht anstupsen, um in der App zu navigieren. Stattdessen rotieren sie durch alle fokussierbaren Ansichten, was für Dialoge ausreichend sein kann.

FocusArea-Anpassung

Zwei standardmäßige Ansichtsattribute können verwendet werden, um die Rotationsnavigation anzupassen:

  • android:nextFocusForward ermöglicht es App-Entwicklern, die Rotationsreihenfolge in einem Fokusbereich festzulegen. Dies ist dasselbe Attribut, das zum Steuern der Tab-Reihenfolge für die Tastaturnavigation verwendet wird. Verwenden Sie dieses Attribut NICHT , um eine Schleife zu erstellen. Verwenden Sie stattdessen app:wrapAround (siehe unten), um eine Schleife zu erstellen.
  • android:focusedByDefault ermöglicht es App-Entwicklern, die Standard-Fokusansicht im Fenster festzulegen. Verwenden Sie dieses Attribut und app:defaultFocus (siehe unten) NICHT in derselben FocusArea .

FocusArea definiert auch einige Attribute zum Anpassen der Rotationsnavigation. Implizite Fokusbereiche können mit diesen Attributen nicht angepasst werden.

  1. ( Android 11 QPR3, Android 11 Auto, Android 12 )
    app:defaultFocus kann verwendet werden, um die ID einer fokussierbaren untergeordneten Ansicht anzugeben, auf die fokussiert werden soll, wenn der Benutzer zu dieser FocusArea .
  2. ( Android 11 QPR3, Android 11 Auto, Android 12 )
    app:defaultFocusOverridesHistory kann auf „ true “ gesetzt werden, damit die oben angegebene Ansicht den Fokus erhält, selbst wenn mit Verlauf angegeben wird, dass eine andere Ansicht in diesem FocusArea fokussiert wurde.
  3. ( Android 12 )
    Verwenden Sie app:nudgeLeftShortcut , app:nudgeRightShortcut , app:nudgeUpShortcut und app:nudgeDownShortcut , um die ID einer fokussierbaren untergeordneten Ansicht anzugeben, auf die fokussiert werden soll, wenn der Benutzer in eine bestimmte Richtung stupst. Um mehr zu erfahren, sehen Sie sich den Inhalt für Nudge-Shortcuts unten an.

    ( Android 11 QPR3, Android 11 Car, veraltet in Android 12 ) app:nudgeShortcut und app:nudgeShortcutDirection unterstützten nur eine Nudge-Verknüpfung.

  4. ( Android 11 QPR3, Android 11 Auto, Android 12 )
    Damit die Rotation in dieser FocusArea umlaufen kann, kann app:wrapAround auf true gesetzt werden. Dies wird am häufigsten verwendet, wenn Ansichten kreisförmig oder oval angeordnet sind.
  5. ( Android 11 QPR3, Android 11 Auto, Android 12 )
    Verwenden Sie app:highlightPaddingStart , app:highlightPaddingEnd , app:highlightPaddingTop , app:highlightPaddingBottom , app:highlightPaddingHorizontal und app:highlightPaddingVertical , um die Auffüllung der Hervorhebung in diesem FocusArea anzupassen.
  6. ( Android 11 QPR3, Android 11 Auto, Android 12 )
    Verwenden Sie app:startBoundOffset , app:endBoundOffset , app:topBoundOffset , app:bottomBoundOffset , app:horizontalBoundOffset und app:verticalBoundOffset , um die wahrgenommenen Grenzen dieser FocusArea , um ein Nudge-Ziel zu finden.
  7. ( Android 11 QPR3, Android 11 Auto, Android 12 )
    Verwenden app:nudgeLeft , app:nudgeRight , app:nudgeUp und app:nudgeDown , um die ID einer angrenzenden FocusArea (oder -bereiche) in den angegebenen Richtungen explizit anzugeben. Verwenden Sie dies, wenn die standardmäßig verwendete geometrische Suche das gewünschte Ziel nicht findet.

Nudging navigiert normalerweise zwischen FocusAreas. Bei Nudge-Shortcuts navigiert das Nudgen jedoch manchmal zuerst innerhalb einer FocusArea , sodass der Benutzer möglicherweise zweimal stupsen muss, um zur nächsten FocusArea zu navigieren. Nudge-Shortcuts sind nützlich, wenn ein FocusArea eine lange Liste enthält, gefolgt von einer schwebenden Aktionsschaltfläche , wie im folgenden Beispiel:

Nudge-Verknüpfung
Abbildung 3. Nudge-Shortcut

Ohne die Nudge-Verknüpfung müsste der Benutzer durch die gesamte Liste rotieren, um den FAB zu erreichen.

Fokus-Highlight-Anpassung

Wie oben erwähnt, baut RotaryService auf dem bestehenden Konzept des Ansichtsfokus des Android-Frameworks auf. Wenn der Benutzer dreht und stupst, verschiebt RotaryService den Fokus, fokussiert eine Ansicht und defokussiert eine andere. In Android, wenn eine Ansicht fokussiert ist, wenn die Ansicht:

  • ein eigenes Fokus-Highlight festgelegt hat, zeichnet Android das Fokus-Highlight der Ansicht.
  • keine Fokushervorhebung angibt und die standardmäßige Fokushervorhebung nicht deaktiviert ist, zeichnet Android die standardmäßige Fokushervorhebung für die Ansicht.

Apps, die für die Berührung entwickelt wurden, geben normalerweise nicht die entsprechenden Fokus-Highlights an.

Die standardmäßige Fokushervorhebung wird vom Android-Framework bereitgestellt und kann vom OEM überschrieben werden. App-Entwickler erhalten es, wenn das von ihnen verwendete Design von Theme.DeviceDefault abgeleitet ist.

Verlassen Sie sich für eine konsistente Benutzererfahrung nach Möglichkeit auf die standardmäßige Fokushervorhebung. Wenn Sie eine benutzerdefinierte (z. B. runde oder pillenförmige) Fokushervorhebung benötigen oder ein Design verwenden, das nicht von Theme.DeviceDefault abgeleitet ist, verwenden Sie die Ressourcen der car-ui-library, um Ihre eigene Fokushervorhebung anzugeben jede Ansicht.

Um eine benutzerdefinierte Fokushervorhebung für eine Ansicht anzugeben, ändern Sie das Drawable im Hintergrund oder Vordergrund der Ansicht in ein Drawable, das sich unterscheidet, wenn die Ansicht fokussiert ist. Normalerweise würden Sie den Hintergrund ändern. Das folgende Drawable erzeugt, wenn es als Hintergrund für eine quadratische Ansicht verwendet wird, ein rundes Fokus-Highlight:

<selector xmlns:android="http://schemas.android.com/apk/res/android">
   <item android:state_focused="true" android:state_pressed="true">
      <shape android:shape="oval">
         <solid android:color="@color/car_ui_rotary_focus_pressed_fill_color"/>
         <stroke
            android:width="@dimen/car_ui_rotary_focus_pressed_stroke_width"
            android:color="@color/car_ui_rotary_focus_pressed_stroke_color"/>
      </shape>
   </item>
   <item android:state_focused="true">
      <shape android:shape="oval">
         <solid android:color="@color/car_ui_rotary_focus_fill_color"/>
         <stroke
            android:width="@dimen/car_ui_rotary_focus_stroke_width"
            android:color="@color/car_ui_rotary_focus_stroke_color"/>
      </shape>
   </item>
   <item>
      <ripple...>
         ...
      </ripple>
   </item>
</selector>

( Android 11 QPR3, Android 11 Car, Android 12 ) Fettgedruckte Ressourcenreferenzen im obigen Beispiel geben Ressourcen an, die von der car-ui-library definiert wurden. Der OEM überschreibt diese, um mit der von ihm angegebenen Standard-Fokushervorhebung konsistent zu sein. Dadurch wird sichergestellt, dass sich die Farbe der Fokushervorhebung, die Strichbreite usw. nicht ändern, wenn der Benutzer zwischen einer Ansicht mit einer benutzerdefinierten Fokushervorhebung und einer Ansicht mit der standardmäßigen Fokushervorhebung navigiert. Das letzte Element ist eine Welligkeit, die für die Berührung verwendet wird. Die für die fettgedruckten Ressourcen verwendeten Standardwerte werden wie folgt angezeigt:

Standardwerte für fettgedruckte Ressourcen
Abbildung 4. Standardwerte für fettgedruckte Ressourcen

Darüber hinaus ist eine benutzerdefinierte Fokushervorhebung erforderlich, wenn eine Schaltfläche eine durchgehende Hintergrundfarbe erhält, um die Aufmerksamkeit des Benutzers darauf zu lenken, wie im folgenden Beispiel. Dadurch kann das Fokus-Highlight schwer zu erkennen sein. Geben Sie in dieser Situation eine benutzerdefinierte Fokushervorhebung mit Sekundärfarben an:

Solide Hintergrundfarbe
  • ( Android 11 QPR3, Android 11 Auto, Android 12 )
    car_ui_rotary_focus_fill_secondary_color
    car_ui_rotary_focus_stroke_secondary_color
  • ( Android 12 )
    car_ui_rotary_focus_pressed_fill_secondary_color
    car_ui_rotary_focus_pressed_stroke_secondary_color

Zum Beispiel:

Fokussiert, nicht gedrücktKonzentriert, gedrückt
Fokussiert, nicht gedrückt Konzentriert, gedrückt

Rotierendes Scrollen

Wenn Ihre App RecyclerView s verwendet, SOLLTEN Sie stattdessen CarUiRecyclerView s verwenden. Dadurch wird sichergestellt, dass Ihre Benutzeroberfläche mit anderen konsistent ist, da die Anpassung eines OEMs für alle CarUiRecyclerView s gilt.

Wenn die Elemente in Ihrer Liste alle fokussierbar sind, brauchen Sie nichts weiter zu tun. Die Drehnavigation bewegt den Fokus durch die Elemente in der Liste und die Liste scrollt, um das neu fokussierte Element sichtbar zu machen.

( Android 11 QPR3, Android 11 Auto, Android 12 )
Wenn es eine Mischung aus fokussierbaren und nicht fokussierbaren Elementen gibt oder wenn alle Elemente nicht fokussierbar sind, können Sie das Dreh-Scrollen aktivieren, wodurch der Benutzer den Drehregler verwenden kann, um schrittweise durch die Liste zu blättern, ohne nicht fokussierbare Elemente zu überspringen. Um das rotierende Scrollen zu aktivieren, setzen Sie das Attribut app:rotaryScrollEnabled auf true .

( Android 11 QPR3, Android 11 Auto, Android 12 )
Mit der Methode setRotaryScrollEnabled() in CarUiUtils können Sie das rotierende Scrollen in jeder scrollbaren Ansicht, einschließlich av CarUiRecyclerView , aktivieren. Wenn Sie dies tun, müssen Sie:

  • Machen Sie die scrollbare Ansicht fokussierbar, sodass sie fokussiert werden kann, wenn keine ihrer fokussierbaren untergeordneten Ansichten sichtbar sind,
  • Deaktivieren Sie die standardmäßige Fokushervorhebung in der scrollbaren Ansicht, indem setDefaultFocusHighlightEnabled(false) aufrufen, sodass die scrollbare Ansicht nicht fokussiert zu sein scheint.
  • Stellen Sie sicher, dass die scrollbare Ansicht vor ihren Nachkommen fokussiert wird, indem setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS) .
  • Hören Sie mit SOURCE_ROTARY_ENCODER und entweder AXIS_VSCROLL oder AXIS_HSCROLL auf MotionEvents, um die Entfernung zum Scrollen und die Richtung (durch das Zeichen) anzugeben.

Wenn das rotierende Scrollen auf einem CarUiRecyclerView ist und der Benutzer zu einem Bereich rotiert, in dem keine fokussierbaren Ansichten vorhanden sind, wechselt die Bildlaufleiste von grau zu blau, als ob die Bildlaufleiste fokussiert ist. Sie können einen ähnlichen Effekt implementieren, wenn Sie möchten.

Die MotionEvents sind mit Ausnahme der Quelle die gleichen wie die, die von einem Mausrad erzeugt werden.

Direkter Manipulationsmodus

Normalerweise navigieren Stupser und Rotation durch die Benutzeroberfläche, während das Drücken der mittleren Taste eine Aktion ausführt, obwohl dies nicht immer der Fall ist. Wenn ein Benutzer beispielsweise die Alarmlautstärke anpassen möchte, kann er mit dem Drehregler zum Lautstärkeregler navigieren, die Mitteltaste drücken, den Regler drehen, um die Alarmlautstärke anzupassen, und dann die Zurück-Taste drücken, um zur Navigation zurückzukehren . Dies wird als direkter Manipulationsmodus (DM) bezeichnet. In diesem Modus wird der Drehregler verwendet, um direkt mit der Ansicht zu interagieren, anstatt zu navigieren.

Implementieren Sie DM auf eine von zwei Arten. Wenn Sie nur die Drehung handhaben müssen und die Ansicht, die Sie bearbeiten möchten, auf ACTION_SCROLL_FORWARD und ACTION_SCROLL_BACKWARD AccessibilityEvent angemessen reagiert, verwenden Sie den einfachen Mechanismus. Verwenden Sie andernfalls den erweiterten Mechanismus.

Der einfache Mechanismus ist die einzige Option in Systemfenstern; Apps können beide Mechanismen verwenden.

Einfacher Mechanismus

( Android 11 QPR3, Android 11 Auto, Android 12 )
Ihre App sollte DirectManipulationHelper.setSupportsRotateDirectly(View view, boolean enable) aufrufen. RotaryService erkennt, wenn sich der Benutzer im DM-Modus befindet, und wechselt in den DM-Modus, wenn der Benutzer die Mitteltaste drückt, während eine Ansicht fokussiert ist. Im DM-Modus führen Drehungen ACTION_SCROLL_FORWARD oder ACTION_SCROLL_BACKWARD aus und beenden den DM-Modus, wenn der Benutzer die Zurück-Taste drückt. Der einfache Mechanismus schaltet den ausgewählten Zustand der Ansicht um, wenn der DM-Modus aufgerufen und verlassen wird.

Um einen visuellen Hinweis darauf zu geben, dass sich der Benutzer im DM-Modus befindet, lassen Sie Ihre Ansicht bei Auswahl anders aussehen. Ändern Sie beispielsweise den Hintergrund, wenn android:state_selected true ist.

Fortschrittlicher Mechanismus

Die App bestimmt, wann RotaryService in den DM-Modus wechselt und ihn verlässt. Für eine konsistente Benutzererfahrung sollte das Drücken der mittleren Taste bei fokussierter DM-Ansicht in den DM-Modus wechseln und die Zurück-Taste sollte den DM-Modus verlassen. Wenn die Center-Taste und/oder der Schubser nicht verwendet werden, können sie alternative Möglichkeiten zum Verlassen des DM-Modus sein. Für Apps wie Maps kann eine Schaltfläche zur Darstellung von DM verwendet werden, um in den DM-Modus zu wechseln.

Um den erweiterten DM-Modus zu unterstützen, eine Ansicht:

  1. ( Android 11 QPR3, Android 11 Car, Android 12 ) MUSS auf ein KEYCODE_DPAD_CENTER Ereignis warten, um in den DM-Modus zu wechseln, und auf ein KEYCODE_BACK Ereignis warten, um den DM-Modus zu verlassen, wobei in jedem Fall DirectManipulationHelper.enableDirectManipulationMode() wird. Führen Sie einen der folgenden Schritte aus, um auf diese Ereignisse zu lauschen:
    • Registrieren Sie einen OnKeyListener .
    • oder,
    • Erweitern Sie die Ansicht und überschreiben Sie dann ihre Methode dispatchKeyEvent() .
  2. SOLLTE auf Nudge-Ereignisse ( KEYCODE_DPAD_UP , KEYCODE_DPAD_DOWN , KEYCODE_DPAD_LEFT oder KEYCODE_DPAD_RIGHT ) warten, wenn die Ansicht Nudges verarbeiten soll.
  3. SOLLTE auf MotionEvent s hören und die Anzahl der Drehungen in AXIS_SCROLL , wenn die Ansicht die Drehung verarbeiten möchte. Dazu gibt es mehrere Möglichkeiten:
    1. Registrieren Sie einen OnGenericMotionListener .
    2. Erweitern Sie die Ansicht und überschreiben Sie die Methode dispatchTouchEvent() .
  4. Um zu vermeiden, im DM-Modus hängen zu bleiben, MUSS der DM-Modus verlassen werden, wenn das Fragment oder die Aktivität, zu der die Ansicht gehört, nicht interaktiv ist.
  5. SOLLTE einen visuellen Hinweis darauf geben, dass sich die Ansicht im DM-Modus befindet.

Nachfolgend finden Sie ein Beispiel einer benutzerdefinierten Ansicht, die den DM-Modus zum Schwenken und Zoomen einer Karte verwendet:

/** Whether this view is in DM mode. */
private boolean mInDirectManipulationMode;

/** Initializes the view. Called by the constructors. */ private void init() { setOnKeyListener((view, keyCode, keyEvent) -> { boolean isActionUp = keyEvent.getAction() == KeyEvent.ACTION_UP; switch (keyCode) { // Always consume KEYCODE_DPAD_CENTER and KEYCODE_BACK events. case KeyEvent.KEYCODE_DPAD_CENTER: if (!mInDirectManipulationMode && isActionUp) { mInDirectManipulationMode = true; DirectManipulationHelper.enableDirectManipulationMode(this, true); setSelected(true); // visually indicate DM mode } return true; case KeyEvent.KEYCODE_BACK: if (mInDirectManipulationMode && isActionUp) { mInDirectManipulationMode = false; DirectManipulationHelper.enableDirectManipulationMode(this, false); setSelected(false); } return true; // Consume controller nudge events only when in DM mode. // When in DM mode, nudges pan the map. case KeyEvent.KEYCODE_DPAD_UP: if (!mInDirectManipulationMode) return false; if (isActionUp) pan(0f, -10f); return true; case KeyEvent.KEYCODE_DPAD_DOWN: if (!mInDirectManipulationMode) return false; if (isActionUp) pan(0f, 10f); return true; case KeyEvent.KEYCODE_DPAD_LEFT: if (!mInDirectManipulationMode) return false; if (isActionUp) pan(-10f, 0f); return true; case KeyEvent.KEYCODE_DPAD_RIGHT: if (!mInDirectManipulationMode) return false; if (isActionUp) pan(10f, 0f); return true; // Don't consume other key events. default: return false; } });
// When in DM mode, rotation zooms the map. setOnGenericMotionListener(((view, motionEvent) -> { if (!mInDirectManipulationMode) return false; float scroll = motionEvent.getAxisValue(MotionEvent.AXIS_SCROLL); zoom(10 * scroll); return true; })); }
@Override public void onPause() { if (mInDirectManipulationMode) { // To ensure that the user doesn't get stuck in DM mode, disable DM mode // when the fragment is not interactive (e.g., a dialog shows up). mInDirectManipulationMode = false; DirectManipulationHelper.enableDirectManipulationMode(this, false); } super.onPause(); }

Weitere Beispiele finden Sie im Projekt RotaryPlayground .

ActivityView

Bei Verwendung einer ActivityView:

  • Die ActivityView sollte nicht fokussierbar sein.
  • ( Android 11 QPR3, Android 11 Auto, veraltet in Android 11 )
    Der Inhalt der ActivityView MUSS eine FocusParkingView als erste fokussierbare Ansicht enthalten, und ihr app:shouldRestoreFocus -Attribut MUSS false sein.
  • Der Inhalt der ActivityView sollte keine android:focusByDefault Ansichten haben.

Für den Benutzer sollten ActivityViews keine Auswirkungen auf die Navigation haben, außer dass Fokusbereiche ActivityViews nicht umfassen können. Mit anderen Worten, Sie können keinen einzelnen Fokusbereich haben, der Inhalte innerhalb und außerhalb einer ActivityView enthält. Wenn Sie Ihrer ActivityView keine FocusAreas hinzufügen, wird der Stamm der Ansichtshierarchie in der ActivityView als impliziter Fokusbereich betrachtet.

Tasten, die funktionieren, wenn sie gedrückt gehalten werden

Die meisten Schaltflächen lösen eine Aktion aus, wenn sie angeklickt werden. Einige Tasten funktionieren stattdessen, wenn sie gedrückt gehalten werden. Beispielsweise funktionieren die Schnellvorlauf- und Rücklauftasten normalerweise, wenn sie gedrückt gehalten werden. Damit solche Tasten Rotation unterstützen, hören Sie wie folgt auf KEYCODE_DPAD_CENTER KeyEvents :

mButton.setOnKeyListener((v, keyCode, event) ->
{
    if (keyCode != KEYCODE_DPAD_CENTER) {
        return false;
    }
    if (event.getAction() == ACTION_DOWN) {
        mButton.setPressed(true);
        mHandler.post(mRunnable);
    } else {
        mButton.setPressed(false);
        mHandler.removeCallbacks(mRunnable);
    }
    return true;
});

Dabei ergreift mRunnable eine Aktion (z. B. Zurückspulen) und plant, dass es nach einer Verzögerung ausgeführt wird.

Touch-Modus

Benutzer können einen Drehregler verwenden, um mit der Haupteinheit in einem Auto auf zwei Arten zu interagieren, entweder durch Verwenden des Drehreglers oder durch Berühren des Bildschirms. Bei Verwendung des Drehreglers wird eine der fokussierbaren Ansichten hervorgehoben. Beim Berühren des Bildschirms wird keine Fokusmarkierung angezeigt. Der Benutzer kann jederzeit zwischen diesen Eingabemodi wechseln:

  • Drehen → berühren. Wenn der Benutzer den Bildschirm berührt, verschwindet die Fokushervorhebung.
  • Berühren Sie → rotierend. Wenn der Benutzer die mittlere Taste anstupst, dreht oder drückt, wird die Fokushervorhebung angezeigt.

Die Zurück- und Home-Tasten haben keine Auswirkung auf den Eingabemodus.

Rotary Piggybacks auf Androids bestehendes Konzept des Touch-Modus . Sie können View.isInTouchMode() verwenden, um festzustellen, welchen Eingabemodus der Benutzer verwendet. Sie können OnTouchModeChangeListener verwenden, um auf Änderungen zu warten. Während dies verwendet werden kann, um Ihre Benutzeroberfläche für den aktuellen Eingabemodus anzupassen, vermeiden Sie größere Änderungen, da sie beunruhigend sein können.

Fehlerbehebung

In einer App, die für die Berührung entwickelt wurde, ist es nicht ungewöhnlich, verschachtelte fokussierbare Ansichten zu haben. Beispielsweise kann ein FrameLayout um einen ImageButton herum vorhanden sein, die beide fokussierbar sind. Dies schadet der Berührung nicht, kann jedoch zu einer schlechten Benutzererfahrung beim Drehen führen, da der Benutzer den Controller zweimal drehen muss, um zur nächsten interaktiven Ansicht zu wechseln. Für ein gutes Nutzererlebnis empfiehlt Google, entweder die Außenansicht oder die Innenansicht fokussierbar zu machen, aber nicht beides.

Wenn eine Taste oder ein Schalter den Fokus verliert, wenn sie über den Drehregler gedrückt wird, kann eine der folgenden Bedingungen zutreffen:

  • Die Taste oder der Schalter wird (kurz oder dauerhaft) deaktiviert, weil die Taste gedrückt wird. In beiden Fällen gibt es zwei Möglichkeiten, dies zu beheben:
    • Belassen Sie den Status android:enabled auf true und verwenden Sie einen benutzerdefinierten Status, um die Schaltfläche oder den Schalter auszugrauen, wie unter Benutzerdefinierter Status beschrieben.
    • Verwenden Sie einen Container, um die Schaltfläche oder den Schalter zu umgeben, und machen Sie den Container anstelle der Schaltfläche oder des Schalters fokussierbar. (Der Klick-Listener muss sich im Container befinden.)
  • Der Knopf oder Schalter wird ersetzt. Beispielsweise kann die Aktion, die ausgeführt wird, wenn die Schaltfläche gedrückt oder der Schalter umgeschaltet wird, eine Aktualisierung der verfügbaren Aktionen auslösen, was dazu führt, dass neue Schaltflächen vorhandene Schaltflächen ersetzen. Es gibt zwei Möglichkeiten, dies zu beheben:
    • Anstatt eine neue Schaltfläche oder einen neuen Schalter zu erstellen, legen Sie das Symbol und/oder den Text der vorhandenen Schaltfläche oder des Schalters fest.
    • Fügen Sie wie oben einen fokussierbaren Container um die Schaltfläche oder den Schalter hinzu.

RotaryPlayground

RotaryPlayground ist eine Referenz-App für Rotary. Verwenden Sie es, um zu erfahren, wie Sie Rotationsfunktionen in Ihre Apps integrieren. RotaryPlayground ist in Emulator-Builds und in Builds für Geräte enthalten, auf denen Android Automotive OS (AAOS) ausgeführt wird.

  • RotaryPlayground Repository: packages/apps/Car/tests/RotaryPlayground/
  • Versionen: Android 11 QPR3, Android 11 Auto und Android 12

Die RotaryPlayground App zeigt auf der linken Seite die folgenden Registerkarten:

  • Karten. Testen Sie das Navigieren in Fokusbereichen, das Überspringen nicht fokussierbarer Elemente und die Texteingabe.
  • Direkte Manipulation. Testen Sie Widgets, die den einfachen und erweiterten direkten Manipulationsmodus unterstützen. Diese Registerkarte ist speziell für die direkte Bearbeitung innerhalb des App-Fensters vorgesehen.
  • Sys-UI-Manipulation. Testen Sie Widgets, die die direkte Manipulation in Systemfenstern unterstützen, in denen nur der einfache direkte Manipulationsmodus unterstützt wird.
  • Netz. Testen Sie die Z-Muster-Rotationsnavigation mit Scrollen.
  • Benachrichtigung. Testen Sie das Ein- und Ausblenden von Heads-up-Benachrichtigungen.
  • Scrollen. Testen Sie das Scrollen durch eine Mischung aus fokussierbaren und nicht fokussierbaren Inhalten.
  • WebView. Testen Sie das Navigieren durch Links in einer WebView .
  • Benutzerdefinierter FocusArea . Testen Sie die FocusArea Anpassung:
    • Rundum.
    • android:focusedByDefault und app:defaultFocus
    • .
    • Explizite Nudge-Ziele.
    • Nudge-Shortcuts.
    • FocusArea ohne fokussierbare Ansichten.
,

Das folgende Material ist für App-Entwickler.

Damit Ihre App Rotary unterstützt, MÜSSEN Sie:

  1. Platzieren Sie eine FocusParkingView im jeweiligen Aktivitätslayout.
  2. Stellen Sie sicher, dass die Ansichten fokussierbar (oder nicht) sind.
  3. Verwenden Sie FocusArea s , um alle Ihre fokussierbaren Ansichten mit Ausnahme von FocusParkingView zu umschließen.

Jede dieser Aufgaben wird unten detailliert beschrieben, nachdem Sie Ihre Umgebung eingerichtet haben, um rotationsfähige Apps zu entwickeln.

Richten Sie einen Drehregler ein

Bevor Sie mit der Entwicklung drehfähiger Apps beginnen können, benötigen Sie entweder einen Drehregler oder einen Stellvertreter. Sie haben die unten beschriebenen Möglichkeiten.

Emulator

source build/envsetup.sh && lunch car_x86_64-userdebug
m -j
emulator -wipe-data -no-snapshot -writable-system

Sie können auch aosp_car_x86_64-userdebug .

So greifen Sie auf den emulierten Drehregler zu:

  1. Tippen Sie auf die drei Punkte unten in der Symbolleiste:

    Greifen Sie auf den emulierten Drehregler zu
    Abbildung 1. Zugriff auf emulierten Drehregler
  2. Wählen Sie im Fenster „Erweiterte Steuerung“ die Option „ Auto-Rotation “ aus:

    Wählen Sie Autorotation
    Abbildung 2. Wählen Sie Car Rotary aus

USB-Tastatur

  • Schließen Sie eine USB-Tastatur an Ihr Gerät an, auf dem Android Automotive OS (AAOS) ausgeführt wird. In einigen Fällen kann dies verhindern, dass die Bildschirmtastatur angezeigt wird.
  • Verwenden Sie einen userdebug oder eng -Build.
  • Schlüsselereignisfilterung aktivieren:
    adb shell settings put secure android.car.ROTARY_KEY_EVENT_FILTER 1
    
  • In der folgenden Tabelle finden Sie die entsprechende Taste für jede Aktion:
    Taste Rotationsaktion
    Q Gegen den Uhrzeigersinn drehen
    E Im Uhrzeiger sinn drehen
    EIN Nach links schubsen
    D Richtig schubsen
    W Stups dich an
    S Stups nach unten
    F oder Komma Mittlere Taste
    R oder Esc Zurück-Button

ADB-Befehle

Sie können car_service Befehle verwenden, um Rotationseingangsereignisse einzufügen. Diese Befehle können auf Geräten mit Android Automotive OS (AAOS) oder auf einem Emulator ausgeführt werden.

car_service-Befehle Dreheingang
adb shell cmd car_service inject-rotary Gegen den Uhrzeigersinn drehen
adb shell cmd car_service inject-rotary -c true Im Uhrzeiger sinn drehen
adb shell cmd car_service inject-rotary -dt 100 50 Mehrmals gegen den Uhrzeigersinn drehen (vor 100 ms und vor 50 ms)
adb shell cmd car_service inject-key 282 Nach links schubsen
adb shell cmd car_service inject-key 283 Richtig schubsen
adb shell cmd car_service inject-key 280 Stups dich an
adb shell cmd car_service inject-key 281 Stups nach unten
adb shell cmd car_service inject-key 23 Klicken Sie auf die mittlere Schaltfläche
adb shell input keyevent inject-key 4 Klicken Sie auf die Zurück-Schaltfläche

OEM-Drehregler

Wenn Ihre Drehregler-Hardware betriebsbereit ist, ist dies die realistischste Option. Es ist besonders nützlich, um schnelle Rotation zu testen.

FocusParkingView

FocusParkingView ist eine transparente Ansicht in der Car UI Library (car-ui-library) . RotaryService verwendet es zur Unterstützung der Drehregler-Navigation. FocusParkingView muss die erste fokussierbare Ansicht im Layout sein. Es muss außerhalb aller FocusArea s platziert werden. Jedes Fenster muss über eine FocusParkingView . Wenn Sie bereits das Basislayout car-ui-library verwenden, das eine FocusParkingView enthält, müssen Sie keine weitere FocusParkingView hinzufügen. Unten sehen Sie ein Beispiel für FocusParkingView in RotaryPlayground .

<FrameLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="match_parent">
   <com.android.car.ui.FocusParkingView
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"/>
   <FrameLayout
       android:layout_width="match_parent"
       android:layout_height="match_parent"/>
</FrameLayout>

Hier sind die Gründe, warum Sie eine FocusParkingView benötigen:

  1. Android löscht den Fokus nicht automatisch, wenn der Fokus in einem anderen Fenster gesetzt wird. Wenn Sie versuchen, den Fokus im vorherigen Fenster zu löschen, fokussiert Android eine Ansicht in diesem Fenster neu, was dazu führt, dass zwei Fenster gleichzeitig fokussiert werden. Durch Hinzufügen einer FocusParkingView zu jedem Fenster kann dieses Problem behoben werden. Diese Ansicht ist transparent und ihre standardmäßige Fokushervorhebung ist deaktiviert, sodass sie für den Benutzer unsichtbar ist, unabhängig davon, ob sie fokussiert ist oder nicht. Es kann den Fokus übernehmen, damit RotaryService den Fokus darauf parken kann, um die Fokushervorhebung zu entfernen.
  2. Wenn im aktuellen Fenster nur eine FocusArea vorhanden ist, bewirkt das Drehen des Controllers in der FocusArea , dass RotaryService den Fokus von der Ansicht rechts auf die Ansicht links verschiebt (und umgekehrt). Das Hinzufügen dieser Ansicht zu jedem Fenster kann das Problem beheben. Wenn RotaryService feststellt, dass das Fokusziel ein FocusParkingView ist, kann es feststellen, dass ein Umlauf bevorsteht, und an diesem Punkt den Umlauf vermeiden, indem es den Fokus nicht bewegt.
  3. Wenn der Drehregler eine App startet, fokussiert Android die erste fokussierbare Ansicht, die immer die FocusParkingView ist. Die FocusParkingView bestimmt die optimale Ansicht, auf die fokussiert werden soll, und wendet dann den Fokus an.

Fokussierbare Ansichten

RotaryService baut auf dem bestehenden Konzept der Ansichtsfokussierung des Android-Frameworks auf, das auf die Zeit zurückgeht, als Telefone physische Tastaturen und D-Pads hatten. Das vorhandene android:nextFocusForward -Attribut wird für Rotation umfunktioniert (siehe FocusArea-Anpassung ), android:nextFocusLeft , android:nextFocusRight , android:nextFocusUp und android:nextFocusDown jedoch nicht.

RotaryService konzentriert sich nur auf Ansichten, die fokussierbar sind. Einige Ansichten, wie z. B. Button , sind normalerweise fokussierbar. Andere, wie z. B. TextView s und ViewGroup s, sind dies normalerweise nicht. Anklickbare Ansichten sind automatisch fokussierbar und Ansichten sind automatisch anklickbar, wenn sie über einen Klick-Listener verfügen. Wenn diese automatische Logik zur gewünschten Fokussierbarkeit führt, müssen Sie die Fokussierbarkeit der Ansicht nicht explizit festlegen. Wenn die automatische Logik nicht zur gewünschten Fokussierbarkeit führt, legen Sie das Attribut android:focusable auf true oder false fest oder legen Sie die Fokussierbarkeit der Ansicht programmgesteuert mit View.setFocusable(boolean) . Damit RotaryService sich darauf konzentrieren kann, MUSS eine Ansicht die folgenden Anforderungen erfüllen:

  • Fokussierbar
  • Ermöglicht
  • Sichtbar
  • Haben Sie Werte ungleich Null für Breite und Höhe

Wenn eine Ansicht nicht alle diese Anforderungen erfüllt, z. B. ein fokussierbarer, aber deaktivierter Button, kann der Benutzer den Drehregler nicht verwenden, um darauf zu fokussieren. Wenn Sie sich auf deaktivierte Ansichten konzentrieren möchten, ziehen Sie die Verwendung eines benutzerdefinierten Status anstelle von android:state_enabled in Betracht, um zu steuern, wie die Ansicht angezeigt wird, ohne anzugeben, dass Android sie als deaktiviert betrachten soll. Ihre App kann den Benutzer darüber informieren, warum die Ansicht beim Antippen deaktiviert wird. Wie das geht, wird im nächsten Abschnitt erklärt.

Benutzerdefinierter Zustand

So fügen Sie einen benutzerdefinierten Status hinzu:

  1. So fügen Sie Ihrer Ansicht ein benutzerdefiniertes Attribut hinzu. Um beispielsweise einen benutzerdefinierten Zustand CustomView zur Ansichtsklasse state_rotary_enabled hinzuzufügen, verwenden Sie:
    <declare-styleable name="CustomView">
        <attr name="state_rotary_enabled" format="boolean" />
    </declare-styleable>
    
  2. Um diesen Zustand zu verfolgen, fügen Sie Ihrer Ansicht eine Instanzvariable zusammen mit Zugriffsmethoden hinzu:
    private boolean mRotaryEnabled;
    public boolean getRotaryEnabled() { return mRotaryEnabled; }
    public void setRotaryEnabled(boolean rotaryEnabled) {
        mRotaryEnabled = rotaryEnabled;
    }
    
  3. Um den Wert Ihres Attributs zu lesen, wenn Ihre Ansicht erstellt wird:
    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomView);
    mRotaryEnabled = a.getBoolean(R.styleable.CustomView_state_rotary_enabled);
    
  4. Überschreiben Sie in Ihrer Ansichtsklasse die Methode onCreateDrawableState() und fügen Sie dann gegebenenfalls den benutzerdefinierten Zustand hinzu. Zum Beispiel:
    @Override
    protected int[] onCreateDrawableState(int extraSpace) {
        if (mRotaryEnabled) extraSpace++;
        int[] drawableState = super.onCreateDrawableState(extraSpace);
        if (mRotaryEnabled) {
            mergeDrawableStates(drawableState, { R.attr.state_rotary_enabled });
        }
        return drawableState;
    }
    
  5. Lassen Sie den Klick-Handler Ihrer Ansicht je nach Zustand unterschiedlich funktionieren. Beispielsweise kann der Click-Handler nichts tun oder einen Toast anzeigen, wenn mRotaryEnabled false ist.
  6. Um die Schaltfläche deaktiviert erscheinen zu lassen, verwenden Sie im Hintergrund Ihrer Ansicht app:state_rotary_enabled anstelle von android:state_enabled . Wenn Sie es noch nicht haben, müssen Sie Folgendes hinzufügen:
    xmlns:app="http://schemas.android.com/apk/res-auto"
    
  7. Wenn Ihre Ansicht in einem beliebigen Layout deaktiviert ist, ersetzen Sie android:enabled="false" durch app:state_rotary_enabled="false" und fügen Sie dann den app -Namespace wie oben hinzu.
  8. Wenn Ihre Ansicht programmgesteuert deaktiviert ist, ersetzen Sie Aufrufe von setEnabled() durch Aufrufe von setRotaryEnabled() .

Fokusbereich

Verwenden Sie FocusAreas , um die fokussierbaren Ansichten in Blöcke zu unterteilen, um die Navigation zu vereinfachen und mit anderen Apps konsistent zu sein. Wenn Ihre App beispielsweise über eine Symbolleiste verfügt, sollte sich die Symbolleiste in einem separaten FocusArea vom Rest Ihrer App befinden. Auch Tab-Bars und andere Navigationselemente sollten vom Rest der App getrennt sein. Große Listen sollten generell eine eigene FocusArea haben. Ist dies nicht der Fall, müssen Benutzer die gesamte Liste durchlaufen, um auf einige Ansichten zugreifen zu können.

FocusArea ist eine Unterklasse von LinearLayout in der car-ui-library. Wenn diese Funktion aktiviert ist, zeichnet eine FocusArea eine Hervorhebung, wenn einer ihrer Nachkommen fokussiert ist. Weitere Informationen finden Sie unter Anpassen der Fokushervorhebung .

Wenn Sie beim Erstellen eines Navigationsblocks in der Layoutdatei beabsichtigen, ein LinearLayout als Container für diesen Block zu verwenden, verwenden Sie stattdessen eine FocusArea . Wickeln Sie andernfalls den Block in eine FocusArea ein.

Verschachteln Sie eine FocusArea NICHT in einer anderen FocusArea . Dies führt zu einem undefinierten Navigationsverhalten. Stellen Sie sicher, dass alle fokussierbaren Ansichten in einer FocusArea verschachtelt sind.

Ein Beispiel für eine FocusArea in RotaryPlayground ist unten dargestellt:

<com.android.car.ui.FocusArea
       android:layout_margin="16dp"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:orientation="vertical">
       <EditText
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:singleLine="true">
       </EditText>
   </com.android.car.ui.FocusArea>

FocusArea funktioniert wie folgt:

  1. When handling rotate and nudge actions, RotaryService looks for instances of FocusArea in the view hierarchy.
  2. When receiving a rotation event, RotaryService moves focus to another View that can take focus in the same FocusArea .
  3. When receiving a nudge event, RotaryService move focus to another view that can take focus in another (typically adjacent) FocusArea .

If you don't include any FocusAreas in your layout, the root view is treated as an implicit focus area. The user can't nudge to navigate in the app. Instead, they'll rotate through all focusable views, which may be adequate for dialogs.

FocusArea customization

Two standard View attributes can be used to customize rotary navigation:

  • android:nextFocusForward allows app developers to specify the rotation order in a focus area. This is the same attribute used to control the Tab order for keyboard navigation. Do NOT use this attribute to create a loop. Instead, use app:wrapAround (see below) to create a loop.
  • android:focusedByDefault allows app developers to specify the default focus view in the window. Do NOT use this attribute and app:defaultFocus (see below) in the same FocusArea .

FocusArea also defines some attributes to customize rotary navigation. Implicit focus areas can't be customized with these attributes.

  1. ( Android 11 QPR3, Android 11 Car, Android 12 )
    app:defaultFocus can be used to specify the ID of a focusable descendant view, which should be focused on when the user nudges to this FocusArea .
  2. ( Android 11 QPR3, Android 11 Car, Android 12 )
    app:defaultFocusOverridesHistory can be set to true to make the view specified above take focus even if with history to indicate another view in this FocusArea had been focused on.
  3. ( Android 12 )
    Use app:nudgeLeftShortcut , app:nudgeRightShortcut , app:nudgeUpShortcut , and app:nudgeDownShortcut to specify the ID of a focusable descendant view, which should be focused on when the user nudges in a given direction. To learn more, see the content for nudge shortcuts below.

    ( Android 11 QPR3, Android 11 Car, deprecated in Android 12 ) app:nudgeShortcut and app:nudgeShortcutDirection supported only one nudge shortcut.

  4. ( Android 11 QPR3, Android 11 Car, Android 12 )
    To enable rotation to wrap around in this FocusArea , app:wrapAround can be set to true . This is most typically used when views are arranged in a circle or oval.
  5. ( Android 11 QPR3, Android 11 Car, Android 12 )
    To adjust the padding of the highlight in this FocusArea , use app:highlightPaddingStart , app:highlightPaddingEnd , app:highlightPaddingTop , app:highlightPaddingBottom , app:highlightPaddingHorizontal , and app:highlightPaddingVertical .
  6. ( Android 11 QPR3, Android 11 Car, Android 12 )
    To adjust the perceived bounds of this FocusArea to find a nudge target, use app:startBoundOffset , app:endBoundOffset , app:topBoundOffset , app:bottomBoundOffset , app:horizontalBoundOffset , and app:verticalBoundOffset .
  7. ( Android 11 QPR3, Android 11 Car, Android 12 )
    To explicitly specify the ID of an adjacent FocusArea (or areas) in the given directions, use app:nudgeLeft , app:nudgeRight , app:nudgeUp , and app:nudgeDown . Use this when the geometric search used by default doesn't find the desired target.

Nudging usually navigates between FocusAreas. But with nudge shortcuts, nudging sometimes first navigates within a FocusArea so that the user may need to nudge twice to navigate to the next FocusArea . Nudge shortcuts are useful when a FocusArea contains a long list followed by a Floating Action Button , as in the example below:

Nudge shortcut
Figure 3. Nudge shortcut

Without the nudge shortcut, the user would have to rotate through the entire list to reach the FAB.

Focus highlight customization

As noted above, RotaryService builds upon the Android framework's existing concept of view focus. When the user rotates and nudges, RotaryService moves the focus around, focusing one view and unfocusing another. In Android, when a view is focused, if the view:

  • has specified its own focus highlight, Android draws the view's focus highlight.
  • doesn't specify a focus highlight, and the default focus highlight is not disabled, Android draws the default focus highlight for the view.

Apps designed for touch usually don't specify the appropriate focus highlights.

The default focus highlight is provided by the Android framework and can be overridden by the OEM. App developers receive it when the theme they're using is derived from Theme.DeviceDefault .

For a consistent user experience, rely on the default focus highlight whenever possible. If you need a custom-shaped (for example, round or pill-shaped) focus highlight, or if you're using a theme not derived from Theme.DeviceDefault , use the car-ui-library resources to specify your own focus highlight for each view.

To specify a custom focus highlight for a view, change the background or foreground drawable of the view to a drawable that differs when the view is focused on. Typically, you'd change the background. The following drawable, if used as the background for a square view, produces a round focus highlight:

<selector xmlns:android="http://schemas.android.com/apk/res/android">
   <item android:state_focused="true" android:state_pressed="true">
      <shape android:shape="oval">
         <solid android:color="@color/car_ui_rotary_focus_pressed_fill_color"/>
         <stroke
            android:width="@dimen/car_ui_rotary_focus_pressed_stroke_width"
            android:color="@color/car_ui_rotary_focus_pressed_stroke_color"/>
      </shape>
   </item>
   <item android:state_focused="true">
      <shape android:shape="oval">
         <solid android:color="@color/car_ui_rotary_focus_fill_color"/>
         <stroke
            android:width="@dimen/car_ui_rotary_focus_stroke_width"
            android:color="@color/car_ui_rotary_focus_stroke_color"/>
      </shape>
   </item>
   <item>
      <ripple...>
         ...
      </ripple>
   </item>
</selector>

( Android 11 QPR3, Android 11 Car, Android 12 ) Bold resource references in the sample above identify resources defined by the car-ui-library. The OEM overrides these to be consistent with the default focus highlight they specify. This ensures that the focus highlight color, stroke width, and so on don't change when the user navigates between a view with a custom focus highlight and a view with the default focus highlight. The last item is a ripple used for touch. Default values used for the bold resources appear as follows:

Default values for bold resources
Figure 4. Default values for bold resources

In addition, a custom focus highlight is called for when a button is given a solid background color to bring it to the user's attention, as in the example below. This can make the focus highlight difficult to see. In this situation, specify a custom focus highlight using secondary colors:

Solid background color
  • ( Android 11 QPR3, Android 11 Car, Android 12 )
    car_ui_rotary_focus_fill_secondary_color
    car_ui_rotary_focus_stroke_secondary_color
  • ( Android 12 )
    car_ui_rotary_focus_pressed_fill_secondary_color
    car_ui_rotary_focus_pressed_stroke_secondary_color

Zum Beispiel:

Focused, not pressedFocused, pressed
Focused, not pressed Focused, pressed

Rotary scrolling

If your app uses RecyclerView s, you SHOULD use CarUiRecyclerView s instead. This ensures that your UI is consistent with others because an OEM's customization applies to all CarUiRecyclerView s.

If the elements in your list are all focusable, you needn't do anything else. Rotary navigation moves the focus through the elements in the list and the list scrolls to make the newly focused element visible.

( Android 11 QPR3, Android 11 Car, Android 12 )
If there is a mix of focusable and unfocusable elements, or if all the elements are unfocusable, you can enable rotary scrolling, which allows the user to use the rotary controller to gradually scroll through the list without skipping unfocusable items. To enable rotary scrolling, set the app:rotaryScrollEnabled attribute to true .

( Android 11 QPR3, Android 11 Car, Android 12 )
You can enable rotary scrolling in any scrollable view, including av CarUiRecyclerView , with the setRotaryScrollEnabled() method in CarUiUtils . If you do so, you need to:

  • Make the scrollable view focusable so that it can be focused on when none of its focusable descendant views are visible,
  • Disable the default focus highlight on the scrollable view by calling setDefaultFocusHighlightEnabled(false) so that the scrollable view doesn't appear to be focused,
  • Ensure that the scrollable view is focused on before its descendants by calling setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS) .
  • Listen for MotionEvents with SOURCE_ROTARY_ENCODER and either AXIS_VSCROLL or AXIS_HSCROLL to indicate the distance to scroll and the direction (through the sign).

When rotary scrolling is enabled on a CarUiRecyclerView and the user rotates to an area where no focusable views are present, the scrollbar changes from gray to blue, as if to indicate the scrollbar is focused. You can implement a similar effect if you like.

The MotionEvents are the same as those generated by a scroll wheel on a mouse, except for the source.

Direct manipulation mode

Normally, nudges and rotation navigate through the user interface, while Center button presses take action, though this isn't always the case. For example, if a user wants to adjust the alarm volume, they might use the rotary controller to navigate to the volume slider, press the Center button, rotate the controller to adjust the alarm volume, and then press the Back button to return to navigation. This is referred to as direct manipulation (DM) mode. In this mode, the rotary controller is used to interact with the view directly rather than to navigate.

Implement DM in one of two ways. If you only need to handle rotation and the view you want to manipulate responds to ACTION_SCROLL_FORWARD and ACTION_SCROLL_BACKWARD AccessibilityEvent s appropriately, use the simple mechanism. Otherwise, use the advanced mechanism.

The simple mechanism is the only option in system windows; apps can use either mechanism.

Simple mechanism

( Android 11 QPR3, Android 11 Car, Android 12 )
Your app should call DirectManipulationHelper.setSupportsRotateDirectly(View view, boolean enable) . RotaryService recognizes when the user is in DM mode and enters DM mode when the user presses the Center button while a view is focused. When in DM mode, rotations perform ACTION_SCROLL_FORWARD or ACTION_SCROLL_BACKWARD and exits DM mode when the user presses the Back button. The simple mechanism toggles the selected state of the view when entering and exiting DM mode.

To provide a visual cue that the user is in DM mode, make your view appear different when selected. For example, change the background when android:state_selected is true .

Advanced mechanism

The app determines when RotaryService enters and exits DM mode. For a consistent user experience, pressing the Center button with a DM view focused should enter DM mode and the Back button should exit DM mode. If the Center button and/or nudge aren't used, they can be alternative ways to exit DM mode. For apps such as Maps, a button to represent DM can be used to enter DM mode.

To support advanced DM mode, a view:

  1. ( Android 11 QPR3, Android 11 Car, Android 12 ) MUST listen for a KEYCODE_DPAD_CENTER event to enter DM mode and listen for a KEYCODE_BACK event to exit DM mode, calling DirectManipulationHelper.enableDirectManipulationMode() in each case. To listen for these events, do one of the following:
    • Register an OnKeyListener .
    • or,
    • Extend the view and then override its dispatchKeyEvent() method.
  2. SHOULD listen for nudge events ( KEYCODE_DPAD_UP , KEYCODE_DPAD_DOWN , KEYCODE_DPAD_LEFT , or KEYCODE_DPAD_RIGHT ) if the view should handle nudges.
  3. SHOULD listen to MotionEvent s and get rotation count in AXIS_SCROLL if the view wants to handle rotation. There are several ways to do this:
    1. Register an OnGenericMotionListener .
    2. Extend the view and override its dispatchTouchEvent() method.
  4. To avoid being stuck in DM mode, MUST exit DM mode when the Fragment or Activity the view belongs to is not interactive.
  5. SHOULD provide a visual cue to indicate that the view is in DM mode.

A sample of a custom view that uses DM mode to pan and zoom a map is provided below:

/** Whether this view is in DM mode. */
private boolean mInDirectManipulationMode;

/** Initializes the view. Called by the constructors. */ private void init() { setOnKeyListener((view, keyCode, keyEvent) -> { boolean isActionUp = keyEvent.getAction() == KeyEvent.ACTION_UP; switch (keyCode) { // Always consume KEYCODE_DPAD_CENTER and KEYCODE_BACK events. case KeyEvent.KEYCODE_DPAD_CENTER: if (!mInDirectManipulationMode && isActionUp) { mInDirectManipulationMode = true; DirectManipulationHelper.enableDirectManipulationMode(this, true); setSelected(true); // visually indicate DM mode } return true; case KeyEvent.KEYCODE_BACK: if (mInDirectManipulationMode && isActionUp) { mInDirectManipulationMode = false; DirectManipulationHelper.enableDirectManipulationMode(this, false); setSelected(false); } return true; // Consume controller nudge events only when in DM mode. // When in DM mode, nudges pan the map. case KeyEvent.KEYCODE_DPAD_UP: if (!mInDirectManipulationMode) return false; if (isActionUp) pan(0f, -10f); return true; case KeyEvent.KEYCODE_DPAD_DOWN: if (!mInDirectManipulationMode) return false; if (isActionUp) pan(0f, 10f); return true; case KeyEvent.KEYCODE_DPAD_LEFT: if (!mInDirectManipulationMode) return false; if (isActionUp) pan(-10f, 0f); return true; case KeyEvent.KEYCODE_DPAD_RIGHT: if (!mInDirectManipulationMode) return false; if (isActionUp) pan(10f, 0f); return true; // Don't consume other key events. default: return false; } });
// When in DM mode, rotation zooms the map. setOnGenericMotionListener(((view, motionEvent) -> { if (!mInDirectManipulationMode) return false; float scroll = motionEvent.getAxisValue(MotionEvent.AXIS_SCROLL); zoom(10 * scroll); return true; })); }
@Override public void onPause() { if (mInDirectManipulationMode) { // To ensure that the user doesn't get stuck in DM mode, disable DM mode // when the fragment is not interactive (e.g., a dialog shows up). mInDirectManipulationMode = false; DirectManipulationHelper.enableDirectManipulationMode(this, false); } super.onPause(); }

More examples can be found in the RotaryPlayground project.

ActivityView

When using an ActivityView:

  • The ActivityView should not be focusable.
  • ( Android 11 QPR3, Android 11 Car, deprecated in Android 11 )
    The contents of the ActivityView MUST contain a FocusParkingView as the first focusable view, and its app:shouldRestoreFocus attribute MUST be false .
  • The contents of the ActivityView should have no android:focusByDefault views.

For the user, ActivityViews should have no effect on navigation except that focus areas can't span ActivityViews. In other words, you can't have a single focus area that has content inside and outside an ActivityView . If you don't add any FocusAreas to your ActivityView , the root of the view hierarchy in the ActivityView is considered an implicit focus area.

Buttons that operate when held down

Most buttons cause some action when clicked. Some buttons operate when held down instead. For example, the Fast Forward and Rewind buttons typically operate when held down. To make such buttons support rotary, listen for KEYCODE_DPAD_CENTER KeyEvents as follows:

mButton.setOnKeyListener((v, keyCode, event) ->
{
    if (keyCode != KEYCODE_DPAD_CENTER) {
        return false;
    }
    if (event.getAction() == ACTION_DOWN) {
        mButton.setPressed(true);
        mHandler.post(mRunnable);
    } else {
        mButton.setPressed(false);
        mHandler.removeCallbacks(mRunnable);
    }
    return true;
});

In which mRunnable takes an action (such as rewinding) and schedules itself to be run after a delay.

Touch mode

Users can use a rotary controller to interact with the head unit in a car in two ways, either by using the rotary controller or by touching the screen. When using the rotary controller, one of the focusable views will be highlighted. When touching the screen, no focus highlight appears. The user can switch between these input modes at any time:

  • Rotary → touch. When the user touches the screen, the focus highlight disappears.
  • Touch → rotary. When the user nudges, rotates, or presses the Center button, the focus highlight appears.

The Back and Home buttons have no effect on the input mode.

Rotary piggybacks on Android's existing concept of touch mode . You can use View.isInTouchMode() to determine which input mode the user is using. You can use OnTouchModeChangeListener to listen for changes. While this can be used to customize your user interface for the current input mode, avoid any major changes as they can be disconcerting.

Troubleshooting

In an app designed for touch, it's not uncommon to have nested focusable views. For example, there may be a FrameLayout around an ImageButton , both of which are focusable. This does no harm for touch but it can result in a poor user experience for rotary because the user must rotate the controller twice to move to the next interactive view. For a good user experience, Google recommends you make either the outer view or the inner view focusable, but not both.

If a button or switch loses focus when pressed through the rotary controller, one of these conditions may apply:

  • The button or switch is being disabled (briefly or indefinitely) due to the button being pressed. In either case, there are two ways to address this:
    • Leave the android:enabled state as true and use a custom state to gray out the button or switch as described in Custom State .
    • Use a container to surround the button or switch and make the container focusable instead of the button or switch. (The click listener must be on the container.)
  • The button or switch is being replaced. For example, the action taken when the button is pressed or the switch is toggled may trigger a refresh of the available actions causing new buttons to replace existing buttons. There are two ways to address this:
    • Instead of creating a new button or switch, set the icon and/or text of the existing button or switch.
    • As above, add a focusable container around the button or switch.

RotaryPlayground

RotaryPlayground is a reference app for rotary. Use it to learn how to integrate rotary features into your apps. RotaryPlayground is included in emulator builds and in builds for devices that run Android Automotive OS (AAOS).

  • RotaryPlayground repository: packages/apps/Car/tests/RotaryPlayground/
  • Versions: Android 11 QPR3, Android 11 Car, and Android 12

The RotaryPlayground app shows the following tabs on the left:

  • Cards. Test navigating around focus areas, skipping unfocusable elements and text input.
  • Direct Manipulation. Test widgets that support simple and advanced direct manipulation mode. This tab is specifically for direct manipulation within the app window.
  • Sys UI Manipulation. Test widgets that support direct manipulation in system windows where only simple direct manipulation mode is supported.
  • Grid. Test z-pattern rotary navigation with scrolling.
  • Notification. Test nudging in and out of heads-up notifications.
  • Scroll. Test scrolling through a mix of focusable and unfocusable content.
  • WebView. Test navigating through links in a WebView .
  • Custom FocusArea . Test FocusArea customization:
    • Wrap-around.
    • android:focusedByDefault and app:defaultFocus
    • .
    • Explicit nudge targets.
    • Nudge shortcuts.
    • FocusArea with no focusable views.