About awscherb

Android guy

Fast Smooth Scroll to Top

A common UX pattern relating to lists is the ability to quickly jump to the top of the list. Whether it be tapping the toolbar, the tab you’re currently on, or some other UI element, this convenient pattern is seen almost everywhere there’s a list. If you’re using a RecyclerView, jumping straight to the top using scrollToPosition can be quite jarring and doesn’t feel smooth, and smoothScrollToPosition can take forever if you’re in a huge list.

One way to provide a smooth experience for your users is to use both methods. First, checking if the user is beyond a certain position and if so, jumping to a preset position with scrollToPosition, and then after that, calling smoothScrollToPosition(0). An example extension function for RecyclerViews might look like this:

fun RecyclerView.scrollToTop() {
    (layoutManager as? LinearLayoutManager)?.let { lm ->
        if (adapter?.itemCount ?: 0 > 10 && lm.findFirstVisibleItemPosition() > 10) {
            scrollToPosition(10)
        }
    }
    smoothScrollToPosition(0)
}

In this example, if the first visible item is greater than position 10, we jump to position 10, then smooth scroll to position zero. The end result is that the user sees a smooth scroll, regardless of where they are in the list, and the smooth scroll itself takes at most a short amount of time. Note that this assumes your layout manager is a LinearLayoutManager; you may have to use different visibility methods or behaviors if you’re using any different type.

CameraX Video UseCase Audio Quality

Although video recording is not yet officially supported in the CameraX library, you still can use the VideoCapture UseCase. There are plenty of StackOverflow and GitHub sample projects showing how to implement this, so I will not go over that here. What I will mention, however, is that none of the samples that I have seen so far address the audio quality.

When you are setting up your VideoCapture instance, you may have noticed VideoCaptureConfig has a couple audio methods. You may have also noticed that the default values for these will leave you with terrible audio quality, and that searching the web reveals nothing as to what you should use for these – no documentation from Google as it isn’t supported yet, and no samples I have seen so far touch on it. The relevant ones you should adjust will set the bit rate, sample rate, and audio channel count. Here’s an example:

    VideoCaptureConfig.Builder().apply {
        setAudioBitRate(320_000)
        setAudioSampleRate(44_100)
        setAudioChannelCount(2)

        /* setup other things here  */
    }

This will set a bitrate of 320bps, a sample rate of 44.1 kHz, and record in stereo. Your videos will sound much better!

Room Embedding @Embedded

Something I just discovered is that @Embedded​ fields in Room entities can themselves contain @Embedded fields. For example:

data class Post(
    @Embedded
    val media: Media
)

data class Media(
    @Embedded
    val metadata: Metadata
)

data class Metadata(
    val resolution: String
)

works as you would expect, with resolution being pulled up to the Post entity table. Not sure how deeply @Embedded you can go, or if there are any performance consequences to this, but a neat trick!

Kotlin Android Extensions with ViewHolders

Something I recently discovered is how easy it is to use Kotlin’s Android View extensions with RecyclerView ViewHolders. Previously using Butterknife, many of my ViewHolder classes were simply a bunch of @BindViews with an init { ButterKnife.bind } . With Kotlin Android extensions, the views are declared as extensions on the ViewHolder’s ItemView field. Additionally, instead of declaring a ViewHolder class with one line, you can actually just use an anonymous object in your onCreateViewHolder method.

You could potentially use any of Kotlin’s scoping extension functions, I prefer to use let, so here’s what that looks like (note also the this[...] notation) :

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
        object : RecyclerView.ViewHolder(
            context.inflate(R.layout.adapter_thread, parent)
        ) {}

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    val item = this[position]
    holder.itemView.let {
        it.viewIdTitle = item.title
        it.viewIdDescription = item.description
    }
}