Refactor and tune FastScroller
This commit is contained in:
@@ -0,0 +1,82 @@
|
|||||||
|
package org.koitharu.kotatsu.base.ui.list.fastscroll
|
||||||
|
|
||||||
|
import android.animation.Animator
|
||||||
|
import android.animation.AnimatorListenerAdapter
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewAnimationUtils
|
||||||
|
import android.view.animation.AccelerateInterpolator
|
||||||
|
import android.view.animation.DecelerateInterpolator
|
||||||
|
import androidx.core.view.isInvisible
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import org.koitharu.kotatsu.utils.ext.animatorDurationScale
|
||||||
|
import org.koitharu.kotatsu.utils.ext.measureWidth
|
||||||
|
import kotlin.math.hypot
|
||||||
|
|
||||||
|
class BubbleAnimator(
|
||||||
|
private val bubble: View,
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val animationDuration = (bubble.resources.getInteger(android.R.integer.config_shortAnimTime) *
|
||||||
|
bubble.context.animatorDurationScale).toLong()
|
||||||
|
private var animator: Animator? = null
|
||||||
|
private var isHiding = false
|
||||||
|
|
||||||
|
fun show() {
|
||||||
|
if (bubble.isVisible && !isHiding) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isHiding = false
|
||||||
|
animator?.cancel()
|
||||||
|
animator = ViewAnimationUtils.createCircularReveal(
|
||||||
|
bubble,
|
||||||
|
bubble.measureWidth(),
|
||||||
|
bubble.measuredHeight,
|
||||||
|
0f,
|
||||||
|
hypot(bubble.width.toDouble(), bubble.height.toDouble()).toFloat(),
|
||||||
|
).apply {
|
||||||
|
bubble.isVisible = true
|
||||||
|
duration = animationDuration
|
||||||
|
interpolator = DecelerateInterpolator()
|
||||||
|
start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hide() {
|
||||||
|
if (!bubble.isVisible || isHiding) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
animator?.cancel()
|
||||||
|
isHiding = true
|
||||||
|
animator = ViewAnimationUtils.createCircularReveal(
|
||||||
|
bubble,
|
||||||
|
bubble.width,
|
||||||
|
bubble.height,
|
||||||
|
hypot(bubble.width.toDouble(), bubble.height.toDouble()).toFloat(),
|
||||||
|
0f,
|
||||||
|
).apply {
|
||||||
|
duration = animationDuration
|
||||||
|
interpolator = AccelerateInterpolator()
|
||||||
|
addListener(HideListener())
|
||||||
|
start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class HideListener : AnimatorListenerAdapter() {
|
||||||
|
|
||||||
|
private var isCancelled = false
|
||||||
|
|
||||||
|
override fun onAnimationCancel(animation: Animator?) {
|
||||||
|
super.onAnimationCancel(animation)
|
||||||
|
isCancelled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAnimationEnd(animation: Animator?) {
|
||||||
|
super.onAnimationEnd(animation)
|
||||||
|
if (!isCancelled && animation === this@BubbleAnimator.animator) {
|
||||||
|
bubble.isInvisible = true
|
||||||
|
isHiding = false
|
||||||
|
this@BubbleAnimator.animator = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package org.koitharu.kotatsu.base.ui.list.fastscroll
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.annotation.AttrRes
|
||||||
|
import androidx.annotation.ColorInt
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.utils.ext.parents
|
||||||
|
|
||||||
|
class FastScrollRecyclerView @JvmOverloads constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet? = null,
|
||||||
|
@AttrRes defStyleAttr: Int = androidx.recyclerview.R.attr.recyclerViewStyle,
|
||||||
|
) : RecyclerView(context, attrs, defStyleAttr) {
|
||||||
|
|
||||||
|
val fastScroller = FastScroller(context, attrs)
|
||||||
|
|
||||||
|
init {
|
||||||
|
fastScroller.id = R.id.fast_scroller
|
||||||
|
fastScroller.layoutParams = ViewGroup.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setAdapter(adapter: Adapter<*>?) {
|
||||||
|
super.setAdapter(adapter)
|
||||||
|
fastScroller.setSectionIndexer(adapter as? FastScroller.SectionIndexer)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setVisibility(visibility: Int) {
|
||||||
|
super.setVisibility(visibility)
|
||||||
|
fastScroller.visibility = visibility
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setFastScrollListener(fastScrollListener: FastScroller.FastScrollListener?) =
|
||||||
|
fastScroller.setFastScrollListener(fastScrollListener)
|
||||||
|
|
||||||
|
fun setFastScrollEnabled(enabled: Boolean) {
|
||||||
|
fastScroller.isEnabled = enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setHideScrollbar(hideScrollbar: Boolean) = fastScroller.setHideScrollbar(hideScrollbar)
|
||||||
|
|
||||||
|
fun setTrackVisible(visible: Boolean) = fastScroller.setTrackVisible(visible)
|
||||||
|
|
||||||
|
fun setTrackColor(@ColorInt color: Int) = fastScroller.setTrackColor(color)
|
||||||
|
|
||||||
|
fun setHandleColor(@ColorInt color: Int) = fastScroller.setHandleColor(color)
|
||||||
|
|
||||||
|
@JvmOverloads
|
||||||
|
fun setBubbleVisible(visible: Boolean, always: Boolean = false) = fastScroller.setBubbleVisible(visible, always)
|
||||||
|
|
||||||
|
fun setBubbleColor(@ColorInt color: Int) = fastScroller.setBubbleColor(color)
|
||||||
|
|
||||||
|
fun setBubbleTextColor(@ColorInt color: Int) = fastScroller.setBubbleTextColor(color)
|
||||||
|
|
||||||
|
fun setBubbleTextSize(size: Int) = fastScroller.setBubbleTextSize(size)
|
||||||
|
|
||||||
|
override fun onAttachedToWindow() {
|
||||||
|
super.onAttachedToWindow()
|
||||||
|
fastScroller.attachRecyclerView(this)
|
||||||
|
for (p in parents) {
|
||||||
|
if (p is SwipeRefreshLayout) {
|
||||||
|
fastScroller.setSwipeRefreshLayout(p)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDetachedFromWindow() {
|
||||||
|
fastScroller.detachRecyclerView()
|
||||||
|
fastScroller.setSwipeRefreshLayout(null)
|
||||||
|
super.onDetachedFromWindow()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,23 +1,5 @@
|
|||||||
/*
|
package org.koitharu.kotatsu.base.ui.list.fastscroll
|
||||||
* Copyright 2022 Randy Webster. All rights reserved.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.koitharu.kotatsu.base.ui.widgets
|
|
||||||
|
|
||||||
import android.animation.Animator
|
|
||||||
import android.animation.AnimatorListenerAdapter
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.TypedArray
|
import android.content.res.TypedArray
|
||||||
@@ -25,54 +7,43 @@ import android.graphics.Color
|
|||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.view.MotionEvent
|
import android.view.*
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.view.ViewPropertyAnimator
|
|
||||||
import android.widget.*
|
import android.widget.*
|
||||||
import android.widget.RelativeLayout.*
|
import androidx.annotation.*
|
||||||
import androidx.annotation.ColorInt
|
|
||||||
import androidx.annotation.DimenRes
|
|
||||||
import androidx.annotation.DrawableRes
|
|
||||||
import androidx.annotation.StyleableRes
|
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.constraintlayout.widget.ConstraintSet
|
import androidx.constraintlayout.widget.ConstraintSet
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.withStyledAttributes
|
import androidx.core.content.withStyledAttributes
|
||||||
import androidx.core.view.GravityCompat
|
import androidx.core.view.GravityCompat
|
||||||
|
import androidx.core.view.isGone
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.databinding.FastScrollerBinding
|
||||||
import org.koitharu.kotatsu.utils.ext.*
|
import org.koitharu.kotatsu.utils.ext.*
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
import com.google.android.material.R as materialR
|
||||||
|
|
||||||
private const val BUBBLE_ANIM_DURATION = 100L
|
|
||||||
private const val SCROLLBAR_HIDE_DELAY = 1000L
|
private const val SCROLLBAR_HIDE_DELAY = 1000L
|
||||||
private const val TRACK_SNAP_RANGE = 5
|
private const val TRACK_SNAP_RANGE = 5
|
||||||
|
|
||||||
@Suppress("MemberVisibilityCanBePrivate", "unused")
|
@Suppress("MemberVisibilityCanBePrivate", "unused")
|
||||||
class FastScroller : LinearLayout {
|
class FastScroller @JvmOverloads constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet? = null,
|
||||||
|
@AttrRes defStyleAttr: Int = R.attr.fastScrollerStyle,
|
||||||
|
) : LinearLayout(context, attrs, defStyleAttr) {
|
||||||
|
|
||||||
enum class Size(@DrawableRes val drawableId: Int, @DimenRes val textSizeId: Int) {
|
enum class BubbleSize(@DrawableRes val drawableId: Int, @DimenRes val textSizeId: Int) {
|
||||||
NORMAL(R.drawable.fastscroll_bubble, R.dimen.fastscroll_bubble_text_size),
|
NORMAL(R.drawable.fastscroll_bubble, R.dimen.fastscroll_bubble_text_size),
|
||||||
SMALL(R.drawable.fastscroll_bubble_small, R.dimen.fastscroll_bubble_text_size_small)
|
SMALL(R.drawable.fastscroll_bubble_small, R.dimen.fastscroll_bubble_text_size_small)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val Size.textSize get() = resources.getDimension(textSizeId)
|
private val binding = FastScrollerBinding.inflate(LayoutInflater.from(context), this)
|
||||||
|
|
||||||
private val animationDuration = (context.resources.getInteger(R.integer.config_defaultAnimTime) *
|
private val scrollbarPaddingEnd = context.resources.getDimension(R.dimen.fastscroll_scrollbar_padding_end)
|
||||||
context.animatorDurationScale).toLong()
|
|
||||||
|
|
||||||
private val bubbleView: TextView by lazy { findViewById(R.id.fastscroll_bubble) }
|
|
||||||
private val handleView: ImageView by lazy { findViewById(R.id.fastscroll_handle) }
|
|
||||||
private val trackView: ImageView by lazy { findViewById(R.id.fastscroll_track) }
|
|
||||||
private val scrollbar: View by lazy { findViewById(R.id.fastscroll_scrollbar) }
|
|
||||||
|
|
||||||
private val scrollbarPaddingEnd by lazy {
|
|
||||||
resources.getDimensionPixelSize(R.dimen.fastscroll_scrollbar_padding_end).toFloat()
|
|
||||||
}
|
|
||||||
|
|
||||||
@ColorInt
|
@ColorInt
|
||||||
private var bubbleColor = 0
|
private var bubbleColor = 0
|
||||||
@@ -86,14 +57,14 @@ class FastScroller : LinearLayout {
|
|||||||
private var hideScrollbar = true
|
private var hideScrollbar = true
|
||||||
private var showBubble = true
|
private var showBubble = true
|
||||||
private var showBubbleAlways = false
|
private var showBubbleAlways = false
|
||||||
private var bubbleSize = Size.NORMAL
|
private var bubbleSize = BubbleSize.NORMAL
|
||||||
private var bubbleImage: Drawable? = null
|
private var bubbleImage: Drawable? = null
|
||||||
private var handleImage: Drawable? = null
|
private var handleImage: Drawable? = null
|
||||||
private var trackImage: Drawable? = null
|
private var trackImage: Drawable? = null
|
||||||
private var recyclerView: RecyclerView? = null
|
private var recyclerView: RecyclerView? = null
|
||||||
private var swipeRefreshLayout: SwipeRefreshLayout? = null
|
private var swipeRefreshLayout: SwipeRefreshLayout? = null
|
||||||
private var scrollbarAnimator: ViewPropertyAnimator? = null
|
private val scrollbarAnimator = ScrollbarAnimator(binding.scrollbar, scrollbarPaddingEnd)
|
||||||
private var bubbleAnimator: ViewPropertyAnimator? = null
|
private val bubbleAnimator = BubbleAnimator(binding.bubble)
|
||||||
|
|
||||||
private var fastScrollListener: FastScrollListener? = null
|
private var fastScrollListener: FastScrollListener? = null
|
||||||
private var sectionIndexer: SectionIndexer? = null
|
private var sectionIndexer: SectionIndexer? = null
|
||||||
@@ -103,19 +74,15 @@ class FastScroller : LinearLayout {
|
|||||||
hideScrollbar()
|
hideScrollbar()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val alphaAnimatorListener = object : AnimatorListenerAdapter() {
|
|
||||||
/* adapter required for new alpha value to stick */
|
|
||||||
}
|
|
||||||
|
|
||||||
private val scrollListener: RecyclerView.OnScrollListener = object : RecyclerView.OnScrollListener() {
|
private val scrollListener: RecyclerView.OnScrollListener = object : RecyclerView.OnScrollListener() {
|
||||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
if (!handleView.isSelected && isEnabled) {
|
if (!binding.thumb.isSelected && isEnabled) {
|
||||||
val y = recyclerView.scrollProportion
|
val y = recyclerView.scrollProportion
|
||||||
setViewPositions(y)
|
setViewPositions(y)
|
||||||
|
|
||||||
if (showBubbleAlways) {
|
if (showBubbleAlways) {
|
||||||
val targetPos = getRecyclerViewTargetPosition(y)
|
val targetPos = getRecyclerViewTargetPosition(y)
|
||||||
sectionIndexer?.let { bubbleView.text = it.getSectionText(targetPos) }
|
sectionIndexer?.let { binding.bubble.text = it.getSectionText(recyclerView.context, targetPos) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,12 +100,10 @@ class FastScroller : LinearLayout {
|
|||||||
when (newState) {
|
when (newState) {
|
||||||
RecyclerView.SCROLL_STATE_DRAGGING -> {
|
RecyclerView.SCROLL_STATE_DRAGGING -> {
|
||||||
handler.removeCallbacks(scrollbarHider)
|
handler.removeCallbacks(scrollbarHider)
|
||||||
scrollbarAnimator?.cancel()
|
showScrollbar()
|
||||||
|
|
||||||
if (!scrollbar.isVisible) showScrollbar()
|
|
||||||
if (showBubbleAlways && sectionIndexer != null) showBubble()
|
if (showBubbleAlways && sectionIndexer != null) showBubble()
|
||||||
}
|
}
|
||||||
RecyclerView.SCROLL_STATE_IDLE -> if (hideScrollbar && !handleView.isSelected) {
|
RecyclerView.SCROLL_STATE_IDLE -> if (hideScrollbar && !binding.thumb.isSelected) {
|
||||||
handler.postDelayed(scrollbarHider, SCROLLBAR_HIDE_DELAY)
|
handler.postDelayed(scrollbarHider, SCROLLBAR_HIDE_DELAY)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -153,22 +118,42 @@ class FastScroller : LinearLayout {
|
|||||||
return viewHeight * proportion
|
return viewHeight * proportion
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmOverloads
|
init {
|
||||||
constructor(context: Context, size: Size = Size.NORMAL) : super(context) {
|
clipChildren = false
|
||||||
context.layout(size = size)
|
orientation = HORIZONTAL
|
||||||
layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT)
|
|
||||||
|
@ColorInt var bubbleColor = context.getThemeColor(materialR.attr.colorControlNormal, Color.DKGRAY)
|
||||||
|
@ColorInt var handleColor = bubbleColor
|
||||||
|
@ColorInt var trackColor = context.getThemeColor(materialR.attr.colorOutline, Color.LTGRAY)
|
||||||
|
@ColorInt var textColor = context.getThemeColor(android.R.attr.textColorPrimaryInverse, Color.WHITE)
|
||||||
|
|
||||||
|
var showTrack = false
|
||||||
|
|
||||||
|
context.withStyledAttributes(attrs, R.styleable.FastScroller, defStyleAttr) {
|
||||||
|
bubbleColor = getColor(R.styleable.FastScroller_bubbleColor, bubbleColor)
|
||||||
|
handleColor = getColor(R.styleable.FastScroller_thumbColor, handleColor)
|
||||||
|
trackColor = getColor(R.styleable.FastScroller_trackColor, trackColor)
|
||||||
|
textColor = getColor(R.styleable.FastScroller_bubbleTextColor, textColor)
|
||||||
|
hideScrollbar = getBoolean(R.styleable.FastScroller_hideScrollbar, hideScrollbar)
|
||||||
|
showBubble = getBoolean(R.styleable.FastScroller_showBubble, showBubble)
|
||||||
|
showBubbleAlways = getBoolean(R.styleable.FastScroller_showBubbleAlways, showBubbleAlways)
|
||||||
|
showTrack = getBoolean(R.styleable.FastScroller_showTrack, showTrack)
|
||||||
|
bubbleSize = getBubbleSize(R.styleable.FastScroller_bubbleSize, BubbleSize.NORMAL)
|
||||||
|
val textSize = getDimension(R.styleable.FastScroller_bubbleTextSize, bubbleSize.textSize)
|
||||||
|
binding.bubble.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
setTrackColor(trackColor)
|
||||||
|
setHandleColor(handleColor)
|
||||||
|
setBubbleColor(bubbleColor)
|
||||||
|
setBubbleTextColor(textColor)
|
||||||
|
setHideScrollbar(hideScrollbar)
|
||||||
|
setBubbleVisible(showBubble, showBubbleAlways)
|
||||||
|
setTrackVisible(showTrack)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmOverloads
|
override fun onSizeChanged(w: Int, h: Int, oldW: Int, oldH: Int) {
|
||||||
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int = 0) : super(context, attrs, defStyleAttr) {
|
super.onSizeChanged(w, h, oldW, oldH)
|
||||||
context.layout(attrs)
|
|
||||||
layoutParams = attrs?.let { generateLayoutParams(it) } ?: LayoutParams(
|
|
||||||
LayoutParams.WRAP_CONTENT,
|
|
||||||
LayoutParams.MATCH_PARENT
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSizeChanged(w: Int, h: Int, oldW: Int, oldH: Int) = super.onSizeChanged(w, h, oldW, oldH).also {
|
|
||||||
viewHeight = h
|
viewHeight = h
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,18 +165,15 @@ class FastScroller : LinearLayout {
|
|||||||
setRecyclerViewPosition(y)
|
setRecyclerViewPosition(y)
|
||||||
}
|
}
|
||||||
|
|
||||||
when (event.action) {
|
when (event.actionMasked) {
|
||||||
MotionEvent.ACTION_DOWN -> {
|
MotionEvent.ACTION_DOWN -> {
|
||||||
if (event.x < handleView.x - scrollbar.compatPaddingStart) return false
|
if (event.x.toInt() !in binding.scrollbar.left..binding.scrollbar.right) return false
|
||||||
|
|
||||||
requestDisallowInterceptTouchEvent(true)
|
requestDisallowInterceptTouchEvent(true)
|
||||||
setHandleSelected(true)
|
setHandleSelected(true)
|
||||||
|
|
||||||
handler.removeCallbacks(scrollbarHider)
|
handler.removeCallbacks(scrollbarHider)
|
||||||
scrollbarAnimator?.cancel()
|
showScrollbar()
|
||||||
bubbleAnimator?.cancel()
|
|
||||||
|
|
||||||
if (!scrollbar.isVisible) showScrollbar()
|
|
||||||
if (showBubble && sectionIndexer != null) showBubble()
|
if (showBubble && sectionIndexer != null) showBubble()
|
||||||
|
|
||||||
fastScrollListener?.onFastScrollStart(this)
|
fastScrollListener?.onFastScrollStart(this)
|
||||||
@@ -224,7 +206,8 @@ class FastScroller : LinearLayout {
|
|||||||
*
|
*
|
||||||
* @param enabled True if this view is enabled, false otherwise
|
* @param enabled True if this view is enabled, false otherwise
|
||||||
*/
|
*/
|
||||||
override fun setEnabled(enabled: Boolean) = super.setEnabled(enabled).also {
|
override fun setEnabled(enabled: Boolean) {
|
||||||
|
super.setEnabled(enabled)
|
||||||
isVisible = enabled
|
isVisible = enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,9 +266,9 @@ class FastScroller : LinearLayout {
|
|||||||
}
|
}
|
||||||
is RelativeLayout -> layoutParams = (layoutParams as RelativeLayout.LayoutParams).apply {
|
is RelativeLayout -> layoutParams = (layoutParams as RelativeLayout.LayoutParams).apply {
|
||||||
height = 0
|
height = 0
|
||||||
addRule(ALIGN_TOP, recyclerViewId)
|
addRule(RelativeLayout.ALIGN_TOP, recyclerViewId)
|
||||||
addRule(ALIGN_BOTTOM, recyclerViewId)
|
addRule(RelativeLayout.ALIGN_BOTTOM, recyclerViewId)
|
||||||
addRule(ALIGN_END, recyclerViewId)
|
addRule(RelativeLayout.ALIGN_END, recyclerViewId)
|
||||||
setMargins(0, marginTop, 0, marginBottom)
|
setMargins(0, marginTop, 0, marginBottom)
|
||||||
}
|
}
|
||||||
else -> throw IllegalArgumentException("Parent ViewGroup must be a ConstraintLayout, CoordinatorLayout, FrameLayout, or RelativeLayout")
|
else -> throw IllegalArgumentException("Parent ViewGroup must be a ConstraintLayout, CoordinatorLayout, FrameLayout, or RelativeLayout")
|
||||||
@@ -302,6 +285,9 @@ class FastScroller : LinearLayout {
|
|||||||
* @see detachRecyclerView
|
* @see detachRecyclerView
|
||||||
*/
|
*/
|
||||||
fun attachRecyclerView(recyclerView: RecyclerView) {
|
fun attachRecyclerView(recyclerView: RecyclerView) {
|
||||||
|
if (this.recyclerView != null) {
|
||||||
|
detachRecyclerView()
|
||||||
|
}
|
||||||
this.recyclerView = recyclerView
|
this.recyclerView = recyclerView
|
||||||
|
|
||||||
if (parent is ViewGroup) {
|
if (parent is ViewGroup) {
|
||||||
@@ -314,7 +300,7 @@ class FastScroller : LinearLayout {
|
|||||||
|
|
||||||
recyclerView.addOnScrollListener(scrollListener)
|
recyclerView.addOnScrollListener(scrollListener)
|
||||||
|
|
||||||
// set initial positions for bubble and handle
|
// set initial positions for bubble and thumb
|
||||||
post { setViewPositions(this.recyclerView?.scrollProportion ?: 0f) }
|
post { setViewPositions(this.recyclerView?.scrollProportion ?: 0f) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -365,7 +351,8 @@ class FastScroller : LinearLayout {
|
|||||||
*/
|
*/
|
||||||
fun setHideScrollbar(hideScrollbar: Boolean) {
|
fun setHideScrollbar(hideScrollbar: Boolean) {
|
||||||
if (this.hideScrollbar != hideScrollbar) {
|
if (this.hideScrollbar != hideScrollbar) {
|
||||||
scrollbar.isVisible = !hideScrollbar.also { this.hideScrollbar = it }
|
this.hideScrollbar = hideScrollbar
|
||||||
|
binding.scrollbar.isGone = hideScrollbar
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -375,7 +362,7 @@ class FastScroller : LinearLayout {
|
|||||||
* @param visible True to show scroll track, false to hide
|
* @param visible True to show scroll track, false to hide
|
||||||
*/
|
*/
|
||||||
fun setTrackVisible(visible: Boolean) {
|
fun setTrackVisible(visible: Boolean) {
|
||||||
trackView.isVisible = visible
|
binding.track.isVisible = visible
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -390,14 +377,14 @@ class FastScroller : LinearLayout {
|
|||||||
|
|
||||||
trackImage?.let {
|
trackImage?.let {
|
||||||
it.setTint(color)
|
it.setTint(color)
|
||||||
trackView.setImageDrawable(it)
|
binding.track.setImageDrawable(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the color of the scroll handle.
|
* Set the color of the scroll thumb.
|
||||||
*
|
*
|
||||||
* @param color The color for the scroll handle
|
* @param color The color for the scroll thumb
|
||||||
*/
|
*/
|
||||||
fun setHandleColor(@ColorInt color: Int) {
|
fun setHandleColor(@ColorInt color: Int) {
|
||||||
handleColor = color
|
handleColor = color
|
||||||
@@ -408,7 +395,7 @@ class FastScroller : LinearLayout {
|
|||||||
|
|
||||||
handleImage?.let {
|
handleImage?.let {
|
||||||
it.setTint(handleColor)
|
it.setTint(handleColor)
|
||||||
handleView.setImageDrawable(it)
|
binding.thumb.setImageDrawable(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -416,7 +403,7 @@ class FastScroller : LinearLayout {
|
|||||||
* Show the section bubble while scrolling.
|
* Show the section bubble while scrolling.
|
||||||
*
|
*
|
||||||
* @param visible True to show the bubble, false to hide
|
* @param visible True to show the bubble, false to hide
|
||||||
* @param always True to always show the bubble, false to only show on handle touch
|
* @param always True to always show the bubble, false to only show on thumb touch
|
||||||
*/
|
*/
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun setBubbleVisible(visible: Boolean, always: Boolean = false) {
|
fun setBubbleVisible(visible: Boolean, always: Boolean = false) {
|
||||||
@@ -438,7 +425,7 @@ class FastScroller : LinearLayout {
|
|||||||
|
|
||||||
bubbleImage?.let {
|
bubbleImage?.let {
|
||||||
it.setTint(bubbleColor)
|
it.setTint(bubbleColor)
|
||||||
bubbleView.background = it
|
binding.bubble.background = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -447,7 +434,7 @@ class FastScroller : LinearLayout {
|
|||||||
*
|
*
|
||||||
* @param color The text color for the section bubble
|
* @param color The text color for the section bubble
|
||||||
*/
|
*/
|
||||||
fun setBubbleTextColor(@ColorInt color: Int) = bubbleView.setTextColor(color)
|
fun setBubbleTextColor(@ColorInt color: Int) = binding.bubble.setTextColor(color)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the scaled pixel text size of the section bubble.
|
* Set the scaled pixel text size of the section bubble.
|
||||||
@@ -455,15 +442,15 @@ class FastScroller : LinearLayout {
|
|||||||
* @param size The scaled pixel text size for the section bubble
|
* @param size The scaled pixel text size for the section bubble
|
||||||
*/
|
*/
|
||||||
fun setBubbleTextSize(size: Int) {
|
fun setBubbleTextSize(size: Int) {
|
||||||
bubbleView.textSize = size.toFloat()
|
binding.bubble.textSize = size.toFloat()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getRecyclerViewTargetPosition(y: Float) = recyclerView?.let { recyclerView ->
|
private fun getRecyclerViewTargetPosition(y: Float) = recyclerView?.let { recyclerView ->
|
||||||
val itemCount = recyclerView.adapter?.itemCount ?: 0
|
val itemCount = recyclerView.adapter?.itemCount ?: 0
|
||||||
|
|
||||||
val proportion = when {
|
val proportion = when {
|
||||||
handleView.y == 0f -> 0f
|
binding.thumb.y == 0f -> 0f
|
||||||
handleView.y + handleHeight >= viewHeight - TRACK_SNAP_RANGE -> 1f
|
binding.thumb.y + handleHeight >= viewHeight - TRACK_SNAP_RANGE -> 1f
|
||||||
else -> y / viewHeight.toFloat()
|
else -> y / viewHeight.toFloat()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -477,139 +464,65 @@ class FastScroller : LinearLayout {
|
|||||||
} ?: 0
|
} ?: 0
|
||||||
|
|
||||||
private fun setRecyclerViewPosition(y: Float) {
|
private fun setRecyclerViewPosition(y: Float) {
|
||||||
recyclerView?.layoutManager?.let { layoutManager ->
|
val layoutManager = recyclerView?.layoutManager ?: return
|
||||||
val targetPos = getRecyclerViewTargetPosition(y)
|
val targetPos = getRecyclerViewTargetPosition(y)
|
||||||
layoutManager.scrollToPosition(targetPos)
|
layoutManager.scrollToPosition(targetPos)
|
||||||
if (showBubble) sectionIndexer?.let { bubbleView.text = it.getSectionText(targetPos) }
|
if (showBubble) sectionIndexer?.let { binding.bubble.text = it.getSectionText(context, targetPos) }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setViewPositions(y: Float) {
|
private fun setViewPositions(y: Float) {
|
||||||
bubbleHeight = bubbleView.measuredHeight
|
bubbleHeight = binding.bubble.measuredHeight
|
||||||
handleHeight = handleView.measuredHeight
|
handleHeight = binding.thumb.measuredHeight
|
||||||
|
|
||||||
val bubbleHandleHeight = bubbleHeight + handleHeight / 2f
|
val bubbleHandleHeight = bubbleHeight + handleHeight / 2f
|
||||||
|
|
||||||
if (showBubble && viewHeight >= bubbleHandleHeight) {
|
if (showBubble && viewHeight >= bubbleHandleHeight) {
|
||||||
bubbleView.y = (y - bubbleHeight).coerceIn(0f, viewHeight - bubbleHandleHeight)
|
binding.bubble.y = (y - bubbleHeight).coerceIn(0f, viewHeight - bubbleHandleHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (viewHeight >= handleHeight) {
|
if (viewHeight >= handleHeight) {
|
||||||
handleView.y = (y - handleHeight / 2).coerceIn(0f, viewHeight - handleHeight.toFloat())
|
binding.thumb.y = (y - handleHeight / 2).coerceIn(0f, viewHeight - handleHeight.toFloat())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateViewHeights() {
|
private fun updateViewHeights() {
|
||||||
val measureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
|
val measureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
|
||||||
bubbleView.measure(measureSpec, measureSpec)
|
binding.bubble.measure(measureSpec, measureSpec)
|
||||||
bubbleHeight = bubbleView.measuredHeight
|
bubbleHeight = binding.bubble.measuredHeight
|
||||||
handleView.measure(measureSpec, measureSpec)
|
binding.thumb.measure(measureSpec, measureSpec)
|
||||||
handleHeight = handleView.measuredHeight
|
handleHeight = binding.thumb.measuredHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showBubble() {
|
private fun showBubble() {
|
||||||
if (!bubbleView.isVisible) {
|
bubbleAnimator.show()
|
||||||
bubbleView.isVisible = true
|
|
||||||
bubbleAnimator = bubbleView.animate().alpha(1f)
|
|
||||||
.setDuration(BUBBLE_ANIM_DURATION)
|
|
||||||
.setListener(alphaAnimatorListener)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun hideBubble() {
|
private fun hideBubble() {
|
||||||
if (bubbleView.isVisible) {
|
bubbleAnimator.hide()
|
||||||
bubbleAnimator = bubbleView.animate().alpha(0f)
|
|
||||||
.setDuration(BUBBLE_ANIM_DURATION)
|
|
||||||
.setListener(object : AnimatorListenerAdapter() {
|
|
||||||
override fun onAnimationEnd(animation: Animator) {
|
|
||||||
super.onAnimationEnd(animation)
|
|
||||||
bubbleView.isVisible = false
|
|
||||||
bubbleAnimator = null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAnimationCancel(animation: Animator) {
|
|
||||||
super.onAnimationCancel(animation)
|
|
||||||
bubbleView.isVisible = false
|
|
||||||
bubbleAnimator = null
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showScrollbar() {
|
private fun showScrollbar() {
|
||||||
if ((recyclerView?.computeVerticalScrollRange() ?: (0 - viewHeight)) > 0) {
|
if ((recyclerView?.computeVerticalScrollRange() ?: (0 - viewHeight)) > 0) {
|
||||||
scrollbar.translationX = scrollbarPaddingEnd
|
scrollbarAnimator.show()
|
||||||
scrollbar.isVisible = true
|
|
||||||
scrollbarAnimator = scrollbar.animate().translationX(0f).alpha(1f)
|
|
||||||
.setDuration(animationDuration)
|
|
||||||
.setListener(alphaAnimatorListener)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun hideScrollbar() {
|
private fun hideScrollbar() {
|
||||||
scrollbarAnimator = scrollbar.animate().translationX(scrollbarPaddingEnd).alpha(0f)
|
scrollbarAnimator.hide()
|
||||||
.setDuration(animationDuration)
|
|
||||||
.setListener(object : AnimatorListenerAdapter() {
|
|
||||||
override fun onAnimationEnd(animation: Animator) {
|
|
||||||
super.onAnimationEnd(animation)
|
|
||||||
scrollbar.isVisible = false
|
|
||||||
scrollbarAnimator = null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAnimationCancel(animation: Animator) {
|
|
||||||
super.onAnimationCancel(animation)
|
|
||||||
scrollbar.isVisible = false
|
|
||||||
scrollbarAnimator = null
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setHandleSelected(selected: Boolean) {
|
private fun setHandleSelected(selected: Boolean) {
|
||||||
handleView.isSelected = selected
|
binding.thumb.isSelected = selected
|
||||||
handleImage?.setTint(if (selected) bubbleColor else handleColor)
|
handleImage?.setTint(if (selected) bubbleColor else handleColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun TypedArray.getSize(@StyleableRes index: Int, defValue: Int) = getInt(index, defValue).let { ordinal ->
|
private fun TypedArray.getBubbleSize(@StyleableRes index: Int, defaultValue: BubbleSize): BubbleSize {
|
||||||
Size.values().find { it.ordinal == ordinal } ?: Size.NORMAL
|
val ordinal = getInt(index, -1)
|
||||||
|
return BubbleSize.values().getOrNull(ordinal) ?: defaultValue
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Context.layout(attrs: AttributeSet? = null, size: Size = Size.NORMAL) {
|
private val BubbleSize.textSize
|
||||||
inflate(this, R.layout.fast_scroller, this@FastScroller)
|
@Px get() = resources.getDimension(textSizeId)
|
||||||
|
|
||||||
clipChildren = false
|
|
||||||
orientation = HORIZONTAL
|
|
||||||
|
|
||||||
@ColorInt var bubbleColor = Color.GRAY
|
|
||||||
@ColorInt var handleColor = Color.DKGRAY
|
|
||||||
@ColorInt var trackColor = Color.LTGRAY
|
|
||||||
@ColorInt var textColor = Color.WHITE
|
|
||||||
|
|
||||||
var showTrack = false
|
|
||||||
var textSize = size.textSize
|
|
||||||
|
|
||||||
withStyledAttributes(attrs, R.styleable.FastScroller) {
|
|
||||||
bubbleColor = getColor(R.styleable.FastScroller_bubbleColor, bubbleColor)
|
|
||||||
handleColor = getColor(R.styleable.FastScroller_handleColor, handleColor)
|
|
||||||
trackColor = getColor(R.styleable.FastScroller_trackColor, trackColor)
|
|
||||||
textColor = getColor(R.styleable.FastScroller_bubbleTextColor, textColor)
|
|
||||||
hideScrollbar = getBoolean(R.styleable.FastScroller_hideScrollbar, hideScrollbar)
|
|
||||||
showBubble = getBoolean(R.styleable.FastScroller_showBubble, showBubble)
|
|
||||||
showBubbleAlways = getBoolean(R.styleable.FastScroller_showBubbleAlways, showBubbleAlways)
|
|
||||||
showTrack = getBoolean(R.styleable.FastScroller_showTrack, showTrack)
|
|
||||||
bubbleSize = getSize(R.styleable.FastScroller_bubbleSize, size.ordinal)
|
|
||||||
textSize = getDimension(R.styleable.FastScroller_bubbleTextSize, bubbleSize.textSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
setTrackColor(trackColor)
|
|
||||||
setHandleColor(handleColor)
|
|
||||||
setBubbleColor(bubbleColor)
|
|
||||||
setBubbleTextColor(textColor)
|
|
||||||
setHideScrollbar(hideScrollbar)
|
|
||||||
setBubbleVisible(showBubble, showBubbleAlways)
|
|
||||||
setTrackVisible(showTrack)
|
|
||||||
|
|
||||||
bubbleView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
interface FastScrollListener {
|
interface FastScrollListener {
|
||||||
|
|
||||||
@@ -620,6 +533,6 @@ class FastScroller : LinearLayout {
|
|||||||
|
|
||||||
interface SectionIndexer {
|
interface SectionIndexer {
|
||||||
|
|
||||||
fun getSectionText(position: Int): CharSequence
|
fun getSectionText(context: Context, position: Int): CharSequence
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package org.koitharu.kotatsu.base.ui.list.fastscroll
|
||||||
|
|
||||||
|
import android.animation.Animator
|
||||||
|
import android.animation.AnimatorListenerAdapter
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewPropertyAnimator
|
||||||
|
import androidx.core.view.isInvisible
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.utils.ext.animatorDurationScale
|
||||||
|
|
||||||
|
class ScrollbarAnimator(
|
||||||
|
private val scrollbar: View,
|
||||||
|
private val scrollbarPaddingEnd: Float,
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val animationDuration = (scrollbar.resources.getInteger(R.integer.config_defaultAnimTime) *
|
||||||
|
scrollbar.context.animatorDurationScale).toLong()
|
||||||
|
private var animator: ViewPropertyAnimator? = null
|
||||||
|
private var isHiding = false
|
||||||
|
|
||||||
|
fun show() {
|
||||||
|
if (scrollbar.isVisible && !isHiding) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isHiding = false
|
||||||
|
animator?.cancel()
|
||||||
|
scrollbar.translationX = scrollbarPaddingEnd
|
||||||
|
scrollbar.isVisible = true
|
||||||
|
animator = scrollbar
|
||||||
|
.animate()
|
||||||
|
.translationX(0f)
|
||||||
|
.alpha(1f)
|
||||||
|
.setDuration(animationDuration)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hide() {
|
||||||
|
if (!scrollbar.isVisible || isHiding) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
animator?.cancel()
|
||||||
|
isHiding = true
|
||||||
|
animator = scrollbar
|
||||||
|
.animate()
|
||||||
|
.translationX(scrollbarPaddingEnd)
|
||||||
|
.alpha(0f)
|
||||||
|
.setDuration(animationDuration)
|
||||||
|
.setListener(HideListener())
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class HideListener : AnimatorListenerAdapter() {
|
||||||
|
|
||||||
|
private var isCancelled = false
|
||||||
|
|
||||||
|
override fun onAnimationCancel(animation: Animator?) {
|
||||||
|
super.onAnimationCancel(animation)
|
||||||
|
isCancelled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAnimationEnd(animation: Animator?) {
|
||||||
|
super.onAnimationEnd(animation)
|
||||||
|
if (!isCancelled && animation === this@ScrollbarAnimator.animator) {
|
||||||
|
scrollbar.isInvisible = true
|
||||||
|
isHiding = false
|
||||||
|
this@ScrollbarAnimator.animator = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2022 Randy Webster. All rights reserved.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.koitharu.kotatsu.base.ui.widgets
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.util.AttributeSet
|
|
||||||
import androidx.annotation.ColorInt
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import org.koitharu.kotatsu.R
|
|
||||||
|
|
||||||
class FastScrollRecyclerView : RecyclerView {
|
|
||||||
|
|
||||||
private val fastScroller: FastScroller
|
|
||||||
|
|
||||||
constructor(context: Context) : super(context) {
|
|
||||||
fastScroller = context.layout()
|
|
||||||
layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmOverloads
|
|
||||||
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int = 0) : super(context, attrs, defStyleAttr) {
|
|
||||||
fastScroller = context.layout(attrs)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setAdapter(adapter: Adapter<*>?) = super.setAdapter(adapter).also {
|
|
||||||
when (adapter) {
|
|
||||||
is FastScroller.SectionIndexer -> fastScroller.setSectionIndexer(adapter)
|
|
||||||
null -> fastScroller.setSectionIndexer(null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setVisibility(visibility: Int) = super.setVisibility(visibility).also {
|
|
||||||
fastScroller.visibility = visibility
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setFastScrollListener(fastScrollListener: FastScroller.FastScrollListener?) =
|
|
||||||
fastScroller.setFastScrollListener(fastScrollListener)
|
|
||||||
|
|
||||||
fun setFastScrollEnabled(enabled: Boolean) {
|
|
||||||
fastScroller.isEnabled = enabled
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setHideScrollbar(hideScrollbar: Boolean) = fastScroller.setHideScrollbar(hideScrollbar)
|
|
||||||
|
|
||||||
fun setTrackVisible(visible: Boolean) = fastScroller.setTrackVisible(visible)
|
|
||||||
|
|
||||||
fun setTrackColor(@ColorInt color: Int) = fastScroller.setTrackColor(color)
|
|
||||||
|
|
||||||
fun setHandleColor(@ColorInt color: Int) = fastScroller.setHandleColor(color)
|
|
||||||
|
|
||||||
@JvmOverloads
|
|
||||||
fun setBubbleVisible(visible: Boolean, always: Boolean = false) = fastScroller.setBubbleVisible(visible, always)
|
|
||||||
|
|
||||||
fun setBubbleColor(@ColorInt color: Int) = fastScroller.setBubbleColor(color)
|
|
||||||
|
|
||||||
fun setBubbleTextColor(@ColorInt color: Int) = fastScroller.setBubbleTextColor(color)
|
|
||||||
|
|
||||||
fun setBubbleTextSize(size: Int) = fastScroller.setBubbleTextSize(size)
|
|
||||||
|
|
||||||
override fun onAttachedToWindow() = super.onAttachedToWindow().also {
|
|
||||||
fastScroller.attachRecyclerView(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDetachedFromWindow() {
|
|
||||||
fastScroller.detachRecyclerView()
|
|
||||||
super.onDetachedFromWindow()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Context.layout(attrs: AttributeSet? = null) =
|
|
||||||
FastScroller(this, attrs).apply { id = R.id.fast_scroller }
|
|
||||||
}
|
|
||||||
@@ -10,6 +10,7 @@ import androidx.appcompat.widget.SearchView
|
|||||||
import androidx.core.graphics.Insets
|
import androidx.core.graphics.Insets
|
||||||
import androidx.core.view.MenuProvider
|
import androidx.core.view.MenuProvider
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.core.view.updateLayoutParams
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
|
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
|
||||||
@@ -29,6 +30,7 @@ import org.koitharu.kotatsu.reader.ui.ReaderActivity
|
|||||||
import org.koitharu.kotatsu.reader.ui.ReaderState
|
import org.koitharu.kotatsu.reader.ui.ReaderState
|
||||||
import org.koitharu.kotatsu.utils.RecyclerViewScrollCallback
|
import org.koitharu.kotatsu.utils.RecyclerViewScrollCallback
|
||||||
import org.koitharu.kotatsu.utils.ext.addMenuProvider
|
import org.koitharu.kotatsu.utils.ext.addMenuProvider
|
||||||
|
import org.koitharu.kotatsu.utils.ext.end
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
class ChaptersFragment :
|
class ChaptersFragment :
|
||||||
@@ -192,6 +194,9 @@ class ChaptersFragment :
|
|||||||
binding.recyclerViewChapters.updatePadding(
|
binding.recyclerViewChapters.updatePadding(
|
||||||
bottom = insets.bottom + (binding.spinnerBranches?.height ?: 0),
|
bottom = insets.bottom + (binding.spinnerBranches?.height ?: 0),
|
||||||
)
|
)
|
||||||
|
binding.recyclerViewChapters.fastScroller.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
|
bottomMargin = insets.bottom
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initSpinner(spinner: Spinner) {
|
private fun initSpinner(spinner: Spinner) {
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
package org.koitharu.kotatsu.details.ui.adapter
|
package org.koitharu.kotatsu.details.ui.adapter
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
|
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
|
||||||
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||||
import org.koitharu.kotatsu.base.ui.widgets.FastScroller
|
import org.koitharu.kotatsu.base.ui.list.fastscroll.FastScroller
|
||||||
import org.koitharu.kotatsu.details.ui.model.ChapterListItem
|
import org.koitharu.kotatsu.details.ui.model.ChapterListItem
|
||||||
import kotlin.jvm.internal.Intrinsics
|
import kotlin.jvm.internal.Intrinsics
|
||||||
|
|
||||||
class ChaptersAdapter(
|
class ChaptersAdapter(
|
||||||
onItemClickListener: OnListItemClickListener<ChapterListItem>,
|
onItemClickListener: OnListItemClickListener<ChapterListItem>,
|
||||||
) : AsyncListDifferDelegationAdapter<ChapterListItem>(DiffCallback()), FastScroller.SectionIndexer {
|
) : AsyncListDifferDelegationAdapter<ChapterListItem>(DiffCallback()), FastScroller.SectionIndexer {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
setHasStableIds(true)
|
setHasStableIds(true)
|
||||||
@@ -41,7 +42,7 @@ class ChaptersAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getSectionText(position: Int): CharSequence {
|
override fun getSectionText(context: Context, position: Int): CharSequence {
|
||||||
return items[position].chapter.number.toString()
|
return items[position].chapter.number.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package org.koitharu.kotatsu.history.ui
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import coil.ImageLoader
|
||||||
|
import org.koitharu.kotatsu.base.ui.list.fastscroll.FastScroller
|
||||||
|
import org.koitharu.kotatsu.core.ui.DateTimeAgo
|
||||||
|
import org.koitharu.kotatsu.list.ui.adapter.MangaListAdapter
|
||||||
|
import org.koitharu.kotatsu.list.ui.adapter.MangaListListener
|
||||||
|
|
||||||
|
class HistoryListAdapter(
|
||||||
|
coil: ImageLoader,
|
||||||
|
lifecycleOwner: LifecycleOwner,
|
||||||
|
listener: MangaListListener
|
||||||
|
) : MangaListAdapter(coil, lifecycleOwner, listener), FastScroller.SectionIndexer {
|
||||||
|
|
||||||
|
override fun getSectionText(context: Context, position: Int): CharSequence {
|
||||||
|
val list = items
|
||||||
|
for (i in (0..position).reversed()) {
|
||||||
|
val item = list[i]
|
||||||
|
if (item is DateTimeAgo) {
|
||||||
|
return item.format(context.resources)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import android.view.MenuItem
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.appcompat.view.ActionMode
|
import androidx.appcompat.view.ActionMode
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import org.koin.android.ext.android.get
|
||||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.base.domain.ReversibleHandle
|
import org.koitharu.kotatsu.base.domain.ReversibleHandle
|
||||||
@@ -53,6 +54,8 @@ class HistoryListFragment : MangaListFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onCreateAdapter() = HistoryListAdapter(get(), viewLifecycleOwner, this)
|
||||||
|
|
||||||
private fun onItemsRemoved(reversibleHandle: ReversibleHandle) {
|
private fun onItemsRemoved(reversibleHandle: ReversibleHandle) {
|
||||||
Snackbar.make(binding.recyclerView, R.string.removed_from_history, Snackbar.LENGTH_LONG)
|
Snackbar.make(binding.recyclerView, R.string.removed_from_history, Snackbar.LENGTH_LONG)
|
||||||
.setAction(R.string.undo) { reversibleHandle.reverseAsync() }
|
.setAction(R.string.undo) { reversibleHandle.reverseAsync() }
|
||||||
|
|||||||
@@ -2,11 +2,13 @@ package org.koitharu.kotatsu.list.ui
|
|||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.*
|
import android.view.*
|
||||||
|
import android.view.ViewGroup.MarginLayoutParams
|
||||||
import androidx.annotation.CallSuper
|
import androidx.annotation.CallSuper
|
||||||
import androidx.appcompat.view.ActionMode
|
import androidx.appcompat.view.ActionMode
|
||||||
import androidx.collection.ArraySet
|
import androidx.collection.ArraySet
|
||||||
import androidx.core.graphics.Insets
|
import androidx.core.graphics.Insets
|
||||||
import androidx.core.view.isNotEmpty
|
import androidx.core.view.isNotEmpty
|
||||||
|
import androidx.core.view.updateLayoutParams
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
@@ -73,11 +75,7 @@ abstract class MangaListFragment :
|
|||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
listAdapter = MangaListAdapter(
|
listAdapter = onCreateAdapter()
|
||||||
coil = get(),
|
|
||||||
lifecycleOwner = viewLifecycleOwner,
|
|
||||||
listener = this,
|
|
||||||
)
|
|
||||||
selectionController = ListSelectionController(
|
selectionController = ListSelectionController(
|
||||||
activity = requireActivity(),
|
activity = requireActivity(),
|
||||||
decoration = MangaSelectionDecoration(view.context),
|
decoration = MangaSelectionDecoration(view.context),
|
||||||
@@ -167,6 +165,14 @@ abstract class MangaListFragment :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected open fun onCreateAdapter(): MangaListAdapter {
|
||||||
|
return MangaListAdapter(
|
||||||
|
coil = get(),
|
||||||
|
lifecycleOwner = viewLifecycleOwner,
|
||||||
|
listener = this,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onWindowInsetsChanged(insets: Insets) {
|
override fun onWindowInsetsChanged(insets: Insets) {
|
||||||
binding.root.updatePadding(
|
binding.root.updatePadding(
|
||||||
left = insets.left,
|
left = insets.left,
|
||||||
@@ -175,6 +181,10 @@ abstract class MangaListFragment :
|
|||||||
binding.recyclerView.updatePadding(
|
binding.recyclerView.updatePadding(
|
||||||
bottom = insets.bottom,
|
bottom = insets.bottom,
|
||||||
)
|
)
|
||||||
|
binding.recyclerView.fastScroller.updateLayoutParams<MarginLayoutParams> {
|
||||||
|
bottomMargin = insets.bottom
|
||||||
|
marginEnd = insets.end(binding.recyclerView)
|
||||||
|
}
|
||||||
if (activity is MainActivity) {
|
if (activity is MainActivity) {
|
||||||
val headerHeight = (activity as? AppBarOwner)?.appBar?.measureHeight() ?: insets.top
|
val headerHeight = (activity as? AppBarOwner)?.appBar?.measureHeight() ?: insets.top
|
||||||
binding.swipeRefreshLayout.setProgressViewOffset(
|
binding.swipeRefreshLayout.setProgressViewOffset(
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import org.koitharu.kotatsu.core.ui.DateTimeAgo
|
|||||||
import org.koitharu.kotatsu.list.ui.model.*
|
import org.koitharu.kotatsu.list.ui.model.*
|
||||||
import kotlin.jvm.internal.Intrinsics
|
import kotlin.jvm.internal.Intrinsics
|
||||||
|
|
||||||
class MangaListAdapter(
|
open class MangaListAdapter(
|
||||||
coil: ImageLoader,
|
coil: ImageLoader,
|
||||||
lifecycleOwner: LifecycleOwner,
|
lifecycleOwner: LifecycleOwner,
|
||||||
listener: MangaListListener,
|
listener: MangaListListener,
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package org.koitharu.kotatsu.utils.ext
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import androidx.core.graphics.Insets
|
||||||
|
|
||||||
|
fun Insets.end(view: View): Int {
|
||||||
|
return if (view.layoutDirection == View.LAYOUT_DIRECTION_RTL) left else right
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Insets.start(view: View): Int {
|
||||||
|
return if (view.layoutDirection == View.LAYOUT_DIRECTION_RTL) right else left
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import android.app.Activity
|
|||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.view.ViewParent
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.children
|
import androidx.core.view.children
|
||||||
@@ -147,4 +148,13 @@ fun RecyclerView.invalidateNestedItemDecorations() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal val View.compatPaddingStart get() = ViewCompat.getPaddingStart(this)
|
internal val View.compatPaddingStart get() = ViewCompat.getPaddingStart(this)
|
||||||
|
|
||||||
|
val View.parents: Sequence<ViewParent>
|
||||||
|
get() = sequence {
|
||||||
|
var p: ViewParent? = parent
|
||||||
|
while (p != null) {
|
||||||
|
yield(p)
|
||||||
|
p = p.parent
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<shape
|
<shape
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:shape="rectangle">
|
android:shape="rectangle">
|
||||||
|
|
||||||
<tools:solid android:color="#777777" />
|
|
||||||
|
|
||||||
<corners
|
<corners
|
||||||
android:topLeftRadius="@dimen/fastscroll_bubble_radius"
|
android:topLeftRadius="@dimen/fastscroll_bubble_radius"
|
||||||
android:topRightRadius="@dimen/fastscroll_bubble_radius"
|
android:topRightRadius="@dimen/fastscroll_bubble_radius"
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
android:id="@+id/appbar"
|
android:id="@+id/appbar"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:fitsSystemWindows="true"
|
||||||
app:elevation="0dp"
|
app:elevation="0dp"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
|||||||
@@ -6,13 +6,12 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<org.koitharu.kotatsu.base.ui.widgets.FastScrollRecyclerView
|
<org.koitharu.kotatsu.base.ui.list.fastscroll.FastScrollRecyclerView
|
||||||
android:id="@+id/recyclerView_chapters"
|
android:id="@+id/recyclerView_chapters"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
app:handleColor="?attr/colorTertiary"
|
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
tools:listitem="@layout/item_chapter" />
|
tools:listitem="@layout/item_chapter" />
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
<!--
|
|
||||||
~ Copyright 2022 Randy Webster. All rights reserved.
|
~ Copyright 2022 Randy Webster. All rights reserved.
|
||||||
~
|
~
|
||||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -17,45 +16,54 @@
|
|||||||
|
|
||||||
<merge
|
<merge
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
tools:clipChildren="false"
|
||||||
|
tools:layout_gravity="end"
|
||||||
|
tools:layout_height="match_parent"
|
||||||
|
tools:layout_width="wrap_content"
|
||||||
|
tools:orientation="horizontal"
|
||||||
|
tools:parentTag="android.widget.LinearLayout">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/fastscroll_bubble"
|
android:id="@+id/bubble"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="end"
|
android:layout_gravity="end"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:maxLines="1"
|
android:singleLine="true"
|
||||||
android:visibility="gone"
|
android:visibility="invisible"
|
||||||
tools:background="@drawable/fastscroll_bubble"
|
tools:background="@drawable/fastscroll_bubble"
|
||||||
|
tools:backgroundTint="@color/blue_primary"
|
||||||
tools:text="A"
|
tools:text="A"
|
||||||
tools:textColor="#ffffff"
|
tools:textColor="#ffffff"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/fastscroll_scrollbar"
|
android:id="@+id/scrollbar"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:paddingEnd="@dimen/fastscroll_scrollbar_padding_end"
|
|
||||||
android:paddingStart="@dimen/fastscroll_scrollbar_padding_start"
|
android:paddingStart="@dimen/fastscroll_scrollbar_padding_start"
|
||||||
|
android:paddingEnd="@dimen/fastscroll_scrollbar_padding_end"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
tools:visibility="visible">
|
tools:visibility="visible">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/fastscroll_track"
|
android:id="@+id/track"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_gravity="center_horizontal"
|
android:layout_gravity="center_horizontal"
|
||||||
tools:ignore="ContentDescription"
|
tools:ignore="ContentDescription"
|
||||||
tools:src="@drawable/fastscroll_track" />
|
tools:src="@drawable/fastscroll_track"
|
||||||
|
tools:tint="@color/kotatsu_outline" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/fastscroll_handle"
|
android:id="@+id/thumb"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center_horizontal"
|
android:layout_gravity="center_horizontal"
|
||||||
tools:ignore="ContentDescription"
|
tools:ignore="ContentDescription"
|
||||||
tools:src="@drawable/fastscroll_handle" />
|
tools:src="@drawable/fastscroll_handle"
|
||||||
|
tools:tint="@color/kotatsu_primary" />
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
tools:listitem="@layout/item_branch"
|
tools:listitem="@layout/item_branch"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<org.koitharu.kotatsu.base.ui.widgets.FastScrollRecyclerView
|
<org.koitharu.kotatsu.base.ui.list.fastscroll.FastScrollRecyclerView
|
||||||
android:id="@+id/recyclerView_chapters"
|
android:id="@+id/recyclerView_chapters"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
@@ -28,11 +28,7 @@
|
|||||||
android:layout_alignParentBottom="true"
|
android:layout_alignParentBottom="true"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
app:bubbleColor="?attr/colorTertiary"
|
|
||||||
app:bubbleTextColor="?attr/colorOnTertiary"
|
|
||||||
app:handleColor="?attr/colorTertiary"
|
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
app:showBubble="true"
|
|
||||||
tools:listitem="@layout/item_chapter" />
|
tools:listitem="@layout/item_chapter" />
|
||||||
|
|
||||||
<com.google.android.material.progressindicator.CircularProgressIndicator
|
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<org.koitharu.kotatsu.base.ui.widgets.FastScrollRecyclerView
|
<org.koitharu.kotatsu.base.ui.list.fastscroll.FastScrollRecyclerView
|
||||||
android:id="@+id/recyclerView"
|
android:id="@+id/recyclerView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
@@ -21,8 +21,8 @@
|
|||||||
android:paddingTop="@dimen/grid_spacing_outer"
|
android:paddingTop="@dimen/grid_spacing_outer"
|
||||||
android:paddingRight="@dimen/list_spacing"
|
android:paddingRight="@dimen/list_spacing"
|
||||||
android:paddingBottom="@dimen/grid_spacing_outer"
|
android:paddingBottom="@dimen/grid_spacing_outer"
|
||||||
app:trackColor="?attr/colorOutline"
|
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
|
app:trackColor="?attr/colorOutline"
|
||||||
tools:listitem="@layout/item_feed" />
|
tools:listitem="@layout/item_feed" />
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/swipeRefreshLayout"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
@@ -12,13 +11,12 @@
|
|||||||
android:paddingRight="@dimen/list_spacing"
|
android:paddingRight="@dimen/list_spacing"
|
||||||
android:paddingBottom="@dimen/grid_spacing_outer">
|
android:paddingBottom="@dimen/grid_spacing_outer">
|
||||||
|
|
||||||
<org.koitharu.kotatsu.base.ui.widgets.FastScrollRecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/recyclerView"
|
android:id="@+id/recyclerView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
app:handleColor="?attr/colorTertiary"
|
|
||||||
tools:listitem="@layout/item_feed" />
|
tools:listitem="@layout/item_feed" />
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
@@ -11,17 +11,17 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<org.koitharu.kotatsu.base.ui.list.fastscroll.FastScrollRecyclerView
|
||||||
android:id="@+id/recyclerView"
|
android:id="@+id/recyclerView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingLeft="@dimen/list_spacing"
|
android:paddingLeft="@dimen/list_spacing"
|
||||||
android:paddingRight="@dimen/list_spacing"
|
|
||||||
android:paddingTop="@dimen/grid_spacing_outer"
|
android:paddingTop="@dimen/grid_spacing_outer"
|
||||||
|
android:paddingRight="@dimen/list_spacing"
|
||||||
android:paddingBottom="@dimen/grid_spacing_outer"
|
android:paddingBottom="@dimen/grid_spacing_outer"
|
||||||
app:handleColor="?attr/colorTertiary"
|
app:bubbleSize="small"
|
||||||
tools:layoutManager="org.koitharu.kotatsu.base.ui.list.FitHeightLinearLayoutManager"
|
tools:layoutManager="org.koitharu.kotatsu.base.ui.list.FitHeightLinearLayoutManager"
|
||||||
tools:listitem="@layout/item_manga_list" />
|
tools:listitem="@layout/item_manga_list" />
|
||||||
|
|
||||||
|
|||||||
@@ -12,13 +12,12 @@
|
|||||||
android:paddingRight="@dimen/list_spacing"
|
android:paddingRight="@dimen/list_spacing"
|
||||||
android:paddingBottom="@dimen/grid_spacing_outer">
|
android:paddingBottom="@dimen/grid_spacing_outer">
|
||||||
|
|
||||||
<org.koitharu.kotatsu.base.ui.widgets.FastScrollRecyclerView
|
<org.koitharu.kotatsu.base.ui.list.fastscroll.FastScrollRecyclerView
|
||||||
android:id="@+id/recyclerView"
|
android:id="@+id/recyclerView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
app:handleColor="?attr/colorTertiary"
|
|
||||||
tools:listitem="@layout/item_feed" />
|
tools:listitem="@layout/item_feed" />
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
@@ -22,13 +22,12 @@
|
|||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<org.koitharu.kotatsu.base.ui.widgets.FastScrollRecyclerView
|
<org.koitharu.kotatsu.base.ui.list.fastscroll.FastScrollRecyclerView
|
||||||
android:id="@+id/recyclerView"
|
android:id="@+id/recyclerView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
app:handleColor="?attr/colorOutline"
|
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
tools:listitem="@layout/item_chapter" />
|
tools:listitem="@layout/item_chapter" />
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
<attr name="multiAutoCompleteTextViewPreferenceStyle" />
|
<attr name="multiAutoCompleteTextViewPreferenceStyle" />
|
||||||
<attr name="autoCompleteTextViewPreferenceStyle" />
|
<attr name="autoCompleteTextViewPreferenceStyle" />
|
||||||
<attr name="listItemTextViewStyle" />
|
<attr name="listItemTextViewStyle" />
|
||||||
|
<attr name="fastScrollerStyle" />
|
||||||
|
|
||||||
<declare-styleable name="Theme">
|
<declare-styleable name="Theme">
|
||||||
<attr name="navigationBarDividerColor" format="color" />
|
<attr name="navigationBarDividerColor" format="color" />
|
||||||
@@ -53,16 +54,16 @@
|
|||||||
</declare-styleable>
|
</declare-styleable>
|
||||||
|
|
||||||
<declare-styleable name="FastScroller">
|
<declare-styleable name="FastScroller">
|
||||||
<attr format="boolean" name="hideScrollbar" />
|
<attr name="hideScrollbar" format="boolean" />
|
||||||
<attr format="boolean" name="showBubble" />
|
<attr name="showBubble" format="boolean" />
|
||||||
<attr format="boolean" name="showBubbleAlways" />
|
<attr name="showBubbleAlways" format="boolean" />
|
||||||
<attr format="boolean" name="showTrack" />
|
<attr name="showTrack" format="boolean" />
|
||||||
<attr format="color" name="bubbleColor" />
|
<attr name="bubbleColor" format="color" />
|
||||||
<attr format="color" name="bubbleTextColor" />
|
<attr name="bubbleTextColor" format="color" />
|
||||||
<attr format="color" name="handleColor" />
|
<attr name="thumbColor" format="color" />
|
||||||
<attr format="color" name="trackColor" />
|
<attr name="trackColor" format="color" />
|
||||||
<attr format="dimension" name="bubbleTextSize" />
|
<attr name="bubbleTextSize" format="dimension" />
|
||||||
<attr format="enum" name="bubbleSize">
|
<attr name="bubbleSize" format="enum">
|
||||||
<enum name="normal" value="0" />
|
<enum name="normal" value="0" />
|
||||||
<enum name="small" value="1" />
|
<enum name="small" value="1" />
|
||||||
</attr>
|
</attr>
|
||||||
|
|||||||
@@ -44,8 +44,8 @@
|
|||||||
<dimen name="fastscroll_bubble_padding">16dp</dimen>
|
<dimen name="fastscroll_bubble_padding">16dp</dimen>
|
||||||
|
|
||||||
<dimen name="fastscroll_bubble_radius_small">32dp</dimen>
|
<dimen name="fastscroll_bubble_radius_small">32dp</dimen>
|
||||||
<dimen name="fastscroll_bubble_size_small">64dp</dimen>
|
<dimen name="fastscroll_bubble_size_small">36dp</dimen>
|
||||||
<dimen name="fastscroll_bubble_text_size_small">36sp</dimen>
|
<dimen name="fastscroll_bubble_text_size_small">24sp</dimen>
|
||||||
<dimen name="fastscroll_bubble_padding_small">12dp</dimen>
|
<dimen name="fastscroll_bubble_padding_small">12dp</dimen>
|
||||||
|
|
||||||
<dimen name="fastscroll_handle_height">58dp</dimen>
|
<dimen name="fastscroll_handle_height">58dp</dimen>
|
||||||
|
|||||||
@@ -115,6 +115,14 @@
|
|||||||
<item name="android:scrollbarStyle">outsideOverlay</item>
|
<item name="android:scrollbarStyle">outsideOverlay</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="Widget.Kotatsu.FastScroller" parent="">
|
||||||
|
<item name="thumbColor">?colorTertiary</item>
|
||||||
|
<item name="bubbleColor">?colorTertiary</item>
|
||||||
|
<item name="bubbleTextColor">?colorOnTertiary</item>
|
||||||
|
<item name="trackColor">?colorOutline</item>
|
||||||
|
<item name="bubbleSize">normal</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
<style name="Widget.Kotatsu.ListItemTextView" parent="">
|
<style name="Widget.Kotatsu.ListItemTextView" parent="">
|
||||||
<item name="android:textColor">@color/list_item_text_color</item>
|
<item name="android:textColor">@color/list_item_text_color</item>
|
||||||
<item name="backgroundFillColor">@color/list_item_background_color</item>
|
<item name="backgroundFillColor">@color/list_item_background_color</item>
|
||||||
|
|||||||
@@ -71,6 +71,7 @@
|
|||||||
<item name="tabStyle">@style/Widget.Kotatsu.Tabs</item>
|
<item name="tabStyle">@style/Widget.Kotatsu.Tabs</item>
|
||||||
<item name="materialCardViewStyle">@style/Widget.Material3.CardView.Filled</item>
|
<item name="materialCardViewStyle">@style/Widget.Material3.CardView.Filled</item>
|
||||||
<item name="recyclerViewStyle">@style/Widget.Kotatsu.RecyclerView</item>
|
<item name="recyclerViewStyle">@style/Widget.Kotatsu.RecyclerView</item>
|
||||||
|
<item name="fastScrollerStyle">@style/Widget.Kotatsu.FastScroller</item>
|
||||||
<item name="listItemTextViewStyle">@style/Widget.Kotatsu.ListItemTextView</item>
|
<item name="listItemTextViewStyle">@style/Widget.Kotatsu.ListItemTextView</item>
|
||||||
<item name="materialSwitchStyle">@style/Widget.Material3.CompoundButton.MaterialSwitch</item>
|
<item name="materialSwitchStyle">@style/Widget.Material3.CompoundButton.MaterialSwitch</item>
|
||||||
<item name="switchPreferenceCompatStyle">@style/Preference.SwitchPreferenceCompat.M3</item>
|
<item name="switchPreferenceCompatStyle">@style/Preference.SwitchPreferenceCompat.M3</item>
|
||||||
|
|||||||
Reference in New Issue
Block a user