جعل تطبيقك مطوّلاً

إنّ الشاشات الكبيرة غير المطوية والحالة الفريدة المطوية توفّر للمستخدمين تجربة جديدة. أجهزة قابلة للطي. لجعل تطبيقك متوافقًا مع الأجهزة القابلة للطي، استخدِم مكتبة Jetpack WindowManager التي توفّر واجهة برمجة تطبيقات لميزات نوافذ الأجهزة القابلة للطي، مثل الطيات والمفصلات. عندما يكون تطبيقك مؤهَّلاً للطي، يمكنه تعديل تنسيقه. لتجنب وضع المحتوى المهم في منطقة الطيات أو المفصلات واستخدام الطيات والمفصلات كفواصل طبيعية.

يمكن أن يساعد معرفة ما إذا كان الجهاز متوافقًا مع أوضاع معيّنة، مثل وضعه على سطح مستوٍ أو على شكل كتاب، في توجيه القرارات بشأن إتاحة تنسيقات مختلفة أو توفير ميزات معيّنة.

معلومات النافذة

واجهة WindowInfoTracker في Jetpack WindowManager تعرض النافذة معلومات التخطيط. تعرض الطريقة windowLayoutInfo() في الواجهة علامة مصدر بيانات WindowLayoutInfo التي تحدِّد تطبيقك بشأن جهاز قابل للطيّ. حالة الطي في الجهاز. تنشئ الطريقة WindowInfoTracker#getOrCreate() مثال لـ WindowInfoTracker.

يقدّم WindowManager إمكانية جمع بيانات WindowLayoutInfo باستخدام عمليات Kotlin وعمليات الاستدعاء في Java.

مسارات Kotlin

لبدء جمع البيانات باستخدام 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.
                    }
            }
        }
    }
}

عمليات معاودة الاتصال بلغة Java

تتيح لك طبقة التوافق مع طلبات إعادة الاتصال المضمّنة في التبعية 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 بدون استخدام مسار Kotlin.

تتضمّن طبقة التوافق التي يوفّرها تبعيتي 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) المستطيل الحدودي لميزة قابلة للطي، مثل الطية أو المفصل. يمكن استخدام الحدود لوضع العناصر على الشاشة بالنسبة إلى العنصر:

Kotlin

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

        }
    }
}

Java

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، سيتمكّن تطبيقك في أوضاع مثل "التثبيت على سطح مستوٍ"، حيث يتم وضع الهاتف على سطح، في وضع أفقي والشاشة القابلة للطي نصف مفتوحة.

يوفر وضع "التثبيت على سطح مستوٍ" للمستخدمين سهولة تشغيل هواتفهم بدون يحملون الهاتف في أيديهم. يُعدّ وضع الجهاز على سطح مستوٍ مثاليًا لمشاهدة الوسائط والتقاط الصور وإجراء مكالمات الفيديو.

الشكل 1. تطبيق مشغّل فيديو في وضع "التثبيت على سطح مستوٍ"

يمكنك استخدام FoldingFeature.State وFoldingFeature.Orientation لتحديد ما إذا كان الجهاز في وضع "التثبيت على سطح مستوٍ":

Kotlin

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

Java

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

بعد معرفة وضع الجهاز على سطح مستوٍ، عدِّل تنسيق تطبيقك وفقًا لذلك. بالنسبة إلى تطبيقات الوسائط، يعني ذلك عادةً وضع أدوات التشغيل فوق الشاشة ووضع عناصر التحكّم والمحتوى الإضافي تحتها مباشرةً لتوفير تجربة مشاهدة أو استماع بدون استخدام اليدين.

في الإصدار 15 من Android (المستوى 35 لواجهة برمجة التطبيقات) والإصدارات الأحدث، يمكنك استدعاء واجهة برمجة تطبيقات متزامنة لتحديد ما إذا كان الجهاز متوافقًا مع وضع "على سطح مستوٍ" بغض النظر عن حالة الجهاز الحالية.

تقدّم واجهة برمجة التطبيقات قائمة بالوضعيات التي يتيح الجهاز قياسها. إذا كانت القائمة يتضمن وضع "الشاشة المسطحة"، يمكنك تقسيم تصميم التطبيق لدعم وضعية وإجراء اختبارات A/B على واجهة المستخدم في تطبيقك للتخطيطات على الطاولة وملء الشاشة.

Kotlin

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

Java

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

أمثلة

وضع الكتاب

من الميزات الفريدة الأخرى القابلة للطيّ هي وضعية الكتاب، حيث يكون الجهاز مفتوحًا نصفه. والمفصلة عمودية. إنّ وضع الكتاب هو أمر رائع لقراءة الكتب الإلكترونية. من خلال استخدام وضع "كتاب"، يمكنك قراءة صفحتَين على شاشة كبيرة قابلة للطيّ مثل كتاب مفتوح.

يمكن استخدامه أيضًا للتصوير الفوتوغرافي إذا أردت التقاط صورة لوجه مختلف. أثناء التقاط الصور بدون لمس الجهاز.

تنفيذ وضعية الكتاب باستخدام الأساليب نفسها المستخدمة في وضع "التثبيت على سطح مستوٍ" ويتمثل اختلافهم الوحيد في أنّ الرمز البرمجي يجب أن يتحقّق من أنّ اتجاه ميزة الطي هو عمودي بدلاً من أفقي:

Kotlin

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

Java

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

تغييرات حجم النافذة

يمكن أن تتغير منطقة عرض التطبيق نتيجة لتغيير إعدادات الجهاز، على سبيل المثال، عندما يكون الجهاز مطويًا أو غير مطوي أو يتم تدويره أو عندما تكون النافذة تغيير حجمها في وضع النوافذ المتعددة.

تتيح لك فئة Jetpack WindowManager WindowMetricsCalculator retrieving current and maximum window metrics. مثل النظام الأساسي WindowMetrics الذي تم تقديمه في المستوى 30 لواجهة برمجة التطبيقات، يقدّم WindowManager WindowMetrics حدود النافذة، ولكن واجهة برمجة التطبيقات متوافقة مع الإصدارات القديمة حتى المستوى 14 لواجهة برمجة التطبيقات.

راجِع مقالة استخدام فئات حجم النوافذ.

مصادر إضافية

نماذج

  • Jetpack WindowManager: مثال على كيفية استخدام Jetpack مكتبة WindowManager
  • Jetcaster: تنفيذ وضع "على سطح مستوٍ" باستخدام Compose

الدروس التطبيقية حول الترميز