Skip to content

Commit b4693e2

Browse files
adampAndroid (Google) Code Review
authored andcommitted
Merge "Smooth out handling of touchMajor/touchMinor in ScaleGestureDetector." into jb-mr1-dev
2 parents 2fd6cb0 + a4ce6ae commit b4693e2

File tree

1 file changed

+130
-1
lines changed

1 file changed

+130
-1
lines changed

core/java/android/view/ScaleGestureDetector.java

Lines changed: 130 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@
1717
package android.view;
1818

1919
import android.content.Context;
20+
import android.os.SystemClock;
2021
import android.util.FloatMath;
2122

23+
import java.util.Arrays;
24+
2225
/**
2326
* Detects scaling transformation gestures using the supplied {@link MotionEvent}s.
2427
* The {@link OnScaleGestureListener} callback will notify users when a particular
@@ -139,6 +142,12 @@ public void onScaleEnd(ScaleGestureDetector detector) {
139142
private int mSpanSlop;
140143
private int mMinSpan;
141144

145+
private float[] mTouchHistoryLastAccepted;
146+
private int[] mTouchHistoryDirection;
147+
private long[] mTouchHistoryLastAcceptedTime;
148+
149+
private static final long TOUCH_STABILIZE_TIME = 64; // ms
150+
142151
/**
143152
* Consistency verifier for debugging purposes.
144153
*/
@@ -154,6 +163,119 @@ public ScaleGestureDetector(Context context, OnScaleGestureListener listener) {
154163
com.android.internal.R.dimen.config_minScalingSpan);
155164
}
156165

166+
/**
167+
* The touchMajor/touchMinor elements of a MotionEvent can flutter/jitter on
168+
* some hardware/driver combos. Smooth it out to get kinder, gentler behavior.
169+
* @param ev MotionEvent to add to the ongoing history
170+
*/
171+
private void addTouchHistory(MotionEvent ev) {
172+
final long currentTime = SystemClock.uptimeMillis();
173+
final int count = ev.getPointerCount();
174+
for (int i = 0; i < count; i++) {
175+
final int id = ev.getPointerId(i);
176+
ensureTouchHistorySize(id);
177+
178+
final boolean hasLastAccepted = !Float.isNaN(mTouchHistoryLastAccepted[id]);
179+
boolean accept = true;
180+
final int historySize = ev.getHistorySize();
181+
for (int h = 0; h < historySize + 1; h++) {
182+
final float major;
183+
final float minor;
184+
if (h < historySize) {
185+
major = ev.getHistoricalTouchMajor(i, h);
186+
minor = ev.getHistoricalTouchMinor(i, h);
187+
} else {
188+
major = ev.getTouchMajor(i);
189+
minor = ev.getTouchMinor(i);
190+
}
191+
final float avg = (major + minor) / 2;
192+
193+
if (hasLastAccepted) {
194+
final int directionSig = (int) Math.signum(avg - mTouchHistoryLastAccepted[id]);
195+
if (directionSig != mTouchHistoryDirection[id]) {
196+
mTouchHistoryDirection[id] = directionSig;
197+
final long time = h < historySize ? ev.getHistoricalEventTime(h)
198+
: ev.getEventTime();
199+
mTouchHistoryLastAcceptedTime[id] = time;
200+
accept = false;
201+
}
202+
if (currentTime - mTouchHistoryLastAcceptedTime[id] < TOUCH_STABILIZE_TIME) {
203+
accept = false;
204+
}
205+
}
206+
}
207+
208+
if (accept) {
209+
float newAccepted = (ev.getTouchMajor(i) + ev.getTouchMinor(i)) / 2;
210+
if (hasLastAccepted) {
211+
newAccepted = (mTouchHistoryLastAccepted[id] + newAccepted) / 2;
212+
}
213+
mTouchHistoryLastAccepted[id] = newAccepted;
214+
mTouchHistoryDirection[id] = 0;
215+
mTouchHistoryLastAcceptedTime[id] = ev.getEventTime();
216+
}
217+
}
218+
}
219+
220+
/**
221+
* Clear out the touch history for a given pointer id.
222+
* @param id pointer id to clear
223+
* @see #addTouchHistory(MotionEvent)
224+
*/
225+
private void removeTouchHistoryForId(int id) {
226+
mTouchHistoryLastAccepted[id] = Float.NaN;
227+
mTouchHistoryDirection[id] = 0;
228+
mTouchHistoryLastAcceptedTime[id] = 0;
229+
}
230+
231+
/**
232+
* Get the adjusted combined touchMajor/touchMinor value for a given pointer id
233+
* @param id the pointer id of the data to obtain
234+
* @return the adjusted major/minor value for the point at id
235+
* @see #addTouchHistory(MotionEvent)
236+
*/
237+
private float getAdjustedTouchHistory(int id) {
238+
return mTouchHistoryLastAccepted[id];
239+
}
240+
241+
/**
242+
* Clear all touch history tracking. Useful in ACTION_CANCEL or ACTION_UP.
243+
* @see #addTouchHistory(MotionEvent)
244+
*/
245+
private void clearTouchHistory() {
246+
Arrays.fill(mTouchHistoryLastAccepted, Float.NaN);
247+
Arrays.fill(mTouchHistoryDirection, 0);
248+
Arrays.fill(mTouchHistoryLastAcceptedTime, 0);
249+
}
250+
251+
private void ensureTouchHistorySize(int id) {
252+
final int requiredSize = id + 1;
253+
if (mTouchHistoryLastAccepted == null || mTouchHistoryLastAccepted.length < requiredSize) {
254+
final float[] newLastAccepted = new float[requiredSize];
255+
final int[] newDirection = new int[requiredSize];
256+
final long[] newLastAcceptedTime = new long[requiredSize];
257+
258+
int oldLength = 0;
259+
if (mTouchHistoryLastAccepted != null) {
260+
System.arraycopy(mTouchHistoryLastAccepted, 0, newLastAccepted, 0,
261+
mTouchHistoryLastAccepted.length);
262+
System.arraycopy(mTouchHistoryDirection, 0, newDirection, 0,
263+
mTouchHistoryDirection.length);
264+
System.arraycopy(mTouchHistoryLastAcceptedTime, 0, newLastAcceptedTime, 0,
265+
mTouchHistoryLastAcceptedTime.length);
266+
oldLength = mTouchHistoryLastAccepted.length;
267+
}
268+
269+
Arrays.fill(newLastAccepted, oldLength, newLastAccepted.length, Float.NaN);
270+
Arrays.fill(newDirection, oldLength, newDirection.length, 0);
271+
Arrays.fill(newLastAcceptedTime, oldLength, newLastAcceptedTime.length, 0);
272+
273+
mTouchHistoryLastAccepted = newLastAccepted;
274+
mTouchHistoryDirection = newDirection;
275+
mTouchHistoryLastAcceptedTime = newLastAcceptedTime;
276+
}
277+
}
278+
157279
/**
158280
* Accepts MotionEvents and dispatches events to a {@link OnScaleGestureListener}
159281
* when appropriate.
@@ -186,6 +308,7 @@ public boolean onTouchEvent(MotionEvent event) {
186308
}
187309

188310
if (streamComplete) {
311+
clearTouchHistory();
189312
return true;
190313
}
191314
}
@@ -208,13 +331,19 @@ public boolean onTouchEvent(MotionEvent event) {
208331
final float focusX = sumX / div;
209332
final float focusY = sumY / div;
210333

334+
if (pointerUp) {
335+
removeTouchHistoryForId(event.getPointerId(event.getActionIndex()));
336+
} else {
337+
addTouchHistory(event);
338+
}
339+
211340
// Determine average deviation from focal point
212341
float devSumX = 0, devSumY = 0;
213342
for (int i = 0; i < count; i++) {
214343
if (skipIndex == i) continue;
215344

216345
// Average touch major and touch minor and convert the resulting diameter into a radius.
217-
final float touchSize = (event.getTouchMajor(i) + event.getTouchMinor(i)) / 4;
346+
final float touchSize = getAdjustedTouchHistory(event.getPointerId(i));
218347
devSumX += Math.abs(event.getX(i) - focusX) + touchSize;
219348
devSumY += Math.abs(event.getY(i) - focusY) + touchSize;
220349
}

0 commit comments

Comments
 (0)