feat: scroll app list from anywhere on screen; gate search item animation

DrawFragment: app-list touch area now forwards vertical drags/flings to the
drawer RecyclerView, so the list scrolls even when grabbing empty space
right of the app names. OnSwipeTouchListener gained no-op vertical scroll
and fling hooks.

When animations are disabled, drop the RecyclerView item animator so
DiffUtil search updates no longer pan entries up into place.
This commit is contained in:
2026-08-19 10:39:40 +02:00
parent e69796535f
commit 0aaf00988d
2 changed files with 75 additions and 23 deletions

View File

@@ -90,6 +90,7 @@ internal open class OnSwipeTouchListener(context: Context?, private val preferen
}
// Vertical swipe
else {
onVerticalScroll(distanceY)
if (abs(diffY) > swipeThreshold && abs(distanceY) > swipeScrollThreshold) {
if (diffY > 0) onSwipeDown() else onSwipeUp()
}
@@ -115,6 +116,7 @@ internal open class OnSwipeTouchListener(context: Context?, private val preferen
}
} else {
if (abs(diffY) > swipeThreshold && abs(velocityY) > swipeVelocityThreshold) {
onVerticalFling(velocityY)
if (diffY < 0) onSwipeUp() else onSwipeDown()
}
}
@@ -129,6 +131,21 @@ internal open class OnSwipeTouchListener(context: Context?, private val preferen
open fun onSwipeLeft() {}
open fun onSwipeUp() {}
open fun onSwipeDown() {}
/**
* Called for every vertical drag movement (per-event delta, same sign
* convention as RecyclerView's own touch handling: positive when the
* finger moves up). Default is a no-op.
*/
open fun onVerticalScroll(distanceY: Float) {}
/**
* Called on a vertical fling with the raw pointer velocity in pixels per
* second (positive when the finger moved down, same convention as
* VelocityTracker.getYVelocity()). Default is a no-op.
*/
open fun onVerticalFling(velocityY: Float) {}
open fun onLongClick() {}
open fun onDoubleClick() {}
open fun onTripleClick() {}

View File

@@ -40,6 +40,7 @@ import com.github.droidworksstudio.launcher.helper.PreferenceHelper
import com.github.droidworksstudio.launcher.listener.OnItemClickedListener
import com.github.droidworksstudio.launcher.listener.OnSwipeTouchListener
import com.github.droidworksstudio.launcher.listener.ScrollEventListener
import kotlin.math.roundToInt
import com.github.droidworksstudio.launcher.ui.bottomsheetdialog.AppInfoBottomSheetFragment
import com.github.droidworksstudio.launcher.utils.Constants
import com.github.droidworksstudio.launcher.viewmodel.AppViewModel
@@ -157,6 +158,11 @@ class DrawFragment : Fragment(),
setHasFixedSize(false)
layoutManager = StaggeredGridLayoutManager(1, StaggeredGridLayoutManager.VERTICAL)
isNestedScrollingEnabled = false
// Search results update the list with DiffUtil. When animations
// are disabled, drop the item animator so entries don't "pan-up"
// into place while filtering.
if (preferenceHelper.disableAnimations) itemAnimator = null
}
}
@@ -237,7 +243,7 @@ class DrawFragment : Fragment(),
binding.apply {
mainView.setOnTouchListener(getSwipeGestureListener(context))
touchArea.setOnTouchListener(getSwipeGestureListener(context))
appListTouchArea.setOnTouchListener(getSwipeGestureListener(context))
appListTouchArea.setOnTouchListener(getAppListSwipeGestureListener(context))
}
}
@@ -302,36 +308,65 @@ class DrawFragment : Fragment(),
return object : OnSwipeTouchListener(context, preferenceHelper) {
override fun onSwipeLeft() {
super.onSwipeLeft()
val actionTypeNavOptions: NavOptions? =
if (preferenceHelper.disableAnimations) null
else appHelper.getActionType(Constants.Swipe.Left)
Handler(Looper.getMainLooper()).post {
findNavController().navigate(
R.id.HomeFragment,
null,
actionTypeNavOptions
)
}
navigateToHome(Constants.Swipe.Left)
}
override fun onSwipeRight() {
super.onSwipeRight()
val actionTypeNavOptions: NavOptions? =
if (preferenceHelper.disableAnimations) null
else appHelper.getActionType(Constants.Swipe.Right)
Handler(Looper.getMainLooper()).post {
findNavController().navigate(
R.id.HomeFragment,
null,
actionTypeNavOptions
)
}
navigateToHome(Constants.Swipe.Right)
}
}
}
/**
* Swipe listener for the app list area.
*
* Behaves like [getSwipeGestureListener] (left/right swipes navigate
* home), but vertical drags and flings over the empty space around the
* list (e.g. right of the app names) scroll the list itself. This makes
* the drawer scrollable from the whole screen, not just the app rows.
*/
private fun getAppListSwipeGestureListener(context: Context): View.OnTouchListener {
return object : OnSwipeTouchListener(context, preferenceHelper) {
override fun onSwipeLeft() {
super.onSwipeLeft()
navigateToHome(Constants.Swipe.Left)
}
override fun onSwipeRight() {
super.onSwipeRight()
navigateToHome(Constants.Swipe.Right)
}
override fun onVerticalScroll(distanceY: Float) {
super.onVerticalScroll(distanceY)
binding.drawAdapter.scrollBy(0, distanceY.roundToInt())
}
override fun onVerticalFling(velocityY: Float) {
super.onVerticalFling(velocityY)
// RecyclerView.fling expects the negated pointer velocity
// (the same negation RecyclerView applies to its own
// VelocityTracker value in onTouchEvent).
binding.drawAdapter.fling(0, -velocityY.roundToInt())
}
}
}
private fun navigateToHome(swipe: Constants.Swipe) {
val actionTypeNavOptions: NavOptions? =
if (preferenceHelper.disableAnimations) null
else appHelper.getActionType(swipe)
Handler(Looper.getMainLooper()).post {
findNavController().navigate(
R.id.HomeFragment,
null,
actionTypeNavOptions
)
}
}
/**
* Runs the fuzzy search synchronously over the in-memory index.
*