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

จอแสดงผลขนาดใหญ่แบบกางออกและสถานะแบบพับที่ไม่ซ้ำกันช่วยให้ผู้ใช้ได้รับประสบการณ์การใช้งานแบบใหม่ในอุปกรณ์แบบพับได้ หากต้องการให้แอปรับรู้การพับ ให้ใช้ไลบรารี Jetpack WindowManager ซึ่งมีแพลตฟอร์ม API สำหรับฟีเจอร์หน้าต่างของอุปกรณ์แบบพับได้ เช่น การพับและการบานพับ เมื่อแอปรับรู้การพับ แอปจะปรับเลย์เอาต์เพื่อหลีกเลี่ยงการวางเนื้อหาที่สําคัญในพื้นที่ของรอยพับหรือรอยบานพับ และใช้รอยพับและรอยบานพับเป็นตัวคั่นตามปกติ

การทราบว่าอุปกรณ์รองรับการกำหนดค่า เช่น แบบตั้งโต๊ะหรือแบบหนังสือ ช่วยแนะนำการตัดสินใจเกี่ยวกับการรองรับเลย์เอาต์ต่างๆ หรือการให้บริการฟีเจอร์เฉพาะได้หรือไม่

ข้อมูลหน้าต่าง

อินเทอร์เฟซ WindowInfoTracker ใน Jetpack WindowManager จะแสดงข้อมูลเลย์เอาต์หน้าต่าง เมธอด windowLayoutInfo() ของอินเทอร์เฟซจะแสดงผลสตรีมข้อมูล WindowLayoutInfo ที่แจ้งให้แอปทราบเกี่ยวกับสถานะการพับของอุปกรณ์แบบพับได้ เมธอด WindowInfoTracker#getOrCreate() จะสร้างอินสแตนซ์ของ WindowInfoTracker

WindowManager รองรับการเก็บรวบรวมข้อมูล WindowLayoutInfo โดยใช้โฟลว์ Kotlin และการเรียกกลับ Java

ความลื่นไหลของ Kotlin

หากต้องการเริ่มและหยุดการเก็บรวบรวมข้อมูล WindowLayoutInfo คุณสามารถใช้ Coroutine ที่รับรู้วงจรการทํางานและเริ่มทํางานใหม่ได้ ซึ่งจะเรียกใช้บล็อกโค้ด 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

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

KotlinJava
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.
               
}

       
}
   
}
}
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 เพื่อระบุว่า อุปกรณ์อยู่ในลักษณะบนโต๊ะหรือไม่ โดยทำดังนี้

KotlinJava

fun isTableTopPosture(foldFeature : FoldingFeature?) : Boolean {
    contract
{ returns(true) implies (foldFeature != null) }
   
return foldFeature?.state == FoldingFeature.State.HALF_OPENED &&
            foldFeature
.orientation == FoldingFeature.Orientation.HORIZONTAL
}


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 ของแอปสำหรับเลย์เอาต์แบบตั้งโต๊ะและแบบเต็มหน้าจอ

KotlinJava
if (WindowSdkExtensions.getInstance().extensionsVersion >= 6) {
   
val postures = WindowInfoTracker.getOrCreate(context).supportedPostures
   
if (postures.contains(TABLE_TOP)) {
       
// Device supports tabletop posture.
   
}
}
if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 6) {
   
List<SupportedPosture> postures = WindowInfoTracker.getOrCreate(context).getSupportedPostures();
   
if (postures.contains(SupportedPosture.TABLETOP)) {
       
// Device supports tabletop posture.
   
}
}

ตัวอย่าง

ลักษณะการถือหนังสือ

ฟีเจอร์แบบพับได้ที่ไม่เหมือนใครอีกอย่างคือลักษณะการวางหนังสือ ซึ่งอุปกรณ์เปิดออกครึ่งหนึ่งและบานพับอยู่ในแนวตั้ง ท่าทางการอ่านหนังสือเหมาะสำหรับการอ่าน eBook การวางเลย์เอาต์ 2 หน้าบนหน้าจอขนาดใหญ่แบบพับได้ซึ่งเปิดอยู่เหมือนหนังสือแบบมีเล่มทำให้ท่าทางการอ่านสื่อถึงประสบการณ์การอ่านหนังสือจริง

นอกจากนี้ยังใช้ถ่ายภาพได้หากต้องการจับภาพในสัดส่วนภาพอื่นขณะถ่ายภาพแบบแฮนด์ฟรี

ใช้การจัดท่าหนังสือด้วยเทคนิคเดียวกับที่ใช้ในการจัดท่าทางบนโต๊ะ ความแตกต่างเพียงอย่างเดียวคือโค้ดควรตรวจสอบว่าการวางแนวของฟีเจอร์การพับเป็นแนวตั้งแทนแนวนอน

KotlinJava
fun isBookPosture(foldFeature : FoldingFeature?) : Boolean {
    contract
{ returns(true) implies (foldFeature != null) }
   
return foldFeature?.state == FoldingFeature.State.HALF_OPENED &&
            foldFeature
.orientation == FoldingFeature.Orientation.VERTICAL
}
boolean isBookPosture(FoldingFeature foldFeature) {
   
return (foldFeature != null) &&
           
(foldFeature.getState() == FoldingFeature.State.HALF_OPENED) &&
           
(foldFeature.getOrientation() == FoldingFeature.Orientation.VERTICAL);
}

การเปลี่ยนแปลงขนาดหน้าต่าง

พื้นที่แสดงผลของแอปอาจเปลี่ยนแปลงเนื่องจากการเปลี่ยนแปลงการกำหนดค่าอุปกรณ์ เช่น เมื่ออุปกรณ์พับหรือกางออก หมุน หรือปรับขนาดหน้าต่างในโหมดหลายหน้าต่าง

คลาส WindowManager WindowMetricsCalculator ของ Jetpack ช่วยให้คุณดึงข้อมูลเมตริกกรอบเวลาปัจจุบันและสูงสุดได้ WindowManager WindowMetrics จะระบุขอบเขตของหน้าต่างเช่นเดียวกับแพลตฟอร์มWindowMetrics ที่เปิดตัวใน API ระดับ 30 แต่ API นี้ใช้งานย้อนหลังได้จนถึง API ระดับ 14

โปรดดูใช้คลาสขนาดหน้าต่าง

แหล่งข้อมูลเพิ่มเติม

ตัวอย่าง

  • Jetpack WindowManager: ตัวอย่างวิธีใช้ไลบรารี Jetpack WindowManager
  • Jetcaster : การใช้ท่าทางบนโต๊ะด้วย Compose

Codelabs