Using material fast scroll on manga details

This concludes the use of reddit's fastscroll

Also updated the fast scroll's scroll listener so that the scroll handle respects its margins (thus making it reach the bottom of the manga details
This commit is contained in:
Jay 2020-05-10 03:03:36 -04:00
parent 60c3f24515
commit 4dae06803c
7 changed files with 51 additions and 186 deletions

View File

@ -206,7 +206,6 @@ dependencies {
implementation("me.zhanghai.android.systemuihelper:library:1.0.0")
implementation("com.nightlynexus.viewstatepageradapter:viewstatepageradapter:1.1.0")
implementation("com.github.mthli:Slice:v1.2")
implementation("com.reddit:indicator-fast-scroll:1.2.1")
implementation("com.github.kizitonwose:AndroidTagGroup:1.6.0")
implementation("com.github.chrisbanes:PhotoView:2.3.0")

View File

@ -3,9 +3,12 @@ package eu.kanade.tachiyomi.ui.library
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import androidx.recyclerview.widget.RecyclerView
import eu.davidea.fastscroller.FastScroller
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.system.dpToPxEnd
import eu.kanade.tachiyomi.util.view.marginTop
import kotlin.math.abs
class MaterialFastScroll @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
FastScroller(context, attrs) {
@ -16,6 +19,7 @@ class MaterialFastScroll @JvmOverloads constructor(context: Context, attrs: Attr
)
autoHideEnabled = true
ignoreTouchesOutsideHandle = true
updateScrollListener()
}
override fun onTouchEvent(event: MotionEvent): Boolean {
@ -30,4 +34,22 @@ class MaterialFastScroll @JvmOverloads constructor(context: Context, attrs: Attr
bubble.translationX = (-45f).dpToPxEnd
}
}
private fun updateScrollListener() {
onScrollListener = object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
if (!isEnabled || bubble == null || handle.isSelected) return
val verticalScrollOffset = recyclerView.computeVerticalScrollOffset()
val verticalScrollRange = recyclerView.computeVerticalScrollRange() - marginTop
val proportion =
verticalScrollOffset.toFloat() / (verticalScrollRange - height).toFloat()
setBubbleAndHandlePosition(height * proportion)
// If scroll amount is small, don't show it
if (minimumScrollThreshold == 0 || dy == 0 || abs(dy) > minimumScrollThreshold || scrollbarAnimator.isAnimating) {
showScrollbar()
if (autoHideEnabled) hideScrollbar()
}
}
}
}
}

View File

@ -59,54 +59,33 @@ class MangaDetailsAdapter(
}
}
fun getSectionText(position: Int): String? {
val chapter = getItem(position) as? ChapterItem ?: return null
if (position == itemCount - 1) return "-"
return when (presenter.scrollType) {
MangaDetailsPresenter.MULTIPLE_VOLUMES, MangaDetailsPresenter.MULTIPLE_SEASONS ->
presenter.getGroupNumber(chapter)?.toString() ?: "*"
MangaDetailsPresenter.HUNDREDS_OF_CHAPTERS ->
if (chapter.chapter_number < 0) "*"
else (chapter.chapter_number / 100).toInt().toString()
MangaDetailsPresenter.TENS_OF_CHAPTERS ->
if (chapter.chapter_number < 0) "*"
else (chapter.chapter_number / 10).toInt().toString()
else -> null
}
}
fun getFullText(position: Int): String {
override fun onCreateBubbleText(position: Int): String {
val chapter =
getItem(position) as? ChapterItem ?: return recyclerView.context.getString(R.string.top)
if (position == itemCount - 1) return recyclerView.context.getString(R.string.bottom)
return when (val scrollType = presenter.scrollType) {
MangaDetailsPresenter.MULTIPLE_VOLUMES, MangaDetailsPresenter.MULTIPLE_SEASONS -> {
val volume = presenter.getGroupNumber(chapter)
if (volume != null) recyclerView.context.getString(
if (scrollType == MangaDetailsPresenter.MULTIPLE_SEASONS) R.string.season_
else R.string.volume_, volume)
else recyclerView.context.getString(R.string.unknown)
if (volume != null) {
recyclerView.context.getString(
if (scrollType == MangaDetailsPresenter.MULTIPLE_SEASONS) R.string.season_
else R.string.volume_, volume
)
} else {
recyclerView.context.getString(R.string.unknown)
}
}
MangaDetailsPresenter.HUNDREDS_OF_CHAPTERS -> recyclerView.context.getString(
R.string.chapters_, get100sRange(
chapter.chapter_number
)
)
MangaDetailsPresenter.TENS_OF_CHAPTERS -> recyclerView.context.getString(
R.string.chapters_, get10sRange(
chapter.chapter_number
)
)
else -> recyclerView.context.getString(R.string.unknown)
}
}
private fun get100sRange(value: Float): String {
val number = value.toInt()
return if (number < 100) "0-99"
else {
val hundred = number / 100
"${hundred}00-${hundred}99"
else -> if (chapter.chapter_number > 0) {
recyclerView.context.getString(
R.string.chapter_, decimalFormat.format(chapter.chapter_number)
)
} else {
chapter.name
}
}
}

View File

@ -53,8 +53,6 @@ import com.bumptech.glide.request.transition.Transition
import com.bumptech.glide.signature.ObjectKey
import com.google.android.material.snackbar.BaseTransientBottomBar
import com.google.android.material.snackbar.Snackbar
import com.reddit.indicatorfastscroll.FastScrollItemIndicator
import com.reddit.indicatorfastscroll.FastScrollerView
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.SelectableAdapter
import eu.kanade.tachiyomi.R
@ -101,14 +99,10 @@ import eu.kanade.tachiyomi.util.system.isOnline
import eu.kanade.tachiyomi.util.system.launchUI
import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.view.getText
import eu.kanade.tachiyomi.util.view.hide
import eu.kanade.tachiyomi.util.view.requestPermissionsSafe
import eu.kanade.tachiyomi.util.view.scrollViewWith
import eu.kanade.tachiyomi.util.view.setBackground
import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener
import eu.kanade.tachiyomi.util.view.setStartTranslationX
import eu.kanade.tachiyomi.util.view.setStyle
import eu.kanade.tachiyomi.util.view.show
import eu.kanade.tachiyomi.util.view.snack
import eu.kanade.tachiyomi.util.view.updateLayoutParams
import eu.kanade.tachiyomi.util.view.updatePaddingRelative
@ -205,7 +199,7 @@ class MangaDetailsController : BaseController,
setRecycler(view)
setPaletteColor()
setFastScroller()
adapter?.fastScroller = fast_scroller2
presenter.onCreate()
swipe_refresh.isRefreshing = presenter.isLoading
@ -259,17 +253,6 @@ class MangaDetailsController : BaseController,
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
val atTop = !recycler.canScrollVertically(-1)
if (atTop) getHeader()?.backdrop?.translationY = 0f
when (newState) {
RecyclerView.SCROLL_STATE_DRAGGING -> {
scrollAnim?.cancel()
if (fast_scroller.translationX != 0f) {
showFastScroller()
}
}
RecyclerView.SCROLL_STATE_IDLE -> {
scrollAnim = fast_scroller.hide()
}
}
}
})
}
@ -281,51 +264,12 @@ class MangaDetailsController : BaseController,
swipe_refresh.setProgressViewOffset(false, (-40).dpToPx, headerHeight + offset)
// 1dp extra to line up chapter header and manga header
getHeader()?.setTopHeight(headerHeight)
fast_scroll_layout.updateLayoutParams<ViewGroup.MarginLayoutParams> {
fast_scroller2.updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin = headerHeight
bottomMargin = insets.systemWindowInsetBottom
}
}
private fun setFastScroller() {
fast_scroller.setStartTranslationX(true)
fast_scroller.setBackground(true)
fast_scroller.setupWithRecyclerView(recycler, { position ->
val letter = adapter?.getSectionText(position)
when {
presenter.scrollType == 0 -> null
letter != null -> FastScrollItemIndicator.Text(letter)
else -> FastScrollItemIndicator.Icon(R.drawable.ic_star_24dp)
}
})
fast_scroller.useDefaultScroller = false
fast_scroller.itemIndicatorSelectedCallbacks += object :
FastScrollerView.ItemIndicatorSelectedCallback {
override fun onItemIndicatorSelected(
indicator: FastScrollItemIndicator,
indicatorCenterY: Int,
itemPosition: Int
) {
scrollAnim?.cancel()
scrollAnim = fast_scroller.hide(2000)
textAnim?.cancel()
textAnim = text_view_m.animate().alpha(0f).setDuration(250L).setStartDelay(1000)
textAnim?.start()
text_view_m.translationY = indicatorCenterY.toFloat() - text_view_m.height / 2
text_view_m.alpha = 1f
text_view_m.text = adapter?.getFullText(itemPosition)
val appbar = activity?.appbar
appbar?.y = 0f
(recycler.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(
itemPosition, headerHeight
)
colorToolbar(itemPosition > 0, false)
}
}
}
/** Set the toolbar to fully transparent or colored and translucent */
fun colorToolbar(isColor: Boolean, animate: Boolean = true) {
if (isColor == toolbarIsColored) return
@ -594,12 +538,6 @@ class MangaDetailsController : BaseController,
activity?.invalidateOptionsMenu()
}
private fun showFastScroller(animate: Boolean = true) {
if (presenter.scrollType != 0) {
fast_scroller.show(animate)
}
}
private fun addMangaHeader() {
if (adapter?.scrollableHeaders?.isEmpty() == true) {
adapter?.removeAllScrollableHeaders()

View File

@ -251,7 +251,6 @@ class MangaDetailsPresenter(
scrollType = when {
hasMultipleVolumes(chapters) -> MULTIPLE_VOLUMES
hasMultipleSeasons(chapters) -> MULTIPLE_SEASONS
hasHundredsOfChapters(chapters) -> HUNDREDS_OF_CHAPTERS
hasTensOfChapters(chapters) -> TENS_OF_CHAPTERS
else -> 0
}
@ -283,7 +282,7 @@ class MangaDetailsPresenter(
val volNum = getVolumeNumber(it)
if (volNum != null) {
volumeSet.add(volNum)
if (volumeSet.size >= 3) return true
if (volumeSet.size >= 2) return true
}
}
return false
@ -295,18 +294,14 @@ class MangaDetailsPresenter(
val volNum = getSeasonNumber(it)
if (volNum != null) {
volumeSet.add(volNum)
if (volumeSet.size >= 3) return true
if (volumeSet.size >= 2) return true
}
}
return false
}
private fun hasHundredsOfChapters(chapters: List<ChapterItem>): Boolean {
return chapters.size > 300
}
private fun hasTensOfChapters(chapters: List<ChapterItem>): Boolean {
return chapters.size in 21..300
return chapters.size > 20
}
/**
@ -878,7 +873,6 @@ class MangaDetailsPresenter(
companion object {
const val MULTIPLE_VOLUMES = 1
const val TENS_OF_CHAPTERS = 2
const val HUNDREDS_OF_CHAPTERS = 3
const val MULTIPLE_SEASONS = 4
const val MULTIPLE_SEASONS = 3
}
}

View File

@ -1,37 +0,0 @@
package eu.kanade.tachiyomi.util.view
import android.content.res.ColorStateList
import android.graphics.Color
import android.view.ViewPropertyAnimator
import androidx.core.content.ContextCompat
import com.reddit.indicatorfastscroll.FastScrollerView
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.system.dpToPxEnd
import eu.kanade.tachiyomi.util.system.getResourceColor
fun FastScrollerView.setBackground(hasBackground: Boolean) {
background = if (hasBackground) ContextCompat.getDrawable(
context, R.drawable.fast_scroll_background
) else null
textColor = ColorStateList.valueOf(
if (hasBackground) Color.WHITE
else context.getResourceColor(android.R.attr.textColorPrimary)
)
iconColor = textColor
}
fun FastScrollerView.setStartTranslationX(shouldHide: Boolean) {
translationX = if (shouldHide) 25f.dpToPxEnd else 0f
}
fun FastScrollerView.hide(duration: Long = 1000): ViewPropertyAnimator {
val scrollAnim = animate().setStartDelay(duration).setDuration(250)
.translationX(25f.dpToPxEnd)
scrollAnim.start()
return scrollAnim
}
fun FastScrollerView.show(animate: Boolean = true) {
if (animate) animate().setStartDelay(0).setDuration(100).translationX(0f).start()
else translationX = 0f
}

View File

@ -14,55 +14,25 @@
android:background="?android:colorBackground">
<LinearLayout
<FrameLayout
android:id="@+id/linear_recycler_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="0.25"
android:clipToPadding="false"
tools:listitem="@layout/chapters_item" />
</LinearLayout>
</FrameLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/fast_scroll_layout"
<eu.kanade.tachiyomi.ui.library.MaterialFastScroll
android:id="@+id/fast_scroller2"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.reddit.indicatorfastscroll.FastScrollerView
android:id="@+id/fast_scroller"
android:layout_width="25dp"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:elevation="10dp"
android:paddingStart="1dp"
android:paddingTop="8dp"
android:paddingEnd="0dp"
android:paddingBottom="8dp"
android:textColor="?android:attr/textColorPrimary"
app:iconColor="?android:attr/textColorPrimary"
app:layout_constrainedHeight="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/text_view_m"
style="@style/FloatingTextView"
android:layout_marginEnd="50dp"
android:alpha="0"
app:layout_constraintEnd_toStartOf="@id/fast_scroller"
app:layout_constraintTop_toTopOf="@id/fast_scroller"
tools:text="Volume 12"
tools:alpha="1" />
</androidx.constraintlayout.widget.ConstraintLayout>
android:layout_height="match_parent"
app:fastScrollerBubbleEnabled="true" />
<View
android:id="@+id/full_backdrop"