1. Sebelum memulai
Codelab praktis ini akan mengajari Anda dasar-dasar pengembangan untuk perangkat dua layar dan perangkat foldable. Setelah selesai, aplikasi Anda dapat mendukung perangkat foldable seperti Pixel Fold, Microsoft Surface Duo, Samsung Galaxy Z Fold 5, dll.
Prasyarat
Untuk menyelesaikan codelab ini, Anda memerlukan:
- Pengalaman membangun aplikasi Android
- Pengalaman dengan Activity, Fragment, View binding, dan xml-layouts
Yang akan Anda lakukan
Membuat aplikasi sederhana yang:
- Menampilkan fitur perangkat
- Mendeteksi saat aplikasi berjalan di perangkat foldable atau perangkat dua layar
- Menentukan status perangkat
- Menggunakan Jetpack WindowManager untuk menggunakan perangkat faktor bentuk baru.
Yang Anda butuhkan
- Android Studio Arctic Fox atau yang lebih tinggi
- Perangkat foldable atau emulator
Android Emulator v30.0.6+ menyertakan dukungan perangkat foldable dengan sensor engsel virtual dan tampilan 3D. Ada beberapa emulator perangkat foldable yang dapat Anda gunakan, seperti yang ditunjukkan pada gambar di bawah:
- Jika ingin menggunakan emulator perangkat dua layar, Anda dapat mendownload emulator Microsoft Surface Duo untuk platform Anda (Windows, MacOS, atau GNU/Linux).
2. Perangkat satu layar vs perangkat foldable
Perangkat foldable menawarkan ukuran layar yang lebih besar dan antarmuka pengguna yang lebih fleksibel daripada yang sebelumnya tersedia di perangkat seluler. Saat dilipat, ukuran perangkat ini sering kali lebih kecil daripada ukuran tablet pada umumnya, sehingga lebih portabel dan fungsional.
Pada saat codelab ini ditulis, ada dua jenis perangkat foldable:
- Perangkat foldable satu layar, dengan satu layar yang dapat dilipat. Pengguna dapat menjalankan beberapa aplikasi di layar yang sama secara bersamaan menggunakan mode
multi-window
. - Perangkat foldable dua layar, dengan dua layar yang digabungkan menggunakan engsel. Perangkat ini juga dapat dilipat, tetapi memiliki dua area tampilan logis yang berbeda.
Seperti tablet dan perangkat seluler satu layar lainnya, perangkat foldable juga dapat:
- Menjalankan satu aplikasi di salah satu region tampilan.
- Menjalankan dua aplikasi secara berdampingan, masing-masing di region tampilan yang berbeda (menggunakan mode
multi-window
).
Tidak seperti perangkat satu layar, perangkat foldable juga mendukung berbagai postur. Postur dapat digunakan untuk menampilkan konten dengan berbagai cara.
Perangkat foldable dapat menawarkan postur bentang yang berbeda saat aplikasi dibentangkan (ditampilkan) ke seluruh area tampilan (menggunakan semua area tampilan di perangkat foldable dua layar).
Perangkat foldable juga dapat menawarkan postur lipat, seperti mode di atas meja, sehingga Anda bisa mendapatkan pemisahan logis antara bagian layar yang datar dan bagian yang miring menghadap Anda, dan mode semi lipat yang memungkinkan Anda memvisualisasikan konten seolah-olah perangkat menggunakan gadget stan.
3. Jetpack WindowManager
Library Jetpack WindowManager membantu developer aplikasi mendukung faktor bentuk perangkat baru dan menyediakan platform API umum untuk berbagai fitur WindowManager pada versi platform lama dan baru.
Fitur utama
Jetpack WindowManager versi 1.1.0 berisi class FoldingFeature
yang mendeskripsikan lipatan di layar yang fleksibel atau engsel di antara dua panel layar fisik. API-nya memberikan akses ke informasi penting yang berkaitan dengan perangkat:
state()
: Memberitahukan postur perangkat saat ini dari daftar postur yang ada (FLAT
danHALF_OPENED
)isSeparating()
: Menghitung apakahFoldingFeature
harus dianggap sebagai pemisah jendela menjadi beberapa area fisik yang dapat dilihat oleh pengguna secara terpisah secara logisocclusionType()
: Menghitung mode oklusi untuk menentukan apakahFoldingFeature
menempati bagian jendela.orientation()
: MenampilkanFoldingFeature.Orientation.HORIZONTAL
jika lebarFoldingFeature
lebih besar dari tinggi; jika tidak, menampilkanFoldingFeature.Orientation.VERTICAL
.bounds()
: Memberikan instanceRect
yang berisi batas fitur perangkat, misalnya, batas engsel fisik.
Dengan menggunakan antarmuka WindowInfoTracker
, Anda dapat mengakses windowLayoutInfo()
untuk mengumpulkan Flow
dari WindowLayoutInfo
yang berisi semua DisplayFeature
yang tersedia.
4. Penyiapan
Buat project baru dan pilih template "Empty Activity":
Biarkan semua parameter seperti default-nya.
Mendeklarasikan dependensi
Untuk menggunakan Jetpack WindowManager, tambahkan dependensi dalam file build.gradle
untuk aplikasi atau modul:
app/build.gradle
dependencies {
ext.windowmanager_version = "1.1.0"
implementation "androidx.window:window:$windowmanager_version"
androidTestImplementation "androidx.window:window-testing:$windowmanager_version"
// Needed to use lifecycleScope to collect the WindowLayoutInfo flow
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.6.2"
}
Menggunakan WindowManager
Fitur jendela dapat diakses melalui antarmuka WindowInfoTracker
WindowManager.
Buka file sumber MainActivity.kt
dan panggil WindowInfoTracker.getOrCreate(this@MainActivity)
untuk melakukan inisialisasi instance WindowInfoTracker
yang terkait dengan aktivitas saat ini:
MainActivity.kt
import androidx.window.layout.WindowInfoTracker
private lateinit var windowInfoTracker: WindowInfoTracker
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
windowInfoTracker = WindowInfoTracker.getOrCreate(this@MainActivity)
}
Dengan instance WindowInfoTracker
, dapatkan informasi tentang status jendela perangkat yang berlaku saat ini.
5. Menyiapkan UI aplikasi
Dari Jetpack WindowManager, dapatkan informasi tentang metrik jendela, tata letak, dan konfigurasi layar. Tampilkan informasi ini di tata letak aktivitas utama, dengan menggunakan TextView
untuk setiap elemen tersebut.
Buat ConstraintLayout
, dengan tiga TextView
, yang berpusat di layar.
Buka file activity_main.xml
dan tempelkan konten berikut:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<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/constraint_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/window_metrics"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="20dp"
tools:text="Window metrics"
android:textSize="20sp"
app:layout_constraintBottom_toTopOf="@+id/layout_change"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" />
<TextView
android:id="@+id/layout_change"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="20dp"
tools:text="Layout change"
android:textSize="20sp"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toTopOf="@+id/configuration_changed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/window_metrics" />
<TextView
android:id="@+id/configuration_changed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="20dp"
tools:text="Using one logic/physical display - unspanned"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/layout_change" />
</androidx.constraintlayout.widget.ConstraintLayout>
Sekarang kita akan menghubungkan elemen UI ini dalam kode menggunakan view binding. Untuk melakukannya, kita mulai mengaktifkannya di file build.gradle
aplikasi:
app/build.gradle
android {
// Other configurations
buildFeatures {
viewBinding true
}
}
Sinkronkan project Gradle seperti yang disarankan oleh Android Studio dan gunakan view binding di MainActivity.kt
dengan menggunakan kode berikut:
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var windowInfoTracker: WindowInfoTracker
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
windowInfoTracker = WindowInfoTracker.getOrCreate(this@MainActivity)
}
}
6. Memvisualisasikan informasi WindowMetrics
Di metode onCreate
MainActivity
, panggil fungsi untuk mendapatkan dan menampilkan informasi WindowMetrics
. Tambahkan panggilan obtainWindowMetrics()
di dalam metode onCreate
:
MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
windowInfoTracker = WindowInfoTracker.getOrCreate(this@MainActivity)
obtainWindowMetrics()
}
Implementasikan metode obtainWindowMetrics
:
MainActivity.kt
import androidx.window.layout.WindowMetricsCalculator
private fun obtainWindowMetrics() {
val wmc = WindowMetricsCalculator.getOrCreate()
val currentWM = wmc.computeCurrentWindowMetrics(this).bounds.flattenToString()
val maximumWM = wmc.computeMaximumWindowMetrics(this).bounds.flattenToString()
binding.windowMetrics.text =
"CurrentWindowMetrics: ${currentWM}\nMaximumWindowMetrics: ${maximumWM}"
}
Dapatkan instance WindowMetricsCalculator
melalui fungsi pendampingnya getOrCreate()
.
Dengan menggunakan instance WindowMetricsCalculator
tersebut, tetapkan informasi ke dalam windowMetrics
TextView
. Gunakan nilai yang ditampilkan oleh fungsi computeCurrentWindowMetrics.bounds
dan computeMaximumWindowMetrics.bounds
.
Nilai ini memberikan informasi berguna tentang metrik area yang ditempati oleh jendela.
Jalankan aplikasi. Di emulator perangkat dua layar (seperti gambar di bawah), Anda akan mendapatkan CurrentWindowMetrics
yang sesuai dengan dimensi perangkat yang dicerminkan emulator. Anda juga dapat melihat metrik saat aplikasi berjalan dalam mode satu layar:
Jika aplikasi dibentangkan ke seluruh layar, metrik jendela akan berubah seperti pada gambar di bawah, sehingga kini menampilkan area jendela yang lebih besar yang digunakan oleh aplikasi:
Metrik jendela saat ini dan maksimum memiliki nilai yang sama, karena aplikasi selalu berjalan dan menggunakan seluruh area tampilan yang tersedia, baik di satu layar maupun dua layar.
Di emulator perangkat foldable dengan lipatan horizontal, nilai akan berbeda saat aplikasi membentang di seluruh layar fisik dan saat aplikasi berjalan dalam mode multi-aplikasi:
Seperti yang dapat Anda lihat pada gambar di sebelah kiri, kedua metrik memiliki nilai yang sama, karena aplikasi berjalan menggunakan seluruh area tampilan yang tersedia, yaitu tampilan saat ini dan layar maksimum.
Tetapi, pada gambar di sebelah kanan, dengan aplikasi berjalan dalam mode multi-aplikasi, Anda dapat melihat bagaimana metrik saat ini menampilkan dimensi area khusus yang ditempati aplikasi berjalan (bagian atas) pada mode layar terpisah, dan Anda juga dapat melihat bahwa metrik maksimum menampilkan area tampilan maksimum yang dimiliki perangkat.
Metrik yang disediakan oleh WindowMetricsCalculator
sangat berguna untuk menentukan area jendela yang digunakan atau dapat digunakan oleh aplikasi.
7. Memvisualisasikan informasi FoldingFeature
Sekarang, daftar untuk menerima perubahan tata letak jendela beserta karakteristik dan batas DisplayFeatures
emulator atau perangkat.
Untuk mengumpulkan informasi dari WindowInfoTracker#windowLayoutInfo()
, gunakan lifecycleScope
yang ditentukan untuk setiap objek Lifecycle
. Setiap coroutine yang diluncurkan dalam cakupan ini akan dibatalkan jika Lifecycle dihancurkan. Anda dapat mengakses cakupan coroutine siklus proses melalui properti lifecycle.coroutineScope
atau lifecycleOwner.lifecycleScope
.
Di metode onCreate
MainActivity
, panggil fungsi untuk mendapatkan dan menampilkan informasi WindowInfoTracker
. Awali dengan menambahkan panggilan onWindowLayoutInfoChange()
di dalam metode onCreate
:
MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
windowInfoTracker = WindowInfoTracker.getOrCreate(this@MainActivity)
obtainWindowMetrics()
onWindowLayoutInfoChange()
}
Gunakan implementasi fungsi tersebut untuk mendapatkan informasi setiap kali konfigurasi tata letak baru berubah.
Tentukan tanda tangan dan kerangka fungsi.
MainActivity.kt
private fun onWindowLayoutInfoChange() {
}
Dengan parameter yang diterima fungsi, WindowInfoTracker
, dapatkan data WindowLayoutInfo
-nya. WindowLayoutInfo
berisi daftar DisplayFeature
yang terletak dalam jendela. Misalnya, engsel atau lipatan layar dapat membentang di sepanjang jendela, yang mungkin masuk akal untuk memisahkan konten visual dan elemen interaktif menjadi dua grup (misalnya, detail daftar atau kontrol tampilan).
Hanya fitur yang ada dalam batas jendela saat ini yang dilaporkan. Posisi dan ukurannya dapat berubah jika jendela dipindahkan atau diubah ukurannya di layar.
Melalui lifecycleScope
yang ditentukan di dalam dependensi lifecycle-runtime-ktx
, dapatkan flow
WindowLayoutInfo
yang berisi daftar semua fitur tampilan. Tambahkan isi onWindowLayoutInfoChange
:
MainActivity.kt
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
private fun onWindowLayoutInfoChange() {
lifecycleScope.launch(Dispatchers.Main) {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
windowInfoTracker.windowLayoutInfo(this@MainActivity)
.collect { value ->
updateUI(value)
}
}
}
}
Fungsi updateUI
dipanggil dari collect
. Implementasikan fungsi ini untuk menampilkan dan mencetak informasi yang diterima dari flow
WindowLayoutInfo
. Periksa apakah data WindowLayoutInfo
memiliki fitur tampilan atau tidak. Jika ya, fitur tampilan akan berinteraksi dengan UI aplikasi. Jika data WindowLayoutInfo
tidak memiliki fitur tampilan, aplikasi akan berjalan di dalam perangkat atau mode satu layar atau dalam mode multi-aplikasi.
MainActivity.kt
import androidx.window.layout.WindowLayoutInfo
private fun updateUI(newLayoutInfo: WindowLayoutInfo) {
binding.layoutChange.text = newLayoutInfo.toString()
if (newLayoutInfo.displayFeatures.isNotEmpty()) {
binding.configurationChanged.text = "Spanned across displays"
} else {
binding.configurationChanged.text = "One logic/physical display - unspanned"
}
}
Jalankan aplikasi. Di emulator perangkat dua layar, Anda akan memiliki:
WindowLayoutInfo
kosong. Kolom ini memiliki List<DisplayFeature>
yang kosong. Namun, jika Anda memiliki emulator dengan engsel di tengah, mengapa Anda tidak mendapatkan informasi dari WindowManager?
WindowManager (melalui WindowInfoTracker
) akan memberikan data WindowLayoutInfo
(jenis fitur perangkat, batas fitur perangkat, dan postur perangkat) tepat saat aplikasi dibentangkan ke seluruh layar (fisik atau tidak). Jadi pada gambar sebelumnya, saat aplikasi berjalan pada mode satu layar, WindowLayoutInfo
menjadi kosong.
Dari informasi tersebut, Anda dapat mengetahui mode yang dijalankan oleh aplikasi (mode satu layar atau membentang di seluruh layar) sehingga Anda dapat membuat perubahan di UI/UX, dan memberikan pengalaman pengguna yang lebih baik yang disesuaikan dengan konfigurasi tertentu.
Pada perangkat yang tidak memiliki dua tampilan fisik (biasanya tidak memiliki engsel fisik), aplikasi dapat berjalan berdampingan menggunakan multi-aplikasi. Pada perangkat ini, saat aplikasi berjalan dalam mode multi-aplikasi, aplikasi akan bertindak seperti halnya pada satu layar di contoh sebelumnya. Dan saat aplikasi berjalan menggunakan semua layar, aplikasi akan bertindak sama seperti saat aplikasi dibentangkan. Lihat gambar berikutnya:
Jika aplikasi berjalan dalam mode multi-aplikasi, WindowManager akan menyediakan List<LayoutInfo>
kosong.
Singkatnya, Anda akan mendapatkan data WindowLayoutInfo
tepat saat aplikasi yang berjalan menempati semua tampilan logika, yang memotong fitur perangkat (lipatan atau engsel). Dalam kasus lain, Anda tidak akan mendapatkan informasi apa pun.
Apa yang akan terjadi saat Anda membentangkan aplikasi ke seluruh layar? Dalam emulator perangkat dua layar, WindowLayoutInfo
akan memiliki objek FoldingFeature
yang memberikan data tentang fitur perangkat: HINGE
, batas fitur tersebut (Rect
(0, 0 - 1434, 1800)), dan postur (status) perangkat (FLAT
).
Mari kita lihat arti setiap kolom:
type = TYPE_HINGE
: Emulator perangkat dua layar ini menduplikasi perangkat Surface Duo sesungguhnya yang memiliki engsel fisik, dan perangkat inilah yang dilaporkan WindowsManager.Bounds
[0, 0 - 1434, 1800]: Mewakili kotak pembatas fitur dalam jendela aplikasi di ruang koordinat jendela. Pada spesifikasi dimensi perangkat Surface Duo, Anda akan melihat bahwa engsel terletak di posisi yang tepat yang dilaporkan oleh batas ini (kiri, atas, kanan, bawah).State
: Ada dua nilai berbeda yang mewakili postur (status) perangkat.HALF_OPENED
: Engsel perangkat foldable berada di posisi tengah antara keadaan terbuka dan tertutup, dan ada sudut yang tidak datar antara bagian pada layar fleksibel atau antara panel layar fisik.FLAT
: Perangkat foldable sepenuhnya terbuka, ruang layar yang ditampilkan kepada pengguna dalam keadaan datar.
Emulator secara default terbuka 180 derajat sehingga postur yang ditampilkan oleh WindowManager adalah FLAT
.
Jika Anda mengubah postur emulator menggunakan opsi sensor Virtual ke postur Setengah Terbuka, WindowManager akan memberitahukan posisi baru kepada Anda: HALF_OPENED
.
Menggunakan WindowManager untuk menyesuaikan UI/UX
Seperti yang terlihat dalam gambar yang menunjukkan informasi tata letak jendela, informasi yang ditampilkan terpotong oleh fitur tampilan, hal yang sama juga terjadi di sini:
Ini bukan pengalaman pengguna yang optimal. Anda dapat menggunakan informasi yang disediakan oleh WindowManager untuk menyesuaikan UI/UX Anda.
Seperti yang Anda lihat sebelumnya, saat aplikasi dibentangkan ke seluruh region tampilan yang berbeda, aplikasi Anda juga bersimpangan dengan fitur perangkat, sehingga WindowManager menyediakan Informasi tata letak jendela sebagai status tampilan dan batas layar. Jadi, saat aplikasi dibentangkan adalah saat Anda perlu menggunakan informasi tersebut dan menyesuaikan UI/UX Anda.
Yang akan Anda lakukan berikutnya adalah menyesuaikan UI/UX yang saat ini berada di runtime saat aplikasi Anda dibentangkan sehingga tidak ada informasi penting yang terpotong atau tertutup oleh fitur tampilan. Anda akan membuat tampilan yang menduplikasi fitur tampilan perangkat, dan akan digunakan sebagai referensi untuk membatasi TextView
yang terpotong atau tertutup, sehingga tidak ada informasi yang hilang lagi.
Untuk tujuan pembelajaran, beri warna pada tampilan baru ini agar mudah diketahui bahwa tampilan ini secara spesifik berada di tempat yang sama dengan fitur tampilan perangkat yang sesungguhnya dan dengan dimensi yang sama.
Tambahkan tampilan baru yang akan Anda gunakan sebagai referensi fitur perangkat di activity_main.xml
:
activity_main.xml
<!-- It's not important where this view is placed by default, it will be positioned dynamically at runtime -->
<View
android:id="@+id/folding_feature"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@android:color/holo_red_dark"
android:visibility="gone"
tools:ignore="MissingConstraints" />
Di MainActivity.kt
, buka fungsi updateUI()
yang Anda gunakan untuk menampilkan informasi dari WindowLayoutInfo
yang ditentukan dan tambahkan panggilan fungsi baru dalam kasus if-else tempat fitur tampilan Anda berada:
MainActivity.kt
private fun updateUI(newLayoutInfo: WindowLayoutInfo) {
binding.layoutChange.text = newLayoutInfo.toString()
if (newLayoutInfo.displayFeatures.isNotEmpty()) {
binding.configurationChanged.text = "Spanned across displays"
alignViewToFoldingFeatureBounds(newLayoutInfo)
} else {
binding.configurationChanged.text = "One logic/physical display - unspanned"
}
}
Anda telah menambahkan fungsi alignViewToFoldingFeatureBounds
yang diterima sebagai parameter WindowLayoutInfo
.
Buat fungsi tersebut. Di dalam fungsi, buat ConstraintSet
untuk menerapkan batasan baru pada tampilan Anda. Lalu, dapatkan batas fitur tampilan menggunakan WindowLayoutInfo
. Karena WindowLayoutInfo
menampilkan daftar DisplayFeature
yang hanya merupakan antarmuka, transmisikan ke FoldingFeature
untuk mendapatkan akses ke semua informasi:
MainActivity.kt
import androidx.constraintlayout.widget.ConstraintSet
import androidx.window.layout.FoldingFeature
private fun alignViewToFoldingFeatureBounds(newLayoutInfo: WindowLayoutInfo) {
val constraintLayout = binding.constraintLayout
val set = ConstraintSet()
set.clone(constraintLayout)
// Get and translate the feature bounds to the View's coordinate space and current
// position in the window.
val foldingFeature = newLayoutInfo.displayFeatures[0] as FoldingFeature
val bounds = getFeatureBoundsInWindow(foldingFeature, binding.root)
// Rest of the code to be added in the following steps
}
Tentukan fungsi getFeatureBoundsInWindow()
untuk menerjemahkan batas fitur ke ruang koordinat tampilan dan posisi saat ini di jendela.
MainActivity.kt
import android.graphics.Rect
import android.view.View
import androidx.window.layout.DisplayFeature
/**
* Get the bounds of the display feature translated to the View's coordinate space and current
* position in the window. This will also include view padding in the calculations.
*/
private fun getFeatureBoundsInWindow(
displayFeature: DisplayFeature,
view: View,
includePadding: Boolean = true
): Rect? {
// Adjust the location of the view in the window to be in the same coordinate space as the feature.
val viewLocationInWindow = IntArray(2)
view.getLocationInWindow(viewLocationInWindow)
// Intersect the feature rectangle in window with view rectangle to clip the bounds.
val viewRect = Rect(
viewLocationInWindow[0], viewLocationInWindow[1],
viewLocationInWindow[0] + view.width, viewLocationInWindow[1] + view.height
)
// Include padding if needed
if (includePadding) {
viewRect.left += view.paddingLeft
viewRect.top += view.paddingTop
viewRect.right -= view.paddingRight
viewRect.bottom -= view.paddingBottom
}
val featureRectInView = Rect(displayFeature.bounds)
val intersects = featureRectInView.intersect(viewRect)
// Checks to see if the display feature overlaps with our view at all
if ((featureRectInView.width() == 0 && featureRectInView.height() == 0) ||
!intersects
) {
return null
}
// Offset the feature coordinates to view coordinate space start point
featureRectInView.offset(-viewLocationInWindow[0], -viewLocationInWindow[1])
return featureRectInView
}
Anda dapat menggunakan informasi tentang batas fitur tampilan untuk menyetel ukuran tinggi yang benar bagi tampilan referensi dan memindahkannya dengan semestinya.
Kode lengkap untuk alignViewToFoldingFeatureBounds
adalah:
MainActivity.kt - alignViewToFoldingFeatureBounds
private fun alignViewToFoldingFeatureBounds(newLayoutInfo: WindowLayoutInfo) {
val constraintLayout = binding.constraintLayout
val set = ConstraintSet()
set.clone(constraintLayout)
// Get and Translate the feature bounds to the View's coordinate space and current
// position in the window.
val foldingFeature = newLayoutInfo.displayFeatures[0] as FoldingFeature
val bounds = getFeatureBoundsInWindow(foldingFeature, binding.root)
bounds?.let { rect ->
// Some devices have a 0px width folding feature. We set a minimum of 1px so we
// can show the view that mirrors the folding feature in the UI and use it as reference.
val horizontalFoldingFeatureHeight = (rect.bottom - rect.top).coerceAtLeast(1)
val verticalFoldingFeatureWidth = (rect.right - rect.left).coerceAtLeast(1)
// Sets the view to match the height and width of the folding feature
set.constrainHeight(
R.id.folding_feature,
horizontalFoldingFeatureHeight
)
set.constrainWidth(
R.id.folding_feature,
verticalFoldingFeatureWidth
)
set.connect(
R.id.folding_feature, ConstraintSet.START,
ConstraintSet.PARENT_ID, ConstraintSet.START, 0
)
set.connect(
R.id.folding_feature, ConstraintSet.TOP,
ConstraintSet.PARENT_ID, ConstraintSet.TOP, 0
)
if (foldingFeature.orientation == FoldingFeature.Orientation.VERTICAL) {
set.setMargin(R.id.folding_feature, ConstraintSet.START, rect.left)
set.connect(
R.id.layout_change, ConstraintSet.END,
R.id.folding_feature, ConstraintSet.START, 0
)
} else {
// FoldingFeature is Horizontal
set.setMargin(
R.id.folding_feature, ConstraintSet.TOP,
rect.top
)
set.connect(
R.id.layout_change, ConstraintSet.TOP,
R.id.folding_feature, ConstraintSet.BOTTOM, 0
)
}
// Set the view to visible and apply constraints
set.setVisibility(R.id.folding_feature, View.VISIBLE)
set.applyTo(constraintLayout)
}
}
Sekarang, TextView
yang bertentangan dengan fitur tampilan perangkat mempertimbangkan lokasi fitur tersebut sehingga kontennya tidak pernah terpotong atau tertutup:
Di emulator perangkat dua layar (di atas, kiri), Anda dapat melihat cara TextView
yang menampilkan konten di berbagai tampilan dan yang terpotong oleh engsel tidak terpotong lagi sehingga tidak ada informasi yang hilang.
Di emulator perangkat foldable (di atas, kanan), Anda akan melihat garis merah terang yang mewakili lokasi fitur tampilan lipat, dan TextView
kini ditempatkan di bawah fitur. Jadi, jika perangkat dilipat (misalnya, 90 derajat dalam postur laptop), tidak ada informasi yang terpengaruh oleh fitur ini.
Jika Anda bertanya-tanya di mana fitur tampilan di emulator perangkat dua layar (karena ini adalah perangkat jenis engsel), tampilan yang mewakili fitur tersebut tertutup engsel. Namun, jika aplikasi berubah dari posisi membentang ke posisi tidak membentang, Anda akan melihat posisinya sama dengan fitur yang memiliki tinggi dan lebar yang benar.
8. Artefak Jetpack WindowManager lainnya
Selain artefak utama, WindowManager juga dilengkapi dengan artefak berguna lainnya yang akan membantu Anda berinteraksi dengan komponen secara berbeda, dengan mempertimbangkan lingkungan saat ini yang Anda gunakan saat membangun aplikasi.
Artefak Java
Jika Anda menggunakan bahasa pemrograman Java, bukan Kotlin, atau jika memproses peristiwa melalui callback adalah pendekatan yang lebih baik untuk arsitektur Anda, artefak Java WindowManager dapat berguna karena menyediakan API yang cocok untuk Java guna mendaftarkan dan membatalkan pendaftaran pemroses untuk peristiwa melalui callback.
Artefak RxJava
Jika sudah menggunakan RxJava
(versi 2
atau 3
), Anda dapat menggunakan artefak tertentu yang akan membantu Anda menjaga konsistensi dalam kode, baik Anda menggunakan Observables
maupun Flowables
.
9. Menguji menggunakan Jetpack WindowManager
Menguji postur perangkat foldable di emulator atau perangkat apa pun dapat sangat berguna untuk menguji cara penempatan elemen UI di sekitar FoldingFeature
.
Untuk mencapainya, WindowManger dilengkapi dengan artefak yang sangat berguna untuk uji instrumentasi.
Mari kita lihat cara menggunakannya.
Bersama dengan dependensi WindowManager utama, kami telah menambahkan artefak pengujian dalam file build.gradle
aplikasi: androidx.window:window-testing
Artefak window-testing
dilengkapi dengan TestRule
baru yang berguna, yang disebut WindowLayoutInfoPublisherRule
, yang akan membantu menguji pemakaian aliran nilai WindowLayoutInfo
. WindowLayoutInfoPublisherRule
memungkinkan Anda mendorong berbagai nilai WindowLayoutInfo
sesuai permintaan.
Untuk menggunakannya, dan membuat contoh yang bisa membantu Anda menguji UI dengan artefak baru ini, perbarui class pengujian yang dibuat oleh template Android Studio. Ganti semua kode di class ExampleInstrumentedTest
dengan kode berikut:
ExampleInstrumentedTest.kt
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.window.testing.layout.WindowLayoutInfoPublisherRule
import org.junit.Rule
import org.junit.rules.RuleChain
import org.junit.rules.TestRule
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class MainActivityTest {
private val activityRule = ActivityScenarioRule(MainActivity::class.java)
private val publisherRule = WindowLayoutInfoPublisherRule()
@get:Rule
val testRule: TestRule
init {
testRule = RuleChain.outerRule(publisherRule).around(activityRule)
}
}
Aturan yang disebutkan telah dirantai dengan ActvityScenarioRule
.
Untuk membuat FoldingFeature
tiruan, artefak baru ini dilengkapi dengan beberapa fungsi yang sangat berguna untuk melakukannya. Berikut adalah tiruan paling sederhana yang memberikan beberapa nilai default.
Di MainActivity
, TextView
sejajar dengan bagian kiri fitur lipat. Buat pengujian yang memeriksa apakah tindakan ini sudah diterapkan dengan benar.
Buat pengujian bernama testText_is_left_of_Vertical_FoldingFeature
:
ExampleInstrumentedTest.kt
import androidx.window.layout.FoldingFeature.Orientation.Companion.VERTICAL
import androidx.window.layout.FoldingFeature.State.Companion.FLAT
import androidx.window.testing.layout.FoldingFeature
import androidx.window.testing.layout.TestWindowLayoutInfo
import org.junit.Test
@Test
fun testText_is_left_of_Vertical_FoldingFeature() {
activityRule.scenario.onActivity { activity ->
val hinge = FoldingFeature(
activity = activity,
state = FLAT,
orientation = VERTICAL,
size = 2
)
val expected = TestWindowLayoutInfo(listOf(hinge))
publisherRule.overrideWindowLayoutInfo(expected)
}
// Add Assertion with EspressoMatcher here
}
Pengujian FoldingFeature
memiliki status FLAT
dan orientasinya adalah VERTICAL
. Kita telah menentukan ukuran tertentu karena ingin FoldingFeature
palsu ditampilkan di UI dalam pengujian sehingga kita dapat melihat lokasinya di perangkat.
Kita menggunakan WindowLayoutInfoPublishRule
yang kita buat sebelumnya untuk memublikasikan FoldingFeaure
palsu, sehingga kita bisa mendapatkannya seperti halnya dengan data WindowLayoutInfo
sebenarnya:
Langkah terakhir adalah menguji apakah elemen UI berada di tempat yang seharusnya untuk menghindari FoldingFeature
. Untuk melakukannya, cukup gunakan EspressoMatchers
, tambahkan pernyataan di akhir pengujian yang baru saja kita buat:
ExampleInstrumentedTest.kt
import androidx.test.espresso.assertion.PositionAssertions
import androidx.test.espresso.matcher.ViewMatchers.withId
onView(withId(R.id.layout_change)).check(
PositionAssertions.isCompletelyLeftOf(withId(R.id.folding_feature))
)
Pengujian lengkapnya adalah:
ExampleInstrumentedTest.kt
@Test
fun testText_is_left_of_Vertical_FoldingFeature() {
activityRule.scenario.onActivity { activity ->
val hinge = FoldingFeature(
activity = activity,
state = FoldingFeature.State.FLAT,
orientation = FoldingFeature.Orientation.VERTICAL,
size = 2
)
val expected = TestWindowLayoutInfo(listOf(hinge))
publisherRule.overrideWindowLayoutInfo(expected)
}
onView(withId(R.id.layout_change)).check(
PositionAssertions.isCompletelyLeftOf(withId(R.id.folding_feature))
)
}
val horizontal_hinge = FoldingFeature(
activity = activity,
state = FLAT,
orientation = HORIZONTAL,
size = 2
)
Sekarang Anda dapat menjalankan pengujian di perangkat atau emulator untuk memeriksa apakah aplikasi berperilaku seperti yang diharapkan. Perhatikan bahwa Anda tidak memerlukan perangkat foldable atau emulator untuk menjalankan pengujian ini.
10. Selamat!
Jetpack WindowManager membantu developer menangani perangkat faktor bentuk baru, seperti perangkat foldable.
Informasi yang disediakan WindowManager sangat membantu dalam menyesuaikan aplikasi Android ke perangkat foldable untuk memberikan pengalaman pengguna yang lebih baik.
Sebagai ringkasan, dalam codelab ini Anda telah mempelajari:
- Pengertian perangkat foldable
- Perbedaan antara berbagai perangkat foldable
- Perbedaan antara perangkat foldable, perangkat satu layar, dan tablet
- Jetpack WindowManager API
- Menggunakan Jetpack WindowManager dan menyesuaikan aplikasi ke faktor bentuk perangkat baru
- Menguji menggunakan Jetpack WindowManager