Tổng quan về tài nguyên có thể kéo

Thử cách dùng Compose
Jetpack Compose là bộ công cụ giao diện người dùng được đề xuất cho Android. Tìm hiểu cách hiển thị đồ hoạ trong Compose.

Khi cần hiển thị hình ảnh tĩnh trong ứng dụng, bạn có thể sử dụng lớp Drawable và các lớp con của lớp này để vẽ hình dạng và hình ảnh. Drawable là một khái niệm trừu tượng chung cho thứ gì đó có thể vẽ được. Các lớp con khác nhau giúp xử lý các trường hợp hình ảnh cụ thể và bạn có thể mở rộng các lớp con này để xác định các đối tượng có thể vẽ của riêng mình hoạt động theo những cách riêng biệt.

Có 2 cách để xác định và khởi tạo Drawable ngoài việc sử dụng hàm khởi tạo lớp:

  • Tăng cường tài nguyên hình ảnh (tệp bitmap) được lưu trong dự án của bạn.
  • Tăng cường tài nguyên XML xác định các thuộc tính có thể vẽ.

Lưu ý: Bạn có thể thích sử dụng một vectơ vẽ được, xác định hình ảnh bằng một nhóm điểm, đường thẳng và đường cong, cùng với thông tin màu sắc liên quan. Điều này cho phép các vectơ vẽ được điều chỉnh tỷ lệ cho nhiều kích thước mà không làm giảm chất lượng. Để biết thêm thông tin, hãy xem Tổng quan về vectơ vẽ được.

Tạo các đối tượng có thể vẽ từ hình ảnh tài nguyên

Bạn có thể thêm đồ hoạ vào ứng dụng bằng cách tham chiếu đến tệp hình ảnh từ tài nguyên dự án. Các loại tệp được hỗ trợ là PNG (ưu tiên), JPG (có thể chấp nhận) và GIF (không nên chọn). Biểu tượng ứng dụng, biểu trưng và các đồ hoạ khác, chẳng hạn như đồ hoạ dùng trong trò chơi, rất phù hợp với kỹ thuật này.

Để sử dụng tài nguyên hình ảnh, hãy thêm tệp vào thư mục res/drawable/ của dự án. Sau khi có trong dự án, bạn có thể tham chiếu đến tài nguyên hình ảnh từ mã hoặc bố cục XML. Dù bằng cách nào, tài nguyên hình ảnh cũng được tham chiếu bằng mã tài nguyên, là tên tệp không có phần mở rộng loại tệp. Ví dụ: hãy tham chiếu đến my_image.png dưới dạng my_image.

Lưu ý: Các tài nguyên hình ảnh được đặt trong thư mục res/drawable/ có thể được tự động tối ưu hoá bằng tính năng nén hình ảnh không mất dữ liệu bằng công cụ aapt trong quá trình tạo. Ví dụ: PNG màu thực không yêu cầu quá 256 màu có thể được chuyển đổi thành PNG 8 bit thông qua bảng màu. Việc này sẽ dẫn đến hình ảnh có chất lượng như nhau nhưng yêu cầu ít bộ nhớ hơn. Do đó, các tệp nhị phân hình ảnh được đặt trong thư mục này có thể thay đổi vào thời gian xây dựng. Nếu bạn định đọc hình ảnh ở dạng luồng bit để chuyển đổi hình ảnh đó thành một bitmap, hãy đặt hình ảnh vào thư mục res/raw/ để công cụ aapt không sửa đổi hình ảnh.

Đoạn mã sau đây minh hoạ cách tạo ImageView sử dụng hình ảnh được tạo từ tài nguyên có thể vẽ và thêm hình ảnh đó vào bố cục:

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);
}

Trong các trường hợp khác, bạn có thể muốn xử lý tài nguyên hình ảnh dưới dạng đối tượng Drawable, như trong ví dụ sau:

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);

Cảnh báo: Mỗi tài nguyên riêng biệt trong dự án của bạn chỉ có thể duy trì một trạng thái, bất kể bạn khởi tạo bao nhiêu đối tượng khác nhau cho tài nguyên đó. Ví dụ: nếu bạn khởi tạo 2 đối tượng Drawable từ cùng một tài nguyên hình ảnh và thay đổi một thuộc tính (chẳng hạn như alpha) cho một đối tượng, thì thuộc tính đó cũng ảnh hưởng đến đối tượng còn lại. Khi xử lý nhiều thực thể của tài nguyên hình ảnh, thay vì trực tiếp chuyển đổi đối tượng Drawable, bạn nên thực hiện ảnh động tween animation.

Đoạn mã XML bên dưới cho biết cách thêm tài nguyên drawable vào ImageView trong bố cục XML:

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

Để biết thêm thông tin về cách sử dụng tài nguyên dự án, hãy xem bài viết Tài nguyên và thành phần.

Lưu ý: Khi sử dụng tài nguyên hình ảnh làm nguồn của các đối tượng có thể vẽ, hãy đảm bảo rằng hình ảnh có kích thước phù hợp với nhiều mật độ pixel. Nếu hình ảnh không chính xác, hình ảnh sẽ được điều chỉnh tỷ lệ để phù hợp, điều này có thể gây ra hiện tượng tạo tác trong các đối tượng có thể vẽ. Để biết thêm thông tin, hãy đọc bài viết Hỗ trợ nhiều mật độ pixel.

Tạo các đối tượng có thể vẽ từ tài nguyên XML

Nếu có đối tượng Drawable mà bạn muốn tạo, ban đầu không phụ thuộc vào các biến do mã hoặc lượt tương tác của người dùng xác định, thì việc xác định Drawable trong XML là một lựa chọn tốt. Ngay cả khi bạn dự kiến Drawable sẽ thay đổi các thuộc tính trong quá trình người dùng tương tác với ứng dụng, bạn nên cân nhắc việc xác định đối tượng trong XML, vì bạn có thể sửa đổi các thuộc tính sau khi đối tượng được khởi tạo.

Sau khi xác định Drawable trong XML, hãy lưu tệp vào thư mục res/drawable/ của dự án. Ví dụ sau đây cho thấy XML xác định tài nguyên TransitionDrawable, kế thừa từ 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>

Sau đó, truy xuất và khởi tạo đối tượng bằng cách gọi Resources#getDrawable() và truyền mã tài nguyên của tệp XML. Mọi Drawable lớp con hỗ trợ phương thức inflate() đều có thể được xác định trong XML và được ứng dụng của bạn khởi tạo.

Mỗi lớp có thể vẽ hỗ trợ việc tăng cường XML đều sử dụng các thuộc tính XML cụ thể giúp xác định các thuộc tính đối tượng. Mã sau đây khởi tạo TransitionDrawable và đặt mã này làm nội dung của đối tượng 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.

Để biết thêm thông tin về các thuộc tính XML được hỗ trợ, hãy tham khảo các lớp được liệt kê ở trên.

Hình dạng có thể vẽ

Đối tượng ShapeDrawable có thể là một lựa chọn tốt khi bạn muốn vẽ đồ hoạ hai chiều một cách linh hoạt. Bạn có thể vẽ các hình dạng nguyên thuỷ theo phương thức lập trình trên đối tượng ShapeDrawable và áp dụng các kiểu mà ứng dụng của bạn cần.

ShapeDrawable là một lớp con của Drawable. Vì lý do này, bạn có thể sử dụng a ShapeDrawable ở bất cứ nơi nào dự kiến có Drawable. Ví dụ: bạn có thể sử dụng đối tượng ShapeDrawable để đặt nền của khung hiển thị bằng cách truyền đối tượng đó vào phương thức setBackgroundDrawable() của khung hiển thị. Bạn cũng có thể vẽ hình dạng của mình dưới dạng khung hiển thị tuỳ chỉnh riêng và thêm hình dạng đó vào bố cục trong ứng dụng.

ShapeDrawable có phương thức draw() riêng, nên bạn có thể tạo một lớp con của View vẽ đối tượng ShapeDrawable trong sự kiện onDraw(), như trong ví dụ về mã sau:

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);
  }
}

Bạn có thể sử dụng lớp CustomDrawableView trong mẫu mã ở trên như cách bạn sử dụng mọi khung hiển thị tuỳ chỉnh khác. Ví dụ: bạn có thể thêm lớp này theo phương thức lập trình vào một hoạt động trong ứng dụng, như trong ví dụ sau:

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);
}

Nếu bạn muốn sử dụng khung hiển thị tuỳ chỉnh trong bố cục XML, thì lớp CustomDrawableView phải ghi đè hàm khởi tạo View(Context, AttributeSet), được gọi khi lớp được tăng cường từ XML. Ví dụ sau đây cho biết cách khai báo CustomDrawableView trong bố cục XML:

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

Lớp ShapeDrawable, giống như nhiều loại có thể vẽ khác trong gói android.graphics.drawable, cho phép bạn xác định nhiều thuộc tính của đối tượng bằng cách sử dụng các phương thức công khai. Một số thuộc tính mẫu mà bạn có thể muốn điều chỉnh bao gồm độ trong suốt alpha, bộ lọc màu, dither, độ mờ và màu.

Bạn cũng có thể xác định các hình dạng có thể vẽ nguyên thuỷ bằng tài nguyên XML. Để biết thêm thông tin, hãy xem phần Drawable hình dạng trong phần Các loại tài nguyên drawable.

Đối tượng có thể vẽ NinePatch

Đồ hoạ NinePatchDrawable là hình ảnh bitmap có thể co giãn mà bạn có thể dùng làm nền của khung hiển thị. Android tự động thay đổi kích thước đồ hoạ để phù hợp với nội dung của khung hiển thị. Một ví dụ về cách sử dụng hình ảnh NinePatch là nền mà các nút Android tiêu chuẩn sử dụng. Các nút phải kéo dài để phù hợp với các chuỗi có độ dài khác nhau. Đồ hoạ NinePatch là hình ảnh PNG tiêu chuẩn bao gồm đường viền 1 pixel bổ sung. Bạn phải lưu đồ hoạ này bằng phần mở rộng 9.png trong thư mục res/drawable/ của dự án.

Sử dụng đường viền để xác định các vùng có thể co giãn và tĩnh của hình ảnh. Bạn cho biết một phần có thể co giãn bằng cách vẽ một (hoặc nhiều) đường màu đen có chiều rộng 1 pixel trong phần bên trái và trên cùng của đường viền (các pixel đường viền khác phải hoàn toàn trong suốt hoặc màu trắng). Bạn có thể có bao nhiêu phần có thể co giãn tuỳ thích. Kích thước tương đối của các phần có thể co giãn vẫn giữ nguyên, vì vậy, phần lớn nhất luôn là phần lớn nhất.

Bạn cũng có thể xác định một phần có thể vẽ không bắt buộc của hình ảnh (thực tế là các đường đệm) bằng cách vẽ một đường ở bên phải và một đường ở dưới cùng. Nếu đối tượng View đặt đồ hoạ NinePatch làm nền rồi chỉ định văn bản của khung hiển thị, thì đối tượng đó sẽ tự kéo dài để tất cả văn bản chỉ chiếm vùng được chỉ định bởi các đường bên phải và dưới cùng (nếu có). Nếu không có các đường đệm, Android sẽ sử dụng các đường bên trái và trên cùng để xác định vùng có thể vẽ này.

Để làm rõ sự khác biệt giữa các đường, các đường bên trái và trên cùng xác định những pixel nào của hình ảnh được phép sao chép để kéo dài hình ảnh. Các đường dưới cùng và bên phải xác định vùng tương đối trong hình ảnh mà nội dung của khung hiển thị được phép chiếm.

Hình 1 cho thấy ví dụ về đồ hoạ NinePatch dùng để xác định nút:

Hình ảnh về vùng có thể kéo giãn và hộp đệm

Hình 1: Ví dụ về đồ hoạ NinePatch xác định nút

Đồ hoạ NinePatch này xác định một vùng có thể co giãn bằng các đường bên trái và trên cùng, cũng như vùng có thể vẽ bằng các đường dưới cùng và bên phải. Trong hình ảnh trên cùng, các đường màu xám chấm chấm xác định các vùng của hình ảnh được sao chép để kéo dài hình ảnh. Hình chữ nhật màu hồng trong hình ảnh dưới cùng xác định vùng mà nội dung của khung hiển thị được phép nằm trong đó. Nếu nội dung không vừa với vùng này, thì hình ảnh sẽ được kéo dài để phù hợp.

Công cụ Draw 9-patch cung cấp một cách cực kỳ tiện lợi để tạo hình ảnh NinePatch bằng trình chỉnh sửa đồ hoạ WYSIWYG. Công cụ này thậm chí còn đưa ra cảnh báo nếu vùng bạn đã xác định cho vùng có thể co giãn có nguy cơ tạo ra các tạo tác vẽ do việc sao chép pixel.

XML bố cục mẫu sau đây minh hoạ cách thêm đồ hoạ NinePatch vào một vài nút. Hình ảnh NinePatch được lưu vào 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"/>

Xin lưu ý rằng các thuộc tính layout_widthlayout_height được đặt thành wrap_content để nút vừa khít với văn bản.

Hình 2 cho thấy 2 nút được kết xuất từ hình ảnh XML và NinePatch được minh hoạ ở trên. Hãy lưu ý cách chiều rộng và chiều cao của nút thay đổi theo văn bản, cũng như hình ảnh nền kéo dài để phù hợp với văn bản.

Hình ảnh về các nút có kích thước nhỏ và kích thước bình thường

Hình 2: Các nút được kết xuất bằng tài nguyên XML và đồ hoạ NinePatch

Đối tượng có thể vẽ tuỳ chỉnh

Khi muốn tạo một số bản vẽ tuỳ chỉnh, bạn có thể thực hiện việc này bằng cách mở rộng lớp Drawable (hoặc bất kỳ lớp con nào của lớp này).

Phương thức quan trọng nhất cần triển khai là draw(Canvas) vì phương thức này cung cấp đối tượng Canvas mà bạn phải sử dụng để cung cấp hướng dẫn vẽ.

Mã sau đây cho thấy một lớp con đơn giản của Drawable vẽ một vòng tròn:

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;
    }
}

Sau đó, bạn có thể thêm đối tượng có thể vẽ ở bất cứ nơi nào bạn muốn, chẳng hạn như vào ImageView như minh hoạ ở đây:

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));

Trên Android 7.0 (cấp độ API 24) trở lên, bạn cũng có thể xác định các thực thể của đối tượng drawable tuỳ chỉnh bằng XML theo những cách sau:

  • Sử dụng tên lớp đủ điều kiện làm tên phần tử XML. Đối với phương pháp này, lớp có thể vẽ tuỳ chỉnh phải là lớp công khai cấp cao nhất:
    <com.myapp.MyDrawable xmlns:android="http://schemas.android.com/apk/res/android"
        android:color="#ffff0000" />
  • Sử dụng drawable làm tên thẻ XML và chỉ định tên lớp đủ điều kiện từ thuộc tính lớp. Phương pháp này có thể được dùng cho cả lớp công khai cấp cao nhất và lớp tĩnh công khai bên trong:
    <drawable xmlns:android="http://schemas.android.com/apk/res/android"
        class="com.myapp.MyTopLevelClass$MyDrawable"
        android:color="#ffff0000" />

Thêm màu sắc vào các đối tượng có thể vẽ

Với Android 5.0 (cấp độ API 21) trở lên, bạn có thể phủ màu bitmap và 9-patch được xác định là mặt nạ alpha. Bạn có thể tô màu các đối tượng này bằng tài nguyên màu hoặc thuộc tính giao diện giải quyết thành tài nguyên màu (ví dụ: ?android:attr/colorPrimary). Thông thường, bạn chỉ tạo các thành phần này một lần và tự động tô màu để phù hợp với giao diện của bạn.

Bạn có thể áp dụng màu sắc cho các đối tượng BitmapDrawable, NinePatchDrawable hoặc VectorDrawable bằng phương thức setTint(). Bạn cũng có thể đặt màu sắc và chế độ màu sắc trong bố cục bằng các thuộc tính android:tintandroid:tintMode.

Trích xuất các màu nổi bật từ hình ảnh

Thư viện hỗ trợ Android bao gồm lớp Palette, cho phép bạn trích xuất các màu nổi bật từ hình ảnh. Bạn có thể tải các đối tượng có thể vẽ dưới dạng Bitmap và truyền đối tượng đó đến Palette để truy cập vào các màu của đối tượng đó. Để biết thêm thông tin, hãy đọc bài viết Chọn màu bằng API Bảng thành phần hiển thị.