GestureStroke.java revision 82f3495b146b267f3786997752cef25310176349
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.Matrix; 2135aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Liimport android.graphics.Paint; 2235aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Liimport android.graphics.Path; 2335aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Liimport android.graphics.RectF; 2435aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li 2535aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Liimport java.io.IOException; 26b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guyimport java.io.DataOutputStream; 27b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guyimport java.io.DataInputStream; 2835aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Liimport java.util.ArrayList; 2935aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li 3035aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li/** 3135aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li * A gesture stroke started on a touch down and ended on a touch up. 3235aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li */ 3335aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Lipublic class GestureStroke { 34d6a463a9f23b3901bf729f2f27a6bb8f78b95248Romain Guy 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 /** 4535aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li * Construct 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 /** 8635aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li * Draw the gesture with a given canvas and paint 8735aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li * 8835aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li * @param canvas 8935aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li */ 9035aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li void draw(Canvas canvas, Paint paint) { 9135aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li if (mCachedPath == null) { 9282f3495b146b267f3786997752cef25310176349Romain Guy makePath(); 9382f3495b146b267f3786997752cef25310176349Romain Guy } 94c534727972c3835ed997e84a349f259915ef2cddRomain Guy 9582f3495b146b267f3786997752cef25310176349Romain Guy canvas.drawPath(mCachedPath, paint); 9682f3495b146b267f3786997752cef25310176349Romain Guy } 97c534727972c3835ed997e84a349f259915ef2cddRomain Guy 9882f3495b146b267f3786997752cef25310176349Romain Guy public Path getPath() { 9982f3495b146b267f3786997752cef25310176349Romain Guy if (mCachedPath == null) { 10082f3495b146b267f3786997752cef25310176349Romain Guy makePath(); 10182f3495b146b267f3786997752cef25310176349Romain Guy } 10282f3495b146b267f3786997752cef25310176349Romain Guy 10382f3495b146b267f3786997752cef25310176349Romain Guy return mCachedPath; 10482f3495b146b267f3786997752cef25310176349Romain Guy } 10582f3495b146b267f3786997752cef25310176349Romain Guy 10682f3495b146b267f3786997752cef25310176349Romain Guy private void makePath() { 10782f3495b146b267f3786997752cef25310176349Romain Guy final float[] localPoints = points; 10882f3495b146b267f3786997752cef25310176349Romain Guy final int count = localPoints.length; 10982f3495b146b267f3786997752cef25310176349Romain Guy 11082f3495b146b267f3786997752cef25310176349Romain Guy Path path = null; 11182f3495b146b267f3786997752cef25310176349Romain Guy 11282f3495b146b267f3786997752cef25310176349Romain Guy float mX = 0; 11382f3495b146b267f3786997752cef25310176349Romain Guy float mY = 0; 114c534727972c3835ed997e84a349f259915ef2cddRomain Guy 11582f3495b146b267f3786997752cef25310176349Romain Guy for (int i = 0; i < count; i += 2) { 11682f3495b146b267f3786997752cef25310176349Romain Guy float x = localPoints[i]; 11782f3495b146b267f3786997752cef25310176349Romain Guy float y = localPoints[i + 1]; 11882f3495b146b267f3786997752cef25310176349Romain Guy if (path == null) { 11982f3495b146b267f3786997752cef25310176349Romain Guy path = new Path(); 12082f3495b146b267f3786997752cef25310176349Romain Guy path.moveTo(x, y); 12182f3495b146b267f3786997752cef25310176349Romain Guy mX = x; 12282f3495b146b267f3786997752cef25310176349Romain Guy mY = y; 12382f3495b146b267f3786997752cef25310176349Romain Guy } else { 12482f3495b146b267f3786997752cef25310176349Romain Guy float dx = Math.abs(x - mX); 12582f3495b146b267f3786997752cef25310176349Romain Guy float dy = Math.abs(y - mY); 12682f3495b146b267f3786997752cef25310176349Romain Guy if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) { 12782f3495b146b267f3786997752cef25310176349Romain Guy path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2); 12835aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li mX = x; 12935aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li mY = y; 13035aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li } 13135aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li } 13235aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li } 13335aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li 13482f3495b146b267f3786997752cef25310176349Romain Guy mCachedPath = path; 13535aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li } 13635aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li 13735aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li /** 13835aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li * Convert the stroke to a Path based on the number of points 13935aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li * 14035aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li * @param width the width of the bounding box of the target path 14135aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li * @param height the height of the bounding box of the target path 14235aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li * @param numSample the number of points needed 143db567c390bd56c05614eaa83c02dbb99f97ad9ccRomain Guy * 14435aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li * @return the path 14535aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li */ 14635aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li public Path toPath(float width, float height, int numSample) { 147c534727972c3835ed997e84a349f259915ef2cddRomain Guy final float[] pts = GestureUtilities.temporalSampling(this, numSample); 148c534727972c3835ed997e84a349f259915ef2cddRomain Guy final RectF rect = boundingBox; 149c534727972c3835ed997e84a349f259915ef2cddRomain Guy 150c534727972c3835ed997e84a349f259915ef2cddRomain Guy final Matrix matrix = new Matrix(); 15135aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li matrix.setTranslate(-rect.left, -rect.top); 152db567c390bd56c05614eaa83c02dbb99f97ad9ccRomain Guy matrix.postScale(width / rect.width(), height / rect.height()); 15335aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li matrix.mapPoints(pts); 15435aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li 15535aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li float mX = 0; 15635aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li float mY = 0; 157c534727972c3835ed997e84a349f259915ef2cddRomain Guy 158c534727972c3835ed997e84a349f259915ef2cddRomain Guy Path path = null; 159c534727972c3835ed997e84a349f259915ef2cddRomain Guy 160c534727972c3835ed997e84a349f259915ef2cddRomain Guy final int count = pts.length; 161c534727972c3835ed997e84a349f259915ef2cddRomain Guy 16235aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li for (int i = 0; i < count; i += 2) { 16335aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li float x = pts[i]; 16435aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li float y = pts[i + 1]; 16535aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li if (path == null) { 16635aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li path = new Path(); 16735aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li path.moveTo(x, y); 16835aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li mX = x; 16935aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li mY = y; 17035aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li } else { 17135aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li float dx = Math.abs(x - mX); 17235aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li float dy = Math.abs(y - mY); 17382f3495b146b267f3786997752cef25310176349Romain Guy if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) { 17435aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2); 17535aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li mX = x; 17635aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li mY = y; 17735aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li } 17835aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li } 17935aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li } 180c534727972c3835ed997e84a349f259915ef2cddRomain Guy 18135aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li return path; 18235aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li } 18335aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li 184b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy void serialize(DataOutputStream out) throws IOException { 185b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy final float[] pts = points; 186b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy final long[] times = timestamps; 187b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy final int count = points.length; 188b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy 189b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy // Write number of points 190b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy out.writeInt(count / 2); 191b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy 192b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy for (int i = 0; i < count; i += 2) { 193b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy // Write X 194b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy out.writeFloat(pts[i]); 195b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy // Write Y 196b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy out.writeFloat(pts[i + 1]); 197b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy // Write timestamp 198b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy out.writeLong(times[i / 2]); 199b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy } 20035aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li } 20135aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li 202b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy static GestureStroke deserialize(DataInputStream in) throws IOException { 203b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy // Number of points 204b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy final int count = in.readInt(); 205b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy 206b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy final ArrayList<GesturePoint> points = new ArrayList<GesturePoint>(count); 207b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy for (int i = 0; i < count; i++) { 208b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy points.add(GesturePoint.deserialize(in)); 20935aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li } 210c534727972c3835ed997e84a349f259915ef2cddRomain Guy 21135aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li return new GestureStroke(points); 212b6d99b7d17fd1bb1326a70744bd01be5d1586487Romain Guy } 21335aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li 21435aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li /** 215db567c390bd56c05614eaa83c02dbb99f97ad9ccRomain Guy * Invalidate the cached path that is used to render the stroke 21635aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li */ 217db567c390bd56c05614eaa83c02dbb99f97ad9ccRomain Guy public void clearPath() { 218db567c390bd56c05614eaa83c02dbb99f97ad9ccRomain Guy if (mCachedPath != null) mCachedPath.rewind(); 21935aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li } 220e6ea003ab66ea8bd91bed8aaf5c3b4cd75555886Yang Li 221e6ea003ab66ea8bd91bed8aaf5c3b4cd75555886Yang Li /** 222e6ea003ab66ea8bd91bed8aaf5c3b4cd75555886Yang Li * Compute an oriented bounding box of the stroke 223e6ea003ab66ea8bd91bed8aaf5c3b4cd75555886Yang Li * @return OrientedBoundingBox 224e6ea003ab66ea8bd91bed8aaf5c3b4cd75555886Yang Li */ 225e6ea003ab66ea8bd91bed8aaf5c3b4cd75555886Yang Li public OrientedBoundingBox computeOrientedBoundingBox() { 226e6ea003ab66ea8bd91bed8aaf5c3b4cd75555886Yang Li return GestureUtilities.computeOrientedBoundingBox(points); 227e6ea003ab66ea8bd91bed8aaf5c3b4cd75555886Yang Li } 22835aa84b1f9f5e42dd00cb66df993ed1628c8963bYang Li} 229