فولد برنامه خود را آگاه کنید

نمایشگرهای بزرگ باز نشده و حالت‌های تاشدگی منحصر به فرد، تجربیات کاربری جدیدی را در دستگاه‌های تاشو فراهم می‌کنند. برای اینکه برنامه شما از قابلیت تا شدن آگاه باشد، از کتابخانه Jetpack WindowManager استفاده کنید که یک سطح API برای ویژگی‌های پنجره دستگاه‌های تاشو مانند تاها و لولاها فراهم می‌کند. وقتی برنامه شما از قابلیت تا شدن آگاه است، می‌تواند طرح‌بندی خود را طوری تنظیم کند که از قرار دادن محتوای مهم در ناحیه تاها یا لولاها جلوگیری کند و از تاها و لولاها به عنوان جداکننده‌های طبیعی استفاده کند.

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

اطلاعات پنجره

رابط WindowInfoTracker در Jetpack WindowManager اطلاعات طرح‌بندی پنجره را نمایش می‌دهد. متد windowLayoutInfo() رابط، جریانی از داده‌های WindowLayoutInfo را برمی‌گرداند که برنامه شما را در مورد وضعیت تا شدن یک دستگاه تاشو مطلع می‌کند. متد WindowInfoTracker#getOrCreate() یک نمونه از WindowInfoTracker ایجاد می‌کند.

WindowManager از جمع‌آوری داده‌های WindowLayoutInfo با استفاده از جریان‌های کاتلین و فراخوانی‌های جاوا پشتیبانی می‌کند.

جریان‌های کاتلین

برای شروع و توقف جمع‌آوری داده‌های WindowLayoutInfo ، می‌توانید از یک کوروتین آگاه از چرخه حیات قابل راه‌اندازی مجدد استفاده کنید که در آن بلوک کد repeatOnLifecycle زمانی اجرا می‌شود که چرخه حیات حداقل STARTED باشد و زمانی متوقف می‌شود که چرخه حیات STOPPED شود. اجرای بلوک کد به طور خودکار زمانی که چرخه حیات دوباره STARTED شود، مجدداً راه‌اندازی می‌شود. در مثال زیر، بلوک کد داده‌های WindowLayoutInfo را جمع‌آوری و استفاده می‌کند:

class DisplayFeaturesActivity : AppCompatActivity() {

    private lateinit var binding: ActivityDisplayFeaturesBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityDisplayFeaturesBinding.inflate(layoutInflater)
        setContentView(binding.root)

        lifecycleScope.launch(Dispatchers.Main) {
            lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
                WindowInfoTracker.getOrCreate(this@DisplayFeaturesActivity)
                    .windowLayoutInfo(this@DisplayFeaturesActivity)
                    .collect { newLayoutInfo ->
                        // Use newLayoutInfo to update the layout.
                    }
            }
        }
    }
}

فراخوانی‌های جاوا

لایه سازگاری فراخوانی برگشتی که در وابستگی androidx.window:window-java قرار دارد، شما را قادر می‌سازد تا به‌روزرسانی‌های WindowLayoutInfo را بدون استفاده از جریان Kotlin جمع‌آوری کنید. این مصنوع شامل کلاس WindowInfoTrackerCallbackAdapter است که یک WindowInfoTracker برای پشتیبانی از ثبت (و لغو ثبت) فراخوانی‌های برگشتی برای دریافت به‌روزرسانی‌های WindowLayoutInfo تطبیق می‌دهد، برای مثال:

public class SplitLayoutActivity extends AppCompatActivity {

    private WindowInfoTrackerCallbackAdapter windowInfoTracker;
    private ActivitySplitLayoutBinding binding;
    private final LayoutStateChangeCallback layoutStateChangeCallback =
            new LayoutStateChangeCallback();

   @Override
   protected void onCreate(@Nullable Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);

       binding = ActivitySplitLayoutBinding.inflate(getLayoutInflater());
       setContentView(binding.getRoot());

       windowInfoTracker =
                new WindowInfoTrackerCallbackAdapter(WindowInfoTracker.getOrCreate(this));
   }

   @Override
   protected void onStart() {
       super.onStart();
       windowInfoTracker.addWindowLayoutInfoListener(
                this, Runnable::run, layoutStateChangeCallback);
   }

   @Override
   protected void onStop() {
       super.onStop();
       windowInfoTracker
           .removeWindowLayoutInfoListener(layoutStateChangeCallback);
   }

   class LayoutStateChangeCallback implements Consumer<WindowLayoutInfo> {
       @Override
       public void accept(WindowLayoutInfo newLayoutInfo) {
           SplitLayoutActivity.this.runOnUiThread( () -> {
               // Use newLayoutInfo to update the layout.
           });
       }
   }
}

پشتیبانی از RxJava

اگر از قبل از RxJava (نسخه 2 یا 3 ) استفاده می‌کنید، می‌توانید از مزایایی بهره‌مند شوید که به شما امکان می‌دهند بدون استفاده از جریان کاتلین، از Observable یا Flowable برای جمع‌آوری به‌روزرسانی‌های WindowLayoutInfo استفاده کنید.

لایه سازگاری ارائه شده توسط وابستگی‌های androidx.window:window-rxjava2 و androidx.window:window-rxjava3 شامل متدهای WindowInfoTracker#windowLayoutInfoFlowable() و WindowInfoTracker#windowLayoutInfoObservable() است که برنامه شما را قادر می‌سازد به‌روزرسانی‌های WindowLayoutInfo را دریافت کند، برای مثال:

class RxActivity: AppCompatActivity {

    private lateinit var binding: ActivityRxBinding

    private var disposable: Disposable? = null
    private lateinit var observable: Observable<WindowLayoutInfo>

   @Override
   protected void onCreate(@Nullable Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);

       binding = ActivitySplitLayoutBinding.inflate(getLayoutInflater());
       setContentView(binding.getRoot());

        // Create a new observable.
        observable = WindowInfoTracker.getOrCreate(this@RxActivity)
            .windowLayoutInfoObservable(this@RxActivity)
   }

   @Override
   protected void onStart() {
       super.onStart();

        // Subscribe to receive WindowLayoutInfo updates.
        disposable?.dispose()
        disposable = observable
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe { newLayoutInfo ->
            // Use newLayoutInfo to update the layout.
        }
   }

   @Override
   protected void onStop() {
       super.onStop();

        // Dispose of the WindowLayoutInfo observable.
        disposable?.dispose()
   }
}

ویژگی‌های نمایشگرهای تاشو

کلاس WindowLayoutInfo از Jetpack WindowManager ویژگی‌های یک پنجره نمایش را به صورت فهرستی از عناصر DisplayFeature در دسترس قرار می‌دهد.

FoldingFeature نوعی DisplayFeature است که اطلاعاتی در مورد نمایشگرهای تاشو ارائه می‌دهد، از جمله ویژگی‌های زیر:

  • state : حالت تا شده دستگاه، FLAT یا HALF_OPENED

  • orientation : جهت تا یا لولا، HORIZONTAL یا VERTICAL

  • occlusionType : اینکه آیا تا یا لولا بخشی از نمایشگر را می‌پوشاند، NONE یا FULL می‌پوشاند

  • isSeparating : آیا تاخوردگی یا لولا دو ناحیه نمایش منطقی ایجاد می‌کند، درست یا غلط

یک دستگاه تاشو که HALF_OPENED است، همیشه isSeparating درست گزارش می‌دهد زیرا صفحه نمایش به دو قسمت نمایش تقسیم شده است. همچنین، isSeparating همیشه در یک دستگاه دو صفحه‌ای درست است، زمانی که برنامه هر دو صفحه را پوشش می‌دهد.

ویژگی FoldingFeature bounds (که از DisplayFeature به ارث رسیده است) مستطیل مرزی یک ویژگی تاشو مانند تا یا لولا را نشان می‌دهد. از این bounds می‌توان برای قرار دادن عناصر روی صفحه نسبت به آن ویژگی استفاده کرد:

کاتلین

override fun onCreate(savedInstanceState: Bundle?) {
    // ...
    lifecycleScope.launch(Dispatchers.Main) {
        lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
            // Safely collects from WindowInfoTracker when the lifecycle is
            // STARTED and stops collection when the lifecycle is STOPPED.
            WindowInfoTracker.getOrCreate(this@MainActivity)
                .windowLayoutInfo(this@MainActivity)
                .collect { layoutInfo ->
                    // New posture information.
                    val foldingFeature = layoutInfo.displayFeatures
                        .filterIsInstance<FoldingFeature>()
                        .firstOrNull()
                    // Use information from the foldingFeature object.
                }
        }
    }
}

جاوا

private WindowInfoTrackerCallbackAdapter windowInfoTracker;
private final LayoutStateChangeCallback layoutStateChangeCallback =
                new LayoutStateChangeCallback();

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    // ...
    windowInfoTracker =
            new WindowInfoTrackerCallbackAdapter(WindowInfoTracker.getOrCreate(this));
}

@Override
protected void onStart() {
    super.onStart();
    windowInfoTracker.addWindowLayoutInfoListener(
            this, Runnable::run, layoutStateChangeCallback);
}

@Override
protected void onStop() {
    super.onStop();
    windowInfoTracker.removeWindowLayoutInfoListener(layoutStateChangeCallback);
}

class LayoutStateChangeCallback implements Consumer<WindowLayoutInfo> {
    @Override
    public void accept(WindowLayoutInfo newLayoutInfo) {
        // Use newLayoutInfo to update the Layout.
        List<DisplayFeature> displayFeatures = newLayoutInfo.getDisplayFeatures();
        for (DisplayFeature feature : displayFeatures) {
            if (feature instanceof FoldingFeature) {
                // Use information from the feature object.
            }
        }
    }
}

وضعیت قرارگیری روی میز

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

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

شکل ۱. یک برنامه پخش ویدیو در حالت رومیزی - ویدیو در قسمت عمودی صفحه؛ کنترل‌های پخش در قسمت افقی.

از FoldingFeature.State و FoldingFeature.Orientation برای تعیین اینکه آیا دستگاه در حالت رومیزی قرار دارد یا خیر، استفاده کنید:

کاتلین

fun isTableTopPosture(foldFeature : FoldingFeature?) : Boolean {
    contract { returns(true) implies (foldFeature != null) }
    return foldFeature?.state == FoldingFeature.State.HALF_OPENED &&
            foldFeature.orientation == FoldingFeature.Orientation.HORIZONTAL
}

جاوا

boolean isTableTopPosture(FoldingFeature foldFeature) {
    return (foldFeature != null) &&
           (foldFeature.getState() == FoldingFeature.State.HALF_OPENED) &&
           (foldFeature.getOrientation() == FoldingFeature.Orientation.HORIZONTAL);
}

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

در اندروید ۱۵ (سطح API 35) و بالاتر، می‌توانید یک API همگام را فراخوانی کنید تا تشخیص دهد که آیا یک دستگاه از حالت قرارگیری روی میز پشتیبانی می‌کند یا خیر، صرف نظر از وضعیت فعلی دستگاه.

این API فهرستی از حالت‌های بدنی پشتیبانی‌شده توسط دستگاه را ارائه می‌دهد. اگر این فهرست شامل حالت‌های بدنی روی میز باشد، می‌توانید طرح‌بندی برنامه خود را برای پشتیبانی از این حالت‌ها تقسیم کنید و تست‌های A/B را روی رابط کاربری برنامه خود برای حالت‌های بدنی روی میز و تمام صفحه اجرا کنید.

کاتلین

if (WindowSdkExtensions.getInstance().extensionsVersion >= 6) {
    val postures = WindowInfoTracker.getOrCreate(context).supportedPostures
    if (postures.contains(TABLE_TOP)) {
        // Device supports tabletop posture.
   }
}

جاوا

if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 6) {
    List<SupportedPosture> postures = WindowInfoTracker.getOrCreate(context).getSupportedPostures();
    if (postures.contains(SupportedPosture.TABLETOP)) {
        // Device supports tabletop posture.
    }
}

مثال‌ها

حالت کتاب

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

همچنین اگر می‌خواهید هنگام عکاسی بدون دخالت دست، نسبت ابعاد متفاوتی را ثبت کنید، می‌توانید از آن برای عکاسی استفاده کنید.

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

کاتلین

fun isBookPosture(foldFeature : FoldingFeature?) : Boolean {
    contract { returns(true) implies (foldFeature != null) }
    return foldFeature?.state == FoldingFeature.State.HALF_OPENED &&
            foldFeature.orientation == FoldingFeature.Orientation.VERTICAL
}

جاوا

boolean isBookPosture(FoldingFeature foldFeature) {
    return (foldFeature != null) &&
           (foldFeature.getState() == FoldingFeature.State.HALF_OPENED) &&
           (foldFeature.getOrientation() == FoldingFeature.Orientation.VERTICAL);
}

تغییر اندازه پنجره

ناحیه نمایش یک برنامه می‌تواند در نتیجه تغییر پیکربندی دستگاه تغییر کند، برای مثال، وقتی دستگاه تا یا باز می‌شود، می‌چرخد یا اندازه یک پنجره در حالت چند پنجره‌ای تغییر می‌کند.

کلاس Jetpack WindowManager WindowMetricsCalculator شما را قادر می‌سازد تا معیارهای فعلی و حداکثری پنجره را بازیابی کنید. مانند پلتفرم WindowMetrics که در API سطح 30 معرفی شد، WindowManager WindowMetrics محدوده‌های پنجره را ارائه می‌دهد، اما این API تا سطح API سطح 14 با نسخه‌های قبلی سازگار است.

به بخش «استفاده از کلاس‌های اندازه پنجره» مراجعه کنید.

منابع اضافی

نمونه‌ها

  • Jetpack WindowManager : مثالی از نحوه استفاده از کتابخانه Jetpack WindowManager
  • Jetcaster : پیاده‌سازی حالت قرارگیری میز با Compose

کدلبز