CollectCircles 1א - בניית המסך בעזרת Layout Editor


תרגול רשות: גרירה, Attributes ו-Constraints כמעט בלי לכתוב XML

זהו שיעור רשות שאפשר לבצע יחד עם CollectCircles 1 או לחזור אליו מאוחר יותר. נבנה את אותו activity_main.xml, אבל הפעם בעיקר בעזרת העכבר וה-Layout Editor של Android Studio.

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

אם יצרתם את הפרויקט בעצמכם, בחרו בתבנית Empty Views Activity וקראו לפעילות MainActivity. התבנית כבר יוצרת:

  • את MainActivity.java;
  • את activity_main.xml;
  • ConstraintLayout ראשי;
  • TextView התחלתי עם הטקסט Hello World.

בשיעור הזה לא מוסיפים Activity נוסף. רק משנים את המסך שהתבנית יצרה.

המראה המדויק של הכפתורים עשוי להשתנות מעט בין גרסאות Android Studio, אבל השמות Palette,‏ Component Tree,‏ Attributes,‏ Design ו-Blueprint נשארים דומים.

מה צריך להיות מוכן

לפני בניית המסך:

  1. הפעילו View Binding כפי שמוסבר בשיעור 1.
  2. הוסיפו את המחרוזות והצבעים ל-strings.xml ול-colors.xml.
  3. צרו את GameBoardView.java.
  4. בחרו בתפריט Build → Make Project.

הבנייה האחרונה חשובה: רק לאחר ש-Android Studio מכירה את המחלקה GameBoardView, היא יכולה להציג אותה ב-Palette → Project.

1. פותחים את עורך המסך

בחלון Project פתחו:

app
└── src
    └── main
        └── res
            └── layout
                └── activity_main.xml

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

בסרגל של משטח העיצוב בחרו Design + Blueprint:

  • Design מראה בערך כיצד המסך ייראה.
  • Blueprint מבליטה את גבולות הרכיבים ואת קווי ה-constraints.

המסך ב-Layout Editor בתצוגת Design ו-Blueprint

בתמונה אפשר לראות את אותו מסך בשתי התצוגות. הקווים הגליים והעיגולים ב-Blueprint הם כלי עבודה של העורך; הם אינם מופיעים באפליקציה.

פתחו את שלושת החלונות הבאים אם הם סגורים:

  • Palette - רשימת רכיבים שאפשר לגרור;
  • Component Tree - רשימת הרכיבים שכבר נמצאים במסך;
  • Attributes - המאפיינים וה-constraints של הרכיב המסומן.

אם חלונות הכלים תופסים מקום רב, אפשר ללחוץ Ctrl+Shift+F12 כדי להסתיר או להחזיר אותם. ה-Layout Editor יקבל יותר מקום.

2. מבינים את ארבעת סוגי הידיות

סמנו רכיב על המסך. סביבו יופיעו ידיות:

  • ריבועים בפינות משנים גודל ידני;
  • עיגול בצד שמאל יוצר constraint אופקי לשמאל;
  • עיגול בצד ימין יוצר constraint אופקי לימין;
  • עיגול למעלה או למטה יוצר constraint אנכי;
  • לרכיבי טקסט אפשר להציג גם ידית Baseline.

כדי ליצור constraint:

  1. לחצו והחזיקו עיגול בצד הרכיב.
  2. גררו אותו אל הצד המתאים של ההורה או של רכיב אחר.
  3. שחררו כאשר נקודת החיבור מודגשת.

המיקום שבו הנחתם רכיב בעורך אינו constraint. לכל רכיב דרוש לפחות constraint אופקי אחד ו-constraint אנכי אחד. רכיב שנראה נכון בעורך אבל אינו מקושר עלול לקפוץ לפינה השמאלית העליונה בזמן הריצה.

אם יצרתם constraint שגוי:

  • לחצו על הקו שלו ואז Delete; או
  • לחצו על נקודת החיבור בתרשים Constraint Widget שבחלון Attributes.

מומלץ להשאיר את Autoconnect כבוי וליצור את החיבורים ידנית. כך אנחנו יודעים בדיוק מדוע כל רכיב נמצא במקומו.

3. מנקים את מסך התבנית

ב-Component Tree סמנו את ה-TextView של Hello World ולחצו Delete.

סמנו את ה-ConstraintLayout הראשי והגדירו בחלון Attributes:

מאפיין ערך
id main
background @color/screen_background
paddingHorizontal 20dp

אפשר להשתמש בסמל החיפוש בראש Attributes כדי למצוא מאפיין.

4. מוסיפים את הכותרת

  1. ב-Palette פתחו Common.
  2. גררו TextView אל החלק העליון של המסך.
  3. בחלון Attributes הגדירו:
מאפיין ערך
id titleText
text @string/game_title
textSize 26sp
textStyle bold
textColor @color/title_purple
gravity center
layout_width 0dp - Match Constraints
layout_height wrap_content

כעת צרו שלושה constraints:

  1. שמאל הכותרת אל שמאל ההורה.
  2. ימין הכותרת אל ימין ההורה.
  3. החלק העליון של הכותרת אל החלק העליון של ההורה.

ב-Constraint Widget הגדירו margin עליון של 20dp.

מדוע רוחב 0dp?

ב-ConstraintLayout, הערך 0dp פירושו Match Constraints: הרכיב נמתח בין ה-constraint השמאלי והימני. זה אינו רוחב אפס.

5. מוסיפים את שתי תוויות הזמן

גררו שני TextView נוספים מתחת לכותרת.

לתווית השמאלית הגדירו:

מאפיין ערך
id elapsedTimeText
text @string/elapsed_time_initial
textSize 16sp
textColor @color/time_brown
gravity start
layout_width 0dp
layout_height wrap_content

לתווית הימנית הגדירו:

מאפיין ערך
id bestTimeText
text @string/best_time_initial
textSize 16sp
textColor @color/title_purple
gravity end
layout_width 0dp
layout_height wrap_content

יוצרים שרשרת אופקית

חברו בעזרת הידיות העגולות:

  1. שמאל elapsedTimeText אל שמאל ההורה.
  2. ימין elapsedTimeText אל שמאל bestTimeText.
  3. שמאל bestTimeText אל ימין elapsedTimeText.
  4. ימין bestTimeText אל ימין ההורה.

שני הרכיבים בעלי רוחב 0dp, ולכן השרשרת מחלקת ביניהם את רוחב השורה.

מחברים את השורה לכותרת

חברו את החלק העליון של elapsedTimeText אל החלק התחתון של titleText. הגדירו margin עליון של 12dp.

כדי ליישר את שני הטקסטים בדיוק:

  1. לחצו לחיצה ימנית על bestTimeText.
  2. בחרו Show Baseline.
  3. גררו את ידית ה-baseline שלו אל ה-baseline של elapsedTimeText.

תפריט הלחיצה הימנית ובו Show Baseline

Baseline היא הקו הדמיוני שעליו יושבות האותיות. היא מדויקת יותר מחיבור הקצוות העליונים של שני רכיבי טקסט.

6. מוסיפים את כפתור Start

ב-Palette פתחו Buttons וגררו Button מתחת לשורת הזמן.

הגדירו:

מאפיין ערך
id startButton
text @string/start
textSize 18sp
layout_width 0dp
layout_height wrap_content

צרו constraints:

  1. שמאל הכפתור אל שמאל ההורה.
  2. ימין הכפתור אל ימין ההורה.
  3. החלק העליון שלו אל החלק התחתון של elapsedTimeText.

הגדירו margin עליון של 12dp.

עריכת XML קטנה אחת: MaterialButton

בגרסת Android Studio הנוכחית ה-Palette מציגה Button רגיל. במסך שלנו נשתמש ב-MaterialButton כדי לקבל פינות מעוגלות.

עברו לרגע ל-Code או ל-Split והחליפו רק את שם התג:

-<Button
+<com.google.android.material.button.MaterialButton

התג נסגר באמצעות />, ולכן אין תג סיום נוסף שצריך לשנות.

חזרו ל-Design, סמנו את הכפתור והגדירו ב-Attributes:

מאפיין ערך
cornerRadius 14dp

אם cornerRadius אינו מופיע, הוסיפו בשורת הכפתור ב-XML:

app:cornerRadius="14dp"

זו עריכת ה-XML היחידה שנחוצה בגלל ההבדל בין הרכיב שב-Palette לבין הרכיב שבחרנו לפרויקט.

7. מוסיפים את לוח המשחק המותאם

ודאו שכבר יצרתם את GameBoardView.java וביצעתם Build → Make Project.

  1. ב-Palette פתחו Project.
  2. גררו GameBoardView אל השטח שמתחת לכפתור.
  3. אם הרכיב אינו מופיע ברשימה, בצעו שוב Make Project ובדקו שאין שגיאת Java.

GameBoardView ב-Palette ולצדו חלון Attributes

בתמונה רואים גם את layout_width="0dp" ואת Constraint Widget בחלון Attributes.

הגדירו:

מאפיין ערך
id gameBoard
layout_width 0dp
layout_height 0dp
minHeight 320dp

צרו ארבעה constraints:

  1. שמאל הלוח אל שמאל ההורה.
  2. ימין הלוח אל ימין ההורה.
  3. החלק העליון שלו אל החלק התחתון של startButton.
  4. החלק התחתון שלו אל החלק התחתון של ההורה.

הגדירו:

  • margin עליון: 16dp;
  • margin תחתון: 20dp.

גם הגובה הוא 0dp, ולכן הלוח ממלא את כל המקום שנותר בין הכפתור לתחתית.

GameBoardView צובעת את הרקע שלה מתוך קוד Java. אם רוצים לראות את צבע הלבנדר גם כשה-preview אינו מצליח להריץ את המחלקה, אפשר להוסיף ידנית:

tools:background="@color/board_background"

זהו מאפיין תצוגה בלבד. הוא אינו משנה את האפליקציה בזמן הריצה.

8. בודקים את מפת ה-constraints

בסיום צריכה להתקבל המפה הבאה:

רכיב אופקי אנכי
titleText שמאל וימין להורה למעלה להורה
elapsedTimeText שמאל להורה, ימין ל-bestTimeText מתחת לכותרת
bestTimeText שמאל ל-elapsedTimeText, ימין להורה baseline לזמן
startButton שמאל וימין להורה מתחת לזמן
gameBoard שמאל וימין להורה מתחת לכפתור ועד תחתית ההורה

לחצו על סמל האזהרות בסרגל Layout Editor. לא אמורה להופיע אזהרת Missing Constraints.

אל תלחצו מיד על Infer Constraints כדי “לתקן הכול”. הוא מנסה לנחש לפי המקום שבו הנחתם את הרכיבים, ולעיתים יוצר חיבורים שאינם מתאימים למסכים אחרים. בתרגיל הזה המטרה היא להבין וליצור כל קשר בעצמנו.

9. מריצים ובודקים שינוי גודל

הריצו את האפליקציה ובדקו:

  1. הכותרת נשארת ממורכזת.
  2. שתי תוויות הזמן חולקות את אותה שורה.
  3. הכפתור נמתח לרוחב.
  4. לוח המשחק מקבל את כל הגובה שנותר.

ב-Layout Editor החליפו זמנית את המכשיר בתצוגה למכשיר צר או רחב יותר. אם ה-constraints נכונים, המבנה נשאר זהה בלי להזיז רכיבים ידנית.

תקלות נפוצות

הרכיב קפץ לפינה בזמן הריצה

חסר לו constraint אופקי או אנכי. המיקום החופשי בעורך נשמר לעיתים רק במאפייני tools:layout_editor_absoluteX ו-tools:layout_editor_absoluteY; אלה אינם קובעים מיקום בזמן הריצה.

הרכיב נשאר קטן למרות שחיברתי אותו לשני הצדדים

שנו את layout_width מ-wrap_content ל-0dp - Match Constraints.

שני זמני התצוגה אינם באותו גובה

מחקו את ה-constraint האנכי של bestTimeText, הציגו Show Baseline וחברו baseline ל-baseline.

GameBoardView אינה מופיעה ב-Palette

  • שמרו את קובץ Java.
  • בצעו Build → Make Project.
  • תקנו שגיאות Java אם קיימות.
  • פתחו שוב Palette → Project.

קשה לבחור רכיב כי רכיב אחר מכסה אותו

בחרו אותו מתוך Component Tree במקום ללחוץ עליו בתצוגה.

אם אין אזהרות Missing Constraints והמסך מסתדר נכון גם בתצוגת מכשיר אחרת, בניתם את ה-ConstraintLayout בעזרת ה-GUI. כעת אפשר לחזור לשיעור 1 ולהתרכז במחלקות הציור.


להעמקה