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!