Bahasa Indonesia

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

Contoh Penerapan Custom View Android Kotlin

Dilihat dari contoh diatas, untuk membuat 1 component view tersebut dibutuhkan beberapa View diantara lain:

  1. TextView
  2. ProgressBar
  3. ImageView

Selain ketiga View diatas, kita juga perlu mengatur state untuk component tersebut, dan state yang harus dibuat kurang lebih adalah sebagai berikut

  1. Loading
  2. Disabled
  3. Success
  4. 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:

  1. Disabled
  2. Loading
  3. Done
  4. 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

  1. Mengenkapsulasi dan memusatkan logic
  2. Menghindari duplikasi code
  3. 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 ๐Ÿ‘‹

0
0
0
0