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

RecyclerView触摸与点击冲突:onFling中e1参数空指针异常

问题描述

原本RecyclerView通过GestureDetector的onFling方法实现上滑缩放功能,运行正常。为RecyclerView Item添加点击事件(通过接口在Activity中实现)后,上滑操作触发空指针异常:

java.lang.NullPointerException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkNotNullParameter, parameter e1

异常堆栈指向com.example.simmone.utils.SwipeServices$GestureListener.onFling方法。

相关代码如下:

SwipeService.kt

class SwipeServices : View.OnTouchListener {
    enum class SwipeDirection {
        bottomToTop
    }

    private var rootLayout: ViewGroup? = null
    private var layoutToShowHide: ViewGroup? = null
    private var gestureDetector: GestureDetector? = null
    private var swipeDirections: MutableList<SwipeDirection>? = null

    fun initialize(rootLayout: ViewGroup, layoutToShowHide:ViewGroup?, swipeDirections: MutableList<SwipeDirection>,maxSwipeDistance: Int = 1) {
        val gestureListener = GestureListener()
        gestureDetector = GestureDetector(rootLayout.context, gestureListener)
        this.rootLayout = rootLayout
        this.layoutToShowHide = layoutToShowHide
        this.swipeDirections = swipeDirections
        gestureListener.MAX_SWIPE_DISTANCE = maxSwipeDistance
        this.rootLayout!!.setOnTouchListener(this)
    }


    @SuppressLint("ClickableViewAccessibility")
    override fun onTouch(v: View?, event: MotionEvent): Boolean {
        return gestureDetector?.onTouchEvent(event)!!
    }

    inner class GestureListener : GestureDetector.SimpleOnGestureListener() {
        var MAX_SWIPE_DISTANCE = 1
        private val SWIPE_VELOCITY_THRESHOLD = 1

        override fun onDown(e: MotionEvent): Boolean {
            return true
        }

        override fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {

            var result = false
            try {
                val diffY = e2.y - e1.y
                if (abs(diffY) > MAX_SWIPE_DISTANCE && abs(velocityY) > SWIPE_VELOCITY_THRESHOLD)  {
                        if (diffY<0) {
                            onSwipeBottomToTop()
                        }
                        }
                result = true
            } catch (exception: Exception) {
                exception.printStackTrace()
            }
            return result
        }
    }

    fun cancel() {
        layoutToShowHide?.animate()?.scaleX(1f)?.scaleY(1f)?.setDuration(500)?.start();
    }

        fun onSwipeBottomToTop() {
        layoutToShowHide?.animate()?.scaleX(.7f)?.scaleY(.7f)?.setDuration(500)?.start();
        }

}

Activity类

class SwappingAppsActivity : AppCompatActivity(),
SwappingAppsAdapter.OnItemClickListener{
    private lateinit var swappingAppsBinding: ActivitySwappingAppsBinding
    private var swappingAppsAdapter: SwappingAppsAdapter? = null
    private lateinit var selectedApp: SwappingItems


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        swappingAppsBinding = ActivitySwappingAppsBinding.inflate(layoutInflater)
        setContentView(swappingAppsBinding.root)

        val swappingAppViewModel = ViewModelProvider(this)[SwappingAppViewModel::class.java]
        swappingAppViewModel.generateSwapItems()

        swappingAppViewModel.swapList.observe(this@SwappingAppsActivity) {
            swappingAppsAdapter = SwappingAppsAdapter(this@SwappingAppsActivity, it)
            swappingAppsBinding.rvSwappingApps.layoutManager = LinearLayoutManager(
                this@SwappingAppsActivity,
                LinearLayoutManager.HORIZONTAL,
                false
            )
            swappingAppsBinding.rvSwappingApps.adapter = swappingAppsAdapter

        }

            val movingElement = swappingAppsBinding.rvSwappingApps
            val swipeServices = SwipeServices()

            //调用滑动服务的部分
            swipeServices.initialize(rootLayout =movingElement,movingElement,
                arrayListOf(SwipeServices.SwipeDirection.bottomToTop), 50)



    }

    override fun onItemClick(view: View, swappingItems: SwappingItems) {

        if (swappingAppsAdapter?.getItemList()?.indexOf(selectedApp)==2){

            val movingElement = swappingAppsBinding.rvSwappingApps

            SwipeServices().cancel()
        }

    }
}
解决方案

问题原因

  1. onFling参数非空声明冲突:Kotlin中GestureListener的onFling方法参数e1被声明为非空,但Android系统在触摸事件被Item点击事件拦截的场景下,会传递null的e1,触发Kotlin非空参数检查,导致空指针异常。
  2. SwipeServices实例未复用:在onItemClick中新建未初始化的SwipeServices实例调用cancel(),属于无效操作,同时也是潜在的资源浪费问题。
  3. selectedApp未初始化风险:onItemClick中直接使用selectedApp,但未提前赋值,可能触发未初始化异常。

修复步骤及代码

1. 修改SwipeService.kt的onFling方法

将e1、e2改为可空参数,并添加空值判断:

inner class GestureListener : GestureDetector.SimpleOnGestureListener() {
    var MAX_SWIPE_DISTANCE = 1
    private val SWIPE_VELOCITY_THRESHOLD = 1

    override fun onDown(e: MotionEvent): Boolean {
        return true
    }

    // 修改参数为可空类型,添加空值校验
    override fun onFling(e1: MotionEvent?, e2: MotionEvent?, velocityX: Float, velocityY: Float): Boolean {
        var result = false
        try {
            // 先判断e1和e2不为null,避免空指针
            if (e1 == null || e2 == null) {
                return result
            }
            val diffY = e2.y - e1.y
            if (abs(diffY) > MAX_SWIPE_DISTANCE && abs(velocityY) > SWIPE_VELOCITY_THRESHOLD)  {
                if (diffY < 0) {
                    onSwipeBottomToTop()
                }
            }
            result = true
        } catch (exception: Exception) {
            exception.printStackTrace()
        }
        return result
    }
}

同时优化onTouch方法的空安全处理:

@SuppressLint("ClickableViewAccessibility")
override fun onTouch(v: View?, event: MotionEvent): Boolean {
    return gestureDetector?.onTouchEvent(event) ?: false
}

2. 修改SwappingAppsActivity.kt,复用SwipeServices实例并初始化selectedApp

class SwappingAppsActivity : AppCompatActivity(), SwappingAppsAdapter.OnItemClickListener{
    private lateinit var swappingAppsBinding: ActivitySwappingAppsBinding
    private var swappingAppsAdapter: SwappingAppsAdapter? = null
    private lateinit var selectedApp: SwappingItems
    // 保存初始化后的SwipeServices实例
    private lateinit var swipeServices: SwipeServices


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        swappingAppsBinding = ActivitySwappingAppsBinding.inflate(layoutInflater)
        setContentView(swappingAppsBinding.root)

        val swappingAppViewModel = ViewModelProvider(this)[SwappingAppViewModel::class.java]
        swappingAppViewModel.generateSwapItems()

        swappingAppViewModel.swapList.observe(this@SwappingAppsActivity) {
            swappingAppsAdapter = SwappingAppsAdapter(this@SwappingAppsActivity, it)
            swappingAppsBinding.rvSwappingApps.layoutManager = LinearLayoutManager(
                this@SwappingAppsActivity,
                LinearLayoutManager.HORIZONTAL,
                false
            )
            swappingAppsBinding.rvSwappingApps.adapter = swappingAppsAdapter
        }

        val movingElement = swappingAppsBinding.rvSwappingApps
        swipeServices = SwipeServices()
        //调用滑动服务的部分
        swipeServices.initialize(rootLayout = movingElement, layoutToShowHide = movingElement,
            arrayListOf(SwipeServices.SwipeDirection.bottomToTop), 50)
    }

    override fun onItemClick(view: View, swappingItems: SwappingItems) {
        // 先赋值selectedApp,避免未初始化异常
        selectedApp = swappingItems
        if (swappingAppsAdapter?.getItemList()?.indexOf(selectedApp) == 2){
            // 复用已初始化的SwipeServices实例
            swipeServices.cancel()
        }
    }
}

内容的提问来源于stack exchange,提问作者Mohamed Rimshad

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 05:24:09