手を動かしてViewDragHelperを学ぶ(無駄にKotlin)
2016/03/13
まず、Youtubeの公式Androidアプリみたいなヤツ作りたくなった。
結構イイカンジに動くので「実装どうなってんのかな」と見てみたら、ViewDragHelperってヤツがいた。
こいつがイイカンジにDrag処理を捌いてくれてるっぽい。
コード読んでみたけど、手を動かしてみないと定着しないのでアレ。
なので簡単なアプリ(ゴミ)を作りながら、どういうことができるのか試してみた。
Contents
とりあえず適当なViewをドラッグしたい
カスタムViewGroupにViewDragHelperインスタンスを持たせ、かつ必要に応じて処理を呼ぶ必要がある。
とりあえず適当なViewをドラッグしたいなら、最低限必要な準備は次の6つ。
- ViewDragHelperインスタンスを作る
onTouchEvent
でprocessTouchEvent(e: MotionEvent)
を呼ぶViewDragHelper.Callback()
のサブクラスを用意するtryCaptureView
を実装clampViewPositionVertical
をオーバライドclampViewPositionHorizontal
をオーバライド
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
class AwesomeDragLayout(context: Context?, attrs: AttributeSet?) : RelativeLayout(context, attrs) { private var dragHelper: ViewDragHelper? = ViewDragHelper.create(this, 1.0f, AwesomeDragHelperCallback()) override fun onTouchEvent(event: MotionEvent?): Boolean { dragHelper?.processTouchEvent(event) return true } } class AwesomeDragHelperCallback : ViewDragHelper.Callback() { override fun tryCaptureView(p0: View?, p1: Int): Boolean { return true } override fun clampViewPositionVertical(child: View?, top: Int, dy: Int): Int { return top } override fun clampViewPositionHorizontal(child: View?, left: Int, dx: Int): Int { return left } } |
これだけ。
あとはカスタムViewGroup上に適当な子Viewを載せてやればOK
あー、ドラッグできた。
気持ちいいね。清原的な意味ではなく。
いろんなメソッド(コールバック)を試してみる
smoothSlideViewTo
指定したViewを指定した位置に移動してくれるメソッド。
ボタンをタップするたびに適当な位置に滑らか移動してもらうようにした。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
class AwesomeDragLayout(context: Context?, attrs: AttributeSet?) : RelativeLayout(context, attrs) { private var dragHelper: ViewDragHelper? = ViewDragHelper.create(this, 1.0f, AwesomeDragHelperCallback()) private var button: Button? = null private final val MOVE = 600 init { LayoutInflater.from(context).inflate(R.layout.awesome_drag_layout, this) button = findViewById(R.id.awesome_action_button) as Button val star = findViewById(R.id.star) button?.setOnClickListener { dragHelper?.smoothSlideViewTo(star, (MOVE * Math.random()).toInt(), (MOVE * Math.random()).toInt()) invalidate() } } override fun computeScroll() { if (dragHelper?.continueSettling(true) ?: false) { invalidate() } } } |
smoothSlideViewTo
を呼び出した後にinvalidate()
を呼ぶcomputeScroll
をオーバライドしてDragHelper
のcontinueSetting
を呼びつつinvalidate()
してやる
setEdgeTrackingEnabled
親ViewのEdgeのタッチイベントを検出できるようにする。
ナビゲーションドロワーみたいなヤツはこれを使って実現してる。
setEdgetTrackgingEnabled(ViewDragHelper.LEFT)
これで画面左端のタッチをトラッキングしてくれるようになった。
トラッキングしたイベントのハンドリングはViewDragHelper.Callback
に委譲する。
onEdgeDragStarted
、エッジ上でのドラッグイベント開始を検出したタイミングでViewDragHelperにドラッグ対象のViewを与える。それがcaptureChildView
。
captureChildView
に与えたViewの親Viewと、ViewDragHelperが保持してるViewが一致しないとエラー吐くので注意。
↑この辺の処理をまとめのが下記。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
class AwesomeDragLayout(context: Context?, attrs: AttributeSet?) : RelativeLayout(context, attrs) { private val dragHelper: ViewDragHelper? private val star: View? private val that: RelativeLayout? init { LayoutInflater.from(context).inflate(R.layout.awesome_drag_layout, this) star = findViewById(R.id.star) that = findViewById(R.id.parent) as RelativeLayout dragHelper = ViewDragHelper.create(that, 1.0f, DragHelperCallback()) dragHelper?.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT) } override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean { return dragHelper?.shouldInterceptTouchEvent(ev) ?: false } override fun onTouchEvent(event: MotionEvent?): Boolean { dragHelper?.processTouchEvent(event) return true } inner class DragHelperCallback : ViewDragHelper.Callback() { override fun tryCaptureView(p0: View?, p1: Int): Boolean { return false } override fun clampViewPositionHorizontal(child: View?, left: Int, dx: Int): Int { return left } override fun onEdgeDragStarted(edgeFlags: Int, pointerId: Int) { super.onEdgeDragStarted(edgeFlags, pointerId) dragHelper?.captureChildView(star, pointerId) } } } |
こんな感じで、エッジのドラッグによってView(星)を動かすことができた。
onViewReleased
ドラッグ状態が解除されたときに呼ばれるコールバック。
先のonEdgeDragStarted
の例に少しコードを付け足して、エッジドラッグ中に画面から指を離したらView(星)が元の位置に戻るようにしてみる。。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
class AwesomeDragLayout(context: Context?, attrs: AttributeSet?) : RelativeLayout(context, attrs) { private val dragHelper: ViewDragHelper? private val star: View? private val that: RelativeLayout? init { LayoutInflater.from(context).inflate(R.layout.awesome_drag_layout, this) star = findViewById(R.id.star) that = findViewById(R.id.parent) as RelativeLayout dragHelper = ViewDragHelper.create(that, 1.0f, DragHelperCallback()) dragHelper?.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT) } override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean { return dragHelper?.shouldInterceptTouchEvent(ev) ?: false } override fun onTouchEvent(event: MotionEvent?): Boolean { dragHelper?.processTouchEvent(event) return true } override fun computeScroll() { if (dragHelper?.continueSettling(true) ?: false) { invalidate() } } inner class DragHelperCallback : ViewDragHelper.Callback() { override fun tryCaptureView(p0: View?, p1: Int): Boolean { return false } override fun clampViewPositionHorizontal(child: View?, left: Int, dx: Int): Int { return left } override fun onEdgeDragStarted(edgeFlags: Int, pointerId: Int) { super.onEdgeDragStarted(edgeFlags, pointerId) dragHelper?.captureChildView(star, pointerId) } override fun onViewReleased(releasedChild: View?, xvel: Float, yvel: Float) { super.onViewReleased(releasedChild, xvel, yvel) dragHelper?.smoothSlideViewTo(star, 0, 0) invalidate() } } } |
onViewReleased
内でsmoothSlideViewTo
を呼ぶだけ。
TODO : 他のヤツも試す
336px
336px
関連記事
- PREV
- PythonでEnumに状態を追加する
- NEXT
- 量産型ファッションはクソなので今すぐやめよう