חזרה ל־019a: הפעלת View Binding והמרת המסך הראשון
בשלב הקודם הפעלנו את View Binding והמרנו מסך פשוט. כאן מתחילים בשינוי קטן ב־MainActivity שאפשר לחזור עליו בפרויקטים חדשים: טוענים את המסך דרך שדה binding, מחליפים את findViewById(R.id.main) ב־binding.main, ומעדכנים את הריווח בקצוות המסך. אחר כך נשתמש ב־binding כדי להמיר את מסך משחק האיקס־עיגול.
המטרה: לא יישארו ב-MainActivity קריאות findViewById.
לפני שמתחילים
ודאו ששלב 019a כבר בוצע:
buildFeatures {
viewBinding = true
}
אחרי Sync Project with Gradle Files, הקובץ activity_main.xml יוצר את המחלקה ActivityMainBinding.
שלב 1 - האתחול הבסיסי של MainActivity
פתחו את MainActivity בתצוגת Android, תחת app > kotlin+java > com.example.tictacmenu > activities.
נקודת המוצא בהשוואה היא הקוד הרגיל של התבנית: setContentView(R.layout.activity_main) טוען את המסך, ו־findViewById(R.id.main) מאתר את ה־View שעליו מחילים את הריווח. נוסיף שדה binding בגוף המחלקה MainActivity, מעל @Override ומחוץ ל־onCreate, ונאתחל אותו בתוך המתודה.
הוסיפו את ה־import של הפרויקט:
import com.example.tictacmenu.databinding.ActivityMainBinding;
השאירו את ה־import של R כל עוד קוד אחר בקובץ משתמש בו.
מוצגת תחילת המתודה בלבד; שאר הקוד ב־onCreate ממשיך אחרי }); ונשאר במקומו. שורות אדומות מוחלפות, שורות ירוקות נוספות, והדגשות הרקע מסמנות את אותן נקודות בקוד בשתי העמודות.
לפני
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
- setContentView(R.layout.activity_main);
- ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
- Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
- v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
אחרי
+ private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
+ binding = ActivityMainBinding.inflate(getLayoutInflater());
+ setContentView(binding.getRoot());
+ ViewCompat.setOnApplyWindowInsetsListener(binding.main, (v, insets) -> {
+ Insets bars = insets.getInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout());
+ v.setPadding(bars.left, bars.top, bars.right, bars.bottom);
return insets;
});
מה השתנה?
- מוסיפים שדה ומאתחלים דרכו את המסך.
private ActivityMainBinding binding;נמצא מחוץ למתודה, כדי שגם מתודות אחרות באותה Activity יוכלו להשתמש בו אחרי האתחול. בתוךonCreate, השורהbinding = ActivityMainBinding.inflate(getLayoutInflater());יוצרת את רכיבי המסך, ו־setContentView(binding.getRoot());מציג את ה־View הראשי שלהם. כך שתי השורות מחליפות אתsetContentView(R.layout.activity_main);. בהשמה בתוך המתודה כותביםbinding =בלי להכריז שוב על הטיפוס, כדי לא ליצור משתנה מקומי שמסתיר את השדה. - ניגשים ל־View ישירות.
binding.mainמחליף אתfindViewById(R.id.main)בשורת ה־listener: זהו כבר אובייקט ה־View בעל המזהה@+id/main, ולכן אין צורך לחפש אותו. - הריווח מתחשב גם במגרעת המסך.
systemBars()מתייחס לפסי המערכת, ו־displayCutout()מוסיף התחשבות במגרעת או בחור המצלמה. הסימן|משלב את שני סוגי ה־insets, ו־setPaddingמשתמש בתוצאה כדי להרחיק את התוכן מהאזורים האלה. השםbarsמחליף אתsystemBarsגם בהכרזה וגם בשימוש. הסבר בתיעוד Android.
לשימוש חוזר בפרויקט חדש: זהו אותו אתחול בסיסי ל־Activity עם View Binding. התאימו את שם מחלקת ה־binding לקובץ ה־layout ואת binding.main למזהה של ה־View שעליו מחילים את הריווח; בדוגמה זו הוא @+id/main.
אפשרות נפרדת — עדכון `.gitignore`
כדי לצמצם שינויים ורעש מיותר ב-Git, מומלץ להחליף את כל תוכן .gitignore בתבנית הבאה. היא מתעלמת מהגדרות מקומיות של Android Studio ו-IntelliJ, מקובצי Gradle ובנייה, ומקבצים זמניים שהמחשב יוצר. כך Git יציג בעיקר שינויים ששייכים לפרויקט, במקום להטריד אותנו בפרטים מקומיים.
- *.iml
- .gradle
- /local.properties
- /.idea/caches
- /.idea/libraries
- /.idea/modules.xml
- /.idea/workspace.xml
- /.idea/navEditor.xml
- /.idea/assetWizardSettings.xml
- .DS_Store
- /build
- /captures
- .externalNativeBuild
- .cxx
- local.properties
+# Android Studio / IntelliJ
+.idea/
+*.iml
+
+# Gradle
+.gradle/
+**/build/
+
+# Local machine configuration
+local.properties
+
+# Android generated files
+captures/
+.externalNativeBuild/
+.cxx/
+
+# Build outputs
+*.apk
+*.aab
+output-metadata.json
+
+# Profiling / logs
+*.hprof
+*.log
+
+# OS junk
+.DS_Store
+Thumbs.db
+
+# Temporary/editor files
+*.swp
+*~
אם קבצים מסויימים כבר נוספו ל-Git בעבר, הסירו אותם מהמעקב בלי למחוק את העותק המקומי:
git rm -r --cached .idea
git rm -r --cached --ignore-unmatch .gradle
git rm -r --cached --ignore-unmatch build
git rm -r --cached --ignore-unmatch app/build
git add .gitignore
git commit -m "Ignore Android Studio and generated files"
בדיקה לפני שממשיכים: הריצו Build > Make Project, פתחו את המסך ובדקו שהתוכן אינו מוסתר על ידי פסי המערכת או מגרעת המסך, גם בסיבוב לרוחב. מכאן ממשיכים לשינויים של מסך המשחק.
שלב 2 - החלפה פשוטה של View יחיד
במתודה onConnectClick, אין צורך לחפש את שדה כתובת השרת לפי מזהה:
-EditText ipEdit = findViewById(R.id.editServerIP);
+EditText ipEdit = binding.editServerIP;
אפשר גם להשתמש ישירות ב-binding.editServerIP, אבל משתנה מקומי בשם ipEdit נשאר קריא ונוח אם משתמשים בו כמה פעמים.
חיבור כפתור Connect דרך binding
אם ב-activity_main.xml הכפתור מכיל listener ישן:
android:onClick="onConnectClick"
הסירו את השורה הזאת מה-XML. לאחר יצירת ה-binding ב-onCreate, חברו listener מפורש:
binding.buttonConnect.setOnClickListener(view -> onConnectClick());
מאחר שהמתודה כבר אינה נקראת אוטומטית מתוך XML, היא אינה צריכה לקבל View ואפשר להפוך אותה לפרטית:
-public void onConnectClick(View view) {
+private void onConnectClick() {
android:onClick מחפש את המתודה לפי שמה בזמן ריצה בתוך ה-Context של ה-View. בפרויקט הזה activity_main.xml נטען גם על ידי MainActivity וגם על ידי Main2Activity, אך רק ב-MainActivity קיימת המתודה onConnectClick. אם ה-layout נטען ב-Main2Activity, הלחיצה מסתיימת ב-IllegalStateException, אף שאפשר לראות את המתודה בקובץ MainActivity.java. listener שמחובר דרך binding.buttonConnect קשור במפורש ל-Activity הנכון ונבדק בזמן קומפילציה.
זהו יתרון נוסף של binding: לא רק שניגשים לאובייקט הכפתור בלי findViewById, אלא גם מחברים אליו התנהגות מפורשת בלי מנגנון reflection שמבוסס על מחרוזת.
כלל אצבע: כאשר R.id משמש רק כדי למצוא View באמצעות findViewById, בדרך כלל מחליפים את כל פעולת החיפוש בשדה המתאים של binding. לעומת זאת, כאשר API דורש במפורש מזהה משאב מסוג int — למשל בזיהוי MenuItem או בקריאה ל-FragmentTransaction.replace — ממשיכים להשתמש ב-R.id.
העיקרון החשוב: 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.
בצעו את שינויי החלק הזה לפי תמונת הדיפ:

שימו לב לשלוש ההחלפות המרכזיות בתמונה:
idForהופכת ל-buttonForוסוג ההחזרה משתנה מ-intל-Button.- במקום
int idואזfindViewById(id), מקבלים מידButton target = buttonFor(r, c). - ב-
resetBoardהמערך הואButton[], ולכן הלולאה מקבלתButtonישירות ולא מזהה.
הערך null בסוף buttonFor מציין ששילוב שורה/עמודה אינו תא חוקי. זו החלופה המתאימה ל-return 0 של מתודה שהחזירה int.
בדיקה
- הריצו
Build > Make Project. - הריצו את האפליקציה ופתחו את מסך המשחק.
- נסו להתחבר לשרת ולבצע מהלך מקומי.
- אם מתקבל מהלך מהשרת, ודאו שהכפתור המתאים מתעדכן.
- ודאו שניצחון או תיקו עדיין מאפסים את כל תשעת הכפתורים.
אם ActivityMainBinding מסומן באדום, בצעו Gradle Sync ובדקו שקיים app/src/main/res/layout/activity_main.xml.
השיעור הבא
מורה־עזר ב־Gemini
פתחו את Gem: מעבדת TicTacToe: Model, View Binding ועיצוב כדי לקבל רמזים, שאלות אבחון והסברים המתאימים לשלב שבו אתם נמצאים.