Merge change 2402 into donut

* changes:
  Cleanup Gestures API and make it easier to use in 3rd party apps. Also fix the events processing in the gestures overlay mechanism. Give better control of the various properties of the overlay through XML attributes.
This commit is contained in:
Android (Google) Code Review
2009-05-24 23:47:30 -07:00
18 changed files with 916 additions and 777 deletions

View File

@ -3529,61 +3529,6 @@
visibility="public"
>
</field>
<field name="donut_resource_pad34"
type="int"
transient="false"
volatile="false"
value="16843390"
static="true"
final="true"
deprecated="not deprecated"
visibility="public"
>
</field>
<field name="donut_resource_pad35"
type="int"
transient="false"
volatile="false"
value="16843389"
static="true"
final="true"
deprecated="not deprecated"
visibility="public"
>
</field>
<field name="donut_resource_pad36"
type="int"
transient="false"
volatile="false"
value="16843388"
static="true"
final="true"
deprecated="not deprecated"
visibility="public"
>
</field>
<field name="donut_resource_pad37"
type="int"
transient="false"
volatile="false"
value="16843387"
static="true"
final="true"
deprecated="not deprecated"
visibility="public"
>
</field>
<field name="donut_resource_pad38"
type="int"
transient="false"
volatile="false"
value="16843386"
static="true"
final="true"
deprecated="not deprecated"
visibility="public"
>
</field>
<field name="donut_resource_pad4"
type="int"
transient="false"
@ -3947,6 +3892,17 @@
visibility="public"
>
</field>
<field name="eventsInterceptionEnabled"
type="int"
transient="false"
volatile="false"
value="16843390"
static="true"
final="true"
deprecated="not deprecated"
visibility="public"
>
</field>
<field name="excludeFromRecents"
type="int"
transient="false"
@ -4420,6 +4376,50 @@
visibility="public"
>
</field>
<field name="gestureStrokeAngleThreshold"
type="int"
transient="false"
volatile="false"
value="16843389"
static="true"
final="true"
deprecated="not deprecated"
visibility="public"
>
</field>
<field name="gestureStrokeLengthThreshold"
type="int"
transient="false"
volatile="false"
value="16843387"
static="true"
final="true"
deprecated="not deprecated"
visibility="public"
>
</field>
<field name="gestureStrokeSquarenessThreshold"
type="int"
transient="false"
volatile="false"
value="16843388"
static="true"
final="true"
deprecated="not deprecated"
visibility="public"
>
</field>
<field name="gestureStrokeType"
type="int"
transient="false"
volatile="false"
value="16843386"
static="true"
final="true"
deprecated="not deprecated"
visibility="public"
>
</field>
<field name="gestureStrokeWidth"
type="int"
transient="false"
@ -46303,6 +46303,70 @@
<parameter name="color" type="int">
</parameter>
</method>
<method name="toPath"
return="android.graphics.Path"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
</method>
<method name="toPath"
return="android.graphics.Path"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="path" type="android.graphics.Path">
</parameter>
</method>
<method name="toPath"
return="android.graphics.Path"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="width" type="int">
</parameter>
<parameter name="height" type="int">
</parameter>
<parameter name="edge" type="int">
</parameter>
<parameter name="numSample" type="int">
</parameter>
</method>
<method name="toPath"
return="android.graphics.Path"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="path" type="android.graphics.Path">
</parameter>
<parameter name="width" type="int">
</parameter>
<parameter name="height" type="int">
</parameter>
<parameter name="edge" type="int">
</parameter>
<parameter name="numSample" type="int">
</parameter>
</method>
<method name="writeToParcel"
return="void"
abstract="false"
@ -46543,7 +46607,7 @@
</field>
</class>
<class name="GestureOverlayView"
extends="android.view.View"
extends="android.widget.FrameLayout"
abstract="false"
static="false"
final="false"
@ -46599,7 +46663,31 @@
<parameter name="listener" type="android.gesture.GestureOverlayView.OnGestureListener">
</parameter>
</method>
<method name="cancelFadingOut"
<method name="addOnGesturePerformedListener"
return="void"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="listener" type="android.gesture.GestureOverlayView.OnGesturePerformedListener">
</parameter>
</method>
<method name="cancelClearAnimation"
return="void"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
</method>
<method name="cancelGesture"
return="void"
abstract="false"
native="false"
@ -46620,11 +46708,11 @@
deprecated="not deprecated"
visibility="public"
>
<parameter name="fadeOut" type="boolean">
<parameter name="animated" type="boolean">
</parameter>
</method>
<method name="getCurrentGesture"
return="android.gesture.Gesture"
<method name="getCurrentStroke"
return="java.util.ArrayList&lt;android.gesture.GesturePoint&gt;"
abstract="false"
native="false"
synchronized="false"
@ -46634,8 +46722,8 @@
visibility="public"
>
</method>
<method name="getCurrentStroke"
return="java.util.ArrayList&lt;android.gesture.GesturePoint&gt;"
<method name="getGesture"
return="android.gesture.Gesture"
abstract="false"
native="false"
synchronized="false"
@ -46656,7 +46744,51 @@
visibility="public"
>
</method>
<method name="getGestureStroke"
<method name="getGestureStrokeAngleThreshold"
return="float"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
</method>
<method name="getGestureStrokeLengthThreshold"
return="float"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
</method>
<method name="getGestureStrokeSquarenessTreshold"
return="float"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
</method>
<method name="getGestureStrokeType"
return="int"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
</method>
<method name="getGestureStrokeWidth"
return="float"
abstract="false"
native="false"
@ -46678,7 +46810,29 @@
visibility="public"
>
</method>
<method name="processEvent"
<method name="isEventsInterceptionEnabled"
return="boolean"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
</method>
<method name="isGesturing"
return="boolean"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
</method>
<method name="removeAllOnGestureListeners"
return="void"
abstract="false"
native="false"
@ -46688,10 +46842,8 @@
deprecated="not deprecated"
visibility="public"
>
<parameter name="event" type="android.view.MotionEvent">
</parameter>
</method>
<method name="removeAllOnGestureListeners"
<method name="removeAllOnGesturePerformedListeners"
return="void"
abstract="false"
native="false"
@ -46715,7 +46867,33 @@
<parameter name="listener" type="android.gesture.GestureOverlayView.OnGestureListener">
</parameter>
</method>
<method name="setCurrentGesture"
<method name="removeOnGesturePerformedListener"
return="void"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="listener" type="android.gesture.GestureOverlayView.OnGesturePerformedListener">
</parameter>
</method>
<method name="setEventsInterceptionEnabled"
return="void"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="enabled" type="boolean">
</parameter>
</method>
<method name="setGesture"
return="void"
abstract="false"
native="false"
@ -46741,7 +46919,7 @@
<parameter name="color" type="int">
</parameter>
</method>
<method name="setGestureDrawingColor"
<method name="setGestureStrokeAngleThreshold"
return="void"
abstract="false"
native="false"
@ -46751,10 +46929,10 @@
deprecated="not deprecated"
visibility="public"
>
<parameter name="color" type="int">
<parameter name="gestureStrokeAngleThreshold" type="float">
</parameter>
</method>
<method name="setGestureStroke"
<method name="setGestureStrokeLengthThreshold"
return="void"
abstract="false"
native="false"
@ -46764,7 +46942,46 @@
deprecated="not deprecated"
visibility="public"
>
<parameter name="gestureStroke" type="float">
<parameter name="gestureStrokeLengthThreshold" type="float">
</parameter>
</method>
<method name="setGestureStrokeSquarenessTreshold"
return="void"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="gestureStrokeSquarenessTreshold" type="float">
</parameter>
</method>
<method name="setGestureStrokeType"
return="void"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="gestureStrokeType" type="int">
</parameter>
</method>
<method name="setGestureStrokeWidth"
return="void"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="gestureStrokeWidth" type="float">
</parameter>
</method>
<method name="setUncertainGestureColor"
@ -46780,6 +46997,28 @@
<parameter name="color" type="int">
</parameter>
</method>
<field name="GESTURE_STROKE_TYPE_MULTIPLE"
type="int"
transient="false"
volatile="false"
value="1"
static="true"
final="true"
deprecated="not deprecated"
visibility="public"
>
</field>
<field name="GESTURE_STROKE_TYPE_SINGLE"
type="int"
transient="false"
volatile="false"
value="0"
static="true"
final="true"
deprecated="not deprecated"
visibility="public"
>
</field>
</class>
<interface name="GestureOverlayView.OnGestureListener"
abstract="true"
@ -46849,6 +47088,29 @@
</parameter>
</method>
</interface>
<interface name="GestureOverlayView.OnGesturePerformedListener"
abstract="true"
static="true"
final="false"
deprecated="not deprecated"
visibility="public"
>
<method name="onGesturePerformed"
return="void"
abstract="true"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="overlay" type="android.gesture.GestureOverlayView">
</parameter>
<parameter name="gesture" type="android.gesture.Gesture">
</parameter>
</method>
</interface>
<class name="GesturePoint"
extends="java.lang.Object"
abstract="false"
@ -46942,6 +47204,17 @@
visibility="public"
>
</method>
<method name="getPath"
return="android.graphics.Path"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
</method>
<method name="toPath"
return="android.graphics.Path"
abstract="false"
@ -47152,194 +47425,6 @@
>
</field>
</class>
<class name="TouchThroughGestureListener"
extends="java.lang.Object"
abstract="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<implements name="android.gesture.GestureOverlayView.OnGestureListener">
</implements>
<constructor name="TouchThroughGestureListener"
type="android.gesture.TouchThroughGestureListener"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="model" type="android.view.View">
</parameter>
</constructor>
<constructor name="TouchThroughGestureListener"
type="android.gesture.TouchThroughGestureListener"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="model" type="android.view.View">
</parameter>
<parameter name="stealEvents" type="boolean">
</parameter>
</constructor>
<method name="addOnGestureActionListener"
return="void"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="listener" type="android.gesture.TouchThroughGestureListener.OnGesturePerformedListener">
</parameter>
</method>
<method name="isGesturing"
return="boolean"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
</method>
<method name="onGesture"
return="void"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="overlay" type="android.gesture.GestureOverlayView">
</parameter>
<parameter name="event" type="android.view.MotionEvent">
</parameter>
</method>
<method name="onGestureCancelled"
return="void"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="overlay" type="android.gesture.GestureOverlayView">
</parameter>
<parameter name="event" type="android.view.MotionEvent">
</parameter>
</method>
<method name="onGestureEnded"
return="void"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="overlay" type="android.gesture.GestureOverlayView">
</parameter>
<parameter name="event" type="android.view.MotionEvent">
</parameter>
</method>
<method name="onGestureStarted"
return="void"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="overlay" type="android.gesture.GestureOverlayView">
</parameter>
<parameter name="event" type="android.view.MotionEvent">
</parameter>
</method>
<method name="removeOnGestureActionListener"
return="void"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="listener" type="android.gesture.TouchThroughGestureListener.OnGesturePerformedListener">
</parameter>
</method>
<method name="setGestureType"
return="void"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="type" type="int">
</parameter>
</method>
<field name="MULTIPLE_STROKE"
type="int"
transient="false"
volatile="false"
value="1"
static="true"
final="true"
deprecated="not deprecated"
visibility="public"
>
</field>
<field name="SINGLE_STROKE"
type="int"
transient="false"
volatile="false"
value="0"
static="true"
final="true"
deprecated="not deprecated"
visibility="public"
>
</field>
</class>
<interface name="TouchThroughGestureListener.OnGesturePerformedListener"
abstract="true"
static="true"
final="false"
deprecated="not deprecated"
visibility="public"
>
<method name="onGesturePerformed"
return="void"
abstract="true"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="overlay" type="android.gesture.GestureOverlayView">
</parameter>
<parameter name="gesture" type="android.gesture.Gesture">
</parameter>
</method>
</interface>
</package>
<package name="android.graphics"
>

View File

@ -57,6 +57,11 @@ public class Gesture implements Parcelable {
mGestureID = GESTURE_ID_BASE + sGestureCount++;
}
void recycle() {
mStrokes.clear();
mBoundingBox.setEmpty();
}
/**
* @return all the strokes of the gesture
*/
@ -111,6 +116,40 @@ public class Gesture implements Parcelable {
return mBoundingBox;
}
public Path toPath() {
return toPath(null);
}
public Path toPath(Path path) {
if (path == null) path = new Path();
final ArrayList<GestureStroke> strokes = mStrokes;
final int count = strokes.size();
for (int i = 0; i < count; i++) {
path.addPath(strokes.get(i).getPath());
}
return path;
}
public Path toPath(int width, int height, int edge, int numSample) {
return toPath(null, width, height, edge, numSample);
}
public Path toPath(Path path, int width, int height, int edge, int numSample) {
if (path == null) path = new Path();
final ArrayList<GestureStroke> strokes = mStrokes;
final int count = strokes.size();
for (int i = 0; i < count; i++) {
path.addPath(strokes.get(i).toPath(width - 2 * edge, height - 2 * edge, numSample));
}
return path;
}
/**
* Set the id of the gesture
*

View File

@ -18,51 +18,62 @@ package android.gesture;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.AnimationUtils;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.FrameLayout;
import android.os.SystemClock;
import com.android.internal.R;
import java.util.ArrayList;
/**
* A (transparent) overlay for gesture input that can be placed on top of other
* widgets.
* A transparent overlay for gesture input that can be placed on top of other
* widgets or contain other widgets.
*
* @attr ref android.R.styleable#GestureOverlayView_gestureStrokeWidth
* @attr ref android.R.styleable#GestureOverlayView_gestureColor
* @attr ref android.R.styleable#GestureOverlayView_uncertainGestureColor
* @attr ref android.R.styleable#GestureOverlayView_eventsInterceptionEnabled
* @attr ref android.R.styleable#GestureOverlayView_fadeDuration
* @attr ref android.R.styleable#GestureOverlayView_fadeOffset
* @attr ref android.R.styleable#GestureOverlayView_gestureStrokeWidth
* @attr ref android.R.styleable#GestureOverlayView_gestureStrokeAngleThreshold
* @attr ref android.R.styleable#GestureOverlayView_gestureStrokeLengthThreshold
* @attr ref android.R.styleable#GestureOverlayView_gestureStrokeSquarenessThreshold
* @attr ref android.R.styleable#GestureOverlayView_gestureStrokeType
* @attr ref android.R.styleable#GestureOverlayView_gestureColor
* @attr ref android.R.styleable#GestureOverlayView_uncertainGestureColor
*/
public class GestureOverlayView extends View {
private static final int TRANSPARENT_BACKGROUND = 0x00000000;
public class GestureOverlayView extends FrameLayout {
public static final int GESTURE_STROKE_TYPE_SINGLE = 0;
public static final int GESTURE_STROKE_TYPE_MULTIPLE = 1;
private static final int FADE_ANIMATION_RATE = 16;
private static final boolean GESTURE_RENDERING_ANTIALIAS = true;
private static final boolean DITHER_FLAG = true;
private Paint mGesturePaint;
private final Paint mGesturePaint = new Paint();
private final Paint mBitmapPaint = new Paint(Paint.DITHER_FLAG);
private Bitmap mBitmap;
private Canvas mBitmapCanvas;
private long mFadeDuration = 300;
private long mFadeOffset = 300;
private long mFadeDuration = 150;
private long mFadeOffset = 420;
private long mFadingStart;
private boolean mFadingHasStarted;
private float mGestureStroke = 12.0f;
private int mCurrentColor;
private int mCertainGestureColor = 0xFFFFFF00;
private int mUncertainGestureColor = 0x3CFFFF00;
private int mUncertainGestureColor = 0x48FFFF00;
private float mGestureStrokeWidth = 12.0f;
private int mInvalidateExtraBorder = 10;
// for rendering immediate ink feedback
private int mGestureStrokeType = GESTURE_STROKE_TYPE_SINGLE;
private float mGestureStrokeLengthThreshold = 30.0f;
private float mGestureStrokeSquarenessTreshold = 0.275f;
private float mGestureStrokeAngleThreshold = 40.0f;
private final Rect mInvalidRect = new Rect();
private final Path mPath = new Path();
@ -72,41 +83,31 @@ public class GestureOverlayView extends View {
private float mCurveEndX;
private float mCurveEndY;
private float mTotalLength;
private boolean mIsGesturing = false;
private boolean mInterceptEvents = true;
private boolean mIsListeningForGestures;
// current gesture
private Gesture mCurrentGesture = null;
private Gesture mCurrentGesture;
private final ArrayList<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(100);
// TODO: Make this a list of WeakReferences
private final ArrayList<OnGestureListener> mOnGestureListeners =
new ArrayList<OnGestureListener>();
private final ArrayList<GesturePoint> mPointBuffer = new ArrayList<GesturePoint>(100);
// TODO: Make this a list of WeakReferences
private final ArrayList<OnGesturePerformedListener> mOnGesturePerformedListeners =
new ArrayList<OnGesturePerformedListener>();
private boolean mHandleGestureActions;
// fading out effect
private boolean mIsFadingOut = false;
private float mFadingAlpha = 1;
private float mFadingAlpha = 1.0f;
private final AccelerateDecelerateInterpolator mInterpolator =
new AccelerateDecelerateInterpolator();
private final Runnable mFadingOut = new Runnable() {
public void run() {
if (mIsFadingOut) {
final long now = AnimationUtils.currentAnimationTimeMillis();
final long duration = now - mFadingStart;
if (duration > mFadeDuration) {
mIsFadingOut = false;
mPath.rewind();
mCurrentGesture = null;
mBitmap.eraseColor(TRANSPARENT_BACKGROUND);
} else {
float interpolatedTime = Math.max(0.0f,
Math.min(1.0f, duration / (float) mFadeDuration));
mFadingAlpha = 1.0f - mInterpolator.getInterpolation(interpolatedTime);
postDelayed(this, 16);
}
invalidate();
}
}
};
private final FadeOutRunnable mFadingOut = new FadeOutRunnable();
public GestureOverlayView(Context context) {
super(context);
@ -123,41 +124,52 @@ public class GestureOverlayView extends View {
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.GestureOverlayView, defStyle, 0);
mGestureStroke = a.getFloat(R.styleable.GestureOverlayView_gestureStrokeWidth,
mGestureStroke);
mInvalidateExtraBorder = Math.max(1, ((int) mGestureStroke) - 1);
mGestureStrokeWidth = a.getFloat(R.styleable.GestureOverlayView_gestureStrokeWidth,
mGestureStrokeWidth);
mInvalidateExtraBorder = Math.max(1, ((int) mGestureStrokeWidth) - 1);
mCertainGestureColor = a.getColor(R.styleable.GestureOverlayView_gestureColor,
mCertainGestureColor);
mUncertainGestureColor = a.getColor(R.styleable.GestureOverlayView_uncertainGestureColor,
mUncertainGestureColor);
mFadeDuration = a.getInt(R.styleable.GestureOverlayView_fadeDuration, (int) mFadeDuration);
mFadeOffset = a.getInt(R.styleable.GestureOverlayView_fadeOffset, (int) mFadeOffset);
mGestureStrokeType = a.getInt(R.styleable.GestureOverlayView_gestureStrokeType,
mGestureStrokeType);
mGestureStrokeLengthThreshold = a.getFloat(
R.styleable.GestureOverlayView_gestureStrokeLengthThreshold,
mGestureStrokeLengthThreshold);
mGestureStrokeAngleThreshold = a.getFloat(
R.styleable.GestureOverlayView_gestureStrokeAngleThreshold,
mGestureStrokeAngleThreshold);
mGestureStrokeSquarenessTreshold = a.getFloat(
R.styleable.GestureOverlayView_gestureStrokeSquarenessThreshold,
mGestureStrokeSquarenessTreshold);
mInterceptEvents = a.getBoolean(R.styleable.GestureOverlayView_eventsInterceptionEnabled,
mInterceptEvents);
a.recycle();
init();
}
private void init() {
setWillNotDraw(false);
final Paint gesturePaint = mGesturePaint;
gesturePaint.setAntiAlias(GESTURE_RENDERING_ANTIALIAS);
gesturePaint.setColor(mCertainGestureColor);
gesturePaint.setStyle(Paint.Style.STROKE);
gesturePaint.setStrokeJoin(Paint.Join.ROUND);
gesturePaint.setStrokeCap(Paint.Cap.ROUND);
gesturePaint.setStrokeWidth(mGestureStrokeWidth);
gesturePaint.setDither(DITHER_FLAG);
mCurrentColor = mCertainGestureColor;
setPaintAlpha(255);
}
public ArrayList<GesturePoint> getCurrentStroke() {
return mPointBuffer;
}
public Gesture getCurrentGesture() {
return mCurrentGesture;
}
/**
* Set Gesture color
*
* @param color
*/
public void setGestureDrawingColor(int color) {
mGesturePaint.setColor(color);
if (mCurrentGesture != null) {
mBitmap.eraseColor(TRANSPARENT_BACKGROUND);
mCurrentGesture.draw(mBitmapCanvas, mGesturePaint);
}
invalidate();
return mStrokeBuffer;
}
public void setGestureColor(int color) {
@ -176,73 +188,77 @@ public class GestureOverlayView extends View {
return mCertainGestureColor;
}
public float getGestureStroke() {
return mGestureStroke;
public float getGestureStrokeWidth() {
return mGestureStrokeWidth;
}
public void setGestureStroke(float gestureStroke) {
mGestureStroke = gestureStroke;
mInvalidateExtraBorder = Math.max(1, ((int) mGestureStroke) - 1);
mGesturePaint.setStrokeWidth(mGestureStroke);
public void setGestureStrokeWidth(float gestureStrokeWidth) {
mGestureStrokeWidth = gestureStrokeWidth;
mInvalidateExtraBorder = Math.max(1, ((int) gestureStrokeWidth) - 1);
mGesturePaint.setStrokeWidth(gestureStrokeWidth);
}
/**
* Set the gesture to be shown in the view
*
* @param gesture
*/
public void setCurrentGesture(Gesture gesture) {
public int getGestureStrokeType() {
return mGestureStrokeType;
}
public void setGestureStrokeType(int gestureStrokeType) {
mGestureStrokeType = gestureStrokeType;
}
public float getGestureStrokeLengthThreshold() {
return mGestureStrokeLengthThreshold;
}
public void setGestureStrokeLengthThreshold(float gestureStrokeLengthThreshold) {
mGestureStrokeLengthThreshold = gestureStrokeLengthThreshold;
}
public float getGestureStrokeSquarenessTreshold() {
return mGestureStrokeSquarenessTreshold;
}
public void setGestureStrokeSquarenessTreshold(float gestureStrokeSquarenessTreshold) {
mGestureStrokeSquarenessTreshold = gestureStrokeSquarenessTreshold;
}
public float getGestureStrokeAngleThreshold() {
return mGestureStrokeAngleThreshold;
}
public void setGestureStrokeAngleThreshold(float gestureStrokeAngleThreshold) {
mGestureStrokeAngleThreshold = gestureStrokeAngleThreshold;
}
public boolean isEventsInterceptionEnabled() {
return mInterceptEvents;
}
public void setEventsInterceptionEnabled(boolean enabled) {
mInterceptEvents = enabled;
}
public Gesture getGesture() {
return mCurrentGesture;
}
public void setGesture(Gesture gesture) {
if (mCurrentGesture != null) {
clear(false);
}
setCurrentColor(mCertainGestureColor);
mCurrentGesture = gesture;
if (gesture != null) {
if (mBitmapCanvas != null) {
gesture.draw(mBitmapCanvas, mGesturePaint);
invalidate();
}
}
}
final Path path = mCurrentGesture.toPath();
final RectF bounds = new RectF();
path.computeBounds(bounds, true);
private void init() {
mGesturePaint = new Paint();
mPath.rewind();
mPath.addPath(path, (getWidth() - bounds.width()) / 2.0f,
(getHeight() - bounds.height()) / 2.0f);
final Paint gesturePaint = mGesturePaint;
gesturePaint.setAntiAlias(GESTURE_RENDERING_ANTIALIAS);
gesturePaint.setColor(mCertainGestureColor);
gesturePaint.setStyle(Paint.Style.STROKE);
gesturePaint.setStrokeJoin(Paint.Join.ROUND);
gesturePaint.setStrokeCap(Paint.Cap.ROUND);
gesturePaint.setStrokeWidth(mGestureStroke);
gesturePaint.setDither(DITHER_FLAG);
}
@Override
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
super.onSizeChanged(width, height, oldWidth, oldHeight);
if (width <= 0 || height <= 0) {
return;
}
int targetWidth = width > oldWidth ? width : oldWidth;
int targetHeight = height > oldHeight ? height : oldHeight;
if (mBitmap != null) mBitmap.recycle();
mBitmap = Bitmap.createBitmap(targetWidth, targetHeight, Bitmap.Config.ARGB_8888);
if (mBitmapCanvas != null) {
mBitmapCanvas.setBitmap(mBitmap);
} else {
mBitmapCanvas = new Canvas(mBitmap);
}
mBitmapCanvas.drawColor(TRANSPARENT_BACKGROUND);
if (mCurrentGesture != null) {
mCurrentGesture.draw(mBitmapCanvas, mGesturePaint);
}
invalidate();
}
public void addOnGestureListener(OnGestureListener listener) {
@ -257,100 +273,172 @@ public class GestureOverlayView extends View {
mOnGestureListeners.clear();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// draw double buffer
if (mIsFadingOut) {
mBitmapPaint.setAlpha((int) (255 * mFadingAlpha));
canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
} else {
mBitmapPaint.setAlpha(255);
canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
public void addOnGesturePerformedListener(OnGesturePerformedListener listener) {
mOnGesturePerformedListeners.add(listener);
if (mOnGesturePerformedListeners.size() > 0) {
mHandleGestureActions = true;
}
// draw the current stroke
canvas.drawPath(mPath, mGesturePaint);
}
/**
* Clear up the overlay
*
* @param fadeOut whether the gesture on the overlay should fade out
* gradually or disappear immediately
*/
public void clear(boolean fadeOut) {
if (fadeOut) {
public void removeOnGesturePerformedListener(OnGesturePerformedListener listener) {
mOnGesturePerformedListeners.remove(listener);
if (mOnGesturePerformedListeners.size() <= 0) {
mHandleGestureActions = false;
}
}
public void removeAllOnGesturePerformedListeners() {
mOnGesturePerformedListeners.clear();
mHandleGestureActions = false;
}
public boolean isGesturing() {
return mIsGesturing;
}
private void setCurrentColor(int color) {
mCurrentColor = color;
if (mFadingHasStarted) {
setPaintAlpha((int) (255 * mFadingAlpha));
} else {
setPaintAlpha(255);
}
invalidate();
}
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
if (mCurrentGesture != null) {
canvas.drawPath(mPath, mGesturePaint);
}
}
private void setPaintAlpha(int alpha) {
alpha += alpha >> 7;
final int baseAlpha = mCurrentColor >>> 24;
final int useAlpha = baseAlpha * alpha >> 8;
mGesturePaint.setColor((mCurrentColor << 8 >>> 8) | (useAlpha << 24));
}
public void clear(boolean animated) {
clear(animated, false);
}
private void clear(boolean animated, boolean fireActionPerformed) {
setPaintAlpha(255);
if (animated && mCurrentGesture != null) {
mFadingAlpha = 1.0f;
mIsFadingOut = true;
mFadingHasStarted = false;
mFadingOut.fireActionPerformed = fireActionPerformed;
removeCallbacks(mFadingOut);
mFadingStart = AnimationUtils.currentAnimationTimeMillis() + mFadeOffset;
postDelayed(mFadingOut, mFadeOffset);
} else {
mPath.rewind();
mCurrentGesture = null;
if (mBitmap != null) {
mBitmap.eraseColor(TRANSPARENT_BACKGROUND);
invalidate();
}
}
}
public void cancelFadingOut() {
public void cancelClearAnimation() {
setPaintAlpha(255);
mIsFadingOut = false;
mFadingHasStarted = false;
removeCallbacks(mFadingOut);
mPath.rewind();
mCurrentGesture = null;
}
public void cancelGesture() {
mIsListeningForGestures = false;
// add the stroke to the current gesture
mCurrentGesture.addStroke(new GestureStroke(mStrokeBuffer));
// pass the event to handlers
final long now = SystemClock.uptimeMillis();
final MotionEvent event = MotionEvent.obtain(now, now,
MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
final int count = listeners.size();
for (int i = 0; i < count; i++) {
listeners.get(i).onGestureCancelled(this, event);
}
event.recycle();
clear(false);
mIsGesturing = false;
mStrokeBuffer.clear();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!isEnabled()) {
protected void onDetachedFromWindow() {
cancelClearAnimation();
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
if (isEnabled()) {
boolean cancelDispatch = (mIsGesturing || (mCurrentGesture != null &&
mCurrentGesture.getStrokesCount() > 0)) && mInterceptEvents;
processEvent(event);
if (cancelDispatch) {
event.setAction(MotionEvent.ACTION_CANCEL);
}
super.dispatchTouchEvent(event);
return true;
}
processEvent(event);
return true;
return super.dispatchTouchEvent(event);
}
public void processEvent(MotionEvent event) {
private boolean processEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Rect rect = touchStart(event);
invalidate(rect);
break;
touchStart(event);
invalidate();
return true;
case MotionEvent.ACTION_MOVE:
rect = touchMove(event);
if (rect != null) {
invalidate(rect);
if (mIsListeningForGestures) {
Rect rect = touchMove(event);
if (rect != null) {
invalidate(rect);
}
return true;
}
break;
case MotionEvent.ACTION_UP:
touchUp(event, false);
invalidate();
if (mIsListeningForGestures) {
touchUp(event, false);
invalidate();
return true;
}
break;
case MotionEvent.ACTION_CANCEL:
touchUp(event, true);
invalidate();
break;
if (mIsListeningForGestures) {
touchUp(event, true);
invalidate();
return true;
}
}
return false;
}
private Rect touchStart(MotionEvent event) {
private void touchStart(MotionEvent event) {
mIsListeningForGestures = true;
// pass the event to handlers
final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
final int count = listeners.size();
for (int i = 0; i < count; i++) {
OnGestureListener listener = listeners.get(i);
listener.onGestureStarted(this, event);
}
// if there is fading out going on, stop it.
if (mIsFadingOut) {
mIsFadingOut = false;
removeCallbacks(mFadingOut);
mBitmap.eraseColor(TRANSPARENT_BACKGROUND);
mCurrentGesture = null;
listeners.get(i).onGestureStarted(this, event);
}
float x = event.getX();
@ -359,22 +447,39 @@ public class GestureOverlayView extends View {
mX = x;
mY = y;
mTotalLength = 0;
mIsGesturing = false;
if (mGestureStrokeType == GESTURE_STROKE_TYPE_SINGLE) {
if (mHandleGestureActions) setCurrentColor(mUncertainGestureColor);
mCurrentGesture = null;
mPath.rewind();
} else if (mCurrentGesture == null || mCurrentGesture.getStrokesCount() == 0) {
if (mHandleGestureActions) setCurrentColor(mUncertainGestureColor);
}
// if there is fading out going on, stop it.
if (mFadingHasStarted) {
cancelClearAnimation();
} else if (mIsFadingOut) {
setPaintAlpha(255);
mIsFadingOut = false;
mFadingHasStarted = false;
removeCallbacks(mFadingOut);
}
if (mCurrentGesture == null) {
mCurrentGesture = new Gesture();
}
mPointBuffer.add(new GesturePoint(x, y, event.getEventTime()));
mPath.rewind();
mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
mPath.moveTo(x, y);
mInvalidRect.set((int) x - mInvalidateExtraBorder, (int) y - mInvalidateExtraBorder,
(int) x + mInvalidateExtraBorder, (int) y + mInvalidateExtraBorder);
final int border = mInvalidateExtraBorder;
mInvalidRect.set((int) x - border, (int) y - border, (int) x + border, (int) y + border);
mCurveEndX = x;
mCurveEndY = y;
return mInvalidRect;
}
private Rect touchMove(MotionEvent event) {
@ -393,36 +498,28 @@ public class GestureOverlayView extends View {
areaToRefresh = mInvalidRect;
// start with the curve end
areaToRefresh.set(
(int) mCurveEndX - mInvalidateExtraBorder,
(int) mCurveEndY - mInvalidateExtraBorder,
(int) mCurveEndX + mInvalidateExtraBorder,
(int) mCurveEndY + mInvalidateExtraBorder);
final int border = mInvalidateExtraBorder;
areaToRefresh.set((int) mCurveEndX - border, (int) mCurveEndY - border,
(int) mCurveEndX + border, (int) mCurveEndY + border);
mCurveEndX = (x + previousX) / 2;
mCurveEndY = (y + previousY) / 2;
float cX = mCurveEndX = (x + previousX) / 2;
float cY = mCurveEndY = (y + previousY) / 2;
mPath.quadTo(previousX, previousY, mCurveEndX, mCurveEndY);
mPath.quadTo(previousX, previousY, cX, cY);
// union with the control point of the new curve
areaToRefresh.union(
(int) previousX - mInvalidateExtraBorder,
(int) previousY - mInvalidateExtraBorder,
(int) previousX + mInvalidateExtraBorder,
(int) previousY + mInvalidateExtraBorder);
areaToRefresh.union((int) previousX - border, (int) previousY - border,
(int) previousX + border, (int) previousY + border);
// union with the end point of the new curve
areaToRefresh.union(
(int) mCurveEndX - mInvalidateExtraBorder,
(int) mCurveEndY - mInvalidateExtraBorder,
(int) mCurveEndX + mInvalidateExtraBorder,
(int) mCurveEndY + mInvalidateExtraBorder);
areaToRefresh.union((int) cX - border, (int) cY - border,
(int) cX + border, (int) cY + border);
mX = x;
mY = y;
}
mPointBuffer.add(new GesturePoint(x, y, event.getEventTime()));
mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
// pass the event to handlers
final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
@ -430,24 +527,48 @@ public class GestureOverlayView extends View {
for (int i = 0; i < count; i++) {
listeners.get(i).onGesture(this, event);
}
if (mHandleGestureActions && !mIsGesturing) {
mTotalLength += (float) Math.sqrt(dx * dx + dy * dy);
if (mTotalLength > mGestureStrokeLengthThreshold) {
final OrientedBoundingBox box =
GestureUtilities.computeOrientedBoundingBox(mStrokeBuffer);
float angle = Math.abs(box.orientation);
if (angle > 90) {
angle = 180 - angle;
}
if (box.squareness > mGestureStrokeSquarenessTreshold ||
angle < mGestureStrokeAngleThreshold) {
mIsGesturing = true;
setCurrentColor(mCertainGestureColor);
}
}
}
return areaToRefresh;
}
private void touchUp(MotionEvent event, boolean cancel) {
// add the stroke to the current gesture
mCurrentGesture.addStroke(new GestureStroke(mPointBuffer));
mIsListeningForGestures = false;
// add the stroke to the double buffer
mBitmapCanvas.drawPath(mPath, mGesturePaint);
// add the stroke to the current gesture
mCurrentGesture.addStroke(new GestureStroke(mStrokeBuffer));
if (!cancel) {
// pass the event to handlers
final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
final int count = listeners.size();
int count = listeners.size();
for (int i = 0; i < count; i++) {
listeners.get(i).onGestureEnded(this, event);
}
if (mHandleGestureActions) {
clear(true, mIsGesturing);
}
} else {
// pass the event to handlers
final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
@ -455,15 +576,52 @@ public class GestureOverlayView extends View {
for (int i = 0; i < count; i++) {
listeners.get(i).onGestureCancelled(this, event);
}
clear(false);
}
mPath.rewind();
mPointBuffer.clear();
mIsGesturing = false;
mStrokeBuffer.clear();
}
private class FadeOutRunnable implements Runnable {
boolean fireActionPerformed;
public void run() {
if (mIsFadingOut) {
final long now = AnimationUtils.currentAnimationTimeMillis();
final long duration = now - mFadingStart;
if (duration > mFadeDuration) {
if (fireActionPerformed) {
final ArrayList<OnGesturePerformedListener> actionListeners =
mOnGesturePerformedListeners;
final int count = actionListeners.size();
for (int i = 0; i < count; i++) {
actionListeners.get(i).onGesturePerformed(GestureOverlayView.this,
mCurrentGesture);
}
}
mIsFadingOut = false;
mFadingHasStarted = false;
mPath.rewind();
mCurrentGesture = null;
setPaintAlpha(255);
} else {
mFadingHasStarted = true;
float interpolatedTime = Math.max(0.0f,
Math.min(1.0f, duration / (float) mFadeDuration));
mFadingAlpha = 1.0f - mInterpolator.getInterpolation(interpolatedTime);
setPaintAlpha((int) (255 * mFadingAlpha));
postDelayed(this, FADE_ANIMATION_RATE);
}
invalidate();
}
}
}
/**
* An interface for processing gesture events
*/
public static interface OnGestureListener {
void onGestureStarted(GestureOverlayView overlay, MotionEvent event);
@ -473,4 +631,8 @@ public class GestureOverlayView extends View {
void onGestureCancelled(GestureOverlayView overlay, MotionEvent event);
}
public static interface OnGesturePerformedListener {
void onGesturePerformed(GestureOverlayView overlay, Gesture gesture);
}
}

View File

@ -89,39 +89,51 @@ public class GestureStroke {
*/
void draw(Canvas canvas, Paint paint) {
if (mCachedPath == null) {
final float[] localPoints = points;
final int count = localPoints.length;
Path path = null;
float mX = 0;
float mY = 0;
for (int i = 0; i < count; i += 2) {
float x = localPoints[i];
float y = localPoints[i + 1];
if (path == null) {
path = new Path();
path.moveTo(x, y);
mX = x;
mY = y;
} else {
float dx = Math.abs(x - mX);
float dy = Math.abs(y - mY);
if (dx >= 3 || dy >= 3) {
path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
mX = x;
mY = y;
}
}
}
mCachedPath = path;
makePath();
}
canvas.drawPath(mCachedPath, paint);
}
public Path getPath() {
if (mCachedPath == null) {
makePath();
}
return mCachedPath;
}
private void makePath() {
final float[] localPoints = points;
final int count = localPoints.length;
Path path = null;
float mX = 0;
float mY = 0;
for (int i = 0; i < count; i += 2) {
float x = localPoints[i];
float y = localPoints[i + 1];
if (path == null) {
path = new Path();
path.moveTo(x, y);
mX = x;
mY = y;
} else {
float dx = Math.abs(x - mX);
float dy = Math.abs(y - mY);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
mX = x;
mY = y;
}
}
}
mCachedPath = path;
}
/**
* Convert the stroke to a Path based on the number of points
*
@ -158,8 +170,7 @@ public class GestureStroke {
} else {
float dx = Math.abs(x - mX);
float dy = Math.abs(y - mY);
if (dx >= TOUCH_TOLERANCE ||
dy >= TOUCH_TOLERANCE) {
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
mX = x;
mY = y;

View File

@ -388,7 +388,7 @@ final class GestureUtilities {
} else { // -PI<alpha<PI
angle = (float) Math.atan2(targetVector[1], targetVector[0]);
angle = (float) (180 * angle / Math.PI);
android.graphics.Matrix trans = new android.graphics.Matrix();
Matrix trans = new Matrix();
trans.setRotate(-angle);
trans.mapPoints(points);
}

View File

@ -1,178 +0,0 @@
/*
* Copyright (C) 2008-2009 The Android Open Source Project
*
* 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.
*/
package android.gesture;
import android.view.MotionEvent;
import android.view.View;
import java.util.ArrayList;
import java.lang.ref.WeakReference;
/**
* TouchThroughGesturing implements the interaction behavior that allows a user
* to gesture over a regular UI widget such as ListView and at the same time,
* still allows a user to perform basic interactions (clicking, scrolling and panning)
* with the underlying widget.
*/
public class TouchThroughGestureListener implements GestureOverlayView.OnGestureListener {
public static final int SINGLE_STROKE = 0;
public static final int MULTIPLE_STROKE = 1;
// TODO: Add properties for all these
private static final float STROKE_LENGTH_THRESHOLD = 30;
private static final float SQUARENESS_THRESHOLD = 0.275f;
private static final float ANGLE_THRESHOLD = 40;
private boolean mIsGesturing = false;
private float mTotalLength;
private float mX;
private float mY;
private WeakReference<View> mModel;
private int mGestureType = SINGLE_STROKE;
// TODO: Use WeakReferences
private final ArrayList<OnGesturePerformedListener> mPerformedListeners =
new ArrayList<OnGesturePerformedListener>();
private boolean mStealEvents = false;
public TouchThroughGestureListener(View model) {
this(model, true);
}
public TouchThroughGestureListener(View model, boolean stealEvents) {
mModel = new WeakReference<View>(model);
mStealEvents = stealEvents;
}
/**
*
* @param type SINGLE_STROKE or MULTIPLE_STROKE
*/
public void setGestureType(int type) {
mGestureType = type;
}
public void onGestureStarted(GestureOverlayView overlay, MotionEvent event) {
if (mGestureType == MULTIPLE_STROKE) {
overlay.cancelFadingOut();
}
mX = event.getX();
mY = event.getY();
mTotalLength = 0;
mIsGesturing = false;
if (mGestureType == SINGLE_STROKE || overlay.getCurrentGesture() == null
|| overlay.getCurrentGesture().getStrokesCount() == 0) {
overlay.setGestureDrawingColor(overlay.getUncertainGestureColor());
}
dispatchEventToModel(event);
}
private void dispatchEventToModel(MotionEvent event) {
View v = mModel.get();
if (v != null) v.dispatchTouchEvent(event);
}
public void onGesture(GestureOverlayView overlay, MotionEvent event) {
//noinspection PointlessBooleanExpression
if (!mStealEvents) {
dispatchEventToModel(event);
}
if (mIsGesturing) {
return;
}
final float x = event.getX();
final float y = event.getY();
final float dx = x - mX;
final float dy = y - mY;
mTotalLength += (float) Math.sqrt(dx * dx + dy * dy);
mX = x;
mY = y;
if (mTotalLength > STROKE_LENGTH_THRESHOLD) {
final OrientedBoundingBox box =
GestureUtilities.computeOrientedBoundingBox(overlay.getCurrentStroke());
float angle = Math.abs(box.orientation);
if (angle > 90) {
angle = 180 - angle;
}
if (box.squareness > SQUARENESS_THRESHOLD || angle < ANGLE_THRESHOLD) {
mIsGesturing = true;
overlay.setGestureDrawingColor(overlay.getGestureColor());
if (mStealEvents) {
event = MotionEvent.obtain(event.getDownTime(), System.currentTimeMillis(),
MotionEvent.ACTION_CANCEL, x, y, event.getPressure(), event.getSize(),
event.getMetaState(), event.getXPrecision(), event.getYPrecision(),
event.getDeviceId(), event.getEdgeFlags());
}
}
}
if (mStealEvents) {
dispatchEventToModel(event);
}
}
public void onGestureEnded(GestureOverlayView overlay, MotionEvent event) {
if (mIsGesturing) {
overlay.clear(true);
final ArrayList<OnGesturePerformedListener> listeners = mPerformedListeners;
final int count = listeners.size();
for (int i = 0; i < count; i++) {
listeners.get(i).onGesturePerformed(overlay, overlay.getCurrentGesture());
}
} else {
dispatchEventToModel(event);
overlay.clear(false);
}
}
public void onGestureCancelled(GestureOverlayView overlay, MotionEvent event) {
overlay.clear(mIsGesturing);
if (!mIsGesturing) {
dispatchEventToModel(event);
}
}
public void addOnGestureActionListener(OnGesturePerformedListener listener) {
mPerformedListeners.add(listener);
}
public void removeOnGestureActionListener(OnGesturePerformedListener listener) {
mPerformedListeners.remove(listener);
}
public boolean isGesturing() {
return mIsGesturing;
}
public static interface OnGesturePerformedListener {
public void onGesturePerformed(GestureOverlayView overlay, Gesture gesture);
}
}

View File

@ -49,7 +49,6 @@ import android.view.inputmethod.InputConnectionWrapper;
import android.view.inputmethod.InputMethodManager;
import android.view.ContextMenu.ContextMenuInfo;
import android.gesture.GestureOverlayView;
import android.gesture.TouchThroughGestureListener;
import android.gesture.Gesture;
import android.gesture.LetterRecognizer;
import android.gesture.Prediction;
@ -472,7 +471,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
private ViewTreeObserver.OnGlobalLayoutListener mGesturesLayoutListener;
private boolean mGlobalLayoutListenerAddedGestures;
private boolean mInstallGesturesOverlay;
private TouchThroughGestureListener mGesturesListener;
private boolean mPreviousGesturing;
private boolean mGlobalLayoutListenerAddedFilter;
@ -736,10 +734,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mGesturesPopup = p;
mGesturesOverlay.removeAllOnGestureListeners();
mGesturesListener = new TouchThroughGestureListener(null);
mGesturesListener.setGestureType(TouchThroughGestureListener.MULTIPLE_STROKE);
mGesturesListener.addOnGestureActionListener(new GesturesProcessor());
mGesturesOverlay.addOnGestureListener(mGesturesListener);
mGesturesOverlay.setGestureStrokeType(GestureOverlayView.GESTURE_STROKE_TYPE_MULTIPLE);
mGesturesOverlay.addOnGesturePerformedListener(new GesturesProcessor());
mPreviousGesturing = false;
}
@ -756,19 +752,23 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mGestures != GESTURES_NONE) {
mGesturesOverlay.processEvent(ev);
if (ev.getAction() != MotionEvent.ACTION_DOWN || mFastScroller == null ||
!mFastScroller.isPointInside(ev.getX(), ev.getY())) {
final boolean isGesturing = mGesturesListener.isGesturing();
mGesturesOverlay.dispatchTouchEvent(ev);
if (!isGesturing) {
mPreviousGesturing = isGesturing;
return super.dispatchTouchEvent(ev);
} else if (!mPreviousGesturing){
mPreviousGesturing = isGesturing;
final MotionEvent event = MotionEvent.obtain(ev);
event.setAction(MotionEvent.ACTION_CANCEL);
super.dispatchTouchEvent(event);
return true;
final boolean isGesturing = mGesturesOverlay.isGesturing();
if (!isGesturing) {
mPreviousGesturing = isGesturing;
return super.dispatchTouchEvent(ev);
} else if (!mPreviousGesturing){
mPreviousGesturing = isGesturing;
final MotionEvent event = MotionEvent.obtain(ev);
event.setAction(MotionEvent.ACTION_CANCEL);
super.dispatchTouchEvent(event);
return true;
}
}
}
@ -2130,13 +2130,13 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (mFastScroller != null) {
boolean intercepted = mFastScroller.onTouchEvent(ev);
if (intercepted) {
return true;
}
}
final int action = ev.getAction();
final int x = (int) ev.getX();
final int y = (int) ev.getY();
@ -3848,8 +3848,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
}
private class GesturesProcessor implements
TouchThroughGestureListener.OnGesturePerformedListener {
private class GesturesProcessor implements GestureOverlayView.OnGesturePerformedListener {
private static final double SCORE_THRESHOLD = 0.1;

View File

@ -402,8 +402,7 @@ class FastScroller {
boolean onInterceptTouchEvent(MotionEvent ev) {
if (mState > STATE_NONE && ev.getAction() == MotionEvent.ACTION_DOWN) {
if (ev.getX() > mList.getWidth() - mThumbW && ev.getY() >= mThumbY &&
ev.getY() <= mThumbY + mThumbH) {
if (isPointInside(ev.getX(), ev.getY())) {
setState(STATE_DRAGGING);
return true;
}
@ -415,11 +414,11 @@ class FastScroller {
if (mState == STATE_NONE) {
return false;
}
if (me.getAction() == MotionEvent.ACTION_DOWN) {
if (me.getX() > mList.getWidth() - mThumbW
&& me.getY() >= mThumbY
&& me.getY() <= mThumbY + mThumbH) {
final int action = me.getAction();
if (action == MotionEvent.ACTION_DOWN) {
if (isPointInside(me.getX(), me.getY())) {
setState(STATE_DRAGGING);
if (mListAdapter == null && mList != null) {
getSectionsFromIndexer();
@ -428,7 +427,7 @@ class FastScroller {
cancelFling();
return true;
}
} else if (me.getAction() == MotionEvent.ACTION_UP) {
} else if (action == MotionEvent.ACTION_UP) {
if (mState == STATE_DRAGGING) {
setState(STATE_VISIBLE);
final Handler handler = mHandler;
@ -436,7 +435,7 @@ class FastScroller {
handler.postDelayed(mScrollFade, 1000);
return true;
}
} else if (me.getAction() == MotionEvent.ACTION_MOVE) {
} else if (action == MotionEvent.ACTION_MOVE) {
if (mState == STATE_DRAGGING) {
final int viewHeight = mList.getHeight();
// Jitter
@ -460,6 +459,10 @@ class FastScroller {
return false;
}
boolean isPointInside(float x, float y) {
return x > mList.getWidth() - mThumbW && y >= mThumbY && y <= mThumbY + mThumbH;
}
public class ScrollFade implements Runnable {
long mStartTime;

View File

@ -353,25 +353,24 @@ public class FrameLayout extends ViewGroup {
if (mForeground != null) {
final Drawable foreground = mForeground;
if (mForegroundBoundsChanged) {
mForegroundBoundsChanged = false;
if (foreground != null) {
final Rect selfBounds = mSelfBounds;
final Rect overlayBounds = mOverlayBounds;
final Rect selfBounds = mSelfBounds;
final Rect overlayBounds = mOverlayBounds;
final int w = mRight-mLeft;
final int h = mBottom-mTop;
if (mForegroundInPadding) {
selfBounds.set(0, 0, w, h);
} else {
selfBounds.set(mPaddingLeft, mPaddingTop, w - mPaddingRight, h - mPaddingBottom);
}
final int w = mRight-mLeft;
final int h = mBottom-mTop;
Gravity.apply(mForegroundGravity, foreground.getIntrinsicWidth(),
foreground.getIntrinsicHeight(), selfBounds, overlayBounds);
foreground.setBounds(overlayBounds);
if (mForegroundInPadding) {
selfBounds.set(0, 0, w, h);
} else {
selfBounds.set(mPaddingLeft, mPaddingTop, w - mPaddingRight, h - mPaddingBottom);
}
Gravity.apply(mForegroundGravity, foreground.getIntrinsicWidth(),
foreground.getIntrinsicHeight(), selfBounds, overlayBounds);
foreground.setBounds(overlayBounds);
}
foreground.draw(canvas);

View File

@ -2091,6 +2091,22 @@
<!-- Duration, in milliseconds, of the fade out effect after the user is done
drawing a gesture. -->
<attr name="fadeDuration" format="integer" />
<!-- Defines the type of strokes that define a gesture. -->
<attr name="gestureStrokeType">
<!-- A gesture is made of only one stroke. -->
<enum name="single" value="0" />
<!-- A gesture is made of multiple strokes. -->
<enum name="multiple" value="1" />
</attr>
<!-- Minimum length of a stroke before it is recognized as a gesture. -->
<attr name="gestureStrokeLengthThreshold" format="float" />
<!-- Squareness threshold of a stroke before it is recognized as a gesture. -->
<attr name="gestureStrokeSquarenessThreshold" format="float" />
<!-- Minimum curve angle a stroke must contain before it is recognized as a gesture. -->
<attr name="gestureStrokeAngleThreshold" format="float" />
<!-- Defines whether the overlay should intercept the motion events when a gesture
is recognized. -->
<attr name="eventsInterceptionEnabled" format="boolean" />
</declare-styleable>
<!-- ======================================= -->

View File

@ -1107,6 +1107,11 @@
<public type="attr" name="fadeOffset" />
<public type="attr" name="fadeDuration" />
<public type="attr" name="gestures" />
<public type="attr" name="gestureStrokeType" />
<public type="attr" name="gestureStrokeLengthThreshold" />
<public type="attr" name="gestureStrokeSquarenessThreshold" />
<public type="attr" name="gestureStrokeAngleThreshold" />
<public type="attr" name="eventsInterceptionEnabled" />
<public-padding type="attr" name="donut_resource_pad" end="0x0101029f" />

View File

@ -174,14 +174,18 @@
<style name="Widget.GestureOverlayView">
<item name="android:gestureStrokeWidth">12.0</item>
<item name="android:gestureColor">#ffffff00</item>
<item name="android:uncertainGestureColor">#3cffff00</item>
<item name="android:fadeOffset">300</item>
<item name="android:fadeDuration">300</item>
<item name="android:uncertainGestureColor">#48ffff00</item>
<item name="android:fadeOffset">420</item>
<item name="android:fadeDuration">150</item>
<item name="android:gestureStrokeLengthThreshold">30.0</item>
<item name="android:gestureStrokeSquarenessThreshold">0.275</item>
<item name="android:gestureStrokeAngleThreshold">40.0</item>
<item name="android:eventsInterceptionEnabled">true</item>
</style>
<style name="Widget.GestureOverlayView.White">
<item name="android:gestureColor">#ff00ff00</item>
<item name="android:uncertainGestureColor">#3c00ff00</item>
<item name="android:uncertainGestureColor">#4800ff00</item>
</style>
<style name="Widget.Button">

View File

@ -17,7 +17,8 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
android:layout_height="fill_parent">
<Spinner
android:id="@+id/spinner"
android:layout_width="fill_parent"
@ -29,6 +30,6 @@
android:id="@+id/drawingpad"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"/>
android:layout_weight="1" />
</LinearLayout>

View File

@ -17,7 +17,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
android:layout_height="fill_parent">
<Spinner
android:id="@+id/spinner"
@ -30,7 +30,7 @@
android:id="@+id/drawingpad"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"/>
android:layout_weight="1" />
<LinearLayout
android:orientation="horizontal"

View File

@ -14,7 +14,14 @@
limitations under the License.
-->
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/list"
<android.gesture.GestureOverlayView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/overlay"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
android:layout_height="fill_parent">
<ListView
android:id="@+id/list"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</android.gesture.GestureOverlayView>

View File

@ -25,7 +25,6 @@ import android.os.Bundle;
import android.provider.Contacts.People;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.AdapterView;
import android.widget.ListView;
@ -34,16 +33,12 @@ import android.gesture.Gesture;
import android.gesture.GestureOverlayView;
import android.gesture.LetterRecognizer;
import android.gesture.Prediction;
import android.gesture.TouchThroughGestureListener;
import java.util.ArrayList;
public class ContactListGestureOverlay extends Activity {
private static final String LOGTAG = "ContactListGestureOverlay";
private static final String LOG_TAG = "ContactListGestureOverlay";
private static final String SORT_ORDER = People.DISPLAY_NAME + " COLLATE LOCALIZED ASC";
private static final String[] CONTACTS_PROJECTION = new String[] {
People._ID, // 0
People.DISPLAY_NAME, // 1
@ -51,11 +46,9 @@ public class ContactListGestureOverlay extends Activity {
private ContactAdapter mContactAdapter;
private TouchThroughGestureListener mGestureProcessor;
private LetterRecognizer mRecognizer;
private ListView mContactList;
private LetterRecognizer mRecognizer;
private GestureOverlayView mOverlay;
@Override
public void onCreate(Bundle savedInstanceState) {
@ -63,10 +56,10 @@ public class ContactListGestureOverlay extends Activity {
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setContentView(R.layout.overlaydemo);
setProgressBarIndeterminateVisibility(true);
// create a letter recognizer
mRecognizer = LetterRecognizer.getLetterRecognizer(this, LetterRecognizer.RECOGNIZER_LATIN_LOWERCASE);
mRecognizer = LetterRecognizer.getLetterRecognizer(this,
LetterRecognizer.RECOGNIZER_LATIN_LOWERCASE);
mOverlay = (GestureOverlayView) findViewById(R.id.overlay);
// load the contact list
mContactList = (ListView) findViewById(R.id.list);
@ -74,13 +67,14 @@ public class ContactListGestureOverlay extends Activity {
mContactList.setTextFilterEnabled(true);
mContactList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
if (!mGestureProcessor.isGesturing()) {
if (!mOverlay.isGesturing()) {
Intent intent = new Intent(Intent.ACTION_VIEW, ContentUris.withAppendedId(
People.CONTENT_URI, id));
startActivity(intent);
}
}
});
ContentResolver resolver = getContentResolver();
Cursor cursor = resolver.query(People.CONTENT_URI, CONTACTS_PROJECTION, null, null,
SORT_ORDER);
@ -91,21 +85,16 @@ public class ContactListGestureOverlay extends Activity {
mContactAdapter = new ContactAdapter(this, list);
mContactList.setAdapter(mContactAdapter);
setProgressBarIndeterminateVisibility(false);
// add a gesture overlay on top of the ListView
GestureOverlayView overlay = new GestureOverlayView(this);
mGestureProcessor = new TouchThroughGestureListener(mContactList);
mGestureProcessor.setGestureType(TouchThroughGestureListener.MULTIPLE_STROKE);
mGestureProcessor.addOnGestureActionListener(new TouchThroughGestureListener.OnGesturePerformedListener() {
mOverlay.setGestureStrokeType(GestureOverlayView.GESTURE_STROKE_TYPE_MULTIPLE);
mOverlay.addOnGesturePerformedListener(new GestureOverlayView.OnGesturePerformedListener() {
public void onGesturePerformed(GestureOverlayView overlay, Gesture gesture) {
ArrayList<Prediction> predictions = mRecognizer.recognize(gesture);
if (!predictions.isEmpty()) {
Log.v(LOGTAG, "1st Prediction : " + predictions.get(0).name +
Log.v(LOG_TAG, "1st Prediction : " + predictions.get(0).name +
" @" + predictions.get(0).score);
Log.v(LOGTAG, "2nd Prediction : " + predictions.get(1).name +
Log.v(LOG_TAG, "2nd Prediction : " + predictions.get(1).name +
" @" + predictions.get(1).score);
Log.v(LOGTAG, "3rd Prediction : " + predictions.get(2).name +
Log.v(LOG_TAG, "3rd Prediction : " + predictions.get(2).name +
" @" + predictions.get(2).score);
int index = mContactAdapter.search(predictions.get(0).name);
if (index != -1) {
@ -114,9 +103,5 @@ public class ContactListGestureOverlay extends Activity {
}
}
});
overlay.addOnGestureListener(mGestureProcessor);
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT);
this.addContentView(overlay, params);
}
}

View File

@ -47,8 +47,9 @@ public class GestureEntry extends Activity {
private static final String PARCEL_KEY = "gesture";
static final String GESTURE_FILE_NAME = Environment.getExternalStorageDirectory().getAbsolutePath()
+ File.separator + "demo_library.gestures";
static final String GESTURE_FILE_NAME =
Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator +
"demo_library.gestures";
private static final int DIALOG_NEW_ENTRY = 1;
@ -82,7 +83,7 @@ public class GestureEntry extends Activity {
// correct the recognition result by adding the new example
if (!mChangedByRecognizer) {
mGestureLibrary.addGesture(parent.getSelectedItem().toString(), mGesturePad
.getCurrentGesture());
.getGesture());
} else {
mChangedByRecognizer = false;
}
@ -99,7 +100,7 @@ public class GestureEntry extends Activity {
mGesturePad.setBackgroundColor(Color.BLACK);
mGesturePad.addOnGestureListener(new GestureOverlayView.OnGestureListener() {
public void onGestureEnded(GestureOverlayView overlay, MotionEvent event) {
recognize(overlay.getCurrentGesture());
recognize(overlay.getGesture());
}
public void onGesture(GestureOverlayView overlay, MotionEvent event) {
@ -116,7 +117,7 @@ public class GestureEntry extends Activity {
if (savedInstanceState != null) {
Gesture gesture = (Gesture) savedInstanceState.getParcelable(PARCEL_KEY);
if (gesture != null) {
mGesturePad.setCurrentGesture(gesture);
mGesturePad.setGesture(gesture);
}
}
}
@ -133,7 +134,7 @@ public class GestureEntry extends Activity {
.findViewById(R.id.gesturename_edit);
String text = edittext.getText().toString().trim();
if (text.length() > 0) {
mGestureLibrary.addGesture(text, mGesturePad.getCurrentGesture());
mGestureLibrary.addGesture(text, mGesturePad.getGesture());
}
}
}).setNegativeButton(R.string.newgesture_dialog_cancel,
@ -157,7 +158,7 @@ public class GestureEntry extends Activity {
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case NEW_ID:
if (mGesturePad.getCurrentGesture() != null) {
if (mGesturePad.getGesture() != null) {
showDialog(DIALOG_NEW_ENTRY);
}
break;
@ -190,7 +191,7 @@ public class GestureEntry extends Activity {
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Gesture gesture = mGesturePad.getCurrentGesture();
Gesture gesture = mGesturePad.getGesture();
if (gesture != null) {
outState.putParcelable(PARCEL_KEY, gesture);
}

View File

@ -78,7 +78,7 @@ public class GestureLibViewer extends Activity {
mCurrentGestureIndex--;
}
gesture = mGestures.get(mCurrentGestureIndex);
mGesturePad.setCurrentGesture(gesture);
mGesturePad.setGesture(gesture);
mGesturePad.invalidate();
}
}
@ -109,7 +109,7 @@ public class GestureLibViewer extends Activity {
mGestures = mGesureLibrary.getGestures(list.get(0));
mCurrentGestureIndex = 0;
Gesture gesture = mGestures.get(mCurrentGestureIndex);
mGesturePad.setCurrentGesture(gesture);
mGesturePad.setGesture(gesture);
}
mGestureCategory.setOnItemSelectedListener(new OnItemSelectedListener() {
@ -118,7 +118,7 @@ public class GestureLibViewer extends Activity {
if (!mGestures.isEmpty()) {
mCurrentGestureIndex = 0;
Gesture gesture = mGestures.get(mCurrentGestureIndex);
mGesturePad.setCurrentGesture(gesture);
mGesturePad.setGesture(gesture);
}
mGesturePad.invalidate();
}
@ -139,7 +139,7 @@ public class GestureLibViewer extends Activity {
}
mCurrentGestureIndex++;
Gesture gesture = mGestures.get(mCurrentGestureIndex);
mGesturePad.setCurrentGesture(gesture);
mGesturePad.setGesture(gesture);
mGesturePad.invalidate();
}
});
@ -150,7 +150,7 @@ public class GestureLibViewer extends Activity {
if (mCurrentGestureIndex >= 1 && !mGestures.isEmpty()) {
mCurrentGestureIndex--;
Gesture gesture = mGestures.get(mCurrentGestureIndex);
mGesturePad.setCurrentGesture(gesture);
mGesturePad.setGesture(gesture);
mGesturePad.invalidate();
}
}