I'm learning android programming and for practicing I'm trying to do a controller for some dc motors, then I did a customview for making a virtual joypad, which uses an interface and a callback for the ontouch listener.
The problem is, I'm working on my app using a single MainActivity as a navhost and then I'm navigating through different fragments, My customview just works when I override the interface method on my MainActivity but I can't make it works on my fragment, where I want to handle all the logic of the joypad.
I've a couple of days researching but most of the post that I've found are written on Java and I just can't make it work on Kotlin.
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){}
}
...
}
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())
}
}
}
}
Also I've tried to make a function in the fragment, and then call that function from the onMoveJoypad method from the Activity, but also I couldn't made it work. I'll appreciate any help or advice on how to implement this, thanks in advance !
This:
if (context is joypadListener){
is a very hacky and error prone way to get a listener reference. It’s also very limiting because it makes it impossible for the listener to be anything but the Activity that created the view. Don’t do this!
You already have a joypad listener property. Just remove the private
keyword so any class can set any listener it wants from the outside. Remove that whole try/catch block. When it’s time to call the listener’s function, use a null-safe ?.
call to do it so it gracefully does nothing if a callback hasn’t been set yet.
Side note: all class and interface names should start with a capital letter by convention. Your code becomes very hard to read and interpret if you fail to follow this convention.
Also, I advise you to avoid using the pattern of making a class implement an interface to serve as a callback for one of the objects it is manipulating or for its own internal functions. You’re doing this twice in your code above. Your custom view does it for its own touch listener, and your Activity does it to serve as the joypad listener.
The reason is that it publicly exposes the class’s inner functionality and reduces modularity. It can make unit testing more difficult. It needlessly exposes ways to misuse the class you’ve designed. It’s not as big deal for Activities to do this because you rarely if ever work with Activity instances from the outside anyway. But it’s ugly for a view class to do it.
The alternative is to implement the interface as an anonymous object or lambda so the functionality of the callback is hidden from outside classes.
Edit: How to do this in your fragment
If you want to follow my advice above, don't implement callback interfaces in your classes. Use lambdas or anonymous classes instead.
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())
}
}
}
}