อุปกรณ์แบบพับได้มอบประสบการณ์การรับชมที่ไม่เหมือนใคร โหมดจอแสดงผลด้านหลังและโหมด 2 หน้าจอช่วยให้คุณสร้างฟีเจอร์การแสดงผลพิเศษสำหรับอุปกรณ์พับได้ เช่น ตัวอย่างเซลฟีจากกล้องหลัง และการแสดงผลพร้อมกันแต่แตกต่างกันบนหน้าจอด้านในและด้านนอก
โหมดการแสดงผลด้านหลัง
โดยปกติแล้วเมื่อกางอุปกรณ์พับได้ จะมีเพียงหน้าจอด้านในเท่านั้นที่ทำงาน โหมดจอแสดงผลด้านหลังช่วยให้คุณย้ายกิจกรรมไปยังหน้าจอด้านนอกของอุปกรณ์พับได้ ซึ่งโดยปกติจะหันออกจากผู้ใช้ขณะที่อุปกรณ์กางออก หน้าจอด้านในจะปิดโดยอัตโนมัติ
แอปพลิเคชันใหม่คือการแสดงตัวอย่างกล้องบนหน้าจอด้านนอก เพื่อให้ ผู้ใช้ถ่ายเซลฟีด้วยกล้องหลังได้ ซึ่งโดยปกติแล้วจะให้ประสิทธิภาพการถ่ายภาพ ที่ดีกว่ากล้องหน้ามาก
หากต้องการเปิดใช้งานโหมดจอแสดงผลด้านหลัง ผู้ใช้จะต้องตอบกล่องโต้ตอบเพื่ออนุญาตให้แอป สลับหน้าจอ เช่น
ระบบจะสร้างกล่องโต้ตอบให้ คุณจึงไม่ต้องพัฒนาใดๆ กล่องโต้ตอบต่างๆ จะปรากฏขึ้นตามสถานะของอุปกรณ์ เช่น ระบบ จะนำผู้ใช้ไปกางอุปกรณ์ออกหากอุปกรณ์ปิดอยู่ คุณปรับแต่ง กล่องโต้ตอบไม่ได้ และกล่องโต้ตอบอาจแตกต่างกันไปในอุปกรณ์จาก OEM ต่างๆ
คุณลองใช้โหมดจอแสดงผลด้านหลังกับแอปกล้องของ Pixel Fold ได้ ดูตัวอย่างการใช้งานใน Codelab เพิ่มประสิทธิภาพแอปกล้องในอุปกรณ์แบบพับได้ด้วย Jetpack WindowManager
โหมด Dual Screen
โหมด Dual Screen ช่วยให้คุณแสดงเนื้อหาบนจอแสดงผลทั้ง 2 จอของอุปกรณ์พับได้พร้อมกันได้ โหมด 2 หน้าจอใช้ได้ใน Pixel Fold ที่ใช้ Android 14 (ระดับ API 34) ขึ้นไป
ตัวอย่างกรณีการใช้งานคือล่ามแบบ 2 หน้าจอ
เปิดใช้โหมดต่างๆ โดยใช้โปรแกรม
คุณเข้าถึงโหมดจอแสดงผลด้านหลังและโหมด 2 หน้าจอได้ผ่าน API ของ Jetpack WindowManager ตั้งแต่ไลบรารีเวอร์ชัน 1.2.0-beta03 เป็นต้นไป
เพิ่มทรัพยากร Dependency ของ WindowManager ลงในไฟล์ build.gradle ของโมดูลแอป
Kotlin
dependencies {
// Define window_version in your project's build configuration.
implementation("androidx.window:window:$window_version")
}
Groovy
dependencies {
// TODO: 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 ประเภท ได้แก่
WindowAreaCapability.Operation.OPERATION_PRESENT_ON_AREAซึ่งใช้เพื่อเริ่มโหมด 2 หน้าจอWindowAreaCapability.Operation.OPERATION_TRANSFER_ACTIVITY_TO_AREAซึ่งใช้เพื่อเริ่มโหมดแสดงผลด้านหลัง
ตัวอย่างวิธีประกาศตัวแปรสำหรับโหมดการแสดงผลด้านหลังและ โหมดดูอัลสกรีนในกิจกรรมหลักของแอปมีดังนี้
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
Listener ต้องใช้เมธอด onSessionStarted(), onSessionEnded(),
และ onContainerVisibilityChanged() เมธอด Callback จะแจ้งให้คุณทราบ
สถานะเซสชันและช่วยให้คุณอัปเดตแอปได้ตามนั้น
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()
หากไม่เป็นเช่นนั้น ซึ่งคล้ายกับตัวอย่างโหมด 2 หน้าจอ
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}");
}
}
เพื่อรักษาความสอดคล้องกันทั่วทั้งระบบนิเวศ ให้ใช้ไอคอนกล้องหลังอย่างเป็นทางการเพื่อระบุให้ผู้ใช้ทราบวิธีเปิดหรือปิดโหมดการแสดงผลด้านหลัง
แหล่งข้อมูลเพิ่มเติม
- เพิ่มประสิทธิภาพแอปกล้องในอุปกรณ์แบบพับได้ด้วย Jetpack WindowManager codelab
- สรุปแพ็กเกจ
androidx.window.area - โค้ดตัวอย่าง Jetpack WindowManager