Thay vì dùng background color đơn sắc, Android cho phép định nghĩa State List Drawable (Selector) để Button thay đổi màu sắc, gradient, viền, bo góc tùy theo trạng thái: normal, pressed, focused, disabled.
1. Tạo Drawable Selector (res/drawable/btn_custom_background.xml)
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="https://schemas.android.com/apk/res/android">
<!-- Trạng thái nhấn (pressed) -->
<item android:state_pressed="true">
<shape android:shape="rectangle">
<gradient
android:startColor="#FFEB3B"
android:endColor="#FDD835"
android:angle="270" />
<stroke android:width="2dp" android:color="#F9A825" />
<corners android:radius="8dp" />
<padding android:left="16dp" android:top="12dp" android:right="16dp" android:bottom="12dp" />
</shape>
</item>
<!-- Trạng thái focus (điều hướng bàn phím/TV) -->
<item android:state_focused="true">
<shape android:shape="rectangle">
<gradient
android:startColor="#FF9800"
android:endColor="#F57C00"
android:angle="270" />
<stroke android:width="2dp" android:color="#E65100" />
<corners android:radius="8dp" />
<padding android:left="16dp" android:top="12dp" android:right="16dp" android:bottom="12dp" />
</shape>
</item>
<!-- Trạng thái disabled -->
<item android:state_enabled="false">
<shape android:shape="rectangle">
<solid android:color="#E0E0E0" />
<stroke android:width="1dp" android:color="#BDBDBD" />
<corners android:radius="8dp" />
<padding android:left="16dp" android:top="12dp" android:right="16dp" android:bottom="12dp" />
</shape>
</item>
<!-- Trạng thái mặc định (normal) -->
<item>
<shape android:shape="rectangle">
<gradient
android:startColor="#2196F3"
android:endColor="#1976D2"
android:angle="270" />
<stroke android:width="2dp" android:color="#1565C0" />
<corners android:radius="8dp" />
<padding android:left="16dp" android:top="12dp" android:right="16dp" android:bottom="12dp" />
</shape>
</item>
</selector>
2. Áp dụng vào Button (Layout XML)
<Button
android:id="@+id/btnCustom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Nút tùy chỉnh"
android:textColor="@android:color/white"
android:textSize="16sp"
android:background="@drawable/btn_custom_background"
android:stateListAnimator="@null" />
💡 Lưu ý:
android:stateListAnimator="@null" tắt hiệu ứng elevation mặc định (shadow nâng lên khi press) của Material Button, để selector drawable hiển thị trọn vẹn. Nếu dùng MaterialButton, hãy dùng app:backgroundTint="@null" và set background drawable.
3. Sử dụng trong Code (Dynamic)
Button btn = findViewById(R.id.btnCustom); // Đổi màu động (ví dụ: theme ngày/đêm) btn.setBackgroundResource(R.drawable.btn_custom_background_night);
4. Mở rộng: Ripple Effect (Android 5.0+)
Kết hợp Ripple với Selector cho cảm giác chạm hiện đại:
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="https://schemas.android.com/apk/res/android"
android:color="?attr/colorControlHighlight">
<item android:id="@android:id/mask">
<shape android:shape="rectangle">
<corners android:radius="8dp" />
<solid android:color="#2196F3" />
</shape>
</item>
<item android:drawable="@drawable/btn_custom_background" />
</ripple>
Lưu file dưới res/drawable-v21/btn_custom_background.xml (thư mục v21 cho API 21+).
5. MaterialButton (Khuyên dùng cho app hiện đại)
<com.google.android.material.button.MaterialButton
android:id="@+id/materialBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Material Button"
app:backgroundTint="@color/selector_btn_bg"
app:cornerRadius="8dp"
app:strokeWidth="2dp"
app:strokeColor="@color/selector_stroke" />
res/color/selector_btn_bg.xml:
<selector xmlns:android="https://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:color="#FFEB3B" />
<item android:state_focused="true" android:color="#FF9800" />
<item android:state_enabled="false" android:color="#E0E0E0" />
<item android:color="#2196F3" />
</selector>
So sánh các cách tùy chỉnh Button
| Cách | Độ phức tạp | Tính năng | Khuyên dùng |
|---|---|---|---|
| Selector Drawable (Shape) | Trung bình | Gradient, stroke, padding, corners tùy ý | Custom UI đặc thù, legacy support |
| Ripple + Selector | Cao | Ripple effect + full custom | App hiện đại, Material Design |
| MaterialButton + ColorStateList | Thấp | Background tint, stroke, icon, shape | Đa số trường hợp, dễ maintain |
| Jetpack Compose | Thấp (Kotlin) | Declarative, state-based styling | Project mới, full Compose |
Best Practices
- Định nghĩa màu trong
res/values/colors.xmlvàres/color/selector_*.xmlđể dễ theme light/dark. - Dùng
dpcho padding, margin, radius;spcho text size. - Test trên cả Light/Dark theme, landscape, split-screen.
- Accessibility: Đảm bảo contrast ratio ≥ 4.5:1 (text trên background).
- Tránh hardcode màu trong Java/Kotlin code.
Kết luận
Drawable Selector là cách mạnh mẽ, linh hoạt nhất để tùy chỉnh Button truyền thống. Với app mới, ưu tiên MaterialButton + ColorStateList hoặc Jetpack Compose để code ngắn gọn, nhất quán, dễ bảo trì.