NASAWorldWind / WorldWindAndroid

The NASA WorldWind Java SDK for Android (WWA) includes the library, examples and tutorials for building 3D virtual globe applications for phones and tablets.
Other
267 stars 125 forks source link

Tilt gesture improvement #76

Open ComBatVision opened 7 years ago

ComBatVision commented 7 years ago

Hello!

Some phones does not support detection of movement for more than 2 fingers, plus 3 finger gesture is not obvious.

I propose to modify BasicWorldWindController to set 2 fingers for tiltRecognizer.

    public BasicWorldWindowController() {
...
        ((PanRecognizer)this.tiltRecognizer).setMinNumberOfPointers(2);
    }

This gives much better user experience. User can tilt, pan and zoom together with one gesture. Please try to test it. You will like this behavior.

UPD!!! This modification broke the rotation gesture, but it will be good to find solution to make them work like in GoogleMaps. If rotation was first, than tilt should be disabled during this gesture.

ComBatVision commented 7 years ago

Here is my fully functional prototype of changes to make rotation and tilt work both with 2 fingers. This behavior of gestures is more user-friendly and support more devices.

public class CustomWorldWindowController extends BasicWorldWindowController {

    public CustomWorldWindowController() {
        // Recognize tilt with 2 fingers instead of 3
        ((PanRecognizer)tiltRecognizer).setMinNumberOfPointers(2);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean handled = false;
        int action = event.getActionMasked();
        for(GestureRecognizer recognizer : allRecognizers) {
            if(!isSkipped(recognizer)) {
                handled |= recognizer.onTouchEvent(event);
                // Skip dependent gestures
                if (recognizer.getState() == WorldWind.CHANGED) {
                    if (recognizer == tiltRecognizer) {
                        skipRotation();
                    } else if (recognizer == rotationRecognizer) {
                        skipTilt();
                    }
                }
            } else if(action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
                // Reset skip flag to enable gesture processing next time
                if (recognizer == rotationRecognizer) {
                    rotationSkipped = false;
                } else if (recognizer == tiltRecognizer) {
                    tiltSkipped = false;
                }
            }
        }
        return handled;
    }

    private boolean isSkipped(GestureRecognizer recognizer) {
        if(recognizer == rotationRecognizer) {
            return rotationSkipped;
        } else if(recognizer == tiltRecognizer) {
            return tiltSkipped;
        } else {
            return false;
        }
    }

    private void skipRotation() {
        if(!rotationSkipped) {
            cancel(rotationRecognizer);
            rotationSkipped = true;
        }
    }

    private void skipTilt() {
        if(!tiltSkipped) {
            cancel(tiltRecognizer);
            tiltSkipped = true;
        }
    }

    private void cancel(GestureRecognizer recognizer) {
        // It is not possible to call handleActionCancel from here, so emulate Cancel event
        recognizer.onTouchEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0, 0, 0));
    }

    // We need external skip state flags, because GestureRecogniser reset state to POSSIBLE directly after setting CANCEL or FAILED
    // If gestures will be able to remember last state until next ACTION_DOWN event,
    // than we may use state analysis instead of external flags
    private boolean rotationSkipped;
    private boolean tiltSkipped;

}
jgiovino commented 7 years ago

@Sufaev this is great, I would suggest if extending the basic controller to have default controller for a “one finger controller” (capacitive touch), your “two finger controller”, and a three finger controller. There will be devices that only have resistive touch and no keyboard/mouse. For the one finger case without keyboard/mouse we could long press right/left to zoom, long press up/down to tilt. Maybe even long press at the top quarter of the screen move left right to rotate. Thoughts?

From: Sufaev [mailto:notifications@github.com] Sent: Wednesday, January 25, 2017 3:31 AM To: NASAWorldWind/WorldWindAndroid WorldWindAndroid@noreply.github.com Cc: Subscribed subscribed@noreply.github.com Subject: Re: [NASAWorldWind/WorldWindAndroid] Tilt gesture improvement (#76)

Here is my fully functional prototype of changes to make rotation and tilt work both with 2 fingers, plus I have added disabling of gestures if required and tilt with right mouse click.

This behavior of gestures is more user-friendly and support more devices.

public class CustomWorldWindowController extends BasicWorldWindowController {

public CustomWorldWindowController() {

    // Recognize tilt with 2 fingers instead of 3

    ((PanRecognizer)tiltRecognizer).setMinNumberOfPointers(2);

    // Add tilt recognition with mouse move when right button is clicked

    allRecognizers = new ArrayList<>(allRecognizers);

    allRecognizers.add(mouseTiltRecognizer);

    mouseTiltRecognizer.addListener(this);

}

@Override

public boolean onTouchEvent(MotionEvent event) {

    boolean handled = false;

    int action = event.getActionMasked();

    for(GestureRecognizer recognizer : allRecognizers) {

        if(!isSkipped(recognizer)) {

            handled |= recognizer.onTouchEvent(event);

            // Skip dependent gestures

            if (recognizer.getState() == WorldWind.CHANGED) {

                if (recognizer == tiltRecognizer) {

                    skipRotation();

                } else if (recognizer == rotationRecognizer) {

                    skipTilt();

                }

            }

        } else if(action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {

            // Reset skip flag to enable gesture processing next time

            if (recognizer == rotationRecognizer) {

                rotationSkipped = false;

            } else if (recognizer == tiltRecognizer) {

                tiltSkipped = false;

            }

        }

    }

    return handled;

}

@Override

public void gestureStateChanged(MotionEvent event, GestureRecognizer recognizer) {

    if(recognizer == mouseTiltRecognizer) {

        handleTilt(recognizer);

    } else {

        super.gestureStateChanged(event, recognizer);

    }

}

public void setAllGesturesEnabled(boolean enabled) {

    for(GestureRecognizer recognizer : allRecognizers) {

        recognizer.setEnabled(enabled);

    }

}

public boolean isGestureInProcess() {

    for(GestureRecognizer recognizer : allRecognizers) {

        if(isInProcess(recognizer)) {

            return true;

        }

    }

    return false;

}

private boolean isInProcess(GestureRecognizer recognizer) {

    return recognizer.getState() == WorldWind.BEGAN || recognizer.getState() == WorldWind.CHANGED;

}

private boolean isSkipped(GestureRecognizer recognizer) {

    if(recognizer == rotationRecognizer) {

        return rotationSkipped;

    } else if(recognizer == tiltRecognizer) {

        return tiltSkipped;

    } else {

        return false;

    }

}

private void skipRotation() {

    if(!rotationSkipped) {

        cancel(rotationRecognizer);

        rotationSkipped = true;

    }

}

private void skipTilt() {

    if(!tiltSkipped) {

        cancel(tiltRecognizer);

        tiltSkipped = true;

    }

}

private void cancel(GestureRecognizer recognizer) {

    // It is not possible to call handleActionCancel from here, so emulate Cancel event

    recognizer.onTouchEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0, 0, 0));

}

private GestureRecognizer mouseTiltRecognizer = new MousePanRecognizer();

// We need external skip state flags, because GestureRecogniser reset state to POSSIBLE directly after setting CANCEL or FAILED

// If gestures will be able to remember last state until next ACTION_DOWN event,

// than we may use state analysis instead of external flags

private boolean rotationSkipped;

private boolean tiltSkipped;

}

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHubhttps://github.com/NASAWorldWind/WorldWindAndroid/issues/76#issuecomment-275048038, or mute the threadhttps://github.com/notifications/unsubscribe-auth/AQqdwXD8a8YNBDG_DZb7yIoJ1KC95srDks5rVwhagaJpZM4LpGgr.

ComBatVision commented 7 years ago

I have investigated more and found that gesture works after some movement limit in Pixels, but in high density screens constants in pixels are too small and my improvement usually start tilt instaed of rotation.

Irrespecting will you apply my proposition about 2 fingers mode or not I think it will be good to have restriction based on DP or even based on android internal touch dimension constant.

ComBatVision commented 7 years ago

I have simplified solution a lot:

public class CustomWorldWindowController extends BasicWorldWindowController {

    public CustomWorldWindowController() {
        // Recognize tilt with 2 fingers instead of 3
        ((PanRecognizer)tiltRecognizer).setMinNumberOfPointers(2);
        // TODO Set interpret distance for all gestures based on screen DPI
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean handled = super.onTouchEvent(event);
        if(handled) {
            tiltRecognizer.setEnabled(!isInProcess(rotationRecognizer) || !rotationRecognizer.isEnabled());
            rotationRecognizer.setEnabled(!isInProcess(tiltRecognizer) || !tiltRecognizer.isEnabled());
        }
        return handled;
    }

    private boolean isInProcess(GestureRecognizer recognizer) {
        return recognizer.getState() == WorldWind.BEGAN || recognizer.getState() == WorldWind.CHANGED;
    }

}