Halo ๐ selamat datang di blog saya, ini adalah artikel pertama saya & saya akan coba membahas apa yang telah menjadi permasalahan umum para programmer android khususnya dalam membuat view (button, textview, dll).
Seperti yang kita ketahui, bahwa pada saat ketika membuat sebuah tampilan atau UI seringkali kita menggunakan view yang sama secara berulang, dan itu akan sangat membuat kita kerepotan ketika kita harus membuat view yang cukup kompleks, dimana view tersebut melibatkan beberapa view lain (gabungan dari view), contohnya seperti ini
Dilihat dari contoh diatas, untuk membuat 1 component view tersebut dibutuhkan beberapa View diantara lain:
- TextView
- ProgressBar
- ImageView
Selain ketiga View diatas, kita juga perlu mengatur state untuk component tersebut, dan state yang harus dibuat kurang lebih adalah sebagai berikut
- Loading
- Disabled
- Success
- Failed
Nahh sekarang jika kalian harus membuat tampilan tersebut, apa yang harus kalian lakukan?
Membuat Compound View
Solusi 1, the obvious one: lakukan semuanya secara manual ๐
Karena kalian membayangkan view tersebut hanya memiliki 3 elemen, maka kalian berfikir untuk membuatnya secara manual, dalam kata lain berarti kalian harus mengatur warna teks, ikon, visibility ProgressBar, dll sesuai dengan State yang sudah ditentukan.
Masalahnya adalah jika kalian menyelesaikannya dengan pendekatan ini, maka coding yang kalian buat menjadi kurang clean, karena akan menghasilkan banyak View dan coding yang berulang.
Solusi 2, the extreme one: menggunakan RecyclerView ๐ค
kalian juga mungkin berfikir untuk menggunakan RecyclerView atau ListView berdasarkan asumsi bahwa sebagian besar hal yang diulang adalah hal yang sama hanya State nya saja yang berbeda.
Permasalahannya adalah apakah pendekatan seperti ini sangat cocok untuk kasus ini? karena biasanya kita menggunakan RecyclerView untuk menampilkan data, sedangkan ini hanya digunakan untuk menampilkan beberapa status kepada user.
Solusi ini mungkin akan berfungsi dengan baik, meskipun memerlukan banyak effort hanya untuk membuat tampilan yang sederhana ini, tetapi untuk kasus ini solusi ini bukan solusi yang baik atau best practice.
Solusi 3, the simple one: Membuat Compound View Dengan State ๐
Oke, pertama2 untuk membuat sebuah Compound View kita memerlukan nama, maka nama dari Compound View yang akan kita buat kali ini adalah StateView dimana StateView ini akan memiliki 4 State:
- Disabled
- Loading
- Done
- Failed
Lalu sekarang kita buat Enum Class dengan nama State.kt
enum class State {
DISABLED,
LOADING,
DONE,
FAILED
}
Kemudian kita buat 1 file layout baru dengan nama state_view.xml
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:gravity="center"
android:orientation="horizontal"
tools:parentTag="android.widget.LinearLayout">
<ProgressBar
android:id="@+id/progressbar"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginEnd="5dp"
android:visibility="gone"/>
<ImageView
android:id="@+id/ivIcState"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginEnd="5dp"
android:visibility="gone"
tools:ignore="ContentDescription"
tools:src="@drawable/baseline_chevron_right_24" />
<TextView
android:id="@+id/tvTitleState"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/black"
tools:text="Upload profile picture" />
</merge>
Seperti yang kalian lihat, disini kita menggunakan tag <merge>
sebagai parent root nya, mengapa kita menggunakan itu? nah ini adalah bagian penting, tag <merge>
digunakan untuk menghindari redundant ViewGroup, dengan kata lain karena kita akan membuat class turunan (extend) dari LinearLayout maka kita tidak perlu menggunakan LinearLayout sebagai root di XML, jika tidak maka kita akan memiliki 2 LinearLayout.
Tanpa tag merge
- LinearLayout -> LinearLayout -> View Yang Lain
Dengan tag <merge>
- LinearLayout -> View Yang Lain
Membuat class Compound View di kotlin mungkin agak tricky bagi pemula. Di Android, ketika kalian akan membuat tampilan kustom, seperti class yang extend ke View dll, maka kalian perlu memanggil 3 constructor yang memanggil super()
. namun ini bukanlah kasus penggunaan umum di kotlin untuk memiliki class dengan beberapa constructor, jika kalian ingin tahu lebih lanjut Antonio Leiva punya artikel yang bagus untuk membahas subjek ini.
Oiya untuk icon2 nya kalian bisa unduh disini, dan untuk code warna nya, kalian bisa tambahkan sendiri di colors.xml
<color name="black">#000000</color>
<color name="red">#FF1111</color>
Oke next, kita buat 1 class baru dengan nama StateView.kt
Disini, kalian bisa buat 3 constructor seperti ini
class StateView: LinearLayout
{
private lateinit var tvTitle: TextView
private lateinit var ivIcon: ImageView
private lateinit var progressBar: ProgressBar
constructor(context: Context) : super(context){
init(context)
}
constructor(context: Context, attrs: AttributeSet): super(context, attrs){
init(context)
}
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
init(context)
}
...
}
atau kalian bisa membuat kotlin untuk meng-handle constructor tersebut dengan default value. Terimakasih @JvmOverloads ๐
class StateView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr)
Nah sekarang kita tambahkan logic untuk meng-handle state2 yang sudah dibuat tadi.
fun setText(text: String) {
tvTitle.text = text
}
fun setState(state: State) {
when (state) {
State.DISABLED -> disabledState()
State.LOADING -> loadingState()
State.DONE -> doneState()
State.FAILED -> failedState()
}
}
private fun disabledState() {
progressBar.visibility = View.GONE
ivIcon.setImageDrawable(
ContextCompat.getDrawable(
context,
R.drawable.baseline_chevron_right_24
)
)
ivIcon.visibility = View.VISIBLE
alpha = 0.5f
}
private fun loadingState() {
ivIcon.visibility = View.GONE
progressBar.visibility = View.VISIBLE
alpha = 1f
}
private fun doneState() {
progressBar.visibility = View.GONE
ivIcon.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.baseline_check_24))
ivIcon.visibility = View.VISIBLE
alpha = 1f
}
private fun failedState() {
progressBar.visibility = View.GONE
ivIcon.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.baseline_clear_24))
ivIcon.visibility = View.VISIBLE
tvTitle.setTextColor(ContextCompat.getColor(context, R.color.red))
alpha = 1f
}
untuk coding lengkapnya kira-kira seperti ini
class StateView @JvmOverloads
constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
LinearLayout(context, attrs, defStyleAttr) {
private lateinit var tvTitle: TextView
private lateinit var ivIcon: ImageView
private lateinit var progressBar: ProgressBar
init {
init()
}
fun setText(text: String) {
tvTitle.text = text
}
fun setState(state: State) {
when (state) {
State.DISABLED -> disabledState()
State.LOADING -> loadingState()
State.DONE -> doneState()
State.FAILED -> failedState()
}
}
private fun disabledState() {
progressBar.visibility = View.GONE
ivIcon.setImageDrawable(
ContextCompat.getDrawable(
context,
R.drawable.baseline_chevron_right_24
)
)
ivIcon.visibility = View.VISIBLE
alpha = 0.5f
}
private fun loadingState() {
ivIcon.visibility = View.GONE
progressBar.visibility = View.VISIBLE
alpha = 1f
}
private fun doneState() {
progressBar.visibility = View.GONE
ivIcon.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.baseline_check_24))
ivIcon.visibility = View.VISIBLE
alpha = 1f
}
private fun failedState() {
progressBar.visibility = View.GONE
ivIcon.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.baseline_clear_24))
ivIcon.visibility = View.VISIBLE
tvTitle.setTextColor(ContextCompat.getColor(context, R.color.red))
alpha = 1f
}
private fun init() {
LayoutInflater.from(context).inflate(R.layout.state_view, this, true)
tvTitle = findViewById(R.id.tvTitleState)
ivIcon = findViewById(R.id.ivIcState)
progressBar = findViewById(R.id.progressbar)
}
}
And Finally sekarang kita bisa mendeklarasikannya di XML kita seperti View pada umumnya sebanyak yang kita inginkan
<dev.fathonaji.compoundviews.StateView
android:id="@+id/state1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<dev.fathonaji.compoundviews.StateView
android:id="@+id/state2"
android:layout_marginTop="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<dev.fathonaji.compoundviews.StateView
android:id="@+id/state3"
android:layout_marginTop="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
lalu tambahkan logic sederhana di Activity kita
state1.setText("Uploading profile picture")
state1.setState(State.LOADING)
// Simulasi running task
Handler(Looper.myLooper()!!).postDelayed({
state1.setState(State.DONE)
},3000)
And we are done. ๐ ๐ ๐
Bayangkan jika kalian memilih solusi 1, artinya kalian harus menulis logic dari fungsi setState()
sebanyak 3x dengan mengulang-ulang logic yang sama. dan jika tampilannya dimodifikasi dengan lebih banyak status misalnya, maka kompleksitas dibaliknya juga akan berkembang. Dan akhirnya, bayangkan jika kalian harus membuat tampilan ini beberapa kali dalam projek kalian dengan tujuan yang berbeda.
Kesimpulan
Apa itu Compound View
Compound View pada dasarnya adalah kumpulan View yang dibungkus kedalam ViewGroup. Dalam kasus ini berarti Compound View yang akan kita buat adalah LinearLayout yang mengandung TextView, ImageView dan ProgressBar, dimana ImageView dan ProgressBar akan kita show hide berdasarkan State yang akan kita buat nanti.
Apa Keuntungan Menggunakan Compound View
- Mengenkapsulasi dan memusatkan logic
- Menghindari duplikasi code
- Memudahkan pada saat maintenance atau modifikasi di kemudian hari
Jika artikel ini bermanfaat, kalian bisa share artikel ini, dan untuk source code nya, kalian bisa clone github saya disini.
Hatur Nuhun & sampai jumpa ๐