Scroller と OverScroller が用意されています。 OverScroller は行き過ぎて戻ってくるようなアニメーションができます。
Scroller にはアニメーションを開始するメソッドとして が用意されています。
使い方はこんな感じです。
- 1. scroller.forceFinished() でアニメーションを止める
- 2. scroller.fling() または scroller.startScroll() でアニメーションを開始する
- 3. View.postInvalidateOnAnimation() を呼ぶ。これを呼ぶと View.computeScroll() が呼ばれる
- 4. View.computeScroll() で scroller.computeScrollOffset() を呼ぶ。戻り値が true の場合アニメーションが終わっていないということ
- 5. scroller.currX, scroller.currY を使って View の位置などを変える
class ScrollerSampleView : FrameLayout {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
)
private val size = (100 * resources.displayMetrics.density).toInt()
private val targetView: View = View(context).apply {
layoutParams = LayoutParams(size, size)
setBackgroundColor(Color.RED)
}
private val textView: TextView = TextView(context).apply {
layoutParams = LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT
)
}
private val scroller = OverScroller(context)
init {
addView(targetView)
addView(textView)
}
fun scroll(dx: Int, dy: Int, duration: Int, friction: Float) {
scroller.setFriction(friction)
// scroll の前に今のアニメーションを止める
scroller.forceFinished(true)
targetView.translationX = 0f
targetView.translationY = 0f
val startX = 0
val startY = 0
// アニメーションを開始
scroller.startScroll(
startX, // scroll の開始位置 (X)
startY, // scroll の開始位置 (Y)
dx, // 移動する距離、正の値だとコンテンツが左にスクロールする (X)
dy, // 移動する距離、正の値だとコンテンツが左にスクロールする (Y)
duration // スクロールにかかる時間 [milliseconds]
)
// これにより computeScroll() が呼ばれる
postInvalidateOnAnimation()
}
fun fling(velocityX: Int, velocityY: Int, overX: Int, overY: Int, friction: Float) {
scroller.setFriction(friction)
// fling の前に今のアニメーションを止める
scroller.forceFinished(true)
targetView.translationX = 0f
targetView.translationY = 0f
val startX = 0
val startY = 0
val minX = 0
val maxX = 800
val minY = 0
val maxY = 800
// アニメーションを開始
scroller.fling(
startX, // scroll の開始位置 (X)
startY, // scroll の開始位置 (Y)
velocityX, // fling の初速 [px/sec] (X)
velocityY, // fling の初速 [px/sec] (Y)
minX, // X の最小値. minX - overX まで移動し、minX 未満のところは overfling 中になる
maxX, // X の最大値. maxX + overX まで移動し、maxX を超えたところは overfling 中になる
minY, // Y の最小値. minY - overY まで移動し、minY 未満のところは overfling 中になる
maxY, // Y の最大値. maxY + overY まで移動し、maxY を超えたところは overfling 中になる
overX, // overfling の範囲 (X). overfling の範囲は両端に適用される
overY // Overfling の範囲 (Y). overfling の範囲は両端に適用される
)
// これにより computeScroll() が呼ばれる
postInvalidateOnAnimation()
}
override fun computeScroll() {
super.computeScroll()
// computeScrollOffset() の戻り値が true == まだアニメーション中
if (scroller.computeScrollOffset()) {
textView.text = """
currVelocity: ${scroller.currVelocity}
currX: ${scroller.currX}
currY: ${scroller.currY}
startX: ${scroller.startX}
startY: ${scroller.startY}
finalX: ${scroller.finalX}
finalY: ${scroller.finalY}
isFinished: ${scroller.isFinished}
isOverScrolled: ${scroller.isOverScrolled}
""".trimIndent()
targetView.translationX = scroller.currX.toFloat()
targetView.translationY = scroller.currY.toFloat()
// アニメーション中なので再度呼ぶ
postInvalidateOnAnimation()
}
}
}
速度を 1000 [px/sec], 2000 [px/sec], 3000 [px/sec], 4000 [px/sec]、摩擦を ViewConfiguration.getScrollFriction(), ViewConfiguration.getScrollFriction() / 2、overfling 範囲を 0, 200 で上記の fling() を呼んだ結果が次の動画です。
摩擦を半分にすると同じ速度でも遠くまで移動し、overfling 範囲をつけると行き過ぎて戻ってくるようになります。