จอแสดงผลขนาดใหญ่ที่กางออกและสถานะการพับที่ไม่เหมือนใครช่วยให้ผู้ใช้ได้รับประสบการณ์ใหม่ๆ บน อุปกรณ์แบบพับได้ หากต้องการให้แอปของคุณรองรับการพับ ให้ใช้ไลบรารี 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_OPENED
- orientation: การวางแนวของรอยพับหรือบานพับ- HORIZONTALหรือ- VERTICAL
- occlusionType: ไม่ว่ารอยพับหรือบานพับจะซ่อนส่วนหนึ่งของจอแสดงผลหรือไม่- NONEหรือ- FULL
- isSeparating: ไม่ว่ารอยพับหรือบานพับจะสร้างพื้นที่แสดงผลเชิงตรรกะ 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
