טיפול בשינויים בהגדרות

ממשק משתמש רספונסיבי וניווט

כדי לספק למשתמשים את חוויית הניווט הטובה ביותר, מומלץ לספק ממשק משתמש לניווט שמותאם לרוחב, לגובה ול הרוחב הקטן ביותר של המכשיר של המשתמש. כדאי להשתמש סרגל האפליקציות התחתון, מוצג תמיד או מתקפל חלונית הזזה לניווט, rail, או משהו חדש לגמרי, בהתאם לשטח המסך הזמין את הסגנון הייחודי של האפליקציה.

דוגמאות למסילת רכבת, לחלונית הזזה לניווט ולסרגל אפליקציות תחתון
איור 1. דוגמאות לרכבת, למגירות ניווט בסרגל התחתון של האפליקציה.

עיצוב החומר מדריך לארכיטקטורת מוצרים מספקת הקשר ושיקולים נוספים לפיתוח ממשק משתמש רספונסיבי – ממשק משתמש שמתאים באופן דינמי לשינויים סביבתיים. כמה דוגמאות ל שינויים סביבתיים כוללים התאמות של רוחב, גובה, כיוון של המשתמש. הנכסים הסביבתיים האלה מקובצים יחד נקראת הגדרות אישיות המכשיר.

כשאחד או יותר מהמאפיינים האלה משתנים בזמן הריצה, מערכת Android OS מגיבה לפי להשמיד את הפעילויות והמקטעים של האפליקציה וליצור אותם מחדש. לכן, הדבר הטוב ביותר שאפשר לעשות כדי לתמוך בממשק משתמש רספונסיבי ב-Android הוא חשוב לוודא שמשתמשים מגדירים הגדרות של משאבים במקרים הרלוונטיים, להימנע משימוש בגדלי פריסה המוגדרים בתוך הקוד.

הטמעת ניווט גלובלי בממשק משתמש רספונסיבי

ההטמעה של ניווט גלובלי כחלק מממשק משתמש רספונסיבי מתחילה ב- פעילות שמארחת את תרשים הניווט שלך. כדי לקבל דוגמה מעשית, אפשר: את Codelab ניווט. ב-Codelab נעשה שימוש ב-NavigationView כדי להציג את תפריט הניווט, כפי שמוצג באיור 2. בזמן ההפעלה במכשיר בתצוגה ברוחב של 960dp לפחות, הNavigationView הזה תמיד במסך.

ב-Codelab של הניווט יש תצוגת ניווט שתמיד גלויה
            כשרוחב המכשיר הוא לפחות 960dp
איור 2. ב-Codelab של הניווט נעשה שימוש NavigationView כדי להציג את תפריט הניווט.

גדלים וכיוונים אחרים של המכשירים יכולים לעבור באופן דינמי בין המכשירים DrawerLayout או BottomNavigationView לפי הצורך.

תצוגת ניווט מלמטה ופריסת חלונית הזזה לניווט
            תפריט לפי הצורך בפריסות מכשירים קטנים יותר
איור 3. ב-Codelab של הניווט נעשה שימוש 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 (או ערך זמין) גובה >= 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" />
    ...

עם ערכי משאבים, ליעדים שלכם יש באופן אוטומטי את הערך המתאים ביותר משאבים שמיושמים בכל פעם שההגדרה משתנה.