Mengoptimalkan aplikasi kamera di perangkat foldable dengan Jetpack WindowManager

1. Sebelum memulai

Apa yang istimewa dari perangkat foldable?

Perangkat foldable adalah inovasi yang hanya terjadi sekali dalam satu generasi. Perangkat foldable memberikan pengalaman unik, dan disertai peluang unik untuk menyenangkan pengguna Anda dengan fitur yang berbeda seperti UI dalam mode di atas meja untuk penggunaan handsfree.

Prasyarat

  • Pengetahuan dasar tentang pengembangan aplikasi Android
  • Pengetahuan dasar framework Injeksi Dependensi dengan Hilt

Yang akan Anda bangun

Dalam codelab ini, Anda membangun aplikasi kamera dengan tata letak yang dioptimalkan untuk perangkat foldable.

c5e52933bcd81859.png

Anda akan mulai dengan aplikasi kamera dasar yang tidak bereaksi terhadap postur perangkat apa pun atau memanfaatkan kamera belakang yang lebih baik untuk mendapatkan hasil selfie yang lebih baik. Anda memperbarui kode sumber untuk memindahkan pratinjau ke layar yang lebih kecil saat perangkat dibentangkan dan bereaksi terhadap ponsel yang diatur dalam mode di atas meja.

Meskipun aplikasi kamera adalah kasus penggunaan yang paling praktis untuk API ini, kedua fitur yang Anda pelajari dalam codelab ini dapat diterapkan ke aplikasi apa pun.

Yang akan Anda pelajari

  • Cara menggunakan Jetpack Window Manager untuk bereaksi terhadap perubahan postur
  • Cara memindahkan aplikasi ke layar perangkat foldable yang lebih kecil

Yang akan Anda butuhkan

  • Versi terbaru Android Studio
  • Perangkat foldable atau emulator perangkat foldable

2. Memulai persiapan

Mendapatkan kode awal

  1. Jika sudah menginstal Git, Anda cukup menjalankan perintah di bawah ini. Untuk memeriksa apakah Git sudah diinstal, ketik git --version di terminal atau command line dan pastikan Git dijalankan dengan benar.
git clone https://github.com/android/large-screen-codelabs.git
  1. Opsional: Jika tidak memiliki Git, Anda dapat mengklik tombol berikut untuk mendownload semua kode untuk codelab ini:

Membuka modul pertama

  • Di Android Studio, buka modul pertama di /step1.

Screenshot Android Studio yang menampilkan kode yang terkait dengan codelab ini

Jika Anda diminta untuk menggunakan versi Gradle terbaru, lanjutkan, lalu update.

3. Menjalankan dan mengamati

  1. Jalankan kode pada modul step1.

Seperti yang Anda lihat, ini adalah aplikasi kamera sederhana. Anda dapat beralih antara kamera depan dan belakang serta menyesuaikan rasio aspek. Namun, tombol pertama dari kiri saat ini tidak memiliki fungsi apa pun. Hal ini akan menjadi titik entri untuk mode Selfie Belakang.

149e3f9841af7726.png

  1. Sekarang, coba letakkan perangkat dalam posisi setengah terbuka, di mana engselnya tidak sepenuhnya lurus atau tertutup, tetapi membentuk sudut 90 derajat.

Seperti yang Anda lihat, aplikasi tidak merespons postur perangkat yang berbeda dan tata letaknya juga tidak berubah, sehingga engsel berada di tengah jendela bidik.

4. Mempelajari Jetpack WindowManager

Library Jetpack WindowManager membantu developer aplikasi membangun pengalaman yang dioptimalkan untuk perangkat foldable. Library ini berisi class FoldingFeature yang menjelaskan lipatan di layar yang fleksibel atau engsel di antara dua panel layar fisik. API-nya memberikan akses ke informasi penting yang berkaitan dengan perangkat:

Class FoldingFeature berisi informasi tambahan, seperti occlusionType() atau isSeparating(), tetapi codelabe ini tidak membahasnya secara mendalam.

Mulai versi 1.2.0-beta01, library ini menggunakan WindowAreaController, sebuah API yang memungkinkan Mode Tampilan Belakang memindahkan jendela saat ini ke layar yang sejajar dengan kamera belakang, yang sangat cocok untuk berfoto selfie menggunakan kamera belakang serta banyak kasus penggunaan lainnya.

Menambahkan dependensi

  • Untuk menggunakan Jetpack WindowManager di aplikasi Anda, tambahkan dependensi berikut ke file build.gradle level modul Anda:

step1/build.gradle

def work_version = '1.2.0'
implementation "androidx.window:window:$work_version"
implementation "androidx.window:window-java:$work_version"
implementation "androidx.window:window-core:$work_version"

Sekarang Anda dapat mengakses class FoldingFeature dan WindowAreaController di aplikasi Anda. Gunakan kedua class tersebut untuk membangun pengalaman kamera terbaik pada perangkat foldable.

5. Mengimplementasikan mode Selfie Belakang

Mulai dengan mode Tampilan Belakang.

API yang memungkinkan mode ini adalah WindowAreaController, yang memberikan informasi dan perilaku terkait perpindahan jendela di antara layar atau area layar pada perangkat.

API ini memungkinkan Anda membuat kueri daftar WindowAreaInfo yang saat ini tersedia untuk interaksi.

Dengan menggunakan WindowAreaInfo, Anda dapat mengakses WindowAreaSession, sebuah antarmuka untuk menampilkan fitur area jendela aktif dan status ketersediaan untuk WindowAreaCapability. tertentu

  1. Deklarasikan variabel ini di MainActivity Anda:

step1/MainActivity.kt

private lateinit var windowAreaController: WindowAreaController
private lateinit var displayExecutor: Executor
private var rearDisplaySession: WindowAreaSession? = null
private var rearDisplayWindowAreaInfo: WindowAreaInfo? = null
private var rearDisplayStatus: WindowAreaCapability.Status =
    WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED
private val rearDisplayOperation = WindowAreaCapability.Operation.OPERATION_TRANSFER_ACTIVITY_TO_AREA
  1. Dan inisialisasi variabel tersebut dalam metode onCreate():

step1/MainActivity.kt

displayExecutor = ContextCompat.getMainExecutor(this)
windowAreaController = WindowAreaController.getOrCreate()

lifecycleScope.launch(Dispatchers.Main) {
  lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
    windowAreaController.windowAreaInfos
      .map{info->info.firstOrNull{it.type==WindowAreaInfo.Type.TYPE_REAR_FACING}}
      .onEach { info -> rearDisplayWindowAreaInfo = info }
      .map{it?.getCapability(rearDisplayOperation)?.status?:  WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED }
      .distinctUntilChanged()
      .collect {
           rearDisplayStatus = it
           updateUI()
      }
  }
}
  1. Sekarang, implementasikan fungsi updateUI() untuk mengaktifkan atau menonaktifkan tombol selfie belakang, bergantung pada status saat ini:

step1/MainActivity.kt

private fun updateUI() {
    if(rearDisplaySession != null) {
        binding.rearDisplay.isEnabled = true
        // A session is already active, clicking on the button will disable it
    } else {
        when(rearDisplayStatus) {
            WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED -> {
                binding.rearDisplay.isEnabled = false
                // RearDisplay Mode is not supported on this device"
            }
            WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNAVAILABLE -> {
                binding.rearDisplay.isEnabled = false
                // RearDisplay Mode is not currently available
            }
            WindowAreaCapability.Status.WINDOW_AREA_STATUS_AVAILABLE -> {
                binding.rearDisplay.isEnabled = true
                // You can enable RearDisplay Mode
            }
            WindowAreaCapability.Status.WINDOW_AREA_STATUS_ACTIVE -> {
                binding.rearDisplay.isEnabled = true
                // You can disable RearDisplay Mode
            }
            else -> {
                binding.rearDisplay.isEnabled = false
                // RearDisplay status is unknown
            }
        }
    }
}

Langkah terakhir ini bersifat opsional, tetapi sangat berguna untuk mempelajari semua kemungkinan status WindowAreaCapability.

  1. Sekarang, terapkan fungsi toggleRearDisplayMode, yang akan menutup sesi, jika kemampuan sudah aktif, atau panggil fungsi transferActivityToWindowArea:

step1/CameraViewModel.kt

private fun toggleRearDisplayMode() {
    if(rearDisplayStatus == WindowAreaCapability.Status.WINDOW_AREA_STATUS_ACTIVE) {
        if(rearDisplaySession == null) {
            rearDisplaySession = rearDisplayWindowAreaInfo?.getActiveSession(rearDisplayOperation)
        }
        rearDisplaySession?.close()
    } else {
        rearDisplayWindowAreaInfo?.token?.let { token ->
            windowAreaController.transferActivityToWindowArea(
                token = token,
                activity = this,
                executor = displayExecutor,
                windowAreaSessionCallback = this
            )
        }
    }
}

Perhatikan penggunaan MainActivity sebagai WindowAreaSessionCallback.

Rear Display API berfungsi dengan pendekatan pemroses: saat Anda meminta untuk memindahkan konten ke layar lain, Anda akan memulai sesi yang ditampilkan melalui metode onSessionStarted() pemroses. Jika ingin kembali ke layar dalam (dan yang lebih besar), tutup sesi, dan Anda mendapatkan konfirmasi dalam metode onSessionEnded(). Untuk membuat pemroses seperti itu, Anda perlu mengimplementasikan antarmuka WindowAreaSessionCallback.

  1. Ubah deklarasi MainActivity agar mengimplementasikan antarmuka WindowAreaSessionCallback:

step1/MainActivity.kt

class MainActivity : AppCompatActivity(), WindowAreaSessionCallback

Sekarang, terapkan metode onSessionStarted dan onSessionEnded di dalam MainActivity. Metode callback tersebut sangat berguna untuk mendapatkan notifikasi tentang status sesi dan mengupdate aplikasi.

Untuk kali ini, agar lebih mudah, cukup periksa body fungsi jika ada error dan catat statusnya.

step1/MainActivity.kt

override fun onSessionEnded(t: Throwable?) {
    if(t != null) {
        Log.d("Something was broken: ${t.message}")
    }else{
        Log.d("rear session ended")
    }
}

override fun onSessionStarted(session: WindowAreaSession) {
    Log.d("rear session started [session=$session]")
}
  1. Bangun dan jalankan aplikasi. Jika Anda kemudian membentangkan perangkat dan mengetuk tombol layar belakang, Anda akan melihat pesan seperti ini:

ba878f120b7c8d58.png

  1. Pilih "Switch screens now" untuk melihat konten Anda dipindahkan ke layar luar.

6. Mengimplementasikan Mode di atas meja

Sekarang saatnya membuat aplikasi Anda fold-aware: pindahkan konten ke samping atau ke atas engsel perangkat berdasarkan orientasi lipatan. Untuk melakukannya, Anda akan bertindak di dalam FoldingStateActor sehingga kode Anda dipisahkan dari Activity agar lebih mudah dibaca.

Bagian inti dari API ini terdiri dari antarmuka WindowInfoTracker, yang dibuat dengan metode statis yang memerlukan Activity:

step1/CameraCodelabDependencies.kt

@Provides
fun provideWindowInfoTracker(activity: Activity) =
        WindowInfoTracker.getOrCreate(activity)

Anda tidak perlu menulis kode ini karena sudah ada, tetapi akan berguna untuk memahami bagaimana WindowInfoTracker dibangun.

  1. Untuk memproses perubahan jendela apa pun, proses perubahan ini dalam metode onResume() dari Activity Anda:

step1/MainActivity.kt

lifecycleScope.launch {
    foldingStateActor.checkFoldingState(
         this@MainActivity, 
         binding.viewFinder
    )
}
  1. Sekarang, buka file FoldingStateActor, karena sudah saatnya memasukkan metode checkFoldingState().

Seperti yang sudah Anda lihat, metode ini berjalan dalam fase RESUMED dari Activity Anda, dan memanfaatkan WindowInfoTracker untuk memproses perubahan tata letak apa pun.

step1/FoldingStateActor.kt

windowInfoTracker.windowLayoutInfo(activity)
      .collect { newLayoutInfo ->
         activeWindowLayoutInfo = newLayoutInfo
         updateLayoutByFoldingState(cameraViewfinder)
      }

Dengan menggunakan antarmuka WindowInfoTracker, Anda dapat memanggil windowLayoutInfo() untuk mengumpulkan Flow dari WindowLayoutInfo yang berisi semua informasi yang tersedia di DisplayFeature.

Langkah terakhir adalah bereaksi terhadap perubahan ini dan memindahkan konten yang sesuai. Anda melakukan hal ini di dalam metode updateLayoutByFoldingState(), selangkah demi selangkah.

  1. Pastikan activityLayoutInfo berisi beberapa properti DisplayFeature, dan setidaknya salah satunya adalah FoldingFeature. Jika tidak, Anda tidak perlu melakukan apa pun:

step1/FoldingStateActor.kt

val foldingFeature = activeWindowLayoutInfo?.displayFeatures
            ?.firstOrNull { it is FoldingFeature } as FoldingFeature?
            ?: return
  1. Hitung posisi lipatan untuk memastikan posisi perangkat memengaruhi tata letak Anda dan tidak berada di luar batas hierarki Anda:

step1/FoldingStateActor.kt

val foldPosition = FoldableUtils.getFeaturePositionInViewRect(
            foldingFeature,
            cameraViewfinder.parent as View
        ) ?: return

Sekarang, Anda yakin telah memiliki FoldingFeature yang memengaruhi tata letak, jadi Anda perlu memindahkan konten Anda.

  1. Periksa apakah FoldingFeature menampilkan HALF_OPEN atau cukup pulihkan posisi konten Anda. Jika yang ditampilkan adalah HALF_OPEN, Anda perlu menjalankan pemeriksaan lain dan bertindak berbeda berdasarkan orientasi lipatan:

step1/FoldingStateActor.kt

if (foldingFeature.state == FoldingFeature.State.HALF_OPENED) {
    when (foldingFeature.orientation) {
        FoldingFeature.Orientation.VERTICAL -> {
            cameraViewfinder.moveToRightOf(foldPosition)
        }
        FoldingFeature.Orientation.HORIZONTAL -> {
            cameraViewfinder.moveToTopOf(foldPosition)
        }
    }
} else {
    cameraViewfinder.restore()
}

Jika lipatannya VERTICAL, Anda memindahkan konten ke kanan. Jika tidak, Anda memindahkannya ke atas posisi lipatan.

  1. Bangun dan jalankan aplikasi Anda, lalu bentangkan perangkat Anda dan tempatkan dalam mode di atas meja untuk melihat konten bergerak sebagaimana yang dimaksudkan.

7. Selamat!

Dalam codelab ini, Anda telah mempelajari beberapa kemampuan unik untuk perangkat foldable, seperti Mode Tampilan Belakang atau Mode di atas meja, serta cara menggunakannya menggunakan Jetpack WindowManager.

Anda siap menerapkan pengalaman pengguna yang luar biasa untuk aplikasi kamera Anda.

Bacaan lebih lanjut

Referensi