The foreword 0.

This paper still belongs to the system of event processing. In most cases, scaling gestures do not exist alone and need to be used with other gestures. Therefore, it is recommended to watch it together with GestureDetector. If it is used in custom controls, it may be more convenient to use with Matrix related content. If you are not familiar with Matrix, you can also read the previous content of this series to supplement relevant knowledge. If you haven’t seen the previous articles, you can check them out in the Custom Views series.

Zooming is rarely used by most Android engineers. It is most commonly used in image browsing, image editing (texturing), web zooming, maps, text reading (text resizing by zooming), etc. The application scenario is relatively narrow, but it certainly has some applications. It can achieve the following effects:

2. ScaleGestureDecetor

Scaling gesture detection is also an official tool that is similar to GentureDecetor. It is also a tool to monitor user gestures through the Listener. It encapsulates scaling gestures, making it convenient for users to develop scaling functions quickly. Pinch gesture is relatively simple, and many unofficial pinch gesture calculation schemes can be found on the Internet, but some unofficial schemes do have limitations. For example, only two fingers are supported. When there are more than two fingers, only the movement of the first two fingers is calculated, which is obviously unreasonable. The official implementation handles multiple fingers with ease, so let’s see how it works.

2.1 Construction method

It has two constructors similar to GestureDetector, as shown below:

ScaleGestureDetector(Context context, ScaleGestureDetector.OnScaleGestureListener listener)

ScaleGestureDetector(Context context, ScaleGestureDetector.OnScaleGestureListener listener, Handler handler)
Copy the code

2.2 Gesture listener

It has only two listeners, but technically they are the same listener, except that one is an interface and the other is an empty implementation.

The listener Introduction to the
OnScaleGestureListener Zoom gesture detector.
SimpleOnScaleGestureListener Empty implementation of zoom gesture detector.

OnScaleGestureListener

There are three methods for scaling the gesture listener:

methods Introduction to the
boolean onScaleBegin(ScaleGestureDetector detector) The zoom gesture starts, and this method is called (only once) when two fingers are on the screen. If false is returned, the current pinch gesture is not used.
boolean onScale(ScaleGestureDetector detector) Scaling is triggered (zero or more times), returns true to indicate that the current scaling event has been processed, and the detector reaccumulates the scaling factor, returns false to accumulate the scaling factor.
void onScaleEnd(ScaleGestureDetector detector) End of zooming gesture.

2.3 Simple Example

This is a minimal use case using ScaleGestureDetector. Of course, it does nothing but log out a few parameters that we care about.

public class ScaleGestureDemoView extends View { private static final String TAG = "ScaleGestureDemoView"; private ScaleGestureDetector mScaleGestureDetector; public ScaleGestureDemoView(Context context) { super(context); } public ScaleGestureDemoView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); initScaleGestureDetector(); } private void initScaleGestureDetector() { mScaleGestureDetector = new ScaleGestureDetector(getContext(), new ScaleGestureDetector.SimpleOnScaleGestureListener() { @Override public boolean onScaleBegin(ScaleGestureDetector detector) { return true; } @Override public boolean onScale(ScaleGestureDetector detector) { Log.i(TAG, "focusX = " + detector.getFocusX()); I (TAG, "focusY = "+ focus.getFocusy ()); I (TAG, "scale = "+ Detector. GetScaleFactor ()); // scaling factor return true; } @Override public void onScaleEnd(ScaleGestureDetector detector) { } }); } @Override public boolean onTouchEvent(MotionEvent event) { mScaleGestureDetector.onTouchEvent(event); return true; }}Copy the code

3. Fundamentals

Because the pinch gesture detection is very simple to use, there is no complex content, not only that, its implementation is also very simple, I will take you a simple analysis of its basic principle. The only two parameters we care about in a pinch gesture are the center of the pinch and the scale. Let’s look at how these two parameters are calculated.

3.1 Calculate the zoom center point (focus)

If there are only two fingers, the center of the scale is very easy to calculate, that is, the coordinates of the two fingers, but how to calculate the center of the scale if there are more than one fingers?

The principle of calculating the center point is actually very simple, that is, add up all the coordinates, and divide by the number.

This is a simple mathematical principle, not complicated, if you don’t understand, try to calculate it yourself can understand. However, in practical application, it should be noted that the number of users’ fingers may not be fixed, and users may lift or press their fingers at any time. The implementation of ScaleGestureDetector is as follows:

final boolean anchoredScaleCancelled = mAnchoredScaleMode == ANCHORED_SCALE_MODE_STYLUS && ! isStylusButtonDown; final boolean streamComplete = action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL || anchoredScaleCancelled; / / note here if (action = = MotionEvent. ACTION_DOWN | | streamComplete) {/ / reset any zoom listener is ongoing. // If ACTION_DOWN, we are starting a new event stream. // This means that the application may not give us all the events (events are directly intercepted by the upper layer). if (mInProgress) { mListener.onScaleEnd(this); mInProgress = false; mInitialSpan = 0; mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE; } else if (inAnchoredScaleMode() && streamComplete) { mInProgress = false; mInitialSpan = 0; mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE; } if (streamComplete) { return true; }}Copy the code

As you can see, when down or UP or cancel is triggered, if it was previously in the scaling state, the state is reset and the onScaleEnd method is called.

Of course, you may have noticed something like mAnchoredScaleMode, which is support for peripherals like the stylus that most engineers don’t use very often, whatever.

Computing center:

final boolean configChanged = action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_POINTER_UP || action == MotionEvent.ACTION_POINTER_DOWN || anchoredScaleCancelled; // Final Boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP; final int skipIndex = pointerUp ? event.getActionIndex() : -1; Float sumX = 0, sumY = 0; final int div = pointerUp ? count - 1 : count; final float focusX; final float focusY; If (inAnchoredScaleMode()) {// In anchor scale mode, focusX = mAnchoredScaleStartX; // In anchor scale mode, focusX = mAnchoredScaleStartX; // In anchor scale mode, focusX = mAnchoredScaleStartX; focusY = mAnchoredScaleStartY; if (event.getY() < focusY) { mEventBeforeOrAboveStartingGestureEvent = true; } else { mEventBeforeOrAboveStartingGestureEvent = false; }} else {for (int I = 0; i < count; i++) { if (skipIndex == i) continue; sumX += event.getX(i); sumY += event.getY(i); } focusX = sumX / div; focusY = sumY / div; }Copy the code

3.2 Calculate the scaling ratio

It is also easy to calculate the zoom ratio by calculating the average distance between each finger and the focus, dividing the new average distance by the old average distance after the user’s finger moves, and then calculating the zoom ratio.

Float devSumX = 0, devSumY = 0; for (int i = 0; i < count; i++) { if (skipIndex == i) continue; devSumX += Math.abs(event.getX(i) - focusX); devSumY += Math.abs(event.getY(i) - focusY); } final float devX = devSumX / div; final float devY = devSumY / div; // Note that final float spanX = devX * 2; final float spanY = devY * 2; final float span; if (inAnchoredScaleMode()) { span = spanY; } else {// equivalent to SQRT (x*x + y*y) span = (float) math.hypot (spanX, spanY); }Copy the code

The onScaleBegin method is triggered when the user moves more than a certain value (the value is defined by the system). If the user returns true in the onScaleBegin method, it means that after receiving the event, the scaling value is reset and the scaling factor starts to accumulate.

// mSpanSlop and mMinSpan are predefined values taken from the system that actually affect the sensitivity of scaling. // However, this parameter does not provide a way to set, if you are not satisfied with the sensitivity, and to directly copy a ScaleGestureDetector into the project and modify the values. final int minSpan = inAnchoredScaleMode() ? mSpanSlop : mMinSpan; if (! mInProgress && span >= minSpan && (wasInProgress || Math.abs(span - mInitialSpan) > mSpanSlop)) { mPrevSpanX = mCurrSpanX = spanX; mPrevSpanY = mCurrSpanY = spanY; mPrevSpan = mCurrSpan = span; mPrevTime = mCurrTime; mInProgress = mListener.onScaleBegin(this); }Copy the code

Notifying the user to zoom:

if (action == MotionEvent.ACTION_MOVE) { mCurrSpanX = spanX; mCurrSpanY = spanY; mCurrSpan = span; boolean updatePrev = true; If (mInProgress) {// Note that the user's return value determines whether to recalculate the scale factor updatePrev = mlistener.onscale (this); } // If the user returns true, the scaling factor is recalculated if (updatePrev) {mPrevSpanX = mCurrSpanX; mPrevSpanY = mCurrSpanY; mPrevSpan = mCurrSpan; mPrevTime = mCurrTime; }}Copy the code

4. Afterword.

Because zooming gesture detection is really simple, there is nothing to talk about, if you see the above content is still confused, recommend to take a look at the source code, the logic of the source code is also very simple, I believe that after reading it will be able to thoroughly understand the principle of it.

Finally, attach the source code of the picture used at the beginning of the article. Click Download project to obtain the source code.

Download the project

About Me

Author’s Microblog:@GcsSloop