پشتیبانی از حالت های نمایش تاشو

دستگاه‌های تاشو تجربیات تماشای منحصر به فردی را ارائه می‌دهند. حالت نمایشگر عقب و حالت صفحه نمایش دوگانه شما را قادر می‌سازد تا ویژگی‌های نمایشی ویژه‌ای برای دستگاه‌های تاشو مانند پیش‌نمایش سلفی دوربین عقب و نمایش همزمان اما متفاوت در صفحه‌های داخلی و خارجی ایجاد کنید.

حالت نمایش عقب

معمولاً وقتی یک دستگاه تاشو باز می‌شود، فقط صفحه نمایش داخلی فعال است. حالت نمایش از پشت به شما امکان می‌دهد یک فعالیت را به صفحه نمایش بیرونی دستگاه تاشو منتقل کنید، که معمولاً هنگام باز شدن دستگاه، رو به کاربر نیست. صفحه نمایش داخلی به طور خودکار خاموش می‌شود.

یک کاربرد جدید، نمایش پیش‌نمایش دوربین روی صفحه نمایش بیرونی است، بنابراین کاربران می‌توانند با دوربین عقب عکس سلفی بگیرند، که معمولاً عملکرد عکس‌برداری بسیار بهتری نسبت به دوربین جلو ارائه می‌دهد.

برای فعال کردن حالت نمایشگر پشتی، کاربران به یک کادر محاوره‌ای پاسخ می‌دهند تا به برنامه اجازه دهند صفحه‌ها را تغییر دهد، برای مثال:

شکل ۱. پنجره‌ی گفتگوی سیستم برای فعال کردن حالت نمایش از پشت.

سیستم این کادر محاوره‌ای را ایجاد می‌کند، بنابراین نیازی به هیچ گونه توسعه‌ای از جانب شما نیست. کادرهای محاوره‌ای مختلفی بسته به وضعیت دستگاه ظاهر می‌شوند؛ برای مثال، سیستم کاربران را راهنمایی می‌کند که در صورت بسته بودن دستگاه، آن را باز کنند. شما نمی‌توانید این کادر محاوره‌ای را سفارشی کنید و می‌تواند در دستگاه‌های مختلف از تولیدکنندگان اصلی تجهیزات (OEM) متفاوت باشد.

می‌توانید حالت نمایشگر پشتی را با برنامه دوربین Pixel Fold امتحان کنید. نمونه‌ای از پیاده‌سازی را در codelab ببینید . برنامه دوربین خود را در دستگاه‌های تاشو با Jetpack WindowManager بهینه کنید .

حالت دو صفحه نمایش

حالت دو صفحه‌ای به شما امکان می‌دهد محتوا را همزمان روی هر دو نمایشگر یک گوشی تاشو نمایش دهید. حالت دو صفحه‌ای در Pixel Fold با اندروید ۱۴ (سطح API ۳۴) یا بالاتر در دسترس است.

یک نمونه از موارد استفاده، مفسر دو صفحه‌ای است.

شکل ۲. مترجم دو صفحه‌ای که محتوای متفاوتی را در نمایشگرهای جلو و عقب نشان می‌دهد.

حالت‌ها را به صورت برنامه‌نویسی فعال کنید

شما می‌توانید از طریق APIهای Jetpack WindowManager ، از نسخه کتابخانه ۱.۲.۰-بتا۰۳، به حالت نمایشگر پشتی و حالت نمایشگر دوگانه دسترسی داشته باشید.

وابستگی WindowManager را به فایل build.gradle ماژول برنامه خود اضافه کنید:

گرووی

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

کاتلین

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 از دو نوع عملیات پشتیبانی می‌کند:

در اینجا مثالی از نحوه تعریف متغیرها برای حالت نمایش از پشت و حالت دو صفحه نمایش در activity اصلی برنامه شما آورده شده است:

کاتلین

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() در activity شما آورده شده است:

کاتلین

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

به استفاده از activity اصلی برنامه به عنوان آرگومان WindowAreaPresentationSessionCallback توجه کنید.

این API از رویکرد شنونده استفاده می‌کند: وقتی درخواستی برای نمایش محتوا به نمایشگر دیگر یک نمایشگر تاشو ارسال می‌کنید، یک جلسه (session) را آغاز می‌کنید که از طریق متد onSessionStarted() شنونده بازگردانده می‌شود. وقتی جلسه را می‌بندید، در متد onSessionEnded() تأیید دریافت می‌کنید.

برای ایجاد شنونده، رابط WindowAreaPresentationSessionCallback را پیاده‌سازی کنید:

کاتلین

class MainActivity : AppCompatActivity(), windowAreaPresentationSessionCallback

جاوا

public class MainActivity extends AppCompatActivity implements WindowAreaPresentationSessionCallback

شنونده باید متدهای onSessionStarted() ، onSessionEnded(), و onContainerVisibilityChanged() را پیاده‌سازی کند. متدهای callback شما را از وضعیت session مطلع می‌کنند و به شما امکان می‌دهند برنامه را بر اساس آن به‌روزرسانی کنید.

تابع فراخوانی 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);
}

برای حفظ هماهنگی در سراسر اکوسیستم، از آیکون رسمی Dual Screen برای نشان دادن نحوه فعال یا غیرفعال کردن حالت دو صفحه‌ای به کاربران استفاده کنید.

برای یک نمونه کار، به 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 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 استفاده می‌شود که پیاده‌سازی آن ساده‌تر است زیرا این فراخوانی، ارائه‌دهنده‌ای دریافت نمی‌کند که امکان نمایش محتوا را در یک ناحیه پنجره فراهم کند، بلکه کل اکتیویتی را به ناحیه دیگری منتقل می‌کند:

کاتلین

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

برای حفظ هماهنگی در کل اکوسیستم، از آیکون رسمی دوربین عقب برای نشان دادن نحوه فعال یا غیرفعال کردن حالت نمایش عقب به کاربران استفاده کنید.

منابع اضافی