จอแสดงผลขนาดใหญ่ที่กางออกและสถานะการพับที่ไม่เหมือนใครช่วยให้ผู้ใช้ได้รับประสบการณ์ใหม่ๆ บน อุปกรณ์แบบพับได้ หากต้องการทำให้แอปของคุณรองรับการพับได้ ให้ใช้ไลบรารี Jetpack WindowManager ซึ่งมีพื้นผิว API สำหรับฟีเจอร์หน้าต่างของอุปกรณ์ที่พับได้ เช่น รอยพับและบานพับ เมื่อแอปของคุณรองรับการพับได้ แอปจะปรับเลย์เอาต์ เพื่อหลีกเลี่ยงการวางเนื้อหาสำคัญในบริเวณรอยพับหรือบานพับ และใช้รอยพับ และบานพับเป็นตัวคั่นตามธรรมชาติได้
การทำความเข้าใจว่าอุปกรณ์รองรับการกำหนดค่าต่างๆ เช่น ท่าทางบนโต๊ะหรือท่าทางหนังสือ หรือไม่ จะช่วยในการตัดสินใจเกี่ยวกับการรองรับเลย์เอาต์ต่างๆ หรือการจัดหา ฟีเจอร์ที่เฉพาะเจาะจง
ข้อมูลหน้าต่าง
อินเทอร์เฟซ WindowInfoTracker ใน Jetpack WindowManager จะแสดงข้อมูลเลย์เอาต์ของหน้าต่าง
เมธอด windowLayoutInfo() ของอินเทอร์เฟซจะแสดงสตรีมข้อมูล WindowLayoutInfo ที่แจ้งให้แอปทราบเกี่ยวกับสถานะการพับของอุปกรณ์แบบพับได้
เมธอด WindowInfoTracker#getOrCreate() จะสร้างอินสแตนซ์ของ WindowInfoTracker
WindowManager รองรับการรวบรวมWindowLayoutInfoข้อมูลโดยใช้
โฟลว์ Kotlin และการเรียกกลับ Java
โฟลว์ Kotlin
หากต้องการเริ่มและหยุดการเก็บรวบรวมข้อมูล WindowLayoutInfo คุณสามารถใช้โครูทีนที่รับรู้ถึงวงจรที่รีสตาร์ทได้ ซึ่งระบบจะเรียกใช้โค้ดบล็อก repeatOnLifecycle เมื่อวงจรมีสถานะอย่างน้อย STARTED และจะหยุดเมื่อวงจรมีสถานะ STOPPED ระบบจะรีสตาร์ทการดำเนินการโค้ดบล็อกโดยอัตโนมัติ
เมื่อวงจรเป็น STARTED อีกครั้ง ในตัวอย่างต่อไปนี้ โค้ดบล็อกจะรวบรวมและใช้WindowLayoutInfoข้อมูล
class DisplayFeaturesActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { // ... lifecycleScope.launch(Dispatchers.Main) { lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { WindowInfoTracker.getOrCreate(this@DisplayFeaturesActivity) .windowLayoutInfo(this@DisplayFeaturesActivity) .collect { newLayoutInfo -> // Use newLayoutInfo to update the layout. } } } } }
การเรียกกลับของ Java
เลเยอร์ความเข้ากันได้ของ Callback ที่รวมอยู่ในทรัพยากร Dependency ของ androidx.window:window-java ช่วยให้คุณรวบรวมข้อมูลอัปเดต WindowLayoutInfo ได้โดยไม่ต้องใช้ Kotlin Flow อาร์ติแฟกต์ประกอบด้วยคลาส WindowInfoTrackerCallbackAdapter ซึ่งปรับ WindowInfoTracker เพื่อรองรับการลงทะเบียน (และยกเลิกการลงทะเบียน) การเรียกกลับเพื่อรับการอัปเดต WindowLayoutInfo เช่น
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.
});
}
}
}
การรองรับ RxJava
หากคุณใช้ RxJava (เวอร์ชัน 2 หรือ 3) อยู่แล้ว
คุณจะใช้ประโยชน์จากอาร์ติแฟกต์ที่ช่วยให้คุณใช้
Observable หรือ Flowable
เพื่อรวบรวมการอัปเดต WindowLayoutInfo ได้โดยไม่ต้องใช้ Kotlin Flow
เลเยอร์ความเข้ากันได้ที่จัดทำโดยการขึ้นต่อกันของ androidx.window:window-rxjava2 และ
androidx.window:window-rxjava3 มีเมธอด
WindowInfoTracker#windowLayoutInfoFlowable() และ
WindowInfoTracker#windowLayoutInfoObservable() ซึ่งช่วยให้แอปของคุณรับการอัปเดต WindowLayoutInfo ได้ เช่น
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 = ActivityRxBinding.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()
}
}
ฟีเจอร์ของจอแสดงผลแบบพับได้
คลาส WindowLayoutInfo ของ Jetpack WindowManager ทำให้ฟีเจอร์ของหน้าต่างแสดงผลพร้อมใช้งานเป็นรายการขององค์ประกอบ DisplayFeature
FoldingFeature เป็นDisplayFeatureประเภทหนึ่งที่ให้ข้อมูล
เกี่ยวกับจอแสดงผลแบบพับได้ ซึ่งรวมถึงพร็อพเพอร์ตี้ต่อไปนี้
state: สถานะพับของอุปกรณ์FLATหรือHALF_OPENEDorientation: การวางแนวของรอยพับหรือบานพับHORIZONTALหรือVERTICALocclusionType: ไม่ว่ารอยพับหรือบานพับจะซ่อนส่วนใดส่วนหนึ่งของจอแสดงผลหรือไม่NONEหรือFULLisSeparating: ไม่ว่ารอยพับหรือบานพับจะสร้างพื้นที่แสดงผลเชิงตรรกะ 2 พื้นที่หรือไม่ จริงหรือเท็จ
อุปกรณ์แบบพับได้ที่HALF_OPENEDรายงานisSeparatingเป็นจริงเสมอ
เนื่องจากหน้าจอแบ่งออกเป็นพื้นที่แสดงผล 2 ส่วน นอกจากนี้ isSeparating จะเป็น
จริงเสมอในอุปกรณ์แบบ 2 หน้าจอเมื่อแอปพลิเคชันครอบคลุมทั้ง 2
หน้าจอ
พร็อพเพอร์ตี้ FoldingFeature bounds (รับค่ามาจาก DisplayFeature)
แสดงถึงสี่เหลี่ยมผืนผ้าล้อมรอบของฟีเจอร์การพับ เช่น การพับหรือบานพับ
ขอบเขตสามารถใช้เพื่อวางตำแหน่งองค์ประกอบบนหน้าจอที่สัมพันธ์กับฟีเจอร์ได้ดังนี้
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.
}
}
}
}
ท่าทางบนโต๊ะ
การใช้ข้อมูลที่รวมอยู่ในออบเจ็กต์ FoldingFeature จะช่วยให้แอปของคุณรองรับท่าทางต่างๆ เช่น ท่าบนโต๊ะ ซึ่งโทรศัพท์วางอยู่บนพื้นผิว บานพับอยู่ในแนวนอน และหน้าจอแบบพับได้เปิดครึ่งหนึ่ง
ท่าตั้งโต๊ะช่วยให้ผู้ใช้ใช้งานโทรศัพท์ได้สะดวกโดยไม่ต้องถือโทรศัพท์ไว้ในมือ ท่าตั้งโต๊ะเหมาะสำหรับการดูสื่อ ถ่ายรูป และวิดีโอคอล
ใช้ FoldingFeature.State และ FoldingFeature.Orientation เพื่อพิจารณาว่าอุปกรณ์อยู่ในท่าตั้งบนโต๊ะหรือไม่
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);
}
เมื่อทราบว่าอุปกรณ์อยู่ในท่าตั้งบนโต๊ะแล้ว ให้อัปเดตเลย์เอาต์แอปตามนั้น สำหรับแอปสื่อ โดยทั่วไปแล้วหมายถึงการวางการเล่นไว้เหนือครึ่งหน้าบน และวางตัวควบคุมและเนื้อหาเสริมไว้ต่อจากนั้นเพื่อประสบการณ์การดูหรือฟังแบบแฮนด์ฟรี
ใน Android 15 (ระดับ API 35) ขึ้นไป คุณสามารถเรียกใช้ API แบบซิงโครนัสเพื่อตรวจหาว่าอุปกรณ์รองรับท่าทางแบบวางบนโต๊ะหรือไม่ โดยไม่คำนึงถึงสถานะปัจจุบันของอุปกรณ์
API จะแสดงรายการท่าทางที่อุปกรณ์รองรับ หากรายการ มีท่าทางแบบตั้งโต๊ะ คุณจะแยกเลย์เอาต์แอปเพื่อรองรับท่าทาง และเรียกใช้การทดสอบ A/B ใน UI ของแอปสำหรับเลย์เอาต์แบบตั้งโต๊ะและแบบเต็มหน้าจอได้
Kotlin
if (WindowSdkExtensions.getInstance().extensionVersion >= 6) { val postures = WindowInfoTracker.getOrCreate(context).supportedPostures if (postures.contains(SupportedPosture.TABLETOP)) { // 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.
}
}
ตัวอย่าง
MediaPlayerActivity: ดูวิธีใช้ Media3 ExoPlayer และ WindowManager เพื่อสร้างวิดีโอที่รองรับการพับเพิ่มประสิทธิภาพแอปกล้องในอุปกรณ์พับได้ด้วย Jetpack WindowManager Codelab: ดูวิธีใช้ท่าตั้งโต๊ะสำหรับแอปถ่ายภาพ แสดงช่องมองภาพที่ครึ่งหน้าบนของหน้าจอ และการควบคุมที่ครึ่งหน้าล่าง
ลักษณะการทำงานของอุปกรณ์
อีกฟีเจอร์พับได้ที่ไม่เหมือนใครคือท่าทางหนังสือ ซึ่งอุปกรณ์จะกางออกครึ่งหนึ่ง และบานพับจะอยู่ในแนวตั้ง ท่าทางอ่านหนังสือเหมาะสำหรับการอ่าน eBook เมื่อเปิดอุปกรณ์พับได้ที่มีหน้าจอขนาดใหญ่ในเลย์เอาต์ 2 หน้าเหมือนหนังสือที่มีการเย็บเล่ม ท่าทางของหนังสือจะจำลองประสบการณ์การอ่านหนังสือจริง
นอกจากนี้ยังใช้ในการถ่ายภาพได้หากต้องการถ่ายภาพที่มีสัดส่วนภาพแตกต่างกัน ขณะถ่ายภาพแบบแฮนด์ฟรี
ใช้ท่าทางแบบหนังสือด้วยเทคนิคเดียวกับที่ใช้สำหรับท่าทางแบบวางบนโต๊ะ ความแตกต่างเพียงอย่างเดียวคือโค้ดควรตรวจสอบว่าการวางแนวฟีเจอร์การพับเป็น แนวตั้งแทนที่จะเป็นแนวนอน
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);
}
การเปลี่ยนแปลงขนาดหน้าต่าง
พื้นที่แสดงผลของแอปอาจเปลี่ยนแปลงได้อันเป็นผลมาจากการเปลี่ยนแปลงการกำหนดค่าอุปกรณ์ เช่น เมื่ออุปกรณ์พับหรือกางออก หมุน หรือเมื่อมีการปรับขนาดหน้าต่าง ในโหมดหลายหน้าต่าง
คลาส Jetpack WindowManager WindowMetricsCalculator ช่วยให้คุณเรียกข้อมูลเมตริกหน้าต่างปัจจุบันและสูงสุดได้ เช่นเดียวกับแพลตฟอร์ม WindowMetrics ที่เปิดตัวในระดับ API 30 WindowManager
WindowMetrics จะระบุขอบเขตของหน้าต่าง แต่ API จะเข้ากันได้แบบย้อนหลัง
จนถึงระดับ API 14
แหล่งข้อมูลเพิ่มเติม
ตัวอย่าง
- WindowManager ของ Jetpack: ตัวอย่างวิธีใช้ไลบรารี WindowManager ของ Jetpack
- Jetcaster : การใช้งานท่าทางบนโต๊ะด้วย Compose
Codelab
- รองรับอุปกรณ์แบบพับได้และอุปกรณ์แบบ 2 หน้าจอด้วย Jetpack WindowManager
- เพิ่มประสิทธิภาพแอปกล้องในอุปกรณ์แบบพับได้ด้วย Jetpack WindowManager