cpp-130: Make drag buttons more usable

The maximum distance of detecting dragging was removed.
The minimum distance now uses DPI value.

Fixes #130
This commit is contained in:
serso 2015-02-16 10:15:37 +01:00
parent 123c745fa5
commit b2eed7a0c3
10 changed files with 104 additions and 303 deletions

View File

@ -41,8 +41,6 @@ import org.solovyev.android.calculator.view.AngleUnitsButton;
import org.solovyev.android.calculator.view.DragListenerVibrator;
import org.solovyev.android.calculator.view.NumeralBasesButton;
import org.solovyev.android.calculator.view.ViewsCache;
import org.solovyev.common.listeners.JListeners;
import org.solovyev.common.listeners.Listeners;
import org.solovyev.common.math.Point2d;
import javax.annotation.Nonnull;
@ -83,9 +81,6 @@ public abstract class BaseUi implements SharedPreferences.OnSharedPreferenceChan
@Nullable
private NumeralBasesButton clearButton;
@Nonnull
private final JListeners<DragPreferencesChangeListener> dpclRegister = Listeners.newHardRefListeners();
protected BaseUi() {
}
@ -140,36 +135,24 @@ public abstract class BaseUi implements SharedPreferences.OnSharedPreferenceChan
}
public void processButtons(@Nonnull final Activity activity, @Nonnull View root) {
dpclRegister.removeListeners();
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
final SimpleDragListener.Preferences dragPreferences = SimpleDragListener.getPreferences(preferences, activity);
final ViewsCache views = ViewsCache.forView(root);
setOnDragListeners(views, dragPreferences, preferences);
setOnDragListeners(views, preferences, activity);
HistoryDragProcessor<CalculatorHistoryState> historyDragProcessor = new HistoryDragProcessor<>(getCalculator());
final DragListener historyDragListener = new DragListenerVibrator(newOnDragListener(historyDragProcessor, dragPreferences), vibrator, preferences);
final DragListener historyDragListener = new DragListenerVibrator(newDragListener(historyDragProcessor, activity), vibrator, preferences);
final DragButton historyButton = getButton(views, R.id.cpp_button_history);
if (historyButton != null) {
historyButton.setOnDragListener(historyDragListener);
}
final DragButton subtractionButton = getButton(views, R.id.cpp_button_subtraction);
if (subtractionButton != null) {
subtractionButton.setOnDragListener(new DragListenerVibrator(newOnDragListener(new SimpleDragListener.DragProcessor() {
@Override
public boolean processDragEvent(@Nonnull DragDirection dragDirection, @Nonnull DragButton dragButton, @Nonnull Point2d startPoint2d, @Nonnull MotionEvent motionEvent) {
if (dragDirection == DragDirection.down) {
Locator.getInstance().getCalculator().fireCalculatorEvent(CalculatorEventType.show_operators, null);
return true;
}
return false;
}
}, dragPreferences), vibrator, preferences));
final DragButton minusButton = getButton(views, R.id.cpp_button_subtraction);
if (minusButton != null) {
minusButton.setOnDragListener(new DragListenerVibrator(newDragListener(new MinusButtonDragProcessor(), activity), vibrator, preferences));
}
final DragListener toPositionDragListener = new DragListenerVibrator(new SimpleDragListener(new CursorDragProcessor(), dragPreferences), vibrator, preferences);
final DragListener toPositionDragListener = new DragListenerVibrator(new SimpleDragListener(new CursorDragProcessor(), activity), vibrator, preferences);
final DragButton rightButton = getButton(views, R.id.cpp_button_right);
if (rightButton != null) {
@ -183,32 +166,32 @@ public abstract class BaseUi implements SharedPreferences.OnSharedPreferenceChan
final DragButton equalsButton = getButton(views, R.id.cpp_button_equals);
if (equalsButton != null) {
equalsButton.setOnDragListener(new DragListenerVibrator(newOnDragListener(new EqualsDragProcessor(), dragPreferences), vibrator, preferences));
equalsButton.setOnDragListener(new DragListenerVibrator(newDragListener(new EqualsDragProcessor(), activity), vibrator, preferences));
}
angleUnitsButton = getButton(views, R.id.cpp_button_6);
if (angleUnitsButton != null) {
angleUnitsButton.setOnDragListener(new DragListenerVibrator(newOnDragListener(new CalculatorButtons.AngleUnitsChanger(activity), dragPreferences), vibrator, preferences));
angleUnitsButton.setOnDragListener(new DragListenerVibrator(newDragListener(new CalculatorButtons.AngleUnitsChanger(activity), activity), vibrator, preferences));
}
clearButton = getButton(views, R.id.cpp_button_clear);
if (clearButton != null) {
clearButton.setOnDragListener(new DragListenerVibrator(newOnDragListener(new CalculatorButtons.NumeralBasesChanger(activity), dragPreferences), vibrator, preferences));
clearButton.setOnDragListener(new DragListenerVibrator(newDragListener(new CalculatorButtons.NumeralBasesChanger(activity), activity), vibrator, preferences));
}
final DragButton varsButton = getButton(views, R.id.cpp_button_vars);
if (varsButton != null) {
varsButton.setOnDragListener(new DragListenerVibrator(newOnDragListener(new CalculatorButtons.VarsDragProcessor(activity), dragPreferences), vibrator, preferences));
varsButton.setOnDragListener(new DragListenerVibrator(newDragListener(new CalculatorButtons.VarsDragProcessor(activity), activity), vibrator, preferences));
}
final DragButton functionsButton = getButton(views, R.id.cpp_button_functions);
if (functionsButton != null) {
functionsButton.setOnDragListener(new DragListenerVibrator(newOnDragListener(new CalculatorButtons.FunctionsDragProcessor(activity), dragPreferences), vibrator, preferences));
functionsButton.setOnDragListener(new DragListenerVibrator(newDragListener(new CalculatorButtons.FunctionsDragProcessor(activity), activity), vibrator, preferences));
}
final DragButton roundBracketsButton = getButton(views, R.id.cpp_button_round_brackets);
if (roundBracketsButton != null) {
roundBracketsButton.setOnDragListener(new DragListenerVibrator(newOnDragListener(new CalculatorButtons.RoundBracketsDragProcessor(), dragPreferences), vibrator, preferences));
roundBracketsButton.setOnDragListener(new DragListenerVibrator(newDragListener(new CalculatorButtons.RoundBracketsDragProcessor(), activity), vibrator, preferences));
}
if (layout == simple || layout == simple_mobile) {
@ -239,8 +222,8 @@ public abstract class BaseUi implements SharedPreferences.OnSharedPreferenceChan
new ButtonOnClickListener().attachToViews(views);
}
private void setOnDragListeners(@Nonnull ViewsCache views, @Nonnull SimpleDragListener.Preferences dragPreferences, @Nonnull SharedPreferences preferences) {
final DragListener dragListener = new DragListenerVibrator(newOnDragListener(new DigitButtonDragProcessor(getKeyboard()), dragPreferences), vibrator, preferences);
private void setOnDragListeners(@Nonnull ViewsCache views, @Nonnull SharedPreferences preferences, @Nonnull Context context) {
final DragListener dragListener = new DragListenerVibrator(newDragListener(new DigitButtonDragProcessor(getKeyboard()), context), vibrator, preferences);
final List<Integer> viewIds = getViewIds();
for (Integer viewId : viewIds) {
@ -252,11 +235,8 @@ public abstract class BaseUi implements SharedPreferences.OnSharedPreferenceChan
}
@Nonnull
private SimpleDragListener newOnDragListener(@Nonnull SimpleDragListener.DragProcessor dragProcessor,
@Nonnull SimpleDragListener.Preferences dragPreferences) {
final SimpleDragListener onDragListener = new SimpleDragListener(dragProcessor, dragPreferences);
dpclRegister.addListener(onDragListener);
return onDragListener;
private SimpleDragListener newDragListener(@Nonnull SimpleDragListener.DragProcessor dragProcessor, @Nonnull Context context) {
return new SimpleDragListener(dragProcessor, context);
}
private void toggleButtonDirectionText(@Nonnull ViewsCache views, int id, boolean showDirectionText, @Nonnull DragDirection... dragDirections) {
@ -328,4 +308,15 @@ public abstract class BaseUi implements SharedPreferences.OnSharedPreferenceChan
}
}
}
private static class MinusButtonDragProcessor implements SimpleDragListener.DragProcessor {
@Override
public boolean processDragEvent(@Nonnull DragDirection dragDirection, @Nonnull DragButton dragButton, @Nonnull Point2d startPoint2d, @Nonnull MotionEvent motionEvent) {
if (dragDirection == DragDirection.down) {
Locator.getInstance().getCalculator().fireCalculatorEvent(CalculatorEventType.show_operators, null);
return true;
}
return false;
}
}
}

View File

@ -48,7 +48,8 @@ public class DigitButtonDragProcessor implements SimpleDragListener.DragProcesso
@Override
public boolean processDragEvent(@Nonnull DragDirection dragDirection, @Nonnull DragButton dragButton, @Nonnull Point2d startPoint2d, @Nonnull MotionEvent motionEvent) {
if (!(dragButton instanceof DirectionDragButton)) throw new AssertionError();
calculatorKeyboard.buttonPressed(((DirectionDragButton) dragButton).getText(dragDirection));
final String text = ((DirectionDragButton) dragButton).getText(dragDirection);
calculatorKeyboard.buttonPressed(text);
return true;
}

View File

@ -398,7 +398,7 @@ public class DirectionDragButton extends DragButton {
private Map<GuiDragDirection, Float> getDirectionTextScales() {
final List<Float> scales = StringCollections.split(getDirectionTextScale(), ";", NumberParser.of(Float.class));
final Map<GuiDragDirection, Float> result = new HashMap<GuiDragDirection, Float>();
final Map<GuiDragDirection, Float> result = new HashMap<>();
for (GuiDragDirection direction : GuiDragDirection.values()) {
result.put(direction, DEFAULT_DIRECTION_TEXT_SCALE_FLOAT);
}

View File

@ -67,8 +67,6 @@ public class DragButton extends Button {
if (localOnDragListener != null) {
// only if onDrag() listener specified
Log.d(String.valueOf(getId()), "onTouch() for: " + getId() + " . Motion event: " + event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// start tracking: set start point

View File

@ -1,36 +0,0 @@
/*
* Copyright 2013 serso aka se.solovyev
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Contact details
*
* Email: se.solovyev@gmail.com
* Site: http://se.solovyev.org
*/
package org.solovyev.android.calculator.drag;
import javax.annotation.Nonnull;
import java.util.EventListener;
/**
* User: serso
* Date: 9/18/11
* Time: 8:48 PM
*/
public interface DragPreferencesChangeListener extends EventListener {
void onDragPreferencesChange(@Nonnull SimpleDragListener.Preferences preferences);
}

View File

@ -23,99 +23,81 @@
package org.solovyev.android.calculator.drag;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import android.support.v4.view.ViewConfigurationCompat;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import org.solovyev.android.calculator.R;
import org.solovyev.common.MutableObject;
import org.solovyev.common.interval.Interval;
import org.solovyev.common.interval.Intervals;
import org.solovyev.common.math.Maths;
import org.solovyev.common.math.Point2d;
import org.solovyev.common.text.Mapper;
import org.solovyev.common.text.NumberIntervalMapper;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
public class SimpleDragListener implements DragListener, DragPreferencesChangeListener {
import java.util.EnumMap;
public class SimpleDragListener implements DragListener {
@Nonnull
public static final Point2d axis = new Point2d(0, 1);
private static final Point2d axis = new Point2d(0, 1);
@Nonnull
private DragProcessor dragProcessor;
private static final EnumMap<DragDirection, Interval<Float>> sAngleIntervals = new EnumMap<>(DragDirection.class);
static {
for (DragDirection direction : DragDirection.values()) {
sAngleIntervals.put(direction, makeAngleInterval(direction, 0, 45));
}
}
@Nonnull
private Preferences preferences;
private final DragProcessor processor;
public SimpleDragListener(@Nonnull DragProcessor dragProcessor, @Nonnull Preferences preferences) {
this.dragProcessor = dragProcessor;
this.preferences = preferences;
private final float minDistancePxs;
public SimpleDragListener(@Nonnull DragProcessor processor, @Nonnull Context context) {
this.processor = processor;
this.minDistancePxs = context.getResources().getDimensionPixelSize(R.dimen.cpp_min_drag_distance);
}
@Override
public boolean onDrag(@Nonnull DragButton dragButton, @Nonnull DragEvent event) {
boolean result = false;
boolean consumed = false;
logDragEvent(dragButton, event);
final Point2d startPoint = event.getStartPoint();
final MotionEvent motionEvent = event.getMotionEvent();
// init end point
final Point2d endPoint = new Point2d(motionEvent.getX(), motionEvent.getY());
final Point2d start = event.getStartPoint();
final Point2d end = new Point2d(motionEvent.getX(), motionEvent.getY());
final float distance = Maths.getDistance(start, end);
final float distance = Maths.getDistance(startPoint, endPoint);
final MutableObject<Boolean> right = new MutableObject<>();
final double angle = Math.toDegrees(Maths.getAngle(start, Maths.sum(start, axis), end, right));
final MutableObject<Boolean> right = new MutableObject<Boolean>();
final double angle = Math.toDegrees(Maths.getAngle(startPoint, Maths.sum(startPoint, axis), endPoint, right));
Log.d(String.valueOf(dragButton.getId()), "Angle: " + angle);
Log.d(String.valueOf(dragButton.getId()), "Is right?: " + right.getObject());
final long duration = motionEvent.getEventTime() - motionEvent.getDownTime();
final DragDirection direction = getDirection(distance, (float) angle, right.getObject());
if (direction != null && duration > 40 && duration < 2500) {
consumed = processor.processDragEvent(direction, dragButton, start, motionEvent);
}
final double duration = motionEvent.getEventTime() - motionEvent.getDownTime();
return consumed;
}
final Preference distancePreferences = preferences.getPreferencesMap().get(PreferenceType.distance);
final Preference anglePreferences = preferences.getPreferencesMap().get(PreferenceType.angle);
DragDirection direction = null;
for (Map.Entry<DragDirection, DragPreference> directionEntry : distancePreferences.getDirectionPreferences().entrySet()) {
Log.d(String.valueOf(dragButton.getId()), "Drag direction: " + directionEntry.getKey());
Log.d(String.valueOf(dragButton.getId()), "Trying direction interval: " + directionEntry.getValue().getInterval());
if (directionEntry.getValue().getInterval().contains(distance)) {
final DragPreference anglePreference = anglePreferences.getDirectionPreferences().get(directionEntry.getKey());
Log.d(String.valueOf(dragButton.getId()), "Trying angle interval: " + anglePreference.getInterval());
if (directionEntry.getKey() == DragDirection.left && right.getObject()) {
} else if (directionEntry.getKey() == DragDirection.right && !right.getObject()) {
} else {
if (anglePreference.getInterval().contains((float) angle)) {
direction = directionEntry.getKey();
Log.d(String.valueOf(dragButton.getId()), "MATCH! Direction: " + direction);
break;
@Nullable
private DragDirection getDirection(float distance, float angle, boolean right) {
if (distance > minDistancePxs) {
for (DragDirection direction : DragDirection.values()) {
final Interval<Float> angleInterval = sAngleIntervals.get(direction);
final boolean wrongDirection = (direction == DragDirection.left && right) ||
(direction == DragDirection.right && !right);
if (!wrongDirection && angleInterval.contains(angle)) {
return direction;
}
}
}
}
if (direction != null) {
final Preference durationPreferences = preferences.getPreferencesMap().get(PreferenceType.duration);
final DragPreference durationDragPreferences = durationPreferences.getDirectionPreferences().get(direction);
Log.d(String.valueOf(dragButton.getId()), "Trying time interval: " + durationDragPreferences.getInterval());
if (durationDragPreferences.getInterval().contains((float) duration)) {
Log.d(String.valueOf(dragButton.getId()), "MATCH!");
result = dragProcessor.processDragEvent(direction, dragButton, startPoint, motionEvent);
}
}
return result;
return null;
}
@Override
@ -123,173 +105,38 @@ public class SimpleDragListener implements DragListener, DragPreferencesChangeLi
return true;
}
private void logDragEvent(@Nonnull DragButton dragButton, @Nonnull DragEvent event) {
final Point2d startPoint = event.getStartPoint();
final MotionEvent motionEvent = event.getMotionEvent();
final Point2d endPoint = new Point2d(motionEvent.getX(), motionEvent.getY());
Log.d(String.valueOf(dragButton.getId()), "Start point: " + startPoint + ", End point: " + endPoint);
Log.d(String.valueOf(dragButton.getId()), "Distance: " + Maths.getDistance(startPoint, endPoint));
final MutableObject<Boolean> right = new MutableObject<Boolean>();
Log.d(String.valueOf(dragButton.getId()), "Angle: " + Math.toDegrees(Maths.getAngle(startPoint, Maths.sum(startPoint, axis), endPoint, right)));
Log.d(String.valueOf(dragButton.getId()), "Is right angle? " + right);
Log.d(String.valueOf(dragButton.getId()), "Axis: " + axis + " Vector: " + Maths.subtract(endPoint, startPoint));
Log.d(String.valueOf(dragButton.getId()), "Total time: " + (motionEvent.getEventTime() - motionEvent.getDownTime()) + " ms");
}
@Override
public void onDragPreferencesChange(@Nonnull Preferences preferences) {
this.preferences = preferences;
}
public interface DragProcessor {
boolean processDragEvent(@Nonnull DragDirection dragDirection, @Nonnull DragButton dragButton, @Nonnull Point2d startPoint2d, @Nonnull MotionEvent motionEvent);
}
// todo serso: currently we do not use direction
public static String getPreferenceId(@Nonnull PreferenceType preferenceType, @Nonnull DragDirection direction) {
return "org.solovyev.android.calculator.DragButtonCalibrationActivity" + "_" + preferenceType.name() /*+ "_" + direction.name()*/;
}
@Nonnull
public static Preferences getDefaultPreferences(@Nonnull Context context) {
return getPreferences0(null, context);
}
@Nonnull
public static Preferences getPreferences(@Nonnull final SharedPreferences preferences, @Nonnull Context context) {
return getPreferences0(preferences, context);
}
@Nonnull
private static Preferences getPreferences0(@Nullable final SharedPreferences preferences, @Nonnull Context context) {
final Mapper<Interval<Float>> mapper = NumberIntervalMapper.of(Float.class);
final Preferences result = new Preferences();
for (PreferenceType preferenceType : PreferenceType.values()) {
for (DragDirection dragDirection : DragDirection.values()) {
final String preferenceId = getPreferenceId(preferenceType, dragDirection);
final String defaultValue;
switch (preferenceType) {
case angle:
defaultValue = context.getResources().getString(org.solovyev.android.view.R.string.p_drag_angle);
break;
case distance:
defaultValue = context.getResources().getString(org.solovyev.android.view.R.string.p_drag_distance);
break;
case duration:
defaultValue = context.getResources().getString(org.solovyev.android.view.R.string.p_drag_duration);
break;
default:
defaultValue = null;
Log.e(SimpleDragListener.class.getName(), "New preference type added: default preferences should be defined. Preference id: " + preferenceId);
}
final String value = preferences == null ? defaultValue : preferences.getString(preferenceId, defaultValue);
if (value != null) {
final Interval<Float> intervalPref = transformInterval(preferenceType, dragDirection, mapper.parseValue(value));
Log.d(SimpleDragListener.class.getName(), "Preference loaded for " + dragDirection + ". Id: " + preferenceId + ", value: " + intervalPref.toString());
final DragPreference directionPreference = new DragPreference(intervalPref);
Preference preference = result.getPreferencesMap().get(preferenceType);
if (preference == null) {
preference = new Preference();
result.getPreferencesMap().put(preferenceType, preference);
}
preference.getDirectionPreferences().put(dragDirection, directionPreference);
}
}
}
return result;
}
@Nonnull
public static Interval<Float> transformInterval(@Nonnull PreferenceType preferenceType,
@Nonnull DragDirection dragDirection,
@Nonnull Interval<Float> interval) {
if (preferenceType == PreferenceType.angle) {
final Float leftLimit = interval.getLeftLimit();
final Float rightLimit = interval.getRightLimit();
if (leftLimit != null && rightLimit != null) {
private static Interval<Float> makeAngleInterval(@Nonnull DragDirection direction,
float leftLimit,
float rightLimit) {
final Float newLeftLimit;
final Float newRightLimit;
if (dragDirection == DragDirection.up) {
switch (direction) {
case up:
newLeftLimit = 180f - rightLimit;
newRightLimit = 180f - leftLimit;
} else if (dragDirection == DragDirection.left) {
newLeftLimit = 90f - rightLimit;
newRightLimit = 90f + rightLimit;
} else if (dragDirection == DragDirection.right) {
newLeftLimit = 90f - rightLimit;
newRightLimit = 90f + rightLimit;
} else {
break;
case down:
newLeftLimit = leftLimit;
newRightLimit = rightLimit;
break;
case left:
newLeftLimit = 90f - rightLimit;
newRightLimit = 90f + rightLimit;
break;
case right:
newLeftLimit = 90f - rightLimit;
newRightLimit = 90f + rightLimit;
break;
default:
throw new AssertionError();
}
return Intervals.newClosedInterval(newLeftLimit, newRightLimit);
}
}
return interval;
}
public static enum PreferenceType {
angle,
distance,
duration
}
public static class DragPreference {
@Nonnull
private Interval<Float> interval;
public DragPreference(@Nonnull Interval<Float> interval) {
this.interval = interval;
}
@Nonnull
public Interval<Float> getInterval() {
return interval;
}
}
public static class Preference {
@Nonnull
private Map<DragDirection, DragPreference> directionPreferences = new HashMap<DragDirection, DragPreference>();
@Nonnull
public Map<DragDirection, DragPreference> getDirectionPreferences() {
return directionPreferences;
}
}
public static class Preferences {
private final Map<PreferenceType, Preference> preferencesMap = new HashMap<>();
public Map<PreferenceType, Preference> getPreferencesMap() {
return preferencesMap;
}
}
}

View File

@ -54,12 +54,12 @@ public class DragListenerVibrator extends DragListenerWrapper {
@Override
public boolean onDrag(@Nonnull DragButton dragButton, @Nonnull DragEvent event) {
boolean result = super.onDrag(dragButton, event);
boolean consumed = super.onDrag(dragButton, event);
if (result) {
if (consumed) {
vibrator.vibrate();
}
return result;
return consumed;
}
}

View File

@ -84,7 +84,7 @@ public class DragButtonWizardStep extends WizardFragment {
dragButton = (DirectionDragButton) root.findViewById(R.id.wizard_dragbutton);
dragButton.setOnClickListener(new DragButtonOnClickListener());
dragButton.setOnDragListener(new SimpleDragListener(new DragButtonProcessor(), SimpleDragListener.getDefaultPreferences(getActivity())));
dragButton.setOnDragListener(new SimpleDragListener(new DragButtonProcessor(), getActivity()));
actionTextView = (TextView) root.findViewById(R.id.wizard_dragbutton_action_textview);
descriptionTextView = (TextView) root.findViewById(R.id.wizard_dragbutton_description_textview);

View File

@ -39,4 +39,5 @@
<dimen name="activity_horizontal_margin">5dp</dimen>
<dimen name="activity_vertical_margin">5dp</dimen>
<dimen name="control_margin">5dp</dimen>
<dimen name="cpp_min_drag_distance">25dp</dimen>
</resources>

View File

@ -44,7 +44,6 @@ public class CalculatorKeyboardImpl implements CalculatorKeyboard {
@Override
public void buttonPressed(@Nullable final String text) {
if (!Strings.isEmpty(text)) {
if (text == null) throw new AssertionError();