בשלבים הקודמים הפעלנו View Binding והמרנו Activities. בשלב הזה נמיר Fragment אחד ואת MenuActivity, שמארח את ה-Fragments ואת מגירת הניווט.
בדרך נפגוש מגבלה חשובה: View Binding יוצר שדות עבור Views בקובץ layout, אבל הוא לא מחליף כל שימוש ב-R.id.
האם MenuActivity הוא Fragment או Activity?
MenuActivity הוא Activity. אפשר לראות זאת לפי ההצהרה:
public class MenuActivity extends AppCompatActivity
הוא מציג את activity_menu.xml, מנהל את ה-NavigationView, ומחליף Fragments בתוך content_container.
HomeFragment, ProfileFragment ו-SettingsFragment הם Fragments. הם מוצגים בתוך ה-Activity, ולכל אחד מהם layout ומחזור חיים משלו.
שלב 1 - View Binding ב-HomeFragment
פתחו את:
app/src/main/java/com/example/tictacmenu/shell/HomeFragment.java
החליפו את ה-import והוסיפו שדה binding:
-import com.example.tictacmenu.R;
+import com.example.tictacmenu.databinding.FragmentHomeBinding;
public class HomeFragment extends Fragment {
+ private FragmentHomeBinding binding;
ב-onCreateView, ה-binding מנפח את ה-layout ומחזיר את ה-root שלו:
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
- return inflater.inflate(R.layout.fragment_home, container, false);
+ binding = FragmentHomeBinding.inflate(inflater, container, false);
+ return binding.getRoot();
}
לבסוף, נקו את ה-binding כאשר ה-View של ה-Fragment נהרס:
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
ב-Fragment חובה לנקות את ה-binding ב-onDestroyView. אובייקט ה-Fragment עשוי להישאר קיים גם לאחר שה-View שלו נהרס. שמירת binding ישן עלולה להחזיק Views שכבר אינם מוצגים ולגרום לדליפת זיכרון.
שלב 2 - התחלת ההמרה של MenuActivity
פתחו את:
app/src/main/java/com/example/tictacmenu/shell/MenuActivity.java
MenuActivity משתמש גם ב-binding וגם ב-R, ולכן צריכים את שני ה-imports:
import com.example.tictacmenu.R;
import com.example.tictacmenu.databinding.ActivityMenuBinding;
הוסיפו את שדה ה-binding:
private ActivityMenuBinding binding;
והחליפו את טעינת ה-layout:
-setContentView(R.layout.activity_menu);
+binding = ActivityMenuBinding.inflate(getLayoutInflater());
+setContentView(binding.getRoot());
שלב 3 - ה-Views מתוך activity_menu.xml
שלושת הרכיבים הבאים מוגדרים בתוך activity_menu.xml, ולכן ActivityMenuBinding מחזיר את האובייקטים שלהם ישירות:
-drawerLayout = findViewById(R.id.drawer_layout);
-MaterialToolbar toolbar = findViewById(R.id.toolbar);
+drawerLayout = binding.drawerLayout;
+MaterialToolbar toolbar = binding.toolbar;
ובהמשך:
-NavigationView navigationView = findViewById(R.id.navigation_view);
+NavigationView navigationView = binding.navigationView;
אלה שדות binding חוקיים מפני שב-activity_menu.xml קיימים Views עם המזהים:
@+id/drawer_layout@+id/toolbar@+id/navigation_view@+id/content_container
שלב 4 - מדוע nav_home אינו קיים ב-binding?
ב-activity_menu.xml, ה-NavigationView מפנה לקובץ תפריט:
app:menu="@menu/menu_drawer"
אבל הפריטים nav_home, nav_local_game, nav_rtdb_prep ו-nav_profile מוגדרים בתוך res/menu/menu_drawer.xml. הם אובייקטי MenuItem, לא Views בתוך activity_menu.xml.
לכן הקוד הבא שגוי:
item.getItemId() == binding.navHome // לא קיים שדה כזה
גם השוואה בין שני האובייקטים אינה נכונה:
item == binding.navigationView // MenuItem אינו NavigationView
ה-listener מקבל MenuItem, והמתודה getItemId() מחזירה מזהה מסוג int. לכן כאן ממשיכים להשתמש ב-R.id:
navigationView.setNavigationItemSelectedListener(item -> {
if (item.getItemId() == R.id.nav_home) {
showFragment(new HomeFragment());
} else if (item.getItemId() == R.id.nav_local_game) {
startActivity(new Intent(this, MainActivity.class));
} else if (item.getItemId() == R.id.nav_rtdb_prep) {
startActivity(new Intent(this, Main2Activity.class));
} else if (item.getItemId() == R.id.nav_profile) {
showFragment(new ProfileFragment());
}
drawerLayout.closeDrawer(GravityCompat.START);
return true;
});
גם setCheckedItem מצפה למזהה של פריט תפריט:
navigationView.setCheckedItem(R.id.nav_home);
שלב 5 - החלפת Fragment דורשת container ID
binding.contentContainer הוא אובייקט FrameLayout. אולם הגרסה שבה אנו משתמשים של FragmentTransaction.replace מצפה למזהה container מסוג int:
private void showFragment(Fragment fragment) {
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.content_container, fragment)
.commit();
}
לכן הקוד הבא לא מתקמפל:
.replace(binding.contentContainer, fragment)
אפשר טכנית לכתוב binding.contentContainer.getId(), אבל במקרה הזה R.id.content_container מבטא ישירות את מה שה-API דורש: מזהה משאב, ולא אובייקט View.
מה binding מחליף ומה לא?
| המקרה | סוג הערך הדרוש | הקוד המתאים |
|---|---|---|
| שינוי toolbar או NavigationView | אובייקט View | binding.toolbar, binding.navigationView |
| זיהוי MenuItem שנלחץ | מזהה int |
item.getItemId() == R.id.nav_home |
| בחירת פריט תפריט | מזהה int |
setCheckedItem(R.id.nav_home) |
| בחירת container לעסקת Fragment | מזהה int |
replace(R.id.content_container, fragment) |
| גישה ל-View של Fragment | אובייקט View | binding.someView של אותו Fragment |
View Binding אינו תחליף גורף למחלקה R. הוא מחליף בעיקר את פעולת החיפוש findViewById ומחזיר Views מטופסים. משאבים אחרים — תפריטים, מחרוזות, תמונות, צבעים, ולעיתים גם IDs ש-API דורש — עדיין נגישים דרך R.
בדיקה
- הריצו
Build > Make Project. - פתחו את
MenuActivityוודאו שה-toolbar ומגירת הניווט מוצגים. - לחצו על Home ועל Profile וודאו שה-Fragment המתאים מוצג.
- לחצו על Local Game ועל RTDB Prep וודאו שה-Activities המתאימים נפתחים.
- חזרו בין Fragments מספר פעמים וודאו שאין קריסה.