1717package android .view ;
1818
1919import android .content .Context ;
20+ import android .os .SystemClock ;
2021import 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