How we use custom views
We use custom views in the Azimo app for a variety of reasons. Sometimes we use them to group a set of views with interdependent states. For example, we have a RatingViewer that contains thumbs up/thumbs down buttons, the initial state of which is unset. When a user clicks one of the buttons, the other one switches state to false. This standardised piece of code allows us to reuse rating buttons that perform predictably no matter where they are placed in our app.
We also use custom views to log events when a user clicks a button or a group of views. We extend some of the Android framework classes and add a new attribute to them called az_analytics_label. Then, during the view initialisation, we read the value of this attribute and save it. Then we override the performClick() method to send analytics with the given label.
This is a simple, reusable way to handle analytics code. We also added lint checks to make sure that framework classes without analytics are never used in the app, and we log everything by default.
More about building analytics code for your views can be found in this blog post:
Gradient background in the style of Facebook Messenger
Below I will explain how we used custom views in my latest project, DynamicGradientRecyclerView! The idea was to have a static common background gradient for a set of RecyclerView items that doesn’t change while scrolling (similar to Facebook Messenger). This is the effect that we would like to achieve:
Custom view attributes
I would like to easily specify two major colours that will appear at the top and the bottom of the screen. The colours will be a base for the gradient. Let’s allow users of DynamicGradientRecylcerView to declare them as a view attribute. First we need to add daclare-styteable attribute in attr.xml with names and types of new attributes. As you can see below I declared two new attributes: topGradientColor and bottomGradientColor, both of reference|color type (color is a field type and reference allows to reference another resource as an attribute).
Now you can read and save the attributes from .xml in local final fields during the view class initialisation in init code block. The code block is executed during the initialisation of a view object. In my case I save those values in PositionedColorGenerator because I won’t use them separately anyway.
Our goal is to have the RecyclerView item background (or an element of the item) coloured with a gradient depending on the position of the screen. It means that we need to react and perform some action when a user scrolls items. It’s quite easy, I just have to add an OnScrollListener to the RecyclerView and run my colouring method every time a user scrolls the view, but what’s more I need to colour items when they’re updated. To do this I need to register AdapterDataObserver and override the onChanged method with my colouring function. All the logic described above is a part of the initialise method of DynamicGradientRecyclerView, so when you can easily use while configuring the view. I also added the gradientId parameter to pass the id of a view that is to be coloured.
Now, we need to somehow decide which of the views should be coloured with a gradient. The idea here is to store those views in the custom view somehow, because we don’t want to calculate them on every scroll event. I created a map with RecyclerView item view as a key and its child view that needs to be coloured as a value. I fulfil and clear the map on onViewAdded() and onViewRemoved() methods. With those two methods I’m able to have a set of items already displayed on the screen. The first method also filters out all of the items that don’t have a view with given gradientId.
So what does the mysterious colorItemsBackgrounds method do? It iterates over the views that should be coloured and takes its vertical top and bottom position to calculate colours based on the values. You must remember to cut off all the partially displayed items at the top and the bottom of the screen. In my case the getScreenYPercentagePosition() takes care of this. It returns 1 when the item is above the screen and 0 when is below the screen. The calculated colours become the top and bottom colours of a gradient item background. ColorItemsBackgrounds function also forms the shape of the view using cornerRadii() function.
It’s done! Now we can easily use it in several places in our app with different border colours and coloured items. If you like the idea of creating a positioned gradient background on your recycler view item elements, you can find all the source code with a usage example on the azimolabs github page