When creating UIs it’s a common task to build your own set of reusable UI Components, if you dont want to rely on the built-in set of the framework.
I was very curious how this is done in native Android app-development and how this fancy new language Kotlin works in this context.
A quick search brought me to this excellent tutorial from Eley “Building Custom Component with Kotlin”.
So here is the goal:
Create a reusable component which contains a label, an editable textline and a switch, formatted as you can see in the picture below (original picture slightly adjusted).
I will show the lines of code you need to achieve this goal with Native Android + Kotlin on the one hand and Android -styled QML on the other hand.
Version 1: Native Android with Kotlin
For a detailed description please check the original source.
First you need a layout xml file which contains the needed TextView as Title, an EditView a Switch and a Layout to place everything properly:
<?xml version="1.0" encoding="utf-8"?> <merge xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/my_title" style="@style/custom_component_title" android:layout_width="match_parent" android:layout_height="wrap_content" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <EditText android:id="@+id/my_edit" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" /> <android.support.v7.widget.SwitchCompat android:id="@+id/my_switch" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="end|center" android:layout_marginTop="6dp" /> </LinearLayout> </merge>
Next we need a class file, written in Kotlin to initiate the visual elements an bring them to live:
package com.elyeproj.myapplication import android.content.Context import android.util.AttributeSet import android.view.LayoutInflater import android.widget.LinearLayout import kotlinx.android.synthetic.main.view_custom_component.view.* class CustomComponent @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyle: Int = 0, defStyleRes: Int = 0 ) : LinearLayout(context, attrs, defStyle, defStyleRes) { init { LayoutInflater.from(context).inflate(R.layout.view_custom_component, this, true) orientation = VERTICAL attrs?.let { val typedArray = context.obtainStyledAttributes(it, R.styleable.custom_component_attributes, 0, 0) val title = resources.getText(typedArray .getResourceId(R.styleable.custom_component_attributes_custom_component_title, R.string.component_one)) my_title.text = title my_edit.hint = "${resources.getString(R.string.hint_text)} $title" typedArray.recycle() } } }
To expose attributes of the component we need a third file, called attrs.xml which is used inside the class constructor.
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="custom_component_attributes"> <attr name="custom_component_title" format="reference" /> </declare-styleable> </resources>
Now we are ready to use it in a layout file:
<com.elyeproj.myapplication.CustomComponent android:layout_width="match_parent" android:layout_height="wrap_content" app:custom_component_title="@string/component_one" />
Version 2: QML
The entire component consists of the following singe QML file, called “CustomComponent.qml”.
import QtQuick 2.9 import QtQuick.Controls 2.2 Column { id: componentRoot property alias labelText: label.text property alias editText: textField.text property alias checked: switchButton.checked signal switchClicked(bool checked) width: parent.width -6 height: 60 x: 6 Label { id: label text: "Custom Component" font.pixelSize: 16 font.weight: Font.DemiBold opacity: 0.8 } Item { width: parent.width height: textField.height TextField { id: textField placeholderText: "Type something for " + label.text anchors.left: parent.left anchors.right: switchButton.left } Switch { id: switchButton anchors.right: parent.right onClicked: componentRoot.switchClicked(checked) } } }
The root class of the component is a Column to layout the content vertically. It contains the Label and a second Item with contains a TextField and a Switch for which I chose the anchors system to set the layout. Three properties of content-controls are exposed easily by using property alias.
Qt creates automatically “on<property>Changed” signals (=events) to make these properties observable from outside. In this case onLabelTextChanged, onEditTextChanged and onCheckedChanged. In addition I have added a new signal (=event) called switchClicked() which is triggered when the user clicks on the switch. This helps to seperate user-inputs from programmatically changes.
The component can be used instantly when it is in the same directory, the filename “CustomComponent” is the class-name. This is the complete code of the test-application:
import QtQuick 2.9 import QtQuick.Controls 2.2 ApplicationWindow { id: app visible: true width: 400 height: 620 header: ToolBar { Label { text: "My Application" x: 14 anchors.verticalCenter: parent.verticalCenter font.pixelSize: 16 font.weight: Font.DemiBold } } Page { anchors.fill: parent Column { y: 6 width: parent.width CustomComponent { labelText: "Custom Component 1" onEditTextChanged: console.log("User typed: " + editText) } CustomComponent { labelText: "Custom Component 2" checked: true onCheckedChanged: console.log("checked changed to: " + checked) } CustomComponent { labelText: "Custom Component 3" onSwitchClicked: console.log("User changed checked to: " + checked) } } } }
When you run the code you are getting this result:
Result
Native Android with Kotlin
- We have a total of three files (located in different directories)
- The code has a total of 96 lines and 1813 characters (without space)
QML Version
- The complete component resides in one file
- The code has a total of 41 lines and 594 characters (without space)
The QML version is less than 1/3 ! of the native Android/Kotlin version and offers more functionality. And it is fully reusable for IOS and Windows Applications as well, the style changes automatically. Even more important: The complete code is readable and understandable with a glance. There is no need to check different files.