在片段上实现带有回调的接口

implementing an interface with callback on a fragment

我正在学习 android 编程和练习我正在尝试为一些直流电机做一个控制器,然后我做了一个自定义视图来制作一个虚拟游戏手柄,它使用一个接口和一个回调ontouch 侦听器。

问题是,我正在开发我的应用程序,使用单个 MainActivity 作为导航主机,然后我在不同的片段中导航,当我重写我的接口方法时,我的自定义视图才起作用MainActivity 但我无法让它在我的片段上运行,我想在其中处理游戏手柄的所有逻辑。

我已经研究了几天,但我发现的大多数 post 都是写在 Java 上的,我无法让它在 Kotlin 上运行。

我的自定义视图class


class KanJoypadView: SurfaceView, SurfaceHolder.Callback, View.OnTouchListener{  

    ...kotlin
    var joypadCallback: JoypadListener? = null

    //the main constructor for the class
    constructor(context: Context): super(context){
        ...

        getHolder().addCallback(this)
        setOnTouchListener(this)

        ...
}

        //the interface for the main functionally of the view
        interface JoypadListener {
            fun onJoypadMove(x: Float, y: Float, src: Int){}
        }

        ...

}

我的主要Activity


class NavActivity : AppCompatActivity(), KanJoypadView.joypadListener {
    ...
    
    //Overriding the Function from the interface, 
    //I just did this for debguging, but I dont want this override here
    override fun onJoypadMove(x: Float, y: Float, src: Int) {
        Log.d(src.toString(), y.toString()) //** I wanna do this in my Fragment, not in my activity **
    }

}

我的片段


class JoystickFragment : Fragment(), KanJoypadView.joypadListener {

    ...

    var enginesArray = arrayOf(0.toFloat(), 0.toFloat(), 0.toFloat(), 0.toFloat())

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
    
    ...
    
        val binding = DataBindingUtil.inflate(
                inflater, R.layout.fragment_joystick, container, false
        )
        binding.leftJoypad.joypadCallback = (container?.context as KanJoypadView.JoypadListener?)
        lJoypad = binding.leftJoypad.id
    }

    /*what I really want to do, but it is not happening as it is just happenning the
 override from the NavActivity, which I dont need, and not from here which I need*/
    override fun onJoypadMove(x: Float, y: Float, src: Int) {
        if (src == lJoypad) {
            if (y >= 0) {
                enginesArray[0] = 1.toFloat()
                enginesArray[1] = y
            } else if (y < 0) {
                enginesArray[0] = 0.toFloat()
                enginesArray[1] = y
            }
            if (src == rJoypad) {
                if (y >= 0) {
                    enginesArray[0] = 1.toFloat()
                    enginesArray[1] = y
                } else if (yAxis < 0) {
                    enginesArray[0] = 0.toFloat()
                    enginesArray[1] = y
                }
                Log.d("Engines array", enginesArray.toString())
            }
        }
    }

}

我也尝试在片段中创建一个函数,然后从 Activity 的 onMoveJoypad 方法调用该函数,但我也无法使其工作。对于如何实施此功能的任何帮助或建议,我将不胜感激,在此先感谢!

这个:

if (context is joypadListener){

是获取听众参考的一种非常笨拙且容易出错的方法。它也非常有限,因为它使侦听器不可能成为创建视图的 Activity 之外的任何东西。不要这样做!

您已经有一个手柄监听器 属性。只需删除 private 关键字,这样任何 class 都可以从外部设置它想要的任何侦听器。删除整个 try/catch 块。当需要调用侦听器的函数时,使用 null-safe ?. 调用来执行此操作,以便在尚未设置回调时优雅地不执行任何操作。

旁注:所有 class 和接口名称都应按照惯例以大写字母开头。如果您不遵循此约定,您的代码将变得很难阅读和解释。

此外,我建议您避免使用使 class 实现接口的模式,以用作它正在操作的对象之一或其自身内部函数的回调。您在上面的代码中执行了两次。您的自定义视图为自己的触摸侦听器执行此操作,而您的 Activity 将其用作游戏手柄侦听器。

原因是它公开暴露了class的内部功能并减少了模块化。它会使单元测试更加困难。它不必要地暴露了滥用您设计的 class 的方法。对于 Activity 来说,这样做并不是什么大不了的事情,因为无论如何你都很少从外部使用 Activity 个实例。但是视图 class 这样做很难看。

另一种方法是将接口实现为匿名对象或 lambda,这样回调的功能对外部 classes 是隐藏的。


编辑:如何在您的片段中执行此操作

如果您想听从我上面的建议,请不要在 classes 中实现回调接口。请改用 lambda 或匿名 classes。

class JoystickFragment : Fragment() {

    //...

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        //...

        adapter.joypadCallback = joypadListener

        //...
    }

    private val joypadListener = KanJoypadView.JoypadListener { x, y, src ->
        if (src == lJoypad) {
            if (y >= 0) {
                enginesArray[0] = 1.toFloat()
                enginesArray[1] = y
            } else if (y < 0) {
                enginesArray[0] = 0.toFloat()
                enginesArray[1] = y
            }
            if (src == rJoypad) {
                if (y >= 0) {
                    enginesArray[0] = 1.toFloat()
                    enginesArray[1] = y
                } else if (yAxis < 0) {
                    enginesArray[0] = 0.toFloat()
                    enginesArray[1] = y
                }
                Log.d("Engines array", enginesArray.toString())
            }
        }
    }

}