פריסות בתצוגות
פריסה מגדירה את המבנה של ממשק משתמש באפליקציה, למשל בפעילות. כל הרכיבים בפריסה בנויים באמצעות היררכיה של אובייקטים מסוג View ו-ViewGroup. View בדרך כלל מצייר משהו שהמשתמש יכול לראות ולקיים איתו אינטראקציה. ViewGroup הוא קונטיינר בלתי נראה שמגדיר את מבנה הפריסה של View ואובייקטים אחרים של ViewGroup, כמו שמוצג באיור 1.
אובייקטים מסוג View נקראים לעיתים קרובות ווידג'טים, והם יכולים להיות אחד מתוך מחלקות משנה רבות, כמו Button או TextView. אובייקטים מסוג ViewGroup נקראים בדרך כלל פריסות, ויכולים להיות אחד מתוך סוגים רבים שמספקים מבנה פריסה שונה, כמו LinearLayout או ConstraintLayout.
יש שתי דרכים להגדיר פריסה:
- הצהרה על רכיבי ממשק משתמש ב-XML. Android מספק אוצר מילים פשוט של XML שתואם למחלקות ולמחלקות המשנה של
View, כמו אלה של הווידג'טים והפריסות. אפשר גם להשתמש בכלי Layout Editor של Android Studio כדי ליצור את פריסת ה-XML באמצעות ממשק של גרירה ושחרור. - יצירת מופעים של רכיבי פריסה בזמן ריצה. האפליקציה יכולה ליצור אובייקטים של
ViewושלViewGroupולשנות את המאפיינים שלהם באופן פרוגרמטי.
הצהרה על ממשק המשתמש ב-XML מאפשרת להפריד בין הצגת האפליקציה לבין הקוד ששולט בהתנהגות שלה. בנוסף, קובצי XML מאפשרים לספק פריסות שונות לגדלים ולכיוונים שונים של מסכים. הנושא הזה מוסבר בהרחבה במאמר תמיכה בגדלים שונים של מסכים.
ה-framework של Android מאפשר לכם להשתמש באחת מהשיטות האלה או בשתיהן כדי לבנות את ממשק המשתמש של האפליקציה. לדוגמה, אפשר להצהיר על פריסות ברירת המחדל של האפליקציה ב-XML, ואז לשנות את הפריסה בזמן הריצה.
כתיבת ה-XML
באמצעות אוצר המילים של XML ב-Android, אפשר לעצב במהירות פריסות של ממשקי משתמש ואת רכיבי המסך שהם מכילים, באותו אופן שבו יוצרים דפי אינטרנט ב-HTML עם סדרה של רכיבים מוטמעים.
כל קובץ פריסה חייב להכיל רכיב בסיסי אחד בלבד, שחייב להיות אובייקט View או ViewGroup. אחרי שמגדירים את רכיב הבסיס, אפשר להוסיף אובייקטים או ווידג'טים נוספים של פריסת רכיבים כרכיבי צאצא כדי ליצור בהדרגה היררכיה של View שמגדירה את הפריסה. לדוגמה, פריסת XML שמשתמשת בפריסה אנכית LinearLayout כדי להכיל TextView ו-Button:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello, I am a TextView" /> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello, I am a Button" /> </LinearLayout>
אחרי שמצהירים על הפריסה ב-XML, שומרים את הקובץ עם הסיומת .xml בספרייה res/layout/ של פרויקט Android כדי שהקובץ יעבור קומפילציה בצורה תקינה.
מידע נוסף על התחביר של קובץ XML של פריסה זמין במאמר בנושא משאב פריסה.
טעינת משאב ה-XML
כשמבצעים קומפילציה של האפליקציה, כל קובץ פריסת XML עובר קומפילציה למשאב View. טוענים את משאב הפריסה בהטמעה של פונקציית הקריאה החוזרת Activity.onCreate() באפליקציה. כדי לעשות זאת, קוראים לפונקציה setContentView() ומעבירים לה את ההפניה למשאב הפריסה בפורמט R.layout.layout_file_name. לדוגמה, אם פריסת ה-XML שלכם נשמרה כ-main_layout.xml, טוענים אותה עבור Activity באופן הבא:
Kotlin
fun onCreate(savedInstanceState: Bundle) { super.onCreate(savedInstanceState) setContentView(R.layout.main_layout) }
Java
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main_layout); }
מסגרת Android קוראת לשיטת הקריאה החוזרת onCreate() ב-Activity כש-Activity מופעל. מידע נוסף על מחזורי החיים של פעילויות זמין במאמר מבוא לפעילויות.
מאפיינים
כל אובייקט View ו-ViewGroup תומך במגוון משלו של מאפייני XML. חלק מהמאפיינים ספציפיים לאובייקט View. לדוגמה, TextView תומך במאפיין textSize. עם זאת, המאפיינים האלה עוברים בירושה גם לכל האובייקטים View
שמרחיבים את המחלקה הזו. חלק מהמאפיינים משותפים לכל האובייקטים View, כי הם עוברים בירושה ממחלקת הבסיס View, כמו המאפיין id. מאפיינים אחרים נחשבים פרמטרים של פריסה, כלומר מאפיינים שמתארים כיווני פריסה מסוימים של אובייקט View, כפי שהוגדרו על ידי אובייקט האב ViewGroup של אותו אובייקט.
מזהה
לכל אובייקט View יכול להיות משויך מזהה מספרי שלם כדי לזהות באופן ייחודי את View בתוך העץ. כשמבצעים קומפילציה של האפליקציה, המזהה הזה מופיע כהפניה למספר שלם, אבל בדרך כלל המזהה מוקצה בקובץ ה-XML של הפריסה כמחרוזת במאפיין id. זהו מאפיין XML שמשותף לכל האובייקטים מסוג View, והוא מוגדר על ידי המחלקה View. אתם משתמשים בו לעיתים קרובות מאוד. התחביר של מזהה בתוך תג XML הוא:
android:id="@+id/my_button"
הסמל at (@) בתחילת המחרוזת מציין שמנתח ה-XML מנתח ומרחיב את שאר מחרוזת המזהה ומזהה אותה כמשאב מזהה. הסמל פלוס (+) מציין שזהו שם משאב חדש שצריך ליצור ולהוסיף למשאבים בקובץ R.java.
מסגרת Android מציעה הרבה משאבי מזהים אחרים. כשמפנים למזהה משאב של Android, לא צריך להוסיף את הסמל +, אבל צריך להוסיף את מרחב השמות של החבילה android באופן הבא:
android:id="@android:id/empty"
מרחב השמות של החבילה android מציין שאתם מפנים למזהה ממחלקת המשאבים android.R, ולא ממחלקת המשאבים המקומית.
כדי ליצור תצוגות ולהפנות אליהן מהאפליקציה, אפשר להשתמש בתבנית נפוצה באופן הבא:
- מגדירים תצוגה בקובץ הפריסה ומקצים לה מזהה ייחודי, כמו בדוגמה הבאה:
<Button android:id="@+id/my_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/my_button_text"/>
- יוצרים מופע של אובייקט התצוגה ומקבלים אותו מהפריסה, בדרך כלל בשיטה
onCreate(), כמו בדוגמה הבאה:Kotlin
val myButton: Button = findViewById(R.id.my_button)
Java
Button myButton = (Button) findViewById(R.id.my_button);
חשוב להגדיר מזהים לאובייקטים של תצוגות כשיוצרים אובייקט RelativeLayout.
בפריסת RelativeLayout, תצוגות אחיות יכולות להגדיר את הפריסה שלהן ביחס לתצוגה אחות אחרת, שאליה מתבצעת הפניה באמצעות המזהה הייחודי.
מזהה לא צריך להיות ייחודי בכל העץ, אבל הוא צריך להיות ייחודי בחלק של העץ שמחפשים בו. יכול להיות שזה יהיה העץ כולו, ולכן מומלץ להשתמש בשם ייחודי ככל האפשר.
פרמטרים של פריסה
מאפייני פריסה של XML בשם layout_something מגדירים פרמטרים של פריסה עבור View שמתאימים ל-ViewGroup שבו הוא נמצא.
כל מחלקת ViewGroup מטמיעה מחלקה מקוננת שמרחיבה את ViewGroup.LayoutParams.
מחלקת המשנה הזו מכילה סוגי מאפיינים שמגדירים את הגודל והמיקום של כל תצוגת צאצא, בהתאם לקבוצת התצוגות. כפי שמוצג באיור 2, קבוצת התצוגה הראשית מגדירה פרמטרים של פריסה לכל תצוגת צאצא, כולל קבוצת תצוגת הצאצא.
לכל מחלקת משנה LayoutParams יש תחביר משלה להגדרת ערכים. בכל אלמנט צאצא צריך להגדיר LayoutParams שמתאים לאלמנט ההורה שלו, אבל אפשר גם להגדיר LayoutParams שונה לצאצאים שלו.
כל קבוצות התצוגה כוללות רוחב וגובה, באמצעות layout_width
ו-layout_height, וכל תצוגה צריכה להגדיר אותם. הרבה
LayoutParams כוללים שוליים וגבולות אופציונליים.
אפשר לציין רוחב וגובה עם מידות מדויקות, אבל לא מומלץ לעשות את זה לעיתים קרובות. ברוב המקרים, משתמשים באחד מהקבועים האלה כדי להגדיר את הרוחב או הגובה:
-
wrap_content: מגדיר את התצוגה כך שהגודל שלה יותאם לממדים שנדרשים לתוכן שלה. -
match_parent: גודל התצוגה יהיה מקסימלי בהתאם לגודל קבוצת התצוגה של התצוגה הראשית.
באופן כללי, לא מומלץ לציין את הרוחב והגובה של פריסה באמצעות יחידות מוחלטות כמו פיקסלים. גישה טובה יותר היא שימוש ביחידות מידה יחסיות, כמו יחידות של פיקסלים שלא תלויים בדחיסות (dp), wrap_content או match_parent, כי כך האפליקציה תוצג בצורה תקינה במגוון גדלים של מסכי מכשירים. סוגי המדידה הקבילים מוגדרים במשאב הפריסה.
מיקום הפריסה
לתצוגה יש גיאומטריה מלבנית. יש לו מיקום, שמבוטא כזוג קואורדינטות של left ו-top, ושני מאפיינים, שמבוטאים כרוחב וגובה. יחידת המידה של המיקום והמימדים היא פיקסל.
כדי לאחזר את המיקום של תצוגה, מפעילים את השיטות getLeft() ו-getTop().
הפונקציה הראשונה מחזירה את קואורדינטת השמאלית (x) של המלבן שמייצג את התצוגה. הפונקציה השנייה מחזירה את הקואורדינטה העליונה (y) של המלבן שמייצג את התצוגה. השיטות האלה מחזירות את המיקום של התצוגה ביחס לאלמנט ההורה שלה. לדוגמה, אם הפונקציה getLeft() מחזירה 20, המשמעות היא שהתצוגה ממוקמת 20 פיקסלים מימין לקצה השמאלי של הרכיב הישיר שכולל אותה.
בנוסף, יש שיטות נוחות להימנע מחישובים מיותרים:
getRight()
וgetBottom().
השיטות האלה מחזירות את הקואורדינטות של הקצוות הימני והתחתון של המלבן שמייצג את התצוגה. לדוגמה, קריאה ל-getRight() דומה לחישוב הבא: getLeft() + getWidth().
גודל, מרווח פנימי ושוליים
הגודל של תצוגה מבוטא באמצעות רוחב וגובה. לתצוגה יש שני זוגות של ערכי רוחב וגובה.
הזוג הראשון נקרא רוחב מדוד וגובה מדוד. המאפיינים האלה מגדירים את הגודל הרצוי של התצוגה בתוך הרכיב ההורה שלה. אפשר לקבל את המאפיינים שנמדדו על ידי קריאה לפונקציות getMeasuredWidth() ו-getMeasuredHeight().
הזוג השני נקרא רוחב וגובה, או לפעמים רוחב הציור וגובה הציור. המאפיינים האלה מגדירים את הגודל בפועל של התצוגה במסך, בזמן הציור ואחרי הפריסה. הערכים האלה יכולים להיות שונים מהרוחב והגובה שנמדדו, אבל הם לא חייבים להיות שונים. אפשר לקבל את הרוחב והגובה באמצעות הקריאות getWidth() ו-getHeight().
כדי למדוד את המידות של תצוגה, המערכת לוקחת בחשבון את הריווח הפנימי שלה. המרווח הפנימי מוצג בפיקסלים עבור החלקים השמאלי, העליון, הימני והתחתון של התצוגה.
אפשר להשתמש בריווח פנימי כדי להזיז את התוכן של התצוגה במספר מסוים של פיקסלים. לדוגמה, אם מגדירים שוליים פנימיים בצד ימין של 2, התוכן של התצוגה מוזז 2 פיקסלים ימינה מהקצה השמאלי. אפשר להגדיר מרווח פנימי באמצעות השיטה setPadding(int, int, int, int) ולשאול עליו באמצעות הקריאה getPaddingLeft(), getPaddingTop(), getPaddingRight() ו-getPaddingBottom().
אפשר להגדיר ריווח פנימי בתצוגה, אבל אי אפשר להגדיר שוליים. עם זאת,
קבוצות תצוגה תומכות בשוליים. מידע נוסף זמין במאמרים ViewGroup וViewGroup.MarginLayoutParams.
מידע נוסף על מאפיינים זמין במאמר מאפיין.
בנוסף להגדרת שוליים וריווח באמצעות קוד, אפשר גם להגדיר אותם בפריסות XML, כמו בדוגמה הבאה:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="16dp" android:padding="8dp" android:text="Hello, I am a TextView" /> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:paddingBottom="4dp" android:paddingEnd="8dp" android:paddingStart="8dp" android:paddingTop="4dp" android:text="Hello, I am a Button" /> </LinearLayout>
בדוגמה שלמעלה אפשר לראות איך מוחלים שוליים פנימיים ושוליים חיצוניים. ב-TextView יש שוליים וריווח אחידים מסביב, וב-Button אפשר לראות איך אפשר להחיל אותם באופן עצמאי על קצוות שונים.
פריסות נפוצות
כל מחלקת משנה של המחלקה ViewGroup מספקת דרך ייחודית להצגת התצוגות שמוטמעות בתוכה. סוג הפריסה הכי גמיש, שמספק את הכלים הכי טובים לשמירה על היררכיית פריסה רדודה, הוא ConstraintLayout.
אלה כמה מסוגי הפריסות הנפוצים שמוטמעים בפלטפורמת Android.
הפריסה מארגנת את הצאצאים בשורה אופקית או אנכית אחת, ויוצרת פס גלילה אם האורך של החלון גדול מהאורך של המסך.
בניית רשימות דינמיות
אם התוכן בפריסה דינמי או לא נקבע מראש, אפשר להשתמש ב-RecyclerView או במחלקת משנה של AdapterView.
בדרך כלל, RecyclerView היא האפשרות הטובה יותר, כי היא משתמשת בזיכרון בצורה יעילה יותר מאשר AdapterView.
אלה כמה פריסות נפוצות שאפשר ליצור באמצעות התגים RecyclerView ו-AdapterView:
RecyclerView מציע אפשרויות נוספות וגם את האפשרות ליצור כלי פריסה בהתאמה אישית.
איך ממלאים תצוגת מתאם בנתונים
אפשר לאכלס AdapterView כמו ListView או GridView על ידי קישור של מופע AdapterView ל-Adapter, שמחלץ נתונים ממקור חיצוני ויוצר View שמייצג כל רשומה של נתונים.
ב-Android יש כמה מחלקות משנה של Adapter שימושיות לאחזור סוגים שונים של נתונים וליצירת תצוגות עבור AdapterView. שני המתאמים הנפוצים ביותר הם:
ArrayAdapter- משתמשים במתאם הזה כשמקור הנתונים הוא מערך. כברירת מחדל,
ArrayAdapterיוצר תצוגה לכל פריט במערך על ידי קריאה ל-toString()לכל פריט והצבת התוכן ב-TextView.לדוגמה, אם יש לכם מערך של מחרוזות שאתם רוצים להציג ב-
ListView, אתם יכולים להגדירArrayAdapterחדש באמצעות בנאי כדי לציין את הפריסה של כל מחרוזת ואת מערך המחרוזות:Kotlin
val adapter = ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, myStringArray)
Java
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, myStringArray);
הארגומנטים של בנאי המחלקה הם:
- האפליקציה שלך
Context - הפריסה שמכילה
TextViewלכל מחרוזת במערך - מערך המחרוזות
אחר כך מתקשרים למספר
setAdapter()בטלפוןListView:Kotlin
val listView: ListView = findViewById(R.id.listview) listView.adapter = adapter
Java
ListView listView = (ListView) findViewById(R.id.listview); listView.setAdapter(adapter);
כדי להתאים אישית את המראה של כל פריט, אפשר לשנות את השיטה
toString()לאובייקטים במערך. לחלופין, כדי ליצור תצוגה לכל פריט שהיא לאTextView– למשל, אם רוצים ליצורImageViewלכל פריט במערך – מרחיבים את המחלקהArrayAdapterומבטלים את ההגדרה שלgetView()כדי להחזיר את סוג התצוגה שרוצים לכל פריט. - האפליקציה שלך
SimpleCursorAdapter- משתמשים במתאם הזה אם הנתונים מגיעים מ-
Cursor. כשמשתמשים ב-SimpleCursorAdapter, צריך לציין פריסה לשימוש בכל שורה ב-Cursorואת העמודות ב-Cursorשרוצים להוסיף לתצוגות של הפריסה הרצויה. לדוגמה, אם רוצים ליצור רשימה של שמות ומספרי טלפון של אנשים, אפשר להריץ שאילתה שמחזירהCursorשמכיל שורה לכל אדם ועמודות לשמות ולמספרים. לאחר מכן יוצרים מערך מחרוזות שמציין אילו עמודות מתוךCursorרוצים בפריסה של כל תוצאה, ומערך של מספרים שלמים שמציין את התצוגות התואמות שבהן צריך למקם כל עמודה:Kotlin
val fromColumns = arrayOf(ContactsContract.Data.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone.NUMBER) val toViews = intArrayOf(R.id.display_name, R.id.phone_number)
Java
String[] fromColumns = {ContactsContract.Data.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone.NUMBER}; int[] toViews = {R.id.display_name, R.id.phone_number};
כשיוצרים מופע של
SimpleCursorAdapter, מעבירים את הפריסה לשימוש בכל תוצאה, אתCursorשמכיל את התוצאות ואת שני המערכים האלה:Kotlin
val adapter = SimpleCursorAdapter(this, R.layout.person_name_and_number, cursor, fromColumns, toViews, 0) val listView = getListView() listView.adapter = adapter
Java
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, R.layout.person_name_and_number, cursor, fromColumns, toViews, 0); ListView listView = getListView(); listView.setAdapter(adapter);
לאחר מכן, הרכיב
SimpleCursorAdapterיוצר תצוגה לכל שורה ברכיבCursorבאמצעות הפריסה שסופקה, על ידי הוספת כל פריטfromColumnsלתצוגה המתאימהtoViews.
אם במהלך השימוש באפליקציה משנים את נתוני הבסיס שהמתאם קורא, צריך להתקשר אל notifyDataSetChanged().
הפעולה הזו שולחת הודעה לתצוגה המצורפת שהנתונים השתנו, והיא מתרעננת.
טיפול באירועים מסוג קליק
אפשר להגיב לאירועי קליקים על כל פריט ב-AdapterView
על ידי הטמעה של
AdapterView.OnItemClickListener
הממשק. לדוגמה:
Kotlin
listView.onItemClickListener = AdapterView.OnItemClickListener { parent, view, position, id -> // Do something in response to the click. }
Java
// Create a message handling object as an anonymous class. private OnItemClickListener messageClickedHandler = new OnItemClickListener() { public void onItemClick(AdapterView parent, View v, int position, long id) { // Do something in response to the click. } }; listView.setOnItemClickListener(messageClickedHandler);
מקורות מידע נוספים
אפשר לראות איך משתמשים בפריסות באפליקציית ההדגמה Sunflower ב-GitHub.


