Android

Custom view

Let’s try to create simple Custom View and looks how it works

First of all, I will create a new class and name it as AddressView this is a class and layout:

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

    <TextView
        android:id="@+id/addressViewZipCode"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="8dp"
        android:visibility="gone"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toStartOf="@id/addressViewCountry"
        app:layout_constraintTop_toTopOf="parent"
        tools:text="Zip code"
        />

    <TextView
        android:id="@+id/addressViewCountry"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintStart_toEndOf="@id/addressViewZipCode"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:text="Country"
        />

    <TextView
        android:id="@+id/addressViewCity"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/addressViewCountry"
        tools:text="City"
        />

    <TextView
        android:id="@+id/addressViewLine1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/addressViewCity"
        tools:text="Address line 1"
        />

    <TextView
        android:id="@+id/addressViewLine2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:visibility="gone"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/addressViewLine1"
        tools:text="Address line 2"
        />

</androidx.constraintlayout.widget.ConstraintLayout>
@Suppress("MemberVisibilityCanBePrivate")
class AddressView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
): ConstraintLayout(context, attrs, defStyleAttr) {

    private val addressViewZipCode: TextView
    private val addressViewCountry: TextView
    private val addressViewCity: TextView
    private val addressViewLine1: TextView
    private val addressViewLine2: TextView

    var zipCode: String? = null
        set(value) {
            field = value
            addressViewZipCode.text = value
            addressViewZipCode.visibility = if (value.isNullOrEmpty()) View.GONE else View.VISIBLE
        }

    var country: String = ""
        set(value) {
            field = value
            addressViewCountry.text = value
        }

    var city: String = ""
        set(value) {
            field = value
            addressViewCity.text = value
        }

    var line1: String = ""
        set(value) {
            field = value
            addressViewLine1.text = value
        }

    var line2: String? = null
        set(value) {
            field = value
            addressViewLine2.text = value
            addressViewLine2.visibility = if (value.isNullOrEmpty()) View.GONE else View.VISIBLE
        }

    init {
        LayoutInflater.from(context).inflate(R.layout.view_address, this)
        addressViewZipCode = findViewById(R.id.addressViewZipCode)
        addressViewCountry = findViewById(R.id.addressViewCountry)
        addressViewCity = findViewById(R.id.addressViewCity)
        addressViewLine1 = findViewById(R.id.addressViewLine1)
        addressViewLine2 = findViewById(R.id.addressViewLine2)
    }
}

Little bit more information about custom view class. I extended this class from ConstraintLayout and created default constructor for required View parameters. Also I annotated constructor with @JvmOverloads, because it will be called by Android Java classes.

Then I created private fields for all views, placed in layout and initialised them in init block. Also I added parameters (and made setter for each of them) for all fields, for simplify accessing from code.

But for layout-based access I need do something different. Firstly I need to create new values file attrs.xml:

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

    <declare-styleable name="AddressView">
        <attr name="zip_code" format="string" />
        <attr name="country" format="string" />
        <attr name="city" format="string" />
        <attr name="line1" format="string" />
        <attr name="line2" format="string" />
    </declare-styleable>

</resources>

This file contains all fields for accessing it from XML layout. Than I added some lines in init block of my custom view:

val attributes = context.obtainStyledAttributes(attrs, R.styleable.AddressView, defStyleAttr, 0)
attributes.getString(R.styleable.AddressView_zip_code).let { zipCode = it }
attributes.getString(R.styleable.AddressView_country).let { country = it ?: "" }
attributes.getString(R.styleable.AddressView_city).let { city = it ?: "" }
attributes.getString(R.styleable.AddressView_line1).let { line1 = it ?: "" }
attributes.getString(R.styleable.AddressView_line2).let { line2 = it }
attributes.recycle()

Now I can access fields both from code and XML layouts.

It’s time to run! I created simple layout and activity:

<?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">

    <com.criticalgnome.customviewdemo.AddressView
        android:id="@+id/address1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:zip_code="39531"
        app:country="USA"
        app:city="New York"
        app:line1="40, Sit Rd."
        app:line2="Ap #867-859"
        app:layout_constraintTop_toTopOf="parent"
        />

    <com.criticalgnome.customviewdemo.AddressView
        android:id="@+id/address2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/address1"
        />

</androidx.constraintlayout.widget.ConstraintLayout>
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        findViewById<AddressView>(R.id.address2).apply {
            zipCode = "39531"
            country = "USA"
            city = "New York"
            line1 = "40, Sit Rd."
            line2 = "Ap #867-859"
        }
    }
}

I have two AddressView on a screen. For first I set all data directly in XML layout, and for second one I set all data from activity on runtime.

As usual, all code placed on my GitHub. Enjoy!

Leave a reply

Your email address will not be published. Required fields are marked *

You may also like

More in:Android