كتاب الطبخ على شاشات كبيرة

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

التقييمات بالنجوم

يتم تقييم الوصفات بالنجوم استنادًا إلى مدى توافقها مع إرشادات جودة تطبيق الشاشة الكبيرة.

تقييم من خمس نجوم تستوفي معايير المستوى 1، وتتميز شاشات كبيرة متميّزة
تقييم أربع نجوم يستوفي معايير المستوى 2، وهو محسَّن للشاشات الكبيرة
تقييم من ثلاث نجوم تستوفي معايير المستوى 3 (الشاشات الكبيرة)
تقييم نجمتين يوفِّر بعض إمكانات الشاشة الكبيرة، ولكنه لا يفي بإرشادات جودة تطبيقات الشاشة الكبيرة.
تقييم نجمة واحدة يلبي احتياجات حالة استخدام معينة، ولكنه لا يتوافق مع الشاشات الكبيرة بشكل صحيح

إمكانية استخدام كاميرا Chromebook

تقييم من ثلاث نجوم

روِّج لمستخدمي Chromebook على Google Play.

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

تتضمّن أجهزة Chromebook كاميرا أمامية (مواجهة للمستخدم) تعمل بشكل جيد لعقد اجتماعات الفيديو واللقطات وغيرها من التطبيقات. ولكن لا تحتوي بعض أجهزة Chromebook على كاميرا خلفية (مواجهة للعالم)، ولا تتوافق معظم الكاميرات الموجَّهة للمستخدمين على أجهزة Chromebook مع التركيز التلقائي أو الفلاش.

أفضل الممارسات

تتوافق تطبيقات الكاميرا المتعدّدة الاستخدامات مع جميع الأجهزة بغض النظر عن إعدادات الكاميرا، مثل الأجهزة المزوّدة بكاميرات أمامية وكاميرات خلفية وكاميرات خارجية متصلة بكابل USB.

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

المكوّنات

  • إذن CAMERA: يمنح تطبيقك الإذن بالوصول إلى كاميرات الجهاز.
  • عنصر البيان <uses-feature>: إبلاغ متاجر التطبيقات بالميزات التي يستخدمها تطبيقك
  • السمة required: تشير إلى متاجر التطبيقات في ما إذا كان يمكن تشغيل تطبيقك بدون ميزة محدّدة.

الخطوات

ملخّص

يُرجى تقديم بيان عن إذن "CAMERA". يُرجى تعريف ميزات الكاميرا التي توفّر الدعم الأساسي للكاميرا. حدد ما إذا كانت كل ميزة مطلوبة أم لا.

1. تقديم بيان عن إذن "CAMERA"

أضِف الإذن التالي إلى بيان التطبيق:

<uses-permission android:name="android.permission.CAMERA" />
2. توضيح ميزات الكاميرا الأساسية

أضِف الميزات التالية إلى بيان التطبيق:

<uses-feature android:name="android.hardware.camera.any" android:required="false" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
<uses-feature android:name="android.hardware.camera.flash" android:required="false" />
3. تحديد ما إذا كانت كل ميزة مطلوبة

يجب ضبط android:required="false" لميزة android.hardware.camera.any لإتاحة الوصول إلى تطبيقك من خلال الأجهزة التي تحتوي على أي نوع من الكاميرات المدمجة أو الخارجية، أو بدون كاميرا على الإطلاق.

بالنسبة إلى الميزات الأخرى، اضبط android:required="false" لضمان وصول الأجهزة، مثل أجهزة Chromebook التي لا تحتوي على كاميرات خلفية أو ميزة "التركيز التلقائي" أو الفلاش، إلى تطبيقك في متاجر التطبيقات.

النتائج

يمكن لمستخدمي أجهزة Chromebook تنزيل تطبيقك وتثبيته من Google Play ومتاجر التطبيقات الأخرى. ولن يتم فرض قيود على وظائف الكاميرا في الأجهزة التي تتيح استخدام الكاميرات كاملة الميزات، مثل الهواتف.

يتم إتاحة تطبيقك لأكبر عدد ممكن من الأجهزة، وذلك من خلال تحديد ميزات الكاميرا التي يتيحها تطبيقك صراحةً وتحديد الميزات التي يتطلبها تطبيقك.

مراجع إضافية

لمزيد من المعلومات، اطّلِع على ميزات جهاز الكاميرا في مستندات <uses-feature>.

يكون اتجاه التطبيق محظورًا على الهواتف ولكن ليس على الأجهزة ذات الشاشات الكبيرة.

تقييم نجمتين

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

كيف يمكنك إجراء ذلك في كلتا الحالتين - تقييد التطبيق إلى الاتجاه الرأسي على الشاشات الصغيرة، مع تمكين الوضع الأفقي على الشاشات الكبيرة؟

أفضل الممارسات

تلتزم أفضل التطبيقات بالإعدادات المفضّلة للمستخدم، مثل اتجاه الجهاز.

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

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

المكوّنات

  • screenOrientation: إعداد بيان التطبيق الذي يتيح لك تحديد كيفية استجابة تطبيقك لتغيُّرات اتجاه الجهاز
  • Jetpack WindowManager: مجموعة من المكتبات التي تتيح لك تحديد حجم نافذة التطبيق ونسبة العرض إلى الارتفاع فيها، وهي متوافقة مع الأنظمة القديمة مع المستوى 14 من واجهة برمجة التطبيقات.
  • Activity#setRequestedOrientation(): الطريقة التي يمكنك من خلالها تغيير اتجاه التطبيق في وقت التشغيل

الخطوات

ملخّص

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

1. تحديد إعداد الاتجاه في بيان التطبيق

يمكنك إما تجنُّب تعريف العنصر screenOrientation في بيان التطبيق (في هذه الحالة يتم ضبط الاتجاه التلقائي على unspecified) أو ضبط اتجاه الشاشة على fullUser. إذا لم يقفل المستخدم التدوير المستند إلى أداة الاستشعار، سيتمكّن تطبيقك من استخدام جميع اتجاهات الجهاز.

<activity
    android:name=".MyActivity"
    android:screenOrientation="fullUser">

الفرق بين استخدام unspecified وfullUser بسيط ولكنه مهم. إذا لم تحدّد قيمة screenOrientation، يختار النظام الاتجاه، وقد تختلف السياسة التي يستخدمها النظام لتحديد الاتجاه من جهاز إلى آخر. من ناحية أخرى، يتطابق تحديد fullUser إلى حدّ أكبر مع السلوك الذي حدّده المستخدم للجهاز: إذا كان المستخدم قد قفل التدوير باستخدام أداة الاستشعار، يتبع التطبيق الخيار المفضَّل للمستخدم. بخلاف ذلك، يسمح النظام بأي من اتجاهات الشاشة الأربعة المحتملة (عمودي أو أفقي أو عمودي عكسي أو أفقي عكسي). يمكنك الاطّلاع على android:screenOrientation.

2. تحديد حجم الشاشة

عند ضبط البيان على إتاحة جميع الاتجاهات التي يسمح بها المستخدم، يمكنك تحديد اتجاه التطبيق آليًا بناءً على حجم الشاشة.

أضِف مكتبات Jetpack WindowManager إلى ملف build.gradle أو build.gradle.kts في الوحدة:

Kotlin

implementation("androidx.window:window:version")
implementation("androidx.window:window-core:version")

رائع

implementation 'androidx.window:window:version'
implementation 'androidx.window:window-core:version'

استخدِم طريقة Jetpack WindowManager WindowMetricsCalculator#computeMaximumWindowMetrics() لمعرفة حجم شاشة الجهاز ككائن WindowMetrics. ويمكن مقارنة مقاييس الفترات بفئات حجم النافذة لتحديد وقت تقييد الاتجاه.

توفر فئات حجم Windows نقاط التوقف بين الشاشات الصغيرة والكبيرة.

استخدِم نقطتَي الإيقاف WindowWidthSizeClass#COMPACT وWindowHeightSizeClass#COMPACT لتحديد حجم الشاشة:

Kotlin

/** Determines whether the device has a compact screen. **/
fun compactScreen() : Boolean {
    val metrics = WindowMetricsCalculator.getOrCreate().computeMaximumWindowMetrics(this)
    val width = metrics.bounds.width()
    val height = metrics.bounds.height()
    val density = resources.displayMetrics.density
    val windowSizeClass = WindowSizeClass.compute(width/density, height/density)

    return windowSizeClass.windowWidthSizeClass == WindowWidthSizeClass.COMPACT ||
        windowSizeClass.windowHeightSizeClass == WindowHeightSizeClass.COMPACT
}

Java

/** Determines whether the device has a compact screen. **/
private boolean compactScreen() {
    WindowMetrics metrics = WindowMetricsCalculator.getOrCreate().computeMaximumWindowMetrics(this);
    int width = metrics.getBounds().width();
    int height = metrics.getBounds().height();
    float density = getResources().getDisplayMetrics().density;
    WindowSizeClass windowSizeClass = WindowSizeClass.compute(width/density, height/density);
    return windowSizeClass.getWindowWidthSizeClass() == WindowWidthSizeClass.COMPACT ||
                windowSizeClass.getWindowHeightSizeClass() == WindowHeightSizeClass.COMPACT;
}
    ملاحظة:
  • يتم تنفيذ الأمثلة أعلاه كأساليب لنشاط؛ وبالتالي، تمت الإشارة إلى النشاط باسم this في وسيطة computeMaximumWindowMetrics().
  • يتم استخدام طريقة computeMaximumWindowMetrics() بدلاً من computeCurrentWindowMetrics() لأنّه يمكن تشغيل التطبيق في وضع النوافذ المتعددة، الذي يتجاهل إعداد اتجاه الشاشة. وليس هناك جدوى من تحديد حجم نافذة التطبيق وتجاوز إعداد الاتجاه ما لم تكن نافذة التطبيق هي شاشة الجهاز بأكملها.

راجِع WindowManager للحصول على تعليمات حول الإفصاح عن التبعيات لإتاحة طريقة computeMaximumWindowMetrics() في تطبيقك.

3. تجاهُل إعداد بيان التطبيق

عند التأكّد من أنّ حجم الشاشة مصغّر في الجهاز، يمكنك استدعاء التطبيق Activity#setRequestedOrientation() لإلغاء إعداد screenOrientation في البيان:

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    requestedOrientation = if (compactScreen())
        ActivityInfo.SCREEN_ORIENTATION_PORTRAIT else
        ActivityInfo.SCREEN_ORIENTATION_FULL_USER
    ...
    // Replace with a known container that you can safely add a
    // view to where the view won't affect the layout and the view
    // won't be replaced.
    val container: ViewGroup = binding.container

    // Add a utility view to the container to hook into
    // View.onConfigurationChanged. This is required for all
    // activities, even those that don't handle configuration
    // changes. You can't use Activity.onConfigurationChanged,
    // since there are situations where that won't be called when
    // the configuration changes. View.onConfigurationChanged is
    // called in those scenarios.
    container.addView(object : View(this) {
        override fun onConfigurationChanged(newConfig: Configuration?) {
            super.onConfigurationChanged(newConfig)
            requestedOrientation = if (compactScreen())
                ActivityInfo.SCREEN_ORIENTATION_PORTRAIT else
                ActivityInfo.SCREEN_ORIENTATION_FULL_USER
        }
    })
}

Java

@Override
protected void onCreate(Bundle savedInstance) {
    super.onCreate(savedInstanceState);
    if (compactScreen()) {
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
    } else {
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_USER);
    }
    ...
    // Replace with a known container that you can safely add a
    // view to where the view won't affect the layout and the view
    // won't be replaced.
    ViewGroup container = binding.container;

    // Add a utility view to the container to hook into
    // View.onConfigurationChanged. This is required for all
    // activities, even those that don't handle configuration
    // changes. You can't use Activity.onConfigurationChanged,
    // since there are situations where that won't be called when
    // the configuration changes. View.onConfigurationChanged is
    // called in those scenarios.
    container.addView(new View(this) {
        @Override
        protected void onConfigurationChanged(Configuration newConfig) {
            super.onConfigurationChanged(newConfig);
            if (compactScreen()) {
                setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
            } else {
                setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_USER);
            }
        }
    });
}

بإضافة المنطق إلى طريقتَي onCreate() وView.onConfigurationChanged()، يمكنك الحصول على الحد الأقصى لمقاييس النوافذ وإلغاء إعداد الاتجاه عندما يتم تغيير حجم النشاط أو نقله بين الشاشات، مثلاً بعد تدوير الجهاز أو عندما يكون جهاز قابل للطي مطويًا أو غير مطوي. لمزيد من المعلومات عن وقت حدوث تغييرات الإعدادات والوقت الذي تؤدي فيه إلى إعادة إنشاء النشاط، يُرجى الاطّلاع على مقالة تغييرات إعدادات الاسم المعرِّف.

النتائج

يجب أن يظل تطبيقك الآن في الاتجاه العمودي على الشاشات الصغيرة بغض النظر عن تدوير الجهاز. على الشاشات الكبيرة، يجب أن يكون التطبيق متوافقًا مع الاتجاهات الأفقية والعمودية.

مراجع إضافية

للمساعدة في ترقية تطبيقك بحيث يتوافق مع جميع إعدادات الأجهزة طوال الوقت، يُرجى الاطّلاع على ما يلي:

إيقاف تشغيل الوسائط مؤقتًا واستئنافها باستخدام مفتاح المسافة للوحة المفاتيح الخارجية

تقييم أربع نجوم

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

عندما تكون الوسائط هي العنصر الوحيد في النافذة (مثل تشغيل الفيديو في وضع ملء الشاشة)، يمكنك الاستجابة لأحداث الضغط على المفاتيح على مستوى النشاط، أو في Jetpack Compose، على مستوى الشاشة.

أفضل الممارسات

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

المكوّنات

  • KEYCODE_SPACE: ثابت رمز المفتاح لمفتاح المسافة.

إنشاء

  • onPreviewKeyEvent: Modifier يتيح لمكوِّن اعتراض الأحداث الرئيسية للجهاز عندما يكون التركيز (أو أحد عناصره الفرعية).
  • onKeyEvent: على غرار onPreviewKeyEvent، يتيح Modifier هذا لمكوِّن اعتراض الأحداث الرئيسية للأجهزة عندما يتم التركيز على الجهاز (أو أحد عناصره الفرعية).

الملفات الشخصية

  • onKeyUp(): يتم استدعاء هذا الإجراء عند تحرير مفتاح ولا تتم معالجته من خلال ملف شخصي ضمن نشاط.

الخطوات

ملخّص

تستجيب التطبيقات والتطبيقات المستندة إلى العرض المستنِدة إلى Jetpack Compose إلى ضغطات مفاتيح لوحة المفاتيح بطرق مشابهة: يجب أن يستمع التطبيق إلى أحداث الضغط على المفاتيح، ويفلتر الأحداث، ويستجيب لعمليات الضغط على المفاتيح التي تم اختيارها، مثل الضغط على مفتاح Spacebar.

1. الاستماع إلى أحداث لوحة المفاتيح

الملفات الشخصية

في نشاط في تطبيقك، يمكنك إلغاء طريقة onKeyUp():

Kotlin

override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
    ...
}

Java

@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
    ...
}

يتم استدعاء الطريقة في كل مرة يتم فيها رفع إصبعك عن مفتاح تم الضغط عليه، بحيث يتم تنشيطها مرة واحدة بالضبط لكل ضغطة مفتاح.

إنشاء

باستخدام Jetpack Compose، يمكنك الاستفادة من عنصر التعديل onPreviewKeyEvent أو onKeyEvent في الشاشة التي تدير ضغطة المفتاح:

Column(modifier = Modifier.onPreviewKeyEvent { event ->
    if (event.type == KeyEventType.KeyUp) {
        ...
    }
    ...
})

أو

Column(modifier = Modifier.onKeyEvent { event ->
    if (event.type == KeyEventType.KeyUp) {
        ...
    }
    ...
})

2. فلترة الضغطات على مفتاح المسافة

داخل الإجراء onKeyUp() أو طريقتي التعديل onPreviewKeyEvent وonKeyEvent، يمكنك الفلترة حسب KeyEvent.KEYCODE_SPACE لإرسال الحدث الصحيح إلى مكوّن الوسائط:

الملفات الشخصية

Kotlin

if (keyCode == KeyEvent.KEYCODE_SPACE) {
    togglePlayback()
    return true
}
return false

Java

if (keyCode == KeyEvent.KEYCODE_SPACE) {
    togglePlayback();
    return true;
}
return false;

إنشاء

Column(modifier = Modifier.onPreviewKeyEvent { event ->
    if (event.type == KeyEventType.KeyUp && event.key == Key.Spacebar) {
        ...
    }
    ...
})

أو

Column(modifier = Modifier.onKeyEvent { event ->
    if (event.type == KeyEventType.KeyUp && event.key == Key.Spacebar) {
        ...
    }
    ...
})

النتائج

يمكن لتطبيقك الآن الاستجابة للضغطات على مفتاح Spacebar لإيقاف الفيديو أو الوسائط الأخرى مؤقتًا واستئناف تشغيله.

مراجع إضافية

لمزيد من المعلومات عن أحداث لوحة المفاتيح وكيفية إدارتها، راجِع التعامل مع إدخال لوحة المفاتيح.

رفض راحة اليد بقلم الشاشة

تقييم من خمس نجوم

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

أفضل الممارسات

يجب أن يرصد تطبيقك أحداث اللمس الغريبة ويتجاهلها. يلغي Android لمسة راحة اليد من خلال إرسال عنصر MotionEvent. ابحث في الكائن عن ACTION_CANCEL أو ACTION_POINTER_UP وFLAG_CANCELED لتحديد ما إذا كان سيتم رفض الإيماءة التي تسببها لمس راحة اليد.

المكوّنات

  • MotionEvent: يمثّل أحداث اللمس والحركة. تحتوي على المعلومات اللازمة لتحديد ما إذا كان يجب تجاهل حدث معيّن.
  • OnTouchListener#onTouch(): يتلقى MotionEvent عنصرًا.
  • MotionEvent#getActionMasked(): تعرض الإجراء المرتبط بحدث متحرّك.
  • ACTION_CANCEL: عدد ثابت MotionEvent يشير إلى ضرورة التراجع عن الإيماءة.
  • ACTION_POINTER_UP: ثابت MotionEvent يشير إلى ارتفاع مؤشر ما غير المؤشر الأول (أي أنّه تراجع عن الاتصال بشاشة الجهاز).
  • FLAG_CANCELED: قيمة ثابتة للسمة MotionEvent تشير إلى أنّ المؤشر الذي ارتفع إلى الأعلى تسبّب في حدوث حدث لمس غير مقصود. تمت الإضافة إلى أحداث "ACTION_POINTER_UP" و"ACTION_CANCEL" على نظام التشغيل Android 13 (المستوى 33 لواجهة برمجة التطبيقات) والإصدارات الأحدث.

الخطوات

ملخّص

يمكنك فحص عناصر MotionEvent التي تم إرسالها إلى تطبيقك. واستخدام واجهات برمجة تطبيقات MotionEvent لتحديد خصائص الأحداث:

  • أحداث النقاط الأحادية: ابحث عن ACTION_CANCEL. على الإصدار 13 من نظام التشغيل Android والإصدارات الأحدث، تحقَّق أيضًا من FLAG_CANCELED.
  • الأحداث المتعدّدة النقاط: على نظام التشغيل Android 13 والإصدارات الأحدث، تحقَّق من ACTION_POINTER_UP وFLAG_CANCELED.

يمكنك الرد على ACTION_CANCEL وACTION_POINTER_UP من أصل FLAG_CANCELED حدث.

1. الحصول على كائنات أحداث متحركة

إضافة OnTouchListener إلى تطبيقك:

Kotlin

val myView = findViewById<View>(R.id.myView).apply {
    setOnTouchListener { view, event ->
        // Process motion event.
    }
}

Java

View myView = findViewById(R.id.myView);
myView.setOnTouchListener( (view, event) -> {
    // Process motion event.
});
2. تحديد إجراء الحدث والعلامات

تحقَّق من وجود ACTION_CANCEL، الذي يشير إلى حدث مؤشّر الماوس فوق جميع مستويات واجهة برمجة التطبيقات. على نظام التشغيل Android 13 والإصدارات الأحدث، تحقَّق من ACTION_POINTER_UP للتأكّد من توفّر FLAG_CANCELED..

Kotlin

val myView = findViewById<View>(R.id.myView).apply {
    setOnTouchListener { view, event ->
        when (event.actionMasked) {
            MotionEvent.ACTION_CANCEL -> {
                //Process canceled single-pointer motion event for all SDK versions.
            }
            MotionEvent.ACTION_POINTER_UP -> {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
                   (event.flags and MotionEvent.FLAG_CANCELED) == MotionEvent.FLAG_CANCELED) {
                    //Process canceled multi-pointer motion event for Android 13 and higher.
                }
            }
        }
        true
    }
}

Java

View myView = findViewById(R.id.myView);
myView.setOnTouchListener( (view, event) -> {
    switch (event.getActionMasked()) {
        case MotionEvent.ACTION_CANCEL:
            // Process canceled single-pointer motion event for all SDK versions.
        case MotionEvent.ACTION_UP:
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
               (event.getFlags() & MotionEvent.FLAG_CANCELED) == MotionEvent.FLAG_CANCELED) {
                //Process canceled multi-pointer motion event for Android 13 and higher.
            }
    }
    return true;
});
3. التراجع عن الإيماءة

بعد تحديد لمسة راحة اليد، يمكنك التراجع عن التأثيرات التي تظهر على الشاشة للإيماءة.

يجب أن يحتفظ تطبيقك بسجلّ إجراءات المستخدم حتى يمكن التراجع عن الإدخالات غير المقصودة، مثل لمس راحة اليد. للاطّلاع على مثال، راجِع تنفيذ تطبيق رسم أساسي في الدرس التطبيقي حول ترميز تحسين توافق قلم الشاشة في تطبيق Android.

النتائج

يمكن لتطبيقك الآن تحديد ورفض لمسات راحة اليد في أحداث المؤشرات المتعددة على Android 13 والمستويات الأعلى من واجهة برمجة التطبيقات، ولأحداث المؤشر الواحد على جميع مستويات واجهة برمجة التطبيقات.

مراجع إضافية

لمزيد من المعلومات، يُرجى الاطّلاع على ما يلي:

إدارة حالة WebView

تقييم من ثلاث نجوم

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

أفضل الممارسات

عليك تقليل عدد مرات إعادة إنشاء "WebView". WebView جيد في إدارة حالته، ويمكنك الاستفادة من هذه الجودة من خلال إدارة أكبر عدد ممكن من التغييرات على الإعدادات. يجب أن يتعامل تطبيقك مع تغييرات الإعدادات لأنّ إعادة إنشاء Activity (طريقة التعامل مع تغييرات الإعدادات في النظام) تعيد إنشاء WebView أيضًا، ما يؤدي إلى فقدان WebView لحالته.

المكوّنات

  • android:configChanges: سمة العنصر <activity> في البيان يسرد تغييرات الضبط التي عالجها النشاط.
  • View#invalidate(): الطريقة التي تؤدي إلى إعادة رسم العرض مكتسَب من قِبل "WebView".

الخطوات

ملخّص

لحفظ حالة WebView، تجنَّب إعادة إنشاء Activity بقدر الإمكان، ثم أبقِ العلامة WebView غير صالحة حتى تتمكّن من تغيير حجمها مع الاحتفاظ بحالتها.

1. إضافة تغييرات الإعدادات إلى ملف AndroidManifest.xml الخاص بتطبيقك

تجنُّب إعادة إنشاء النشاط من خلال تحديد تغييرات الإعدادات التي يعالجها تطبيقك (وليس من خلال النظام):

<activity
  android:name=".MyActivity"
  android:configChanges="screenLayout|orientation|screenSize
      |keyboard|keyboardHidden|smallestScreenSize" />

2. إلغاء صلاحية WebView عندما يتلقّى تطبيقك تغييرًا في الإعدادات

Kotlin

override fun onConfigurationChanged(newConfig: Configuration) {
    super.onConfigurationChanged(newConfig)
    webView.invalidate()
}

Java

@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    webview.invalidate();
}

تنطبق هذه الخطوة على نظام العرض فقط، لأنّ Jetpack Compose لا يحتاج إلى إلغاء صلاحية أي عنصر لتغيير حجم عناصر Composable بشكل صحيح. ومع ذلك، يعيد Compose إنشاء WebView غالبًا إذا لم تتم إدارته بشكلٍ صحيح. استخدِم برنامج تضمين Accompanist WebView لحفظ حالة WebView واستعادتها في تطبيقات Compose الخاصة بك.

النتائج

تحتفظ الآن مكوّنات WebView في تطبيقك بحالتها وموضع التمرير عبر التغييرات المتعددة في الإعدادات، بدءًا من تغيير الحجم ووصولاً إلى تغيير الاتجاه ووصولاً إلى الطي والفتح.

مراجع إضافية

لمعرفة المزيد من المعلومات عن تغييرات الإعدادات وكيفية إدارتها، يُرجى الاطّلاع على مقالة التعامل مع تغييرات الإعدادات.

إدارة حالة RecyclerView

تقييم من ثلاث نجوم

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

أفضل الممارسات

يجب أن يحافظ RecyclerView على حالته، لا سيّما موضع التمرير، وحالة عناصر القائمة خلال جميع تغييرات الإعدادات.

المكوّنات

الخطوات

ملخّص

يمكنك ضبط سياسة استعادة حالة RecyclerView.Adapter لحفظ موضع التمرير RecyclerView. حفظ حالة RecyclerView عنصر قائمة يمكنك إضافة حالة عناصر القائمة إلى محوّل RecyclerView واستعادة حالة عناصر القائمة عند ربطها بعنصر ViewHolder.

1. تفعيل سياسة استعادة الحالة "Adapter"

يمكنك تفعيل سياسة استعادة حالة محوّل RecyclerView للاحتفاظ بموضع التمرير في RecyclerView في كل تغييرات الإعدادات. أضِف مواصفات السياسة إلى الدالة الإنشائية للمهايئ:

Kotlin

class MyAdapter() : RecyclerView.Adapter() {
    init {
        stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY
    }
    ...
}

Java

class MyAdapter extends RecyclerView.Adapter {

    public Adapter() {
        setStateRestorationPolicy(StateRestorationPolicy.PREVENT_WHEN_EMPTY);
    }
    ...
}

2. حفظ حالة عناصر القائمة ذات الحالة

يمكنك حفظ حالة عناصر قائمة RecyclerView المعقّدة، مثل العناصر التي تحتوي على عناصر EditText. على سبيل المثال، لحفظ حالة EditText، أضِف معاودة الاتصال مشابهة لمعالج onClick لتسجيل تغييرات النص. ضمن معاودة الاتصال، حدِّد البيانات التي تريد حفظها:

Kotlin

input.addTextChangedListener(
    afterTextChanged = { text ->
        text?.let {
            // Save state here.
        }
    }
)

Java

input.addTextChangedListener(new TextWatcher() {
    
    ...

    @Override
    public void afterTextChanged(Editable s) {
        // Save state here.
    }
});

يُرجى توضيح اسم معاودة الاتصال في "Activity" أو "Fragment". استخدِم ViewModel لتخزين الولاية.

3. إضافة حالة عنصر القائمة إلى Adapter

أضِف حالة عناصر القائمة إلى RecyclerView.Adapter. مرِّر حالة العنصر إلى الدالة الإنشائية للمحوّل عند إنشاء المضيف Activity أو Fragment:

Kotlin

val adapter = MyAdapter(items, viewModel.retrieveState())

Java

MyAdapter adapter = new MyAdapter(items, viewModel.retrieveState());

4. استرداد حالة عنصر القائمة في ViewHolder للمحوِّل

عند ربط ViewHolder بعنصر في "RecyclerView.Adapter"، يمكنك استعادة حالة العنصر:

Kotlin

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
    ...
    val item = items[position]
    val state = states.firstOrNull { it.item == item }

    if (state != null) {
        holder.restore(state)
    }
}

Java

@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
    ...
    Item item = items[position];
    Arrays.stream(states).filter(state -> state.item == item)
        .findFirst()
        .ifPresent(state -> holder.restore(state));
}

النتائج

بإمكان "RecyclerView" الآن استعادة موضع التمرير وحالة كل عنصر في قائمة "RecyclerView".

مراجع إضافية

إدارة لوحة المفاتيح القابلة للفصل

تقييم من ثلاث نجوم

يساعد دعم لوحات المفاتيح القابلة للفصل في زيادة إنتاجية المستخدمين إلى أقصى حد على الأجهزة ذات الشاشات الكبيرة. يقوم Android بإجراء تغيير في التهيئة في كل مرة يتم فيها توصيل لوحة مفاتيح بأحد الأجهزة أو فصلها عن الجهاز، مما قد يتسبب في فقدان حالة واجهة المستخدم. يمكن لتطبيقك إما حفظ حالته واستعادتها، ما يتيح للنظام إمكانية معالجة النشاط، أو تقييد إعادة إنشاء النشاط عند إجراء تغييرات على إعدادات لوحة المفاتيح. وفي جميع الحالات، يتم تخزين كل البيانات المرتبطة بلوحة المفاتيح في كائن Configuration. يحتوي الحقلان keyboard وkeyboardHidden لعنصر الإعداد على معلومات حول نوع لوحة المفاتيح ومدى توفّرها.

أفضل الممارسات

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

يتضمن دعم لوحات المفاتيح الخارجية تغييرات في التهيئة، ويمكنك إدارتها بأي من الطريقتين التاليتين:

  1. اسمح للنظام بإعادة إنشاء النشاط قيد التشغيل حاليًا، وستتولى إدارة حالة تطبيقك.
  2. إدارة تغيير الإعدادات بنفسك (لن تتم إعادة إنشاء النشاط):
    • توضيح جميع قيم الإعدادات المتعلّقة بلوحة المفاتيح
    • إنشاء معالِج تغيير الإعدادات

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

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

وبما أنّ الطريقة الوحيدة الموثوقة لمعالجة تغييرات الإعدادات هي تجاوز طريقة العرض onConfigurationChanged()، يمكنك إضافة مثيل عرض جديد للنشاط على تطبيقك والاستجابة في معالج onConfigurationChanged() لطريقة العرض للتغييرات التي تحدث بسبب إرفاق لوحة المفاتيح أو فصلها.

المكوّنات

  • android:configChanges: سمة العنصر <activity> في بيان التطبيق يُعلِم النظام بالتغييرات في الإعدادات التي يديرها التطبيق.
  • View#onConfigurationChanged(): الطريقة التي تتفاعل مع نشر إعدادات تطبيق جديدة.

الخطوات

ملخّص

يُرجى تعريف السمة configChanges وإضافة قيم متعلّقة بلوحة المفاتيح. أضِف View إلى العرض الهرمي لطريقة عرض النشاط مع رصد أي تغييرات في الإعدادات.

1. تعريف السمة configChanges

يمكنك تعديل العنصر <activity> في بيان التطبيق عن طريق إضافة قيم keyboard|keyboardHidden إلى قائمة تغييرات الإعدادات المُدارة مسبقًا:

<activity
      …
      android:configChanges="...|keyboard|keyboardHidden">

2. إضافة طريقة عرض فارغة إلى التدرّج الهرمي لطريقة العرض

تعريف طريقة عرض جديدة وإضافة رمز المعالج الخاص بك داخل طريقة onConfigurationChanged() الخاصة بطريقة العرض:

Kotlin

val v = object : View(this) {
  override fun onConfigurationChanged(newConfig: Configuration?) {
    super.onConfigurationChanged(newConfig)
    // Handler code here.
  }
}

Java

View v = new View(this) {
    @Override
    protected void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        // Handler code here.
    }
};

النتائج

سيستجيب تطبيقك الآن للوحة مفاتيح خارجية يتم إرفاقها أو فصلها بدون إعادة إنشاء النشاط الجاري.

مراجع إضافية

للتعرّف على كيفية حفظ حالة واجهة المستخدم للتطبيق أثناء تغييرات الإعدادات مثل مرفق لوحة المفاتيح أو الانفصال، يمكنك الاطّلاع على حفظ حالات واجهة المستخدم.