รองรับโหมดการแสดงผลแบบพับได้

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

โหมดการแสดงผลด้านหลัง

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

แอปพลิเคชันใหม่คือการแสดงตัวอย่างกล้องบนหน้าจอด้านนอก เพื่อให้ ผู้ใช้ถ่ายเซลฟีด้วยกล้องหลังได้ ซึ่งโดยปกติแล้วจะให้ประสิทธิภาพการถ่ายภาพ ที่ดีกว่ากล้องหน้ามาก

หากต้องการเปิดใช้งานโหมดจอแสดงผลด้านหลัง ผู้ใช้จะต้องตอบกล่องโต้ตอบเพื่ออนุญาตให้แอป สลับหน้าจอ เช่น

รูปที่ 1 กล่องโต้ตอบของระบบเพื่ออนุญาตให้เริ่มโหมดการแสดงผลด้านหลัง

ระบบจะสร้างกล่องโต้ตอบ คุณจึงไม่ต้องพัฒนาใดๆ กล่องโต้ตอบต่างๆ จะปรากฏขึ้นตามสถานะของอุปกรณ์ เช่น ระบบ จะนำผู้ใช้ไปกางอุปกรณ์ออกหากอุปกรณ์ปิดอยู่ คุณปรับแต่ง กล่องโต้ตอบไม่ได้ และกล่องโต้ตอบอาจแตกต่างกันไปในอุปกรณ์จาก OEM ต่างๆ

คุณลองใช้โหมดจอแสดงผลด้านหลังกับแอปกล้องของ Pixel Fold ได้ ดูตัวอย่าง การใช้งานในโค้ดแล็บเพิ่มประสิทธิภาพแอปกล้องในอุปกรณ์พับได้ด้วย Jetpack WindowManager

โหมด Dual Screen

โหมด Dual Screen ช่วยให้คุณแสดงเนื้อหาบนจอแสดงผลทั้ง 2 จอของอุปกรณ์พับได้พร้อมกันได้ โหมด 2 หน้าจอใช้ได้ใน Pixel Fold ที่ใช้ Android 14 (API ระดับ 34) ขึ้นไป

ตัวอย่างกรณีการใช้งานคือล่ามแบบ 2 หน้าจอ

รูปที่ 2 ล่ามแบบ 2 หน้าจอแสดงเนื้อหาที่แตกต่างกันบนจอแสดงผลด้านหน้าและด้านหลัง

เปิดใช้โหมดต่างๆ โดยใช้โปรแกรม

คุณเข้าถึงโหมดจอแสดงผลด้านหลังและโหมด 2 หน้าจอได้ผ่าน API ของ Jetpack WindowManager ตั้งแต่ไลบรารีเวอร์ชัน 1.2.0-beta03 เป็นต้นไป

เพิ่มทรัพยากร Dependency ของ WindowManager ลงในไฟล์ build.gradle ของโมดูลแอป

Groovy

dependencies {
    // TODO: Define window_version in your project's build configuration.
    implementation "androidx.window:window:$window_version"
}

Kotlin

dependencies {
    // Define window_version in your project's build configuration.
    implementation("androidx.window:window:$window_version")
}

จุดแรกเข้าคือ WindowAreaController ซึ่งให้ข้อมูลและลักษณะการทำงานที่เกี่ยวข้องกับการย้ายหน้าต่างระหว่างจอแสดงผลหรือระหว่างพื้นที่แสดงผลในอุปกรณ์ WindowAreaController ช่วยให้คุณค้นหารายการออบเจ็กต์ WindowAreaInfo ที่พร้อมใช้งานได้

ใช้ WindowAreaInfo เพื่อเข้าถึง WindowAreaSession ซึ่งเป็นอินเทอร์เฟซที่ แสดงถึงฟีเจอร์พื้นที่หน้าต่างที่ใช้งานอยู่ ใช้ WindowAreaSession เพื่อกำหนด ความพร้อมใช้งานของ WindowAreaCapability ที่เฉพาะเจาะจง

ความสามารถแต่ละอย่างจะเกี่ยวข้องกับ WindowAreaCapability.Operation ที่เฉพาะเจาะจง ในเวอร์ชัน 1.2.0-beta03 Jetpack WindowManager รองรับการดำเนินการ 2 ประเภท ได้แก่

ตัวอย่างวิธีประกาศตัวแปรสำหรับโหมดการแสดงผลด้านหลังและ โหมดดูอัลสกรีนในกิจกรรมหลักของแอปมีดังนี้

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

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

โหมด Dual Screen

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

หากต้องการสร้าง Listener ให้ใช้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);
}

หากต้องการรักษาความสอดคล้องกันทั่วทั้งระบบนิเวศ ให้ใช้ไอคอนDual Screen อย่างเป็นทางการเพื่อระบุให้ผู้ใช้ทราบวิธีเปิดหรือปิดใช้โหมด Dual Screen

ดูตัวอย่างที่ใช้งานได้ที่ 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 toggleRearDisplayMode() {
    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 ซึ่งนำไปใช้ได้ง่ายกว่าเนื่องจาก Callback ไม่ได้รับ Presenter ที่อนุญาตให้แสดงเนื้อหาในพื้นที่หน้าต่าง แต่จะโอนกิจกรรมทั้งหมด ไปยังพื้นที่อื่นแทน

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

เพื่อรักษาความสอดคล้องกันทั่วทั้งระบบนิเวศ ให้ใช้ไอคอนกล้องหลังอย่างเป็นทางการเพื่อระบุให้ผู้ใช้ทราบวิธีเปิดหรือปิดโหมดการแสดงผลด้านหลัง

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