In Android, scrolling is typically achieved by using the
ScrollView
class. Nest any standard layout that might extend beyond the bounds of its
container in a ScrollView to provide a scrollable view managed by
the framework. Implementing a custom scroller is only necessary for special
scenarios. This document describes how to display a scrolling effect in response
to touch gestures using scrollers.
Your app can use
scrollers—Scroller
or
OverScroller—to
collect the data needed to produce a scrolling animation in response to a touch
event. They are similar, but OverScroller also includes methods for
indicating to users when they reach the content edges after a pan or fling
gesture.
- Starting in Android 12 (API level 31), the visual elements stretch and bounce back on a drag event and fling and bounce back on a fling event.
- On Android 11 (API level 30) and earlier, the boundaries display a "glow" effect after a drag or fling gesture to the edge.
The InteractiveChart sample in this document uses the
EdgeEffect
class to display these overscroll effects.
You can use a scroller to animate scrolling over time, using platform-standard scrolling physics such as friction, velocity, and other qualities. The scroller itself doesn't draw anything. Scrollers track scroll offsets for you over time, but they don't automatically apply those positions to your view. You must get and apply new coordinates at a rate that makes the scrolling animation look smooth.
Understand scrolling terminology
Scrolling is a word that can mean different things in Android, depending on the context.
Scrolling is the general process of moving the viewport—that is,
the "window" of content you're looking at. When scrolling is in both the
x- and y-axes, it's called panning. The
InteractiveChart sample app in this document illustrates two
different types of scrolling, dragging and flinging:
- Dragging: this is the type of scrolling that occurs when a user
drags their finger across the touchscreen. You can implement dragging by
overriding
onScroll()inGestureDetector.OnGestureListener. For more information about dragging, see Drag and scale. - Flinging: this is the type of scrolling that occurs when a user
drags and lifts their finger quickly. After the user lifts their finger, you
generally want to keep moving the viewport, but decelerate until the
viewport stops moving. You can implement flinging by overriding
onFling()inGestureDetector.OnGestureListenerand using a scroller object. - Panning: scrolling simultaneously along both the x- and y-axes is called panning.
It's common to use scroller objects in conjunction with a fling gesture, but
you can use them in any context where you want the UI to display scrolling in
response to a touch event. For example, you can override
onTouchEvent()
to process touch events directly and produce a scrolling effect or a
"snap-to-page" animation in response to those touch events.
Components that contain built-in scrolling implementations
The following Android components contain built-in support for scrolling and overscrolling behavior:
GridViewHorizontalScrollViewListViewNestedScrollViewRecyclerViewScrollViewViewPagerViewPager2
If your app needs to support scrolling and overscrolling inside a different component, complete the following steps:
- Create a custom touch-based scrolling implementation.
- To support devices that run Android 12 and later, implement the stretch overscroll effect.
Create a custom touch-based scrolling implementation
This section describes how to create your own scroller if your app uses a component that doesn't contain built-in support for scrolling and overscrolling.
The following snippet comes from the
InteractiveChart
sample. It uses a
GestureDetector
and overrides the
GestureDetector.SimpleOnGestureListener
method onFling(). It uses OverScroller to track the
fling gesture. If the user reaches the content edges after they perform the
fling gesture, the container indicates when the user reaches the end of the
content. The indication depends on the version of Android that a device
runs:
- On Android 12 and later, the visual elements stretch and bounce back.
- On Android 11 and earlier, the visual elements display a glow effect.
The first part of the following snippet shows the implementation of
onFling():
Kotlin
// Viewport extremes. See currentViewport for a discussion of the viewport. private val AXIS_X_MIN = -1f private val AXIS_X_MAX = 1f private val AXIS_Y_MIN = -1f private val AXIS_Y_MAX = 1f // The current viewport. This rectangle represents the visible chart // domain and range. The viewport is the part of the app that the // user manipulates via touch gestures. private val currentViewport = RectF(AXIS_X_MIN, AXIS_Y_MIN, AXIS_X_MAX, AXIS_Y_MAX) // The current destination rectangle—in pixel coordinates—into which // the chart data must be drawn. private lateinit var contentRect: Rect private lateinit var scroller: OverScroller private lateinit var scrollerStartViewport: RectF ... private val gestureListener = object : GestureDetector.SimpleOnGestureListener() { override fun onDown(e: MotionEvent): Boolean { // Initiates the decay phase of any active edge effects. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { releaseEdgeEffects() } scrollerStartViewport.set(currentViewport) // Aborts any active scroll animations and invalidates. scroller.forceFinished(true) ViewCompat.postInvalidateOnAnimation(this@InteractiveLineGraphView) return true } ... override fun onFling( e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float ): Boolean { fling((-velocityX).toInt(), (-velocityY).toInt()) return true } } private fun fling(velocityX: Int, velocityY: Int) { // Initiates the decay phase of any active edge effects. // On Android 12 and later, the edge effect (stretch) must // continue. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { releaseEdgeEffects() } // Flings use math in pixels, as opposed to math based on the viewport. val surfaceSize: Point = computeScrollSurfaceSize() val (startX: Int, startY: Int) = scrollerStartViewport.run { set(currentViewport) (surfaceSize.x * (left - AXIS_X_MIN) / (AXIS_X_MAX - AXIS_X_MIN)).toInt() to (surfaceSize.y * (AXIS_Y_MAX - bottom) / (AXIS_Y_MAX - AXIS_Y_MIN)).toInt() } // Before flinging, stops the current animation. scroller.forceFinished(true) // Begins the animation. scroller.fling( // Current scroll position. startX, startY, velocityX, velocityY, /* * Minimum and maximum scroll positions. The minimum scroll * position is generally 0 and the maximum scroll position * is generally the content size less the screen size. So if the * content width is 1000 pixels and the screen width is 200 * pixels, the maximum scroll offset is 800 pixels. */ 0, surfaceSize.x - contentRect.width(), 0, surfaceSize.y - contentRect.height(), // The edges of the content. This comes into play when using // the EdgeEffect class to draw "glow" overlays. contentRect.width() / 2, contentRect.height() / 2 ) // Invalidates to trigger computeScroll(). ViewCompat.postInvalidateOnAnimation(this) }
Java
// Viewport extremes. See currentViewport for a discussion of the viewport. private static final float AXIS_X_MIN = -1f; private static final float AXIS_X_MAX = 1f; private static final float AXIS_Y_MIN = -1f; private static final float AXIS_Y_MAX = 1f; // The current viewport. This rectangle represents the visible chart // domain and range. The viewport is the part of the app that the // user manipulates via touch gestures. private RectF currentViewport = new RectF(AXIS_X_MIN, AXIS_Y_MIN, AXIS_X_MAX, AXIS_Y_MAX); // The current destination rectangle—in pixel coordinates—into which // the chart data must be drawn. private final Rect contentRect = new Rect(); private final OverScroller scroller; private final RectF scrollerStartViewport