019b - View Binding ב-MainActivity


המרת מסך המשחק מ-findViewById ל-View Binding

בשלב הקודם הפעלנו את View Binding והמרנו מסך פשוט. כאן נמיר את MainActivity של משחק איקס-עיגול. במסך הזה יש גם עדכון רגיל של EditText, וגם קוד שמאתר כפתור לפי שורה ועמודה. החלק השני דורש שינוי מחשבתי קטן וחשוב.

המטרה: לא יישארו ב-MainActivity קריאות findViewById.

לפני שמתחילים

ודאו ששלב 019a כבר בוצע:

buildFeatures {
    viewBinding = true
}

אחרי Sync Project with Gradle Files, הקובץ activity_main.xml יוצר את המחלקה ActivityMainBinding.

שלב 1 - יצירת ה-binding

פתחו את:

  • app/src/main/java/com/example/tictacmenu/activities/MainActivity.java

הוסיפו את ה-import ואת השדה:

-import com.example.tictacmenu.R;
+import com.example.tictacmenu.databinding.ActivityMainBinding;

 public class MainActivity extends AppCompatActivity {
+    private ActivityMainBinding binding;

בתחילת onCreate, החליפו את טעינת ה-layout:

-setContentView(R.layout.activity_main);
+binding = ActivityMainBinding.inflate(getLayoutInflater());
+setContentView(binding.getRoot());

binding.getRoot() הוא ה-View הראשי של activity_main.xml; לכן הוא הערך שמעבירים ל-setContentView.

שלב 2 - החלפה פשוטה של View יחיד

במתודה onConnectClick, אין צורך לחפש את שדה כתובת השרת לפי מזהה:

-EditText ipEdit = findViewById(R.id.editServerIP);
+EditText ipEdit = binding.editServerIP;

אפשר גם להשתמש ישירות ב-binding.editServerIP, אבל משתנה מקומי בשם ipEdit נשאר קריא ונוח אם משתמשים בו כמה פעמים.

העיקרון החשוב: R.id הוא מספר, binding הוא אובייקט

לפני ההמרה, R.id.button00 הוא מזהה משאב מסוג int. המזהה מתאים לפעולות כמו:

Button button = findViewById(R.id.button00);

לעומת זאת, binding.button00 הוא כבר האובייקט עצמו, מסוג Button:

Button button = binding.button00;
button.setText("X");

לכן אין להחליף את R.id.button00 ב-binding.button00 בתוך מקום שמצפה ל-int. למשל, המתודה הבאה לא יכולה להמשיך להחזיר int:

private int idFor(int row, int col) {
    return binding.button00; // שגיאת קומפילציה: Button אינו int
}

זה בדיוק מקור הסימון האדום: binding לא מחזיר את ה-ID של הכפתור, אלא מחזיר את הכפתור. זו בדרך כלל תועלת — אין חיפוש נוסף, אין casting, והטיפוס נבדק בזמן קומפילציה — אך בקוד שבנוי סביב IDs צריך להתאים את סוגי המשתנים והמתודות.

כלל אצבע: אם הקוד צריך לבצע פעולה על View, השתמשו ב-binding.someView. אם API חיצוני דורש במפורש מזהה משאב, כגון int @IdRes, העבירו לו R.id.some_view ולא את שדה ה-binding.

שלב 3 - כפתור לפי שורה ועמודה

ב-MainActivity, הודעה שמגיעה מהשרת כוללת שורה ועמודה. בקוד הישן המתודה idFor החזירה מזהה int, ואחר כך הקוד חיפש באמצעותו את הכפתור. לאחר מעבר ל-binding, המתודה צריכה להחזיר Button ישירות; אין צורך, ואסור, לקרוא שוב ל-findViewById.

בצעו את שינויי החלק הזה לפי תמונת הדיפ:

Diff של מעבר MainActivity ל-View Binding

שימו לב לשלוש ההחלפות המרכזיות בתמונה:

  1. idFor הופכת ל-buttonFor וסוג ההחזרה משתנה מ-int ל-Button.
  2. במקום int id ואז findViewById(id), מקבלים מיד Button target = buttonFor(r, c).
  3. ב-resetBoard המערך הוא Button[], ולכן הלולאה מקבלת Button ישירות ולא מזהה.

הערך null בסוף buttonFor מציין ששילוב שורה/עמודה אינו תא חוקי. זו החלופה המתאימה ל-return 0 של מתודה שהחזירה int.

בדיקה

  1. הריצו Build > Make Project.
  2. הריצו את האפליקציה ופתחו את מסך המשחק.
  3. נסו להתחבר לשרת ולבצע מהלך מקומי.
  4. אם מתקבל מהלך מהשרת, ודאו שהכפתור המתאים מתעדכן.
  5. ודאו שניצחון או תיקו עדיין מאפסים את כל תשעת הכפתורים.

אם ActivityMainBinding מסומן באדום, בצעו Gradle Sync ובדקו שקיים app/src/main/res/layout/activity_main.xml.