1be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang/*
2be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang * Copyright (C) 2013 Google Inc.
3be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang * Licensed to The Android Open Source Project.
4be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang *
5be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang * Licensed under the Apache License, Version 2.0 (the "License");
6be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang * you may not use this file except in compliance with the License.
7be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang * You may obtain a copy of the License at
8be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang *
9be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang *      http://www.apache.org/licenses/LICENSE-2.0
10be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang *
11be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang * Unless required by applicable law or agreed to in writing, software
12be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang * distributed under the License is distributed on an "AS IS" BASIS,
13be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang * See the License for the specific language governing permissions and
15be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang * limitations under the License.
16be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang */
17be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang
18be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huangpackage com.android.mail.browse;
19be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang
20be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huangimport android.content.Context;
21be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huangimport android.util.AttributeSet;
22f2dabb90c33740189112757349c49ef9e48ea541Tony Mantlerimport android.view.GestureDetector;
23be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huangimport android.view.MotionEvent;
24d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantlerimport android.view.ScaleGestureDetector;
25f2dabb90c33740189112757349c49ef9e48ea541Tony Mantlerimport android.view.ViewConfiguration;
26be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huangimport android.widget.ScrollView;
27be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang
28be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huangimport com.android.mail.utils.LogUtils;
29be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang
3050554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdonimport java.util.Set;
3150554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdonimport java.util.concurrent.CopyOnWriteArraySet;
3250554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon
33be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang/**
34be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang * A container that tries to play nice with an internally scrollable {@link Touchable} child view.
35be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang * The assumption is that the child view can scroll horizontally, but not vertically, so any
36be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang * touch events on that child view should ALSO be sent here so it can simultaneously vertically
37be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang * scroll (not the standard either/or behavior).
38be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang * <p>
39be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang * Touch events on any other child of this ScrollView are intercepted in the standard fashion.
40be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang */
41d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantlerpublic class MessageScrollView extends ScrollView implements ScrollNotifier,
42f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler        ScaleGestureDetector.OnScaleGestureListener, GestureDetector.OnDoubleTapListener {
43be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang
44be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    /**
45be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang     * A View that reports whether onTouchEvent() was recently called.
46be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang     */
47be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    public interface Touchable {
48be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang        boolean wasTouched();
49be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang        void clearTouched();
50f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler        boolean zoomIn();
51f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler        boolean zoomOut();
52be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    }
53be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang
54be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    /**
55be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang     * True when performing "special" interception.
56be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang     */
57be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    private boolean mWantToIntercept;
58be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    /**
59be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang     * Whether to perform the standard touch interception procedure. This is set to true when we
60be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang     * want to intercept a touch stream from any child OTHER than {@link #mTouchableChild}.
61be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang     */
62be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    private boolean mInterceptNormally;
63be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    /**
64be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang     * The special child that we want to NOT intercept from in the normal way. Instead, this child
65be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang     * will continue to receive the touch event stream (so it can handle the horizontal component)
66be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang     * while this parent will additionally handle the events to perform vertical scrolling.
67be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang     */
68be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    private Touchable mTouchableChild;
69be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang
70d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler    /**
71d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler     * We want to detect the scale gesture so that we don't try to scroll instead, but we don't
72d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler     * care about actually interpreting it because the webview does that by itself when it handles
73d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler     * the touch events.
74d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler     *
75d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler     * This might lead to really weird interactions if the two gesture detectors' implementations
76d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler     * drift...
77d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler     */
78d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler    private ScaleGestureDetector mScaleDetector;
79d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler    private boolean mInScaleGesture;
80d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler
81f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler    /**
82f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler     * We also want to detect double-tap gestures, but in a way that doesn't conflict with
83f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler     * tap-tap-drag gestures
84f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler     */
85f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler    private GestureDetector mGestureDetector;
86f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler    private boolean mDoubleTapOccurred;
87f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler    private boolean mZoomedIn;
88f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler
89f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler    /**
90f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler     * Touch slop used to determine if this double tap is valid for starting a scale or should be
91f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler     * ignored.
92f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler     */
93f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler    private int mTouchSlopSquared;
94f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler
95f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler    /**
96f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler     * X and Y coordinates for the current down event. Since mDoubleTapOccurred only contains the
97f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler     * information that there was a double tap event, use these to get the secondary tap
98f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler     * information to determine if a user has moved beyond touch slop.
99f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler     */
100f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler    private float mDownFocusX;
101f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler    private float mDownFocusY;
102f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler
10350554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    private final Set<ScrollListener> mScrollListeners =
10450554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon            new CopyOnWriteArraySet<ScrollListener>();
10550554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon
106be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    public static final String LOG_TAG = "MsgScroller";
107be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang
108be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    public MessageScrollView(Context c) {
109be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang        this(c, null);
110be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    }
111be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang
112be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    public MessageScrollView(Context c, AttributeSet attrs) {
113be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang        super(c, attrs);
114f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler        final int touchSlop = ViewConfiguration.get(c).getScaledTouchSlop();
115f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler        mTouchSlopSquared = touchSlop * touchSlop;
116d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler        mScaleDetector = new ScaleGestureDetector(c, this);
117f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler        mGestureDetector = new GestureDetector(c, new GestureDetector.SimpleOnGestureListener());
118f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler        mGestureDetector.setOnDoubleTapListener(this);
119be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    }
120be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang
121be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    public void setInnerScrollableView(Touchable child) {
122be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang        mTouchableChild = child;
123be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    }
124be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang
125be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    @Override
126be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    public boolean onInterceptTouchEvent(MotionEvent ev) {
127be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang        if (mInterceptNormally) {
128be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang            LogUtils.d(LOG_TAG, "IN ScrollView.onIntercept, NOW stealing. ev=%s", ev);
129be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang            return true;
130be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang        } else if (mWantToIntercept) {
131be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang            LogUtils.d(LOG_TAG, "IN ScrollView.onIntercept, already stealing. ev=%s", ev);
132be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang            return false;
133be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang        }
134be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang
135be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang        mWantToIntercept = super.onInterceptTouchEvent(ev);
136be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang        LogUtils.d(LOG_TAG, "OUT ScrollView.onIntercept, steal=%s ev=%s", mWantToIntercept, ev);
137be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang        return false;
138be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    }
139be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang
140be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    @Override
141be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    public boolean dispatchTouchEvent(MotionEvent ev) {
142be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang        final int action = ev.getActionMasked();
143be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang        switch (action) {
144be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang            case MotionEvent.ACTION_DOWN:
145be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang                LogUtils.d(LOG_TAG, "IN ScrollView.dispatchTouch, clearing flags");
146be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang                mWantToIntercept = false;
147be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang                mInterceptNormally = false;
148be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang                break;
149be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang        }
150be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang        if (mTouchableChild != null) {
151be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang            mTouchableChild.clearTouched();
152be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang        }
153d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler
154d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler        mScaleDetector.onTouchEvent(ev);
155f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler        mGestureDetector.onTouchEvent(ev);
156d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler
157be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang        final boolean handled = super.dispatchTouchEvent(ev);
158be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang        LogUtils.d(LOG_TAG, "OUT ScrollView.dispatchTouch, handled=%s ev=%s", handled, ev);
159be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang
160d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler        if (mWantToIntercept && !mInScaleGesture) {
161be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang            final boolean touchedChild = (mTouchableChild != null && mTouchableChild.wasTouched());
162be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang            if (touchedChild) {
163be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang                // also give the event to this scroll view if the WebView got the event
164be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang                // and didn't stop any parent interception
165be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang                LogUtils.d(LOG_TAG, "IN extra ScrollView.onTouch, ev=%s", ev);
166be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang                onTouchEvent(ev);
167be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang            } else {
168be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang                mInterceptNormally = true;
169be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang                mWantToIntercept = false;
170be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang            }
171be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang        }
172be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang
173be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang        return handled;
174be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    }
175be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang
17650554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    @Override
177d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler    public boolean onScale(ScaleGestureDetector detector) {
178d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler        return true;
179d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler    }
180d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler
181d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler    @Override
182d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler    public boolean onScaleBegin(ScaleGestureDetector detector) {
183d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler        LogUtils.d(LOG_TAG, "Begin scale gesture");
184d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler        mInScaleGesture = true;
185d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler        return true;
186d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler    }
187d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler
188d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler    @Override
189d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler    public void onScaleEnd(ScaleGestureDetector detector) {
190d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler        LogUtils.d(LOG_TAG, "End scale gesture");
191d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler        mInScaleGesture = false;
192d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler    }
193d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler
194d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler    @Override
195f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler    public boolean onSingleTapConfirmed(MotionEvent e) {
196f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler        return false;
197f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler    }
198f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler
199f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler    @Override
200f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler    public boolean onDoubleTap(MotionEvent e) {
201f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler        mDoubleTapOccurred = true;
202f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler        return false;
203f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler    }
204f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler
205f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler    @Override
206f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler    public boolean onDoubleTapEvent(MotionEvent e) {
207f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler        final int action = e.getAction();
208f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler        boolean handled = false;
209f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler
210f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler        switch (action) {
211f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler            case MotionEvent.ACTION_DOWN:
212f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler                mDownFocusX = e.getX();
213f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler                mDownFocusY = e.getY();
214f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler                break;
215f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler            case MotionEvent.ACTION_UP:
216f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler                handled = triggerZoom();
217f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler                break;
218f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler            case MotionEvent.ACTION_MOVE:
219f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler                final int deltaX = (int) (e.getX() - mDownFocusX);
220f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler                final int deltaY = (int) (e.getY() - mDownFocusY);
221f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler                int distance = (deltaX * deltaX) + (deltaY * deltaY);
222f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler                if (distance > mTouchSlopSquared) {
223f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler                    mDoubleTapOccurred = false;
224f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler                }
225f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler                break;
226f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler
227f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler        }
228f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler        return handled;
229f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler    }
230f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler
231f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler    private boolean triggerZoom() {
232f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler        boolean handled = false;
233f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler        if (mDoubleTapOccurred) {
2349d3b1dd6e411bba6c415e19d51e32569956a8bb7Tony Mantler            if (mZoomedIn) {
2359d3b1dd6e411bba6c415e19d51e32569956a8bb7Tony Mantler                mTouchableChild.zoomOut();
2369d3b1dd6e411bba6c415e19d51e32569956a8bb7Tony Mantler            } else {
2379d3b1dd6e411bba6c415e19d51e32569956a8bb7Tony Mantler                mTouchableChild.zoomIn();
238f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler            }
2399d3b1dd6e411bba6c415e19d51e32569956a8bb7Tony Mantler            mZoomedIn = !mZoomedIn;
2409d3b1dd6e411bba6c415e19d51e32569956a8bb7Tony Mantler            LogUtils.d(LogUtils.TAG, "Trigger Zoom!");
2419d3b1dd6e411bba6c415e19d51e32569956a8bb7Tony Mantler            handled = true;
242f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler        }
243f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler        mDoubleTapOccurred = false;
244f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler        return handled;
245f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler    }
246f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler
247f2dabb90c33740189112757349c49ef9e48ea541Tony Mantler    @Override
24850554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    public void addScrollListener(ScrollListener l) {
24950554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon        mScrollListeners.add(l);
25050554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    }
25150554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon
25250554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    @Override
25350554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    public void removeScrollListener(ScrollListener l) {
25450554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon        mScrollListeners.remove(l);
25550554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    }
25650554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon
25750554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    @Override
25850554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
25950554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon        super.onScrollChanged(l, t, oldl, oldt);
26050554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon        for (ScrollListener listener : mScrollListeners) {
26150554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon            listener.onNotifierScroll(t);
26250554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon        }
26350554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    }
26450554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon
26550554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    @Override
26650554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    public int computeVerticalScrollRange() {
26750554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon        return super.computeVerticalScrollRange();
26850554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    }
26950554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon
27050554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    @Override
27150554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    public int computeVerticalScrollOffset() {
27250554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon        return super.computeVerticalScrollOffset();
27350554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    }
27450554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon
27550554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    @Override
27650554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    public int computeVerticalScrollExtent() {
27750554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon        return super.computeVerticalScrollExtent();
27850554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    }
27950554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon
28050554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    @Override
28150554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    public int computeHorizontalScrollRange() {
28250554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon        return super.computeHorizontalScrollRange();
28350554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    }
28450554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon
28550554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    @Override
28650554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    public int computeHorizontalScrollOffset() {
28750554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon        return super.computeHorizontalScrollOffset();
28850554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    }
28950554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon
29050554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    @Override
29150554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    public int computeHorizontalScrollExtent() {
29250554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon        return super.computeHorizontalScrollExtent();
29350554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    }
294be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang}
295