在折疊式裝置上,未折疊的大型螢幕和獨特的折疊狀態可以提供新的使用者體驗。如要讓應用程式適用於折疊式裝置,您可以使用 Jetpack WindowManager 程式庫。這個程式庫可針對折疊式裝置,為折疊或轉軸等視窗功能提供 API 介面。適用於折疊式裝置的應用程式可以調整版面配置,避免將重要內容放在折疊或轉軸區域,並使用折疊和轉軸做為自然分隔符。
視窗資訊
Jetpack WindowManager 的 WindowInfoTracker
介面可以曝露視窗版面配置資訊。介面的 windowLayoutInfo()
方法會傳回 WindowLayoutInfo
資料串流,通知應用程式有關折疊式裝置的折疊狀態。WindowInfoTracker
getOrCreate()
方法會建立 WindowInfoTracker
的執行個體。
WindowManager 支援使用 Kotlin 資料流及 Java 回呼收集 WindowLayoutInfo
資料。
Kotlin 資料流
若要開始及停止收集 WindowLayoutInfo
資料,可使用可重新啟動的生命週期感知協同程式,當生命週期至少 STARTED
時便會執行 repeatOnLifecycle
程式碼區塊,而當生命週期為 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
(版本 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 the WindowLayoutInfo observable
disposable?.dispose()
}
}
折疊式裝置螢幕功能
Jetpack WindowManager 的 WindowLayoutInfo
類別可用 DisplayFeature
元素清單形式提供顯示視窗功能。
FoldingFeature
是一種 DisplayFeature
,可以提供折疊式裝置螢幕相關資訊,包括:
state
:裝置折疊狀態,可為FLAT
或HALF_OPENED
orientation
:折疊或轉軸方向,可為HORIZONTAL
或VERTICAL
occlusionType
:折疊或轉軸是否會擋住部分螢幕,可為NONE
或FULL
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 windowInfoRepo 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() .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); }
確認裝置處於桌面模式後,請更新應用程式版面配置。以媒體應用程式來說,通常是指將播放內容放在不需捲動的位置,並將播放控制項和補充內容放在下方,讓使用者不必動手就能欣賞影音內容。
範例
MediaPlayerActivity
應用程式:瞭解如何使用 Media3 Exoplayer 和 WindowManager 建立折疊式影片播放器。展開相機體驗程式碼研究室:瞭解如何實作攝影應用程式的免手持模式。將觀景窗顯示在畫面上半部、摺疊上方,以及需捲動位置的下半部和需捲動位置。
書籍模式
另一種獨特的折疊式型態是書籍模式,其中半部為裝置開啟,轉軸為垂直方向。書籍模式適合用來閱讀電子書。在大螢幕折疊式裝置上採用雙頁版面配置,如同一本繫結書籍,書籍模式可記錄閱讀實際書籍的體驗。
如果你想在不用手持方式拍照時拍攝各種長寬比,可以使用這項功能。
實作書籍模式,採用用於免手持模式的相同技巧。唯一的差別在於程式碼應檢查折疊功能方向是否為垂直,而非水平:
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
類別擷取目前和最大的視窗指標。如同 API 級別 30 所導入的平台 WindowMetrics
,WindowManager WindowMetrics
也能提供視窗邊界,但是 API 可以回溯相容到 API 級別 14。
如要瞭解如何支援不同的視窗大小,請參閱「支援不同的螢幕大小」。
其他資源
範例
- Jetpack WindowManager:Jetpack WindowManager 程式庫的使用方式範例
- Jetcaster:使用 Compose 實作桌面型態