Using Vector Drawable Safely on pre-lollipop version
Bài đăng này đã không được cập nhật trong 6 năm
Vector Drawable - offically support trên android version 5.0 (API level 21) là vector graphic định nghĩa trong một file XML bởi tập các points, lines, curves cùng với thông tin về màu sắc tương ứng. Điểm lợi thế của nó là khả năng scalability. Nó có thể scale mà không bị mất chất lượng, điều đó có nghĩa là chỉ cần 1 file Vector Drawable, bạn có thể resize cho các màn hình với density khác nhau mà không bị mất chất lượng hình ảnh. Rõ ràng điều này giúp cho kích thước của file APK giảm đáng kể.
Bạn có thể tìm hiểu thêm nhiều thông tin về loại drawable này tại đây .
Vì lợi ích đặc biệt của nó nên dù ra đời từ android 5.0, nhưng google vẫn cho phép các version android trước đó sử dụng thông qua các gói support. Tuy nhiên, trong lúc sử dụng cho các version trước đó, có một vài điểm cần lưu ý. Trong bài viêt này, mình xin tóm tắt vài tips để tránh những issues lúc sử dụng Vector Drawable cho android 4.4 (pre-lollipop).
build.gradle
Để hỗ trợ cho android pre-lollipop bạn cần enable vectorDrawable config trong build.grale của module app. Lưu ý, đây là điều bắt buộc.
android {
defaultConfig {
vectorDrawables.useSupportLibrary = true
}
}
Setting in Application class
override fun onCreate() {
super.onCreate()
// App crashes when selector with using vector-drawable
// So, keep using this line + AppCompatImageView for android 4.4
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
// Your other logic code ...
}
Đây là option không bắt buộc, mặc định sẽ là false, bởi vì enable nó có thể gây những issue về sử dụng memory, và khi update đối tượng Configuration. Nếu bạn thay đổi configuration bằng code, thì tốt nhất là không nên enable feature này.
Còn lý do để thêm dòng này là bạn có thể bị crash trên 4.4 nếu vô tình tạo 1 selector có chứa reference đến một Vector Drawable
VD:
<selector xmlns:android="...">
<item android:state_checked="true"
android:drawable="@drawable/vector_checked_icon" />
<item android:drawable="@drawable/vector_icon" />
</selector>
=> Vì thế, hãy cân nhắc trường hợp của ứng dụng trước khi sử dụng option này.
Way to getDrawable programmatically
Có nhiều cách để get và tạo Vector Drawable thông qua gói support.
// using ContextCompat
ContextCompat.getDrawable(@NonNull Context context, @DrawableRes int resId)
// using ResourcesCompat
ResourcesCompat.getDrawable(@NonNull Resources res, @DrawableRes int id, @Nullable Theme theme)
// using AppCompatResources
AppCompatResources.getDrawable(@NonNull Context context, @DrawableRes int resId)
// using VectorDrawableCompat
VectorDrawableCompat.create(@NonNull Resources res, @DrawableRes int resId, @Nullable Theme theme)
Tuy nhiên, với mỗi cách khác nhau, trong các điều kiện khác nhau có thể gây ra những lỗi không mong muốn. Đặc biệt trong trường hợp làm custom view nếu bạn cho phép các attribute reference đến resource là một drawable (normal drawable hoặc vector drawable). Hãy xem kết quả:
Normal image resource (non-vector)
| Kind | Set setCompatVectorFromResourcesEnabled |
Not Set |
|---|---|---|
| ContextCompat | Good | Good |
| ResourcesCompat | Good | Good |
| AppCompatResources | Good | Good |
| VectorDrawableCompat | Crashed | Crashed |
Vector drawable resource
| Kind | Set setCompatVectorFromResourcesEnabled |
Not Set |
|---|---|---|
| ContextCompat | Good | Crash API < 21 |
| ResourcesCompat | Good | Crash API < 21 |
| AppCompatResources. | Good | Good |
| VectorDrawableCompat | Good | Good |
Với cả Normal hay Vector Drawable resource, pre-lollipop hay không, chỉ có AppCompatResources.getDrawable(...) là an toàn nhất để getDrawable().
Dưới đây là ví dụ về cách custom view có thể dùng cho bất kì drawable nào:
const val INVALID_RES_ID = -1
private fun initAttrs(attrs: AttributeSet?) {
if (isInEditMode || attrs == null) return
var drawableStart: Drawable? = null
var ta: TypedArray? = null
try {
ta = context.obtainStyledAttributes(attrs, R.styleable.YourCustomView)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// Directly call getDrawable is safely
drawableStart = typedArray.getDrawable(...)
} else {
val drawableStartId = typedArray.getResourceId(..., INVALID_RES_ID)
if (drawableStartId != INVALID_RES_ID)
drawableStart = AppCompatResources.getDrawable(context, drawableStartId)
}
// other logic
} finally {
ta?.recycle()
}
}
Ngoài ra, lúc tạo custom view, mình khuyên các bạn nên extends các class từ gói support như AppCompatImageView, AppCompatTextView,... Vì bản thân các component trong gói support có hỗ trợ tốt hơn trên những device pre-lollipop version.
Happy Coding !
All rights reserved