pull/32/head
kunfei 5 years ago
parent 1f5de3bd2c
commit b2267bf6e6
  1. 195
      app/src/main/java/io/legado/app/ui/widget/recycler/scroller/FastScrollRecyclerView.kt
  2. 14
      app/src/main/java/io/legado/app/ui/widget/recycler/scroller/FastScrollStateChangeListener.kt
  3. 518
      app/src/main/java/io/legado/app/ui/widget/recycler/scroller/FastScroller.kt
  4. 80
      app/src/main/java/io/legado/app/utils/ColorUtil.kt
  5. 49
      app/src/main/res/layout/view_fastscroller.xml
  6. 1
      app/src/main/res/values/ids.xml

@ -0,0 +1,195 @@
package io.legado.app.ui.widget.recycler.scroller
import android.content.Context
import android.util.AttributeSet
import android.view.ViewGroup
import androidx.annotation.ColorInt
import androidx.recyclerview.widget.RecyclerView
import io.legado.app.R
class FastScrollRecyclerView : RecyclerView {
private var mFastScroller: FastScroller? = null
constructor(context: Context) : super(context) {
layout(context, null)
layoutParams =
LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
}
@JvmOverloads
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int = 0) : super(context, attrs, defStyleAttr) {
layout(context, attrs)
}
override fun setAdapter(adapter: Adapter<*>?) {
super.setAdapter(adapter)
if (adapter is FastScroller.SectionIndexer) {
setSectionIndexer(adapter as FastScroller.SectionIndexer?)
} else if (adapter == null) {
setSectionIndexer(null)
}
}
override fun setVisibility(visibility: Int) {
super.setVisibility(visibility)
mFastScroller?.visibility = visibility
}
/**
* Set the [FastScroller.SectionIndexer] for the [FastScroller].
*
* @param sectionIndexer The SectionIndexer that provides section text for the FastScroller
*/
fun setSectionIndexer(sectionIndexer: FastScroller.SectionIndexer?) {
mFastScroller?.setSectionIndexer(sectionIndexer)
}
/**
* Set the enabled state of fast scrolling.
*
* @param enabled True to enable fast scrolling, false otherwise
*/
fun setFastScrollEnabled(enabled: Boolean) {
mFastScroller!!.isEnabled = enabled
}
/**
* Hide the scrollbar when not scrolling.
*
* @param hideScrollbar True to hide the scrollbar, false to show
*/
fun setHideScrollbar(hideScrollbar: Boolean) {
mFastScroller?.setFadeScrollbar(hideScrollbar)
}
/**
* Display a scroll track while scrolling.
*
* @param visible True to show scroll track, false to hide
*/
fun setTrackVisible(visible: Boolean) {
mFastScroller?.setTrackVisible(visible)
}
/**
* Set the color of the scroll track.
*
* @param color The color for the scroll track
*/
fun setTrackColor(@ColorInt color: Int) {
mFastScroller?.setTrackColor(color)
}
/**
* Set the color for the scroll handle.
*
* @param color The color for the scroll handle
*/
fun setHandleColor(@ColorInt color: Int) {
mFastScroller?.setHandleColor(color)
}
/**
* Show the section bubble while scrolling.
*
* @param visible True to show the bubble, false to hide
*/
fun setBubbleVisible(visible: Boolean) {
mFastScroller?.setBubbleVisible(visible)
}
/**
* Set the background color of the index bubble.
*
* @param color The background color for the index bubble
*/
fun setBubbleColor(@ColorInt color: Int) {
mFastScroller?.setBubbleColor(color)
}
/**
* Set the text color of the index bubble.
*
* @param color The text color for the index bubble
*/
fun setBubbleTextColor(@ColorInt color: Int) {
mFastScroller?.setBubbleTextColor(color)
}
/**
* Set the fast scroll state change listener.
*
* @param fastScrollStateChangeListener The interface that will listen to fastscroll state change events
*/
fun setFastScrollStateChangeListener(fastScrollStateChangeListener: FastScrollStateChangeListener) {
mFastScroller?.setFastScrollStateChangeListener(fastScrollStateChangeListener)
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
mFastScroller?.attachRecyclerView(this)
val parent = parent
if (parent is ViewGroup) {
parent.addView(mFastScroller)
mFastScroller?.setLayoutParams(parent)
}
}
override fun onDetachedFromWindow() {
mFastScroller?.detachRecyclerView()
super.onDetachedFromWindow()
}
private fun layout(context: Context, attrs: AttributeSet?) {
mFastScroller = FastScroller(context, attrs)
mFastScroller?.id = R.id.fast_scroller
}
}

@ -0,0 +1,14 @@
package io.legado.app.ui.widget.recycler.scroller
interface FastScrollStateChangeListener {
/**
* Called when fast scrolling begins
*/
fun onFastScrollStart(fastScroller: FastScroller)
/**
* Called when fast scrolling ends
*/
fun onFastScrollStop(fastScroller: FastScroller)
}

@ -0,0 +1,518 @@
package io.legado.app.ui.widget.recycler.scroller
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.ViewPropertyAnimator
import android.widget.*
import androidx.annotation.ColorInt
import androidx.annotation.IdRes
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.DrawableCompat
import androidx.core.view.GravityCompat
import androidx.core.view.ViewCompat
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import io.legado.app.R
import io.legado.app.lib.theme.accentColor
import io.legado.app.utils.ColorUtil
import io.legado.app.utils.getCompatColor
import kotlin.math.max
import kotlin.math.min
import kotlin.math.roundToInt
class FastScroller : LinearLayout {
@ColorInt
private var mBubbleColor: Int = 0
@ColorInt
private var mHandleColor: Int = 0
private var mBubbleHeight: Int = 0
private var mHandleHeight: Int = 0
private var mViewHeight: Int = 0
private var mFadeScrollbar: Boolean = false
private var mShowBubble: Boolean = false
private var mSectionIndexer: SectionIndexer? = null
private var mScrollbarAnimator: ViewPropertyAnimator? = null
private var mBubbleAnimator: ViewPropertyAnimator? = null
private var mRecyclerView: RecyclerView? = null
private var mBubbleView: TextView? = null
private var mHandleView: ImageView? = null
private var mTrackView: ImageView? = null
private var mScrollbar: View? = null
private var mBubbleImage: Drawable? = null
private var mHandleImage: Drawable? = null
private var mTrackImage: Drawable? = null
private var mFastScrollStateChangeListener: FastScrollStateChangeListener? = null
private val mScrollbarHider = Runnable { this.hideScrollbar() }
private val mScrollListener = object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
if (!mHandleView!!.isSelected && isEnabled) {
setViewPositions(getScrollProportion(recyclerView))
}
}
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (isEnabled) {
when (newState) {
RecyclerView.SCROLL_STATE_DRAGGING -> {
handler.removeCallbacks(mScrollbarHider)
cancelAnimation(mScrollbarAnimator)
if (!isViewVisible(mScrollbar)) {
showScrollbar()
}
}
RecyclerView.SCROLL_STATE_IDLE -> if (mFadeScrollbar && !mHandleView!!.isSelected) {
handler.postDelayed(mScrollbarHider, sScrollbarHideDelay.toLong())
}
}
}
}
}
constructor(context: Context) : super(context) {
layout(context, null)
layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT)
}
@JvmOverloads
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int = 0) : super(context, attrs, defStyleAttr) {
layout(context, attrs)
layoutParams = generateLayoutParams(attrs)
}
override fun setLayoutParams(params: ViewGroup.LayoutParams) {
params.width = LayoutParams.WRAP_CONTENT
super.setLayoutParams(params)
}
fun setLayoutParams(viewGroup: ViewGroup) {
@IdRes val recyclerViewId = if (mRecyclerView != null) mRecyclerView!!.id else View.NO_ID
val marginTop = resources.getDimensionPixelSize(R.dimen.fastscroll_scrollbar_margin_top)
val marginBottom = resources.getDimensionPixelSize(R.dimen.fastscroll_scrollbar_margin_bottom)
if (recyclerViewId == View.NO_ID) {
throw IllegalArgumentException("RecyclerView must have a view ID")
}
when (viewGroup) {
is ConstraintLayout -> {
val constraintSet = ConstraintSet()
@IdRes val layoutId = id
constraintSet.clone(viewGroup)
constraintSet.connect(layoutId, ConstraintSet.TOP, recyclerViewId, ConstraintSet.TOP)
constraintSet.connect(layoutId, ConstraintSet.BOTTOM, recyclerViewId, ConstraintSet.BOTTOM)
constraintSet.connect(layoutId, ConstraintSet.END, recyclerViewId, ConstraintSet.END)
constraintSet.applyTo(viewGroup)
val layoutParams = layoutParams as ConstraintLayout.LayoutParams
layoutParams.setMargins(0, marginTop, 0, marginBottom)
setLayoutParams(layoutParams)
}
is CoordinatorLayout -> {
val layoutParams = layoutParams as CoordinatorLayout.LayoutParams
layoutParams.anchorId = recyclerViewId
layoutParams.anchorGravity = GravityCompat.END
layoutParams.setMargins(0, marginTop, 0, marginBottom)
setLayoutParams(layoutParams)
}
is FrameLayout -> {
val layoutParams = layoutParams as FrameLayout.LayoutParams
layoutParams.gravity = GravityCompat.END
layoutParams.setMargins(0, marginTop, 0, marginBottom)
setLayoutParams(layoutParams)
}
is RelativeLayout -> {
val layoutParams = layoutParams as RelativeLayout.LayoutParams
val endRule = RelativeLayout.ALIGN_END
layoutParams.addRule(RelativeLayout.ALIGN_TOP, recyclerViewId)
layoutParams.addRule(RelativeLayout.ALIGN_BOTTOM, recyclerViewId)
layoutParams.addRule(endRule, recyclerViewId)
layoutParams.setMargins(0, marginTop, 0, marginBottom)
setLayoutParams(layoutParams)
}
else -> throw IllegalArgumentException("Parent ViewGroup must be a ConstraintLayout, CoordinatorLayout, FrameLayout, or RelativeLayout")
}
updateViewHeights()
}
fun setSectionIndexer(sectionIndexer: SectionIndexer?) {
mSectionIndexer = sectionIndexer
}
fun attachRecyclerView(recyclerView: RecyclerView) {
mRecyclerView = recyclerView
if (mRecyclerView != null) {
mRecyclerView!!.addOnScrollListener(mScrollListener)
post {
// set initial positions for bubble and handle
setViewPositions(getScrollProportion(mRecyclerView))
}
}
}
fun detachRecyclerView() {
if (mRecyclerView != null) {
mRecyclerView!!.removeOnScrollListener(mScrollListener)
mRecyclerView = null
}
}
/**
* Hide the scrollbar when not scrolling.
*
* @param fadeScrollbar True to hide the scrollbar, false to show
*/
fun setFadeScrollbar(fadeScrollbar: Boolean) {
mFadeScrollbar = fadeScrollbar
mScrollbar!!.visibility = if (fadeScrollbar) View.GONE else View.VISIBLE
}
/**
* Show the section bubble while scrolling.
*
* @param visible True to show the bubble, false to hide
*/
fun setBubbleVisible(visible: Boolean) {
mShowBubble = visible
}
/**
* Display a scroll track while scrolling.
*
* @param visible True to show scroll track, false to hide
*/
fun setTrackVisible(visible: Boolean) {
mTrackView!!.visibility = if (visible) View.VISIBLE else View.GONE
}
/**
* Set the color of the scroll track.
*
* @param color The color for the scroll track
*/
fun setTrackColor(@ColorInt color: Int) {
if (mTrackImage == null) {
val drawable = ContextCompat.getDrawable(context, R.drawable.fastscroll_track)
if (drawable != null) {
mTrackImage = DrawableCompat.wrap(drawable)
}
}
DrawableCompat.setTint(mTrackImage!!, color)
mTrackView!!.setImageDrawable(mTrackImage)
}
/**
* Set the color for the scroll handle.
*
* @param color The color for the scroll handle
*/
fun setHandleColor(@ColorInt color: Int) {
mHandleColor = color
if (mHandleImage == null) {
val drawable = ContextCompat.getDrawable(context, R.drawable.fastscroll_handle)
if (drawable != null) {
mHandleImage = DrawableCompat.wrap(drawable)
}
}
DrawableCompat.setTint(mHandleImage!!, mHandleColor)
mHandleView!!.setImageDrawable(mHandleImage)
}
/**
* Set the background color of the index bubble.
*
* @param color The background color for the index bubble
*/
fun setBubbleColor(@ColorInt color: Int) {
mBubbleColor = color
if (mBubbleImage == null) {
val drawable = ContextCompat.getDrawable(context, R.drawable.fastscroll_bubble)
if (drawable != null) {
mBubbleImage = DrawableCompat.wrap(drawable)
}
}
DrawableCompat.setTint(mBubbleImage!!, mBubbleColor)
mBubbleView!!.background = mBubbleImage
}
/**
* Set the text color of the index bubble.
*
* @param color The text color for the index bubble
*/
fun setBubbleTextColor(@ColorInt color: Int) {
mBubbleView!!.setTextColor(color)
}
/**
* Set the fast scroll state change listener.
*
* @param fastScrollStateChangeListener The interface that will listen to fastscroll state change events
*/
fun setFastScrollStateChangeListener(fastScrollStateChangeListener: FastScrollStateChangeListener) {
mFastScrollStateChangeListener = fastScrollStateChangeListener
}
override fun setEnabled(enabled: Boolean) {
super.setEnabled(enabled)
visibility = if (enabled) View.VISIBLE else View.GONE
}
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
if (event.x < mHandleView!!.x - ViewCompat.getPaddingStart(mHandleView!!)) {
return false
}
requestDisallowInterceptTouchEvent(true)
setHandleSelected(true)
handler.removeCallbacks(mScrollbarHider)
cancelAnimation(mScrollbarAnimator)
cancelAnimation(mBubbleAnimator)
if (!isViewVisible(mScrollbar)) {
showScrollbar()
}
if (mShowBubble && mSectionIndexer != null) {
showBubble()
}
if (mFastScrollStateChangeListener != null) {
mFastScrollStateChangeListener!!.onFastScrollStart(this)
}
val y = event.y
setViewPositions(y)
setRecyclerViewPosition(y)
return true
}
MotionEvent.ACTION_MOVE -> {
val y = event.y
setViewPositions(y)
setRecyclerViewPosition(y)
return true
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
requestDisallowInterceptTouchEvent(false)
setHandleSelected(false)
if (mFadeScrollbar) {
handler.postDelayed(mScrollbarHider, sScrollbarHideDelay.toLong())
}
hideBubble()
if (mFastScrollStateChangeListener != null) {
mFastScrollStateChangeListener!!.onFastScrollStop(this)
}
return true
}
}
return super.onTouchEvent(event)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
mViewHeight = h
}
private fun setRecyclerViewPosition(y: Float) {
if (mRecyclerView != null && mRecyclerView!!.adapter != null) {
val itemCount = mRecyclerView!!.adapter!!.itemCount
val proportion: Float = when {
mHandleView!!.y == 0f -> 0f
mHandleView!!.y + mHandleHeight >= mViewHeight - sTrackSnapRange -> 1f
else -> y / mViewHeight.toFloat()
}
var scrolledItemCount = (proportion * itemCount).roundToInt()
if (isLayoutReversed(mRecyclerView!!.layoutManager!!)) {
scrolledItemCount = itemCount - scrolledItemCount
}
val targetPos = getValueInRange(0, itemCount - 1, scrolledItemCount)
mRecyclerView!!.layoutManager!!.scrollToPosition(targetPos)
if (mShowBubble && mSectionIndexer != null) {
mBubbleView!!.text = mSectionIndexer!!.getSectionText(targetPos)
}
}
}
private fun getScrollProportion(recyclerView: RecyclerView?): Float {
if (recyclerView == null) {
return 0f
}
val verticalScrollOffset = recyclerView.computeVerticalScrollOffset()
val verticalScrollRange = recyclerView.computeVerticalScrollRange()
val rangeDiff = (verticalScrollRange - mViewHeight).toFloat()
val proportion = verticalScrollOffset.toFloat() / if (rangeDiff > 0) rangeDiff else 1f
return mViewHeight * proportion
}
private fun getValueInRange(min: Int, max: Int, value: Int): Int {
val minimum = max(min, value)
return min(minimum, max)
}
private fun setViewPositions(y: Float) {
mBubbleHeight = mBubbleView!!.height
mHandleHeight = mHandleView!!.height
val bubbleY = getValueInRange(0, mViewHeight - mBubbleHeight - mHandleHeight / 2, (y - mBubbleHeight).toInt())
val handleY = getValueInRange(0, mViewHeight - mHandleHeight, (y - mHandleHeight / 2).toInt())
if (mShowBubble) {
mBubbleView!!.y = bubbleY.toFloat()
}
mHandleView!!.y = handleY.toFloat()
}
private fun updateViewHeights() {
val measureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
mBubbleView!!.measure(measureSpec, measureSpec)
mBubbleHeight = mBubbleView!!.measuredHeight
mHandleView!!.measure(measureSpec, measureSpec)
mHandleHeight = mHandleView!!.measuredHeight
}
private fun isLayoutReversed(layoutManager: RecyclerView.LayoutManager): Boolean {
if (layoutManager is LinearLayoutManager) {
return layoutManager.reverseLayout
} else if (layoutManager is StaggeredGridLayoutManager) {
return layoutManager.reverseLayout
}
return false
}
private fun isViewVisible(view: View?): Boolean {
return view != null && view.visibility == View.VISIBLE
}
private fun cancelAnimation(animator: ViewPropertyAnimator?) {
animator?.cancel()
}
private fun showBubble() {
if (!isViewVisible(mBubbleView)) {
mBubbleView!!.visibility = View.VISIBLE
mBubbleAnimator = mBubbleView!!.animate().alpha(1f)
.setDuration(sBubbleAnimDuration.toLong())
.setListener(object : AnimatorListenerAdapter() {
// adapter required for new alpha value to stick
})
}
}
private fun hideBubble() {
if (isViewVisible(mBubbleView)) {
mBubbleAnimator = mBubbleView!!.animate().alpha(0f)
.setDuration(sBubbleAnimDuration.toLong())
.setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
super.onAnimationEnd(animation)
mBubbleView!!.visibility = View.GONE
mBubbleAnimator = null
}
override fun onAnimationCancel(animation: Animator) {
super.onAnimationCancel(animation)
mBubbleView!!.visibility = View.GONE
mBubbleAnimator = null
}
})
}
}
private fun showScrollbar() {
if (mRecyclerView!!.computeVerticalScrollRange() - mViewHeight > 0) {
val transX = resources.getDimensionPixelSize(R.dimen.fastscroll_scrollbar_padding_end).toFloat()
mScrollbar!!.translationX = transX
mScrollbar!!.visibility = View.VISIBLE
mScrollbarAnimator = mScrollbar!!.animate().translationX(0f).alpha(1f)
.setDuration(sScrollbarAnimDuration.toLong())
.setListener(object : AnimatorListenerAdapter() {
// adapter required for new alpha value to stick
})
}
}
private fun hideScrollbar() {
val transX = resources.getDimensionPixelSize(R.dimen.fastscroll_scrollbar_padding_end).toFloat()
mScrollbarAnimator = mScrollbar!!.animate().translationX(transX).alpha(0f)
.setDuration(sScrollbarAnimDuration.toLong())
.setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
super.onAnimationEnd(animation)
mScrollbar!!.visibility = View.GONE
mScrollbarAnimator = null
}
override fun onAnimationCancel(animation: Animator) {
super.onAnimationCancel(animation)
mScrollbar!!.visibility = View.GONE
mScrollbarAnimator = null
}
})
}
private fun setHandleSelected(selected: Boolean) {
mHandleView!!.isSelected = selected
DrawableCompat.setTint(mHandleImage!!, if (selected) mBubbleColor else mHandleColor)
}
private fun layout(context: Context, attrs: AttributeSet?) {
View.inflate(context, R.layout.view_fastscroller, this)
clipChildren = false
orientation = HORIZONTAL
mBubbleView = findViewById(R.id.fastscroll_bubble)
mHandleView = findViewById(R.id.fastscroll_handle)
mTrackView = findViewById(R.id.fastscroll_track)
mScrollbar = findViewById(R.id.fastscroll_scrollbar)
@ColorInt var bubbleColor = ColorUtil.adjustAlpha(context.accentColor, 0.8f)
@ColorInt var handleColor = context.accentColor
@ColorInt var trackColor = context.getCompatColor(R.color.transparent30)
@ColorInt var textColor = if (ColorUtil.isColorLight(bubbleColor)) Color.BLACK else Color.WHITE
var fadeScrollbar = true
var showBubble = false
var showTrack = true
if (attrs != null) {
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.FastScroller, 0, 0)
if (typedArray != null) {
try {
bubbleColor = typedArray.getColor(R.styleable.FastScroller_bubbleColor, bubbleColor)
handleColor = typedArray.getColor(R.styleable.FastScroller_handleColor, handleColor)
trackColor = typedArray.getColor(R.styleable.FastScroller_trackColor, trackColor)
textColor = typedArray.getColor(R.styleable.FastScroller_bubbleTextColor, textColor)
fadeScrollbar = typedArray.getBoolean(R.styleable.FastScroller_fadeScrollbar, fadeScrollbar)
showBubble = typedArray.getBoolean(R.styleable.FastScroller_showBubble, showBubble)
showTrack = typedArray.getBoolean(R.styleable.FastScroller_showTrack, showTrack)
} finally {
typedArray.recycle()
}
}
}
setTrackColor(trackColor)
setHandleColor(handleColor)
setBubbleColor(bubbleColor)
setBubbleTextColor(textColor)
setFadeScrollbar(fadeScrollbar)
setBubbleVisible(showBubble)
setTrackVisible(showTrack)
}
interface SectionIndexer {
fun getSectionText(position: Int): String
}
companion object {
private const val sBubbleAnimDuration = 100
private const val sScrollbarAnimDuration = 300
private const val sScrollbarHideDelay = 1000
private const val sTrackSnapRange = 5
}
}

@ -0,0 +1,80 @@
package io.legado.app.utils
import android.graphics.Color
import androidx.annotation.ColorInt
import androidx.annotation.FloatRange
object ColorUtil {
fun intToString(intColor: Int): String {
return String.format("#%06X", 0xFFFFFF and intColor)
}
fun stripAlpha(@ColorInt color: Int): Int {
return -0x1000000 or color
}
@ColorInt
fun shiftColor(@ColorInt color: Int, @FloatRange(from = 0.0, to = 2.0) by: Float): Int {
if (by == 1f) return color
val alpha = Color.alpha(color)
val hsv = FloatArray(3)
Color.colorToHSV(color, hsv)
hsv[2] *= by // value component
return (alpha shl 24) + (0x00ffffff and Color.HSVToColor(hsv))
}
@ColorInt
fun darkenColor(@ColorInt color: Int): Int {
return shiftColor(color, 0.9f)
}
@ColorInt
fun lightenColor(@ColorInt color: Int): Int {
return shiftColor(color, 1.1f)
}
fun isColorLight(@ColorInt color: Int): Boolean {
val darkness = 1 - (0.299 * Color.red(color) + 0.587 * Color.green(color) + 0.114 * Color.blue(color)) / 255
return darkness < 0.4
}
@ColorInt
fun invertColor(@ColorInt color: Int): Int {
val r = 255 - Color.red(color)
val g = 255 - Color.green(color)
val b = 255 - Color.blue(color)
return Color.argb(Color.alpha(color), r, g, b)
}
@ColorInt
fun adjustAlpha(@ColorInt color: Int, @FloatRange(from = 0.0, to = 1.0) factor: Float): Int {
val alpha = Math.round(Color.alpha(color) * factor)
val red = Color.red(color)
val green = Color.green(color)
val blue = Color.blue(color)
return Color.argb(alpha, red, green, blue)
}
@ColorInt
fun withAlpha(@ColorInt baseColor: Int, @FloatRange(from = 0.0, to = 1.0) alpha: Float): Int {
val a = Math.min(255, Math.max(0, (alpha * 255).toInt())) shl 24
val rgb = 0x00ffffff and baseColor
return a + rgb
}
/**
* Taken from CollapsingToolbarLayout's CollapsingTextHelper class.
*/
fun blendColors(color1: Int, color2: Int, @FloatRange(from = 0.0, to = 1.0) ratio: Float): Int {
val inverseRatio = 1f - ratio
val a = Color.alpha(color1) * inverseRatio + Color.alpha(color2) * ratio
val r = Color.red(color1) * inverseRatio + Color.red(color2) * ratio
val g = Color.green(color1) * inverseRatio + Color.green(color2) * ratio
val b = Color.blue(color1) * inverseRatio + Color.blue(color2) * ratio
return Color.argb(a.toInt(), r.toInt(), g.toInt(), b.toInt())
}
}

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<TextView
android:id="@+id/fastscroll_bubble"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:gravity="center"
android:maxLines="1"
android:textSize="@dimen/fastscroll_bubble_textsize"
android:visibility="gone"
tools:background="@drawable/fastscroll_bubble"
tools:text="A"
tools:textColor="#ffffff"
tools:visibility="visible" />
<FrameLayout
android:id="@+id/fastscroll_scrollbar"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:paddingEnd="@dimen/fastscroll_scrollbar_padding_end"
android:paddingLeft="@dimen/fastscroll_scrollbar_padding_start"
android:paddingRight="@dimen/fastscroll_scrollbar_padding_end"
android:paddingStart="@dimen/fastscroll_scrollbar_padding_start"
android:visibility="gone"
tools:visibility="visible">
<ImageView
android:id="@+id/fastscroll_track"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
tools:ignore="ContentDescription"
tools:src="@drawable/fastscroll_track" />
<ImageView
android:id="@+id/fastscroll_handle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
tools:ignore="ContentDescription"
tools:src="@drawable/fastscroll_handle" />
</FrameLayout>
</merge>

@ -7,4 +7,5 @@
<item name="tag1" type="id" />
<item name="tag2" type="id" />
<item name="fast_scroller" type="id" />
</resources>
Loading…
Cancel
Save