Kotlin

KTX: Views

KTX is a series of Kotlin extension functions for Android that first appeared in February 2018. They can simplify many repetitive tasks or those which require boilerplate code. However, they are not always easy to discover. I find it useful to periodically scan through the list here. I find this a good way of discovering extensions that I wasn’t aware of. So in this post, we’ll look at some that I find particularly useful.

ViewTreeObserver

Often we may need to perform operations after the layout pass has completed. For example, if we need to start an animation based on values determined from the position of views within the layout. In such cases, we can use an OnPreDrawListener which will be triggered after the layout has completed, but before the frame is drawn. We need to do something like this:

with(binding.root) {
    viewTreeObserver.addOnPreDrawListener(
        MyOnPreDrawListener(viewTreeObserver)
    )
}
.
.
.
private class MyOnPreDrawListener(private val observer: ViewTreeObserver) :
    ViewTreeObserver.OnPreDrawListener {
    override fun onPreDraw(): Boolean {
        observer.removeOnPreDrawListener(this)
        // Do something
        return true
    }
}

With KTX we can replace this by using the doOnPreDraw() extension function:

binding.root.doOnPreDraw { 
    // Do Something
}

This is a whole lot simpler. It does not give us quite the same behaviour, though. The verbose form requires us to return a boolean which controls whether the draw will continue. The KTX extension does not allow us to cancel the draw – it always allows it to continue. But that is the only case where we might need to revert to the longer form.

There are several other useful callback extensions such as doOnAttach(), doOnDetach(), doOnLayout().

LayoutParams

Another thing we often need to do with Views is to update their layout params programmatically. Typically we may need to do something like this:

binding.root.layoutParams = binding.root.layoutParams.apply { 
    width  = newWidth
    height = newHeight
}

We must first get the layout params from the view, then modify them. Finally, we must set them back on the view for them to take effect. It is easy to forget this final step. I have had many a head-scratching moment when I forgot to do this.

However, with the KTX updateLayoutParams() extension this is done for us:

binding.root.updateLayoutParams { 
    width = newWidth
    height = newHeight
}

PostDelayed

Sometimes we need to delay an operation, and we can use postDelayed() to do that. However, this particular API was designed long before Kotlin arrived, and the argument ordering is not Kotlin-friendly. It takes two arguments: The first is a Runnable instance, and the second is a long which controls the delay. The Runnable can be replaced by a lambda because it is a SAM interface, but the code is still a little awkward:

binding.root.postDelayed({
        //Do Something 
    },
    1000L
)

The KTX version does nothing more that swaps the order of these two arguments putting the lambda last. That enables us to use a trailing lambda which is much nicer:

binding.root.postDelayed(1000L) {
    // Do Something
}

Draw View To Bitmap

Sometimes it can be useful to render a view as a Bitmap. Sometimes this can be necessary for animation. A scaling animation is much more efficient on a Bitmap than if we try and change the layout between each frame of the animation.

Another case where we might want to capture a View to a Bitmap is in our tests where we might want to capture a specific view for comparison purposes.

This usually requires a bit of boilerplate:

val bitmap: Bitmap = binding.root.let { view ->
    val bm = Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888)
    val canvas = Canvas(bm)
    view.draw(canvas)
    bm
}

The KTX drawToBitmap() extension makes this a whole lot easier:

val bitmap: Bitmap = binding.root.drawToBitmap()

ViewGroup

There are also some useful extensions on ViewGroup – mainly around the child views of the ViewGroup. Before I discovered these extensions, I use to do stuff like this:

val children: List = binding.root.let { viewGroup ->
    (0 until viewGroup.childCount).map { index ->
        viewGroup.get(index) 
    }
}

But KTX simplifies that:

val children: Sequence = binding.root.children

There are several other useful extensions. isEmpty() and isNotEmpty() give some commonly used checks. We can iterate through the child views using either iterator() of the more Kotlin like forEach() and forEachIndexed().

There are also some nice operators which allow us to add and remove views, and also retrieve the view at a specific index:

binding.root += myView
binding.root -= myView
binding.root[0]

TextView

There are also some extensions which are specific to TextView which simplify handling text changes. It’s fairly common to have to do stuff like this:

binding.text1.addTextChangedListener(object : TextWatcher {
    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
        // We don't care about this
    }

    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
        // Do Something
    }

    override fun afterTextChanged(s: Editable?) {
        // We don't care about this
    }

})

We’re only interested in performing operations in one of the callbacks, but we have to implement no-op version of the other callbacks defined in TextWatcher.

KTS provides some simple overrides so that we can implement just the one we want:

binding.text1.doOnTextChanged { text, start, before, count ->
    // Do Something
}

There is also an addTextChangedListener() extension which accepts lambdas for each of the three methods, but with no-op default implementations of each. So if we need to do stuff in multiple callbacks, then we can use this extension instead.

Conclusion

The KTX extensions that we’ve looked at here do nothing that we can’t do with a little bit of work, but they do make life much easier. They also simplify our code, making it easier to read, understand, review, and therefore maintain.

I usually publish a GitHub repo containing the working code for my blog posts. However, in this case, the individual code snippets pretty much stand up on their own, so I haven’t published a repo in this case.

© 2020, Mark Allison. All rights reserved.

Copyright © 2020 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.