סקירה כללית של מודעות השרטוט

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

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

יש שתי דרכים להגדיר וליצור Drawable מלבד השימוש ב-constructor של המחלקות:

  • מתנפחים משאב של תמונה (קובץ מפת סיביות (bitmap)) שנשמר בפרויקט.
  • מתנפח משאב XML שמגדיר את המאפיינים שניתנים להזזה.

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

יצירת פריטים שניתנים להזזה מתמונות של משאבים

אפשר להוסיף גרפיקה לאפליקציה על ידי הפניה לקובץ תמונה מה במשאבי הפרויקט. סוגי הקבצים הנתמכים הם PNG (מועדף), JPG (קביל), ו-GIF (לא מומלץ). סמלי אפליקציות, סמלי לוגו ופריטים גרפיים אחרים, כמו אלה שנעשה בהם שימוש מאוד מתאימים לטכניקה הזו.

כדי להשתמש במשאב של תמונות, צריך להוסיף את הקובץ אל res/drawable/ של הפרויקט. בתוך הפרויקט, תוכלו להפנות אל התמונה מהקוד או מפריסת ה-XML. בכל מקרה, זה נקרא שימוש מזהה משאב, שהוא שם הקובץ ללא הסיומת של סוג הקובץ. עבור לדוגמה, התייחסו אל my_image.png בתור my_image.

הערה: משאבי תמונות שנמצאים ב ניתן לבצע אופטימיזציה אוטומטית של הספרייה res/drawable/ באמצעות דחיסת תמונות ללא אובדן מידע על ידי הכלי aapt במהלך ה-build תהליך האימות. לדוגמה, קובץ PNG בצבע אמיתי שלא דורש יותר מ-256 צבעים ניתן להמיר לקובץ PNG בגודל 8 סיביות עם לוח צבעים. התוצאה היא תמונה באיכות זהה אך דורש פחות זיכרון. כתוצאה מכך, הקובץ הבינארי של התמונה בספרייה הזו יכולים להשתנות בזמן ה-build. אם אתם מתכננים לקרוא התמונה בתור קטע סיביות (bitstream) כדי להמיר אותה למפת סיביות (bitmap), במקום זאת, תיקייה אחת (res/raw/), שבה הכלי aapt לשנות אותם.

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

Kotlin

private lateinit var constraintLayout: ConstraintLayout

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    // Instantiate an ImageView and define its properties
    val i = ImageView(this).apply {
        setImageResource(R.drawable.my_image)
        contentDescription = resources.getString(R.string.my_image_desc)

        // set the ImageView bounds to match the Drawable's dimensions
        adjustViewBounds = true
        layoutParams = ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT)
    }

    // Create a ConstraintLayout in which to add the ImageView
    constraintLayout = ConstraintLayout(this).apply {

        // Add the ImageView to the layout.
        addView(i)
    }

    // Set the layout as the content view.
    setContentView(constraintLayout)
}

Java

ConstraintLayout constraintLayout;

protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);

  // Create a ConstraintLayout in which to add the ImageView
  constraintLayout = new ConstraintLayout(this);

  // Instantiate an ImageView and define its properties
  ImageView i = new ImageView(this);
  i.setImageResource(R.drawable.my_image);
  i.setContentDescription(getResources().getString(R.string.my_image_desc));

  // set the ImageView bounds to match the Drawable's dimensions
  i.setAdjustViewBounds(true);
  i.setLayoutParams(new ViewGroup.LayoutParams(
          ViewGroup.LayoutParams.WRAP_CONTENT,
          ViewGroup.LayoutParams.WRAP_CONTENT));

  // Add the ImageView to the layout and set the layout as the content view.
  constraintLayout.addView(i);
  setContentView(constraintLayout);
}

במקרים אחרים, כדאי לטפל במשאב התמונה כאובייקט Drawable, כפי שמוצג באופן הבא: דוגמה:

Kotlin

val myImage: Drawable = ResourcesCompat.getDrawable(context.resources, R.drawable.my_image, null)

Java

Resources res = context.getResources();
Drawable myImage = ResourcesCompat.getDrawable(res, R.drawable.my_image, null);

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

קטע קוד ה-XML הבא מראה איך להוסיף משאב שניתן להזזה ל-ImageView בפריסת XML:

<ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/my_image"
        android:contentDescription="@string/my_image_desc" />

מידע נוסף על השימוש במשאבי פרויקט זמין במאמר משאבים ונכסים.

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

יצירת פריטים שניתנים להזזה ממשאבי XML

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

אחרי שמגדירים את Drawable ב-XML, שומרים את הקובץ ב ספריית res/drawable/ של הפרויקט. הדוגמה הבאה מציגה את קוד ה-XML מגדיר TransitionDrawable במשאב שיורש מ-Drawable:

<!-- res/drawable/expand_collapse.xml -->
<transition xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/image_expand"/>
    <item android:drawable="@drawable/image_collapse"/>
</transition>

לאחר מכן, מאחזרים את האובייקט ויוצרים אותו באמצעות קריאה Resources#getDrawable() ולהעביר את מזהה המשאב של קובץ ה-XML. כלשהו תת-מחלקה אחת (Drawable) שתומך ב-method inflate(), ניתן להגדיר ב-XML וליצור על ידי האפליקציה.

כל מחלקה ניתנת להזזה שתומכת באינפלציה של XML משתמשת במאפייני XML ספציפיים שעוזרים להגדיר את מאפייני האובייקט. הקוד הבא מייצר את TransitionDrawable ומגדיר אותו כתוכן של אובייקט ImageView:

Kotlin

val transition= ResourcesCompat.getDrawable(
        context.resources,
        R.drawable.expand_collapse,
        null
) as TransitionDrawable

val image: ImageView = findViewById(R.id.toggle_image)
image.setImageDrawable(transition)

// Description of the initial state that the drawable represents.
image.contentDescription = resources.getString(R.string.collapsed)

// Then you can call the TransitionDrawable object's methods.
transition.startTransition(1000)

// After the transition is complete, change the image's content description
// to reflect the new state.

Java

Resources res = context.getResources();
TransitionDrawable transition =
    (TransitionDrawable) ResourcesCompat.getDrawable(res, R.drawable.expand_collapse, null);

ImageView image = (ImageView) findViewById(R.id.toggle_image);
image.setImageDrawable(transition);

// Description of the initial state that the drawable represents.
image.setContentDescription(getResources().getString(R.string.collapsed));

// Then you can call the TransitionDrawable object's methods.
transition.startTransition(1000);

// After the transition is complete, change the image's content description
// to reflect the new state.

למידע נוסף על מאפייני ה-XML הנתמכים, ראו את המחלקות (מחלקות) שצוינו למעלה.

צורות ניתנות להזזה

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

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

ל-ShapeDrawable יש שיטת draw() משלה, לכן אפשר ליצור המחלקה המשנית של View שמושכת את ShapeDrawable במהלך האירוע onDraw(), כפי שמוצג דוגמת הקוד הבאה:

Kotlin

class CustomDrawableView(context: Context) : View(context) {
    private val drawable: ShapeDrawable = run {
        val x = 10
        val y = 10
        val width = 300
        val height = 50
        contentDescription = context.resources.getString(R.string.my_view_desc)

        ShapeDrawable(OvalShape()).apply {
            // If the color isn't set, the shape uses black as the default.
            paint.color = 0xff74AC23.toInt()
            // If the bounds aren't set, the shape can't be drawn.
            setBounds(x, y, x + width, y + height)
        }
    }

    override fun onDraw(canvas: Canvas) {
        drawable.draw(canvas)
    }
}

Java

public class CustomDrawableView extends View {
  private ShapeDrawable drawable;

  public CustomDrawableView(Context context) {
    super(context);

    int x = 10;
    int y = 10;
    int width = 300;
    int height = 50;
    setContentDescription(context.getResources().getString(
            R.string.my_view_desc));

    drawable = new ShapeDrawable(new OvalShape());
    // If the color isn't set, the shape uses black as the default.
    drawable.getPaint().setColor(0xff74AC23);
    // If the bounds aren't set, the shape can't be drawn.
    drawable.setBounds(x, y, x + width, y + height);
  }

  protected void onDraw(Canvas canvas) {
    drawable.draw(canvas);
  }
}

אפשר להשתמש במחלקה CustomDrawableView בדוגמת הקוד למעלה כמו בכל תצוגה מותאמת אישית אחרת. לדוגמה, אפשר: להוסיף אותו באופן פרוגרמטי לפעילות באפליקציה, כמו בדוגמה הבאה דוגמה:

Kotlin

private lateinit var customDrawableView: CustomDrawableView

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    customDrawableView = CustomDrawableView(this)

    setContentView(customDrawableView)
}

Java

CustomDrawableView customDrawableView;

protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  customDrawableView = new CustomDrawableView(this);

  setContentView(customDrawableView);
}

אם רוצים להשתמש במקום זאת בתצוגה מותאמת אישית בפריסת XML, צריך להשתמש המחלקה CustomDrawableView חייבת לשנות את ה-constructor של View(Context, AttributeSet), שמופעל כשהמחלקה היא התנפחות מ-XML. הדוגמה הבאה מראה איך להצהיר (declare) על CustomDrawableView בפריסת ה-XML:

<com.example.shapedrawable.CustomDrawableView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        />

הכיתה ShapeDrawable, כמו הרבה אחרים בחבילה android.graphics.drawable, מאפשרים לכם מגדירים מאפיינים שונים של האובייקט באמצעות שיטות ציבוריות. דוגמה מאפיינים שאולי תרצה לשנות כוללים שקיפות אלפא, פילטר צבע, מיזוג, שקיפות וצבע.

אפשר גם להגדיר צורות פרימיטיביות שניתנות להזזה באמצעות משאבי XML. לקבלת מידע נוסף מידע נוסף, ראה צורה ניתנת להזזה ב סוגי משאבים שניתנים לשרטוט.

פריטים שנמשכים על ידי NinePatch

גרפיקה של NinePatchDrawable היא תמונה של מפת סיביות ניתנת למתיחה שניתן להשתמש בה כרקע של תצוגה. במכשירי Android משנה אוטומטית את גודל הגרפיקה כדי להתאים לתוכן של התצוגה. שימוש לדוגמה בתמונת NinePatch הוא הרקע שמשמש את מערכת Android הרגילה לחצנים — הלחצנים חייבים להימתח כדי להתאים למחרוזות באורכים שונים. א' גרפיקה של NinePatch היא תמונה סטנדרטית בפורמט PNG עם גבול נוסף של פיקסל אחד. צריך לשמור אותה עם התוסף 9.png ב- ספריית res/drawable/ של הפרויקט.

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

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

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

איור 1 מציג דוגמה לגרפיקה של NinePatch שמשמשת להגדרת לחצן:

תמונה של אזור למתיחה
ותיבה למילוי משבצת

איור 1: דוגמה לגרפיקה של NinePatch שמגדירה לחצן

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

הכלי Draw 9-patch מאפשר דרך שימושית במיוחד ליצירת תמונות NinePatch באמצעות גרפיקה של WYSIWYG עם הרשאת עריכה. היא אפילו מציגה אזהרות אם האזור שהגדרתם למתיחה ייתכן שהאזור עלול להיווצר כתוצאה מפעולת פיקסל רפליקציה.

קטע ה-XML לדוגמה של הפריסה הבא מדגים איך להוסיף גרפיקה של NinePatch לכמה לחצנים. תמונת NinePatch נשמרה ב res/drawable/my_button_background.9.png

<Button android:id="@+id/tiny"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerInParent="true"
        android:text="Tiny"
        android:textSize="8sp"
        android:background="@drawable/my_button_background"/>

<Button android:id="@+id/big"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerInParent="true"
        android:text="Biiiiiiig text!"
        android:textSize="30sp"
        android:background="@drawable/my_button_background"/>

חשוב לשים לב שהערכים layout_width וlayout_height מוגדרים ל-wrap_content כדי שהלחצן יתאים בדיוק סביב הטקסט.

איור 2 מציג את שני הלחצנים שעברו רינדור מהתמונה XML ו-NinePatch שמוצג למעלה. שימו לב שהרוחב והגובה של הלחצן משתנים בהתאם לטקסט. ותמונת הרקע נמתחת כדי להתאים לה.

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

איור 2: לחצנים שעברו רינדור באמצעות XML משאב וגרפיקה של NinePatch

פריטים שניתנים להזזה בהתאמה אישית

כדי ליצור שרטוטים בהתאמה אישית, צריך להרחיב את המחלקה Drawable (או כל אחת ממחלקות המשנה שלה).

השיטה החשובה ביותר להטמעה היא draw(Canvas) כי זה מספק את האובייקט Canvas שצריך להשתמש בו כדי לספק את הוראות השרטוט.

הקוד הבא מציג מחלקה פשוטה של Drawable שמשרטטת מעגל:

Kotlin

class MyDrawable : Drawable() {
    private val redPaint: Paint = Paint().apply { setARGB(255, 255, 0, 0) }

    override fun draw(canvas: Canvas) {
        // Get the drawable's bounds
        val width: Int = bounds.width()
        val height: Int = bounds.height()
        val radius: Float = Math.min(width, height).toFloat() / 2f

        // Draw a red circle in the center
        canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), radius, redPaint)
    }

    override fun setAlpha(alpha: Int) {
        // This method is required
    }

    override fun setColorFilter(colorFilter: ColorFilter?) {
        // This method is required
    }

    override fun getOpacity(): Int =
        // Must be PixelFormat.UNKNOWN, TRANSLUCENT, TRANSPARENT, or OPAQUE
        PixelFormat.OPAQUE
}

Java

public class MyDrawable extends Drawable {
    private final Paint redPaint;

    public MyDrawable() {
        // Set up color and text size
        redPaint = new Paint();
        redPaint.setARGB(255, 255, 0, 0);
    }

    @Override
    public void draw(Canvas canvas) {
        // Get the drawable's bounds
        int width = getBounds().width();
        int height = getBounds().height();
        float radius = Math.min(width, height) / 2;

        // Draw a red circle in the center
        canvas.drawCircle(width/2, height/2, radius, redPaint);
    }

    @Override
    public void setAlpha(int alpha) {
        // This method is required
    }

    @Override
    public void setColorFilter(ColorFilter colorFilter) {
        // This method is required
    }

    @Override
    public int getOpacity() {
        // Must be PixelFormat.UNKNOWN, TRANSLUCENT, TRANSPARENT, or OPAQUE
        return PixelFormat.OPAQUE;
    }
}

לאחר מכן תוכלו להוסיף פריט גרפי שנוח לכם בכל מקום, למשל ImageView כפי שמוצג כאן:

Kotlin

val myDrawing = MyDrawable()
val image: ImageView = findViewById(R.id.imageView)
image.setImageDrawable(myDrawing)
image.contentDescription = resources.getString(R.string.my_image_desc)

Java

MyDrawable mydrawing = new MyDrawable();
ImageView image = findViewById(R.id.imageView);
image.setImageDrawable(mydrawing);
image.setContentDescription(getResources().getString(R.string.my_image_desc));

ב-Android 7.0 (רמת API 24) ואילך אפשר גם להגדיר מופעים של פריט גרפי בהתאמה אישית עם XML בדרכים הבאות:

  • שימוש בשם המחלקה המוגדר במלואו כשם רכיב ה-XML. בגישה הזאת, מחלקה ניתנת להזזה חייבת להיות מחלקה ציבורית ברמה עליונה:
    <com.myapp.MyDrawable xmlns:android="http://schemas.android.com/apk/res/android"
        android:color="#ffff0000" />
    
  • שימוש ב-drawable כשם תג ה-XML וציון המחלקה המלאה מהמאפיין class. ניתן להשתמש בגישה הזו גם לכיתות ציבוריות ברמה עליונה וגם מחלקות פנימיות סטטיות ציבוריות:
    <drawable xmlns:android="http://schemas.android.com/apk/res/android"
        class="com.myapp.MyTopLevelClass$MyDrawable"
        android:color="#ffff0000" />
    

הוספת גוון לפריטי גרפיקה

ב-Android 5.0 (רמת API 21) ואילך, אפשר לבצע גוון של מפות סיביות ותשעה תיקונים שמוגדרים כך מסכות אלפא. אפשר לשנות את הגוון שלהם באמצעות משאבי צבע או מאפייני עיצוב שמתאימים לצבע משאבים (לדוגמה, ?android:attr/colorPrimary). בדרך כלל יוצרים את הנכסים האלה רק פעם אחת ולצבע אותן באופן אוטומטי כך שיתאימו לעיצוב שלך.

אפשר להחיל גוון על BitmapDrawable, NinePatchDrawable או VectorDrawable אובייקטים עם השיטה setTint(). אפשר להגדיר גם את גוון הצבע והמצב של הפריסות באמצעות android:tint וגם android:tintMode.

חילוץ צבעים בולטים מהתמונה

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