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

Thử cách 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à khái niệm trừu tượng chung cho nội dung nào đó có thể vẽ được. Các lớp con khác nhau giúp hỗ trợ hình ảnh cụ thể tình huống và bạn có thể mở rộng chúng để 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 độc đáo.

Ngoài việc sử dụng hàm khởi tạo lớp, còn có hai cách để xác định và tạo thực thể cho Drawable:

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

Lưu ý: Thay vào đó, bạn có thể thích sử dụng vectơ vẽ được. Vectơ này xác định hình ảnh bằng một tập hợp các điểm, đường kẻ và đường cong, cùng với thông tin màu sắc có liên quan. Thao tác này cho phép các vectơ vẽ được có thể điều chỉnh cho phù hợp với 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 Vector tổng quan về đối tượng có thể vẽ.

Tạo đố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 một tệp hình ảnh trên nguồ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ạ được sử 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 của bạn vào res/drawable/ của dự án. Sau khi ở trong dự án, bạn có thể tham chiếu hình ảnh từ mã hoặc bố cục XML của bạn. Dù bằng cách nào, mã này đều được tham chiếu đến việc sử dụng mã nhận dạng tài nguyên, tức là tên tệp không có đuôi tệp. Cho ví dụ: tham chiếu my_image.pngmy_image.

Lưu ý: 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 nén hình ảnh không tổn hao bằng công cụ aapt trong quá trình tạo bản dựng của chúng tôi. 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 tệp PNG 8 bit với bảng màu. Kết quả là một hình ảnh có chất lượng tương đương nhưng cần ít bộ nhớ hơn. Do đó, tệp nhị phân hình ảnh được đặt trong thư mục này có thể thay đổi tại thời điểm xây dựng. Nếu bạn định đọc một hình ảnh dưới dạng luồng bit để chuyển đổi hình ảnh đó thành bitmap, hãy đặt hình ảnh của bạn vào Thay vào đó, thư mục res/raw/, trong đó công cụ aapt không có sửa đổi chúng.

Đoạn mã sau đây minh hoạ cách tạo ImageView sử dụng hình ảnh được tạo từ một tài nguyên có thể vẽ rồi thêm 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ể cần xử lý tài nguyên hình ảnh dưới dạng đối tượng Drawable, như minh hoạ sau đây ví dụ:

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 duy nhất trong dự án của bạn chỉ có thể duy trì một trạng thái, bất kể bạn có bao nhiêu đối tượng khác nhau tạo thực thể cho nó. Ví dụ: nếu bạn tạo thực thể cho 2 đối tượng Drawable từ cùng một tài nguyên hình ảnh và thay đổi thuộc tính (chẳng hạn như alpha) cho một đối tượng, thì thao tác này cũng ảnh hưởng đến ứng dụng khác. Khi xử lý nhiều thực thể của một tài nguyên hình ảnh, Trong quá trình chuyển đổi trực tiếp đối tượng Drawable, bạn nên thực hiện tween ảnh động.

Đoạn mã XML dưới đây cho biết cách thêm một tài nguyên có thể vẽ 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 của 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 cho đối tượng có thể vẽ, hãy đảm bảo hình ảnh có kích thước thích hợp cho nhiều mật độ pixel khác nhau. Nếu hình ảnh không chính xác thì chúng sẽ được điều chỉnh theo tỷ lệ cho phù hợp, điều này có thể gây ra cấu phần phần mềm trong đối tượng có thể vẽ của bạn. Để biết thêm thông tin, hãy đọc bài viết Hỗ trợ các mật độ pixel.

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

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

Sau khi bạn 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 trình bày tệp XML xác định một TransitionDrawable tài nguyên 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à tạo thực thể cho đối tượng bằng cách gọi Resources#getDrawable() và chuyển mã nhận dạng tài nguyên của tệp XML. Bất kỳ hạng nào Lớp con Drawable có hỗ trợ phương thức inflate() có thể được xác định trong XML và tạo thực thể theo ứng dụng của bạn.

Mỗi lớp có thể vẽ hỗ trợ tăng cường XML sử dụng các thuộc tính XML cụ thể giúp xác định các thuộc tính của đối tượng. Đoạn mã sau đây sẽ tạo thực thể cho TransitionDrawable và đặt tệp đó 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 phù hợp khi bạn muốn vẽ đồ hoạ hai chiều một cách linh động. Bạn có thể vẽ các hình dạng gốc theo phương thức lập trình trên đối tượng ShapeDrawable và áp dụng kiểu mà ứng dụng của bạn cần.

ShapeDrawable là lớp con của Drawable. Vì lý do này, bạn có thể sử dụng ShapeDrawable ở bất cứ nơi nào dự kiến có Drawable. Cho ví dụ: bạn có thể dùng đối tượng ShapeDrawable để đặt nền của một khung hiển thị bằng cách truyền khung hiển thị đó 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 riêng bạn và thêm khung hiển thị đó 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 phương thức lớp con của View vẽ ShapeDrawable trong quá trình diễn ra sự kiện onDraw(), như minh hoạ trong ví dụ về mã sau đây:

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ể dùng lớp CustomDrawableView trong mã mẫu ở trên giống như cách bạn sử dụng bất kỳ khung hiển thị tuỳ chỉnh nào khác. Ví dụ: bạn có thể thêm phương thức lập trình vào một hoạt động trong ứng dụng, như minh hoạ sau ví dụ:

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 thành phần 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). Hàm này sẽ được gọi khi lớp này được được tăng cường từ XML. Ví dụ sau đây trình bày 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 lớp khác các loại đối tượng có thể vẽ trong gói android.graphics.drawable, cho phép bạn xác định các thuộc tính khác nhau của đối tượng bằng cách sử dụng các phương thức công khai. Một số ví dụ các thuộc tính bạn có thể muốn điều chỉnh bao gồm độ trong suốt alpha, bộ lọc màu, chuyển màu, độ mờ và màu sắc.

Bạn cũng có thể xác định các hình dạng gốc có thể vẽ bằng tài nguyên XML. Để biết thêm thông tin, hãy xem Chọn hình dạng cho đối tượng có thể vẽ trong Các loại tài nguyên có thể vẽ.

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

Hình ảnh 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ị. của Android tự động đổi kích thước đồ hoạ cho phù hợp với nội dung trong chế độ xem. Một ví dụ về cách sử dụng hình ảnh NinePatch làm nền mà Android tiêu chuẩn sử dụng nút—các nút phải co giãn để vừa với các chuỗi có độ dài khác nhau. Đáp Hình ảnh NinePatch là hình ảnh PNG chuẩn có đường viền phụ 1 pixel. Bạn phải lưu hình ảnh với đuôi 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 tĩnh và có thể co giãn của hình ảnh. Bạn chỉ định phần có thể co giãn bằng cách vẽ một (hoặc nhiều) chiều rộng 1 pixel (các) đường màu đen ở phần bên trái và phần trên của đường viền (các pixel đường viền khác phải hoàn toàn trong suốt hoặc có màu trắng). Bạn có thể tạo nhiều phần co giãn như bạn muốn. Kích thước tương đối của các phần co giãn vẫn giữ nguyên, do đó 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ẽ tuỳ chọn của hình ảnh (một cách hiệu quả, 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 một Đối tượng View đặt đồ hoạ NinePatch làm nền sau đó chỉ định văn bản của thành phần hiển thị, nó sẽ tự kéo giãn để tất cả văn bản chỉ chiếm vùng được chỉ định bởi các dòng bên phải và dưới cùng (nếu có). Nếu không bao gồm các dòng khoảng đệm, Android sẽ sử dụng các dò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 dòng, dòng bên trái và dòng trên cùng xác định 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à đường bên phải xác định vùng tương đối trong hình ảnh nội dung của khung hiển thị được phép chiếm.

Hình 1 là ví dụ về thành phần đồ hoạ NinePatch dùng để xác định một nút:

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

Hình 1: Ví dụ về thành phần đồ hoạ NinePatch định nghĩa một nút

Hình ảnh NinePatch này xác định một vùng có thể co giãn gồm phần bên trái và trên cùng và vùng có thể vẽ cùng với các đường dưới cùng và bên phải. Ở hình ảnh trên cùng, các đường chấm màu xám xác định các vùng 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 khu vực cho phép nội dung của khung hiển thị. Nếu nội dung không vừa với vùng này, thì hình ảnh sẽ được kéo giãn cho vừa khít.

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

Tệp XML bố cục mẫu sau đây minh hoạ cách thêm một thành phần đồ 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 layout_widthlayout_height các thuộc tính được thiết lập thành wrap_content để nút này trông gọn gàng xung quanh 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 hiển thị ở trên. Hãy lưu ý chiều rộng và chiều cao của nút thay đổi theo văn bản như thế nào. và hình nền sẽ kéo giãn để phù hợp với kích thước này.

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

Hình 2: Các nút kết xuất bằng XML và hình ảnh 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 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 để triển khai là draw(Canvas) vì hàm 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ẽ của bạn.

Mã sau đây cho thấy một lớp con đơn giản của Drawable để vẽ một đườ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ạ dưới đâ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 (API cấp 24) trở lên, bạn cũng có thể xác định các thực thể của đối tượng có thể vẽ tuỳ chỉnh với XML theo những cách sau:

  • 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, phương pháp tuỳ chỉnh lớp có thể vẽ phải là lớp cấp cao nhất công khai:
    <com.myapp.MyDrawable xmlns:android="http://schemas.android.com/apk/res/android"
        android:color="#ffff0000" />
    
  • Dùng drawable làm tên thẻ XML và chỉ định lớp đủ điều kiện tên khỏi 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à các lớp tĩnh bên trong công khai:
    <drawable xmlns:android="http://schemas.android.com/apk/res/android"
        class="com.myapp.MyTopLevelClass$MyDrawable"
        android:color="#ffff0000" />
    

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

Với Android 5.0 (API cấp 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ể phủ màu cho chúng bằng tài nguyên màu sắc hoặc thuộc tính giao diện phân giải thành màu (ví dụ: ?android:attr/colorPrimary). Thông thường, bạn tạo các nội dung này chỉ một lần và tự động tô màu chúng cho phù hợp với chủ đề của bạn.

Bạn có thể phủ màu cho BitmapDrawable, NinePatchDrawable hoặc VectorDrawable bằng phương thức setTint(). Bạn có thể bạn cũng có thể đặt chế độ và màu phủ màu trong bố cục bằng android:tint và Thuộc tính android:tintMode.

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

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