Introduction
- This series is not going to be in any particular order, so feel free to read whatever blog post you want. Anytime I find something that I think could use a blog post, I will write one and put it here
Github for code
Who is this tutorial for?
- This tutorial is meant for someone who already has a Room database and a RecyclerView set up but wants to be able to click on an individual item within the RecyclerView and have it expand and or shrink.
What we are building?
- In the end we are going to create a custom reusable View that will allow us to click and either shrink or expand our view, like so:
- Is it pretty? No. Animations? No. Does it work? YES!!
Why do this?
- Because that's what the grown folks do!!! HERE is the Epoxy library which is AirBnb's open source custom view library for complex RecyclerViews
- Of course ours won't be that complex or pretty.... yet
Custom View?
- That's right no more hacky boiler plate code to write over and over again. Instead we are going to implement a custom View and make the hacky code reusable!!!
Understanding Custom Views
- So we all know that a View is a basic building block for our app's UI. Things like Buttons and Layouts are all subclasses that extend the View class.
- It is important to remember that anytime we create a custom view, it will inherit the looks and behaviour of its parent and we can override the behaviour or aspect of the appearance we want to change. This is an important thing to remember because we are going to inherit from the RelativeLayout class and override its
performClick()
method.
Creating our very own Custom View
- So to implement a custom RelativeLayout we have to create a new Kotlin file called
ExpandOnClickView
and implement this constructor:
class ExpandOnClickView @JvmOverloads constructor(
context: Context,
attrs:AttributeSet? = null,
defStyleAttr: Int =0
) : RelativeLayout(context,attrs,defStyleAttr) {
}
- Confusing, right? Sure is!!
- This constructor is used to inflate the XML file and apply all the specific styles that are necessary.
context: Context
is essentially an instance of our current running android app. 'AttributeSet' is a collection of attributes that are associated with the element in the XML file. This is important because we will be defining our own attributes later on. Now I could not find good documentation on whatdefStyleAttr: Int =0
is used for but I think we can all assume it has something to do with the styles and the=0
means no styles. - All these values get passed into our
ExpandOnClickView
class at runtime, which are then passed on to theRelativeLayout
class
- This constructor is used to inflate the XML file and apply all the specific styles that are necessary.
What is @JvmOverloads ?
- Now lets talk about this weird annotation, what is it and why we are using it. So since Java doesn't have the concept of default parameter values, we have to specify all the parameter values from Java and we want to make it easier for Java callers, we can annotate it with
@JvmOverloads
. what this means is that it will tell the compiler to create 3 overloaded constructors to aid with Java calls. But we are using Kotlin, what's up with this Java stuff. Well, if you actually try to inflate this View without the@JvmOverloads
annotation. The app will crash and you can see in the error tab of logcat that under the hood. Android is still calling Java APIs. Now I am not 100% sure why but just know that we need@JvmOverloads
Placing the Custom View
- Technically speaking we have created a custom View that will work. It can go inside a XML file like any other. Yours will look different that mine due to the package names but if the class name is the same it will look roughly like this:
<com.elliottsoftware.ecalvingtracker.customViews.ExpandOnClickView>
</com.elliottsoftware.ecalvingtracker.customViews.ExpandOnClickView>
- Then whatever we want to disappear and appear we will put in between our View
Adding clicking functionality
- Heading back to our Kotlin file we need to do three things to make the our View clickable
1) isClickable = true : we need to set the isClickable property to true. This will enable out custom View to respond to clicks
2) Override performClick() method : this is where we perform the operations we want when the View is clicked
3) Call invalidate() method : this tells the Android system to call onDraw() method to redraw the view.
isClickable = true
- Inside the
init
block of the class we need to setisClickable = true
, like so:
init {
isClickable = true
}
Implement performClick() method
- Now we can override the RelativeLayout view's performClick() method to implement our own logic, like this:
override fun performClick(): Boolean {
if (super.performClick()) return true
getChildAt(4).isVisible = visibilityCheck(getChildAt(4).isVisible)
invalidate()
return true
}
- The
if (super.performClick()) return true
is called to ensure that the original function still works. ThegetChildAt(4)
might seem a little strange and that's because it is, and it is specific to my codebase. As you can see HERE the view that we want to hide, is the fourth child of our RelativeLayout. Now this is very bad practice and it makes the code very spaghetti(its already spaghetti enough). Later we will look at a way to make this less spaghetti..isVisible = visibilityCheck(getChildAt(4).isVisible)
is us accessing theisVisibility
attribute on the View and using the utility functionvisibilityCheck()
to set it to the opposite of what it already is. Create a private utility function called visibilityCheck() inside the ExpandOnClickView class:
private fun visibilityCheck(isVisible:Boolean):Boolean{
return !isVisible
}
The
!
makes sure that we return the opposite of what it already is.With that our custom
ExpandOnClickView
View should work. Now lets learn how to make our own attributes and make the code more reusable.
Custom Attributes
- Now we are going to implement custom attributes for our custom View. So inside the
res/values
package create a new file calledattrs.xml
. Inside that file paste this in:
<resources>
<declare-styleable name="ExpandOnClickView">
<attr name="childToCollapse" format="integer"/>
</declare-styleable>
</resources>
- The
name="ExpandOnClickView"
is how we will reference these custom attributes later in our class.name="childToCollapse"
is the name we will use inside the XML file.format="integer"
is what the value ofchildToCollapse
will be replaced with
Using the custom attributes
Now in order to use the custom attributes inside
ExpandOnClickView
we need to retrieve them. They are stored in anAttributeSet
(remember from earlier), which is handed to ourExpandOnClickView
upon creation.Next, above our
init{}
block lets define a variable to hold our attribute.
private var childToCollapse: Int = 0
- So inside the
init{}
block of ourExpandOnClickView
class add the following code using thewithStyledAttributes
extension function:
init {
isClickable = true
context.withStyledAttributes(attrs, R.styleable.ExpandOnClickView){
childToCollapse = getInt(R.styleable.ExpandOnClickView_childToCollapse,0)
}
}
- As you can see HERE we can use our custom attribute just like a normal attribute.
Adding animations
- Now I not 100% on how to do this yet. However, I think that THIS would be a great starting place
Conclusion
- Thank you for taking the time out of your day to read this blog post of mine. If you have any questions or concerns please comment below or reach out to me on Twitter.
Oldest comments (0)