Android Custom View

Sometimes we need to use same type of view in multiple screen of Android App. We can create a custom view to make our code reusable. Below screenshot you will see sunrise and sunset time using custom view.

You can easily design this UI in traditional way. All TextView, ImageView in the xml file of Activity. How many UI component do you need to design this UI? You need to use four components for each item. So to design above UI there will be 8 components (TextView, ImageView) to design sunrise and sunset view. What if you need 5 view like sunrise/sunset view? You have to use 5*4=20 components! And the xml code will be very messy.

You can avoid this code redundancy, you can put this view in a seperate xml (layout/custom_view.xml) file like below:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:ignore="ContentDescription"
        tools:src="@drawable/location" />

    <TextView
        android:id="@+id/titleTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="8dp"
        android:textSize="16sp"
        android:textStyle="bold"
        app:layout_constraintStart_toEndOf="@+id/imageView"
        app:layout_constraintTop_toTopOf="@+id/imageView"
        tools:text="This is title. It should be in one line" />

    <TextView
        android:id="@+id/subtitleTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="4dp"
        app:layout_constraintStart_toStartOf="@+id/titleTextView"
        app:layout_constraintTop_toBottomOf="@+id/titleTextView"
        tools:text="This is subtitle" />

    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:layout_marginTop="8dp"
        android:background="@color/color_divider"
        app:layout_constraintTop_toBottomOf="@id/imageView" />

</androidx.constraintlayout.widget.ConstraintLayout>

And you can use include tag in activity layout. But if we create a custom view then we don't need to use <include> tag. We can treat our view like a single view (e.g. TextView).

So that our xml code of Activity layout will be like this:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/custom_view_label"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <com.hellohasan.androidcustomview.CustomView
        android:id="@+id/sunrise_custom_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
        app:layout_constraintTop_toBottomOf="@+id/textView"
        app:setImageDrawable="@drawable/sun_rise"
        app:setTitle="Sun rise time - Dhaka, Bangladesh"
        tools:setSubTitle="5:25 AM" />

    <com.hellohasan.androidcustomview.CustomView
        android:id="@+id/sunset_custom_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@+id/sunrise_custom_view"
        app:setImageDrawable="@drawable/sunset"
        app:setTitle="Sunset time - Dhaka, Bangladesh"
        tools:setSubTitle="6:12 PM" />

</androidx.constraintlayout.widget.ConstraintLayout>

Here, com.hellohasan.androidcustomview.CustomView is our custom view. And we set our custom attributes (image drawable, title and subtitle) from xml. To acheive this functionality we have to create file values/attrs.

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="CustomView">
        <attr name="setImageDrawable" format="reference" />
        <attr name="setTitle" format="string" />
        <attr name="setSubTitle" format="string" />
    </declare-styleable>

</resources>

Now we will create CustomView.kt for custom view.

import androidx.constraintlayout.widget.ConstraintLayout

import android.content.Context
import android.graphics.drawable.AnimatedImageDrawable
import android.graphics.drawable.Drawable
import android.widget.TextView
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.widget.ImageView
import androidx.annotation.Nullable
import kotlinx.android.synthetic.main.custom_view.view.*
import java.lang.Exception

class CustomView(context: Context, @Nullable attrs: AttributeSet) : ConstraintLayout(context, attrs) {

    private var view : View = LayoutInflater.from(context).inflate(R.layout.custom_view, this, true)
    private var imageView: ImageView
    private var titleTextView: TextView
    private var subtitleTextView: TextView
    private var imageDrawable : Drawable?
    private var title: String?
    private var subtitle: String?

    init {
        imageView = view.imageView as ImageView
        titleTextView = view.titleTextView as TextView
        subtitleTextView = view.subtitleTextView as TextView

        val typedArray = context.theme.obtainStyledAttributes(attrs, R.styleable.CustomView, 0, 0)

        try {
            imageDrawable = typedArray.getDrawable(R.styleable.CustomView_setImageDrawable)
            title = typedArray.getString(R.styleable.CustomView_setTitle)
            subtitle = typedArray.getString(R.styleable.CustomView_setSubTitle)

            imageView.setImageDrawable(imageDrawable)
            titleTextView.text = title
            subtitleTextView.text = subtitle
        }
        finally {
            typedArray.recycle()
        }

        /** Uncomment below line if all of your attribute fields are required.
         * Throw an exception if required attributes are not set. It will caused Run Time Exception.
         * In this sample project we assume that, no attributes are mandatory
         * *
         */
        /*
        if (imageDrawable == null)
            throw RuntimeException("No image drawable provided")
        if (title == null) {
            throw RuntimeException("No title provided")
        }
        if (subtitle == null) {
            throw RuntimeException("No subtitle provided")
        }*/
    }

    /**
     * Below getter-setter will work, if we need to access the attributes programmatically
     */

    fun setImageDrawable(drawable: Drawable?) {
        imageView.setImageDrawable(drawable)
    }
    fun getImageDrawable() : Drawable? {
        return imageDrawable
    }

    fun setTitle(text: String?) {
        titleTextView.text = text
    }
    fun getTitle() : String? {
        return title
    }

    fun setSubtitle(text: String?) {
        subtitleTextView.text = text
    }
    fun getSubtitle() : String? {
        return subtitle
    }
}

We can set our custom attributes from xml and also from Java/Kotlin code. Here is out MainActivity.kt. We already set image drawable and title from xml of MainActivity. Now we will set the subtitle programmatically from Kotlin class.

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // image drawable and title are already set from xml.
        // I want to set subtitle programmatically. you can set title and image drawable from here
        sunrise_custom_view.setSubtitle("5:31 AM") // sunrise time set as subtitle
        sunset_custom_view.setSubtitle("5:01 PM") // subset time set as subtitle
    }
}