0

I'm developing a game, and I have a custom view Joystick which haves a onTouch event. I also have a ImageView which also haves onTouch for shooting.

If I add the views programatically using java code to a main relative layout, the onTouch of the two views works perfectly and at the same time.

The problem is that if I add the views using a XML layout file, then, if you touch the custom view Joystick, the onTouch of the other view doesn't works. It only happens if I use XML layout file for creating the layout.

How can I solve the problem?

The layout:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/mainLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ImageView
        android:id="@+id/shootImageView"
        android:layout_width="100dp"
        android:layout_height="wrap_content"
        android:src="@drawable/shoot"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true" />
    <com.myapp.uiviews.Joystick
        android:id="@+id/joystick"
        android:layout_width="wrap_content"
        android:layout_height="150dp"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true" />
</RelativeLayout>

The class:

    mainLayout = (RelativeLayout) findViewById(R.id.mainLayout);

    shootImageView= (ImageView) findViewById(R.id.shootImageView);
    shootImageView.setOnTouchListener(new OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            touchX=sw/2;
            touchY=sh/2;
            switch (event.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN:
                LaserManager.getInstance().startLaserThread();
                break;
            case MotionEvent.ACTION_UP:
                LaserManager.getInstance().stopLaserThread();
                break;
            }
            return true;
        }
    });
    shootImageView.invalidate();
    shootImageView.refreshDrawableState();
    shootImageView.forceLayout();

    joystickOnScreen = (Joystick) findViewById(R.id.joystick);
    int factor = GameState.getInstance().getSpaceship().getSpeedFactorCorrespondence();
    joystickOnScreen.setMovementRange(sh/factor);
    joystickOnScreen.setInnerPadding(sh/30);
    joystickOnScreen.setOnJostickMovedListener(joystickListener);
    joystickOnScreen.invalidate();
    joystickOnScreen.refreshDrawableState();
    joystickOnScreen.forceLayout();

Using that code doesn't works, but if I use this next code it works perfectly... why?

    shootImageView = new ImageView(this);
    shootImageView.setId(102);
    RelativeLayout.LayoutParams shootImageViewParams = new RelativeLayout.LayoutParams(sw/8, sw/8);
    shootImageViewParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
    shootImageViewParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
    shootImageViewParams.setMargins(0, 0, sh/15, sh/15);
    shootImageView.setLayoutParams(shootImageViewParams);
    shootImageView.setImageDrawable(getResources().getDrawable(R.drawable.shoot));
    shootImageView.setAlpha(0.4f);  

    shootImageView.setOnTouchListener(new OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            touchX=sw/2;
            touchY=sh/2;
            switch (event.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN:
                LaserManager.getInstance().startLaserThread();
                break;
            case MotionEvent.ACTION_UP:
                LaserManager.getInstance().stopLaserThread();
                break;
            }
            return true;
        }
    });

    main.addView(shootImageView);

    joystickOnScreen = new Joystick(this);
    int factor = GameState.getInstance().getSpaceship().getSpeedFactorCorrespondence();
    joystickOnScreen.setMovementRange(sh/factor);
    joystickOnScreen.setInnerPadding(sh/30);
    joystickOnScreen.setOnJostickMovedListener(joystickListener);
    RelativeLayout.LayoutParams joystickParams = new RelativeLayout.LayoutParams(sh/3, sh/3);
    joystickParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
    joystickParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
    joystickParams.setMargins(sh/100, 0, 0, sh/100);
    joystickOnScreen.setLayoutParams(joystickParams);
    joystickOnScreen.setAlpha(0.3f);

    main.addView(joystickOnScreen);

This is the Joystick class:

public class Joystick extends View {
    public static final int INVALID_POINTER = -1;

    private JoystickMovedListener moveListener;

    //# of pixels moveWithAcceleration required between reporting to the listener
    private float moveResolution;

    //Max range of moveWithAcceleration in user coordinate system
    private float movementRange;

    //Last touch point in view coordinates
    private int pointerId = INVALID_POINTER;
    private float touchX;
    private float touchY;
    private float touchXDelayedMovement;
    private float touchYDelayedMovement;

    //Handle center in view coordinates
    private float handleX;
    private float handleY;

    //Last reported position in view coordinates (allows different reporting sensitivities)
    private float reportX;
    private float reportY;

    //Center of the view in view coordinates
    private int cX;
    private int cY;

    //Size of the view in view coordinates
    private int dimX;
    private int dimY;

    private int innerPadding;
    private int bgRadius;
    private int handleRadius;
    private int movementRadius;
    private int handleInnerBoundaries;

    //Cartesian coordinates of last touch point - joystick center is (0,0)
    private int cartX;
    private int cartY;

    //User coordinates of last touch point
    private int userX;
    private int userY;

    //Offset co-ordinates (used when touch events are received from parent's coordinate origin)
    private int offsetX;
    private int offsetY;

    private Paint bgPaint;
    private Paint handlePaint;

    boolean disabled;

    Handler handler;
    Handler handlerDelayedMovement;

    public Joystick(Context context, AttributeSet attrs) {
        super(context, attrs);
        initJoystickView();
    }

    public Joystick(Context context) {
        super(context);
        initJoystickView();
    }

    private void initJoystickView() {
        setFocusable(true);

        handlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        handlePaint.setColor(Color.RED);
        handlePaint.setStrokeWidth(1);
        handlePaint.setStyle(Paint.Style.FILL_AND_STROKE);

        bgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        bgPaint.setColor(Color.DKGRAY);
        bgPaint.setStrokeWidth(1);
        bgPaint.setStyle(Paint.Style.FILL_AND_STROKE);

        this.moveResolution = 1.0f;

        handler = new Handler();
        handlerDelayedMovement = new Handler();
    }

    public void setMovementRange(float movementRange) {
        this.movementRange = movementRange;
    }

    public void setOnJostickMovedListener(JoystickMovedListener listener) {
        this.moveListener = listener;
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        int d = Math.min(getMeasuredWidth(), getMeasuredHeight());

        dimX = d;
        dimY = d;

        cX = d / 2;
        cY = d / 2;

        bgRadius = dimX/2 - innerPadding;
        handleRadius = (int)(d * 0.2);
        handleInnerBoundaries = handleRadius;
        movementRadius = Math.min(cX, cY) - handleInnerBoundaries;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // Here we make sure that we have a perfect circle
        int measuredWidth = measure(widthMeasureSpec);
        int measuredHeight = measure(heightMeasureSpec);
        setMeasuredDimension(measuredWidth, measuredHeight);
    }

    private int measure(int measureSpec) {
        int result = 0;
        // Decode the measurement specifications.
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
        if (specMode == MeasureSpec.UNSPECIFIED) {
            result = 200; // Return a default size of 200 if no bounds are specified.
        } else {
            result = specSize; // As you want to fill the available space always return the full available bounds.
        }
        return result;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.save();
        // Draw the background
        canvas.drawCircle(cX, cY, bgRadius, bgPaint);

        // Draw the handle
        handleX = touchX + cX;
        handleY = touchY + cY;
        canvas.drawCircle(handleX, handleY, handleRadius, handlePaint);

        canvas.restore();
    }

    public void setPointerId(int id) {
        this.pointerId = id;
    }

    public int getPointerId() {
        return pointerId;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        final int action = ev.getAction();
        switch (action & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_MOVE: {
                if (disabled==true)
                    break;
                return processMoveEvent(ev);
            }
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP: {
                if ( pointerId != INVALID_POINTER ) {
                    returnHandleToCenterVisually();
                    returnHandleToCenterAcceleratedMovement();
                    setPointerId(INVALID_POINTER);
                }
                break;
            }
            case MotionEvent.ACTION_POINTER_UP: {
                if ( pointerId != INVALID_POINTER ) {
                    final int pointerIndex = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
                    final int pointerId = ev.getPointerId(pointerIndex);
                    if ( pointerId == this.pointerId ) {
                        returnHandleToCenterVisually();
                        returnHandleToCenterAcceleratedMovement();
                        setPointerId(INVALID_POINTER);
                        return true;
                    }
                }
                break;
            }
            case MotionEvent.ACTION_DOWN: {
                handlerDelayedMovement.removeCallbacksAndMessages(null);
                if ( pointerId == INVALID_POINTER ) {
                    int x = (int) ev.getX();
                    if ( x >= offsetX && x < offsetX + dimX ) {
                        setPointerId(ev.getPointerId(0));
                        if (disabled==true){
                            return true;
                        }
                        return processMoveEvent(ev);
                    }
                }
                break;
            }
            case MotionEvent.ACTION_POINTER_DOWN: {
                if ( pointerId == INVALID_POINTER ) {
                    final int pointerIndex = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
                    final int pointerId = ev.getPointerId(pointerIndex);
                    int x = (int) ev.getX(pointerId);
                    if ( x >= offsetX && x < offsetX + dimX ) {
                        setPointerId(pointerId);
                        return true;
                    }
                }
                break;
            }
        }
        return false;
    }

    private boolean processMoveEvent(MotionEvent ev) {
        if ( pointerId != INVALID_POINTER ) {
            final int pointerIndex = ev.findPointerIndex(pointerId);

            // Translate touch position to center of view
            float x = ev.getX(pointerIndex);
            touchX = x - cX - offsetX;
            float y = ev.getY(pointerIndex);
            touchY = y - cY - offsetY;

            moveWithAcceleration();
            invalidate();

            return true;
        }
        return false;
    }

    private void moveWithAcceleration() {
        float diffX = touchX;
        float diffY = touchY;
        double radial = Math.sqrt((diffX*diffX) + (diffY*diffY));
        if ( radial > movementRadius ) {
            touchX = (int)((diffX / radial) * movementRadius);
            touchY = (int)((diffY / radial) * movementRadius);
        }

        final int numberOfFrames = 5;

        final double intervalsX = (touchX - touchXDelayedMovement) / numberOfFrames;
        final double intervalsY = (touchY - touchYDelayedMovement) / numberOfFrames;

        handlerDelayedMovement.removeCallbacksAndMessages(null);

        for (int i = 0; i <= numberOfFrames; i++) {
            handlerDelayedMovement.postDelayed(new Runnable() {
                @Override
                public void run() {
                    touchXDelayedMovement += intervalsX;
                    touchYDelayedMovement += intervalsY;

                    reportOnMoved();
                }
            }, i * 50);
        }
    }

    private void reportOnMoved() {
        //We calc user coordinates
        //First convert to cartesian coordinates
        cartX = (int)(touchXDelayedMovement / movementRadius * movementRange);
        cartY = (int)(touchYDelayedMovement / movementRadius * movementRange);

        //Cartesian Coordinates
        userX = cartX;
        userY = cartY;

        if (moveListener != null) {
            boolean rx = Math.abs(touchXDelayedMovement - reportX) >= moveResolution;
            boolean ry = Math.abs(touchYDelayedMovement - reportY) >= moveResolution;
            if (rx || ry) {
                this.reportX = touchXDelayedMovement;
                this.reportY = touchYDelayedMovement;

                moveListener.OnMoved(userX, userY);
            }
        }
    }

    private void returnHandleToCenterVisually() {
        final int numberOfFrames = 2;
        final double intervalsX = (0 - touchX) / numberOfFrames;
        final double intervalsY = (0 - touchY) / numberOfFrames;

        handler.removeCallbacksAndMessages(null);
        for (int i = 0; i < numberOfFrames; i++) {
            final int j = i;
            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    touchX += intervalsX;
                    touchY += intervalsY;

                    invalidate();

                    if (moveListener != null && j == numberOfFrames - 1) {
                        moveListener.OnReturnedToCenter();
                    }
                }
            }, i * 15);
        }

        if (moveListener != null) {
            moveListener.OnReleased();
        }
    }

    private void returnHandleToCenterAcceleratedMovement() {
        final int numberOfFrames = 10;
        final double intervalsX = (0 - touchXDelayedMovement) / numberOfFrames;
        final double intervalsY = (0 - touchYDelayedMovement) / numberOfFrames;

        handlerDelayedMovement.removeCallbacksAndMessages(null);
        for (int i = 0; i < numberOfFrames; i++) {
            handlerDelayedMovement.postDelayed(new Runnable() {
                @Override
                public void run() {
                    touchXDelayedMovement += intervalsX;
                    touchYDelayedMovement += intervalsY;

                    reportOnMoved();
                }
            }, i * 50);
        }
    }

    public void setInnerPadding(int innerPadding){
        this.innerPadding=innerPadding;
    }

    public void disable(){
        disabled=true;
    }

    public void enable(){
        disabled=false;
    }

    public interface JoystickMovedListener {
        public void OnMoved(int pan, int tilt);
        public void OnReleased();
        public void OnReturnedToCenter();
    }
}
NullPointerException
  • 36,107
  • 79
  • 222
  • 382

1 Answers1

0

Incredible but true, the problem was with

android:layout_height="wrap_content" in shoot button and android:layout_width="wrap_content" in joystick, after setting a fixed size the problem dissapeared. Didn't understand why..

NullPointerException
  • 36,107
  • 79
  • 222
  • 382