如何在Kotlin中实现Java版OnSwipeTouchListener手势监听类
将Java自定义手势监听器转为Kotlin实现
问题背景
在Kotlin开发视频播放器时,需要迁移此前Java中可用的OnSwipeTouchListener自定义手势监听器,以下是具体实现方案:
原Java版OnSwipeTouchListener代码
import android.content.Context; import android.graphics.Rect; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; import androidx.core.view.GestureDetectorCompat; public abstract class OnSwipeTouchListener implements View.OnTouchListener { private final GestureDetectorCompat gestureDetector; private final GestureListener gestureListener; protected OnSwipeTouchListener(Context context){ gestureListener = new GestureListener(); gestureDetector = new GestureDetectorCompat(context, gestureListener); } @Override public boolean onTouch(View v, MotionEvent event) { gestureListener.onTouchEvent(event); return true; } public boolean onSwipeRight() { return false; } public boolean onSwipeLeft() { return false; } public boolean onSwipeTop() { return false; } public boolean onSwipeBottom() { return false; } public boolean onDoubleTap() { return false; } public boolean onSingleTap() { return true; } public abstract void onGestureDone(); public abstract void adjustBrightness(double adjustPercent); public abstract void adjustVolumeLevel(double adjustPercent); public abstract void adjustVideoPosition(double adjustPercent, boolean forwardDirection); public abstract Rect viewRect(); private enum SwipeEventType { NONE, BRIGHTNESS, VOLUME, SEEK, COMMENTS, DESCRIPTION } private final class GestureListener extends GestureDetector.SimpleOnGestureListener { private static final int SWIPE_THRESHOLD = 50; private SwipeEventType swipeEventType = SwipeEventType.NONE; private MotionEvent startEvent; @Override public boolean onDoubleTap(MotionEvent e) { return OnSwipeTouchListener.this.onDoubleTap(); } @Override public boolean onSingleTapConfirmed(MotionEvent e) { return onSingleTap(); } @Override public boolean onScroll(MotionEvent start, MotionEvent end, float distanceX, float distanceY) { double yDistance = end.getY() - start.getY(); double xDistance = end.getX() - start.getX(); if (swipeEventType == SwipeEventType.COMMENTS) { return onSwipeLeft(); } else if (swipeEventType == SwipeEventType.DESCRIPTION) { return onSwipeTop(); } else if (swipeEventType == SwipeEventType.BRIGHTNESS) { double percent = yDistance / (getBrightnessRect().height() / 2.0f) * -1.0f; adjustBrightness(percent); } else if (swipeEventType == SwipeEventType.VOLUME) { double percent = yDistance / (getVolumeRect().height() / 2.0f) * -1.0f; adjustVolumeLevel(percent); } else if (swipeEventType == SwipeEventType.SEEK) { double percent = xDistance / viewRect().width(); adjustVideoPosition(percent, distanceX < 0); } return true; } private boolean isEventValid(MotionEvent event) { return event.getPointerCount() == 1; } private void onTouchEvent(MotionEvent event) { if (isEventValid(event)) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: startEvent = MotionEvent.obtain(event); break; case MotionEvent.ACTION_MOVE: if (swipeEventType == SwipeEventType.NONE && startEvent != null) { swipeEventType = whatTypeIsIt(startEvent, event); } break; case MotionEvent.ACTION_UP: onGestureDone(); swipeEventType = SwipeEventType.NONE; startEvent = null; break; default: break; } } gestureDetector.onTouchEvent(event); } private SwipeEventType whatTypeIsIt(MotionEvent startEvent, MotionEvent currentEvent) { float startX = startEvent.getX(); float startY = startEvent.getY(); float currentX = currentEvent.getX(); float currentY = currentEvent.getY(); float diffY = startY - currentY; float diffX = startX - currentX; if (Math.abs(currentX - startX) >= SWIPE_THRESHOLD && Math.abs(currentX - startX) > Math.abs(currentY - startY)) { if (getCommentsRect().contains((int) startX, (int) startY) && diffX > 0) return SwipeEventType.COMMENTS; return SwipeEventType.SEEK; } else if (Math.abs(currentY - startY) >= SWIPE_THRESHOLD) { if (getDescriptionRect().contains((int) startX, (int) startY) && diffY > 0) { return SwipeEventType.DESCRIPTION; } else if (getBrightnessRect().contains((int) startX, (int) startY)) { return SwipeEventType.BRIGHTNESS; } else if (getVolumeRect().contains((int) startX, (int) startY)) { return SwipeEventType.VOLUME; } } return SwipeEventType.NONE; } private Rect getBrightnessRect() { return new Rect(viewRect().right / 2, 0, viewRect().right, viewRect().bottom); } private Rect getVolumeRect() { return new Rect(0, 0, viewRect().right / 2, viewRect().bottom); } private Rect getCommentsRect() { return new Rect((int)(viewRect().right - Math.min(viewRect().bottom, viewRect().right) * 0.2), 0, viewRect().right, viewRect().bottom); } private Rect getDescriptionRect() { return new Rect(0, (int)(viewRect().bottom - Math.min(viewRect().bottom, viewRect().right) * 0.2), viewRect().right, viewRect().bottom); } } }
Kotlin版OnSwipeTouchListener实现
import android.content.Context import android.graphics.Rect import android.view.GestureDetector import android.view.MotionEvent import android.view.View import androidx.core.view.GestureDetectorCompat abstract class OnSwipeTouchListener(context: Context) : View.OnTouchListener { private val gestureDetector: GestureDetectorCompat private val gestureListener: GestureListener init { gestureListener = GestureListener() gestureDetector = GestureDetectorCompat(context, gestureListener) } override fun onTouch(v: View?, event: MotionEvent?): Boolean { event?.let { gestureListener.onTouchEvent(it) } return true } open fun onSwipeRight(): Boolean = false open fun onSwipeLeft(): Boolean = false open fun onSwipeTop(): Boolean = false open fun onSwipeBottom(): Boolean = false open fun onDoubleTap(): Boolean = false open fun onSingleTap(): Boolean = true abstract fun onGestureDone() abstract fun adjustBrightness(adjustPercent: Double) abstract fun adjustVolumeLevel(adjustPercent: Double) abstract fun adjustVideoPosition(adjustPercent: Double, forwardDirection: Boolean) abstract fun viewRect(): Rect private enum class SwipeEventType { NONE, BRIGHTNESS, VOLUME, SEEK, COMMENTS, DESCRIPTION } private inner class GestureListener : GestureDetector.SimpleOnGestureListener() { private companion object { const val SWIPE_THRESHOLD = 50 } private var swipeEventType = SwipeEventType.NONE private var startEvent: MotionEvent? = null override fun onDoubleTap(e: MotionEvent): Boolean { return this@OnSwipeTouchListener.onDoubleTap() } override fun onSingleTapConfirmed(e: MotionEvent): Boolean { return this@OnSwipeTouchListener.onSingleTap() } override fun onScroll( start: MotionEvent, end: MotionEvent, distanceX: Float, distanceY: Float ): Boolean { val yDistance = end.y - start.y val xDistance = end.x - start.x when (swipeEventType) { SwipeEventType.COMMENTS -> return this@OnSwipeTouchListener.onSwipeLeft() SwipeEventType.DESCRIPTION -> return this@OnSwipeTouchListener.onSwipeTop() SwipeEventType.BRIGHTNESS -> { val percent = yDistance / (getBrightnessRect().height() / 2.0f) * -1.0f this@OnSwipeTouchListener.adjustBrightness(percent) } SwipeEventType.VOLUME -> { val percent = yDistance / (getVolumeRect().height() / 2.0f) * -1.0f this@OnSwipeTouchListener.adjustVolumeLevel(percent) } SwipeEventType.SEEK -> { val percent = xDistance / viewRect().width() this@OnSwipeTouchListener.adjustVideoPosition(percent, distanceX < 0) } else -> {} } return true } private fun isEventValid(event: MotionEvent): Boolean { return event.pointerCount == 1 } fun onTouchEvent(event: MotionEvent) { if (isEventValid(event)) { when (event.action) { MotionEvent.ACTION_DOWN -> { startEvent = MotionEvent.obtain(event) } MotionEvent.ACTION_MOVE -> { if (swipeEventType == SwipeEventType.NONE && startEvent != null) { startEvent?.let { swipeEventType = whatTypeIsIt(it, event) } } } MotionEvent.ACTION_UP -> { this@OnSwipeTouchListener.onGestureDone() swipeEventType = SwipeEventType.NONE startEvent = null } } } gestureDetector.onTouchEvent(event) } private fun whatTypeIsIt(startEvent: MotionEvent, currentEvent: MotionEvent): SwipeEventType { val startX = startEvent.x val startY = startEvent.y val currentX = currentEvent.x val currentY = currentEvent.y val diffY = startY - currentY val diffX = startX - currentX return when { Math.abs(currentX - startX) >= SWIPE_THRESHOLD && Math.abs(currentX - startX) > Math.abs(currentY - startY) -> { if (getCommentsRect().contains(startX.toInt(), startY.toInt()) && diffX > 0) { SwipeEventType.COMMENTS } else { SwipeEventType.SEEK } } Math.abs(currentY - startY) >= SWIPE_THRESHOLD -> { when { getDescriptionRect().contains(startX.toInt(), startY.toInt()) && diffY > 0 -> SwipeEventType.DESCRIPTION getBrightnessRect().contains(startX.toInt(), startY.toInt()) -> SwipeEventType.BRIGHTNESS getVolumeRect().contains(startX.toInt(), startY.toInt()) -> SwipeEventType.VOLUME else -> SwipeEventType.NONE } } else -> SwipeEventType.NONE } } private fun getBrightnessRect(): Rect { val rect = viewRect() return Rect(rect.right / 2, 0, rect.right, rect.bottom) } private fun getVolumeRect(): Rect { val rect = viewRect() return Rect(0, 0, rect.right / 2, rect.bottom) } private fun getCommentsRect(): Rect { val rect = viewRect() val sideLength = Math.min(rect.bottom, rect.right) * 0.2 return Rect((rect.right - sideLength).toInt(), 0, rect.right, rect.bottom) } private fun getDescriptionRect(): Rect { val rect = viewRect() val sideLength = Math.min(rect.bottom, rect.right) * 0.2 return Rect(0, (rect.bottom - sideLength).toInt(), rect.right, rect.bottom) } } }
Kotlin中正确使用方式
在播放Activity中,通过匿名内部类实现抽象方法并绑定到View:
binding.playerView.setOnTouchListener(object : OnSwipeTouchListener(this) { override fun onGestureDone() { // 手势结束后的逻辑,比如隐藏控制栏 } override fun adjustBrightness(adjustPercent: Double) { // 调整亮度的逻辑实现 } override fun adjustVolumeLevel(adjustPercent: Double) { // 调整音量的逻辑实现 } override fun adjustVideoPosition(adjustPercent: Double, forwardDirection: Boolean) { // 调整视频进度的逻辑实现 } override fun viewRect(): Rect { // 返回播放器View的矩形区域 return Rect(0, 0, binding.playerView.width, binding.playerView.height) } // 可选:重写默认实现的方法,比如双击、单按等 override fun onDoubleTap(): Boolean { // 双击播放/暂停逻辑 return true } })
内容的提问来源于stack exchange,提问作者Ayan Mishra
相关产品推荐
相关产品推荐

