Penyematan aktivitas lanjutan

1. Pengantar

Penyematan aktivitas, yang diperkenalkan di Android 12L (level API 32), memungkinkan aplikasi berbasis aktivitas menampilkan beberapa aktivitas secara bersamaan di perangkat layar besar untuk membuat tata letak dua panel seperti daftar-detail.

Codelab Membangun tata letak daftar-detail dengan penyematan aktivitas dan Desain Material membahas cara menggunakan panggilan API WindowManager XML atau Jetpack untuk membuat tata letak daftar-detail.

Codelab ini memandu Anda mempelajari beberapa fitur yang baru dirilis untuk penyematan aktivitas, yang akan lebih meningkatkan pengalaman aplikasi Anda di perangkat layar besar. Fitur ini mencakup perluasan panel, penyematan aktivitas, dan peredupan dialog layar penuh.

Prasyarat

Yang akan Anda pelajari

Cara:

  • Mengaktifkan perluasan panel
  • Mengimplementasikan penyematan aktivitas dengan salah satu jendela terpisah
  • Menggunakan peredupan dialog layar penuh

Yang akan Anda butuhkan

  • Android Studio versi terbaru
  • Ponsel atau emulator Android dengan Android 15
  • Tablet besar atau emulator Android dengan lebar terkecil lebih besar dari 600 dp

2. Penyiapan

Mendapatkan aplikasi contoh

Langkah 1: Membuat clone repo

Buat clone repositori Git codelab layar besar:

git clone https://github.com/android/large-screen-codelabs

atau download dan batalkan pengarsipan file zip codelab layar besar:

Download kode sumber

Langkah 2: Memeriksa file sumber codelab

Buka folder activity-embedding-advanced.

Langkah 3: Membuka project codelab

Di Android Studio, buka project Kotlin atau Java

Daftar file untuk folder aktivitas di file repo dan zip.

Folder activity-embedding-advanced di file zip dan repo berisi dua project Android Studio: satu di Kotlin, satu di Java. Buka project pilihan Anda. Cuplikan codelab tersedia dalam kedua bahasa.

Membuat perangkat virtual

Jika Anda tidak memiliki ponsel Android, tablet kecil, atau tablet besar di level API 35 atau yang lebih tinggi, buka Pengelola Perangkat di Android Studio dan buat perangkat virtual berikut yang Anda perlukan:

  • Ponsel — Pixel 8, level API 35 atau yang lebih tinggi
  • Tablet — Pixel Tablet, level API 35 atau yang lebih tinggi

3. Menjalankan aplikasi

Aplikasi contoh menampilkan daftar item. Saat pengguna memilih item, aplikasi akan menampilkan informasi tentang item tersebut.

Aplikasi ini terdiri dari tiga aktivitas:

  • ListActivity — Berisi daftar item di RecyclerView
  • DetailActivity — Menampilkan informasi tentang item daftar saat item dipilih dari daftar
  • SummaryActivity — Menampilkan ringkasan informasi saat item daftar Ringkasan dipilih

Melanjutkan dari codelab sebelumnya

Dalam codelab Membuat tata letak daftar-detail dengan penyematan aktivitas dan Desain Material, kita mengembangkan aplikasi yang menampilkan tampilan daftar-detail menggunakan penyematan aktivitas dengan navigasi yang difasilitasi oleh kolom samping navigasi dan menu navigasi bawah.

  1. Jalankan aplikasi di tablet besar atau emulator Pixel dalam mode potret. Anda akan melihat layar daftar utama dan menu navigasi di bagian bawah.

74906232acad76f.png

  1. Putar tablet ke samping (lanskap). Layar akan terpisah, menampilkan daftar di satu sisi dan detail di sisi lainnya. Menu navigasi di bagian bawah seharusnya tergantikan oleh kolom samping navigasi vertikal.

dc6a7d1c02c49cd4.png

Fitur baru dengan penyematan aktivitas

Siap meningkatkan kualitas tata letak panel ganda Anda? Dalam codelab ini, kita akan menambahkan beberapa fitur baru yang keren untuk meningkatkan pengalaman pengguna. Berikut yang akan kita buat:

  1. Mari buat panel tersebut menjadi dinamis. Kita akan menerapkan perluasan panel, yang memberi pengguna kemampuan untuk mengubah ukuran (atau memperluas) panel untuk melihat tampilan yang disesuaikan.

2ec5f7fd6df5d8cd.gif

  1. Mari beri pengguna kemampuan untuk memilih prioritas. Dengan Penyematan Aktivitas, pengguna dapat membuat tugas terpenting mereka selalu berada di layar.

980d0033972737ed.gif

  1. Perlu fokus pada tugas tertentu? Kita akan menambahkan fitur peredupan layar penuh untuk mengurangi gangguan secara samar dan memungkinkan pengguna berkonsentrasi pada hal yang paling penting.

2d3455e0f8901f95.png

4. Perluasan panel

Sering kali, pengguna perlu berfokus pada salah satu panel terpisah sambil mempertahankan panel lainnya di layar saat menggunakan tata letak panel ganda di layar besar. Misalnya, membaca artikel di satu sisi sambil mempertahankan daftar percakapan chat di sisi lain. Biasanya, pengguna ingin mengubah ukuran panel untuk memudahkan fokus pada satu aktivitas.

Untuk mencapai tujuan ini, penyematan aktivitas menambahkan API baru agar Anda dapat memberi pengguna kesempatan untuk mengubah rasio pemisahan dan menyesuaikan transisi pengubahan ukuran.

Menambahkan dependensi

Pertama, tambahkan WindowManager 1.4 ke file build.gradle Anda.

Catatan: Beberapa fitur dalam library ini hanya berfungsi di Android 15 (level API 35) dan yang lebih tinggi.

build.gradle

 implementation 'androidx.window:window:1.4.0-alpha02'

Menyesuaikan pemisah jendela

Buat instance DividerAttributes dan tambahkan ke SplitAttributes. Objek ini mengonfigurasi perilaku keseluruhan tata letak terpisah Anda. Anda dapat menggunakan properti warna, lebar, dan rentang penarikan DividerAttributes untuk meningkatkan pengalaman pengguna.

Menyesuaikan pemisah:

  1. Periksa level API Ekstensi WindowManager. Karena fitur perluasan panel hanya tersedia di level API 6 dan yang lebih tinggi, hal ini juga berlaku untuk fitur baru lainnya.
  2. Buat DividerAttributes: Untuk menata gaya pemisah antar-panel, buat objek DividerAttributes. Objek ini memungkinkan Anda menetapkan:
  • color: Mengubah warna pemisah agar sesuai dengan tema aplikasi atau membuat pemisahan visual.
  • widthDp: Menyesuaikan lebar pemisah untuk visibilitas yang lebih baik atau tampilan yang lebih halus.
  1. Tambahkan ke SplitAttributes: Setelah menyesuaikan pemisah, tambahkan ke objek DividerAttributes.
  2. Tetapkan rentang penarikan (opsional): Anda juga dapat mengontrol seberapa jauh pengguna dapat menarik pemisah untuk mengubah ukuran panel.
  • DRAG_RANGE_SYSTEM_DEFAULT: Gunakan nilai khusus ini untuk memungkinkan sistem menentukan rentang penarikan yang sesuai berdasarkan ukuran layar dan faktor bentuk perangkat.
  • Nilai kustom (antara 0,33 dan 0,66): Tetapkan rentang penarikan Anda sendiri untuk membatasi seberapa banyak pengguna dapat mengubah ukuran panel. Ingat, jika pengguna menariknya hingga melewati batas ini, tata letak terpisah akan dinonaktifkan.

Ganti splitAttributes dengan kode berikut.

SplitManager.kt

val splitAttributesBuilder: SplitAttributes.Builder = SplitAttributes.Builder()
   .setSplitType(SplitAttributes.SplitType.ratio(0.33f))
   .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)

if (WindowSdkExtensions.getInstance().extensionVersion >= 6) {
   splitAttributesBuilder.setDividerAttributes(
       DividerAttributes.DraggableDividerAttributes.Builder()
           .setColor(getColor(context, R.color.divider_color))
           .setWidthDp(4)
           .setDragRange(
               DividerAttributes.DragRange.DRAG_RANGE_SYSTEM_DEFAULT)
           .build()
   )
}
val splitAttributes: SplitAttributes = splitAttributesBuilder.build()

SplitManager.java

SplitAttributes.Builder splitAttributesBuilder = new SplitAttributes.Builder()
        .setSplitType(SplitAttributes.SplitType.ratio(0.33f))
        .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT);

if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 6) {
    splitAttributesBuilder.setDividerAttributes(
            new DividerAttributes.DraggableDividerAttributes.Builder()
                    .setColor(ContextCompat.getColor(context, R.color.divider_color))
                    .setWidthDp(4)
                    .setDragRange(DividerAttributes.DragRange.DRAG_RANGE_SYSTEM_DEFAULT)
                    .build()
    );
}
SplitAttributes splitAttributes = splitAttributesBuilder.build();

Buat divider_color.xml di folder res/color dengan konten berikut.

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
   <item android:color="#669df6" />
</selector>

Menjalankan aplikasi

Selesai. Bangun dan jalankan aplikasi contoh.

Anda akan melihat perluasan panel dan dapat menariknya.

2ec5f7fd6df5d8cd.gif

Mengubah rasio pemisahan di versi lama

Catatan kompatibilitas penting: Fitur perluasan panel hanya tersedia di Ekstensi WindowManager 6 atau yang lebih baru, yang berarti Anda memerlukan Android 15 (level API 35) atau yang lebih tinggi.

Namun, Anda tetap harus memberikan pengalaman yang baik bagi pengguna di versi Android yang lebih lama.

Di Android 14 (level API 34) dan yang lebih rendah, Anda masih dapat memberikan penyesuaian rasio pemisahan dinamis menggunakan class SplitAttributesCalculator. Hal ini menawarkan cara untuk mempertahankan beberapa level kontrol pengguna terhadap tata letak, bahkan tanpa perlu memperluas panel.

a36f8ba4226353c5.gif

Ingin tahu cara yang optimal untuk menggunakan fitur ini? Kami akan membahas semua praktik terbaik dan tips eksklusif di bagian "Praktik Terbaik".

5. Penyematan aktivitas

Pernahkah Anda ingin mempertahankan satu bagian tampilan layar terpisah agar tetap terbuka sambil membuka-buka layar dengan bebas di bagian lainnya? Bayangkan Anda membaca artikel panjang di satu sisi sambil tetap dapat berinteraksi dengan konten aplikasi lain di sisi lainnya.

Di sinilah peran penyematan aktivitas. Fitur ini memungkinkan Anda menyematkan salah satu jendela terpisah sehingga tetap berada di layar meskipun Anda membuka-buka jendela lain. Hal ini memberikan pengalaman multitasking yang lebih fokus dan produktif bagi pengguna.

Menambahkan tombol penyematan

Pertama, mari tambahkan tombol di DetailActivity.. Aplikasi menyematkan DetailActivity ini saat pengguna mengklik tombol.

Buat perubahan berikut ke activity_detail.xml:

  1. Tambahkan ID ke ConstraintLayout
android:id="@+id/detailActivity"
  1. Tambahkan tombol di bagian bawah tata letak
<androidx.appcompat.widget.AppCompatButton
      android:id="@+id/pinButton"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="@string/pin_this_activity"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toBottomOf="@id/textViewItemDetail"/>
  1. Batasi bagian bawah TextView ke bagian atas tombol
app:layout_constraintBottom_toTopOf="@id/pinButton"

Hapus baris ini di TextView.

app:layout_constraintBottom_toBottomOf="parent"

Berikut adalah kode XML lengkap untuk file tata letak activity_detail.xml Anda, termasuk tombol PIN THIS ACTIVITY (SEMATKAN AKTIVITAS INI) yang baru saja kita tambahkan:

<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/detailActivity"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/white"
    tools:context=".DetailActivity">

  <TextView
      android:id="@+id/textViewItemDetail"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:textSize="36sp"
      android:textColor="@color/obsidian"
      app:layout_constraintBottom_toTopOf="@id/pinButton"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent" />

  <androidx.appcompat.widget.AppCompatButton
      android:id="@+id/pinButton"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="@string/pin_this_activity"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toBottomOf="@id/textViewItemDetail"/>

</androidx.constraintlayout.widget.ConstraintLayout>

Tambahkan string pin_this_activity ke res/values/strings.xml.

<string name="pin_this_activity">PIN THIS ACTIVITY</string>

Menghubungkan tombol penyematan

  1. Deklarasikan variabel: Dalam file DetailActivity.kt, deklarasikan variabel untuk menyimpan referensi ke tombol PIN THIS ACTIVITY:

DetailActivity.kt

private lateinit var pinButton: Button

DetailActivity.java

private Button pinButton;
  1. Temukan tombol dalam tata letak dan tambahkan callback setOnClickListener().

DetailActivity.kt / onCreate

pinButton = findViewById(R.id.pinButton)
pinButton.setOnClickListener {
 pinActivityStackExample(taskId)
}

DetailActivity.java / onCreate()

Button pinButton = findViewById(R.id.pinButton);
pinButton.setOnClickListener( (view) => {
        pinActivityStack(getTaskId());

});
  1. Buat metode baru bernama pinActivityStackExample di class DetailActivity Anda. Kita akan mengimplementasikan logika penyematan yang sebenarnya di sini.

DetailActivity.kt

private fun pinActivityStackExample(taskId: Int) {

 val splitAttributes: SplitAttributes = SplitAttributes.Builder()
   .setSplitType(SplitAttributes.SplitType.ratio(0.66f))
   .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
   .build()

 val pinSplitRule = SplitPinRule.Builder()
   .setSticky(true)
   .setDefaultSplitAttributes(splitAttributes)
   .build()

 SplitController.getInstance(applicationContext).pinTopActivityStack(taskId, pinSplitRule)
}

DetailActivity.java

private void pinActivityStackExample(int taskId) {
    SplitAttributes splitAttributes = new SplitAttributes.Builder()
            .setSplitType(SplitAttributes.SplitType.ratio(0.66f))
            .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
            .build();

    SplitPinRule pinSplitRule = new SplitPinRule.Builder()
            .setSticky(true)
            .setDefaultSplitAttributes(splitAttributes)
            .build();

    SplitController.getInstance(getApplicationContext()).pinTopActivityStack(taskId, pinSplitRule);
}

Catatan:

  1. Hanya satu aktivitas yang dapat disematkan dalam satu waktu. Lepaskan sematan aktivitas yang saat ini disematkan dengan
unpinTopActivityStack()

sebelum Anda menyematkan aktivitas lain.

  1. Untuk mengaktifkan perluasan panel saat menyematkan aktivitas, panggil
setDividerAttributes()

untuk

SplitAttributes

yang juga baru dibuat.

Perubahan navigasi kembali

Dengan WindowManager 1.4, perilaku navigasi kembali berubah. Peristiwa kembali dikirim ke aktivitas yang terakhir difokuskan saat menggunakan navigasi tombol.

Navigasi tombol:

  • Dengan navigasi tombol, peristiwa kembali kini secara konsisten dikirim ke aktivitas yang terakhir difokuskan. Hal ini menyederhanakan perilaku navigasi kembali, sehingga lebih mudah diprediksi oleh pengguna.

Navigasi gestur:

  • Android 14 (level API 34) dan yang lebih rendah: Gestur kembali mengirim peristiwa ke aktivitas tempat gestur terjadi, yang dapat menyebabkan perilaku yang tidak terduga dalam skenario layar terpisah.
  • Android 15 (level API 35) dan yang lebih tinggi:
  • Aktivitas dalam aplikasi yang sama: Gestur kembali secara konsisten menyelesaikan aktivitas teratas, terlepas dari arah geser, sehingga memberikan pengalaman yang lebih terpadu.
  • Aktivitas dalam aplikasi (overlay) yang berbeda: Peristiwa kembali akan membuka aktivitas terakhir yang difokuskan, yang selaras dengan perilaku navigasi tombol.

Menjalankan aplikasi

Bangun dan jalankan aplikasi contoh.

Menyematkan aktivitas

  • Buka layar DetailActivity.
  • Ketuk tombol PIN THIS ACTIVITY.

980d0033972737ed.gif

6. Peredupan dialog layar penuh

Meskipun penyematan aktivitas memfasilitasi tata letak layar terpisah, dialog dalam versi sebelumnya hanya meredupkan penampung aktivitasnya sendiri. Hal ini dapat menciptakan pengalaman visual yang terputus-putus, terutama jika Anda ingin dialog menjadi sorotan.

Solusi: WindowManager 1.4

  • Kami siap membantu. Dengan WindowManager 1.4, dialog kini meredupkan seluruh jendela aplikasi secara default (DimAreaBehavior.Companion.ON_TASK), sehingga memberikan nuansa yang lebih imersif dan fokus.
  • Ingin kembali ke perilaku lama? Tidak masalah. Anda masih dapat memilih untuk meredupkan hanya penampung aktivitas menggunakan ON_ACTIVITY_STACK.

ON_ACTIVITY_STACK

ON_TASK

Berikut cara menggunakan ActivityEmbeddingController untuk mengelola perilaku peredupan layar penuh:

Catatan: Peredupan dialog layar penuh tersedia dengan Ekstensi WindowManager 5 atau yang lebih baru.

SplitManager.kt / createSplit()

with(ActivityEmbeddingController.getInstance(context)) {
   if (WindowSdkExtensions.getInstance().extensionVersion  >= 5) {
       setEmbeddingConfiguration(
           EmbeddingConfiguration.Builder()
               .setDimAreaBehavior(ON_TASK)
               .build()
       )
   }
}

SplitManager.java / createSplit()

ActivityEmbeddingController controller = ActivityEmbeddingController.getInstance(context);
if (WindowSdkExtensions.getInstance().getExtensionVersion()  >= 5) {
    controller.setEmbeddingConfiguration(
        new EmbeddingConfiguration.Builder()
            .setDimAreaBehavior(EmbeddingConfiguration.DimAreaBehavior.ON_TASK)
            .build()
    );
}

Untuk menampilkan fitur peredupan layar penuh, kita akan membuat dialog pemberitahuan yang meminta konfirmasi pengguna sebelum menyematkan aktivitas. Setelah muncul, dialog ini akan meredupkan seluruh jendela aplikasi, bukan hanya penampung tempat aktivitas berada.

DetailActivity.kt

pinButton.setOnClickListener {
 showAlertDialog(taskId)
}

...
private fun showAlertDialog(taskId: Int) {
 val builder = AlertDialog.Builder(this)
 builder.setTitle(getString(R.string.dialog_title))
 builder.setMessage(getString(R.string.dialog_message))
 builder.setPositiveButton(getString(R.string.button_yes)) { _, _ ->
   if (WindowSdkExtensions.getInstance().extensionVersion  >= 6) {
     pinActivityStackExample(taskId)
   }
 }
 builder.setNegativeButton(getString(R.string.button_cancel)) { _, _ ->
   // Cancel
 }
 val dialog: AlertDialog = builder.create()
 dialog.show()
}

DetailActivity.java

pinButton.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View v) {
       showAlertDialog(getTaskId());
   }
});

...

private void showAlertDialog(int taskId) {
   AlertDialog.Builder builder = new AlertDialog.Builder(this);
   builder.setTitle(getString(R.string.dialog_title));
   builder.setMessage(getString(R.string.dialog_message));

   builder.setPositiveButton(getString(R.string.button_yes), new DialogInterface.OnClickListener() {
       @Override
       public void onClick(DialogInterface dialog, int which) {
           if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 6) {
               pinActivityStackExample(taskId);
           }
       }
   });
   builder.setNegativeButton(getString(R.string.button_cancel), new DialogInterface.OnClickListener() {
       @Override
       public void onClick(DialogInterface dialog, int which) {
           // Cancel
       }
   });
   AlertDialog dialog = builder.create();
   dialog.show();
}

Tambahkan string berikut ke res/values/strings.xml.

<!-- Dialog information -->
<string name="dialog_title">Activity Pinning</string>
<string name="dialog_message">Confirm to pin this activity</string>
<string name="button_yes">Yes</string>
<string name="button_cancel">Cancel</string>

Menjalankan aplikasi

Bangun dan jalankan aplikasi contoh.

Klik tombol sematkan aktivitas:

  • Dialog pemberitahuan akan muncul, yang meminta Anda mengonfirmasi tindakan penyematan.
  • Perhatikan bagaimana seluruh layar, termasuk kedua panel terpisah, diredupkan, sehingga memfokuskan perhatian pada dialog.

2d3455e0f8901f95.png

7. Praktik terbaik

Mengizinkan pengguna menonaktifkan tata letak panel ganda

Agar transisi ke tata letak baru lebih lancar, mari beri pengguna kemampuan untuk beralih antara tampilan panel ganda dan kolom tunggal. Kita dapat melakukannya menggunakan SplitAttributesCalculator dan SharedPreferences untuk menyimpan preferensi pengguna.

Mengubah rasio pemisahan di Android 14 dan yang lebih rendah

Kita telah mempelajari perluasan panel, yang memberikan cara yang bagus bagi pengguna untuk menyesuaikan rasio pemisahan di Android 15 dan yang lebih tinggi. Namun, bagaimana kita dapat menawarkan tingkat fleksibilitas yang serupa kepada pengguna di versi Android yang lebih lama?

Mari pelajari cara SplitAttributesCalculator dapat membantu kita mencapai hal ini dan memastikan pengalaman yang konsisten di berbagai perangkat.

Berikut ini contoh tampilannya:

a87452341434c86d.gif

Membuat layar setelan

Untuk memulai, mari buat layar setelan khusus untuk konfigurasi pengguna.

Dalam layar setelan ini, kita akan menyertakan tombol untuk mengaktifkan atau menonaktifkan fitur penyematan aktivitas untuk seluruh aplikasi. Selain itu, kita akan menyertakan status progres yang memungkinkan pengguna menyesuaikan rasio pemisahan tata letak panel ganda. Perhatikan bahwa nilai rasio pemisahan hanya akan diterapkan jika tombol penyematan aktivitas diaktifkan.

Setelah pengguna menetapkan nilai di SettingsActivity, kita menyimpannya di SharedPreferences untuk digunakan nanti di tempat lain dalam aplikasi.

build.gradle

Tambahkan dependensi preferensi.

implementation 'androidx.preference:preference-ktx:1.2.1' // Kotlin

Atau

implementation 'androidx.preference:preference:1.2.1' // Java

SettingsActivity.kt

package com.example.activity_embedding

import android.os.Bundle
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SeekBarPreference
import androidx.preference.SwitchPreferenceCompat

class SettingsActivity : AppCompatActivity() {

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.settings_activity)
    if (savedInstanceState == null) {
      supportFragmentManager
        .beginTransaction()
        .replace(R.id.settings, SettingsFragment())
        .commit()
    }
    supportActionBar?.setDisplayHomeAsUpEnabled(true)
  }

  override fun onOptionsItemSelected(item: MenuItem): Boolean {
    if (item.itemId == android.R.id.home) finishActivity()
    return super.onOptionsItemSelected(item)
  }

  private fun finishActivity() { finish() }

  class SettingsFragment : PreferenceFragmentCompat() {
    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
      setPreferencesFromResource(R.xml.root_preferences, rootKey)
findPreference<SwitchPreferenceCompat>("dual_pane")?.setOnPreferenceChangeListener { _, newValue ->
        if (newValue as Boolean) {
          this.activity?.let {
            SharePref(it.applicationContext).setAEFlag(true)
          }
        } else {
          this.activity?.let {
            SharePref(it.applicationContext).setAEFlag(false)
          }
        }
        this.activity?.finish()
        true
      }

      val splitRatioPreference: SeekBarPreference? = findPreference("split_ratio")
      splitRatioPreference?.setOnPreferenceChangeListener { _, newValue ->
        if (newValue is Int) {
          this.activity?.let { SharePref(it.applicationContext).setSplitRatio(newValue.toFloat()/100) }
        }
        true
      }
    }
  }
}

SettingsActivity.java

package com.example.activity_embedding;

import android.os.Bundle;
import android.view.MenuItem;

import androidx.appcompat.app.AppCompatActivity;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.SeekBarPreference;
import androidx.preference.SwitchPreferenceCompat;

public class SettingsActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.settings_activity);
        if (savedInstanceState == null) {
            getSupportFragmentManager()
                .beginTransaction()
                .replace(R.id.settings, new SettingsFragment())
                .commit();
        }
        if (getSupportActionBar() != null) {
            getSupportActionBar().setDisplayHomeAsUpEnabled(true);
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == android.R.id.home) {
            finishActivity();
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    private void finishActivity() {
        finish();
    }

    public static class SettingsFragment extends PreferenceFragmentCompat {
        @Override
        public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
            setPreferencesFromResource(R.xml.root_preferences, rootKey);

            SwitchPreferenceCompat dualPanePreference = findPreference("dual_pane");
            if (dualPanePreference != null) {
                dualPanePreference.setOnPreferenceChangeListener((preference, newValue) -> {
                    boolean isDualPane = (Boolean) newValue;
                    if (getActivity() != null) {
                        SharePref sharePref = new SharePref(getActivity().getApplicationContext());
                        sharePref.setAEFlag(isDualPane);
                        getActivity().finish();
                    }
                    return true;
                });
            }

            SeekBarPreference splitRatioPreference = findPreference("split_ratio");
            if (splitRatioPreference != null) {
                splitRatioPreference.setOnPreferenceChangeListener((preference, newValue) -> {
                    if (newValue instanceof Integer) {
                        float splitRatio = ((Integer) newValue) / 100f;
                        if (getActivity() != null) {
                            SharePref sharePref = new SharePref(getActivity().getApplicationContext());
                            sharePref.setSplitRatio(splitRatio);
                        }
                    }
                    return true;
                });
            }
        }
    }
}

Tambahkan settings_activity.xml di folder tata letak

settings_activity.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="match_parent">

   <FrameLayout
       android:id="@+id/settings"
       android:layout_width="match_parent"
       android:layout_height="match_parent" />
</LinearLayout>

Tambahkan SettingsActivity ke file manifes Anda.

<activity
   android:name=".SettingsActivity"
   android:exported="false"
   android:label="@string/title_activity_settings" />

Konfigurasi aturan pemisahan untuk SettingsActivity.

SplitManager.kt / createSplit()

val settingActivityFilter = ActivityFilter(
   ComponentName(context, SettingsActivity::class.java),
   null
)
val settingActivityFilterSet = setOf(settingActivityFilter)
val settingActivityRule = ActivityRule.Builder(settingActivityFilterSet)
   .setAlwaysExpand(true)
   .build()
ruleController.addRule(settingActivityRule)

SplitManager.java / createSplit()

Set<ActivityFilter> settingActivityFilterSet = new HashSet<>();
ActivityFilter settingActivityFilter = new ActivityFilter(
        new ComponentName(context, SettingsActivity.class),
        null
);
settingActivityFilterSet.add(settingActivityFilter);
ActivityRule settingActivityRule = new ActivityRule.Builder(settingActivityFilterSet)
        .setAlwaysExpand(true).build();
ruleController.addRule(settingActivityRule);

Berikut adalah kode untuk menyimpan setelan pengguna di SharedPreferences.

SharedPref.kt

package com.example.activity_embedding

import android.content.Context
import android.content.SharedPreferences

class SharePref(context: Context) {
    private val sharedPreferences: SharedPreferences =
        context.getSharedPreferences("my_app_preferences", Context.MODE_PRIVATE)

    companion object {
        private const val AE_FLAG = "is_activity_embedding_enabled"
        private const val SPLIT_RATIO = "activity_embedding_split_ratio"
        const val DEFAULT_SPLIT_RATIO = 0.3f
    }

    fun setAEFlag(isEnabled: Boolean) {
        sharedPreferences.edit().putBoolean(AE_FLAG, isEnabled).apply()
    }

    fun getAEFlag(): Boolean = sharedPreferences.getBoolean(AE_FLAG, true)

    fun getSplitRatio(): Float = sharedPreferences.getFloat(SPLIT_RATIO, DEFAULT_SPLIT_RATIO)

    fun setSplitRatio(ratio: Float) {
        sharedPreferences.edit().putFloat(SPLIT_RATIO, ratio).apply()
    }
}

SharedPref.java

package com.example.activity_embedding;

import android.content.Context;
import android.content.SharedPreferences;

public class SharePref {
    private static final String PREF_NAME = "my_app_preferences";
    private static final String AE_FLAG = "is_activity_embedding_enabled";
    private static final String SPLIT_RATIO = "activity_embedding_split_ratio";
    public static final float DEFAULT_SPLIT_RATIO = 0.3f;

    private final SharedPreferences sharedPreferences;

    public SharePref(Context context) {
        this.sharedPreferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
    }

    public void setAEFlag(boolean isEnabled) {
        SharedPreferences.Editor editor = sharedPreferences.edit();
        editor.putBoolean(AE_FLAG, isEnabled);
        editor.apply();
    }

    public boolean getAEFlag() {
        return sharedPreferences.getBoolean(AE_FLAG, true);
    }

    public float getSplitRatio() {
        return sharedPreferences.getFloat(SPLIT_RATIO, DEFAULT_SPLIT_RATIO);
    }

    public void setSplitRatio(float ratio) {
        SharedPreferences.Editor editor = sharedPreferences.edit();
        editor.putFloat(SPLIT_RATIO, ratio);
        editor.apply();
    }
}

Anda juga memerlukan xml tata letak layar preferensi. Buat root_preferences.xml di bagian res/xml dengan kode berikut.

<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:android="http://schemas.android.com/apk/res/android">
   <PreferenceCategory app:title="@string/split_setting_header">

       <SwitchPreferenceCompat
           app:key="dual_pane"
           app:title="@string/dual_pane_title" />

       <SeekBarPreference
           app:key="split_ratio"
           app:title="@string/split_ratio_title"
           android:min="0"
           android:max="100"
           app:defaultValue="50"
           app:showSeekBarValue="true" />
   </PreferenceCategory>
</PreferenceScreen>

Lalu, tambahkan kode berikut ke res/values/strings.xml.

<string name="title_activity_settings">SettingsActivity</string>
<string name="split_setting_header">Dual Pane Display</string>
<string name="dual_pane_title">Dual Pane</string>
<string name="split_ratio_title">Split Ratio</string>

Tambahkan SettingsActivity ke menu

Mari hubungkan SettingsActivity yang baru dibuat ke tujuan navigasi, sehingga pengguna dapat dengan mudah mengaksesnya dari antarmuka utama aplikasi.

  1. Dalam file ListActivity, deklarasikan variabel untuk menu navigasi bawah dan kolom samping navigasi kiri:

ListActivity.kt

 private lateinit var navRail: NavigationRailView private lateinit var bottomNav: BottomNavigationView

ListActivity.java

 private NavigationRailView navRail;  private BottomNavigationView bottomNav;
  1. Di dalam metode onCreate() ListActivity, gunakan findViewById untuk menghubungkan variabel ini ke tampilan yang sesuai dalam tata letak Anda;
  2. Tambahkan OnItemSelectedListener ke menu navigasi bawah dan kolom samping navigasi untuk menangani peristiwa pemilihan item:

ListActivity.kt / onCreate()

navRail  = findViewById(R.id.navigationRailView)
bottomNav = findViewById(R.id.bottomNavigationView)

val menuListener = NavigationBarView.OnItemSelectedListener { item ->
    when (item.itemId) {
        R.id.navigation_home -> {
            true
        }
        R.id.navigation_dashboard -> {
            true
        }
        R.id.navigation_settings -> {
            startActivity(Intent(this, SettingsActivity::class.java))
            true
        }
        else -> false
    }
}

navRail.setOnItemSelectedListener(menuListener)
bottomNav.setOnItemSelectedListener(menuListener)

ListActivity.java / onCreate()

NavigationRailView navRail = findViewById(R.id.navigationRailView);
BottomNavigationView bottomNav = findViewById(R.id.bottomNavigationView);

NavigationBarView.OnItemSelectedListener menuListener = new NavigationBarView.OnItemSelectedListener() {
   @Override
   public boolean onNavigationItemSelected(@NonNull MenuItem item) {
       switch (item.getItemId()) {
           case R.id.navigation_home:
               // Handle navigation_home selection
               return true;
           case R.id.navigation_dashboard:
               // Handle navigation_dashboard selection
               return true;
           case R.id.navigation_settings:
               startActivity(new Intent(ListActivity.this, SettingsActivity.class));
               return true;
           default:
               return false;
       }
   }
};

navRail.setOnItemSelectedListener(menuListener);
bottomNav.setOnItemSelectedListener(menuListener);

Aplikasi membaca SharedPreferences dan merender aplikasi dalam mode terpisah atau mode SPLIT_TYPE_EXPAND.

  • Saat konfigurasi jendela berubah, program akan memeriksa apakah batasan jendela terpisah terpenuhi (jika lebar > 840 dp)
  • Selain itu, aplikasi akan memeriksa nilai SharedPreferences untuk melihat apakah pengguna telah mengaktifkan tampilan jendela terpisah. Jika tidak, aplikasi akan menampilkan SplitAttribute dengan jenis SPLIT_TYPE_EXPAND.
  • Jika jendela terpisah diaktifkan, aplikasi akan membaca nilai SharedPreferences untuk mendapatkan rasio pemisahan. Hal ini hanya berfungsi jika versi WindowSDKExtensions kurang dari 6, karena versi 6 sudah mendukung perluasan panel dan mengabaikan setelan rasio pemisahan. Sebagai gantinya, developer dapat mengizinkan pengguna menarik pemisah di UI.

ListActivity.kt / onCreate()

...

SplitController.getInstance(this).setSplitAttributesCalculator{
       params -> params.defaultSplitAttributes
   if (params.areDefaultConstraintsSatisfied) {
       setWiderScreenNavigation(true)

       if (SharePref(this.applicationContext).getAEFlag()) {
           if (WindowSdkExtensions.getInstance().extensionVersion  < 6) {
               // Read a dynamic split ratio from shared preference.
               val currentSplit = SharePref(this.applicationContext).getSplitRatio()
               if (currentSplit != SharePref.DEFAULT_SPLIT_RATIO) {
                   return@setSplitAttributesCalculator SplitAttributes.Builder()
                       .setSplitType(SplitAttributes.SplitType.ratio(SharePref(this.applicationContext).getSplitRatio()))
                     .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
                       .build()
               }
           }
           return@setSplitAttributesCalculator params.defaultSplitAttributes
       } else {
           SplitAttributes.Builder()
               .setSplitType(SPLIT_TYPE_EXPAND)
               .build()
       }
   } else {
       setWiderScreenNavigation(false)
       SplitAttributes.Builder()
           .setSplitType(SPLIT_TYPE_EXPAND)
           .build()
   }
}

...

ListActivity.java / onCreate()

...
SplitController.getInstance(this).setSplitAttributesCalculator(params -> {
   if (params.areDefaultConstraintsSatisfied()) {
       setWiderScreenNavigation(true);

       SharePref sharedPreference = new SharePref(this.getApplicationContext());
       if (sharedPreference.getAEFlag()) {
           if (WindowSdkExtensions.getInstance().getExtensionVersion()  < 6) {
               // Read a dynamic split ratio from shared preference.
               float currentSplit = sharedPreference.getSplitRatio();
               if (currentSplit != SharePref.DEFAULT_SPLIT_RATIO) {
                   return new SplitAttributes.Builder()
                           .setSplitType(SplitAttributes.SplitType.ratio(sharedPreference.getSplitRatio()))
                           .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
                           .build();
               }
           }
           return params.getDefaultSplitAttributes();
       } else {
           return new SplitAttributes.Builder()
                   .setSplitType(SPLIT_TYPE_EXPAND)
                   .build();
       }
   } else {
       setWiderScreenNavigation(false);
       return new SplitAttributes.Builder()
               .setSplitType(SPLIT_TYPE_EXPAND)
               .build();
   }
});

...

Untuk memicu SplitAttributesCalculator setelah perubahan setelan, kita perlu membatalkan validasi atribut saat ini. Kita melakukannya dengan memanggil invalidateVisibleActivityStacks() dari ActivityEmbeddingController; sebelum WindowManager 1.4, metode ini bernama

invalidateTopVisibleSplitAttributes.

ListActivity.kt / onResume()

override fun onResume() {
   super.onResume()
   ActivityEmbeddingController.getInstance(this).invalidateVisibleActivityStacks()
}

ListActivity.java / onResume()

@Override
public void onResume() {
    super.onResume();
    ActivityEmbeddingController.getInstance(this).invalidateVisibleActivityStacks();
}

Menjalankan aplikasi

Bangun dan jalankan aplikasi contoh.

Mempelajari setelan:

  • Buka layar setelan.
  • Aktifkan dan nonaktifkan tombol Enable Split Window.
  • Sesuaikan penggeser rasio pemisahan (jika tersedia di perangkat Anda).

Memperhatikan perubahan tata letak:

  • Di perangkat yang menjalankan Android 14 dan yang lebih rendah: Tata letak akan beralih antara mode panel tunggal dan panel ganda berdasarkan tombol, dan rasio pemisahan akan berubah saat Anda menyesuaikan penggeser.
  • Di perangkat yang menjalankan Android 15 dan yang lebih tinggi: Perluasan panel akan memungkinkan Anda mengubah ukuran panel secara dinamis, terlepas dari setelan penggeser.

8. Selamat!

Bagus! Anda telah berhasil meningkatkan kualitas aplikasi dengan fitur baru yang canggih menggunakan penyematan aktivitas dan WindowManager. Pengguna Anda kini akan menikmati pengalaman yang lebih fleksibel, intuitif, dan menarik di perangkat layar besar, terlepas dari versi Android mereka.

9. Pelajari lebih lanjut