https://github.com/enyason/custom_view
Repository
https://github.com/enyason/custom_view
In the first part of this tutorial series, we considered a custom view, what it means and how to implement it on android. We started off by extending a simple text view that displayed a custom icon and a text. For this second part of the series, we are going to implement a compound control.
To delve further into compound control, we are going to implement a ListTile. The list tile will have 3 views: A trailing icon, A leading icon, and a title. see image below for clarity.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
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/trailing"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_margin="5dp" />
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toRightOf="@id/trailing"
android:textSize="18sp" />
<ImageView
android:id="@+id/leading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_margin="5dp" />
</RelativeLayout>
This XML code creates our ListTile layout as shown below. The image views for displaying the trailing and leading icons
This attributes will be tied up to the listTile view so it can be referenced from XML
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ListTile">
<attr name="title" format="string" />
<attr name="leading" format="reference" />
<attr name="trailing" format="reference" />
</declare-styleable>
</resources>
We add the required attributes in the declare-styleable tag using the attr tag. The name property of the attr tag defines a label for the attribute while the format property defines the Type of the attribute. String format defines a Type for text while reference defines a Type for resource files e.g a drawable. Note: The name property of the declare-styleable tag should have a value equal to the custom view class name created.
public class ListTile extends RelativeLayout {
//define android views
private ImageView trailing,leading;
private TextView title;
public ListTile(Context context) {
super(context);
init(context,null);
}
public ListTile(Context context, AttributeSet attrs) {
super(context, attrs);
init(context,attrs);
}
public ListTile(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context,attrs);
}
void init(Context context,AttributeSet attrs) {
Drawable leadingIcon,trailingIcon;
String text;
//check for if AttributeSet is null
if (attrs ==null) {
return;
}
//get attributes from attrs.xml into array
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ListTile);
//extract attributes from array into the variables
leadingIcon = typedArray.getDrawable(R.styleable.ListTile_leading);
trailingIcon = typedArray.getDrawable(R.styleable.ListTile_trailing);
text = typedArray.getString(R.styleable.ListTile_title);
//inflate layout
LayoutInflater inflater = LayoutInflater.from(getContext());
View tile= inflater.inflate(R.layout.list_tile,this);
tile.setBackground(getResources().getDrawable(R.drawable.background));
//find views
trailing = findViewById(R.id.trailing);
leading = findViewById(R.id.leading);
title = findViewById(R.id.title);
//set view properties
trailing.setImageDrawable(trailingIcon);
leading.setImageDrawable(leadingIcon);
title.setText(text);
invalidate(); // redraw ui
}
}
The ListTile extends a RelativeLayout and overrides 3 constructors, passing the current context and attributes to the init function
We get the attributes defined in our attrs.xml file and store in a TypedArray. A Typed array is a Container for an array of values that were retrieved from XML.
We inflate the layout we defined for our custom view that's list_tile.xml. This is done using the layout inflater. This is similar to when we call setContentView in onCreate method of an Activity
We call setBackground on the tile view and pass a drawable from XML to it. This drawable marks the Tile border with a specified color. see code snippet for code
<?xml version="1.0" encoding="utf-8"?>
<shape
android:shape="rectangle"
xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#fff"/>
<stroke android:width="1dp" android:color="#cacaca"/>
</shape>
Get a reference to the views defined in the list_tile.xml
set view properties from the attributes specified in XML
call invalidate();. This method call will redraw the UI to reflect changes made in XML immediately
With our ListTile ready, we can use it now in our XML.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
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"
android:orientation="vertical"
tools:context=".MainActivity">
<com.enyason.custom_view_1.ListTile
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:leading="@drawable/ic_navigate_next"
app:title="User profile"
app:trailing="@drawable/ic_person" />
</LinearLayout>
The code snippet above shows the use of the custom view created in the activity_main.xml file. Running the app gives the output show in the image below.
<merge/> is useful because it can get rid of unneeded ViewGroups. In our list_tile.xml file, we have the root element as RelativeLayout which is inflated in the Listile.java class. With this class extended the Relative layout, we now have a duplicate view group. To see this duplication we'll use the Android Layout Inspector
To get rid of the unneeded view group, use the merge tag in list_tile.xml as shown in the code snippet below
<?xml version="1.0" encoding="utf-8"?>
<merge
xmlns:android="http://schemas.android.com/apk/res/android"
>
<ImageView
android:id="@+id/trailing"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_margin="5dp" />
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toRightOf="@id/trailing"
android:textSize="18sp" />
<ImageView
android:id="@+id/leading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_margin="5dp" />
</merge>
With the Listile created we should be able to make screens such as one below easily
The complete source code can be found on gitHub https://github.com/enyason/custom_view