摺疊式裝置提供獨特的檢視體驗。透過後置顯示模式和雙螢幕模式,您可以為摺疊式裝置建構特殊的顯示功能,例如後置鏡頭自拍預覽,以及內外螢幕同時顯示不同畫面。
後置顯示模式
一般而言,摺疊式裝置處於展開狀態時,只會用到內螢幕。後置顯示模式可讓您將活動移至摺疊式裝置的外螢幕 (該螢幕通常會在裝置展開時背向使用者),內螢幕會自動關閉。
其中一個新穎的應用方式,就是在外螢幕顯示相機預覽畫面,讓使用者可以使用後置鏡頭自拍,這通常比前置鏡頭更出色的拍攝效能。
如要啟用後置顯示模式,使用者需要回覆對話方塊,允許應用程式切換螢幕,例如:

系統會建立此對話方塊,因此您不必為此執行任何開發作業。根據裝置狀態,系統會顯示不同的對話方塊;例如在裝置關起時引導使用者展開裝置。您無法自訂這個對話方塊,對話方塊會因裝置的原始設備製造商 (OEM) 而有所不同。
您可以透過 Pixel Fold 相機應用程式試用後置顯示模式。請參閱程式碼研究室「使用 Jetpack WindowManager 最佳化適用於摺疊式裝置的相機應用程式」中的實作範例。
雙螢幕模式
雙螢幕模式可讓您同時在兩個摺疊式裝置螢幕上顯示內容。雙螢幕模式適用於搭載 Android 14 (API 級別 34) 以上版本的 Pixel Fold。
其中一個用途範例就是雙螢幕翻譯模式。

以程式輔助方式啟用模式
自 Jetpack WindowManager 1.2.0-beta03 版開始,您可以透過此程式庫的 API 使用後置顯示模式和雙螢幕模式。
請將 WindowManager 依附元件新增至應用程式模組的 build.gradle
檔案:
dependencies {
implementation "androidx.window:window:1.2.0-beta03"
}
dependencies {
implementation("androidx.window:window:1.2.0-beta03")
}
進入點是 WindowAreaController
,可在裝置螢幕或顯示區域之間移動視窗,並提供相關資訊和行為。WindowAreaController
可讓您查詢可用 WindowAreaInfo
物件的清單。
使用 WindowAreaInfo
即可存取 WindowAreaSession
,這是代表有效視窗區域功能的介面。使用 WindowAreaSession
則可判斷特定 WindowAreaCapability
的可用性。
請注意,每項功能都與特定 WindowAreaCapability.Operation
相關。在 1.2.0-beta03 版中,Jetpack WindowManager 支援以下兩種作業:
WindowAreaCapability.Operation.OPERATION_PRESENT_ON_AREA
,用於啟動雙螢幕模式WindowAreaCapability.Operation.OPERATION_TRANSFER_ACTIVITY_TO_AREA
,用於啟動後置顯示模式
以下範例說明如何在應用程式的主要活動中,宣告後置顯示模式和雙螢幕模式的變數:
private lateinit var windowAreaController: WindowAreaController
private lateinit var displayExecutor: Executor
private var windowAreaSession: WindowAreaSession? = null
private var windowAreaInfo: WindowAreaInfo? = null
private var capabilityStatus: WindowAreaCapability.Status =
WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED
private val dualScreenOperation = WindowAreaCapability.Operation.OPERATION_PRESENT_ON_AREA
private val rearDisplayOperation = WindowAreaCapability.Operation.OPERATION_TRANSFER_ACTIVITY_TO_AREA
private WindowAreaControllerCallbackAdapter windowAreaController = null;
private Executor displayExecutor = null;
private WindowAreaSessionPresenter windowAreaSession = null;
private WindowAreaInfo windowAreaInfo = null;
private WindowAreaCapability.Status capabilityStatus =
WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED;
private WindowAreaCapability.Operation dualScreenOperation =
WindowAreaCapability.Operation.OPERATION_PRESENT_ON_AREA;
private WindowAreaCapability.Operation rearDisplayOperation =
WindowAreaCapability.Operation.OPERATION_TRANSFER_ACTIVITY_TO_AREA;
以下說明如何初始化活動中 onCreate()
方法的變數:
displayExecutor = ContextCompat.getMainExecutor(this)
windowAreaController = WindowAreaController.getOrCreate()
lifecycleScope.launch(Dispatchers.Main) {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
windowAreaController.windowAreaInfos
.map { info -> info.firstOrNull { it.type == WindowAreaInfo.Type.TYPE_REAR_FACING } }
.onEach { info -> windowAreaInfo = info }
.map { it?.getCapability(operation)?.status ?: WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED }
.distinctUntilChanged()
.collect {
capabilityStatus = it
}
}
}
displayExecutor = ContextCompat.getMainExecutor(this);
windowAreaController = new WindowAreaControllerCallbackAdapter(WindowAreaController.getOrCreate());
windowAreaController.addWindowAreaInfoListListener(displayExecutor, this);
windowAreaController.addWindowAreaInfoListListener(displayExecutor,
windowAreaInfos -> {
for(WindowAreaInfo newInfo : windowAreaInfos){
if(newInfo.getType().equals(WindowAreaInfo.Type.TYPE_REAR_FACING)){
windowAreaInfo = newInfo;
capabilityStatus = newInfo.getCapability(presentOperation).getStatus();
break;
}
}
});
請先檢查特定功能的可用性,再開始執行作業:
when (capabilityStatus) {
WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED -> {
// The selected display mode is not supported on this device.
}
WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNAVAILABLE -> {
// The selected display mode is not available.
}
WindowAreaCapability.Status.WINDOW_AREA_STATUS_AVAILABLE -> {
// The selected display mode is available and can be enabled.
}
WindowAreaCapability.Status.WINDOW_AREA_STATUS_ACTIVE -> {
// The selected display mode is already active.
}
else -> {
// The selected display mode status is unknown.
}
}
if (capabilityStatus.equals(WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED)) {
// The selected display mode is not supported on this device.
}
else if (capabilityStatus.equals(WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNAVAILABLE)) {
// The selected display mode is not available.
}
else if (capabilityStatus.equals(WindowAreaCapability.Status.WINDOW_AREA_STATUS_AVAILABLE)) {
// The selected display mode is available and can be enabled.
}
else if (capabilityStatus.equals(WindowAreaCapability.Status.WINDOW_AREA_STATUS_ACTIVE)) {
// The selected display mode is already active.
}
else {
// The selected display mode status is unknown.
}
雙螢幕模式
以下範例會在功能已啟用時關閉工作階段,否則會呼叫 presentContentOnWindowArea()
函式:
fun toggleDualScreenMode() {
if (windowAreaSession != null) {
windowAreaSession?.close()
}
else {
windowAreaInfo?.token?.let { token ->
windowAreaController.presentContentOnWindowArea(
token = token,
activity = this,
executor = displayExecutor,
windowAreaPresentationSessionCallback = this
)
}
}
}
private void toggleDualScreenMode() {
if(windowAreaSession != null) {
windowAreaSession.close();
}
else {
Binder token = windowAreaInfo.getToken();
windowAreaController.presentContentOnWindowArea( token, this, displayExecutor, this);
}
}
請注意,使用應用程式主要活動做為 WindowAreaPresentationSessionCallback
引數。
API 採用事件監聽器方法:當您要求在摺疊式裝置的其他螢幕上顯示內容時,就會啟動透過事件監聽器的 onSessionStarted()
方法傳回的工作階段。關閉工作階段後,onSessionEnded()
方法中會顯示確認訊息。
如要建立事件監聽器,請實作 WindowAreaPresentationSessionCallback
介面:
class MainActivity : AppCompatActivity(), windowAreaPresentationSessionCallback
public class MainActivity extends AppCompatActivity implements WindowAreaPresentationSessionCallback
事件監聽器需要實作 onSessionStarted()
、onSessionEnded(),
和 onContainerVisibilityChanged()
方法。回呼方法會提供工作階段狀態通知,方便您據此更新應用程式。
onSessionStarted()
回呼接收 WindowAreaSessionPresenter
時採用的是引數形式。引數就是讓您存取視窗區域及顯示內容的容器。當使用者離開主要應用程式視窗時,系統可以自動關閉畫面,但您也可以呼叫 WindowAreaSessionPresenter#close()
關閉畫面。
為了簡單起見,若是其他回呼,只需在函式主體中檢查錯誤及記錄狀態即可:
override fun onSessionStarted(session: WindowAreaSessionPresenter) {
windowAreaSession = session
val view = TextView(session.context)
view.text = "Hello world!"
session.setContentView(view)
}
override fun onSessionEnded(t: Throwable?) {
if(t != null) {
Log.e(logTag, "Something was broken: ${t.message}")
}
}
override fun onContainerVisibilityChanged(isVisible: Boolean) {
Log.d(logTag, "onContainerVisibilityChanged. isVisible = $isVisible")
}
@Override
public void onSessionStarted(@NonNull WindowAreaSessionPresenter session) {
windowAreaSession = session;
TextView view = new TextView(session.getContext());
view.setText("Hello world, from the other screen!");
session.setContentView(view);
}
@Override public void onSessionEnded(@Nullable Throwable t) {
if(t != null) {
Log.e(logTag, "Something was broken: ${t.message}");
}
}
@Override public void onContainerVisibilityChanged(boolean isVisible) {
Log.d(logTag, "onContainerVisibilityChanged. isVisible = " + isVisible);
}
為了在生態系統中維持一致性,請使用雙螢幕官方圖示,向使用者說明如何啟用或停用雙螢幕模式。
如需實際範例,請參閱 DualScreenActivity.kt。
後置顯示模式
與雙螢幕模式範例類似,以下範例中的 toggleRearDisplayMode()
函式會在功能已啟用或呼叫 transferActivityToWindowArea()
函式時關閉工作階段:
fun toggleRearDisplayMode() {
if(capabilityStatus == WindowAreaCapability.Status.WINDOW_AREA_STATUS_ACTIVE) {
if(windowAreaSession == null) {
windowAreaSession = windowAreaInfo?.getActiveSession(
operation
)
}
windowAreaSession?.close()
} else {
windowAreaInfo?.token?.let { token ->
windowAreaController.transferActivityToWindowArea(
token = token,
activity = this,
executor = displayExecutor,
windowAreaSessionCallback = this
)
}
}
}
void toggleDualScreenMode() {
if(capabilityStatus == WindowAreaCapability.Status.WINDOW_AREA_STATUS_ACTIVE) {
if(windowAreaSession == null) {
windowAreaSession = windowAreaInfo.getActiveSession(
operation
)
}
windowAreaSession.close()
}
else {
Binder token = windowAreaInfo.getToken();
windowAreaController.transferActivityToWindowArea(token, this, displayExecutor, this);
}
}
在此案例中,顯示的活動會做為 WindowAreaSessionCallback
,這可簡化實作方式,因為該回呼不會接收允許在視窗區域顯示內容的呈現工具,而會將整個活動轉移至其他區域:
override fun onSessionStarted() {
Log.d(logTag, "onSessionStarted")
}
override fun onSessionEnded(t: Throwable?) {
if(t != null) {
Log.e(logTag, "Something was broken: ${t.message}")
}
}
@Override public void onSessionStarted(){
Log.d(logTag, "onSessionStarted");
}
@Override public void onSessionEnded(@Nullable Throwable t) {
if(t != null) {
Log.e(logTag, "Something was broken: ${t.message}");
}
}
為了在生態系統中維持一致性,請使用後置鏡頭官方圖示,向使用者說明如何啟用或停用後置顯示模式。
其他資源
- 使用 Jetpack WindowManager 最佳化適用於摺疊式裝置的相機應用程式 程式碼研究室
androidx.window.area
套件摘要- Jetpack WindowManager 程式碼範例: