Unterstützung von Desktop-Fenstern

Mit dem Desktop-Freiform-Fenster können Nutzer mehrere Apps gleichzeitig in App-Fenstern mit anpassbarer Größe ausführen und so ein vielseitiges, desktopähnliches Erlebnis erhalten.

In Abbildung 1 sehen Sie, wie der Bildschirm aussieht, wenn das Desktop-Freiform-Fenster aktiviert ist. Hinweise:

  • Nutzer können mehrere Apps gleichzeitig nebeneinander ausführen.
  • Die Taskleiste befindet sich an einer festen Position unten auf dem Display und zeigt die ausgeführten Apps an. Nutzer können Apps für den schnellen Zugriff anpinnen.
  • Eine neue anpassbare Kopfzeile ziert den oberen Rand jedes Fensters mit Steuerelementen wie „Minimieren“ und „Maximieren“.
Abbildung 1 Desktop-Freiform-Fenster auf einem Tablet.

Standardmäßig werden Apps auf Android-Tablets im Vollbildmodus geöffnet. Wenn Sie eine App im Desktop-Freiform-Fenster starten möchten, halten Sie den Fensterziehpunkt oben auf dem Bildschirm gedrückt und ziehen Sie ihn in der Benutzeroberfläche, wie in Abbildung 2 zu sehen.

Wenn eine App im Desktop-Freiform-Fenster geöffnet ist, werden auch andere Apps in Desktop-Fenstern geöffnet.

Abbildung 2 Halten Sie den Ziehpunkt des App-Fensters gedrückt und ziehen Sie ihn, um den Modus „Desktop-Freiform-Fenster“ zu starten.

Nutzer können das Desktop-Freiform-Fenster auch über das Menü aufrufen, das unter dem Fensterziehpunkt angezeigt wird, wenn Sie auf den Ziehpunkt tippen oder klicken oder die Tastenkombination Meta-Taste (Windows, Befehlstaste oder Suche) + Strg + Nach unten verwenden.

Nutzer beenden das Desktop-Freiform-Fenster, indem sie alle aktiven Fenster schließen oder den Fensterziehpunkt oben in einem Desktop-Fenster greifen und die App nach oben auf den Bildschirm ziehen. Mit der Tastenkombination Meta + H wird das Desktop Freiform-Fenster ebenfalls beendet und Apps werden wieder im Vollbildmodus ausgeführt.

Wenn Sie zum Desktop-Freiform-Fenster zurückkehren möchten, tippen oder klicken Sie auf die Kachel „Desktop-Bereich“ auf dem Bildschirm „Letzte Aktivitäten“.

App-Layout für eine desktopähnliche Umgebung optimieren

Das Design für einen Desktop kann sich erheblich vom mobilen Design unterscheiden, da mehr Bildschirmfläche zur Verfügung steht, die Eingabe über Maus und Tastatur präziser ist und eine hohe Produktivität erwartet wird.

Jetpack WindowManager bietet eine API, mit der Entwickler entscheiden können, wann eine Desktop-Benutzeroberfläche angezeigt werden soll. Diese hat in der Regel eine höhere Informationsdichte, andere Navigationsmuster und optimierte Mausinteraktionen.

lifecycleScope.launch(Dispatchers.Main) {
    lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
        windowInfoTracker.windowEngagementInfo(this@DesktopWindowingActivity)
            .collect { windowEngagementInfo ->
                if(windowEngagementInfo.hasEngagementMode(WindowEngagementInfo.EngagementMode.PRECISE_POINTER)){
                    showDesktopOptimizedUI()
                }else {
                    showTouchOptimizedUI()
                }
        }
    }
}

Weitere Informationen finden Sie unter Design für Desktop.

Größenänderung und Kompatibilitätsmodus

Im Desktop-Freiform-Fenster können Apps mit gesperrter Ausrichtung frei in der Größe angepasst werden. Das bedeutet, dass Nutzer die Größe der App auch dann an ein Fenster im Querformat anpassen können, wenn eine Aktivität auf das Hochformat gesperrt ist.

Abbildung 3 Größe des Fensters einer App mit Hochformatbeschränkung an das Querformat anpassen.

Bei Apps, die als nicht größenveränderbar deklariert sind (d. h. resizeableActivity = false), wird die Benutzeroberfläche skaliert, wobei das Seitenverhältnis beibehalten wird.

Abbildung 4 Die Benutzeroberfläche einer nicht größenveränderbaren App wird mit der Größe des Fensters skaliert.

Bei Kamera-Apps, die die Ausrichtung sperren oder als nicht größenveränderbar deklariert sind, wird der Sucher anders behandelt: Die Größe des Fensters kann vollständig angepasst werden, aber das Seitenverhältnis des Suchers bleibt gleich. Wenn davon ausgegangen wird, dass Apps immer im Hoch- oder Querformat ausgeführt werden, werden in den Apps Annahmen getroffen, die zu Fehlberechnungen der Ausrichtung oder des Seitenverhältnisses der Vorschau oder des aufgenommenen Bildes führen. Dadurch können Bilder verzerrt, seitlich oder auf dem Kopf dargestellt werden.

Bis Apps vollständig responsive Kamerasucher implementieren können, bietet die spezielle Behandlung eine einfachere Nutzererfahrung, die die Auswirkungen falscher Annahmen abmildert.

Weitere Informationen zum Kompatibilitätsmodus für Kamera-Apps finden Sie unter Geräte kompatibilitätsmodus.

Abbildung 5 Das Seitenverhältnis des Kamerasuchers bleibt beim Ändern der Fenstergröße gleich.

Anpassbare Kopfzeileneinrückungen

Alle Apps, die im Desktop-Freiform-Fenster ausgeführt werden, haben eine Kopfzeile, auch im immersiven Modus. Sie können diese Leiste anpassen, damit der Inhalt Ihrer App nicht verdeckt wird und benutzerdefinierte UI-Elemente direkt in den Kopfzeilenbereich eingefügt werden können.

Chrome vor und nach der Implementierung benutzerdefinierter Header.
Abbildung 6. Chrome vor und nach der Implementierung benutzerdefinierter Kopfzeilen.

Implementierung

Wenn Sie benutzerdefinierte Inhalte in die Kopfzeile einfügen möchten, müssen Sie zuerst den Hintergrund der Kopfzeile transparent machen. Dazu können Sie das APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND Flag mit dem WindowInsetsController verwenden.

window.insetsController?.setSystemBarsAppearance(
    WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND,
    WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND
)

Sobald die Kopfzeile transparent ist, können Sie den Kopfzeilenbereich an das Design Ihrer App anpassen. Verwenden Sie WindowInsets.isCaptionBarVisible, um zu erkennen, ob die Leiste vorhanden ist, und wenden Sie die entsprechende Höhe oder den entsprechenden Abstand auf Ihr Layout an.

@OptIn(ExperimentalLayoutApi::class)
@Composable
fun CaptionBar() {
    if (WindowInsets.isCaptionBarVisible) {
        Row(
            modifier = Modifier
                .windowInsetsTopHeight(WindowInsets.captionBar)
                .fillMaxWidth()
                .background(if (isSystemInDarkTheme()) Color.White else Color.Black),
            horizontalArrangement = Arrangement.Center,
            verticalAlignment = Alignment.CenterVertically
        ) {
            Text(
                text = "Caption Bar Title",
                style = MaterialTheme.typography.titleMedium,
                modifier = Modifier.padding(4.dp)
            )
        }
    }
}

  • setSystemBarsAppearance(appearance,mask): Konfiguriert den visuellen Stil der Systemleisten. Der erste Parameter definiert die Flags für das Ziel-Erscheinungsbild, während der zweite als Maske dient, um zu steuern, welche spezifischen Flags geändert werden.

  • windowInsetsTopHeight(): Legt die Höhe Ihres Composables automatisch so fest, dass sie der Kopfzeile des Systems entspricht. So kann der benutzerdefinierte Hintergrund den Titelbereich ausfüllen, ohne Pixelwerte fest zu codieren.

  • WindowInsets.captionBar: Gibt die Abmessungen für die Steuerelemente des Desktop Freiform-Fensters an (Schließen, Maximieren usw.). So kann die Größe der Benutzeroberfläche automatisch angepasst oder sie kann ausgeblendet werden, wenn Sie das Desktop-Freiform-Fenster aufrufen oder verlassen.

Weitere Informationen finden Sie unter Fenster-Insets. Neben einem Titel können Sie in der Titelleiste auch andere UI-Elemente anzeigen, z. B. Tabs wie in Google Chrome, Suchleisten oder Profilavatare.

Benutzeroberfläche

Um zu vermeiden, dass sich Ihre Benutzeroberfläche mit Systemschaltflächen überschneidet, bietet Android 15 die WindowInsets#getBoundingRects() Methode. Die Methode gibt eine Liste von Rect Objekten zurück, die Bereiche darstellen, die von Systemelementen belegt sind. Der verbleibende Platz in der Titelleiste ist ein sicherer Bereich , in dem Sie benutzerdefinierte Inhalte platzieren können.

Mit APPEARANCE_LIGHT_CAPTION_BARS können Sie das Erscheinungsbild von System-Titelleistenelementen für helle und dunkle Designs umschalten. Greifen Sie in Compose mit WindowInsets.Companion.captionBar() oder in Views mit WindowInsets.Type.captionBar() auf Insets zu.

Weitere Informationen finden Sie unter Fenster-Insets.

Multitasking und Unterstützung für den Multi-Instanz-Modus

Multitasking ist das Herzstück des Desktop-Freiform-Fensters. Wenn Sie mehrere Instanzen Ihrer App zulassen, kann die Produktivität der Nutzer erheblich gesteigert werden.

Ab Android 15 können Sie PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI verwenden. Wenn Sie diese Eigenschaft in Ihrer AndroidManifest.xml festlegen, geben Sie an, dass die System-Benutzeroberfläche Optionen (z. B. eine Schaltfläche „Neues Fenster“) bereitstellen soll, damit die App in mehreren Instanzen gestartet werden kann.

<application>
    <property
        android:name="android.window.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI"
        android:value="true" />
</application>

Hinweis:Im Desktop-Freiform-Fenster und in anderen Umgebungen mit mehreren Fenstern werden neue Aufgaben in einem neuen Fenster geöffnet. Überprüfen Sie daher den Nutzerablauf, wenn Ihre App mehrere Aufgaben startet.

App-Instanzen mit Ziehgesten verwalten

Im Mehrfenstermodus können Nutzer eine neue App-Instanz starten, indem sie ein UI-Element (z. B. einen Tab oder ein Dokument) aus dem Fenster der App ziehen. Nutzer können auch Elemente zwischen verschiedenen Instanzen derselben App verschieben.

Abbildung 7 Starten Sie eine neue Instanz von Chrome, indem Sie einen Tab aus dem Desktop-Fenster ziehen.

Daten per Drag-and-drop übertragen

Wenn Sie eine komponierbare Funktion als Ziehquelle für Drag-and-drop im Multi-Instanz-Modus konfigurieren möchten, damit Nutzer Inhalte in eine andere Instanz Ihrer App ziehen oder eine neue Instanz erstellen können, indem sie Inhalte in einen leeren Bereich des Bildschirms ziehen, verwenden Sie den dragAndDropSource Modifikator. Geben Sie in der Lambda-Funktion DragAndDropTransferData zurück und übergeben Sie das ClipData, das die zu übertragenden Daten enthält, sowie Flags zur Konfiguration des Multi-Instanz-Modus-Verhaltens.

Android 15 führt zwei wichtige Flags für das Desktop-Fenster und Interaktionen im Multi-Instanz-Modus ein:

  • DRAG_FLAG_GLOBAL_SAME_APPLICATION: Gibt an, dass ein Ziehvorgang Fenstergrenzen überschreiten kann (für mehrere Instanzen derselben Anwendung). Wenn startDragAndDrop() mit diesem Flag aufgerufen wird, können nur sichtbare Fenster, die zur selben Anwendung gehören, an dem Ziehvorgang teilnehmen und die gezogenen Inhalte empfangen.

Modifier.dragAndDropSource { _ ->
    DragAndDropTransferData(
        clipData = ClipData.newPlainText("label", "Your data"),
        flags = View.DRAG_FLAG_GLOBAL_SAME_APPLICATION
    )
}

Modifier.dragAndDropSource { _ ->
    val intent = Intent.makeMainActivity(activity.componentName).apply {
        putExtra("EXTRA_ITEM_ID", itemId)
        flags = Intent.FLAG_ACTIVITY_NEW_TASK or
                Intent.FLAG_ACTIVITY_MULTIPLE_TASK or
                Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT
    }

    val pendingIntent = PendingIntent.getActivity(
        activity, 0, intent, PendingIntent.FLAG_IMMUTABLE
    )

    val data = ClipData(
        "Item $itemId",
        arrayOf(ClipDescription.MIMETYPE_TEXT_INTENT),
        ClipData.Item.Builder().setIntentSender(pendingIntent.intentSender).build()
    )

    DragAndDropTransferData(
        clipData = data,
        flags = View.DRAG_FLAG_GLOBAL_SAME_APPLICATION or
                View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG,
    )
}

Übertragene Daten empfangen

Wenn Sie Daten von einer anderen Instanz akzeptieren möchten, verwenden Sie den dragAndDropTarget Modifikator. Sie müssen Berechtigungen explizit anfordern, wenn die Daten von einer anderen Instanz oder App stammen.

Modifier.dragAndDropTarget(
    shouldStartDragAndDrop = { event ->
        event.toAndroidDragEvent().clipDescription.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)
    },
    target = object : DragAndDropTarget {
        override fun onDrop(event: DragAndDropEvent): Boolean {
            requestDragAndDropPermissions(activity, event.toAndroidDragEvent())
            val clipData = event.toAndroidDragEvent().clipData
            val item = clipData?.getItemAt(0)?.text
            if (item != null) {
                // Process the dropped text item here
            }
            return item != null
        }
    }
)

Wichtige Schritte :

  • Filtern: Verwenden Sie shouldStartDragAndDrop, um zu prüfen, ob die eingehenden Daten (MIME-Typ) unterstützt werden.
  • Berechtigungen: Rufen Sie requestDragAndDropPermissions(event) auf, um auf die Daten zuzugreifen.
  • Verarbeiten: Extrahieren Sie die Daten im onDrop-Callback.

Zusätzliche Optimierungen

Passen Sie App-Starts an und wechseln Sie von Desktop-Freiform-Fenstern zum Vollbildmodus.

Standardgröße und -position angeben

Nicht alle Apps benötigen ein großes Fenster, um Nutzern einen Mehrwert zu bieten, auch wenn die Größe angepasst werden kann. Mit der Methode ActivityOptions#setLaunchBounds() können Sie eine Standard größe und -position festlegen, wenn eine Aktivität gestartet wird.

Vollbildmodus über den Desktop-Bereich aufrufen

Apps können mit Activity#requestFullScreenMode() in den Vollbildmodus wechseln. Die Methode zeigt die App direkt aus dem Desktop-Freiform-Fenster im Vollbildmodus an.