จอแสดงผลขนาดใหญ่ที่กางออกและสถานะการพับที่ไม่เหมือนใครช่วยให้ผู้ใช้ได้รับประสบการณ์ใหม่ๆ บน อุปกรณ์แบบพับได้ หากต้องการให้แอปของคุณรองรับการพับ ให้ใช้ไลบรารี Jetpack WindowManager ซึ่งมีแพลตฟอร์ม API สำหรับฟีเจอร์หน้าต่างของอุปกรณ์พับได้ เช่น รอยพับและบานพับ เมื่อแอปของคุณรับรู้ถึงการพับได้ แอปจะปรับเลย์เอาต์ เพื่อหลีกเลี่ยงการวางเนื้อหาสำคัญในบริเวณรอยพับหรือบานพับ และใช้รอยพับ และบานพับเป็นตัวคั่นตามธรรมชาติ
การทำความเข้าใจว่าอุปกรณ์รองรับการกำหนดค่าต่างๆ เช่น ท่าทางบนโต๊ะหรือท่าทางแบบหนังสือ หรือไม่ จะช่วยในการตัดสินใจเกี่ยวกับการรองรับเลย์เอาต์ต่างๆ หรือการจัดหา ฟีเจอร์ที่เฉพาะเจาะจง
ข้อมูลหน้าต่าง
อินเทอร์เฟซ WindowInfoTracker ใน Jetpack WindowManager จะแสดงข้อมูลเลย์เอาต์ของหน้าต่าง
เมธอด windowLayoutInfo() ของอินเทอร์เฟซจะแสดงสตรีมข้อมูล WindowLayoutInfo ที่แจ้งให้แอปทราบเกี่ยวกับสถานะการพับของอุปกรณ์แบบพับได้
เมธอด WindowInfoTracker#getOrCreate() จะสร้างอินสแตนซ์ของ WindowInfoTracker
WindowManager รองรับการรวบรวมWindowLayoutInfoข้อมูลโดยใช้
โฟลว์ Kotlin และการเรียกกลับของ Java
โฟลว์ Kotlin
หากต้องการเริ่มและหยุดการเก็บรวบรวมข้อมูล WindowLayoutInfo คุณสามารถใช้โครูทีนที่รับรู้ถึงวงจร
ที่เริ่มใหม่ได้ ซึ่งระบบจะเรียกใช้บล็อกโค้ด repeatOnLifecycle
เมื่อวงจรมีสถานะเป็น STARTED เป็นอย่างน้อย และจะหยุดเมื่อวงจรมีสถานะเป็น STOPPED ระบบจะรีสตาร์ทการดำเนินการบล็อกโค้ดโดยอัตโนมัติ
เมื่อวงจรเป็น STARTED อีกครั้ง ในตัวอย่างต่อไปนี้ บล็อกโค้ดจะรวบรวมและใช้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
เลเยอร์ความเข้ากันได้ของ 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 = 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()
}
}
ฟีเจอร์ของจอแสดงผลแบบพับได้
คลาส 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().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.
}
}
ตัวอย่าง
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
แหล่งข้อมูลเพิ่มเติม
ตัวอย่าง
- Jetpack WindowManager: ตัวอย่างวิธีใช้ไลบรารี Jetpack WindowManager
- Jetcaster : การใช้งานท่าทางบนโต๊ะด้วย Compose
Codelabs
- รองรับอุปกรณ์แบบพับได้และอุปกรณ์ที่มี 2 หน้าจอด้วย Jetpack WindowManager
- เพิ่มประสิทธิภาพแอปกล้องในอุปกรณ์พับได้ด้วย Jetpack WindowManager