mirror of
https://github.com/Aviortheking/Neo-Store.git
synced 2025-04-23 19:32:16 +00:00
Replace Default FastScroller (Closes #79)
This commit is contained in:
parent
f66d91ed9f
commit
9c82a9f7c4
@ -163,6 +163,8 @@ dependencies {
|
|||||||
implementation 'io.reactivex.rxjava3:rxjava:3.1.3'
|
implementation 'io.reactivex.rxjava3:rxjava:3.1.3'
|
||||||
implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
|
implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
|
||||||
|
|
||||||
|
implementation 'me.zhanghai.android.fastscroll:library:1.1.7'
|
||||||
|
|
||||||
// LibSu
|
// LibSu
|
||||||
implementation 'com.github.topjohnwu.libsu:core:3.1.2'
|
implementation 'com.github.topjohnwu.libsu:core:3.1.2'
|
||||||
|
|
||||||
|
@ -13,7 +13,8 @@ import com.looker.droidify.database.CursorOwner
|
|||||||
import com.looker.droidify.service.Connection
|
import com.looker.droidify.service.Connection
|
||||||
import com.looker.droidify.service.SyncService
|
import com.looker.droidify.service.SyncService
|
||||||
import com.looker.droidify.utility.Utils
|
import com.looker.droidify.utility.Utils
|
||||||
import com.looker.droidify.widget.RecyclerFastScroller
|
import com.looker.droidify.utility.extension.resources.getDrawableCompat
|
||||||
|
import me.zhanghai.android.fastscroll.FastScrollerBuilder
|
||||||
|
|
||||||
class RepositoriesFragment : ScreenFragment(), CursorOwner.Callback {
|
class RepositoriesFragment : ScreenFragment(), CursorOwner.Callback {
|
||||||
private var recyclerView: RecyclerView? = null
|
private var recyclerView: RecyclerView? = null
|
||||||
@ -38,7 +39,10 @@ class RepositoriesFragment : ScreenFragment(), CursorOwner.Callback {
|
|||||||
syncConnection.binder?.setEnabled(repository, isEnabled) == true
|
syncConnection.binder?.setEnabled(repository, isEnabled) == true
|
||||||
})
|
})
|
||||||
recyclerView = this
|
recyclerView = this
|
||||||
RecyclerFastScroller(this)
|
FastScrollerBuilder(this)
|
||||||
|
.useMd2Style()
|
||||||
|
.setThumbDrawable(this.context.getDrawableCompat(R.drawable.scrollbar_thumb))
|
||||||
|
.build()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
this.toolbar = fragmentBinding.toolbar
|
this.toolbar = fragmentBinding.toolbar
|
||||||
|
@ -32,7 +32,8 @@ abstract class ScreenActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
sealed class SpecialIntent {
|
sealed class SpecialIntent {
|
||||||
object Updates : SpecialIntent()
|
object Updates : SpecialIntent()
|
||||||
class Install(val packageName: String?, val status: Int?, val promptIntent: Intent?) : SpecialIntent()
|
class Install(val packageName: String?, val status: Int?, val promptIntent: Intent?) :
|
||||||
|
SpecialIntent()
|
||||||
}
|
}
|
||||||
|
|
||||||
private class FragmentStackItem(
|
private class FragmentStackItem(
|
||||||
@ -236,8 +237,7 @@ abstract class ScreenActivity : AppCompatActivity() {
|
|||||||
.putExtra(Intent.EXTRA_INTENT, promptIntent)
|
.putExtra(Intent.EXTRA_INTENT, promptIntent)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
throw IllegalArgumentException("Missing parameters needed to relaunch InstallerService and trigger prompt.")
|
throw IllegalArgumentException("Missing parameters needed to relaunch InstallerService and trigger prompt.")
|
||||||
}
|
}
|
||||||
Unit
|
Unit
|
||||||
|
@ -18,13 +18,14 @@ import com.looker.droidify.screen.BaseFragment
|
|||||||
import com.looker.droidify.ui.adapters.AppListAdapter
|
import com.looker.droidify.ui.adapters.AppListAdapter
|
||||||
import com.looker.droidify.ui.viewmodels.AppListViewModel
|
import com.looker.droidify.ui.viewmodels.AppListViewModel
|
||||||
import com.looker.droidify.utility.RxUtils
|
import com.looker.droidify.utility.RxUtils
|
||||||
import com.looker.droidify.widget.RecyclerFastScroller
|
import com.looker.droidify.utility.extension.resources.getDrawableCompat
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.rxjava3.core.Observable
|
import io.reactivex.rxjava3.core.Observable
|
||||||
import io.reactivex.rxjava3.disposables.Disposable
|
import io.reactivex.rxjava3.disposables.Disposable
|
||||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import me.zhanghai.android.fastscroll.FastScrollerBuilder
|
||||||
|
|
||||||
class AppListFragment() : BaseFragment(), CursorOwner.Callback {
|
class AppListFragment() : BaseFragment(), CursorOwner.Callback {
|
||||||
|
|
||||||
@ -67,7 +68,10 @@ class AppListFragment() : BaseFragment(), CursorOwner.Callback {
|
|||||||
recycledViewPool.setMaxRecycledViews(AppListAdapter.ViewType.PRODUCT.ordinal, 30)
|
recycledViewPool.setMaxRecycledViews(AppListAdapter.ViewType.PRODUCT.ordinal, 30)
|
||||||
val adapter = AppListAdapter { screenActivity.navigateProduct(it.packageName) }
|
val adapter = AppListAdapter { screenActivity.navigateProduct(it.packageName) }
|
||||||
this.adapter = adapter
|
this.adapter = adapter
|
||||||
RecyclerFastScroller(this)
|
FastScrollerBuilder(this)
|
||||||
|
.useMd2Style()
|
||||||
|
.setThumbDrawable(this.context.getDrawableCompat(R.drawable.scrollbar_thumb))
|
||||||
|
.build()
|
||||||
recyclerView = this
|
recyclerView = this
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,11 +17,12 @@ import com.looker.droidify.entity.Repository
|
|||||||
import com.looker.droidify.ui.adapters.AppListAdapter
|
import com.looker.droidify.ui.adapters.AppListAdapter
|
||||||
import com.looker.droidify.ui.viewmodels.MainNavFragmentViewModelX
|
import com.looker.droidify.ui.viewmodels.MainNavFragmentViewModelX
|
||||||
import com.looker.droidify.utility.RxUtils
|
import com.looker.droidify.utility.RxUtils
|
||||||
import com.looker.droidify.widget.RecyclerFastScroller
|
import com.looker.droidify.utility.extension.resources.getDrawableCompat
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import me.zhanghai.android.fastscroll.FastScrollerBuilder
|
||||||
|
|
||||||
class ExploreFragment : MainNavFragmentX(), CursorOwner.Callback {
|
class ExploreFragment : MainNavFragmentX(), CursorOwner.Callback {
|
||||||
|
|
||||||
@ -51,7 +52,10 @@ class ExploreFragment : MainNavFragmentX(), CursorOwner.Callback {
|
|||||||
setHasFixedSize(true)
|
setHasFixedSize(true)
|
||||||
recycledViewPool.setMaxRecycledViews(AppListAdapter.ViewType.PRODUCT.ordinal, 30)
|
recycledViewPool.setMaxRecycledViews(AppListAdapter.ViewType.PRODUCT.ordinal, 30)
|
||||||
adapter = AppListAdapter { mainActivityX.navigateProduct(it.packageName) }
|
adapter = AppListAdapter { mainActivityX.navigateProduct(it.packageName) }
|
||||||
RecyclerFastScroller(this)
|
FastScrollerBuilder(this)
|
||||||
|
.useMd2Style()
|
||||||
|
.setThumbDrawable(this.context.getDrawableCompat(R.drawable.scrollbar_thumb))
|
||||||
|
.build()
|
||||||
}
|
}
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import android.view.ViewGroup
|
|||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.looker.droidify.R
|
||||||
import com.looker.droidify.database.CursorOwner
|
import com.looker.droidify.database.CursorOwner
|
||||||
import com.looker.droidify.databinding.FragmentInstalledXBinding
|
import com.looker.droidify.databinding.FragmentInstalledXBinding
|
||||||
import com.looker.droidify.entity.ProductItem
|
import com.looker.droidify.entity.ProductItem
|
||||||
@ -16,11 +17,12 @@ import com.looker.droidify.ui.items.HAppItem
|
|||||||
import com.looker.droidify.ui.items.VAppItem
|
import com.looker.droidify.ui.items.VAppItem
|
||||||
import com.looker.droidify.ui.viewmodels.MainNavFragmentViewModelX
|
import com.looker.droidify.ui.viewmodels.MainNavFragmentViewModelX
|
||||||
import com.looker.droidify.utility.RxUtils
|
import com.looker.droidify.utility.RxUtils
|
||||||
import com.looker.droidify.widget.RecyclerFastScroller
|
import com.looker.droidify.utility.extension.resources.getDrawableCompat
|
||||||
import com.mikepenz.fastadapter.FastAdapter
|
import com.mikepenz.fastadapter.FastAdapter
|
||||||
import com.mikepenz.fastadapter.adapters.ItemAdapter
|
import com.mikepenz.fastadapter.adapters.ItemAdapter
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||||
|
import me.zhanghai.android.fastscroll.FastScrollerBuilder
|
||||||
|
|
||||||
class InstalledFragment : MainNavFragmentX(), CursorOwner.Callback {
|
class InstalledFragment : MainNavFragmentX(), CursorOwner.Callback {
|
||||||
|
|
||||||
@ -55,7 +57,10 @@ class InstalledFragment : MainNavFragmentX(), CursorOwner.Callback {
|
|||||||
isMotionEventSplittingEnabled = false
|
isMotionEventSplittingEnabled = false
|
||||||
isVerticalScrollBarEnabled = false
|
isVerticalScrollBarEnabled = false
|
||||||
adapter = installedFastAdapter
|
adapter = installedFastAdapter
|
||||||
RecyclerFastScroller(this)
|
FastScrollerBuilder(this)
|
||||||
|
.useMd2Style()
|
||||||
|
.setThumbDrawable(this.context.getDrawableCompat(R.drawable.scrollbar_thumb))
|
||||||
|
.build()
|
||||||
}
|
}
|
||||||
updatedFastAdapter = FastAdapter.with(updatedItemAdapter)
|
updatedFastAdapter = FastAdapter.with(updatedItemAdapter)
|
||||||
updatedFastAdapter?.setHasStableIds(true)
|
updatedFastAdapter?.setHasStableIds(true)
|
||||||
@ -64,7 +69,10 @@ class InstalledFragment : MainNavFragmentX(), CursorOwner.Callback {
|
|||||||
isMotionEventSplittingEnabled = false
|
isMotionEventSplittingEnabled = false
|
||||||
isVerticalScrollBarEnabled = false
|
isVerticalScrollBarEnabled = false
|
||||||
adapter = updatedFastAdapter
|
adapter = updatedFastAdapter
|
||||||
RecyclerFastScroller(this)
|
FastScrollerBuilder(this)
|
||||||
|
.useMd2Style()
|
||||||
|
.setThumbDrawable(this.context.getDrawableCompat(R.drawable.scrollbar_thumb))
|
||||||
|
.build()
|
||||||
}
|
}
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import android.view.ViewGroup
|
|||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.looker.droidify.R
|
||||||
import com.looker.droidify.database.CursorOwner
|
import com.looker.droidify.database.CursorOwner
|
||||||
import com.looker.droidify.databinding.FragmentLatestXBinding
|
import com.looker.droidify.databinding.FragmentLatestXBinding
|
||||||
import com.looker.droidify.entity.ProductItem
|
import com.looker.droidify.entity.ProductItem
|
||||||
@ -16,11 +17,12 @@ import com.looker.droidify.ui.items.HAppItem
|
|||||||
import com.looker.droidify.ui.items.VAppItem
|
import com.looker.droidify.ui.items.VAppItem
|
||||||
import com.looker.droidify.ui.viewmodels.MainNavFragmentViewModelX
|
import com.looker.droidify.ui.viewmodels.MainNavFragmentViewModelX
|
||||||
import com.looker.droidify.utility.RxUtils
|
import com.looker.droidify.utility.RxUtils
|
||||||
import com.looker.droidify.widget.RecyclerFastScroller
|
import com.looker.droidify.utility.extension.resources.getDrawableCompat
|
||||||
import com.mikepenz.fastadapter.FastAdapter
|
import com.mikepenz.fastadapter.FastAdapter
|
||||||
import com.mikepenz.fastadapter.adapters.ItemAdapter
|
import com.mikepenz.fastadapter.adapters.ItemAdapter
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||||
|
import me.zhanghai.android.fastscroll.FastScrollerBuilder
|
||||||
|
|
||||||
class LatestFragment : MainNavFragmentX(), CursorOwner.Callback {
|
class LatestFragment : MainNavFragmentX(), CursorOwner.Callback {
|
||||||
|
|
||||||
@ -55,7 +57,10 @@ class LatestFragment : MainNavFragmentX(), CursorOwner.Callback {
|
|||||||
isMotionEventSplittingEnabled = false
|
isMotionEventSplittingEnabled = false
|
||||||
isVerticalScrollBarEnabled = false
|
isVerticalScrollBarEnabled = false
|
||||||
adapter = updatedFastAdapter
|
adapter = updatedFastAdapter
|
||||||
RecyclerFastScroller(this)
|
FastScrollerBuilder(this)
|
||||||
|
.useMd2Style()
|
||||||
|
.setThumbDrawable(this.context.getDrawableCompat(R.drawable.scrollbar_thumb))
|
||||||
|
.build()
|
||||||
}
|
}
|
||||||
newFastAdapter = FastAdapter.with(newItemAdapter)
|
newFastAdapter = FastAdapter.with(newItemAdapter)
|
||||||
newFastAdapter?.setHasStableIds(true)
|
newFastAdapter?.setHasStableIds(true)
|
||||||
@ -64,7 +69,10 @@ class LatestFragment : MainNavFragmentX(), CursorOwner.Callback {
|
|||||||
isMotionEventSplittingEnabled = false
|
isMotionEventSplittingEnabled = false
|
||||||
isVerticalScrollBarEnabled = false
|
isVerticalScrollBarEnabled = false
|
||||||
adapter = newFastAdapter
|
adapter = newFastAdapter
|
||||||
RecyclerFastScroller(this)
|
FastScrollerBuilder(this)
|
||||||
|
.useMd2Style()
|
||||||
|
.setThumbDrawable(this.context.getDrawableCompat(R.drawable.scrollbar_thumb))
|
||||||
|
.build()
|
||||||
}
|
}
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
@ -1,287 +0,0 @@
|
|||||||
package com.looker.droidify.widget
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.graphics.Canvas
|
|
||||||
import android.graphics.Rect
|
|
||||||
import android.os.SystemClock
|
|
||||||
import android.view.MotionEvent
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import com.looker.droidify.utility.extension.resources.getDrawableFromAttr
|
|
||||||
import com.looker.droidify.utility.extension.resources.sizeScaled
|
|
||||||
import kotlin.math.max
|
|
||||||
import kotlin.math.roundToInt
|
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
|
||||||
class RecyclerFastScroller(private val recyclerView: RecyclerView) {
|
|
||||||
companion object {
|
|
||||||
private const val TRANSITION_IN = 100L
|
|
||||||
private const val TRANSITION_OUT = 200L
|
|
||||||
private const val TRANSITION_OUT_DELAY = 1000L
|
|
||||||
|
|
||||||
private val stateNormal = intArrayOf()
|
|
||||||
private val statePressed = intArrayOf(android.R.attr.state_pressed)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val thumbDrawable =
|
|
||||||
recyclerView.context.getDrawableFromAttr(android.R.attr.fastScrollThumbDrawable)
|
|
||||||
private val trackDrawable =
|
|
||||||
recyclerView.context.getDrawableFromAttr(android.R.attr.fastScrollTrackDrawable)
|
|
||||||
private val minTrackSize = recyclerView.resources.sizeScaled(16)
|
|
||||||
|
|
||||||
private data class FastScrolling(
|
|
||||||
val startAtThumbOffset: Float?,
|
|
||||||
val startY: Float,
|
|
||||||
val currentY: Float,
|
|
||||||
)
|
|
||||||
|
|
||||||
private var scrolling = false
|
|
||||||
private var fastScrolling: FastScrolling? = null
|
|
||||||
private var display = Pair(0L, false)
|
|
||||||
|
|
||||||
private val invalidateTransition = Runnable(recyclerView::invalidate)
|
|
||||||
|
|
||||||
private fun updateState(scrolling: Boolean, fastScrolling: FastScrolling?) {
|
|
||||||
val oldDisplay = this.scrolling || this.fastScrolling != null
|
|
||||||
val newDisplay = scrolling || fastScrolling != null
|
|
||||||
this.scrolling = scrolling
|
|
||||||
this.fastScrolling = fastScrolling
|
|
||||||
if (oldDisplay != newDisplay) {
|
|
||||||
recyclerView.removeCallbacks(invalidateTransition)
|
|
||||||
val time = SystemClock.elapsedRealtime()
|
|
||||||
val passed = time - display.first
|
|
||||||
val start = if (newDisplay && passed < (TRANSITION_OUT + TRANSITION_OUT_DELAY)) {
|
|
||||||
if (passed <= TRANSITION_OUT_DELAY) {
|
|
||||||
0L
|
|
||||||
} else {
|
|
||||||
time - ((TRANSITION_OUT_DELAY + TRANSITION_OUT - passed).toFloat() /
|
|
||||||
TRANSITION_OUT * TRANSITION_IN).toLong()
|
|
||||||
}
|
|
||||||
} else if (!newDisplay && passed < TRANSITION_IN) {
|
|
||||||
time - ((TRANSITION_IN - passed).toFloat() / TRANSITION_IN *
|
|
||||||
TRANSITION_OUT).toLong() - TRANSITION_OUT_DELAY
|
|
||||||
} else {
|
|
||||||
if (!newDisplay) {
|
|
||||||
recyclerView.postDelayed(invalidateTransition, TRANSITION_OUT_DELAY)
|
|
||||||
}
|
|
||||||
time
|
|
||||||
}
|
|
||||||
display = Pair(start, newDisplay)
|
|
||||||
recyclerView.invalidate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val scrollListener = object : RecyclerView.OnScrollListener() {
|
|
||||||
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
|
|
||||||
updateState(newState != RecyclerView.SCROLL_STATE_IDLE, fastScrolling)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
|
||||||
if (fastScrolling == null) {
|
|
||||||
recyclerView.invalidate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private inline fun withScroll(callback: (itemHeight: Int, thumbHeight: Int, range: Int) -> Unit): Boolean {
|
|
||||||
val count = recyclerView.adapter?.itemCount ?: 0
|
|
||||||
return count > 0 && run {
|
|
||||||
val itemHeight = Rect().apply {
|
|
||||||
recyclerView
|
|
||||||
.getDecoratedBoundsWithMargins(recyclerView.getChildAt(0), this)
|
|
||||||
}.height()
|
|
||||||
val scrollCount = count - recyclerView.height / itemHeight
|
|
||||||
scrollCount > 0 && run {
|
|
||||||
val range = count * itemHeight
|
|
||||||
val thumbHeight = max(
|
|
||||||
recyclerView.height * recyclerView.height / range,
|
|
||||||
thumbDrawable.intrinsicHeight
|
|
||||||
)
|
|
||||||
range >= recyclerView.height * 2 && run {
|
|
||||||
callback(itemHeight, thumbHeight, range)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun calculateOffset(thumbHeight: Int, fastScrolling: FastScrolling): Float {
|
|
||||||
return if (fastScrolling.startAtThumbOffset != null) {
|
|
||||||
(fastScrolling.startAtThumbOffset + (fastScrolling.currentY - fastScrolling.startY) /
|
|
||||||
(recyclerView.height - thumbHeight)).coerceIn(0f, 1f)
|
|
||||||
} else {
|
|
||||||
((fastScrolling.currentY - thumbHeight / 2f) / (recyclerView.height - thumbHeight)).coerceIn(
|
|
||||||
0f,
|
|
||||||
1f
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun currentOffset(itemHeight: Int, range: Int): Float {
|
|
||||||
val view = recyclerView.getChildAt(0)
|
|
||||||
val position = recyclerView.getChildAdapterPosition(view)
|
|
||||||
val positionOffset = -view.top
|
|
||||||
val scrollPosition = position * itemHeight + positionOffset
|
|
||||||
return scrollPosition.toFloat() / (range - recyclerView.height)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun scroll(
|
|
||||||
itemHeight: Int,
|
|
||||||
thumbHeight: Int,
|
|
||||||
range: Int,
|
|
||||||
fastScrolling: FastScrolling,
|
|
||||||
) {
|
|
||||||
val offset = calculateOffset(thumbHeight, fastScrolling)
|
|
||||||
val scrollPosition = ((range - recyclerView.height) * offset).roundToInt()
|
|
||||||
val position = scrollPosition / itemHeight
|
|
||||||
val positionOffset = scrollPosition - position * itemHeight
|
|
||||||
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
|
|
||||||
layoutManager.scrollToPositionWithOffset(position, -positionOffset)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val touchListener = object : RecyclerView.OnItemTouchListener {
|
|
||||||
private var disallowIntercept = false
|
|
||||||
|
|
||||||
private fun handleTouchEvent(event: MotionEvent, intercept: Boolean): Boolean {
|
|
||||||
val recyclerView = recyclerView
|
|
||||||
val lastFastScrolling = fastScrolling
|
|
||||||
return when {
|
|
||||||
intercept && disallowIntercept -> {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
event.action == MotionEvent.ACTION_DOWN -> {
|
|
||||||
val rtl = recyclerView.layoutDirection == RecyclerView.LAYOUT_DIRECTION_RTL
|
|
||||||
val trackWidth = max(
|
|
||||||
minTrackSize,
|
|
||||||
max(thumbDrawable.intrinsicWidth, trackDrawable.intrinsicWidth)
|
|
||||||
)
|
|
||||||
val atThumbVertical =
|
|
||||||
if (rtl) event.x <= trackWidth else event.x >= recyclerView.width - trackWidth
|
|
||||||
atThumbVertical && run {
|
|
||||||
withScroll { itemHeight, thumbHeight, range ->
|
|
||||||
(recyclerView.parent as? ViewGroup)?.requestDisallowInterceptTouchEvent(
|
|
||||||
true
|
|
||||||
)
|
|
||||||
val offset = currentOffset(itemHeight, range)
|
|
||||||
val thumbY = ((recyclerView.height - thumbHeight) * offset).roundToInt()
|
|
||||||
val atThumb = event.y >= thumbY && event.y <= thumbY + thumbHeight
|
|
||||||
val fastScrolling =
|
|
||||||
FastScrolling(if (atThumb) offset else null, event.y, event.y)
|
|
||||||
scroll(itemHeight, thumbHeight, range, fastScrolling)
|
|
||||||
updateState(scrolling, fastScrolling)
|
|
||||||
recyclerView.invalidate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> lastFastScrolling != null && run {
|
|
||||||
val success = withScroll { itemHeight, thumbHeight, range ->
|
|
||||||
val fastScrolling = lastFastScrolling.copy(currentY = event.y)
|
|
||||||
scroll(itemHeight, thumbHeight, range, fastScrolling)
|
|
||||||
updateState(scrolling, fastScrolling)
|
|
||||||
recyclerView.invalidate()
|
|
||||||
}
|
|
||||||
val cancel =
|
|
||||||
event.action == MotionEvent.ACTION_UP || event.action == MotionEvent.ACTION_CANCEL
|
|
||||||
if (!success || cancel) {
|
|
||||||
(recyclerView.parent as? ViewGroup)?.requestDisallowInterceptTouchEvent(
|
|
||||||
false
|
|
||||||
)
|
|
||||||
updateState(scrolling, null)
|
|
||||||
recyclerView.invalidate()
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {
|
|
||||||
return handleTouchEvent(e, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onTouchEvent(rv: RecyclerView, e: MotionEvent) {
|
|
||||||
handleTouchEvent(e, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {
|
|
||||||
this.disallowIntercept = disallowIntercept
|
|
||||||
if (disallowIntercept && fastScrolling != null) {
|
|
||||||
updateState(scrolling, null)
|
|
||||||
recyclerView.invalidate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleDraw(canvas: Canvas) {
|
|
||||||
withScroll { itemHeight, thumbHeight, range ->
|
|
||||||
val display = display
|
|
||||||
val time = SystemClock.elapsedRealtime()
|
|
||||||
val passed = time - display.first
|
|
||||||
val shouldInvalidate = display.second && passed < TRANSITION_IN ||
|
|
||||||
!display.second && passed >= TRANSITION_OUT_DELAY && passed < TRANSITION_OUT_DELAY + TRANSITION_OUT
|
|
||||||
val stateValue = (if (display.second) {
|
|
||||||
passed.toFloat() / TRANSITION_IN
|
|
||||||
} else {
|
|
||||||
1f - (passed - TRANSITION_OUT_DELAY).toFloat() / TRANSITION_OUT
|
|
||||||
}).coerceIn(0f, 1f)
|
|
||||||
|
|
||||||
if (stateValue > 0f) {
|
|
||||||
val rtl = recyclerView.layoutDirection == RecyclerView.LAYOUT_DIRECTION_RTL
|
|
||||||
val thumbDrawable = thumbDrawable
|
|
||||||
val trackDrawable = trackDrawable
|
|
||||||
val maxWidth = max(thumbDrawable.intrinsicWidth, trackDrawable.intrinsicHeight)
|
|
||||||
val translateX = (maxWidth * (1f - stateValue)).roundToInt()
|
|
||||||
val fastScrolling = fastScrolling
|
|
||||||
|
|
||||||
val scrollValue = (if (fastScrolling != null) {
|
|
||||||
calculateOffset(thumbHeight, fastScrolling)
|
|
||||||
} else {
|
|
||||||
currentOffset(itemHeight, range)
|
|
||||||
}).coerceIn(0f, 1f)
|
|
||||||
val thumbY = ((recyclerView.height - thumbHeight) * scrollValue).roundToInt()
|
|
||||||
|
|
||||||
trackDrawable.state = if (fastScrolling != null) statePressed else stateNormal
|
|
||||||
val trackExtra = (maxWidth - trackDrawable.intrinsicWidth) / 2
|
|
||||||
if (rtl) {
|
|
||||||
trackDrawable.setBounds(
|
|
||||||
trackExtra - translateX, 0,
|
|
||||||
trackExtra + trackDrawable.intrinsicWidth - translateX, recyclerView.height
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
trackDrawable.setBounds(
|
|
||||||
recyclerView.width - trackExtra - trackDrawable.intrinsicWidth + translateX,
|
|
||||||
0, recyclerView.width - trackExtra + translateX, recyclerView.height
|
|
||||||
)
|
|
||||||
}
|
|
||||||
trackDrawable.draw(canvas)
|
|
||||||
val thumbExtra = (maxWidth - thumbDrawable.intrinsicWidth) / 2
|
|
||||||
thumbDrawable.state = if (fastScrolling != null) statePressed else stateNormal
|
|
||||||
if (rtl) {
|
|
||||||
thumbDrawable.setBounds(
|
|
||||||
thumbExtra - translateX, thumbY,
|
|
||||||
thumbExtra + thumbDrawable.intrinsicWidth - translateX, thumbY + thumbHeight
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
thumbDrawable.setBounds(
|
|
||||||
recyclerView.width - thumbExtra - thumbDrawable.intrinsicWidth + translateX,
|
|
||||||
thumbY, recyclerView.width - thumbExtra + translateX, thumbY + thumbHeight
|
|
||||||
)
|
|
||||||
}
|
|
||||||
thumbDrawable.draw(canvas)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldInvalidate) {
|
|
||||||
recyclerView.invalidate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
recyclerView.addOnScrollListener(scrollListener)
|
|
||||||
recyclerView.addOnItemTouchListener(touchListener)
|
|
||||||
recyclerView.addItemDecoration(object : RecyclerView.ItemDecoration() {
|
|
||||||
override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) =
|
|
||||||
handleDraw(c)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +1,12 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<solid
|
<solid android:color="?attr/colorPrimary" />
|
||||||
android:angle="0"
|
|
||||||
android:color="?attr/colorPrimary" />
|
|
||||||
|
|
||||||
<size
|
<corners android:radius="8dp" />
|
||||||
android:width="6dp"
|
|
||||||
android:height="50dp" />
|
|
||||||
|
|
||||||
<corners android:radius="3dp" />
|
<padding android:top="4dp" android:bottom="4dp" />
|
||||||
|
|
||||||
|
<size android:width="8dp" android:height="52dp" />
|
||||||
|
|
||||||
</shape>
|
</shape>
|
@ -1,12 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
|
|
||||||
<solid
|
|
||||||
android:angle="0"
|
|
||||||
android:color="?attr/colorSurface" />
|
|
||||||
|
|
||||||
<size android:width="6dp" />
|
|
||||||
|
|
||||||
<corners android:radius="3dp" />
|
|
||||||
|
|
||||||
</shape>
|
|
@ -32,9 +32,6 @@
|
|||||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||||
<item name="android:windowLightStatusBar" tools:targetApi="m">true</item>
|
<item name="android:windowLightStatusBar" tools:targetApi="m">true</item>
|
||||||
|
|
||||||
<item name="android:fastScrollThumbDrawable">@drawable/scrollbar_thumb</item>
|
|
||||||
<item name="android:fastScrollTrackDrawable">@drawable/scrollbar_track</item>
|
|
||||||
|
|
||||||
<item name="tabStyle">@style/Theme.Tab</item>
|
<item name="tabStyle">@style/Theme.Tab</item>
|
||||||
<item name="switchStyle">@style/Theme.Switch</item>
|
<item name="switchStyle">@style/Theme.Switch</item>
|
||||||
<item name="toolbarStyle">@style/Theme.Toolbar</item>
|
<item name="toolbarStyle">@style/Theme.Toolbar</item>
|
||||||
@ -72,9 +69,6 @@
|
|||||||
<item name="android:statusBarColor">@color/grey_dark</item>
|
<item name="android:statusBarColor">@color/grey_dark</item>
|
||||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||||
|
|
||||||
<item name="android:fastScrollThumbDrawable">@drawable/scrollbar_thumb</item>
|
|
||||||
<item name="android:fastScrollTrackDrawable">@drawable/scrollbar_track</item>
|
|
||||||
|
|
||||||
<item name="tabStyle">@style/Theme.Tab</item>
|
<item name="tabStyle">@style/Theme.Tab</item>
|
||||||
<item name="switchStyle">@style/Theme.Switch</item>
|
<item name="switchStyle">@style/Theme.Switch</item>
|
||||||
<item name="toolbarStyle">@style/Theme.Toolbar</item>
|
<item name="toolbarStyle">@style/Theme.Toolbar</item>
|
||||||
@ -112,9 +106,6 @@
|
|||||||
<item name="android:statusBarColor">@color/md_theme_dark_surface</item>
|
<item name="android:statusBarColor">@color/md_theme_dark_surface</item>
|
||||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||||
|
|
||||||
<item name="android:fastScrollThumbDrawable">@drawable/scrollbar_thumb</item>
|
|
||||||
<item name="android:fastScrollTrackDrawable">@drawable/scrollbar_track</item>
|
|
||||||
|
|
||||||
<item name="tabStyle">@style/Theme.Tab</item>
|
<item name="tabStyle">@style/Theme.Tab</item>
|
||||||
<item name="switchStyle">@style/Theme.Switch</item>
|
<item name="switchStyle">@style/Theme.Switch</item>
|
||||||
<item name="toolbarStyle">@style/Theme.Toolbar</item>
|
<item name="toolbarStyle">@style/Theme.Toolbar</item>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user