Membuat aplikasi Anda fold aware

Dengan layar besar yang dibentangkan dan bentuk lipatan unik memungkinkan pengalaman pengguna baru di perangkat foldable. Agar aplikasi Anda menjadi fold aware, gunakan library Jetpack WindowManager yang menyediakan platform API untuk fitur jendela perangkat foldable, seperti lipatan dan engsel. Jika aplikasi Anda fold-aware, aplikasi dapat menyesuaikan tata letaknya untuk menghindari penempatan konten penting di area lipatan atau engsel serta menggunakan lipatan dan engsel sebagai pemisah alami.

Memahami apakah perangkat mendukung konfigurasi seperti postur di atas meja atau postur buku dapat memandu keputusan tentang mendukung tata letak yang berbeda atau menyediakan fitur tertentu.

Informasi jendela

Antarmuka WindowInfoTracker di Jetpack WindowManager mengekspos informasi tata letak jendela. Metode windowLayoutInfo() antarmuka menampilkan aliran data WindowLayoutInfo yang memberi tahu aplikasi Anda tentang status lipatan perangkat foldable. Metode WindowInfoTracker#getOrCreate() membuat instance WindowInfoTracker.

WindowManager menyediakan dukungan untuk mengumpulkan data WindowLayoutInfo menggunakan alur Kotlin dan callback Java.

Alur Kotlin

Untuk memulai dan menghentikan pengumpulan data WindowLayoutInfo, Anda dapat menggunakan coroutine berbasis siklus proses yang dapat dimulai ulang tempat blok kode repeatOnLifecycle dijalankan saat siklus proses setidaknya dalam status STARTED dan dihentikan saat siklus proses dalam status STOPPED. Eksekusi blok kode otomatis dimulai ulang saat siklus proses dalam status STARTED lagi. Pada contoh berikut, blok kode mengumpulkan dan menggunakan data WindowLayoutInfo:

class DisplayFeaturesActivity : AppCompatActivity() {

    private lateinit var binding: ActivityDisplayFeaturesBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityDisplayFeaturesBinding.inflate(layoutInflater)
        setContentView(binding.root)

        lifecycleScope.launch(Dispatchers.Main) {
            lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
                WindowInfoTracker.getOrCreate(this@DisplayFeaturesActivity)
                    .windowLayoutInfo(this@DisplayFeaturesActivity)
                    .collect { newLayoutInfo ->
                        // Use newLayoutInfo to update the layout.
                    }
            }
        }
    }
}

Callback Java

Lapisan kompatibilitas callback yang disertakan dalam dependensi androidx.window:window-java memungkinkan Anda mengumpulkan update WindowLayoutInfo tanpa menggunakan flow Kotlin. Artefak ini menyertakan class WindowInfoTrackerCallbackAdapter, yang menyesuaikan WindowInfoTracker untuk mendukung pendaftaran (dan pembatalan pendaftaran) callback untuk menerima update WindowLayoutInfo, misalnya:

public class SplitLayoutActivity extends AppCompatActivity {

    private WindowInfoTrackerCallbackAdapter windowInfoTracker;
    private ActivitySplitLayoutBinding binding;
    private final LayoutStateChangeCallback layoutStateChangeCallback =
            new LayoutStateChangeCallback();

   @Override
   protected void onCreate(@Nullable Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);

       binding = ActivitySplitLayoutBinding.inflate(getLayoutInflater());
       setContentView(binding.getRoot());

       windowInfoTracker =
                new WindowInfoTrackerCallbackAdapter(WindowInfoTracker.getOrCreate(this));
   }

   @Override
   protected void onStart() {
       super.onStart();
       windowInfoTracker.addWindowLayoutInfoListener(
                this, Runnable::run, layoutStateChangeCallback);
   }

   @Override
   protected void onStop() {
       super.onStop();
       windowInfoTracker
           .removeWindowLayoutInfoListener(layoutStateChangeCallback);
   }

   class LayoutStateChangeCallback implements Consumer<WindowLayoutInfo> {
       @Override
       public void accept(WindowLayoutInfo newLayoutInfo) {
           SplitLayoutActivity.this.runOnUiThread( () -> {
               // Use newLayoutInfo to update the layout.
           });
       }
   }
}

Dukungan RxJava

Jika sudah menggunakan RxJava (versi 2 atau 3), Anda dapat memanfaatkan artefak yang memungkinkan Anda menggunakan Observable atau Flowable untuk mengumpulkan WindowLayoutInfo update tanpa menggunakan alur Kotlin.

Lapisan kompatibilitas yang disediakan oleh dependensi androidx.window:window-rxjava2 dan androidx.window:window-rxjava3 menyertakan metode WindowInfoTracker#windowLayoutInfoFlowable() dan WindowInfoTracker#windowLayoutInfoObservable(), yang memungkinkan aplikasi Anda menerima update WindowLayoutInfo, misalnya:

class RxActivity: AppCompatActivity {

    private lateinit var binding: ActivityRxBinding

    private var disposable: Disposable? = null
    private lateinit var observable: Observable<WindowLayoutInfo>

   @Override
   protected void onCreate(@Nullable Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);

       binding = ActivitySplitLayoutBinding.inflate(getLayoutInflater());
       setContentView(binding.getRoot());

        // Create a new observable.
        observable = WindowInfoTracker.getOrCreate(this@RxActivity)
            .windowLayoutInfoObservable(this@RxActivity)
   }

   @Override
   protected void onStart() {
       super.onStart();

        // Subscribe to receive WindowLayoutInfo updates.
        disposable?.dispose()
        disposable = observable
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe { newLayoutInfo ->
            // Use newLayoutInfo to update the layout.
        }
   }

   @Override
   protected void onStop() {
       super.onStop();

        // Dispose of the WindowLayoutInfo observable.
        disposable?.dispose()
   }
}

Fitur layar perangkat foldable

Class WindowLayoutInfo Jetpack WindowManager membuat fitur jendela tampilan tersedia sebagai daftar elemen DisplayFeature.

FoldingFeature adalah jenis DisplayFeature yang memberikan informasi tentang layar perangkat foldable, termasuk hal berikut:

  • state: Status lipatan perangkat, FLAT atau HALF_OPENED

  • orientation: Orientasi lipatan atau engsel, HORIZONTAL atau VERTICAL

  • occlusionType: Apakah lipatan atau engsel menyembunyikan bagian layar, NONE atau FULL

  • isSeparating: Apakah lipatan atau engsel membuat dua area tampilan logis, benar (true) atau salah (salah)

Perangkat foldable HALF_OPENED selalu melaporkan isSeparating sebagai benar karena layar dipisahkan menjadi dua area tampilan. Selain itu, isSeparating selalu bernilai benar (true) pada perangkat layar ganda saat aplikasi membentang pada kedua layar.

Properti bounds FoldingFeature (diwarisi dari DisplayFeature) mewakili persegi panjang pembatas fitur lipat seperti lipatan atau engsel. Batas dapat digunakan untuk memosisikan elemen di layar yang terkait dengan fitur:

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    ...
    lifecycleScope.launch(Dispatchers.Main) {
        lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
            // Safely collects from WindowInfoTracker when the lifecycle is
            // STARTED and stops collection when the lifecycle is STOPPED.
            WindowInfoTracker.getOrCreate(this@MainActivity)
                .windowLayoutInfo(this@MainActivity)
                .collect { layoutInfo ->
                    // New posture information.
                    val foldingFeature = layoutInfo.displayFeatures
                        .filterIsInstance<FoldingFeature>()
                        .firstOrNull()
                    // Use information from the foldingFeature object.
                }

        }
    }
}

Java

private WindowInfoTrackerCallbackAdapter windowInfoTracker;
private final LayoutStateChangeCallback layoutStateChangeCallback =
                new LayoutStateChangeCallback();

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    ...
    windowInfoTracker =
            new WindowInfoTrackerCallbackAdapter(WindowInfoTracker.getOrCreate(this));
}

@Override
protected void onStart() {
    super.onStart();
    windowInfoTracker.addWindowLayoutInfoListener(
            this, Runnable::run, layoutStateChangeCallback);
}

@Override
protected void onStop() {
    super.onStop();
    windowInfoTracker.removeWindowLayoutInfoListener(layoutStateChangeCallback);
}

class LayoutStateChangeCallback implements Consumer<WindowLayoutInfo> {
    @Override
    public void accept(WindowLayoutInfo newLayoutInfo) {
        // Use newLayoutInfo to update the Layout.
        List<DisplayFeature> displayFeatures = newLayoutInfo.getDisplayFeatures();
        for (DisplayFeature feature : displayFeatures) {
            if (feature instanceof FoldingFeature) {
                // Use information from the feature object.
            }
        }
    }
}

Postur mode di atas meja

Dengan menggunakan informasi yang disertakan dalam objek FoldingFeature, aplikasi Anda dapat mendukung postur seperti di atas meja, dengan ponsel berada di permukaan, engsel berada dalam posisi horizontal, dan layar perangkat foldable dibuka setengah.

Postur di atas meja menawarkan kenyamanan kepada pengguna dalam mengoperasikan ponsel tanpa memegang ponsel. Postur di atas meja sangat cocok untuk menonton media, mengambil foto, dan melakukan panggilan video.

Gambar 1. Aplikasi pemutar video dalam postur di atas meja.

Gunakan FoldingFeature.State dan FoldingFeature.Orientation untuk menentukan apakah perangkat dalam postur mode di atas meja:

Kotlin


fun isTableTopPosture(foldFeature : FoldingFeature?) : Boolean {
    contract { returns(true) implies (foldFeature != null) }
    return foldFeature?.state == FoldingFeature.State.HALF_OPENED &&
            foldFeature.orientation == FoldingFeature.Orientation.HORIZONTAL
}

Java


boolean isTableTopPosture(FoldingFeature foldFeature) {
    return (foldFeature != null) &&
           (foldFeature.getState() == FoldingFeature.State.HALF_OPENED) &&
           (foldFeature.getOrientation() == FoldingFeature.Orientation.HORIZONTAL);
}

Setelah mengetahui bahwa perangkat dalam postur di atas meja, perbarui tata letak aplikasi Anda. Untuk aplikasi media, hal ini biasanya berarti menempatkan pemutaran di paruh atas dan kontrol posisi serta konten tambahan tepat di bawahnya untuk pengalaman menonton atau mendengarkan secara handsfree.

Di Android 15 (API level 35) dan yang lebih baru, Anda dapat memanggil API sinkron untuk mendeteksi apakah perangkat mendukung postur di atas meja, terlepas dari status perangkat saat ini.

API ini menyediakan daftar postur yang didukung oleh perangkat. Jika daftar berisi postur mode di atas meja, Anda dapat memisahkan tata letak aplikasi untuk mendukung postur tersebut dan menjalankan pengujian A/B di UI aplikasi untuk tata letak mode di atas meja dan layar penuh.

Kotlin

if (WindowSdkExtensions.getInstance().extensionsVersion >= 6) {
    val postures = WindowInfoTracker.getOrCreate(context).supportedPostures
    if (postures.contains(TABLE_TOP)) {
        // Device supports tabletop posture.
   }
}

Java

if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 6) {
    List<SupportedPosture> postures = WindowInfoTracker.getOrCreate(context).getSupportedPostures();
    if (postures.contains(SupportedPosture.TABLETOP)) {
        // Device supports tabletop posture.
    }
}

Contoh

Postur buku

Fitur unik lainnya dari perangkat foldable adalah postur buku, dengan perangkat terbuka setengah dan engsel vertikal. Postur buku sangat cocok untuk membaca e-book. Dengan tata letak dua halaman pada perangkat foldable layar besar yang dapat dilipat dan terbuka seperti buku, postur buku menciptakan pengalaman membaca buku sungguhan.

Mode ini juga dapat digunakan untuk fotografi jika Anda ingin mengambil rasio aspek yang berbeda sambil mengambil gambar secara handsfree.

Terapkan postur buku dengan teknik yang sama dengan yang digunakan untuk postur di atas meja. Satu-satunya perbedaan adalah kode harus memeriksa apakah orientasi fitur lipat adalah vertikal, bukan horizontal:

Kotlin

fun isBookPosture(foldFeature : FoldingFeature?) : Boolean {
    contract { returns(true) implies (foldFeature != null) }
    return foldFeature?.state == FoldingFeature.State.HALF_OPENED &&
            foldFeature.orientation == FoldingFeature.Orientation.VERTICAL
}

Java

boolean isBookPosture(FoldingFeature foldFeature) {
    return (foldFeature != null) &&
           (foldFeature.getState() == FoldingFeature.State.HALF_OPENED) &&
           (foldFeature.getOrientation() == FoldingFeature.Orientation.VERTICAL);
}

Perubahan ukuran jendela

Area tampilan aplikasi dapat berubah sebagai akibat dari perubahan konfigurasi perangkat, misalnya, saat perangkat dilipat atau dibentangkan, diputar, atau jendela diubah ukurannya dalam mode multi-aplikasi.

Class WindowMetricsCalculator Jetpack WindowManager memungkinkan Anda mengambil metrik jendela saat ini dan maksimum. Seperti platform WindowMetrics yang diperkenalkan di API level 30, WindowManager WindowMetrics menyediakan batas jendela, tetapi API-nya kompatibel dengan versi lama sampai dengan API level 14.

Lihat Menggunakan class ukuran jendela.

Referensi lainnya

Contoh

  • Jetpack WindowManager: Contoh cara menggunakan library Jetpack WindowManager
  • Jetcaster : Implementasi postur mode di atas meja dengan Compose

Codelab