支援折疊式顯示模式

折疊式裝置提供獨特的觀看體驗。您可以使用後置顯示模式和雙螢幕模式,為折疊式裝置建構特殊顯示功能,例如後置鏡頭自拍預覽畫面,以及讓裝置在內外螢幕上同時顯示不同的螢幕。

後置顯示模式

一般而言,展開折疊式裝置時,只會啟用內部螢幕。後置顯示模式可讓您將活動移至折疊式裝置的外側螢幕,通常在裝置展開時面向使用者。內螢幕會自動關閉。

這個創新的應用程式是在外螢幕螢幕上顯示相機預覽畫面,方便使用者使用後置鏡頭自拍,這通常擁有更佳的拍攝效能。

如要啟用後置顯示模式,使用者可以回應對話方塊來允許應用程式切換畫面,例如:

圖 1. 允許啟動後置顯示模式的系統對話方塊。

系統會建立對話方塊,因此您不需要進行開發。視裝置狀態而定,系統會顯示不同的對話方塊;例如,系統會在裝置關閉時引導使用者展開裝置。您無法自訂對話方塊,但該對話方塊可能會因原始設備製造商 (OEM) 的裝置而異。

你可以試用 Pixel Fold 相機應用程式後置顯示模式。請參閱程式碼研究室的「展開相機體驗」中的實作範例。

雙螢幕模式

雙螢幕模式可讓您同時在折疊式裝置的兩種螢幕上顯示內容。雙螢幕模式適用於搭載 Android 14 (API 級別 34) 以上版本的 Pixel Fold。

其中一個應用實例是雙螢幕翻譯工具。

圖 2. 雙螢幕翻譯工具,在正面和後置螢幕上顯示不同內容。

以程式輔助方式啟用模式

從程式庫 1.2.0-beta03 版開始,您可以透過 Jetpack WindowManager API 存取後置顯示模式和雙螢幕模式。

將 WindowManager 依附元件新增至應用程式的模組 build.gradle 檔案:

Groovy

dependencies {
    implementation "androidx.window:window:1.2.0-beta03"
}

Kotlin

dependencies {
    implementation("androidx.window:window:1.2.0-beta03")
}

進入點是 WindowAreaController,可用於在螢幕之間或裝置顯示區域之間移動視窗,並提供相關資訊和行為。WindowAreaController 可讓您查詢可用的 WindowAreaInfo 物件清單。

使用 WindowAreaInfo 存取 WindowAreaSession,這是代表使用中視窗區域功能的介面。使用 WindowAreaSession 判斷特定 WindowAreaCapability 的可用性。

每項功能都會與特定 WindowAreaCapability.Operation 相關。在 1.2.0-beta03 版中,Jetpack WindowManager 支援兩種作業:

以下範例說明如何在應用程式的主要活動中,宣告後置顯示模式和雙螢幕模式的變數:

Kotlin

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

Java

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() 方法中初始化變數:

Kotlin

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

Java

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;
        }
    }
});

開始作業之前,請先檢查特定功能的可用性:

Kotlin

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 currently available to be enabled.
    }
    WindowAreaCapability.Status.WINDOW_AREA_STATUS_AVAILABLE -> {
      // The selected display mode is currently available to be enabled.
    }
    WindowAreaCapability.Status.WINDOW_AREA_STATUS_ACTIVE -> {
      // The selected display mode is already active.
    }
    else -> {
      // The selected display mode status is unknown.            
    }
}

Java

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 currently available to be enabled.
}
else if (capabilityStatus.equals(WindowAreaCapability.Status.WINDOW_AREA_STATUS_AVAILABLE)) {
  // The selected display mode is currently available to 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() 函式:

Kotlin

fun toggleDualScreenMode() {
    if (windowAreaSession != null) {
        windowAreaSession?.close()
    }
    else {
        windowAreaInfo?.token?.let { token ->
            windowAreaController.presentContentOnWindowArea(
                token = token,
                activity = this,
                executor = displayExecutor,
                windowAreaPresentationSessionCallback = this
            )
        }
    }
}

Java

private void toggleDualScreenMode() {
    if(windowAreaSession != null) {
        windowAreaSession.close();
    }
    else {
        Binder token = windowAreaInfo.getToken();
        windowAreaController.presentContentOnWindowArea( token, this, displayExecutor, this);
    }
}

請注意,應用程式的主要活動會做為 WindowAreaPresentationSessionCallback

API 採用事件監聽器的方法:當您發出要求,將內容分享到折疊式裝置的另一螢幕時,會啟動透過事件監聽器的 onSessionStarted() 方法傳回的工作階段。關閉工作階段後,onSessionEnded() 方法中會顯示確認訊息。

如要建立事件監聽器,請實作 WindowAreaPresentationSessionCallback 介面:

Kotlin

class MainActivity : AppCompatActivity(), windowAreaPresentationSessionCallback

Java

public class MainActivity extends AppCompatActivity implements WindowAreaPresentationSessionCallback

事件監聽器需要實作 onSessionStarted()onSessionEnded(),onContainerVisibilityChanged() 方法。回呼方法會通知工作階段狀態,方便您更新應用程式。

onSessionStarted() 回呼會收到 WindowAreaSessionPresenter 做為引數。引數是一種容器,可讓您存取視窗區域並顯示內容。使用者離開主要應用程式視窗後,系統即可自動關閉呈現畫面,呼叫 WindowAreaSessionPresenter#close() 則可關閉簡報。

至於其他回呼,為求簡單起見,只要檢查函式主體中是否有任何錯誤並記錄狀態即可:

Kotlin

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")
}

Java

@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() 函式:

Kotlin

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

Java

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, 使用,較易於實作,因為回呼未能接收允許在視窗區域顯示內容,而是將整個活動傳輸至另一個區域:

Kotlin

override fun onSessionStarted() {
    Log.d(logTag, "onSessionStarted")
}

override fun onSessionEnded(t: Throwable?) {
    if(t != null) {
        Log.e(logTag, "Something was broken: ${t.message}")
    }
}

Java

@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}");
    }
}

為了維持整個生態系統的一致性,請使用後置鏡頭官方圖示,告知使用者如何啟用或停用後置顯示模式。

其他資源