diff --git a/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollPopupView.kt b/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollPopupView.kt
deleted file mode 100644
index c96ceaeb6..000000000
--- a/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollPopupView.kt
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * Copyright (c) 2022 Auxio Project
- * FastScrollPopupView.kt is part of Auxio.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package org.oxycblt.auxio.home.fastscroll
-
-import android.content.Context
-import android.graphics.Canvas
-import android.graphics.ColorFilter
-import android.graphics.Matrix
-import android.graphics.Outline
-import android.graphics.Paint
-import android.graphics.Path
-import android.graphics.PixelFormat
-import android.graphics.Rect
-import android.graphics.drawable.Drawable
-import android.os.Build
-import android.text.TextUtils
-import android.util.AttributeSet
-import android.view.Gravity
-import androidx.core.widget.TextViewCompat
-import com.google.android.material.R as MR
-import com.google.android.material.textview.MaterialTextView
-import org.oxycblt.auxio.R
-import org.oxycblt.auxio.util.getAttrColorCompat
-import org.oxycblt.auxio.util.getDimenPixels
-import org.oxycblt.auxio.util.isRtl
-
-/**
- * A [MaterialTextView] that displays the popup indicator used in FastScrollRecyclerView
- *
- * @author Alexander Capehart (OxygenCobalt), Hai Zhang
- */
-class FastScrollPopupView
-@JvmOverloads
-constructor(context: Context, attrs: AttributeSet? = null, defStyleRes: Int = 0) :
- MaterialTextView(context, attrs, defStyleRes) {
- init {
- minimumWidth = context.getDimenPixels(R.dimen.size_touchable_mid_huge)
- minimumHeight = context.getDimenPixels(R.dimen.size_touchable_large)
-
- TextViewCompat.setTextAppearance(this, R.style.TextAppearance_Auxio_HeadlineLarge)
- setTextColor(context.getAttrColorCompat(MR.attr.colorOnSecondary))
- ellipsize = TextUtils.TruncateAt.MIDDLE
- gravity = Gravity.CENTER
- includeFontPadding = false
-
- alpha = 0f
- elevation = context.getDimenPixels(MR.dimen.m3_sys_elevation_level2).toFloat()
- background = FastScrollPopupDrawable(context)
- }
-
- private class FastScrollPopupDrawable(context: Context) : Drawable() {
- private val paint: Paint =
- Paint().apply {
- isAntiAlias = true
- color =
- context
- .getAttrColorCompat(com.google.android.material.R.attr.colorSecondary)
- .defaultColor
- style = Paint.Style.FILL
- }
-
- private val path = Path()
- private val matrix = Matrix()
-
- private val paddingStart = context.getDimenPixels(R.dimen.spacing_medium)
- private val paddingEnd = context.getDimenPixels(R.dimen.spacing_mid_huge)
-
- override fun draw(canvas: Canvas) {
- canvas.drawPath(path, paint)
- }
-
- override fun onBoundsChange(bounds: Rect) {
- updatePath()
- }
-
- override fun onLayoutDirectionChanged(layoutDirection: Int): Boolean {
- updatePath()
- return true
- }
-
- @Suppress("DEPRECATION")
- override fun getOutline(outline: Outline) {
- when {
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> outline.setPath(path)
-
- // Paths don't need to be convex on android Q, but the API was mislabeled and so
- // we still have to use this method.
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> outline.setConvexPath(path)
- else ->
- if (!path.isConvex) {
- // The outline path must be convex before Q, but we may run into floating
- // point errors caused by calculations involving sqrt(2) or OEM differences,
- // so in this case we just omit the shadow instead of crashing.
- super.getOutline(outline)
- }
- }
- }
-
- override fun getPadding(padding: Rect): Boolean {
- if (isRtl) {
- padding[paddingEnd, 0, paddingStart] = 0
- } else {
- padding[paddingStart, 0, paddingEnd] = 0
- }
-
- return true
- }
-
- override fun isAutoMirrored(): Boolean = true
-
- override fun setAlpha(alpha: Int) {}
-
- override fun setColorFilter(colorFilter: ColorFilter?) {}
-
- override fun getOpacity(): Int = PixelFormat.TRANSLUCENT
-
- private fun updatePath() {
- val r = bounds.height().toFloat() / 2
- val w = (r + SQRT2 * r).coerceAtLeast(bounds.width().toFloat())
-
- path.apply {
- reset()
-
- // Draw the left pill shape
- val o1X = w - SQRT2 * r
- arcToSafe(r, r, r, 90f, 180f)
- arcToSafe(o1X, r, r, -90f, 45f)
-
- // Draw the right arrow shape
- val point = r / 5
- val o2X = w - SQRT2 * point
- arcToSafe(o2X, r, point, -45f, 90f)
- arcToSafe(o1X, r, r, 45f, 45f)
-
- close()
- }
-
- matrix.apply {
- reset()
- if (isRtl) setScale(-1f, 1f, w / 2, 0f)
- postTranslate(bounds.left.toFloat(), bounds.top.toFloat())
- }
-
- path.transform(matrix)
- }
-
- private fun Path.arcToSafe(
- centerX: Float,
- centerY: Float,
- radius: Float,
- startAngle: Float,
- sweepAngle: Float
- ) {
- arcTo(
- centerX - radius,
- centerY - radius,
- centerX + radius,
- centerY + radius,
- startAngle,
- sweepAngle,
- false)
- }
- }
-
- private companion object {
- // Pre-calculate sqrt(2)
- const val SQRT2 = 1.4142135f
- }
-}
diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt
index a3ad98835..4d241eaf7 100644
--- a/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt
@@ -29,12 +29,12 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.home.HomeViewModel
-import org.oxycblt.auxio.home.fastscroll.FastScrollRecyclerView
import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.ListViewModel
import org.oxycblt.auxio.list.SelectableListListener
import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter
import org.oxycblt.auxio.list.recycler.AlbumViewHolder
+import org.oxycblt.auxio.list.recycler.FastScrollRecyclerView
import org.oxycblt.auxio.list.sort.Sort
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Music
diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt
index 0f99cdb48..b2e805a75 100644
--- a/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt
@@ -27,12 +27,12 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.home.HomeViewModel
-import org.oxycblt.auxio.home.fastscroll.FastScrollRecyclerView
import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.ListViewModel
import org.oxycblt.auxio.list.SelectableListListener
import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter
import org.oxycblt.auxio.list.recycler.ArtistViewHolder
+import org.oxycblt.auxio.list.recycler.FastScrollRecyclerView
import org.oxycblt.auxio.list.sort.Sort
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Music
diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt
index ef001d36e..4ea68bb5c 100644
--- a/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt
@@ -27,11 +27,11 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.home.HomeViewModel
-import org.oxycblt.auxio.home.fastscroll.FastScrollRecyclerView
import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.ListViewModel
import org.oxycblt.auxio.list.SelectableListListener
import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter
+import org.oxycblt.auxio.list.recycler.FastScrollRecyclerView
import org.oxycblt.auxio.list.recycler.GenreViewHolder
import org.oxycblt.auxio.list.sort.Sort
import org.oxycblt.auxio.music.Genre
diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/PlaylistListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/PlaylistListFragment.kt
index e0fe15bec..901693eb9 100644
--- a/app/src/main/java/org/oxycblt/auxio/home/list/PlaylistListFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/home/list/PlaylistListFragment.kt
@@ -26,11 +26,11 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.home.HomeViewModel
-import org.oxycblt.auxio.home.fastscroll.FastScrollRecyclerView
import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.ListViewModel
import org.oxycblt.auxio.list.SelectableListListener
import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter
+import org.oxycblt.auxio.list.recycler.FastScrollRecyclerView
import org.oxycblt.auxio.list.recycler.PlaylistViewHolder
import org.oxycblt.auxio.list.sort.Sort
import org.oxycblt.auxio.music.Music
diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt
index 6a5d91bcf..1d455b58c 100644
--- a/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt
@@ -28,11 +28,11 @@ import java.util.Formatter
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.home.HomeViewModel
-import org.oxycblt.auxio.home.fastscroll.FastScrollRecyclerView
import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.ListViewModel
import org.oxycblt.auxio.list.SelectableListListener
import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter
+import org.oxycblt.auxio.list.recycler.FastScrollRecyclerView
import org.oxycblt.auxio.list.recycler.SongViewHolder
import org.oxycblt.auxio.list.sort.Sort
import org.oxycblt.auxio.music.Music
diff --git a/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollRecyclerView.kt b/app/src/main/java/org/oxycblt/auxio/list/recycler/FastScrollRecyclerView.kt
similarity index 52%
rename from app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollRecyclerView.kt
rename to app/src/main/java/org/oxycblt/auxio/list/recycler/FastScrollRecyclerView.kt
index 4a0e35873..89e0b5e74 100644
--- a/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollRecyclerView.kt
+++ b/app/src/main/java/org/oxycblt/auxio/list/recycler/FastScrollRecyclerView.kt
@@ -16,31 +16,25 @@
* along with this program. If not, see .
*/
-package org.oxycblt.auxio.home.fastscroll
+package org.oxycblt.auxio.list.recycler
import android.animation.Animator
import android.content.Context
import android.graphics.Canvas
import android.graphics.Rect
import android.util.AttributeSet
-import android.view.Gravity
import android.view.MotionEvent
-import android.view.View
import android.view.ViewConfiguration
-import android.view.ViewGroup
import android.view.WindowInsets
-import android.widget.FrameLayout
import androidx.annotation.AttrRes
-import androidx.core.view.isInvisible
-import androidx.recyclerview.widget.GridLayoutManager
-import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlin.math.abs
+import kotlin.math.max
+import kotlin.math.roundToInt
import org.oxycblt.auxio.R
-import org.oxycblt.auxio.list.recycler.AuxioRecyclerView
-import org.oxycblt.auxio.ui.MaterialFader
+import org.oxycblt.auxio.ui.MaterialSlider
import org.oxycblt.auxio.util.getDimenPixels
-import org.oxycblt.auxio.util.getDrawableCompat
+import org.oxycblt.auxio.util.inflater
import org.oxycblt.auxio.util.isRtl
import org.oxycblt.auxio.util.isUnder
import org.oxycblt.auxio.util.systemBarInsetsCompat
@@ -67,6 +61,7 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat
* - Variable names are no longer prefixed with m
* - Added drag listener
* - Added documentation
+ * - Completely new design
*
* @author Hai Zhang, Alexander Capehart (OxygenCobalt)
*
@@ -78,14 +73,12 @@ class FastScrollRecyclerView
constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
AuxioRecyclerView(context, attrs, defStyleAttr) {
// Thumb
- private val thumbView =
- View(context).apply {
- scaleX = 0f
- background = context.getDrawableCompat(R.drawable.ui_scroll_thumb)
- }
+ private val thumbSize = context.getDimenPixels(R.dimen.size_touchable_small)
+ private val slider = MaterialSlider(context, thumbSize)
+ private var thumbAnimator: Animator? = null
- private val thumbWidth = thumbView.background.intrinsicWidth
- private val thumbHeight = thumbView.background.intrinsicHeight
+ private val thumbView =
+ context.inflater.inflate(R.layout.view_scroll_thumb, null).apply { slider.jumpOut(this) }
private val thumbPadding = Rect(0, 0, 0, 0)
private var thumbOffset = 0
@@ -96,27 +89,6 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
}
}
- // Popup
- private val popupView =
- FastScrollPopupView(context).apply {
- scaleX = 0f
- scaleY = 0f
- alpha = 0f
- layoutParams =
- FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
- .apply {
- gravity = Gravity.CENTER_HORIZONTAL or Gravity.TOP
- marginEnd = context.getDimenPixels(R.dimen.spacing_small)
- }
- }
-
- private val fader = MaterialFader.quickLopsided(context)
- private var thumbAnimator: Animator? = null
- private var popupAnimator: Animator? = null
-
- private var showingPopup = false
-
// Touch
private val minTouchTargetSize = context.getDimenPixels(R.dimen.size_touchable_small)
private val touchSlop = ViewConfiguration.get(context).scaledTouchSlop
@@ -144,23 +116,18 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
if (field) {
removeCallbacks(hideThumbRunnable)
showScrollbar()
- showPopup()
} else {
postAutoHideScrollbar()
- hidePopup()
}
listener?.onFastScrollingChanged(field)
}
- private val tRect = Rect()
-
var popupProvider: PopupProvider? = null
var listener: Listener? = null
init {
overlay.add(thumbView)
- overlay.add(popupView)
addItemDecoration(
object : ItemDecoration() {
@@ -192,85 +159,17 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
updateScrollbarState()
thumbView.layoutDirection = layoutDirection
- popupView.layoutDirection = layoutDirection
-
+ thumbView.measure(
+ MeasureSpec.makeMeasureSpec(thumbSize, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(thumbSize, MeasureSpec.EXACTLY))
+ val thumbTop = thumbPadding.top + thumbOffset
val thumbLeft =
if (isRtl) {
thumbPadding.left
} else {
- width - thumbPadding.right - thumbWidth
+ width - thumbPadding.right - thumbSize
}
-
- val thumbTop = thumbPadding.top + thumbOffset
-
- thumbView.layout(thumbLeft, thumbTop, thumbLeft + thumbWidth, thumbTop + thumbHeight)
-
- val child = getChildAt(0)
- val firstAdapterPos =
- if (child != null) {
- layoutManager?.getPosition(child) ?: NO_POSITION
- } else {
- NO_POSITION
- }
-
- val popupText: String
- val provider = popupProvider
- if (firstAdapterPos != NO_POSITION && provider != null) {
- popupView.isInvisible = false
- // Get the popup text. If there is none, we default to "?".
- popupText = provider.getPopup(firstAdapterPos) ?: "?"
- } else {
- // No valid position or provider, do not show the popup.
- popupView.isInvisible = true
- popupText = ""
- }
-
- val popupLayoutParams = popupView.layoutParams as FrameLayout.LayoutParams
-
- if (popupView.text != popupText) {
- popupView.text = popupText
-
- val widthMeasureSpec =
- ViewGroup.getChildMeasureSpec(
- MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
- thumbPadding.left +
- thumbPadding.right +
- thumbWidth +
- popupLayoutParams.leftMargin +
- popupLayoutParams.rightMargin,
- popupLayoutParams.width)
-
- val heightMeasureSpec =
- ViewGroup.getChildMeasureSpec(
- MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY),
- thumbPadding.top +
- thumbPadding.bottom +
- popupLayoutParams.topMargin +
- popupLayoutParams.bottomMargin,
- popupLayoutParams.height)
-
- popupView.measure(widthMeasureSpec, heightMeasureSpec)
- }
-
- val popupWidth = popupView.measuredWidth
- val popupHeight = popupView.measuredHeight
- val popupLeft =
- if (layoutDirection == View.LAYOUT_DIRECTION_RTL) {
- thumbPadding.left + thumbWidth + popupLayoutParams.leftMargin
- } else {
- width - thumbPadding.right - thumbWidth - popupLayoutParams.rightMargin - popupWidth
- }
-
- val popupAnchorY = popupHeight / 2
- val thumbAnchorY = thumbView.paddingTop
-
- val popupTop =
- (thumbTop + thumbAnchorY - popupAnchorY)
- .coerceAtLeast(thumbPadding.top + popupLayoutParams.topMargin)
- .coerceAtMost(
- height - thumbPadding.bottom - popupLayoutParams.bottomMargin - popupHeight)
-
- popupView.layout(popupLeft, popupTop, popupLeft + popupWidth, popupTop + popupHeight)
+ thumbView.layout(thumbLeft, thumbTop, thumbLeft + thumbSize, thumbTop + thumbSize)
}
override fun onScrolled(dx: Int, dy: Int) {
@@ -295,26 +194,15 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
}
private fun updateScrollbarState() {
- if (scrollRange <= height || childCount == 0) {
- return
- }
-
- // Combine the previous item dimensions with the current item top to find our scroll
- // position
- getDecoratedBoundsWithMargins(getChildAt(0), tRect)
- val child = getChildAt(0)
- val firstAdapterPos =
- when (val mgr = layoutManager) {
- is GridLayoutManager -> mgr.getPosition(child) / mgr.spanCount
- is LinearLayoutManager -> mgr.getPosition(child)
- else -> 0
- }
-
- val scrollOffset = paddingTop + (firstAdapterPos * itemHeight) - tRect.top
-
// Then calculate the thumb position, which is just:
// [proportion of scroll position to scroll range] * [total thumb range]
- thumbOffset = (thumbOffsetRange.toLong() * scrollOffset / scrollOffsetRange).toInt()
+ val offsetY = computeVerticalScrollOffset()
+ if (computeVerticalScrollRange() < height || childCount == 0) {
+ return
+ }
+ val extentY = computeVerticalScrollExtent()
+ val fraction = (offsetY).toFloat() / (computeVerticalScrollRange() - extentY)
+ thumbOffset = (thumbOffsetRange * fraction).toInt()
}
private fun onItemTouch(event: MotionEvent): Boolean {
@@ -331,10 +219,11 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
if (thumbView.isUnder(eventX, eventY, minTouchTargetSize)) {
dragStartThumbOffset = thumbOffset
- } else {
- dragStartThumbOffset =
- (eventY - thumbPadding.top - thumbHeight / 2f).toInt()
+ } else if (eventX > thumbView.right - thumbSize / 4) {
+ dragStartThumbOffset = (eventY - thumbPadding.top - thumbSize / 2f).toInt()
scrollToThumbOffset(dragStartThumbOffset)
+ } else {
+ return false
}
dragging = true
@@ -349,8 +238,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
dragStartThumbOffset = thumbOffset
} else {
dragStartY = eventY
- dragStartThumbOffset =
- (eventY - thumbPadding.top - thumbHeight / 2f).toInt()
+ dragStartThumbOffset = (eventY - thumbPadding.top - thumbSize / 2f).toInt()
scrollToThumbOffset(dragStartThumbOffset)
}
@@ -371,44 +259,19 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
}
private fun scrollToThumbOffset(thumbOffset: Int) {
- val clampedThumbOffset = thumbOffset.coerceAtLeast(0).coerceAtMost(thumbOffsetRange)
-
- val scrollOffset =
- (scrollOffsetRange.toLong() * clampedThumbOffset / thumbOffsetRange).toInt() -
- paddingTop
-
- scrollTo(scrollOffset)
- }
-
- private fun scrollTo(offset: Int) {
- if (childCount == 0) {
+ val rangeY = computeVerticalScrollRange() - computeVerticalScrollExtent()
+ val previousThumbOffset = this.thumbOffset.coerceAtLeast(0).coerceAtMost(thumbOffsetRange)
+ val previousOffsetY = rangeY * (previousThumbOffset / thumbOffsetRange.toFloat())
+ val newThumbOffset = thumbOffset.coerceAtLeast(0).coerceAtMost(thumbOffsetRange)
+ val newOffsetY = rangeY * (newThumbOffset / thumbOffsetRange.toFloat())
+ if (newOffsetY == 0f) {
+ // Hacky workaround to drift in vertical scroll offset where we just snap
+ // to the top if the thumb offset hit zero.
+ scrollToPosition(0)
return
}
-
- stopScroll()
-
- val trueOffset = offset - paddingTop
- val itemHeight = itemHeight
-
- val firstItemPosition = 0.coerceAtLeast(trueOffset / itemHeight)
- val firstItemTop = firstItemPosition * itemHeight - trueOffset
-
- scrollToPositionWithOffset(firstItemPosition, firstItemTop)
- }
-
- private fun scrollToPositionWithOffset(position: Int, offset: Int) {
- var targetPosition = position
- val trueOffset = offset - paddingTop
-
- when (val mgr = layoutManager) {
- is GridLayoutManager -> {
- targetPosition *= mgr.spanCount
- mgr.scrollToPositionWithOffset(targetPosition, trueOffset)
- }
- is LinearLayoutManager -> {
- mgr.scrollToPositionWithOffset(targetPosition, trueOffset)
- }
- }
+ val dy = newOffsetY - previousOffsetY
+ scrollBy(0, max(dy.roundToInt(), -computeVerticalScrollOffset()))
}
// --- SCROLLBAR APPEARANCE ---
@@ -425,7 +288,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
showingThumb = true
thumbAnimator?.cancel()
- thumbAnimator = fader.fadeIn(thumbView).also { it.start() }
+ thumbAnimator = slider.slideIn(thumbView).also { it.start() }
}
private fun hideScrollbar() {
@@ -435,79 +298,16 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
showingThumb = false
thumbAnimator?.cancel()
- thumbAnimator = fader.fadeOut(thumbView).also { it.start() }
- }
-
- private fun showPopup() {
- if (showingPopup) {
- return
- }
-
- popupView.scaleX = 0f
- popupView.scaleY = 0f
-
- popupView.alpha = 1f
- showingPopup = true
- popupAnimator?.cancel()
- popupAnimator = fader.fadeIn(popupView).also { it.start() }
- }
-
- private fun hidePopup() {
- if (!showingPopup) {
- return
- }
-
- showingPopup = false
- popupAnimator?.cancel()
- popupAnimator = fader.fadeOut(popupView).also { it.start() }
+ thumbAnimator = slider.slideOut(thumbView).also { it.start() }
}
// --- LAYOUT STATE ---
private val thumbOffsetRange: Int
get() {
- return height - thumbPadding.top - thumbPadding.bottom - thumbHeight
+ return height - thumbPadding.top - thumbPadding.bottom - thumbSize
}
- private val scrollRange: Int
- get() {
- val itemCount = itemCount
-
- if (itemCount == 0) {
- return 0
- }
-
- val itemHeight = itemHeight
-
- return if (itemHeight != 0) {
- paddingTop + itemCount * itemHeight + paddingBottom
- } else {
- 0
- }
- }
-
- private val scrollOffsetRange: Int
- get() = scrollRange - height
-
- private val itemHeight: Int
- get() {
- if (childCount == 0) {
- return 0
- }
-
- val itemView = getChildAt(0)
- getDecoratedBoundsWithMargins(itemView, tRect)
- return tRect.height()
- }
-
- private val itemCount: Int
- get() =
- when (val mgr = layoutManager) {
- is GridLayoutManager -> (mgr.itemCount - 1) / mgr.spanCount + 1
- is LinearLayoutManager -> mgr.itemCount
- else -> 0
- }
-
/** An interface to provide text to use in the popup when fast-scrolling. */
interface PopupProvider {
/**
@@ -531,6 +331,6 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
}
private companion object {
- const val AUTO_HIDE_SCROLLBAR_DELAY_MILLIS = 1500
+ const val AUTO_HIDE_SCROLLBAR_DELAY_MILLIS = 500
}
}
diff --git a/app/src/main/java/org/oxycblt/auxio/ui/Animations.kt b/app/src/main/java/org/oxycblt/auxio/ui/Animations.kt
index 1d889c5a6..43155aa8d 100644
--- a/app/src/main/java/org/oxycblt/auxio/ui/Animations.kt
+++ b/app/src/main/java/org/oxycblt/auxio/ui/Animations.kt
@@ -200,3 +200,25 @@ class MaterialFlipper(context: Context) {
return AnimatorSet().apply { playTogether(outAnimator, inAnimator) }
}
}
+
+class MaterialSlider(context: Context, private val x: Int) {
+ private val outConfig =
+ AnimConfig.of(context, AnimConfig.EMPHASIZED_ACCELERATE, AnimConfig.SHORT3)
+ private val inConfig =
+ AnimConfig.of(context, AnimConfig.EMPHASIZED_DECELERATE, AnimConfig.MEDIUM1)
+
+ fun jumpOut(view: View) {
+ view.translationX = x.toFloat()
+ }
+
+ fun slideOut(view: View): Animator {
+ val animator =
+ outConfig.genericFloat(view.translationX, x.toFloat()) { view.translationX = it }
+ return animator
+ }
+
+ fun slideIn(view: View): Animator {
+ val animator = inConfig.genericFloat(view.translationX, 0f) { view.translationX = it }
+ return animator
+ }
+}
diff --git a/app/src/main/res/drawable/ic_scroll_24.xml b/app/src/main/res/drawable/ic_scroll_24.xml
new file mode 100644
index 000000000..9ab04ff6c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_scroll_24.xml
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ui_popup.xml b/app/src/main/res/drawable/ui_popup.xml
new file mode 100644
index 000000000..af0c3a354
--- /dev/null
+++ b/app/src/main/res/drawable/ui_popup.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ui_scroll_thumb.xml b/app/src/main/res/drawable/ui_scroll_thumb.xml
index 5802c85a9..ed34a3c3f 100644
--- a/app/src/main/res/drawable/ui_scroll_thumb.xml
+++ b/app/src/main/res/drawable/ui_scroll_thumb.xml
@@ -3,14 +3,9 @@
android:shape="rectangle"
android:tint="?attr/colorSecondary">
-
-
+
+ android:width="48dp"
+ android:height="48dp" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_home_list.xml b/app/src/main/res/layout/fragment_home_list.xml
index 138fef24c..fd9720d57 100644
--- a/app/src/main/res/layout/fragment_home_list.xml
+++ b/app/src/main/res/layout/fragment_home_list.xml
@@ -1,5 +1,5 @@
-
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index 3a5892b30..f611b6fde 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -25,6 +25,9 @@
40dp
48dp
+ 48dp
+ 48dp
+
16dp
128dp