CodeNewbie Community 🌱

Cover image for Tap to expand RecyclerView items with Android and Kotlin
Tristan
Tristan

Posted on

Tap to expand RecyclerView items with Android and Kotlin

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:

All views expanded

All views collapses

  • 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) {
}

Enter fullscreen mode Exit fullscreen mode
  • 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:
<com.elliottsoftware.ecalvingtracker.customViews.ExpandOnClickView>  


</com.elliottsoftware.ecalvingtracker.customViews.ExpandOnClickView>

Enter fullscreen mode Exit fullscreen mode
  • 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
}

Enter fullscreen mode Exit fullscreen mode

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
    }

Enter fullscreen mode Exit fullscreen mode
  • 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 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 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
    }
Enter fullscreen mode Exit fullscreen mode
  • 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:
<resources>
    <declare-styleable name="ExpandOnClickView">
        <attr name="childToCollapse" format="integer"/>
    </declare-styleable>
</resources>

Enter fullscreen mode Exit fullscreen mode
  • 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
Enter fullscreen mode Exit fullscreen mode
  • 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)
        }
    }

Enter fullscreen mode Exit fullscreen mode
  • 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)