Tristan Elliott
Posted on June 15, 2022
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
References
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 what `defStyleAttr: 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 the `RelativeLayout` class
### 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:
- 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 set `isClickable = 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. The `getChildAt(4)` might seem a little strange and that's because it is, and it is specific to my codebase. As you can see [HERE](https://github.com/thePlebDev/Calf-Tracker-2/blob/master/app/src/main/res/layout/indiv_calf.xml#L56) the view that we want to hide, is the fourth child of our [RelativeLayout](https://github.com/thePlebDev/Calf-Tracker-2/blob/master/app/src/main/res/layout/indiv_calf.xml#L9). 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 the `isVisibility` attribute on the View and using the utility function `visibilityCheck()` 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 called `attrs.xml`. Inside that file paste this in:
- 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 of `childToCollapse` 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 an `AttributeSet`(remember from earlier), which is handed to our `ExpandOnClickView` 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 our `ExpandOnClickView` class add the following code using the `withStyledAttributes` extension function:
init {
isClickable = true
context.withStyledAttributes(attrs, R.styleable.ExpandOnClickView){
childToCollapse = getInt(R.styleable.ExpandOnClickView_childToCollapse,0)
}
}
- As you can see [HERE](https://github.com/thePlebDev/Calf-Tracker-2/blob/master/app/src/main/res/layout/indiv_calf.xml#L13) 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](https://developer.android.com/training/transitions) 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](https://twitter.com/AndroidTristan).
Posted on June 15, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
February 15, 2024
January 29, 2024