讓應用程式適用於折疊式裝置

未摺疊的大型螢幕和獨特的摺疊狀態能在 摺疊式裝置。如要為應用程式採用摺疊機制,請使用 Jetpack WindowManager 程式庫,提供摺疊式裝置視窗功能的 API 介面 例如摺疊和轉軸應用程式採用摺疊機制時,可調整版面配置 ,避免在摺疊或轉軸區域放置重要內容,並應使用摺疊畫面 以及轉軸做為自然分隔符

瞭解裝置是否支援桌面或書本等設定 防護機制可做為決定支援不同版面配置或提供 特定功能

視窗資訊

Jetpack WindowManager 的 WindowInfoTracker 介面可公開視窗版面配置資訊。此介面的 windowLayoutInfo() 方法會傳回一連串的 WindowLayoutInfo 資料,告知應用程式摺疊式裝置的摺疊狀態。WindowInfoTracker#getOrCreate() 方法會建立 WindowInfoTracker 例項。

WindowManager 支援使用 Kotlin 流程和 Java 回呼收集 WindowLayoutInfo 資料。

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 回呼

回呼相容性層,包含在 androidx.window:window-java 依附元件可讓您收集 在不使用 Kotlin 資料流的情況下更新 WindowLayoutInfo。構件包括 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 (版本 23),請善用可讓您使用 ObservableFlowable 的特定構件,以收集 WindowLayoutInfo 更新,無需使用 Kotlin 資料流。

androidx.window:window-rxjava2androidx.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()
   }
}

折疊式裝置螢幕功能

Jetpack WindowManager 的 WindowLayoutInfo 類別可用 DisplayFeature 元素清單形式提供顯示視窗功能。

FoldingFeature 是一種 DisplayFeature,可以提供資訊 有關摺疊式裝置螢幕的資訊,包括:

  • state:裝置摺疊狀態,可為 FLATHALF_OPENED

  • orientation:摺疊或轉軸方向,可為 HORIZONTALVERTICAL

  • occlusionType:摺疊或轉軸是否會擋住部分螢幕,可為 NONEFULL

  • isSeparating:摺疊或轉軸是否會建立兩個邏輯顯示區域,可為 true 或 false

處於 HALF_OPENED 狀態的摺疊式裝置一律會將 isSeparating 回報為 true 因為螢幕會分割成兩個顯示區域此外,如果應用程式在雙螢幕裝置上橫跨兩個螢幕顯示,則 isSeparating 也會一律為 true。

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.StateFoldingFeature.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 裝置而言,摺疊以及放置控制項和補充內容 不必動手就能觀看或聆聽音樂。

在 Android 15 (API 級別 35) 以上版本中,您可以叫用同步 API,無論裝置目前的狀態為何,都能偵測裝置是否支援桌面模式。

API 會提供裝置支援的姿勢清單。如果清單 包含桌面型態,您可以分割應用程式版面配置來支援一種型態 ,並針對桌面和全螢幕版面配置,在應用程式 UI 上執行 A/B 版本測試。

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

範例

書本型態

另一種獨特的摺疊型態是書本型態,亦即裝置半開,轉軸為垂直方向。書本適合用來閱讀電子書。在大螢幕摺疊式裝置上利用雙頁版面配置閱讀書籍,呈現翻開實體精裝書的感觸。

此外,如果想使用免持方式拍攝不同顯示比例的相片,也可以使用這項功能。

實作書本型態時,採用的技術應與桌面型態相同。唯一的差別在於程式碼應檢查摺疊功能螢幕方向是否為垂直,而非水平:

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 實作桌面型態

程式碼研究室