Android 自定义视图onTouch在其他视图中断开onTouch(如果使用XML布局添加)

Android 自定义视图onTouch在其他视图中断开onTouch(如果使用XML布局添加),android,android-view,touch-event,ontouch,xml-layout,Android,Android View,Touch Event,Ontouch,Xml Layout,我正在开发一个游戏,我有一个自定义视图操纵杆,其中有一个onTouch事件。我也有一个图像视图,它也有一个用于拍摄的接口 如果我使用java代码以编程方式将视图添加到主相对布局中,那么两个视图的onTouch可以完美地同时工作 问题是,如果我使用XML布局文件添加视图,那么,如果您触摸自定义视图操纵杆,则另一个视图的onTouch不起作用。只有使用XML布局文件创建布局时才会发生这种情况 我怎样才能解决这个问题 布局: <?xml version="1.0" encoding="utf-8

我正在开发一个游戏,我有一个自定义视图
操纵杆
,其中有一个onTouch事件。我也有一个图像视图,它也有一个用于拍摄的接口

如果我使用java代码以编程方式将视图添加到主相对布局中,那么两个视图的onTouch可以完美地同时工作

问题是,如果我使用XML布局文件添加视图,那么,如果您触摸自定义视图
操纵杆
,则另一个视图的onTouch不起作用。只有使用XML布局文件创建布局时才会发生这种情况

我怎样才能解决这个问题

布局:

<?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>
使用该代码不起作用,但如果我使用下一个代码,它将非常有效。。。为什么?

    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);
这是操纵杆类:

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();
    }
}
公共类操纵杆扩展视图{
公共静态final int无效_指针=-1;
私用JoystickMovedListener-moveListener;
//#在向侦听器报告之间需要加速的像素数
私人决议;
//用户坐标系中加速度移动的最大范围
私人浮动范围;
//视图坐标中的最后一个接触点
private int pointerId=无效的\u指针;
私人浮动触摸屏;
私人浮动敏感;
私人浮动触摸延时移动;
私人浮动触摸屏移动;
//控制柄在视图坐标中居中
私人浮动手柄;
私人浮球;
//视图坐标中上次报告的位置(允许不同的报告灵敏度)
私人浮动报告x;
私人流动报道;
//视图坐标中的视图中心
私人int cX;
私密性;
//视图坐标中视图的大小
私有int dimX;
私密性模糊;
私有内部填充;
私用内特半径;
私人住宅;
私有int-movementRadius;
私人int Handleinerboundary;
//最后一个接触点的笛卡尔坐标-操纵杆中心为(0,0)
私人int cartX;
私家车;
//最后一个接触点的用户坐标
私有int-userX;
私有用户;
//偏移坐标(从父坐标原点接收触摸事件时使用)
私人国际贸易抵销;
私人内部网外;
私人涂料;
私人油漆手绘;
布尔禁用;
处理者;
处理手延迟移动;
公共操纵杆(上下文、属性集属性){
超级(上下文,attrs);
initJoystickView();
}
公共操纵杆(上下文){
超级(上下文);
initJoystickView();
}
私有void initJoystickView(){
设置聚焦(真);
handlePaint=新油漆(油漆.防油漆别名标志);
手绘。设置颜色(颜色。红色);
手刷设置行程宽度(1);
手绘。设置样式(绘制。样式。填充和笔划);
bgPaint=新油漆(油漆.防油漆别名标志);
bgPaint.setColor(Color.DKGRAY);
bgPaint.设置行程宽度(1);
bgPaint.setStyle(Paint.Style.FILL_和_笔划);
此分辨率为1.0f;
handler=新的handler();
handlerDelayedMovement=新处理程序();
}
公共void设置移动范围(浮动移动范围){
this.movementRange=movementRange;
}
公共无效设置JoystickMovedListener(JoystickMovedListener侦听器){
this.moveListener=listener;
}
@凌驾
仅限受保护的空心布局(布尔值已更改、整数左侧、整数顶部、整数右侧、整数底部){
超级。仅限布局(已更改、左、上、右、下);
int d=Math.min(getMeasuredWidth(),getMeasuredHeight());
dimX=d;
dimY=d;
cX=d/2;
cY=d/2;
bgRadius=dimX/2-内部填充;
handleRadius=(int)(d*0.2);
handleInnerBoundaries=handleRadius;
movementRadius=数学最小值(cX,cY)-手动基准;
}
@凌驾
测量时的保护空隙(内部宽度测量等级、内部高度测量等级){
//在这里,我们确保我们有一个完美的圆
int MEASUREDWITH=测量值(widthMeasureSpec);
int measuredHeight=测量值(heightMeasureSpec);
设置测量尺寸(测量宽度、测量高度);
}
私有整数度量(整数度量){
int结果=0;
//解码测量规格。
int specMode=MeasureSpec.getMode(MeasureSpec);
int specSize=MeasureSpec.getSize(MeasureSpec);
if(specMode==MeasureSpec.UNSPECIFIED){
result=200;//如果未指定边界,则返回默认大小200。
}否则{
result=specSize;//如果要填充可用空间,请始终返回完整的可用边界。
}
返回结果;
}
@凌驾
受保护的void onDraw(画布){
canvas.save();
//画背景
画布.画圈(cX、cY、bgRadius、bgPaint);
//拉手
handleX=touchX+cX;
汉德利=敏感+cY;
帆布拉丝圈(handleX、handleY、handleRadius、handlePaint);
canvas.restore();
}
public void setPointerId(int-id){
this.pointerId=id;
}
public int getPointerId(){
返回指针ID;
}
@凌驾
公共事件(MotionEvent ev){
final int action=ev.getAction();
开关(动作和动作事件.动作屏蔽){
case MotionEvent.ACTION\u移动:{
if(disabled==true)
打破
返回事件(ev);
}
case MotionEvent.ACTION\u取消:
case MotionEvent.ACTION\u UP:{
if(pointerId!=无效的\u指针){
returnHandletoCenter();
ReturnHandleToCenter加速移动();
setPointerId(无效的_指针);
}
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();
    }
}