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

ATAK插件中JoystickView回调为NULL的技术求助

ATAK插件虚拟摇杆回调函数始终为NULL的问题

我正在为ATAK(Android Team Awareness Kit)开发插件,尝试实现虚拟摇杆功能。将参考实现的JoystickView.java移入项目后,摇杆渲染和操作均正常,但回调对象joystickCallback始终为NULL,无法触发onJoystickMoved()方法。

JoystickView与使用它的DropDownReceiver类在同一包下,DropDownReceiver已实现JoystickListener接口,Android Studio显示重写的onJoystickMoved()在JoystickView接口中有两处引用,但运行时joystickCallback始终为空,怀疑是上下文问题但无法定位。

相关代码

DropDownReceiver类

package com.myawesomeatakplugin;

public class DropDownReceiver implements JoystickView.JoystickListener {

    // 其他代码 ...

    @Override
    public void onJoystickMoved(float xPercent, float yPercent, int source)
    {
        Log.e("CLASSTAG", "X percent: " + xPercent + " Y percent: " + yPercent);
    }

    // 其他代码 ...

}

布局文件

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android" android:theme="@style/ATAKTheme">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center_vertical"
        android:orientation="vertical">
        <com.myawesomeatakplugin.JoystickView
            android:id="@+id/joystick"
            android:layout_width="200dp"
            android:layout_height="200dp"
            android:layout_marginTop="100dp"/>
    </LinearLayout>
</layout>

JoystickView类

package com.myawesomeatakplugin;

public class JoystickView extends SurfaceView implements SurfaceHolder.Callback, View.OnTouchListener
{
    private float centerX;
    private float centerY;
    private float baseRadius;
    private float hatRadius;
    private JoystickListener joystickCallback;

    public JoystickView(Context context)
    {
        super(context);
        getHolder().addCallback(this);
        setOnTouchListener(this);
        if(context instanceof JoystickListener){
            joystickCallback = (JoystickListener) context;
        }
    }

    public JoystickView(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        getHolder().addCallback(this);
        setOnTouchListener(this);
        if(context instanceof JoystickListener){
            joystickCallback = (JoystickListener) context;
        }
    }

    public JoystickView(Context context, AttributeSet attrs, int defStyleAttr)
    {
        super(context, attrs, defStyleAttr);
        getHolder().addCallback(this);
        setOnTouchListener(this);
        if(context instanceof JoystickListener){
            joystickCallback = (JoystickListener) context;
        }
    }

    public JoystickView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
    {
        super(context, attrs, defStyleAttr, defStyleRes);
        getHolder().addCallback(this);
        setOnTouchListener(this);
        if(context instanceof JoystickListener){
            joystickCallback = (JoystickListener) context;
        }
    }

    @Override
    public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder)
    {
        setupDimensions();
        drawJoystick(centerX, centerY);
    }

    @Override
    public void surfaceChanged(@NonNull SurfaceHolder surfaceHolder, int i, int i1, int i2)
    {
        // TBD
    }

    @Override
    public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder)
    {
        // TBD
    }

    private void setupDimensions(){
        centerX = (float) getWidth() / 2;
        centerY = (float) getHeight() / 2;
        baseRadius = (float) Math.min(getWidth(), getHeight()) / 3;
        hatRadius = (float) Math.min(getWidth(), getHeight()) / 5;
    }

    private void drawJoystick(float newX, float newY){
        if(getHolder().getSurface().isValid()){
            Canvas myCanvas = this.getHolder().lockCanvas();
            Paint colors = new Paint();

            myCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);

            colors.setARGB(255, 50, 50, 50);
            myCanvas.drawCircle(centerX, centerY, baseRadius, colors);

            colors.setARGB(255, 0, 0, 255);
            myCanvas.drawCircle(newX, newY, hatRadius, colors);

            getHolder().unlockCanvasAndPost(myCanvas);
        }
    }

    @Override
    public boolean onTouch(View view, MotionEvent motionEvent)
    {
        if(view.equals(this)){
            if(motionEvent.getAction() != MotionEvent.ACTION_UP){
                float displacement = (float) Math.sqrt(Math.pow(motionEvent.getX() - centerX, 2) + Math.pow(motionEvent.getY() - centerY, 2));
                if(displacement < baseRadius){
                    drawJoystick(motionEvent.getX(), motionEvent.getY());
                    // FOR SOME REASON, joystickCallback IS NULL HERE?!?!?
                    if(joystickCallback != null) joystickCallback.onJoystickMoved((motionEvent.getX() - centerX)/baseRadius, (motionEvent.getY() - centerY)/baseRadius, getId());
                } else {
                    float ratio = baseRadius / displacement;
                    float constrainedX = centerX + (motionEvent.getX() - centerX) * ratio;
                    float constrainedY = centerY + (motionEvent.getY() - centerY) * ratio;
                    drawJoystick(constrainedX, constrainedY);
                    // FOR SOME REASON, joystickCallback IS NULL HERE?!?!?
                    if(joystickCallback != null) joystickCallback.onJoystickMoved((constrainedX-centerX)/baseRadius, (constrainedY-centerY)/baseRadius, getId());
                }
            } else {
                drawJoystick(centerX, centerY);
            }
        }
        return true;
    }

    public interface JoystickListener
    {
        void onJoystickMoved(float xPercent, float yPercent, int source);
    }
}

问题原因

当前JoystickView的构造函数中,是通过判断传入的Context是否实现了JoystickListener来初始化回调对象,但DropDownReceiver并不是Context对象(它是ATAK插件的下拉接收器,属于BroadcastReceiver子类),而布局加载时传入的Context是Activity或Application,这些类并没有实现JoystickListener接口,因此joystickCallback始终为null。

解决方案

方案1:给JoystickView添加回调设置方法(推荐)

修改JoystickView,新增一个公开方法用于手动设置回调,摆脱对Context的依赖:

public class JoystickView extends SurfaceView implements SurfaceHolder.Callback, View.OnTouchListener {
    // ... 原有代码 ...

    // 添加回调设置方法
    public void setJoystickListener(JoystickListener listener) {
        this.joystickCallback = listener;
    }

    // ... 原有代码 ...
}

在DropDownReceiver中加载布局后,获取JoystickView实例并手动设置回调:

// 假设你通过LayoutInflater加载布局
View layoutView = inflater.inflate(R.layout.your_joystick_layout, null);
JoystickView joystick = layoutView.findViewById(R.id.joystick);
// DropDownReceiver已实现JoystickListener,直接传入this
joystick.setJoystickListener(this);

方案2:调整布局加载的Context(不推荐,耦合性高)

如果坚持使用Context强转的方式,需要让加载布局的Context是实现了JoystickListener的类。但DropDownReceiver本身不是Context,因此需要将回调逻辑迁移到自定义Activity/Fragment中,并让该类实现JoystickListener,再用这个类的Context加载布局。不过这种方式不符合ATAK插件的架构设计,不推荐使用。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 01:59:52