অ্যান্ড্রয়েডের ডিফল্ট `Button` উইজেটে কাস্টম কালার, রিপল ইফেক্ট, এবং স্টেট-ভিত্তিক স্টাইলিং (Pressed, Focused, Default) দেওয়ার জন্য State List Drawable (Selector) ব্যবহার করা সবচেয়ে ভালো এবং পারফরম্যান্ট পদ্ধতি। এই টিউটোরিয়ালে একটি বাটনের জন্য প্রেসড, ফোকাসড, এবং নরমাল স্টেটে আলাদা আলাদা কালার এবং গ্রেডিয়েন্ট দেওয়ার পদ্ধতি দেখানো হলো।
কনসেপ্ট: State List Drawable (Selector)
একটি XML ফাইল (`drawable/background_button.xml`) এ `
১. drawable/background_button.xml (Selector)
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="https://schemas.android.com/apk/res/android">
<!-- State: Pressed (User টাচ করছে) -->
<item android:state_pressed="true">
<shape android:shape="rectangle">
<gradient
android:startColor="#FFF000" <!-- Bright Yellow -->
android:endColor="#D9DD95" <!-- Light Olive -->
android:angle="270" />
<stroke
android:width="2dp"
android:color="#D1D3E3" />
<corners android:radius="8dp" />
<padding
android:left="16dp"
android:top="12dp"
android:right="16dp"
android:bottom="12dp" />
</shape>
</item>
<!-- State: Focused (Keyboard/Gamepad Navigation) -->
<item android:state_focused="true">
<shape android:shape="rectangle">
<gradient
android:startColor="#ECA680" <!-- Light Orange -->
android:endColor="#FD5900" <!-- Deep Orange -->
android:angle="270" />
<stroke
android:width="2dp"
android:color="#D1D3E3" />
<corners android:radius="8dp" />
<padding
android:left="16dp"
android:top="12dp"
android:right="16dp"
android:bottom="12dp" />
</shape>
</item>
<!-- State: Enabled but not pressed/focused (Default) -->
<item android:state_enabled="true">
<shape android:shape="rectangle">
<gradient
android:startColor="#A1AAEE" <!-- Light Blue -->
android:endColor="#4356E7" <!-- Deep Blue -->
android:angle="270" />
<stroke
android:width="2dp"
android:color="#D1D3E3" />
<corners android:radius="8dp" />
<padding
android:left="16dp"
android:top="12dp"
android:right="16dp"
android:bottom="12dp" />
</shape>
</item>
<!-- State: Disabled -->
<item android:state_enabled="false">
<shape android:shape="rectangle">
<solid android:color="#CCCCCC" />
<stroke android:width="1dp" android:color="#999999" />
<corners android:radius="8dp" />
<padding
android:left="16dp"
android:top="12dp"
android:right="16dp"
android:bottom="12dp" />
</shape>
</item>
</selector>
২. activity_main.xml (লেআউট)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="24dp"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="State List Drawable Demo"
android:textSize="20sp"
android:textStyle="bold"
android:layout_marginBottom="32dp" />
<Button
android:id="@+id/btnCustom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Custom Styled Button"
android:textSize="16sp"
android:textColor="@android:color/white"
android:textAllCaps="false"
android:background="@drawable/background_button" />
<Button
android:id="@+id/btnDisabled"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Disabled Button"
android:textSize="16sp"
android:textAllCaps="false"
android:enabled="false"
android:background="@drawable/background_button" />
</LinearLayout>
৩. MainActivity.java (Optional - Interaction Demo)
package com.example.custombuttoncolor;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btnCustom = findViewById(R.id.btnCustom);
btnCustom.setOnClickListener(v ->
Toast.makeText(this, "Button Clicked! State: Pressed → Normal", Toast.LENGTH_SHORT).show()
);
}
}
৪. Kotlin Version (Modern)
// MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById
৫. MaterialButton (Recommended for Modern Apps)
নতুন প্রজেক্টে `MaterialButton` ব্যবহার করা উচিত, যেটি বিল্ট-ইন রিপল, কালার থিমিং, এবং শেপ সাপোর্ট করে।
XML Style (res/values/themes.xml)
<style name="CustomButton" parent="Widget.Material3.Button">
<item name="backgroundTint">@color/button_bg_selector</item>
<item name="rippleColor">@color/ripple_selector</item>
<item name="cornerRadius">8dp</item>
<item name="android:textColor">@android:color/white</item>
<item name="android:insetTop">0dp</item>
<item name="android:insetBottom">0dp</item>
</style>
Color State List (res/color/button_bg_selector.xml)
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="https://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:color="#D9DD95" />
<item android:state_focused="true" android:color="#FD5900" />
<item android:state_enabled="false" android:color="#CCCCCC" />
<item android:color="#4356E7" /> <!-- Default -->
</selector>
Ripple Color (res/color/ripple_selector.xml)
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="https://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:color="#FFF000" />
<item android:color="@android:color/transparent" />
</selector>
Usage in Layout
<com.google.android.material.button.MaterialButton
android:id="@+id/materialBtn"
style="@style/CustomButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Material Button" />
Jetpack Compose Approach (Modern Standard)
@Composable
fun CustomButton(
text: String,
onClick: () -> Unit,
enabled: Boolean = true
) {
val colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary,
disabledContainerColor = MaterialTheme.colorScheme.surfaceContainerHighest,
contentColor = MaterialTheme.colorScheme.onPrimary,
disabledContentColor = MaterialTheme.colorScheme.onSurfaceVariant
)
Button(
onClick = onClick,
enabled = enabled,
colors = colors,
shape = RoundedCornerShape(8.dp),
modifier = Modifier.fillMaxWidth()
) {
Text(text = text, fontWeight = FontWeight.Medium)
}
}
রান করুন এবং পর্যবেক্ষণ করুন
- অ্যাপ চালান।
- নরমাল স্টেট: নীল গ্রেডিয়েন্ট ব্যাকগ্রাউন্ড।
- প্রেসড স্টেট: বাটনে হোল্ড করলে হলদে/অলিভ গ্রেডিয়েন্ট।
- ফোকাসড স্টেট: ট্যাব কী বা গেমপ্যাড দিয়ে ফোকাস করলে نارাঞ্জি গ্রেডিয়েন্ট।
- ডিজেবল্ড স্টেট: দ্বিতীয় বাটনটি গ্রে আউট থাকবে।
সেরা অনুশীলন (Best Practices)
| প্যрадаইম | রিকমেন্ডেশন |
|---|---|
| নতুন প্রজেক্ট | MaterialButton + ColorStateList বা Jetpack Compose |
| লিগ্যাসি প্রজেক্ট | AppCompatButton + Selector Drawable (এই টিউটোরিয়াল) |
| থিমিং | Hardcoded কালারের বদলে `?attr/colorPrimary` ব্যবহার করুন |
| অ্যাক্সেসিবিলিটি | ফোকাস স্টেট অবশ্যই ডিফাইন করুন (TalkBack/Keyboard nav) |
| পারফরম্যান্স | Selector XML ক্যাশ হয়, রানটাইমে অ্যালোকেশন নেই |
সাধারণ ভুলগুলো এড়িয়ে চলুন
- ❌ `android:background` এ সরাসরি কালার দিয়ে স্টেট হ্যান্ডল করা যায় না (Selector লাগবে)।
- ❌ `state_pressed` এবং `state_focused` এর অর্ডার মেনেই রাখুন (স্পেসিফিক স্টেট আগে, জেনেরিক পরে)।
- ❌ `android:state_enabled="false"` আইটেম যোগ না করলে ডিজেবল্ড স্টেটে অপ্রত্যাশিত UI দেখায়।
- ❌ বাটনের `android:textColor` স্টেট-লিস্ট দিতে চাইলে `ColorStateList` ব্যবহার করুন (আলাদা ফাইল)।
State List Drawable ব্যবহার করা অ্যান্ড্রয়েডে বাটন (এবং যেকোনো View) স্টাইল করার সবচেয়ে ক্লাসিক, লাইটওয়েট, এবং কম্প্যাটিবল পদ্ধতি। মডার্ন অ্যাপের জন্য MaterialButton বা Compose ব্যবহার করুন, কিন্তু এই কনসেপ্টটি বুঝতে থাকা অপরিহার্য।