إدخالات Android: التعامل مع المخاوف والاستعداد لـ Android Q

Android Q هو الإصدار العاشر من Android مع مستوى API 29. أحد الأفكار الرئيسية للإصدار الجديد هو مفهوم الحافة إلى الحافة ، عندما تشغل التطبيقات الشاشة بأكملها ، من الأسفل إلى الأعلى. هذا يعني أن شريط الحالة وشريط التنقل يجب أن يكونا شفافين. ولكن ، إذا كانت شفافة ، فلا توجد واجهة مستخدم للنظام - فهي تتداخل مع المكونات التفاعلية للتطبيق. يتم حل هذه المشكلة مع أقحم.

يتجنب مطورو الهواتف المحمولة الإدخالات ويسببون الخوف في نفوسهم ولكن في Android Q لن يكون من الممكن الالتفاف حول المدخلات - سيكون عليك دراستها وتطبيقها. في الواقع ، لا يوجد شيء معقد بشأن المدخلات: فهي تُظهر عناصر الشاشة التي تتقاطع مع واجهة النظام وتقترح كيفية نقل العنصر بحيث لا يتعارض مع واجهة مستخدم النظام. سوف يخبر كونستانتين تسخوفريبوف عن كيفية عمل المدخلات وكيف أنها مفيدة.


كونستانتين تسخوفريبوف (terrakok) يعمل في Redmadrobot . شارك في Android لمدة 10 سنوات ولديه خبرة كبيرة في العديد من المشاريع التي لم يكن فيها مكان للإدخالات ، تمكنوا دائمًا من التنقل بطريقة أو بأخرى. سوف يخبر كونستانتين عن تاريخ طويل لتجنب مشكلة المدخلات ، عن دراسة ومحاربة Android. سيأخذ في الاعتبار المهام النموذجية من تجربته حيث سيكون من الممكن تطبيق المدخلات ، ويوضح كيفية التوقف عن الخوف من لوحة المفاتيح ، والتعرف على حجمها والاستجابة للمظهر.

ملحوظة. تمت كتابة المقالة بناءً على تقرير من كونستانتين في Saint AppsConf 2019 . استخدم التقرير مواد من عدة مقالات حول المدخلات. رابط لهذه المواد في النهاية.

المهام النموذجية


شريط حالة اللون. في العديد من المشاريع ، يرسم المصمم شريط الحالة الملون. أصبح من المألوف عندما جاء Android 5 مع تصميم المواد الجديد.



كيف ترسم شريط الحالة؟ الابتدائية - إضافة colorPrimaryDarkويتم استبدال اللون.

 <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
    ...
    <item name="colorPrimaryDark">@color/colorAccent</item>
    ...
</style>

بالنسبة إلى Android أعلى الإصدار الخامس (API من 21 وما فوق) ، يمكنك تعيين معلمات خاصة في الموضوع:

<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
    ...
    <item name="android:statusBarColor">@color/colorAccent</item>
    ...
</style>

شريط الحالة متعدد الألوان . في بعض الأحيان ، يقوم المصممون على شاشات مختلفة برسم شريط الحالة بألوان مختلفة.



لا بأس ، أسهل طريقة تعمل هي في مواضيع مختلفة في أنشطة مختلفة .

الطريقة الأكثر إثارة للاهتمام هي تغيير الألوان مباشرة في وقت التشغيل .

override fun onCreateView(...): View {
    requireActivity().window.statusBarColor = requireContext().getColor(R.color.colorPrimary)
    ...
}

يتم تغيير المعلمة من خلال علامة خاصة. لكن الشيء الرئيسي هو عدم نسيان تغيير اللون مرة أخرى عندما يخرج المستخدم من الشاشة مرة أخرى.

شريط الحالة الشفاف. هذا أصعب. في أغلب الأحيان ، ترتبط الشفافية بالخرائط ، لأنه من الأفضل رؤية الشفافية على الخرائط. في هذه الحالة ، كما كان من قبل ، قمنا بتعيين معلمة خاصة:

<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">    
    ...
    <item name="android:windowTranslucentStatus">true</item>
    ...
</style>

هنا ، بالطبع ، هناك حيلة معروفة - لزيادة المسافة البادئة ، وإلا سيتم فرض شريط الحالة على الرمز وسيكون قبيحًا.


ولكن على الشاشات الأخرى كل شيء يكسر.



كيفية حل هذه المشكلة؟ الطريقة الأولى التي تتبادر إلى الذهن هي الأنشطة المختلفة: لدينا مواضيع مختلفة ، معلمات مختلفة ، تعمل بشكل مختلف.

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



لا أحد يحب الخيار على اليسار ، ولكن لتحويله إلى خيار على اليمين ، هناك حل بسيط.

<activity
    ...
    android:windowSoftInputMode="adjustResize">
    ...
</activity>

يمكن الآن تغيير حجم النشاط عندما تظهر لوحة المفاتيح. يعمل ببساطة وبشدة. ولكن لا تنس خدعة أخرى - لف كل شيء فيها ScrollView. فجأة ستحتل لوحة المفاتيح الشاشة بأكملها وسيكون هناك شريط صغير في الأعلى؟

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



لقد قمنا بتدريب العديد من العكازات باستخدام لوحة المفاتيح وشريط الحالة الشفاف ، والآن من الصعب إيقافنا. انتقل إلى StackOverflow وانسخ الرمز الجميل.

boolean isOpened = false;

public void setListenerToRootView() {
    final View activityRootView = getWindow().getDecorView().findViewById(android.R.id.content);
    activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
    
            int heightDiff = activityRootView.getRootView().getHeight() - activityRootView.getHeight();      
            if (heightDiff > 100) { // 99% of the time the height diff will be due to a keyboard.       
                Toast.makeText(getApplicationContext(), "Gotcha!!! softKeyboardup", 0).show();

                if (isOpened == false) {
                   //Do two things, make the view top visible and the editText smaller
                }
                isOpened = true;
            } else if (isOpened == true) {
                Toast.makeText(getApplicationContext(), "softkeyborad Down!!!", 0).show();
                isOpened = false;
            }    
        }
    });
}

هنا ، حتى يتم احتساب احتمال ظهور لوحة المفاتيح. تعمل الشفرة في أحد المشاريع التي استخدمناها ، ولكن لفترة طويلة.

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

ماذا فعلنا قبل Android 10


نحن (في Redmadrobot) نقوم بتطوير تطبيقات عالية الجودة إلى حد ما ، لكننا تجنبنا منذ فترة طويلة. كيف استطعنا تجنبها دون عكازات كبيرة وفي نشاط واحد؟

ملحوظة. لقطات وشفرة مأخوذة من مشروع GitFox للحيوان الأليف .

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



في الجزء العلوي ، قمنا أولاً بتعيين المعلمة القائلة بأن شريط الحالة أسود مع الشفافية. كيف تبدو من حيث التخطيط والرمز؟



في الشكل ، التجريد: الكتلة الحمراء هي نشاط التطبيق ، والأزرق عبارة عن جزء به روبوت تنقل (مع علامات التبويب) ، وداخله يتم تبديل الأجزاء الخضراء ذات المحتوى. يمكن ملاحظة أن شريط الأدوات ليس تحت شريط الحالة. كيف حققنا ذلك؟

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

العلامة FitSystemWindow=”true”الحشو إلى الحاوية التي حددتها. لكن التسلسل الهرمي مهم: إذا قام أحد الوالدين بتعيين هذا العلم على "صحيح" ، فلن يؤخذ توزيعه في الاعتبار ، لأن الحاوية قد طبقت بالفعل المسافة البادئة.

يعمل العلم ، ولكن تظهر مشكلة أخرى. تخيل شاشة تحتوي على علامتي تبويب. عند التبديل ، يتم إطلاق المعاملة ، مما يؤدي إلى replaceتجزئة جزء على آخر ، وكل شيء ينكسر.



يحتوي الجزء الثاني أيضًا على مجموعة أعلام FitSystemWindow=”true”، ويجب ألا يحدث هذا. لكن ماذا حدث ، لماذا؟ الجواب هو أن هذا عكاز ، وفي بعض الأحيان لا يعمل.

لكننا وجدنا حلاً على Stack Overflow : استخدم الجذر ليس للشظايا FrameLayout، ولكن CoordinatorLayout. تم إنشاؤه لأغراض أخرى ، لكنه يعمل هنا.

لماذا يعمل؟


دعونا نرى في المصدر ما يحدث فيه CoordinatorLayout.

@Override
public void onAttachedToWindow() {   
    super.onAttachedToWindow();
    ...    
    if (mLastInsets == null && ViewCompat.getFitsSystemWindows(this)) {         
        // We're set to fitSystemWindows but we haven't had any insets yet...
        // We should request a new dispatch of window insets
        ViewCompat.requestApplyInsets(this);
    }     
    ...
}

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

في ربيع عام 2019 ، قمنا بتطوير التطبيق ، وهو الوقت الذي مرت فيه Google I / O. لم نكتشف كل شيء حتى الآن ، لذلك واصلنا التمسك بالتحيز. لكننا لاحظنا أن تبديل علامات التبويب في الأسفل بطيء إلى حد ما ، لأن لدينا تخطيطًا معقدًا ، محملًا بواجهة المستخدم. نجد طريقة بسيطة لحل هذه المشكلة - قم بالتغيير replaceإلى show/hideأنه في كل مرة لا تقوم فيها بإعادة إنشاء التخطيط.



نتغير ، ومرة ​​أخرى لا شيء يعمل - كل شيء قد كسر! يبدو أنه من غير الممكن تشتيت العكازات ، عليك أن تفهم سبب عملها. نحن ندرس الشفرة وتبين أن أي مجموعة ViewGroup قادرة أيضًا على العمل مع المدخلات.


@Override
public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {    
    insets = super.dispatchApplyWindowInsets(insets);     
    if (!insets.isConsumed()) {         
        final int count = getChildCount();        
        for (int i = 0; i < count; i++) {
            insets = getChildAt(i).dispatchApplyWindowInsets(insets);             
            if (insets.isConsumed()) {          
                break;
            }
        }
    }
    return insets;
}

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



يوجد في الداخل الجزء الأول الذي يحتوي على مجموعة العلم fitSystemWindow=”true”. هذا يعني أن الجزء الأول يعالج المدخلات. بعد ذلك ، نسمي الجزء HIDEالأول SHOWوالثاني. لكن يبقى الأول في التخطيط - يتم ترك طريقة العرض الخاصة به ، مخفية فقط.

تمر الحاوية من خلال وجهة نظرها: فهي تأخذ الجزء الأول ، وتعطيها إدخالات ، ومنه fitSystemWindow=”true”- أخذها وعالجها. تمامًا ، بدا FrameLayout أنه تمت معالجة المدخلات ، ولم يعطها للجزء الثاني. كل شيء يعمل على مايرام. لكن هذا لا يناسبنا ، فماذا نفعل؟

نكتب ViewGroup الخاصة بنا


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

class WindowInsetFrameLayout @JvmOverloads constructor(
    context: Context,      
    attrs: AttributeSet? = null,     
    defStyleRes: Int = 0
) : FrameLayout(context, attrs, defStyleRes) {

    override fun dispatchApplyWindowInsets(insets: WindowInsets): WindowInsets { 
        for (child in (0 until childCount)) {
            getChildAt(child).dispatchApplyWindowInsets(insets)
        }
        return insets.consumeSystemWindowInsets()
    }
}

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

أندرويد 10


أظهر لنا Android 10 مفهومين مهمين لواجهة المستخدم يوصى بشدة بالالتزام بهما: التنقل من الحافة إلى الحافة والتنقل الإيمائي.

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



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

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


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

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

أقحم ماذا تريد ان تعرف عنه؟


تم إنشاء إدخالات لإخافة المطورين.
لقد كانوا يفعلون ذلك بشكل مثالي منذ اللحظة التي ظهر فيها في Android 5.

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

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

للتعامل مع أقحم ، استخدم Windowlnsetsلنظام Android 10 WindowInsetsCompatوالإصدارات الأخرى.

هناك 5 أنواع مختلفة من الملحقات في Android 10ومكافأة واحدة ، والتي لا تسمى أقحم ، ولكن على خلاف ذلك. سنتعامل مع جميع الأنواع ، لأن معظمهم يعرفون شيئًا واحدًا فقط - إدخالات نافذة النظام.

إدخالات نافذة النظام


تم تقديمه في Android 5.0 (API 21). حصلت عليها الطريقة getSystemWindowInsets().

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



إدراج عنصر قابل للنقر


ظهرت فقط في Android 10. تم الحصول عليها بواسطة الطريقة getTappableElementInsets().

هذه المدخلات مفيدة فقط للتعامل مع أوضاع شريط التنقل المختلفة . كما يقول Chris Bane نفسه ، يمكنك نسيان هذا النوع من الإدخالات والتفاف حول إدخالات نافذة النظام فقط. ولكن إذا كنت تريد أن يكون التطبيق باردًا بنسبة 100 ٪ ، وليس بنسبة 99.9 ٪ ، فيجب عليك استخدامه.

انظر الى الصورة.



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

تذكر كيف يعمل التنقل بالإيماءات: في الوضع الصحيح ، لا ننقر أبدًا على لوحة جديدة ، ولكننا نسحب دائمًا أصابعنا من الأسفل إلى الأعلى. هذا يعني أنه لا يمكن إزالة الأزرار. يمكننا الاستمرار في الضغط على زر FAB (زر الإجراء العائم) ، لن يتدخل أحد. لذلك ، TappableElementInsetsسيصبح فارغًا لأن FAB ليس ضروريًا للتحرك. ولكن إذا انتقلنا إلى أعلى قليلاً ، فلا بأس.

يظهر الاختلاف فقط مع التنقل بالإيماءات وشريط التنقل الشفاف (تعديل اللون). هذه ليست حالة ممتعة للغاية.



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

إدخالات لفتة النظام


ظهرت فقط في Android 10. تم الحصول عليها بواسطة الطريقة getSystemGestureInsets().

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

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



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

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

إدخالات إيماءة النظام الإلزامية


ظهر فقط في Android 10. هذا هو نوع فرعي من إدخالات إيماءات النظام ، ولكن لا يمكن تجاوزه بواسطة التطبيق.

نستخدم الطريقة getMandatorySystemGestureInsets()لتحديد المنطقة التي لن تعمل فيها. تم ذلك عن قصد بحيث كان من المستحيل تطوير تطبيق "ميئوس منه": إعادة تعريف إيماءة التنقل من الأسفل إلى الأعلى ، مما يسمح لك بالخروج من التطبيق إلى الشاشة الرئيسية. هنا ، يقوم النظام دائمًا بمعالجة الإيماءات نفسها .


ليس بالضرورة أنه سيكون في أسفل الجهاز وليس بالضرورة بهذا الحجم. اعمل معها كتجريد.

كانت هذه أنواع جديدة نسبيا من المدخلات. ولكن هناك تلك التي ظهرت حتى قبل Android 5 وتم تسميتها بشكل مختلف.

إدخالات مستقرة


تم تقديمه مع Android 5.0 (API 21). حصلت عليها الطريقة getStableInsets().

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

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

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

ملحوظة. getStableInsets()ظهرت الطريقة فقط مع API 21. سابقًا ، كانت هناك عدة طرق لكل جانب من الشاشة. 

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

الانفجارات وخطوط العنق


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



حتى 28 APIs لم نتمكن من معالجتها. كان عليّ أن أخمن من خلال ما يدرج هناك. ولكن مع 28 API وما بعدها (من Android السابق) ، ظهرت الفئة رسميًا DisplayCutout. إنه في نفس الإدخالات التي يمكنك من خلالها الحصول على جميع الأنواع الأخرى. يتيح لك الفصل معرفة موقع وحجم القطع الأثرية.

بالإضافة إلى معلومات الموقع ، يتم توفير مجموعة من علامات y للمطور WindowManager.LayoutParams. تسمح لك بتضمين سلوكيات مختلفة حول القواطع. قد يتم عرض المحتوى الخاص بك حولهم ، أو قد لا يتم عرضه: في الوضع الأفقي ووضع عمودي بطرق مختلفة.

الأعلامwindow.attributes.layoutInDisplayCutoutMode =

  • LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT. في الوضع الرأسي - يوجد ، وفي الوضع الأفقي - شريط أسود افتراضيًا.
  • LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER. لا على الإطلاق - شريط أسود.
  • LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES - دائما يكون.

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

insets.displayCutout
    .boundingRects
    .forEach { rect -> ... }

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

فهمت مع 6 أنواع من المدخلات. الآن دعونا نتحدث عن كيفية عملها.

كيف تعمل


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



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

إدراجات انتشار


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



يستدعي النظام الطريقة dispatchApplyWindowInsetsالتي تأتي بها المدخلات. عودة هذا الرأي يجب أن يعود شيئا. ماذا تفعل للتعامل مع هذا السلوك؟

من أجل البساطة ، نعتبر WindowInsets فقط.

معالجة المدخلات


بادئ ذي بدء ، إعادة تعريف الطريقة dispatchApplyWindowInsetsوالتطبيق داخل منطقها تبدو منطقية : جاءت الإدخالات ، وقمنا بتنفيذ كل شيء بداخلها.

إذا قمت بفتح فئة عرض ونظرت إلى Javadoc المكتوب أعلى هذه الطريقة ، يمكنك أن ترى: "لا تتجاوز هذه الطريقة!"
نتعامل مع إدخالات من خلال التفويض أو الميراث.
استخدم التفويض . أتاحت Google الفرصة لفضح مندوبها المسؤول عن التعامل مع المدخلات. يمكنك تثبيته من خلال الطريقة setOnApplyWindowInsetsListener(listener). قمنا بتعيين رد اتصال يعالج هذه الإدخالات.

إذا كان هذا لا يناسبنا لسبب ما ، يمكنك أن ترث من عرض وتجاوز طريقة أخرى onApplyWindowInsets(WindowInsets insets) . نحن نستبدل إدخالاتنا الخاصة بها.

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

ماذا نعود؟

لماذا ترجع شيئًا؟


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

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



عرض العمليات التالية ويعرض إدخالات. الآن ترى ViewGroup أن المدخلات تتم معالجتها فوق وتحت - لم يبق شيء ، كل المعلمات صفر.

المنظر الثالث لا يعرف حتى أن هناك بعض المدخلات وشيء ما يحدث. ستعيد ViewGroup المدخلات إلى الشخص الذي كشفها.

عندما بدأنا في التعامل مع هذا الموضوع ، قمنا بتدوين الفكرة لأنفسنا - دائمًا نعيد نفس المدخلات التي جاءت. أردنا تجنب مثل هذا الموقف عندما لم يدرك بعض View أن هناك إدخالات. بدت الفكرة منطقية. ولكن اتضح أنه لا. لم تضيف Google سلوكًا إضافيًا إلى المدخلات ، كما في المثال.

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

ممارسة


تم الانتهاء من النظرية. دعونا نرى كيف يبدو في الكود ، لأنه من الناحية النظرية كل شيء دائمًا سلس. سنستخدم AndroidX.

ملحوظة. في GitFox ، يتم تنفيذ كل شيء بالفعل في الرمز ، الدعم الكامل لجميع الأشياء الجيدة من Android الجديد. لكي لا تتحقق من إصدارات Android ولا تبحث عن النوع المطلوب من Insets ، استخدم دائمًا view.setOnApplyWindowInsetsListener { v, insets -> ... }الإصدار من AndroidX - بدلاً من ذلك ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets -> ... }.

هناك شاشة بها شريط تنقل مظلم. لا يتداخل مع أي عنصر في الأعلى. كل شيء بالطريقة التي اعتدنا عليها.


ولكن تبين أننا الآن بحاجة إلى جعلها شفافة. كيف؟

قم بتشغيل الشفافية


أبسط شيء هو الإشارة في الموضوع إلى أن شريط الحالة وشريط التنقل شفافان.

<style name="AppTheme" parent="Theme.MaterialComponents.Light">
    <item name="android:windowTranslucentStatus">true</item>
    <item name="android:windowTranslucentNavigation">true</item>
</style>

في هذه اللحظة ، يبدأ كل شيء بالتداخل معًا من الأعلى ومن الأسفل.

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

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

قم بتشغيل الشفافية مع اللون


من الجيد أن لدينا تطبيق نشاط واحد ، لذلك وضعنا علامتين في نشاط واحد.

rootView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or  
    View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION

<style name="AppTheme" parent="Theme.MaterialComponents.Light">
    <!-- Set the navigation bar to 50% translucent white -->
    <item name="android:navigationBarColor">#80FFFFFF</item> 
</style>

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

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

نفذنا عمليات احتيال - أشار إلى أن شريط التنقل أبيض مع الشفافية. الآن يبدو التطبيق مثل هذا.


ما يمكن أن يكون أسهل من التعامل مع المدخلات؟


في الواقع ، الابتدائية.

ViewCompat
    .setOnApplyWindowInsetsListener(bottomNav) { view, insets ->  
        view.updatePadding(bottom = insets.systemWindowInsetBottom) 
        insets
    }

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

كل شيء يبدو رائعًا.


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

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

val bottomNavBottomPadding = bottomNav.paddingBottom
ViewCompat
    .setOnApplyWindowInsetsListener(bottomNav) { view, insets ->                 
        view.updatePadding(
        bottom = bottomNavBottomPadding + insets.systemWindowInsetBottom
    )
    Insets
}

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

أضف Kotlin!


نتذكرها initialPaddingونعطيها للمعالج عندما تأتي الإضافات الجديدة (معهم). سيساعدهم ذلك على الجمع معًا بطريقة ما أو بناء نوع من المنطق من الأعلى.

fun View.doOnApplyWindowInsets(block: (View, WindowInsetsCompat, Rect) -> WindowInsetsCompat) {    

    val initialPadding = recordInitialPaddingForView(this)

    ViewCompat.setOnApplyWindowInsetsListener(this) { v, insets ->        
        block(v, insets, initialPadding)
    }
    
    requestApplyInsetsWhenAttached()
}

private fun recordInitialPaddingForView(view: View) =
   Rect(view.paddingLeft, view.paddingTop, view.paddingRight, view.paddingBottom)

  .

bottomNav.doOnApplyWindowInsets { view, insets, padding ->   
    view.updatePadding(
        bottom = padding.bottom + insets.systemWindowInsetBottom
    ) 
    insets
}

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

نسي شيء!


هناك دعوة لأسلوب غير مفهوم.

fun View.doOnApplyWindowInsets(block: (View, WindowInsetsCompat, Rect) -> WindowInsetsCompat) {

    val initialPadding = recordInitialPaddingForView(this)
     
    ViewCompat.setOnApplyWindowInsetsListener(this) { v, insets ->        
        block(v, insets, initialPadding)
    }
      
    requestApplyInsetsWhenAttached() 
}

private fun recordInitialPaddingForView(view: View) =
      Rect(view.paddingLeft, view.paddingTop, view.paddingRight, view.paddingBottom)

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

يجب أن نتحقق من أنه عندما يكون العرض على الشاشة ، أخبرنا النظام: "لقد وصلت المدخلات ، نريد معالجتها". قمنا بتعيين المستمع ويجب علينا تقديم طلب requestsApplyInsets".

fun View.requestApplyInsetsWhenAttached() {    
    if (isAttachedToWindow) {        
        requestApplyInsets()
    } else {
        addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {            
            override fun onViewAttachedToWindow(v: View) {
                v.removeOnAttachStateChangeListener(this)
                v.requestApplyInsets()           
            }

            override fun onViewDetachedFromWindow(v: View) = Unit
        })
    }
}

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

ولكن مرة أخرى ، هناك Kotlin: لقد كتبوا ملحقًا بسيطًا ولا يتعين علينا التفكير فيه بعد الآن.

تكرير RecyclerView


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



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

لكن هناك اختراق رسمي آخر. من المستغرب أنها تعمل بكفاءة.

<androidx.recyclerview.widget.RecyclerView 
        android:id="@+id/recyclerView"        
    android:layout_width="match_parent"    
    android:layout_height="match_parent"             
    android:clipToPadding="false" />

recyclerView.doOnApplyWindowInsets { view, insets, padding ->    
    view.updatePadding(
        bottom = padding.bottom + insets.systemWindowInsetBottom
    )
}

هناك مثل هذا العلم في التخطيط clipToPadding. بشكل افتراضي ، هذا صحيح دائمًا. هذا يشير إلى أنك لست بحاجة إلى رسم العناصر التي تظهر حيث تتعرض الحشو. ولكن إذا تم تعيينها clipToPadding="false"، فيمكنك الرسم.

الآن ، إذا قمت بتعيين الحشو إلى الأسفل ، في RecyclerView ستعمل على النحو التالي: يتم تعيين الحشو إلى الأسفل ، ويتم رسم العنصر فوقه حتى ننتقل. بمجرد الوصول إلى النهاية ، سيقوم RecyclerView بتمرير العناصر إلى الموضع الذي نحتاجه.

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

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

علة التطبيق


في العديد من التطبيقات على Google Play التي تمت معالجتها بالفعل ، لاحظت وجود خطأ صغير. الآن سأخبر عنه.

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



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



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



نعود إلى شاشتنا ، حيث يعالج الجميع المدخلات. تذكر ، لا أحد أفاد أنه عالجهم؟



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



إذا كانت هذه هي الحالة ، فإننا نتذكر أننا نعيد جميع الإدخالات إلى العملية. لذا (اتضح!) من الضروري الإبلاغ عن معالجة المعالجات: يجب أن يشير شريط التنقل إلى أن المعالجات مدرجة. لا يجب عليهم الوصول إلى RecyclerView.



للقيام بذلك ، قم بتثبيت شريط التنقل السفلي InsetsListener. اطرح من الداخل bottom + insetsحتى لا تتم معالجتها ، ارجع 0.

doOnApplyWindowInsets { view, insets, initialPadding ->    
    view.updatePadding(
        bottom = initialPadding.bottom + insets.systemWindowInsetBottom
    )
    insets.replaceSystemWindowInsets(
        Rect(
            insets.systemWindowInsetLeft,            
            insets.systemWindowInsetTop,            
            insets.systemWindowInsetRight,
            0
        )
    )
}

نعيد إدخالات جديدة ، حيث تكون جميع المعلمات القديمة متساوية ، ولكنها bottom + insetsتساوي 0. يبدو أن كل شيء على ما يرام. نبدأ هراء ومرة ​​أخرى - لا يزال RecyclerView يتعامل مع إدخالات لسبب ما.



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

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



ماذا أفعل؟ لا يعمل LinearLayout بالطريقة التي أريدها ، فهو ينقلها من الأعلى إلى الأسفل ، وأحتاج إلى الحصول على الجزء السفلي أولاً.

إعادة تعريف الكل


لعب مطور جافا في داخلي - كل شيء يحتاج إلى إعادة تعريف ! حسنًا ، الآن dispatchApplyWindowInsetsأعد تعريف المجموعة yLinearLayout، والتي ستنتقل دائمًا من الأسفل إلى الأعلى. سيرسل أولاً إدخالات شريط التنقل السفلي ، ثم إلى أي شخص آخر.

@Override
public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {     
    insets = super.dispatchApplyWindowInsets(insets);     
    if (!insets.isConsumed()) {        
        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            insets = getChildAt(i).dispatchApplyWindowInsets(insets);
            if (insets.isConsumed()) {
                break;
            }
        }
    }
    return insets;
}

لكنني توقفت في الوقت ، تذكرت التعليق بأنه لا توجد حاجة لإعادة تعريف هذه الطريقة.

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

لقد كتبت ملحقًا بسيطًا. يسمح ، بالتطبيق InsetsListenerعلى طريقة عرض واحدة ، أن يقول لمن تمر هذه المدخلات.

fun View.addSystemBottomPadding(    
    targetView: View = this
) {
    doOnApplyWindowInsets { _, insets, initialPadding ->           
        targetView.updatePadding(
            bottom = initialPadding.bottom + insets.systemWindowInsetBottom
        )
        insets.replaceSystemWindowInsets(
            Rect(
                insets.systemWindowInsetLeft,
                insets.systemWindowInsetTop,     
                insets.systemWindowInsetRight,
                0
            )
        )
    }
}

هنا targetViewيساوي بشكل افتراضي نفس العرض الذي نطبق عليه addSystemBottomPadding. يمكننا إعادة تعريفه.

في LinearLayout ، قمت بتعليق معالج ، مروراً targetView- هذا هو شريط التنقل السفلي.

  • أولاً سيعطي إدراجات أسفل شريط التنقل. 
  • سيؤدي ذلك إلى معالجة المدخلات ، وسيُرجع الصفر السفلي.
  • علاوة على ذلك ، بشكل افتراضي ، ستنتقل من أعلى إلى أسفل: شريط الأدوات ، جزء مع RecyclerView.
  • ثم ، ربما ، سيرسل مرة أخرى إدخالات شريط التنقل السفلي. لكن هذا لم يعد مهما ، كل شيء سيعمل بشكل جيد.

لقد حققت ما أريده بالضبط: تتم معالجة جميع المدخلات بالترتيب الذي تحتاج إليه.



مهم


بعض الأشياء المهمة التي يجب وضعها في الاعتبار.

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

لا يزال درج التنقل لا يدعم التنقل بالإيماءات. في Google IO ، وعدوا بأن كل شيء سيعمل خارج الصندوق. ولكن في Android 10 ، لا يزال Navigation Drawer لا يدعم هذا. إذا قمت بالترقية إلى Android 10 وقمت بتمكين التنقل بالإيماءات ، فسوف يسقط التنقل درج. الآن تحتاج إلى النقر على قائمة الهامبرغر لإظهارها ، أو تسمح لك مجموعة من الظروف العشوائية بتمديدها.

في إصدار ما قبل ألفا من Android ، يعمل Navigation Drawer ، لكني لم أجرؤ على التحديث - هذا هو الإصدار الأولي. لذلك ، حتى إذا قمت بتثبيت أحدث إصدار من GitFox من المستودع ، يوجد درج تنقل هناك ، ولكن لا يمكن سحبه. بمجرد ظهور الدعم الرسمي ، سأقوم بالتحديث وسيعمل كل شيء على ما يرام.

قائمة التحقق من إعداد Android 10


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

أضف ملحقات إلى Kotlin - إنها أسهل معها.

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

لجميع RecyclerViews ، أضف حشوة من الأسفل والقدرة على التمرير من خلال clipToPadding="false".

فكر في جميع الأزرار الموجودة على حواف الشاشة (FAB) . على الأرجح لن تكون FABs حيث تتوقع.

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

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

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

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

روابط مفيدة:

  • rmr_spb في برقية - سجلات الإنجازات الداخلية لـ Redmadrobot SPb.
  • كل كود المصدر في هذه المقالة وأكثر في GitFox . هناك فرع منفصل لـ Android 10 ، حيث يمكنك أن ترى من خلال الالتزام بكيفية الوصول إلى كل هذا وكيف دعمت Android الجديد.
  • مكتبة كريس بان إنسيتر . يحتوي على 5-6 ملحقات عرضتها. للاستخدام في مشروعك ، اتصل بالمكتبة ، على الأرجح أنها ستنتقل إلى Android Jetpack. لقد طورتهم في مشروعي ، ويبدو لي أنهم تحسنوا.
  • مقالة كريس بان.
  • FunCorn, , .
  • «Why would I want to fitsSystemWindows?» .

AppConf, youtube- . , - . AppsConf , . , , stay tuned!

All Articles