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

רוצה לנסות את שיטת הכתיבה?
'Jetpack פיתוח נייטיב' היא ערכת הכלים המומלצת לממשק המשתמש ל-Android. הסבר איך עובדים עם פריסות ב'כתיבה'.

מערכת Android מציעה מודל רכיבים מתוחכם וחזק לפיתוח ממשק המשתמש, שמבוסס על את סוגי הפריסה הבסיסיים View והקבוצה ViewGroup. הפלטפורמה כוללת מגוון של מחלקות משנה View ו-ViewGroup מוכנות מראש – שנקראות ווידג'טים שונים, בהתאמה, שבהם ניתן להשתמש כדי לבנות את ממשק המשתמש.

רשימה חלקית של הווידג'טים הזמינים כוללת את Button, TextView, EditText, ListView, CheckBox, RadioButton, Gallery, Spinner, והפורמט המיוחד יותר AutoCompleteTextView, ImageSwitcher, וגם TextSwitcher

אלה כמה מהפריסות הזמינות: LinearLayout, FrameLayout, RelativeLayout, ועוד. דוגמאות נוספות זמינות בכתובת פריסות נפוצות.

אם אף אחד מהווידג'טים או מהפריסות שהוגדרו מראש לא עונה על הצרכים שלך, אפשר ליצור אותם מחלקה משנית אחת (View). אם אתם צריכים רק לבצע שינויים קלים בווידג'ט קיים או אפשר ליצור מחלקה משנית של הווידג'ט או הפריסה ולשנות את שיטות הפעולה שלהם.

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

  • אפשר ליצור סוג View שעבר עיבוד מותאם אישית לחלוטין – לדוגמה, 'נפח אחסון' רכיב" חדש, שעבר רינדור באמצעות גרפיקה דו-ממדית, שדומה לפקד אלקטרוני אנלוגי.
  • אפשר לשלב קבוצה של רכיבים של View לרכיב יחיד חדש, אולי כדי ליצור משהו כמו תיבה משולבת (שילוב של רשימה קופצת ושדה טקסט להזנה בחינם), פקד בורר עם שתי חלוניות (חלונית שמאלית וימינה עם רשימה בכל אחת מהן שבה ניתן להקצות מחדש איזה פריט מופיע בכל רשימה), וכן הלאה.
  • אפשר לשנות את האופן שבו רכיב EditText מעובד במסך. לאפליקציה לדוגמה של NotePad יש השפעה חיובית על יצירת דף של פנקס.
  • תוכלו לתעד אירועים אחרים, כמו לחיצות המקשים, ולטפל בהם באופן מותאם אישית, כמו משחק.

בקטעים הבאים מוסבר איך ליצור תצוגות מותאמות אישית ולהשתמש בהן באפליקציה שלכם. עבור למידע מפורט כיתה אחת (View).

הגישה הבסיסית

המאמר הזה מספק סקירה כללית של כל מה שצריך לדעת כדי ליצור View משלכם. רכיבים:

  1. הרחבה של כיתה קיימת או כיתת משנה קיימת ב-View באמצעות הכיתה שלכם.
  2. שינוי חלק מהשיטות ממחלקת העל. השיטות של מחלקות-העל שבהן אפשר לשנות מתחילות on – לדוגמה, onDraw(), onMeasure(), וגם onKeyDown() האירוע הזה דומה לאירועי on ב- Activity או ListActivity שאת/ה לשנות עבור מחזור חיים והוקים (hooks) של פונקציונליות אחרת.
  3. שימוש בסיווג החדש של התוסף. בסיום התהליך, אפשר להשתמש בסיווג החדש של התוספים במקום התצוגה שעליה היא התבססה.

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

ניתן ליצור רכיבים גרפיים מותאמים אישית לחלוטין שיופיעו בכל דרך רוצה. אולי אתם רוצים מד VU גרפי שנראה כמו מד אנלוגי ישן, או תצוגת טקסט של שירה בציבור שבו כדור מקפץ נע לאורך המילים כשאתם שרים יחד עם מכונת קריוקי. ייתכן שתרצו את מה שהרכיבים המובנים לא יכולים לעשות, לא משנה איך משלבים ביניהם.

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

כדי ליצור רכיב בהתאמה אישית מלאה, כדאי:

  • התצוגה הכללית ביותר שניתן להרחיב היא View, כך שבדרך כלל מתחילים בהרחבה כדי ליצור רכיב-על חדש.
  • תוכלו לספק constructor שיכול לקבל מאפיינים ופרמטרים מה-XML. צורכים מאפיינים ופרמטרים משלכם, כמו הצבע והטווח של מד ה-VU את הרוחב ואת שימור המחט.
  • כדאי ליצור גם פונקציות event listener, רכיבי גישה לנכסים ורכיבי שינוי משלכם התנהגות מתוחכמת יותר במחלקת הרכיבים שלכם.
  • כמעט בטוח שברצונך לשנות את onMeasure(), וסביר להניח שיהיה גם צורך אם רוצים שהרכיב יציג משהו, צריך לשנות את הערך של onDraw(). אמנם בשניהם יש פעולת ברירת המחדל, ברירת המחדל onDraw() לא עושה דבר, וברירת המחדל onMeasure() תמיד מגדירה גודל של 100x100, מה שסביר להניח שאתם לא רוצים.
  • אפשר גם לשנות שיטות on אחרות לפי הצורך.

Extend onDraw() ו-onMeasure()

השיטה onDraw() מספקת Canvas שבו ניתן להטמיע כל מה שרוצים: גרפיקה דו-ממדית, רכיבים סטנדרטיים או מותאמים אישית אחרים, טקסט מסוגנן או כל דבר אחר שאפשר לחשוב עליו.

ל-onMeasure() יש קצת יותר מעורבות. onMeasure() הוא חלק חיוני בחוזה הרינדור בין הרכיב לבין הקונטיינר שלו. onMeasure() חייב להיות בוטלה כדי לדווח באופן יעיל ומדויק על המדידות של החלקים שהיא מכילה. הדבר עשויות להיות קצת יותר מורכבות בגלל דרישות המגבלה מהורה – שמועברות onMeasure() - ובאמצעות הדרישה לקרוא ל- השיטה setMeasuredDimension() עם הרוחב והגובה שנמדדים ברגע שהם מחושב. אם לא שולחים קריאה לשיטה הזו משיטת onMeasure() שבוטלה, היא יובילו לחריגה בזמן המדידה.

ברמה הכללית, ההטמעה של onMeasure() נראית בערך כך:

  • שיטת onMeasure() שבוטלה נקראת עם רוחב וגובה מפרטים, שנחשבים כדרישות למגבלות הרוחב והגובה מדידה שאתם מייצרים. widthMeasureSpec ו-heightMeasureSpec הפרמטרים הם שני קודים של מספרים שלמים שמייצגים מאפיינים. התייחסות מלאה לסוג של תוכל למצוא את ההגבלות שהמפרטים האלה דורשים. אפשר למצוא אותן במשאבי העזרה View.onMeasure(int, int) במשאבי העזרה האלה מוסבר גם על פעולת המדידה כולה.
  • שיטת onMeasure() של הרכיב מחשבת את הרוחב והגובה של המדידה, שנדרשות לעיבוד הרכיב. הוא חייב לנסות לפעול בהתאם למפרטים שמועברים למרות שהוא יכול לחרוג מהם. במקרה כזה, ההורה יכול לבחור מה לעשות, כולל מתבצעת חיתוך, גלילה, פעולה חריגה או בקשה מ-onMeasure() לנסות שוב, אולי עם מפרטי מדידה שונים.
  • לאחר חישוב הרוחב והגובה, קוראים לפונקציה השיטה setMeasuredDimension(int width, int height) עם החישוב מדידות. אם לא תעשו זאת, המערכת תחריג את התוכן.

לפניכם סיכום של שיטות סטנדרטיות אחרות שה-framework קורא בתצוגות:

קטגוריה שיטות תיאור
יצירה יצרנים יש סוג של ה-constructor שמופעל כשהתצוגה המפורטת נוצרת מקוד וטופס שנקרא כאשר התצוגה מתנפחת מקובץ פריסה. הטופס השני מנתח ומחיל את המאפיינים שהוגדרו בקובץ הפריסה.
onFinishInflate() הקריאה מופעלת אחרי view וכל הצאצאים שלה מתנפחים מ-XML.
פריסה onMeasure(int, int) בוצעה שיחה כדי לקבוע את דרישות הגודל לתצוגה הזו ולכל של הילדים שלו.
onLayout(boolean, int, int, int, int) מתבצעת קריאה כאשר תצוגה זו חייבת להקצות גודל ומיקום לכל הצאצאים שלה.
onSizeChanged(int, int, int, int) מתבצעת שיחה כאשר גודל התצוגה הזו משתנה.
שרטוט onDraw(Canvas) מתבצעת קריאה כאשר התצוגה חייבת לעבד את התוכן שלה.
עיבוד אירועים onKeyDown(int, KeyEvent) מתבצעת קריאה כשמתרחש אירוע מרכזי.
onKeyUp(int, KeyEvent) מתבצעת שיחה כשמתרחש אירוע מרכזי.
onTrackballEvent(MotionEvent) מתבצעת קריאה כשמתרחש אירוע של תנועת כדור עקיבה.
onTouchEvent(MotionEvent) מתבצעת שיחה כשמתרחש אירוע תנועה במסך מגע.
מיקוד onFocusChanged(boolean, int, Rect) מתבצעת קריאה כאשר התצוגה צוברת או מאבדת מיקוד.
onWindowFocusChanged(boolean) מופעלת כאשר החלון שמכיל את התצוגה צובר או מאבד מיקוד.
מתבצע חיבור onAttachedToWindow() מתבצעת קריאה כשהתצוגה מצורפת לחלון.
onDetachedFromWindow() מתבצעת קריאה כאשר התצוגה מנותקת מהחלון.
onWindowVisibilityChanged(int) מתבצעת קריאה כאשר הנראות של החלון שמכיל את התצוגה משתנה.

בקרות מורכבות

אם לא רוצים ליצור רכיב מותאם אישית לחלוטין, אלא רוצים במקום זאת רכיב לשימוש חוזר שמורכב מקבוצה של אמצעי בקרה קיימים, ואז יוצר תרכובת רכיב (או פקד מורכב) הוא המתאים ביותר. לסיכום, זה כולל כמה היבטים נוספים אמצעים אטומיים או תצוגות לקבוצה לוגית של פריטים שאפשר להתייחס אליהם כאל דבר אחד יחיד. לדוגמה, תיבה משולבת יכולה להיות שילוב של שדה EditText בשורה אחת. ולחצן סמוך עם רשימה קופצת מצורפת. אם המשתמש מקיש על הלחצן ובוחר פריט מתוך הרשימה, היא מאכלסת את השדה EditText, אבל הם יכולים גם להקליד משהו ישירות אל EditText, אם הם מעדיפים.

ב-Android יש שתי תצוגות זמינות נוספות שיכולות לעשות זאת: Spinner ו AutoCompleteTextView. בכל מקרה, הקונספט הזה של תיבה משולבת הוא דוגמה טובה.

כדי ליצור רכיב מורכב, מבצעים את הפעולות הבאות:

  • בדיוק כמו במקרה של Activity, צריך להשתמש בגישה הצהרתית (מבוססת XML) כדי ליצור את הרכיבים הכלולים או להציב אותם באופן פרוגרמטי מהקוד שלכם. נקודת ההתחלה הרגילה היא Layout מסוג כלשהו, לכן כדאי ליצור מחלקה שמרחיבים Layout במקרה של תיבה משולבת, אפשר להשתמש ב-LinearLayout עם כיוון אופקי. ניתן להציב פריסות אחרות בפנים, כך שהרכיב המורכב יכול להיות מורכבים ומובנים באופן שרירותי.
  • ב-constructor של המחלקה החדשה, לוקחים את הפרמטרים שמחלקת העל מצפה להעביר קודם כל לבונה של מחלקה-על. לאחר מכן תוכלו להגדיר את התצוגות האחרות לשימוש ברכיב החדש. כאן יוצרים את השדה EditText, רשימה קופצת. תוכלו להציג מאפיינים ופרמטרים משלכם ב-XML, שיכול למשוך ולהשתמש.
  • אפשר גם ליצור פונקציות מאזינים לאירועים שהתצוגות המפורטות שלכם עשויות ליצור. לדוגמה, שיטת ה-listener עבור ה-listening של פריט ברשימה כדי לעדכן את התוכן של EditText אם נבחרה רשימה.
  • אפשר גם ליצור נכסים משלכם עם רכיבי גישה ומגבילי גישה. לדוגמה, אפשר לתת ל- הערך של EditText יוגדר בהתחלה ברכיב ובשאילתה עבור התוכן שלו כאשר הדרושים.
  • אפשר גם לשנות את onDraw() ואת onMeasure(). בדרך כלל אין צורך בכך כאשר הרחבה של Layout, כי לפריסה יש התנהגות ברירת מחדל שככל הנראה פועלת כמו שצריך.
  • לחלופין, אפשר לבטל שיטות on אחרות, כמו onKeyDown(), למשל כדי לבחור שיטות מסוימות מתוך הרשימה הקופצת של תיבה משולבת, שמקישים על מקש מסוים.

יש יתרונות לשימוש ב-Layout כבסיס לבקרה מותאמת אישית, כולל:

  • אפשר לציין את הפריסה באמצעות קובצי ה-XML ההצהרתיים, בדיוק כמו במסך פעילות, או שאפשר ליצור תצוגות באופן פרוגרמטי ולהציב אותן בתוך הפריסה מהקוד שלכם.
  • השיטות onDraw() ו-onMeasure(), וגם רוב השיטות האחרות ל-on השיטות יש התנהגות מתאימה, לכן אין צורך לשנות אותן.
  • אפשר ליצור במהירות תצוגות מורכבות שרירותיות ולהשתמש בהן שוב כאילו היו רכיב יחיד.

שינוי סוג תצוגה קיימת

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

לדוגמה, פנקס רשימות האפליקציה לדוגמה מדגימה היבטים רבים של השימוש בפלטפורמת Android. ביניהם הרחבה של תצוגה אחת (EditText) ליצירת פנקס שורות. זו לא דוגמה מושלמת, וממשקי ה-API האופן שבו זה עשוי להשתנות, אבל הוא מדגים את העקרונות.

אם עדיין לא עשית זאת, אפשר לייבא את דוגמת ההערות אל Android Studio או לעיין במקור באמצעות הקישור שסופק. באופן ספציפי, יש לעיין בהגדרה של LinedEditText ב NoteEditor.java חדש.

הנה כמה דברים שכדאי לשים לב אליהם בקובץ:

  1. ההגדרה

    המחלקה מוגדרת עם השורה הבאה:
    public static class LinedEditText extends EditText

    LinedEditText מוגדר כמחלקה פנימית בתוך NoteEditor פעילות, אבל הוא ציבורי כך שניתן לגשת אליו בתור NoteEditor.LinedEditText מחוץ לכיתה NoteEditor.

    בנוסף, LinedEditText הוא static, כלומר הוא לא יוצר מה שנקרא "שיטות סינתטיות" שמאפשרים לו לגשת לנתונים מכיתת ההורה. כלומר, פועל כמחלקה נפרדת ולא כתחום שקשור באופן הדוק ל-NoteEditor. זוהי דרך נקייה יותר ליצור כיתות פנימיות אם הם לא צריכים גישה למצב מכיתה חיצונית. הוא שומר על גודל הכיתה שנוצר ומאפשר להשתמש בה בקלות הסוגים.

    LinedEditText מרחיב את EditText, שזו התצוגה שניתן להתאים אישית במקרה הזה. בסיום, הכיתה החדשה תוכל להחליף EditText רגיל. צפייה.

  2. אתחול הכיתה

    כמו תמיד, הסופר-אדמין נקרא קודם. זהו לא Builder ברירת מחדל, אבל הפרמטר הזה. השדה EditText נוצר עם הפרמטרים האלה כשהוא מתנפחים מקובץ פריסת XML. לכן ה-constructor צריך לקחת אותם ולהעביר אותם גם את ה-constructor של מחלקת העל.

  3. שיטות שיוחלפו

    הדוגמה הזו מבטלת רק את ה-method onDraw(), אבל יכול להיות שתצטרכו לשנות אותה אחרים בזמן שאתם יוצרים רכיבים מותאמים אישית משלכם.

    בדוגמה הזו, אפשר לשנות את השיטה onDraw() כדי לצבוע את הקווים הכחולים את אזור העריכה של התצוגה EditText. אזור העריכה מועבר אל אזור העריכה אמצעי תשלום אחד (onDraw()). קוראים לשיטה super.onDraw() לפני ה-method מסתיים. יש להפעיל את השיטה 'סיווג-על'. במקרה הזה, הפעילו אותו בסוף אחרי תציירו את הקווים שרוצים לכלול.

  4. רכיב בהתאמה אישית

    עכשיו יש לכם את הרכיב המותאם אישית, אבל איך תוכלו להשתמש בו? בדוגמה ב-Notepad, נעשה שימוש ישירות ברכיב בהתאמה אישית מהפריסה המוצהרת, לכן צריך לבדוק note_editor.xml ב- res/layout תיקייה:

    <view xmlns:android="http://schemas.android.com/apk/res/android"
        class="com.example.android.notepad.NoteEditor$LinedEditText"
        android:id="@+id/note"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/transparent"
        android:padding="5dp"
        android:scrollbars="vertical"
        android:fadingEdge="vertical"
        android:gravity="top"
        android:textSize="22sp"
        android:capitalize="sentences"
    />
    

    הרכיב המותאם אישית נוצר כתצוגה גנרית ב-XML, והמחלקה מצוינת באמצעות החבילה המלאה. המחלקה הפנימית שמגדירים מוזכרת באמצעות סימון NoteEditor$LinedEditText, שהוא דרך סטנדרטית להתייחסות בשפת התכנות Java.

    אם רכיב התצוגה בהתאמה אישית לא מוגדר כמחלקה פנימית, אתם יכולים להצהיר על התצוגה עם שם רכיב ה-XML, ללא המאפיין class. עבור דוגמה:

    <com.example.android.notepad.LinedEditText
      id="@+id/note"
      ... />
    

    שימו לב שהכיתה LinedEditText היא עכשיו קובץ כיתתי נפרד. כאשר ה-class הוא במחלקה NoteEditor, והשיטה הזו לא פועלת.

    המאפיינים והפרמטרים האחרים בהגדרה הם אלו שמועברים constructor של רכיב ואז מועבר אל ה-constructor של EditText, כך אותם הפרמטרים הם אותם פרמטרים שבהם אתם משתמשים בתצוגה מפורטת של EditText. ניתן להוסיף אחרים גם כן.

היצירה של רכיבים בהתאמה אישית מורכבת רק ממה שאתם צריכים.

רכיב מתוחכם יותר יכול לבטל עוד יותר שיטות של on ולהציג את המאפיין שלו שיטות סיוע שלו, להתאמה אישית של המאפיינים וההתנהגות שלו. המגבלה היחידה היא את הדמיון ומה שהרכיב צריך לעשות.