You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 06:55:55