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

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

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

รูปที่ 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