ה-codelab הזה הוא חלק מהקורס Advanced Android in Kotlin (פיתוח מתקדם ל-Android ב-Kotlin). כדי להפיק את המרב מהקורס הזה, מומלץ לעבוד על ה-codelabs לפי הסדר, אבל זה לא חובה. כל ה-codelab של הקורס מפורטים בדף הנחיתה של ה-codelab בנושא Android מתקדם ב-Kotlin.
מבוא
Android מציעה קבוצה גדולה של מחלקות משנה של View, כמו Button, TextView, EditText, ImageView, CheckBox או RadioButton. אפשר להשתמש במחלקות המשנה האלה כדי ליצור ממשק משתמש שמאפשר אינטראקציה עם המשתמש ומציג מידע באפליקציה. אם אף אחת ממחלקות המשנה של View לא מתאימה לצרכים שלכם, אתם יכולים ליצור מחלקת משנה של View שנקראת תצוגה מותאמת אישית .
כדי ליצור תצוגה בהתאמה אישית, אפשר להרחיב מחלקת משנה קיימת של View (כמו Button או EditText) או ליצור מחלקת משנה משלכם של View. אם מרחיבים את View ישירות, אפשר ליצור רכיב אינטראקטיבי בממשק המשתמש בכל גודל וצורה על ידי החלפת השיטה onDraw() של View כדי לצייר אותו.
אחרי שיוצרים תצוגה בהתאמה אישית, אפשר להוסיף אותה לפריסות של הפעילות באותו אופן שבו מוסיפים TextView או Button.
בשיעור הזה נסביר איך ליצור תצוגה בהתאמה אישית מאפס על ידי הרחבה של View.
מה שכדאי לדעת
- איך יוצרים אפליקציה עם פעילות ומריצים אותה באמצעות Android Studio.
מה תלמדו
- איך מרחיבים את
Viewכדי ליצור תצוגה בהתאמה אישית. - איך מציירים תצוגה מותאמת אישית בצורת עיגול.
- איך משתמשים ב-listeners כדי לטפל באינטראקציה של המשתמש עם התצוגה המותאמת אישית.
- איך משתמשים בתצוגה מותאמת אישית בפריסה.
הפעולות שתבצעו:
באפליקציה CustomFanController מוצג איך ליצור מחלקת משנה של תצוגה בהתאמה אישית על ידי הרחבת המחלקה View. שם תת-הסיווג החדש הוא DialView.
באפליקציה מוצג רכיב ממשק משתמש עגול שדומה לבקר פיזי של מאוורר, עם הגדרות לכיבוי (0), נמוך (1), בינוני (2) וגבוה (3). כשהמשתמש מקיש על התצוגה, אינדיקטור הבחירה עובר למיקום הבא: 0-1-2-3, וחוזר ל-0. בנוסף, אם הבחירה היא 1 או יותר, צבע הרקע של החלק העגול בתצוגה משתנה מאפור לירוק (מה שמציין שהמאוורר פועל).


תצוגות הן אבני הבניין הבסיסיות של ממשק המשתמש של אפליקציה. המחלקות View מספקות מחלקות משנה רבות, שנקראות ווידג'טים של ממשק המשתמש, שמכסות הרבה מהצרכים של ממשק המשתמש של אפליקציית Android טיפוסית.
אבני בניין של ממשק המשתמש, כמו Button ו-TextView, הן מחלקות משנה שמרחיבות את המחלקה View. כדי לחסוך זמן ומאמץ בפיתוח, אפשר להרחיב אחת מהמחלקות המשנה האלה של View. התצוגה המותאמת אישית מקבלת בירושה את המראה וההתנהגות של התצוגה הראשית, ואפשר לשנות את ההתנהגות או את המראה שלה. לדוגמה, אם מרחיבים את EditText כדי ליצור תצוגה בהתאמה אישית, התצוגה פועלת בדיוק כמו תצוגה של EditText, אבל אפשר גם להתאים אותה אישית כך שיוצג בה, למשל, לחצן X שמנקה את הטקסט משדה הזנת הטקסט.
אפשר להרחיב כל מחלקת משנה של View, כמו EditText, כדי לקבל תצוגה מותאמת אישית – בוחרים את המחלקה שהכי קרובה למה שרוצים להשיג. לאחר מכן אפשר להשתמש בתצוגה המותאמת אישית כמו בכל מחלקת משנה של View בפריסה אחת או יותר כאלמנט XML עם מאפיינים.
כדי ליצור תצוגה מותאמת אישית משלכם מאפס, מרחיבים את המחלקה View עצמה. הקוד שלכם מחליף את השיטות של View כדי להגדיר את המראה והפונקציונליות של התצוגה. כדי ליצור תצוגה מותאמת אישית משלכם, אתם צריכים לצייר את כל רכיב ממשק המשתמש בכל גודל וצורה על המסך. אם יוצרים מחלקת משנה של תצוגה קיימת כמו Button, המחלקה הזו מטפלת בציור בשבילכם. (בהמשך ה-Codelab הזה נסביר על ציור).
כדי ליצור תצוגה בהתאמה אישית, פועלים לפי השלבים הכלליים הבאים:
- יוצרים מחלקת תצוגה מותאמת אישית שמרחיבה את
View, או מרחיבה מחלקת משנה שלView(כמוButtonאוEditText). - אם מרחיבים מחלקת משנה קיימת
View, צריך לשנות רק את ההתנהגות או את ההיבטים של המראה שרוצים לשנות. - אם מרחיבים את המחלקה
View, מציירים את הצורה של התצוגה המותאמת אישית ושולטים במראה שלה על ידי החלפת שיטותViewכמוonDraw()ו-onMeasure()במחלקה החדשה. - מוסיפים קוד כדי להגיב לאינטראקציה של המשתמש, ואם צריך, מציירים מחדש את התצוגה בהתאמה אישית.
- משתמשים במחלקת התצוגה המותאמת אישית כווידג'ט של ממשק משתמש בפריסת ה-XML של הפעילות. אפשר גם להגדיר מאפיינים מותאמים אישית לתצוגה, כדי לספק התאמה אישית לתצוגה בפריסות שונות.
במשימה הזו תלמדו:
- יוצרים אפליקציה עם
ImageViewבתור placeholder זמני לתצוגה המותאמת אישית. - מרחיבים את
Viewכדי ליצור את התצוגה המותאמת אישית. - מאתחלים את התצוגה המותאמת אישית עם ערכי ציור וצביעה.
שלב 1: יצירת אפליקציה עם placeholder של ImageView
- יוצרים אפליקציית Kotlin עם השם
CustomFanControllerבאמצעות התבנית Empty Activity (פעילות ריקה). מוודאים ששם החבילה הואcom.example.android.customfancontroller. - פותחים את
activity_main.xmlבכרטיסייה Text כדי לערוך את קוד ה-XML. - מחליפים את הקוד הקיים
TextViewבקוד הזה. הטקסט הזה משמש כתווית בפעילות של התצוגה המותאמת אישית.
<TextView
android:id="@+id/customViewLabel"
android:textAppearance="@style/Base.TextAppearance.AppCompat.Display3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="16dp"
android:textColor="@android:color/black"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginTop="24dp"
android:text="Fan Control"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>- מוסיפים את הרכיב
ImageViewלפריסה. זהו placeholder לתצוגה המותאמת אישית שתיצרו בסדנת התכנות הזו.
<ImageView
android:id="@+id/dialView"
android:layout_width="200dp"
android:layout_height="200dp"
android:background="@android:color/darker_gray"
app:layout_constraintTop_toBottomOf="@+id/customViewLabel"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="8dp"/>- חילוץ משאבי מחרוזות ומשאבי מאפיינים בשני רכיבי ממשק המשתמש.
- לוחצים על הכרטיסייה עיצוב. הפריסה צריכה להיראות כך:

שלב 2. יצירת מחלקה של תצוגה בהתאמה אישית
- יוצרים מחלקה חדשה של Kotlin בשם
DialView. - משנים את הגדרת המחלקה כדי להרחיב את
View. כשמוצגת בקשה, מייבאים אתandroid.view.View. - לוחצים על
Viewואז על הנורה האדומה. בוחרים באפשרות הוספת בנאים של תצוגת Android באמצעות '@JvmOverloads'. Android Studio מוסיף את הבונה מהמחלקהView. ההערה@JvmOverloadsמורה לקומפיילר של Kotlin ליצור עומסים יתרים לפונקציה הזו, שמחליפים את ערכי ברירת המחדל של הפרמטרים.
class DialView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {- מעל הגדרת המחלקה
DialView, ממש מתחת לייבוא, מוסיפיםenumברמה העליונה כדי לייצג את מהירויות המאוורר הזמינות. שימו לב שהערךenumהוא מסוגIntכי הערכים הם משאבי מחרוזות ולא מחרוזות בפועל. ב-Android Studio יוצגו שגיאות לגבי משאבי המחרוזות החסרים בכל אחד מהערכים האלה. תצטרכו לתקן את זה בשלב מאוחר יותר.
private enum class FanSpeed(val label: Int) {
OFF(R.string.fan_off),
LOW(R.string.fan_low),
MEDIUM(R.string.fan_medium),
HIGH(R.string.fan_high);
}- מתחת ל-
enum, מוסיפים את הקבועים האלה. תשתמשו בהם כחלק מציור המדדים והתוויות.
private const val RADIUS_OFFSET_LABEL = 30
private const val RADIUS_OFFSET_INDICATOR = -35- בתוך המחלקה
DialView, מגדירים כמה משתנים שצריך כדי לצייר את התצוגה המותאמת אישית. מייבאים אתandroid.graphics.PointFאם מתבקשים.
private var radius = 0.0f // Radius of the circle.
private var fanSpeed = FanSpeed.OFF // The active selection.
// position variable which will be used to draw label and indicator circle position
private val pointPosition: PointF = PointF(0.0f, 0.0f)- הערך
radiusהוא הרדיוס הנוכחי של העיגול. הערך הזה מוגדר כשהתצוגה מצוירת על המסך. -
fanSpeedהיא המהירות הנוכחית של המאוורר, אחד מהערכים ברשימת הערכיםFanSpeed. ערך ברירת המחדל הואOFF. - לבסוף
postPositionהוא נקודה X,Y שתשמש לציור של כמה מרכיבי התצוגה במסך.
הערכים האלה נוצרים ומאותחלים כאן במקום בזמן שבו התצוגה מצוירת בפועל, כדי להבטיח ששלב הציור בפועל יפעל כמה שיותר מהר.
- בנוסף, בתוך הגדרת המחלקה
DialView, מאתחלים אובייקטPaintעם כמה סגנונות בסיסיים. מייבאים אתandroid.graphics.Paintואתandroid.graphics.Typefaceכשמוצגת בקשה לעשות זאת. כמו במשתנים, הסגנונות האלה מאותחלים כאן כדי להאיץ את שלב הציור.
private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
style = Paint.Style.FILL
textAlign = Paint.Align.CENTER
textSize = 55.0f
typeface = Typeface.create( "", Typeface.BOLD)
}- פותחים את
res/values/strings.xmlומוסיפים את משאבי המחרוזות למהירויות המאוורר:
<string name="fan_off">off</string>
<string name="fan_low">1</string>
<string name="fan_medium">2</string>
<string name="fan_high">3</string>אחרי שיוצרים תצוגה מותאמת אישית, צריך להיות אפשר לצייר אותה. כשמרחיבים מחלקת משנה של View כמו EditText, מחלקת המשנה הזו מגדירה את המראה והמאפיינים של התצוגה ומציירת את עצמה על המסך. לכן, לא צריך לכתוב קוד כדי לצייר את התצוגה. במקום זאת, אפשר לשנות את השיטות של הרכיב האב כדי להתאים אישית את התצוגה.
אם אתם יוצרים תצוגה משלכם מאפס (על ידי הרחבת View), אתם אחראים לציור של התצוגה כולה בכל פעם שהמסך מתרענן, ולביטול השיטות של View שמטפלות בציור. כדי לצייר תצוגה מותאמת אישית שמרחיבה את View, צריך:
- כדי לחשב את הגודל של התצוגה כשהיא מופיעה בפעם הראשונה, ובכל פעם שהגודל שלה משתנה, צריך לבטל את השיטה
onSizeChanged(). - מחליפים את השיטה
onDraw()כדי לצייר את התצוגה המותאמת אישית, באמצעות אובייקטCanvasשמעוצב על ידי אובייקטPaint. - קוראים לשיטה
invalidate()בתגובה לקליק של משתמש שמשנה את אופן הציור של התצוגה, כדי לבטל את התוקף של התצוגה כולה, וכך לכפות קריאה לשיטהonDraw()כדי לצייר מחדש את התצוגה.
השיטה onDraw() מופעלת בכל פעם שהמסך מתרענן, וזה יכול לקרות הרבה פעמים בשנייה. מטעמי ביצועים וכדי למנוע תקלות חזותיות, מומלץ לבצע כמה שפחות פעולות ב-onDraw(). בפרט, אל תציב הקצאות ב-onDraw(), מכיוון שהקצאות עלולות להוביל לאיסוף אשפה שעלול לגרום לגמגום חזותי.
בכיתות Canvas ו-Paint יש כמה קיצורי דרך שימושיים לציור:
- ציור טקסט באמצעות
drawText(). מציינים את הגופן באמצעות הקריאהsetTypeface(), ואת צבע הטקסט באמצעות הקריאהsetColor(). - שרטוט צורות פשוטות באמצעות
drawRect(),drawOval()ו-drawArc(). כדי לשנות את הצורה כך שתהיה מלאה, עם קו מתאר או שילוב של שניהם, קוראים ל-setStyle(). - לצייר מפות סיביות באמצעות
drawBitmap().
ב-codelab נוסף נסביר על Canvas ועל Paint. מידע נוסף על אופן הצגת התצוגות ב-Android זמין במאמר איך Android מצייר תצוגות.
במשימה הזו תציירו את התצוגה המותאמת אישית של בקר המאוורר על המסך – החוגה עצמה, האינדיקטור של המיקום הנוכחי והתוויות של האינדיקטור – באמצעות השיטות onSizeChanged() ו- onDraw(). בנוסף, תיצרו שיטת עזר, computeXYForSpeed(),, כדי לחשב את המיקום הנוכחי של התווית של המחוג על השעון.
שלב 1. חישוב המיקומים וציור התצוגה
- במחלקת
DialView, מתחת לאתחולים, מבטלים את השיטהonSizeChanged()מהמחלקהViewכדי לחשב את הגודל של החוגה בתצוגה המותאמת אישית. ייבוא שלkotlinmath.minלפי בקשה.
השיטהonSizeChanged()נקראת בכל פעם שגודל התצוגה משתנה, כולל בפעם הראשונה שהיא מצוירת כשהפריסה מורחבת. מחליפים אתonSizeChanged()כדי לחשב מיקומים, מאפיינים וערכים אחרים שקשורים לגודל של התצוגה המותאמת אישית, במקום לחשב אותם מחדש בכל פעם שמציירים. במקרה הזה, משתמשים ב-onSizeChanged()כדי לחשב את הרדיוס הנוכחי של רכיב העיגול של החוגה.
override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) {
radius = (min(width, height) / 2.0 * 0.8).toFloat()
}- מתחת ל-
onSizeChanged(), מוסיפים את הקוד הזה כדי להגדיר פונקציית הרחבה שלcomputeXYForSpeed()עבור המחלקהPointF. מייבאים אתkotlin.math.cosואתkotlin.math.sinכשמוצגת בקשה לעשות זאת. פונקציית ההרחבה הזו במחלקהPointFמחשבת את הקואורדינטות X ו-Y במסך של תווית הטקסט והאינדיקטור הנוכחי (0, 1, 2 או 3), בהינתן המיקום הנוכחיFanSpeedוהרדיוס של החוגה. השימוש יהיה ב-onDraw().
private fun PointF.computeXYForSpeed(pos: FanSpeed, radius: Float) {
// Angles are in radians.
val startAngle = Math.PI * (9 / 8.0)
val angle = startAngle + pos.ordinal * (Math.PI / 4)
x = (radius * cos(angle)).toFloat() + width / 2
y = (radius * sin(angle)).toFloat() + height / 2
}- מבטלים את השיטה
onDraw()כדי להציג את התצוגה במסך באמצעות המחלקותCanvasו-Paint. מייבאים אתandroid.graphics.Canvasכשמתבקשים. זהו שינוי ברירת המחדל של השלד:
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
}- בתוך
onDraw(), מוסיפים את השורה הזו כדי להגדיר את צבע הצביעה לאפור (Color.GRAY) או לירוק (Color.GREEN) בהתאם למהירות המאוורר, שהיאOFFאו ערך אחר. מייבאים אתandroid.graphics.Colorכשמתבקשים.
// Set dial background color to green if selection not off.
paint.color = if (fanSpeed == FanSpeed.OFF) Color.GRAY else Color.GREEN- מוסיפים את הקוד הזה כדי לשרטט עיגול לחוגה, באמצעות השיטה
drawCircle(). בשיטה הזו נעשה שימוש ברוחב ובגובה של התצוגה הנוכחית כדי למצוא את מרכז העיגול, את הרדיוס של העיגול ואת צבע הצביעה הנוכחי. המאפייניםwidthו-heightהם חלק ממחלקת העלViewומציינים את הממדים הנוכחיים של התצוגה.
// Draw the dial.
canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), radius, paint)- מוסיפים את הקוד הבא כדי לצייר עיגול קטן יותר לסימון של מהירות המאוורר, גם באמצעות השיטה
drawCircle(). החלק הזה משתמש ב-PointF.computeXYforSpeed()שיטת הרחבה לחישוב הקואורדינטות X ו-Y של מרכז האינדיקטור על סמך מהירות המאוורר הנוכחית.
// Draw the indicator circle.
val markerRadius = radius + RADIUS_OFFSET_INDICATOR
pointPosition.computeXYForSpeed(fanSpeed, markerRadius)
paint.color = Color.BLACK
canvas.drawCircle(pointPosition.x, pointPosition.y, radius/12, paint)- לבסוף, מציירים את התוויות של מהירות המאוורר (0, 1, 2, 3) במיקומים המתאימים סביב החוגה. החלק הזה של השיטה קורא שוב ל-
PointF.computeXYForSpeed()כדי לקבל את המיקום של כל תווית, ומשתמש מחדש באובייקטpointPositionבכל פעם כדי להימנע מהקצאות. משתמשים ב-drawText()כדי לצייר את התוויות.
// Draw the text labels.
val labelRadius = radius + RADIUS_OFFSET_LABEL
for (i in FanSpeed.values()) {
pointPosition.computeXYForSpeed(i, labelRadius)
val label = resources.getString(i.label)
canvas.drawText(label, pointPosition.x, pointPosition.y, paint)
}השיטה onDraw() המלאה נראית כך:
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// Set dial background color to green if selection not off.
paint.color = if (fanSpeed == FanSpeed.OFF) Color.GRAY else Color.GREEN
// Draw the dial.
canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), radius, paint)
// Draw the indicator circle.
val markerRadius = radius + RADIUS_OFFSET_INDICATOR
pointPosition.computeXYForSpeed(fanSpeed, markerRadius)
paint.color = Color.BLACK
canvas.drawCircle(pointPosition.x, pointPosition.y, radius/12, paint)
// Draw the text labels.
val labelRadius = radius + RADIUS_OFFSET_LABEL
for (i in FanSpeed.values()) {
pointPosition.computeXYForSpeed(i, labelRadius)
val label = resources.getString(i.label)
canvas.drawText(label, pointPosition.x, pointPosition.y, paint)
}
}שלב 2. הוספת התצוגה לפריסה
כדי להוסיף תצוגה בהתאמה אישית לממשק המשתמש של אפליקציה, צריך לציין אותה כרכיב בפריסת ה-XML של הפעילות. אפשר לשלוט במראה ובהתנהגות שלו באמצעות מאפייני רכיבי XML, כמו בכל רכיב אחר בממשק המשתמש.
- ב-
activity_main.xml, משנים את התגImageViewשלdialViewל-com.example.android.customfancontroller.DialViewומוחקים את המאפייןandroid:background. גםDialViewוגםImageViewהמקורי יורשים את המאפיינים הרגילים מהמחלקהView, כך שאין צורך לשנות אף אחד מהמאפיינים האחרים. האלמנט החדשDialViewנראה כך:
<com.example.android.customfancontroller.DialView
android:id="@+id/dialView"
android:layout_width="@dimen/fan_dimen"
android:layout_height="@dimen/fan_dimen"
app:layout_constraintTop_toBottomOf="@+id/customViewLabel"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginLeft="@dimen/default_margin"
android:layout_marginRight="@dimen/default_margin"
android:layout_marginTop="@dimen/default_margin" />- מריצים את האפליקציה. תצוגת השליטה במאוורר מופיעה בפעילות.

המשימה האחרונה היא להפעיל את התצוגה המותאמת אישית כדי לבצע פעולה כשהמשתמש מקיש על התצוגה. כל הקשה אמורה להעביר את סימן הבחירה למיקום הבא: כבוי-1-2-3 וחזרה למצב כבוי. בנוסף, אם הבחירה היא 1 או יותר, הרקע משתנה מאפור לירוק, מה שמציין שעוצמת המאוורר מופעלת.
כדי להפוך את התצוגה בהתאמה אישית ללחיצה, אתם צריכים:
- מגדירים את המאפיין
isClickableשל התצוגה לערךtrue. כך התצוגה המותאמת אישית יכולה להגיב לקליקים. - מטמיעים את
performClick()של המחלקהViewכדי לבצע פעולות כשלוחצים על התצוגה. - מבצעים קריאה ל-method
invalidate(). הפעולה הזו אומרת למערכת Android להפעיל את השיטהonDraw()כדי לצייר מחדש את התצוגה.
בדרך כלל, בתצוגה רגילה של Android, מטמיעים את OnClickListener() כדי לבצע פעולה כשהמשתמש לוחץ על התצוגה הזו. בתצוגה בהתאמה אישית, מטמיעים במקום זאת את ה-method performClick() של המחלקה View וקוראים ל-super.performClick(). שיטת ברירת המחדל performClick() קוראת גם ל-onClickListener(), כך שאפשר להוסיף את הפעולות ל-performClick() ולהשאיר את onClickListener() זמין להתאמה אישית נוספת על ידכם או על ידי מפתחים אחרים שעשויים להשתמש בתצוגה המותאמת אישית שלכם.
- ב-
DialView.kt, בתוך המנייהFanSpeed, מוסיפים פונקציית הרחבהnext()שמשנה את מהירות המאוורר הנוכחית למהירות הבאה ברשימה (מ-OFFל-LOW, ל-MEDIUMול-HIGH, ואז חזרה ל-OFF). המנייה המלאה נראית עכשיו כך:
private enum class FanSpeed(val label: Int) {
OFF(R.string.fan_off),
LOW(R.string.fan_low),
MEDIUM(R.string.fan_medium),
HIGH(R.string.fan_high);
fun next() = when (this) {
OFF -> LOW
LOW -> MEDIUM
MEDIUM -> HIGH
HIGH -> OFF
}
}- בתוך המחלקה
DialView, ממש לפני השיטהonSizeChanged(), מוסיפים בלוקinit(). הגדרה של המאפייןisClickableשל התצוגה כ-true מאפשרת לתצוגה לקבל קלט מהמשתמש.
init {
isClickable = true
}- מתחת ל-
init(),, מחליפים את השיטהperformClick()בקוד שבהמשך.
override fun performClick(): Boolean {
if (super.performClick()) return true
fanSpeed = fanSpeed.next()
contentDescription = resources.getString(fanSpeed.label)
invalidate()
return true
}השיחה אל super.קודם צריך לבצע את הפעולה performClick(), שמפעילה אירועי נגישות וגם שיחות onClickListener().
בשתי השורות הבאות, המהירות של המאוורר עולה באמצעות השיטה next(), ותיאור התוכן של התצוגה מוגדר למשאב המחרוזת שמייצג את המהירות הנוכחית (off, 1, 2 או 3).
לבסוף, השיטה invalidate() מבטלת את התוקף של התצוגה כולה, ומאלצת קריאה ל-onDraw() כדי לצייר מחדש את התצוגה. אם משהו בתצוגה המותאמת אישית משתנה מסיבה כלשהי, כולל אינטראקציה של המשתמש, וצריך להציג את השינוי, צריך לקרוא ל-invalidate().
- מריצים את האפליקציה ומקישים על הרכיב
DialViewכדי להעביר את האינדיקטור ממצב מושבת למצב 1. החוגה אמורה להפוך לירוקה. בכל הקשה, האינדיקטור אמור לעבור למיקום הבא. כשהאינדיקטור חוזר למצב מושבת, החוגה אמורה לחזור לצבע אפור.


בדוגמה הזו מוצג המנגנון הבסיסי של שימוש במאפיינים מותאמים אישית בתצוגה המותאמת אישית. אתם מגדירים מאפיינים מותאמים אישית עבור המחלקה DialView עם צבע שונה לכל מיקום בחוגה.
- יצירה ופתיחה של
res/values/attrs.xml. - בתוך
<resources>, מוסיפים אלמנט משאב<declare-styleable>. - בתוך אלמנט המשאב
<declare-styleable>, מוסיפים שלושה אלמנטיםattr, אחד לכל מאפיין, עםnameו-format. formatהוא כמו סוג, ובמקרה הזה הואcolor.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="DialView">
<attr name="fanColor1" format="color" />
<attr name="fanColor2" format="color" />
<attr name="fanColor3" format="color" />
</declare-styleable>
</resources>- פותחים את קובץ הפריסה
activity_main.xml. - במאפיין
DialView, מוסיפים את המאפייניםfanColor1,fanColor2ו-fanColor3ומגדירים את הערכים שלהם לצבעים שמוצגים בהמשך. צריך להשתמש ב-app:כקידומת למאפיין המותאם אישית (כמו ב-app:fanColor1) ולא ב-android:, כי המאפיינים המותאמים אישית שייכים למרחב השמותschemas.android.com/apk/res/your_app_package_nameולא למרחב השמותandroid.
app:fanColor1="#FFEB3B"
app:fanColor2="#CDDC39"
app:fanColor3="#009688"כדי להשתמש במאפיינים במחלקה DialView, צריך לאחזר אותם. הם נשמרים בAttributeSet, שמועבר לכיתה שלכם כשהיא נוצרת, אם הוא קיים. מאחזרים את המאפיינים ב-init ומקצים את ערכי המאפיינים למשתנים מקומיים לצורך שמירה במטמון.
- פותחים את קובץ הכיתה
DialView.kt. - בתוך התג
DialView, מצהירים על משתנים כדי לשמור במטמון את ערכי המאפיינים.
private var fanSpeedLowColor = 0
private var fanSpeedMediumColor = 0
private var fanSeedMaxColor = 0- בבלוק
init, מוסיפים את הקוד הבא באמצעות פונקציית ההרחבהwithStyledAttributes. אתם מספקים את המאפיינים ואת התצוגה, ומגדירים את המשתנים המקומיים. כשמייבאים אתwithStyledAttributes, מייבאים גם את הפונקציה הנכונהgetColor().
context.withStyledAttributes(attrs, R.styleable.DialView) {
fanSpeedLowColor = getColor(R.styleable.DialView_fanColor1, 0)
fanSpeedMediumColor = getColor(R.styleable.DialView_fanColor2, 0)
fanSeedMaxColor = getColor(R.styleable.DialView_fanColor3, 0)
}- משתמשים במשתנים המקומיים ב-
onDraw()כדי להגדיר את צבע החוגה על סמך מהירות המאוורר הנוכחית. מחליפים את השורה שבה מוגדר צבע הצביעה (paint.color=if(fanSpeed== FanSpeed.OFF) Color.GRAYelseColor.GREEN) בקוד שבהמשך.
paint.color = when (fanSpeed) {
FanSpeed.OFF -> Color.GRAY
FanSpeed.LOW -> fanSpeedLowColor
FanSpeed.MEDIUM -> fanSpeedMediumColor
FanSpeed.HIGH -> fanSeedMaxColor
} as Int- מריצים את האפליקציה, לוחצים על החוגה, והגדרת הצבע אמורה להיות שונה בכל מיקום, כמו שמוצג בהמשך.
|
|
|
|
מידע נוסף על מאפייני תצוגה בהתאמה אישית זמין במאמר יצירת מחלקת תצוגה.
נגישות היא אוסף של טכניקות עיצוב, הטמעה ובדיקה שמאפשרות לכולם להשתמש באפליקציה, כולל אנשים עם מוגבלויות.
מוגבלויות נפוצות שיכולות להשפיע על השימוש במכשיר Android כוללות עיוורון, לקות ראייה, עיוורון צבעים, חירשות או לקות שמיעה ומוגבלות מוטורית. כשמפתחים אפליקציות תוך התחשבות בנגישות, משפרים את חוויית המשתמש לא רק עבור משתמשים עם מוגבלויות, אלא גם עבור כל שאר המשתמשים.
ב-Android יש כמה תכונות נגישות שמוגדרות כברירת מחדל בתצוגות הרגילות של ממשק המשתמש, כמו TextView ו-Button. עם זאת, כשיוצרים תצוגה בהתאמה אישית, צריך לחשוב איך התצוגה הזו תספק תכונות נגישות כמו תיאורים מדוברים של התוכן שמוצג במסך.
במשימה הזו נסביר על TalkBack, קורא המסך של Android, ונראה איך לשנות את האפליקציה כך שתכלול רמזים ותיאורים שאפשר להקריא עבור DialView התצוגה המותאמת אישית.
שלב 1. הסבר על TalkBack
TalkBack הוא קורא המסך המובנה של Android. כשהתכונה TalkBack מופעלת, המשתמש יכול להשתמש במכשיר Android בלי לראות את המסך, כי Android מתאר בקול את רכיבי המסך. משתמשים עם ליקויי ראייה עשויים להסתמך על TalkBack כדי להשתמש באפליקציה שלכם.
במשימה הזו נסביר איך מפעילים את TalkBack כדי להבין איך קוראי מסך פועלים ואיך מנווטים באפליקציות.
- במכשיר או באמולטור Android, עוברים אל הגדרות > נגישות > TalkBack.
- מקישים על לחצן ההפעלה/הכיבוי כדי להפעיל את TalkBack.
- מקישים על אישור כדי לאשר את ההרשאות.
- אם מתבקשים, מאשרים את הסיסמה של המכשיר. אם זו הפעם הראשונה שאתם מפעילים את TalkBack, ייפתח מדריך. (יכול להיות שההדרכה לא תהיה זמינה במכשירים ישנים).
- מומלץ לנווט במדריך עם עיניים עצומות. כדי לפתוח שוב את המדריך בעתיד, עוברים אל הגדרות > נגישות > TalkBack > הגדרות > הפעלת המדריך של TalkBack.
- מהדרים ומפעילים את אפליקציית
CustomFanController, או פותחים אותה באמצעות הלחצן סקירה כללית או אחרונים במכשיר. כש-TalkBack מופעל, השם של האפליקציה מוכרז, וגם הטקסט של התוויתTextView("Fan Control"). עם זאת, אם מקישים על התצוגה עצמהDialView, לא מושמע מידע על מצב התצוגה (ההגדרה הנוכחית של החוגה) או על הפעולה שתתבצע כשמקישים על התצוגה כדי להפעיל אותה.
שלב 2. הוספת תיאורי תוכן לתוויות של חוגות
תיאורי תוכן מתארים את המשמעות והמטרה של התצוגות באפליקציה. התוויות האלה מאפשרות לקוראי מסך כמו TalkBack ב-Android להסביר את הפונקציה של כל אלמנט בצורה מדויקת. לתצוגות סטטיות כמו ImageView, אפשר להוסיף את תיאור התוכן לתצוגה בקובץ הפריסה באמצעות המאפיין contentDescription. בתצוגות טקסט (TextView ו-EditText) נעשה שימוש אוטומטי בטקסט שבתצוגה כתיאור התוכן.
בתצוגה המותאמת אישית של בקרת המאוורר, צריך לעדכן באופן דינמי את תיאור התוכן בכל פעם שלוחצים על התצוגה, כדי לציין את הגדרת המאוורר הנוכחית.
- בחלק התחתון של המחלקה
DialView, מכריזים על פונקציהupdateContentDescription()ללא ארגומנטים או סוג החזרה.
fun updateContentDescription() {
}- בתוך
updateContentDescription(), משנים את המאפייןcontentDescriptionשל התצוגה המותאמת אישית למשאב המחרוזת שמשויך למהירות המאוורר הנוכחית (off, 1, 2 או 3). אלה אותן התוויות שמוצגות ב-onDraw()כשהחוגה מצוירת על המסך.
fun updateContentDescription() {
contentDescription = resources.getString(fanSpeed.label)
}- גוללים למעלה אל הבלוק
init()ובסוף הבלוק מוסיפים קריאה ל-updateContentDescription(). הפעולה הזו מאתחלת את תיאור התוכן כשהתצוגה מאותחלת.
init {
isClickable = true
// ...
updateContentDescription()
}- מוסיפים עוד קריאה ל-
updateContentDescription()בשיטהperformClick(), ממש לפניinvalidate().
override fun performClick(): Boolean {
if (super.performClick()) return true
fanSpeed = fanSpeed.next()
updateContentDescription()
invalidate()
return true
}- קומפילציה והרצה של האפליקציה, ומוודאים ש-TalkBack מופעל. מקישים כדי לשנות את ההגדרה של תצוגת החוגה, ושימו לב שעכשיו TalkBack מכריז על התווית הנוכחית (off, 1, 2, 3) וגם על הביטוי 'לחיצה כפולה להפעלה'.
שלב 3. הוספת מידע נוסף לפעולת הקליק
אפשר לעצור כאן, והתצוגה תהיה שמישה ב-TalkBack. אבל יהיה מועיל אם התצוגה תציין לא רק שאפשר להפעיל אותה ("הקשה כפולה להפעלה"), אלא גם מה יקרה כשהתצוגה תופעל ("הקשה כפולה לשינוי" או "הקשה כפולה לאיפוס").
כדי לעשות זאת, מוסיפים מידע על הפעולה של התצוגה (במקרה הזה, פעולת קליק או הקשה) לאובייקט מידע של צומת נגישות, באמצעות נציג נגישות. נציג נגישות מאפשר לכם להתאים אישית את התכונות שקשורות לנגישות באפליקציה באמצעות קומפוזיציה (ולא באמצעות ירושה).
כדי לבצע את המשימה הזו, תשתמשו במחלקות הנגישות בספריות Android Jetpack (androidx.*), כדי להבטיח תאימות לאחור.
- ב-
DialView.kt, בבלוקinit, מגדירים נציג נגישות בתצוגה כאובייקטAccessibilityDelegateCompatחדש. מייבאים אתandroidx.core.view.ViewCompatואתandroidx.core.view.AccessibilityDelegateCompatכשמוצגת בקשה לעשות זאת. השיטה הזו מאפשרת את רמת התאימות לאחור הגבוהה ביותר באפליקציה.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
})- באובייקט
AccessibilityDelegateCompat, מחליפים את הפונקציהonInitializeAccessibilityNodeInfo()באובייקטAccessibilityNodeInfoCompatוקוראים לשיטה של המחלקה העליונה. כשמוצגת בקשה, מייבאים אתandroidx.core.view.accessibility.AccessibilityNodeInfoCompat.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
override fun onInitializeAccessibilityNodeInfo(host: View,
info: AccessibilityNodeInfoCompat) {
super.onInitializeAccessibilityNodeInfo(host, info)
}
})לכל תצוגה יש עץ של צמתי נגישות, שיכולים להתאים או לא להתאים לרכיבי הפריסה בפועל של התצוגה. שירותי הנגישות של Android מנווטים בין הצמתים האלה כדי למצוא מידע על התצוגה (למשל, תיאורי תוכן שאפשר להקריא או פעולות אפשריות שאפשר לבצע בתצוגה הזו). כשיוצרים תצוגה בהתאמה אישית, יכול להיות שיהיה צורך גם לבטל את המידע על הצומת כדי לספק מידע בהתאמה אישית לצורך נגישות. במקרה כזה, תבטלו את המידע של הצומת כדי לציין שיש מידע מותאם אישית לגבי הפעולה של התצוגה.
- בתוך
onInitializeAccessibilityNodeInfo(), יוצרים אובייקטAccessibilityNodeInfoCompat.AccessibilityActionCompatחדש ומקצים אותו למשתנהcustomClick. מעבירים לקונסטרוקטור את הקבועAccessibilityNodeInfo.ACTION_CLICKואת מחרוזת ה-placeholder. מייבאים אתAccessibilityNodeInfoכשמתבקשים.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
override fun onInitializeAccessibilityNodeInfo(host: View,
info: AccessibilityNodeInfoCompat) {
super.onInitializeAccessibilityNodeInfo(host, info)
val customClick = AccessibilityNodeInfoCompat.AccessibilityActionCompat(
AccessibilityNodeInfo.ACTION_CLICK,
"placeholder"
)
}
})המחלקות AccessibilityActionCompat מייצגות פעולה בתצוגה למטרות נגישות. פעולה טיפוסית היא קליק או הקשה, כמו במקרה הזה, אבל פעולות אחרות יכולות לכלול קבלת המיקוד או איבוד שלו, פעולה בלוח (גזירה/העתקה/הדבקה) או גלילה בתצוגה. הפעולה ליצירת אובייקט (constructor) של המחלקה הזו דורשת קבוע של פעולה (במקרה הזה, AccessibilityNodeInfo.ACTION_CLICK) ומחרוזת שמשמשת את TalkBack כדי לציין מהי הפעולה.
- מחליפים את המחרוזת
"placeholder"בקריאה ל-context.getString()כדי לאחזר משאב מחרוזת. למשאב הספציפי, בודקים את מהירות המאוורר הנוכחית. אם המהירות הנוכחית היאFanSpeed.HIGH, המחרוזת היא"Reset". אם מהירות המאוורר היא כל ערך אחר, המחרוזת היא"Change."יוצרים את משאבי המחרוזת האלה בשלב מאוחר יותר.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
override fun onInitializeAccessibilityNodeInfo(host: View,
info: AccessibilityNodeInfoCompat) {
super.onInitializeAccessibilityNodeInfo(host, info)
val customClick = AccessibilityNodeInfoCompat.AccessibilityActionCompat(
AccessibilityNodeInfo.ACTION_CLICK,
context.getString(if (fanSpeed != FanSpeed.HIGH) R.string.change else R.string.reset)
)
}
})- אחרי הסוגריים הסוגרים של ההגדרה
customClick, משתמשים בשיטהaddAction()כדי להוסיף את פעולת הנגישות החדשה לאובייקט של פרטי הצומת.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
override fun onInitializeAccessibilityNodeInfo(host: View,
info: AccessibilityNodeInfoCompat) {
super.onInitializeAccessibilityNodeInfo(host, info)
val customClick = AccessibilityNodeInfoCompat.AccessibilityActionCompat(
AccessibilityNodeInfo.ACTION_CLICK,
context.getString(if (fanSpeed != FanSpeed.HIGH)
R.string.change else R.string.reset)
)
info.addAction(customClick)
}
})- ב-
res/values/strings.xml, מוסיפים את משאבי המחרוזות 'שינוי' ו'איפוס'.
<string name="change">Change</string>
<string name="reset">Reset</string>- קומפילציה והרצה של האפליקציה, ומוודאים ש-TalkBack מופעל. שימו לב שעכשיו הביטוי "הקשה כפולה להפעלה" הוא "הקשה כפולה לשינוי" (אם מהירות המאוורר נמוכה או 3) או "הקשה כפולה לאיפוס" (אם מהירות המאוורר כבר גבוהה או 3). הערה: ההנחיה 'הקשה כפולה כדי...' מסופקת על ידי שירות TalkBack עצמו.
מורידים את הקוד של ה-Codelab המוגמר.
$ git clone https://github.com/googlecodelabs/android-kotlin-drawing-custom-views
אפשר גם להוריד את המאגר כקובץ ZIP, לבטל את הדחיסה שלו ולפתוח אותו ב-Android Studio.
- כדי ליצור תצוגה בהתאמה אישית שמקבלת בירושה את המראה וההתנהגות של מחלקת משנה
ViewכמוEditText, מוסיפים מחלקה חדשה שמרחיבה את מחלקת המשנה הזו, ומבצעים שינויים על ידי החלפת חלק מהשיטות של מחלקת המשנה. - כדי ליצור תצוגה בהתאמה אישית בכל גודל וצורה, מוסיפים מחלקה חדשה שמרחיבה את
View. - אפשר לשנות את ההגדרות של שיטות
ViewכמוonDraw()כדי להגדיר את הצורה והמראה הבסיסי של התצוגה. - אפשר להשתמש ב-
invalidate()כדי לאלץ ציור או ציור מחדש של התצוגה. - כדי לשפר את הביצועים, צריך להקצות משתנים ולהקצות ערכים נדרשים לציור ולצביעה לפני שמשתמשים בהם ב-
onDraw(), למשל בהפעלה של משתני חברים. - מחליפים את
performClick()ולא אתOnClickListener() בתצוגה המותאמת אישית כדי לספק את ההתנהגות האינטראקטיבית של התצוגה. ההגדרה הזו מאפשרת לכם או למפתחי Android אחרים שעשויים להשתמש במחלקת התצוגה המותאמת אישית שלכם להשתמש ב-onClickListener()כדי לספק התנהגות נוספת. - מוסיפים את התצוגה המותאמת אישית לקובץ פריסה של XML עם מאפיינים להגדרת המראה שלה, כמו שמוסיפים רכיבי ממשק משתמש אחרים.
- יוצרים את הקובץ
attrs.xmlבתיקייהvaluesכדי להגדיר מאפיינים מותאמים אישית. לאחר מכן תוכלו להשתמש במאפיינים המותאמים אישית לתצוגה המותאמת אישית בקובץ פריסת ה-XML.
קורס ב-Udacity:
מסמכי תיעוד למפתחי Android:
- יצירת תצוגות מותאמות אישית
@JvmOverloads- רכיבים בהתאמה אישית
- איך מערכת Android מציירת תצוגות
onMeasure()onSizeChanged()onDraw()CanvasPaintdrawText()setTypeface()setColor()drawRect()drawOval()drawArc()drawBitmap()setStyle()invalidate()- תצוגה
- אירועי קלט
- Paint
- ספריית ההרחבות של Kotlin android-ktx
withStyledAttributes- מסמכי התיעוד של Android KTX
- הודעה מקורית בבלוג על Android KTX
- שיפור הנגישות של תצוגות בהתאמה אישית
AccessibilityDelegateCompatAccessibilityNodeInfoCompatAccessibilityNodeInfoCompat.AccessibilityActionCompat
סרטי וידאו:
בקטע הזה מפורטות אפשרויות למשימות ביתיות לתלמידים שעובדים על ה-Codelab הזה כחלק מקורס בהנחיית מדריך. המורה צריך:
- אם צריך, מקצים שיעורי בית.
- להסביר לתלמידים איך להגיש מטלות.
- בודקים את שיעורי הבית.
אנשי ההוראה יכולים להשתמש בהצעות האלה כמה שרוצים, ומומלץ להם להקצות כל שיעורי בית אחרים שהם חושבים שמתאימים.
אם אתם עובדים על ה-codelab הזה לבד, אתם יכולים להשתמש במשימות האלה כדי לבדוק את הידע שלכם.
שאלה 1
כדי לחשב את המיקומים, המאפיינים וערכים אחרים כשמקצים גודל לתצוגה המותאמת אישית בפעם הראשונה, איזו שיטה צריך לבטל?
▢ onMeasure()
▢ onSizeChanged()
▢ invalidate()
▢ onDraw()
שאלה 2
כדי לציין שרוצים לצייר מחדש את התצוגה עם onDraw(), איזו שיטה צריך להפעיל משרשור ממשק המשתמש, אחרי שערך מאפיין השתנה?
▢ onMeasure()
▢ onSizeChanged()
▢ invalidate()
▢ getVisibility()
שאלה 3
באיזו שיטה View צריך להשתמש כדי להוסיף אינטראקטיביות לתצוגה המותאמת אישית?
▢ setOnClickListener()
▢ onSizeChanged()
▢ isClickable()
▢ performClick()
קישורים למדריכי Codelab נוספים בקורס הזה זמינים בדף הנחיתה של מדריכי Codelab בנושא Android מתקדם ב-Kotlin.



