Detecting clicks or scrolls in Kotlin for Android

Build a responsive UI that shows or hides the toolbar in response to user clicks whilst allowing undisturbed user scrolling.

Photo by Clément H on Unsplash

Kotlin has a fantastic ability to detect user gestures on Android systems. This is important for creating smooth, functional user interfaces that respond in a consistent and intuitive way. However, it can sometimes be difficult to differentiate between similar user actions. For example, when a user scrolls down a window of text this is detected as both a click and a scroll activity. This article will look at how to detect whether a user is clicking the screen, or whether they are scrolling, so that the toolbar can be toggled by user clicks but remain hidden whilst the user scrolls.

The screenshots above show the toolbar being displayed and hidden. We would like to toggle this behaviour in response to a user click, whilst allowing the user to scroll down the text without displaying the toolbar. To do this we need to follow a few steps:

  1. Set a listener to detect when a user is touching the ScrollView which contains the text
  2. Identify whether the user is clicking the screen or scrolling up/down
  3. If the user is clicking toggle the toolbar, otherwise, ignore the action

We are going to decompose this into two functions. Firstly a setHidingToolbar() function which detects the clicks or scrolls on a given a view. Secondly, a toggleToolbar() function which animates the showing and hiding of a given toolbar. These functions will be created in such a way that you can use them on multiple toolbars across your app.

setHidingToolbar()

fun setHidingToolbar(view: View, toolbar: Toolbar) {
view.setOnTouchListener { v, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
x = event.x
y
= event.y
}

MotionEvent.ACTION_UP -> {
if (abs(x - event.x) < SCROLL_THRESHOLD || abs(y - event.y) < SCROLL_THRESHOLD){
toggleToolbar(toolbar)
}
}
}
v.performClick()
v.onTouchEvent(event) ?: true
}
}

The code above takes a view, which is the UI component on which you would like to detect user clicks. In this case, it is a ScrollView. This function also takes as a parameter a toolbar so that we can generalise this function for multiple toolbars across your project.

First, we set an onTouchListener to this view so that whenever the user touches or interacts with this view our function will be called. When the user touches the screen, the MotionEvent.ACTION_DOWN will immediately be called, however at this stage we don’t know whether they are looking to scroll the screen or whether they are tapping it to display the toolbar.

There are two ways of resolving this problem. Either we could see if the user leaves their finger on the screen for a given period of time, or we could see if the user moves their finger across the screen. I have had more luck with the latter as different users touch the screen at different speeds and it can be hard to differentiate between short scrolls (perhaps to read the line above or below the current window) and genuine clicks.

So secondly, we need to determine how far across the screen a user has scrolled and see if that is far enough to count as a scroll or whether we should consider it a click. We store the location of the initial ACTION_DOWN in the form of x and y coordinates.

When the user lifts their finger off the screen, the ACTION_UP motion event will be called. We can then check to see if the user’s finger has moved more than our given tolerance threshold, in this case, the constant SCROLL_THRESHOLD which I have set to 10px. If the absolute distance travelled, abs(), in either the x or y direction is less than 10px between the user pressing their finger to the screen and lifting it off again then we detect a click and call our second function toggleToolbar().

The final piece of this function is to call performClick() and pass the remainder of the event handling to onTouchEvent(). The performClick() component is important for accessibility reasons.

toggleToolbar()

We would also like to generalise this function in order to be able to show or hide any given toolbar. As such we will need a way to keep track of whether any given toolbar is currently showing and then a method to animate the movement either onto or off the screen. This looks much nicer than simply calling .show() or .hide().

fun toggleToolbar(toolbar: Toolbar) {
if (showingTaskbar.getOrDefault(toolbar, false)) {

// Toggle tracker for displaying toolbar
showingTaskbar[toolbar] = false

// Extract the height of the action bar
val tv = TypedValue()
var actionBarHeight = 0
if (toolbar.context.theme.resolveAttribute(android.R.attr.actionBarSize, tv, true)) {
actionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data, toolbar.context.resources.displayMetrics)
}

// Initiate the animation
toolbar.animate().translationY(-actionBarHeight.toFloat()).setInterpolator(AccelerateInterpolator(2f)).start()
} else {
// Toggle tracker for displaying toolbar
showingTaskbar[toolbar] = true

// Initiate the animation
toolbar.animate().translationY(0f).setInterpolator(DecelerateInterpolator(2f)).start()
}
}

This function accepts a toolbar as a parameter and keeps track of its toolbars, and their current visibility, in a HashMap. We are hiding all our toolbars by default so we first check to see if our HashMap has a value for the toolbar and if not we know it is currently hidden and take action to show the toolbar. This moves the toolbar from above the top of the screen down into its usual position and sets its value in the HashMap.

When the function is called again, we check our HashMap for a value and know that the toolbar is currently displaying. We then find the size of the Toolbar using the resolveAttribute function. For this to work, your toolbars must be sized with the XML android:layout_height=”?attr/actionBarSize”.

We then move the toolbar off-screen by the negative of its size, meaning it is sitting just above the visible screen.

This is all animated by the inbuilt animate() function using translationY() which simply corresponds to a movement in the Y (vertical) direction. The Interpolators we are using make speed up the movement as the toolbar disappears and slow it down as the toolbar reappears. This looks better than a simple, linear movement in one direction or another.

This provides all the functionality we need to detect whether a user is clicking the screen or performing some other action and then toggling the toolbar in response. The final thing to do is to call the function with:

setHidingToolbar(scrollView as View, findViewById(R.id.my_toolbar))

and watch it work! The full code is included below for your reference.

Full Code

The full code

Conclusion

It is easy to detect user actions within Android using Kotlin and the granular actions than can be captured can be assessed to delineate different user intentions. In this case, we have detected when a user is tapping the screen, rather than scrolling or highlighting, and used this to toggle the toolbar in an animated way.

You can follow me on Twitter @mgrint or read more of my articles at https://grint.tech. Email me at matt@grint.tech.

Markets and Technology

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store