รับรู้ถึงการพับของแอป

จอแสดงผลขนาดใหญ่เมื่อกางออกและสถานะการพับที่ไม่เหมือนใครช่วยให้ผู้ใช้ได้รับประสบการณ์ใหม่ๆ บนอุปกรณ์แบบพับได้ หากต้องการให้แอปของคุณรองรับการพับได้ ให้ใช้ไลบรารี 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.
                    }
            }
        }
    }
}

การเรียกกลับของ Java

เลเยอร์ความเข้ากันได้ของ Callback ที่รวมอยู่ในการอ้างอิง androidx.window:window-java ช่วยให้คุณรวบรวมข้อมูลอัปเดต WindowLayoutInfo ได้โดยไม่ต้องใช้โฟลว์ Kotlin อาร์ติแฟกต์ประกอบด้วยคลาส 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: Whether the fold or hinge creates two logical display areas, true or false

อุปกรณ์แบบพับได้ที่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

ท่าตั้งโต๊ะช่วยให้ผู้ใช้ใช้งานโทรศัพท์ได้อย่างสะดวกโดยไม่ต้องถือโทรศัพท์ไว้ในมือ ท่าตั้งโต๊ะเหมาะสำหรับการดูสื่อ ถ่ายรูป และวิดีโอคอล

รูปที่ 1 แอปวิดีโอเพลเยอร์ในท่าตั้งโต๊ะ - วิดีโอบนส่วนแนวตั้งของหน้าจอ ตัวควบคุมการเล่นบนส่วนแนวนอน

ใช้ 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.
    }
}

ตัวอย่าง

ลักษณะการทำงานของอุปกรณ์ในโหมดหนังสือ

อีกฟีเจอร์เฉพาะตัวของอุปกรณ์พับได้คือท่าทางหนังสือ ซึ่งอุปกรณ์จะกางออกครึ่งหนึ่ง และบานพับจะอยู่ในแนวตั้ง ท่าทางอ่านหนังสือเหมาะสำหรับการอ่าน 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