135aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li/* 2d6a463a9f23b3901bf729f2f27a6bb8f78b95248Romain Guy * Copyright (C) 2009 The Android Open Source Project 335aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li * 435aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li * Licensed under the Apache License, Version 2.0 (the "License"); 535aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li * you may not use this file except in compliance with the License. 635aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li * You may obtain a copy of the License at 735aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li * 835aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li * http://www.apache.org/licenses/LICENSE-2.0 935aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li * 1035aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li * Unless required by applicable law or agreed to in writing, software 1135aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li * distributed under the License is distributed on an "AS IS" BASIS, 1235aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1335aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li * See the License for the specific language governing permissions and 1435aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li * limitations under the License. 1535aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li */ 1635aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li 17db567c390bd56c05614eaa83c02dbb99f97ad9ccRomain Guypackage android.gesture; 1835aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li 1935aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Liimport android.graphics.Canvas; 2035aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Liimport android.graphics.Paint; 2135aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Liimport android.graphics.Path; 2235aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Liimport android.graphics.RectF; 2335aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li 2435aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Liimport java.io.IOException; 25b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guyimport java.io.DataOutputStream; 26b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guyimport java.io.DataInputStream; 2735aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Liimport java.util.ArrayList; 2835aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li 2935aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li/** 306fc1f151341c628125e7d20c1667a23862f32ce4Yang Li * A gesture stroke started on a touch down and ended on a touch up. A stroke 316fc1f151341c628125e7d20c1667a23862f32ce4Yang Li * consists of a sequence of timed points. One or multiple strokes form a gesture. 3235aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li */ 3335aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Lipublic class GestureStroke { 34c7f930f5a9a62470775e913945c771ca57e0b10fYang Li static final float TOUCH_TOLERANCE = 3; 35d6a463a9f23b3901bf729f2f27a6bb8f78b95248Romain Guy 3635aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li public final RectF boundingBox; 3735aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li 3835aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li public final float length; 3935aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li public final float[] points; 4035aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li 4135aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li private final long[] timestamps; 4235aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li private Path mCachedPath; 4335aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li 4435aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li /** 456fc1f151341c628125e7d20c1667a23862f32ce4Yang Li * A constructor that constructs a gesture stroke from a list of gesture points. 4635aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li * 47c534727972c3835ed997e84a349f259915ef2cddRomain Guy * @param points 4835aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li */ 49c534727972c3835ed997e84a349f259915ef2cddRomain Guy public GestureStroke(ArrayList<GesturePoint> points) { 50c534727972c3835ed997e84a349f259915ef2cddRomain Guy final int count = points.size(); 51c534727972c3835ed997e84a349f259915ef2cddRomain Guy final float[] tmpPoints = new float[count * 2]; 52c534727972c3835ed997e84a349f259915ef2cddRomain Guy final long[] times = new long[count]; 5335aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li 5435aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li RectF bx = null; 5535aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li float len = 0; 5635aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li int index = 0; 5735aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li 5835aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li for (int i = 0; i < count; i++) { 59c534727972c3835ed997e84a349f259915ef2cddRomain Guy final GesturePoint p = points.get(i); 60c534727972c3835ed997e84a349f259915ef2cddRomain Guy tmpPoints[i * 2] = p.x; 61c534727972c3835ed997e84a349f259915ef2cddRomain Guy tmpPoints[i * 2 + 1] = p.y; 6235aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li times[index] = p.timestamp; 6335aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li 6435aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li if (bx == null) { 6535aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li bx = new RectF(); 66c534727972c3835ed997e84a349f259915ef2cddRomain Guy bx.top = p.y; 67c534727972c3835ed997e84a349f259915ef2cddRomain Guy bx.left = p.x; 68c534727972c3835ed997e84a349f259915ef2cddRomain Guy bx.right = p.x; 69c534727972c3835ed997e84a349f259915ef2cddRomain Guy bx.bottom = p.y; 7035aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li len = 0; 7135aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li } else { 72c534727972c3835ed997e84a349f259915ef2cddRomain Guy len += Math.sqrt(Math.pow(p.x - tmpPoints[(i - 1) * 2], 2) 73c534727972c3835ed997e84a349f259915ef2cddRomain Guy + Math.pow(p.y - tmpPoints[(i -1 ) * 2 + 1], 2)); 74c534727972c3835ed997e84a349f259915ef2cddRomain Guy bx.union(p.x, p.y); 7535aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li } 7635aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li index++; 7735aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li } 7835aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li 7935aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li timestamps = times; 80c534727972c3835ed997e84a349f259915ef2cddRomain Guy this.points = tmpPoints; 8135aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li boundingBox = bx; 8235aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li length = len; 8335aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li } 8435aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li 8535aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li /** 866fc1f151341c628125e7d20c1667a23862f32ce4Yang Li * A faster constructor specially for cloning a stroke. 876fc1f151341c628125e7d20c1667a23862f32ce4Yang Li */ 886fc1f151341c628125e7d20c1667a23862f32ce4Yang Li private GestureStroke(RectF bbx, float len, float[] pts, long[] times) { 896fc1f151341c628125e7d20c1667a23862f32ce4Yang Li boundingBox = new RectF(bbx.left, bbx.top, bbx.right, bbx.bottom); 906fc1f151341c628125e7d20c1667a23862f32ce4Yang Li length = len; 916fc1f151341c628125e7d20c1667a23862f32ce4Yang Li points = pts.clone(); 926fc1f151341c628125e7d20c1667a23862f32ce4Yang Li timestamps = times.clone(); 936fc1f151341c628125e7d20c1667a23862f32ce4Yang Li } 946fc1f151341c628125e7d20c1667a23862f32ce4Yang Li 956fc1f151341c628125e7d20c1667a23862f32ce4Yang Li @Override 966fc1f151341c628125e7d20c1667a23862f32ce4Yang Li public Object clone() { 976fc1f151341c628125e7d20c1667a23862f32ce4Yang Li return new GestureStroke(boundingBox, length, points, timestamps); 986fc1f151341c628125e7d20c1667a23862f32ce4Yang Li } 996fc1f151341c628125e7d20c1667a23862f32ce4Yang Li 1006fc1f151341c628125e7d20c1667a23862f32ce4Yang Li /** 1016fc1f151341c628125e7d20c1667a23862f32ce4Yang Li * Draws the stroke with a given canvas and paint. 10235aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li * 10335aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li * @param canvas 10435aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li */ 10535aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li void draw(Canvas canvas, Paint paint) { 10635aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li if (mCachedPath == null) { 10782f3495b146b267f3786997752cef25310176349Romain Guy makePath(); 10882f3495b146b267f3786997752cef25310176349Romain Guy } 109c534727972c3835ed997e84a349f259915ef2cddRomain Guy 11082f3495b146b267f3786997752cef25310176349Romain Guy canvas.drawPath(mCachedPath, paint); 11182f3495b146b267f3786997752cef25310176349Romain Guy } 112c534727972c3835ed997e84a349f259915ef2cddRomain Guy 11382f3495b146b267f3786997752cef25310176349Romain Guy public Path getPath() { 11482f3495b146b267f3786997752cef25310176349Romain Guy if (mCachedPath == null) { 11582f3495b146b267f3786997752cef25310176349Romain Guy makePath(); 11682f3495b146b267f3786997752cef25310176349Romain Guy } 11782f3495b146b267f3786997752cef25310176349Romain Guy 11882f3495b146b267f3786997752cef25310176349Romain Guy return mCachedPath; 11982f3495b146b267f3786997752cef25310176349Romain Guy } 12082f3495b146b267f3786997752cef25310176349Romain Guy 12182f3495b146b267f3786997752cef25310176349Romain Guy private void makePath() { 12282f3495b146b267f3786997752cef25310176349Romain Guy final float[] localPoints = points; 12382f3495b146b267f3786997752cef25310176349Romain Guy final int count = localPoints.length; 12482f3495b146b267f3786997752cef25310176349Romain Guy 12582f3495b146b267f3786997752cef25310176349Romain Guy Path path = null; 12682f3495b146b267f3786997752cef25310176349Romain Guy 12782f3495b146b267f3786997752cef25310176349Romain Guy float mX = 0; 12882f3495b146b267f3786997752cef25310176349Romain Guy float mY = 0; 129c534727972c3835ed997e84a349f259915ef2cddRomain Guy 13082f3495b146b267f3786997752cef25310176349Romain Guy for (int i = 0; i < count; i += 2) { 13182f3495b146b267f3786997752cef25310176349Romain Guy float x = localPoints[i]; 13282f3495b146b267f3786997752cef25310176349Romain Guy float y = localPoints[i + 1]; 13382f3495b146b267f3786997752cef25310176349Romain Guy if (path == null) { 13482f3495b146b267f3786997752cef25310176349Romain Guy path = new Path(); 13582f3495b146b267f3786997752cef25310176349Romain Guy path.moveTo(x, y); 13682f3495b146b267f3786997752cef25310176349Romain Guy mX = x; 13782f3495b146b267f3786997752cef25310176349Romain Guy mY = y; 13882f3495b146b267f3786997752cef25310176349Romain Guy } else { 13982f3495b146b267f3786997752cef25310176349Romain Guy float dx = Math.abs(x - mX); 14082f3495b146b267f3786997752cef25310176349Romain Guy float dy = Math.abs(y - mY); 14182f3495b146b267f3786997752cef25310176349Romain Guy if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) { 14282f3495b146b267f3786997752cef25310176349Romain Guy path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2); 14335aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li mX = x; 14435aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li mY = y; 14535aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li } 14635aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li } 14735aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li } 14835aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li 14982f3495b146b267f3786997752cef25310176349Romain Guy mCachedPath = path; 15035aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li } 15135aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li 15235aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li /** 1536fc1f151341c628125e7d20c1667a23862f32ce4Yang Li * Converts the stroke to a Path of a given number of points. 15435aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li * 15535aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li * @param width the width of the bounding box of the target path 15635aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li * @param height the height of the bounding box of the target path 15735aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li * @param numSample the number of points needed 158db567c390bd56c05614eaa83c02dbb99f97ad9ccRomain Guy * 15935aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li * @return the path 16035aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li */ 16135aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li public Path toPath(float width, float height, int numSample) { 16246c53129c6f27c9193ab195a69cb50591b8c1fa2Romain Guy final float[] pts = GestureUtils.temporalSampling(this, numSample); 163c534727972c3835ed997e84a349f259915ef2cddRomain Guy final RectF rect = boundingBox; 164c534727972c3835ed997e84a349f259915ef2cddRomain Guy 16546c53129c6f27c9193ab195a69cb50591b8c1fa2Romain Guy GestureUtils.translate(pts, -rect.left, -rect.top); 166f40f074c43fcef627131d4b631251192761b4daaRomain Guy 167f40f074c43fcef627131d4b631251192761b4daaRomain Guy float sx = width / rect.width(); 168f40f074c43fcef627131d4b631251192761b4daaRomain Guy float sy = height / rect.height(); 169f40f074c43fcef627131d4b631251192761b4daaRomain Guy float scale = sx > sy ? sy : sx; 17046c53129c6f27c9193ab195a69cb50591b8c1fa2Romain Guy GestureUtils.scale(pts, scale, scale); 17135aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li 17235aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li float mX = 0; 17335aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li float mY = 0; 174c534727972c3835ed997e84a349f259915ef2cddRomain Guy 175c534727972c3835ed997e84a349f259915ef2cddRomain Guy Path path = null; 176c534727972c3835ed997e84a349f259915ef2cddRomain Guy 177c534727972c3835ed997e84a349f259915ef2cddRomain Guy final int count = pts.length; 178c534727972c3835ed997e84a349f259915ef2cddRomain Guy 17935aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li for (int i = 0; i < count; i += 2) { 18035aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li float x = pts[i]; 18135aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li float y = pts[i + 1]; 18235aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li if (path == null) { 18335aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li path = new Path(); 18435aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li path.moveTo(x, y); 18535aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li mX = x; 18635aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li mY = y; 18735aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li } else { 18835aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li float dx = Math.abs(x - mX); 18935aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li float dy = Math.abs(y - mY); 19082f3495b146b267f3786997752cef25310176349Romain Guy if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) { 19135aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2); 19235aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li mX = x; 19335aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li mY = y; 19435aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li } 19535aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li } 19635aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li } 197c534727972c3835ed997e84a349f259915ef2cddRomain Guy 19835aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li return path; 19935aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li } 20035aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li 201b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy void serialize(DataOutputStream out) throws IOException { 202b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy final float[] pts = points; 203b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy final long[] times = timestamps; 204b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy final int count = points.length; 205b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy 206b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy // Write number of points 207b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy out.writeInt(count / 2); 208b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy 209b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy for (int i = 0; i < count; i += 2) { 210b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy // Write X 211b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy out.writeFloat(pts[i]); 212b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy // Write Y 213b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy out.writeFloat(pts[i + 1]); 214b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy // Write timestamp 215b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy out.writeLong(times[i / 2]); 216b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy } 21735aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li } 21835aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li 219b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy static GestureStroke deserialize(DataInputStream in) throws IOException { 220b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy // Number of points 221b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy final int count = in.readInt(); 222b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy 223b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy final ArrayList<GesturePoint> points = new ArrayList<GesturePoint>(count); 224b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy for (int i = 0; i < count; i++) { 225b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy points.add(GesturePoint.deserialize(in)); 22635aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li } 227c534727972c3835ed997e84a349f259915ef2cddRomain Guy 22835aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li return new GestureStroke(points); 229b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy } 23035aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li 23135aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li /** 2326fc1f151341c628125e7d20c1667a23862f32ce4Yang Li * Invalidates the cached path that is used to render the stroke. 23335aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li */ 234db567c390bd56c05614eaa83c02dbb99f97ad9ccRomain Guy public void clearPath() { 235db567c390bd56c05614eaa83c02dbb99f97ad9ccRomain Guy if (mCachedPath != null) mCachedPath.rewind(); 23635aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li } 237e6ea003ab66ea8bd91bed8aaf5c3b4cd75555886Yang Li 238e6ea003ab66ea8bd91bed8aaf5c3b4cd75555886Yang Li /** 2396fc1f151341c628125e7d20c1667a23862f32ce4Yang Li * Computes an oriented bounding box of the stroke. 2406fc1f151341c628125e7d20c1667a23862f32ce4Yang Li * 241e6ea003ab66ea8bd91bed8aaf5c3b4cd75555886Yang Li * @return OrientedBoundingBox 242e6ea003ab66ea8bd91bed8aaf5c3b4cd75555886Yang Li */ 243e6ea003ab66ea8bd91bed8aaf5c3b4cd75555886Yang Li public OrientedBoundingBox computeOrientedBoundingBox() { 24446c53129c6f27c9193ab195a69cb50591b8c1fa2Romain Guy return GestureUtils.computeOrientedBoundingBox(points); 245e6ea003ab66ea8bd91bed8aaf5c3b4cd75555886Yang Li } 24635aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li} 247