Skip to content

Commit

Permalink
Support setting color of activated indicator
Browse files Browse the repository at this point in the history
  • Loading branch information
DSteve595 committed Oct 31, 2019
1 parent 1fc3137 commit de91777
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,31 @@ class FastScrollerView @JvmOverloads constructor(
defStyleRes
) {

var iconColor: ColorStateList? by onUpdate(::bindItemIndicatorViews)
var textAppearanceRes: Int by onUpdate(::bindItemIndicatorViews)
var textColor: ColorStateList? by onUpdate(::bindItemIndicatorViews)
var textPadding: Float by onUpdate(::bindItemIndicatorViews)
var iconColor: ColorStateList? = null
set(value) {
field = value
pressedIconColor = value?.getColorForState(intArrayOf(android.R.attr.state_activated))
bindItemIndicatorViews()
}
var textAppearanceRes: Int = 0
set(value) {
field = value
bindItemIndicatorViews()
}
var textColor: ColorStateList? = null
set(value) {
field = value
pressedTextColor = value?.getColorForState(intArrayOf(android.R.attr.state_activated))
bindItemIndicatorViews()
}
var textPadding: Float = 0f
set(value) {
field = value
bindItemIndicatorViews()
}

private var pressedIconColor: Int? = null
private var pressedTextColor: Int? = null

internal var itemIndicatorsBuilder: ItemIndicatorsBuilder = ItemIndicatorsBuilder()

Expand Down Expand Up @@ -79,7 +100,7 @@ class FastScrollerView @JvmOverloads constructor(
* The function will be called when building the list of indicators, which happens after the
* RecyclerView's adapter's data changes. It will be called on the UI thread.
*/
var showIndicator: ((FastScrollItemIndicator, Int, Int) -> Boolean)? by onUpdate {
var showIndicator: ((FastScrollItemIndicator, Int, Int) -> Boolean)? by onUpdate { _ ->
postUpdateItemIndicators()
}

Expand Down Expand Up @@ -235,7 +256,7 @@ class FastScrollerView @JvmOverloads constructor(
}

// Optimize the views by batching adjacent text indicators into a single TextView
val viewCreators = ArrayList<() -> View>()
val views = ArrayList<View>()
itemIndicators.run {
var index = 0
while (index <= lastIndex) {
Expand All @@ -244,12 +265,12 @@ class FastScrollerView @JvmOverloads constructor(
.takeWhile { it is FastScrollItemIndicator.Text }
as List<FastScrollItemIndicator.Text>
if (textIndicatorsBatch.isNotEmpty()) {
viewCreators.add { createTextView(textIndicatorsBatch) }
views.add(createTextView(textIndicatorsBatch))
index += textIndicatorsBatch.size
} else {
when (val indicator = this[index]) {
is FastScrollItemIndicator.Icon -> {
viewCreators.add { createIconView(indicator) }
views.add(createIconView(indicator))
}
is FastScrollItemIndicator.Text -> {
throw IllegalStateException("Text indicator wasn't batched")
Expand All @@ -259,19 +280,20 @@ class FastScrollerView @JvmOverloads constructor(
}
}
}
viewCreators.forEach { createView ->
addView(createView())
}
views.forEach(::addView)
}

private fun selectItemIndicator(
indicator: FastScrollItemIndicator,
indicatorCenterY: Int
indicatorCenterY: Int,
touchedView: View,
textLine: Int?
) {
val position = itemIndicatorsWithPositions
.first { it.first == indicator }
.let(ItemIndicatorWithPosition::second)
if (position != lastSelectedPosition) {
clearSelectedItemIndicator()
lastSelectedPosition = position
if (useDefaultScroller) {
scrollToPosition(position)
Expand All @@ -284,12 +306,29 @@ class FastScrollerView @JvmOverloads constructor(
HapticFeedbackConstants.KEYBOARD_TAP
}
)
if (touchedView is ImageView) {
touchedView.isActivated = true
} else if (textLine != null) {
pressedTextColor?.let { color ->
TextColorUtil.highlightAtIndex(touchedView as TextView, textLine, color)
}
}
itemIndicatorSelectedCallbacks.forEach {
it.onItemIndicatorSelected(indicator, indicatorCenterY, position)
}
}
}

private fun clearSelectedItemIndicator() {
lastSelectedPosition = null
if (pressedIconColor != null) {
children.filterIsInstance<ImageView>().forEach { it.isActivated = false }
}
if (pressedTextColor != null) {
children.filterIsInstance<TextView>().forEach(TextColorUtil::clearHighlight)
}
}

private fun scrollToPosition(position: Int) {
recyclerView!!.apply {
stopScroll()
Expand All @@ -301,9 +340,9 @@ class FastScrollerView @JvmOverloads constructor(
override fun onTouchEvent(event: MotionEvent): Boolean {
fun View.containsY(y: Int) = y in (top until bottom)

if (event.action in MOTIONEVENT_STOP_ACTIONS) {
if (event.actionMasked in MOTIONEVENT_STOP_ACTIONS) {
isPressed = false
lastSelectedPosition = null
clearSelectedItemIndicator()
onItemIndicatorTouched?.invoke(false)
return false
}
Expand All @@ -316,7 +355,7 @@ class FastScrollerView @JvmOverloads constructor(
is ImageView -> {
val touchedIndicator = view.tag as FastScrollItemIndicator.Icon
val centerY = view.y.toInt() + (view.height / 2)
selectItemIndicator(touchedIndicator, centerY)
selectItemIndicator(touchedIndicator, centerY, view, textLine = null)
consumed = true
}
is TextView -> {
Expand All @@ -332,7 +371,7 @@ class FastScrollerView @JvmOverloads constructor(

val centerY = view.y.toInt() +
(textLineHeight / 2) + (touchedIndicatorIndex * textLineHeight)
selectItemIndicator(touchedIndicator, centerY)
selectItemIndicator(touchedIndicator, centerY, view, textLine = touchedIndicatorIndex)
consumed = true
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.reddit.indicatorfastscroll

import android.content.res.ColorStateList
import android.view.View
import androidx.annotation.ColorInt
import androidx.annotation.StyleRes

internal fun View.throwIfMissingAttrs(@StyleRes styleRes: Int, block: () -> Unit) {
Expand All @@ -15,3 +17,8 @@ internal fun View.throwIfMissingAttrs(@StyleRes styleRes: Int, block: () -> Unit
)
}
}

@ColorInt
internal fun ColorStateList.getColorForState(stateSet: IntArray): Int? {
return getColorForState(stateSet, defaultColor).takeIf { it != defaultColor }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.reddit.indicatorfastscroll

import android.text.SpannableString
import android.text.Spanned
import android.text.style.ForegroundColorSpan
import android.widget.TextView
import androidx.core.text.clearSpans

internal object TextColorUtil {

fun highlightAtIndex(textView: TextView, highlightedIndex: Int?, color: Int) {
textView.apply {
if (highlightedIndex == null) {
clearHighlight(this)
} else {
text = SpannableString.valueOf(text).apply {
clearSpans()
val linesUpToHighlight = lineSequence().take(highlightedIndex + 1).toList() // inclusive
val start = linesUpToHighlight
.dropLast(1)
.fold(0) { acc, line ->
acc + line.length + 1
}
val highlightedLineSize = linesUpToHighlight.lastOrNull()?.length ?: 0

setSpan(ForegroundColorSpan(color), start, start + highlightedLineSize, 0)
}
}
}
}

fun clearHighlight(textView: TextView) {
textView.apply {
if (text is Spanned) {
text = SpannableString.valueOf(text).apply {
clearSpans()
}
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package com.reddit.indicatorfastscroll
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

internal class UpdateDelegate<T>(val update: () -> Unit) : ReadWriteProperty<Any?, T> {
internal class UpdateDelegate<T>(val update: (T) -> Unit) : ReadWriteProperty<Any?, T> {

var set = false
var value: T? = null
Expand All @@ -21,12 +21,14 @@ internal class UpdateDelegate<T>(val update: () -> Unit) : ReadWriteProperty<Any
this.set = true
this.value = value
if (wasSet) {
update()
update(value)
}
}
}

/**
* A delegate that sets a backing value and calls [update] on every change after the first.
*/
internal fun <T> onUpdate(update: () -> Unit) = UpdateDelegate<T>(update)
internal fun <T> onUpdate(update: (T) -> Unit) = UpdateDelegate(update)

internal fun <T> onUpdate(update: () -> Unit) = UpdateDelegate<T> { update() }
1 change: 1 addition & 0 deletions sample/src/main/res/color/pressed_selector.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/colorAccent" android:state_activated="true" />
<item android:color="@color/colorPrimary" android:state_pressed="true" />
<item android:color="@android:color/black" />
</selector>

0 comments on commit de91777

Please sign in to comment.