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 đối tượng trừu tượng chung cho nội dung có thể vẽ được. Các lớp con khác nhau sẽ hỗ trợ các trường hợp hình ảnh cụ thể. 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 khác nhau.
Có 2 cách để xác định và tạo thực thể cho Drawable
ngoài việc sử dụng các 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 nên sử dụng vectơ vẽ được để 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 liên quan. Điều này cho phép điều chỉnh các vectơ vẽ được theo 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 bài viết Tổng quan về vectơ vẽ được.
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 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 hình ảnh đồ hoạ khác, chẳng hạn như nội dung 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 thư mục res/drawable/
của dự án. Sau khi đã ở trong dự án, bạn có thể tham chiếu tài nguyên hình ảnh từ mã hoặc bố cục XML của mình. Dù bằng cách nào, thì hệ thống cũng tham chiếu đến việc sử dụng mã nhận dạng tài nguyên, là tên tệp mà không có đuôi loại tệp. Ví dụ: tham chiếu my_image.png
là my_image
.
Lưu ý: Các tài nguyên hình ảnh đặt trong thư mục res/drawable/
có thể được tự động tối ưu hoá với tính năng nén hình ảnh không tổn hao bằng công cụ aapt
trong quy trình xây dựng. Ví dụ: tệp 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 thông qua bảng màu. Kết quả là hình ảnh có chất lượng như nhau, nhưng cần í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 theo thời gian 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 một bitmap, hãy đặt hình ảnh của bạn vào thư mục res/raw/
, nơi công cụ aapt
không sửa đổi những 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 nê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 tạo thực thể cho bao nhiêu đối tượng khác nhau. 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 một thuộc tính (chẳng hạn như alpha) cho một đối tượng, thì đối tượng đó cũng sẽ ảnh hưởng đến đối tượng còn lại. Khi xử lý nhiều thực thể của một tài nguyên hình ảnh, thay vì chuyển đổi trực tiếp đối tượng Drawable
, bạn nên thực hiện ảnh động dạng tween.
Đoạn mã XML dưới đây cho biết cách thêm 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 của các đối tượng có thể vẽ, hãy đảm bảo hình ảnh có kích thước phù hợp với nhiều mật độ pixel. Nếu không chính xác, hình ảnh sẽ được điều chỉnh theo tỷ lệ cho phù hợp. Điều này có thể gây ra việc tạo cấu 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ợ nhiều mật độ pixel.
Tạo đối tượng có thể vẽ từ tài nguyên XML
Nếu bạn muốn tạo một đối tượng Drawable
, ban đầu đối tượng này không phụ thuộc vào các biến do mã của bạn hoặc hoạt động tương tác của người dùng xác định, thì bạn nên xác định Drawable
trong XML. Ngay cả khi bạn muốn Drawable
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 cũng 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 tạo thực thể.
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 cho thấy mã 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 đó, hãy truy xuất và tạo thực thể cho đối tượng bằng cách gọi Resources#getDrawable()
và truyền mã nhận dạng tài nguyên của tệp XML. Bất kỳ lớp con Drawable
nào hỗ trợ phương thức inflate()
đều có thể được xác định trong XML và được ứng dụng tạo thực thể.
Mỗi lớp có thể vẽ hỗ trợ tính năng tăng cường XML sẽ 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 sẽ tạo thực thể cho TransitionDrawable
và đặt thành 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 nêu 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 tự động vẽ đồ hoạ hai chiều. Bạn có thể lập trình để vẽ các hình dạng gốc 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à lớp con của Drawable
. Vì lý do này, bạn có thể sử dụng ShapeDrawable
tại bất cứ nơi nào 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 dưới dạng khung hiển thị tuỳ chỉnh riêng rồi thêm hình dạng đó vào một bố cục trong ứng dụng.
Vì 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ư minh hoạ trong đoạn mã ví dụ 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ã mẫu ở trên giống như cách 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 thông tin này vào một hoạt động trong ứng dụng theo phương thức lập trình, 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 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)
. Hàm này được gọi khi lớp được tăng cường từ XML. Ví dụ sau 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" />
Giống như nhiều loại đối tượng có thể vẽ khác trong gói android.graphics.drawable
, lớp ShapeDrawable
cho phép bạn xác định nhiều thuộc tính của đối tượng bằ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, tông màu, độ mờ và màu sắc.
Bạn cũng có thể xác định các hình dạng có thể vẽ nguyên gốc bằng tài nguyên XML. Để biết thêm thông tin, hãy xem phần Hình dạng đối tượng có thể vẽ trong phần Các loại tài nguyên có thể vẽ.
Đố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 sẽ tự động đổi kích thước đồ hoạ cho phù hợp với nội dung của thành phần hiển thị. Một ví dụ về cách sử dụng hình ảnh NinePatch làm nền cho các nút Android tiêu chuẩn – các nút phải kéo giãn để phù hợp với các chuỗi có độ dài khác nhau. Đồ hoạ NNAPI là một hình ảnh PNG tiêu chuẩn có thêm đường viền 1 pixel.
Bạn phải lưu tệp này bằng phần mở rộng 9.png
trong thư mục res/drawable/
của dự án.
Dùng đường viền để xác định các vùng tĩnh và vùng 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) đường màu đen rộng 1 pixel ở 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 có màu trắng). Bạn có thể có số lượng phần có thể co giãn tuỳ ý. 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 luô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 (hiệu quả hơn là các đường khoả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 khung hiển thị, thì đối tượng này sẽ tự kéo giãn để tất cả văn bản chỉ chiếm khu vực đượ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 đườ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 đường này, dòng bên trái và trên cùng sẽ 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 dò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 thành phần hiển thị được phép chiếm.
Hình 1 cho thấy ví dụ về đồ hoạ NinePatch dùng để xác định một nút:
Hình ảnh đồ hoạ NinePatch này xác định một vùng có thể co giãn với các dòng bên trái và trên cùng, cũng như vùng có thể vẽ với các đường dưới cùng và bên phải. Trong hình ảnh trên cùng, các đường chấm màu xá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 khu vực mà nội dung của thành phần hiển thị được phép. 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 để vừa với vùng này.
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 cách sử dụng trình chỉnh sửa đồ hoạ WYSIWYG. Thao tá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 cấu phần phần mềm vẽ do quá trình sao chép pixel.
XML bố cục mẫu sau đây minh hoạ cách thêm đồ hoạ NinePatch vào một số 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"/>
Lưu ý rằng các thuộc tính layout_width
và layout_height
được đặt thành wrap_content
để làm cho nút nằm vừa vặn quanh văn bản.
Hình 2 cho thấy hai nút được kết xuất từ hình ảnh XML và NinePatch hiển thị ở trên. Hãy lưu ý đến cách chiều rộng và chiều cao của nút thay đổi theo văn bản và hình nền sẽ kéo giãn để phù hợp với văn bản đó.
Đối tượng có thể vẽ tuỳ chỉnh
Nếu muốn, bạn có thể tạo một số bản vẽ tuỳ chỉnh 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ạ 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 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 đối tượng 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ể 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 bằng các tài nguyên màu hoặc thuộc tính giao diện phân giải các tài nguyên màu (ví dụ: ?android:attr/colorPrimary
). Thông thường, bạn chỉ tạo những thành phần này một lần và tự động tô màu cho phù hợp với giao diện của mình.
Bạn có thể áp dụng sắc thái màu 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 chế độ và màu phủ trong bố cục bằng các thuộc tính android:tint
và android:tintMode
.
Trích xuất màu nổi bật từ mộ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 các đối tượng có thể vẽ dưới dạng Bitmap
rồi truyền đối tượng đó vào Palette
để truy cập vào các màu đó.
Để biết thêm thông tin, hãy đọc bài viết Chọn màu bằng Palette API (API Bảng khung hiển thị).