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();
}
}