RecyclerView custom Thumb Size & Length

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:bottom="8dp"
android:left="8dp"
android:right="8dp"
android:top="8dp">
<shape>
<solid android:color="@color/scroll_bar_color" />
<stroke
android:width="3dp"
android:color="@android:color/transparent" />
<size android:width="4dp"
android:height="4dp" />
</shape>
</item>
</layer-list>
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:bottom="8dp"
android:left="8dp"
android:right="8dp"
android:top="8dp">
<shape android:shape="rectangle">
<solid android:color="@color/scroll_bar_color" />
<corners android:radius="4dp" />
</shape>
</item>
</layer-list>
<style name="scrollbar_style">
<item name="android:scrollbarAlwaysDrawVerticalTrack">true</item>
<item name="android:scrollbars">vertical</item>
<item name="android:fadeScrollbars">false</item>
<item name="android:scrollbarThumbVertical">@drawable/thumb_shape</item>
<item name="android:scrollbarTrackVertical">@drawable/scrollbar_track</item>
</style>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
style="@style/scrollbar_style" />
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CustomThumbSizeRecyclerView">
<attr name="thumbLength" format="dimension"/>
</declare-styleable>

</resources>
class CustomThumbSizeRecyclerView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : RecyclerView(context, attrs, defStyleAttr), ScrollingView {

var thumbLength: Int
private var trackLength = -1
private var totalLength = -1

init {
val typedArray =
context.obtainStyledAttributes(attrs, R.styleable.CustomThumbSizeRecyclerView)
thumbLength = dpToPx(
typedArray.getDimension(
R.styleable.CustomThumbSizeRecyclerView_thumbLength,
64F
).toInt()
)
typedArray.recycle()
}
override fun computeHorizontalScrollRange(): Int {
if (trackLength == -1) {
trackLength = this.measuredWidth
}
return trackLength
}

override fun computeHorizontalScrollOffset(): Int {
getWidths()
val highestVisiblePixel = super.computeHorizontalScrollOffset()
return computeScrollOffset(highestVisiblePixel)
}

override fun computeHorizontalScrollExtent(): Int {
return thumbLength
}
override fun computeVerticalScrollRange(): Int {
if (trackLength == -1) {
trackLength = this.measuredHeight
}
return trackLength
}

override fun computeVerticalScrollOffset(): Int {
getHeights()
val highestVisiblePixel = super.computeVerticalScrollOffset()

return computeScrollOffset(highestVisiblePixel)
}

private fun computeScrollOffset(highestVisiblePixel: Int): Int {
val invisiblePartOfRecyclerView: Int = totalLength - trackLength
val scrollAmountRemaining = invisiblePartOfRecyclerView - highestVisiblePixel
return when {
invisiblePartOfRecyclerView == scrollAmountRemaining -> {
0
}
scrollAmountRemaining > 0 -> {
((trackLength - thumbLength) / (invisiblePartOfRecyclerView.toFloat() / highestVisiblePixel)).roundToInt()
}
else -> {
trackLength - thumbLength
}
}
}

override fun computeVerticalScrollExtent(): Int {
return thumbLength
}

private fun getHeights() {
if (totalLength == -1) {
val rec = this.measuredHeight
trackLength = rec
measure(
MeasureSpec.makeMeasureSpec(this.measuredWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
)
totalLength = this.measuredHeight
measure(
MeasureSpec.makeMeasureSpec(this.measuredWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(rec, MeasureSpec.AT_MOST)
)
}
}
private fun getWidths() {
if (totalLength == -1) {
val rec = this.measuredWidth
trackLength = rec
measure(
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
MeasureSpec.makeMeasureSpec(this.measuredHeight, MeasureSpec.EXACTLY)
)
totalLength = this.measuredWidth
measure(
MeasureSpec.makeMeasureSpec(rec, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(this.measuredHeight, MeasureSpec.EXACTLY)
)
}
}
fun dpToPx(dp: Int): Int {
return (dp * resources.displayMetrics.density).roundToInt()
}
}
class CustomThumbSizeRecyclerView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : RecyclerView(context, attrs, defStyleAttr), ScrollingView {
var thumbLength: Int
private var trackLength = -1
private var totalLength = -1
init {
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomThumbSizeRecyclerView)
thumbLength = dpToPx(
typedArray.getDimension(
R.styleable.CustomThumbSizeRecyclerView_thumbLength,
64F
).toInt()
)
typedArray.recycle()
}
override fun computeVerticalScrollRange(): Int {
if (trackLength == -1) {
trackLength = this.measuredHeight
}
return trackLength
}
override fun computeVerticalScrollOffset(): Int {
getHeights()
val highestVisiblePixel = super.computeVerticalScrollOffset()

return computeScrollOffset(highestVisiblePixel)
}
override fun computeVerticalScrollOffset(): Int {
getHeights()
val highestVisiblePixel = super.computeVerticalScrollOffset()

return computeScrollOffset(highestVisiblePixel)
}
private fun computeScrollOffset(highestVisiblePixel: Int): Int {
val invisiblePartOfRecyclerView: Int = totalLength - trackLength
val scrollAmountRemaining = invisiblePartOfRecyclerView - highestVisiblePixel
return when {
invisiblePartOfRecyclerView == scrollAmountRemaining -> {
0
}
scrollAmountRemaining > 0 -> {
((trackLength - thumbLength) / (invisiblePartOfRecyclerView.toFloat() / highestVisiblePixel)).roundToInt()
}
else -> {
trackLength - thumbLength
}
}
}
private fun getHeights() {
if (totalLength == -1) {
val normalHeight = this.measuredHeight
trackLength = normalHeight
measure(
MeasureSpec.makeMeasureSpec(this.measuredWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
)
totalLength = this.measuredHeight
measure(
MeasureSpec.makeMeasureSpec(this.measuredWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(normalHeight, MeasureSpec.AT_MOST)
)
}
}
<com.example.something.CustomThumbSizeRecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
style="@style/scrollbar_style"
app:thumbLength="20dp" />
val recyclerView  = findViewById<CustomThumbSizeRecyclerView>(R.id.recyclerView)(or)
private lateinit var recyclerView: CustomThumbSizeRecyclerView
(& this inside onCreate(), or some place similar)
recyclerView = findViewById(R.id.recyclerView)

--

--

--

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Essential requirements for Android developers Part 01

Android 13 Leak Reveals New Features that Google is Working on

OTP View in Android (Easy)

How to set up Android Studio when working in an enterprise with proxies

Android’s Activity and it’s life-cycle

Coordinator Layout

How to Expose HUAWEI Push Kit features in Xamarin.Forms

Implement Dagger HILT Dependency Injection in Android Login Activity with Kotlin — Part 1

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
CoffeeCode

CoffeeCode

More from Medium

Caffeine cache simplified in Kotlin

Why Android developer ❤ to use Kotlin as a main language besides Java?

Introduction to Kotlin Functions

Working with enum constants