التعامل مع تغييرات الضبط

واجهة مستخدم سريعة الاستجابة والتنقل

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

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

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

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

تنفيذ التنقل العام في واجهة مستخدم سريعة الاستجابة

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

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

يتم تبديل أحجام الأجهزة والاتجاهات الأخرى ديناميكيًا بين DrawerLayout أو BottomNavigationView حسب الحاجة.

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

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

تُحدَّد الإعدادات التي ينطبق عليها كل تنسيق حسب بنية الدليل التي يتم وضع ملف التنسيق فيها. على سبيل المثال، تم العثور على ملف التنسيق NavigationView في دليل res/layout-w960dp.

<!-- res/layout-w960dp/navigation_activity.xml -->
<RelativeLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context="com.example.android.codelabs.navigation.MainActivity">

   <com.google.android.material.navigation.NavigationView
       android:id="@+id/nav_view"
       android:layout_width="wrap_content"
       android:layout_height="match_parent"
       android:layout_alignParentStart="true"
       app:elevation="0dp"
       app:headerLayout="@layout/nav_view_header"
       app:menu="@menu/nav_drawer_menu" />

   <View
       android:layout_width="1dp"
       android:layout_height="match_parent"
       android:layout_toEndOf="@id/nav_view"
       android:background="?android:attr/listDivider" />

   <androidx.appcompat.widget.Toolbar
       android:id="@+id/toolbar"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:layout_alignParentTop="true"
       android:layout_toEndOf="@id/nav_view"
       android:background="@color/colorPrimary"
       android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar" />

   <androidx.fragment.app.FragmentContainerView
       android:id="@+id/my_nav_host_fragment"
       android:name="androidx.navigation.fragment.NavHostFragment"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:layout_below="@id/toolbar"
       android:layout_toEndOf="@id/nav_view"
       app:defaultNavHost="true"
       app:navGraph="@navigation/mobile_navigation" />
</RelativeLayout>

يمكنك العثور على طريقة عرض التنقّل السفلية في دليل res/layout-h470dp:

<!-- res/layout-h470dp/navigation_activity.xml -->
<LinearLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:orientation="vertical"
   tools:context="com.example.android.codelabs.navigation.MainActivity">

   <androidx.appcompat.widget.Toolbar
       android:id="@+id/toolbar"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:background="@color/colorPrimary"
       android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar" />

   <androidx.fragment.app.FragmentContainerView
       android:id="@+id/my_nav_host_fragment"
       android:name="androidx.navigation.fragment.NavHostFragment"
       android:layout_width="match_parent"
       android:layout_height="0dp"
       android:layout_weight="1"
       app:defaultNavHost="true"
       app:navGraph="@navigation/mobile_navigation" />

   <com.google.android.material.bottomnavigation.BottomNavigationView
       android:id="@+id/bottom_nav_view"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       app:menu="@menu/bottom_nav_menu" />
</LinearLayout>

يمكن العثور على تنسيق الدرج في دليل res/layout. استخدم هذا الدليل للتخطيطات الافتراضية بدون مؤهلات خاصة بالتهيئة:

<!-- res/layout/navigation_activity.xml -->
<androidx.drawerlayout.widget.DrawerLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:id="@+id/drawer_layout"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context="com.example.android.codelabs.navigation.MainActivity">

   <LinearLayout
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:orientation="vertical">

       <androidx.appcompat.widget.Toolbar
           android:id="@+id/toolbar"
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:background="@color/colorPrimary"
           android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar" />

       <androidx.fragment.app.FragmentContainerView
           android:id="@+id/my_nav_host_fragment"
           android:name="androidx.navigation.fragment.NavHostFragment"
           android:layout_width="match_parent"
           android:layout_height="match_parent"
           app:defaultNavHost="true"
           app:navGraph="@navigation/mobile_navigation" />
   </LinearLayout>

   <com.google.android.material.navigation.NavigationView
       android:id="@+id/nav_view"
       android:layout_width="wrap_content"
       android:layout_height="match_parent"
       android:layout_gravity="start"
       app:menu="@menu/nav_drawer_menu" />
</androidx.drawerlayout.widget.DrawerLayout>

يتبع Android ترتيب الأسبقية عند تحديد الموارد المطلوب تطبيقها. وبالنسبة إلى هذا المثال، يكون للقاعدة -w960dp (أو العرض المتاح >= 960dp) الأولوية على -h470dp (أو المتاح height >= 470). إذا لم تتطابق إعدادات الجهاز مع أي من هذين الشرطين، سيتم استخدام مورد التنسيق التلقائي (res/layout/navigation_activity.xml).

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

Kotlin

class MainActivity : AppCompatActivity() {

   private lateinit var appBarConfiguration : AppBarConfiguration

   override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      setContentView(R.layout.navigation_activity)
      val drawerLayout : DrawerLayout? = findViewById(R.id.drawer_layout)
      appBarConfiguration = AppBarConfiguration(
                  setOf(R.id.home_dest, R.id.deeplink_dest),
                  drawerLayout)

      ...

      // Initialize the app bar with the navigation drawer if present.
      // If the drawerLayout is not null here, a Navigation button will be added
      // to the app bar whenever the user is on a top-level destination.
      setupActionBarWithNavController(navController, appBarConfig)

      // Initialize the NavigationView if it is present,
      // so that clicking an item takes
      // the user to the appropriate destination.
      val sideNavView = findViewById<NavigationView>(R.id.nav_view)
      sideNavView?.setupWithNavController(navController)

      // Initialize the BottomNavigationView if it is present,
      // so that clicking an item takes
      // the user to the appropriate destination.
      val bottomNav = findViewById<BottomNavigationView>(R.id.bottom_nav_view)
      bottomNav?.setupWithNavController(navController)

      ...
    }

    ...
}

Java

public class MainActivity extends AppCompatActivity {

   private AppBarConfiguration appBarConfiguration;

   @Override
   protected void onCreate(@Nullable Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.navigation_activity);
       NavHostFragment host = (NavHostFragment) getSupportFragmentManager()
               .findFragmentById(R.id.my_nav_host_fragment);
       NavController navController = host.getNavController();

       DrawerLayout drawerLayout = findViewById(R.id.drawer_layout);
       appBarConfiguration = new AppBarConfiguration.Builder(
               R.id.home_dest, R.id.deeplink_dest)
               .setDrawerLayout(drawerLayout)
               .build();

       // Initialize the app bar with the navigation drawer if present.
       // If the drawerLayout is not null here, a Navigation button will be added to
       // the app bar whenever the user is on a top-level destination.
       NavigationUI.setupActionBarWithNavController(
               this, navController, appBarConfiguration);


       // Initialize the NavigationView if it is present,
       // so that clicking an item takes
       // the user to the appropriate destination.
       NavigationView sideNavView = findViewById(R.id.nav_view);
       if(sideNavView != null) {
           NavigationUI.setupWithNavController(sideNavView, navController);
       }

       // Initialize the BottomNavigationView if it is present,
       // so that clicking an item takes
       // the user to the appropriate destination.
       BottomNavigationView bottomNav = findViewById(R.id.bottom_nav_view);
       if(bottomNav != null) {
           NavigationUI.setupWithNavController(bottomNav, navController);
       }

   }
}

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

استخدام بدائل للتنسيقات ذات العرض المقسّم

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

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

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

أسماء الوجهات

في حال توفير أسماء الوجهات في الرسم البياني باستخدام السمة android:label، احرص دائمًا على استخدام قيم الموارد حتى تتم أقلمة المحتوى.

<navigation ...>
    <fragment
        android:id="@+id/my_dest"
        android:name="com.example.MyFragment"
        android:label="@string/my_dest_label"
        tools:layout="@layout/my_fragment" />
    ...

باستخدام قيم الموارد، سيتم تلقائيًا تطبيق الموارد الأكثر ملاءمة لوجهاتك كلما تغيّرت الإعدادات.