MessageScrollView.java revision d4dae4817fdddbd7a43f18ddbef38815d6e0a182
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;
22be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huangimport android.view.MotionEvent;
23d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantlerimport android.view.ScaleGestureDetector;
24be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huangimport android.widget.ScrollView;
25be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang
26be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huangimport com.android.mail.utils.LogUtils;
27be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang
2850554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdonimport java.util.Set;
2950554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdonimport java.util.concurrent.CopyOnWriteArraySet;
3050554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon
31be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang/**
32be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang * A container that tries to play nice with an internally scrollable {@link Touchable} child view.
33be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang * The assumption is that the child view can scroll horizontally, but not vertically, so any
34be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang * touch events on that child view should ALSO be sent here so it can simultaneously vertically
35be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang * scroll (not the standard either/or behavior).
36be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang * <p>
37be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang * Touch events on any other child of this ScrollView are intercepted in the standard fashion.
38be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang */
39d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantlerpublic class MessageScrollView extends ScrollView implements ScrollNotifier,
40d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler        ScaleGestureDetector.OnScaleGestureListener {
41be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang
42be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    /**
43be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang     * A View that reports whether onTouchEvent() was recently called.
44be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang     */
45be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    public interface Touchable {
46be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang        boolean wasTouched();
47be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang        void clearTouched();
48be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    }
49be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang
50be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    /**
51be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang     * True when performing "special" interception.
52be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang     */
53be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    private boolean mWantToIntercept;
54be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    /**
55be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang     * Whether to perform the standard touch interception procedure. This is set to true when we
56be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang     * want to intercept a touch stream from any child OTHER than {@link #mTouchableChild}.
57be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang     */
58be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    private boolean mInterceptNormally;
59be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    /**
60be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang     * The special child that we want to NOT intercept from in the normal way. Instead, this child
61be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang     * will continue to receive the touch event stream (so it can handle the horizontal component)
62be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang     * while this parent will additionally handle the events to perform vertical scrolling.
63be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang     */
64be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    private Touchable mTouchableChild;
65be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang
66d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler    /**
67d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler     * We want to detect the scale gesture so that we don't try to scroll instead, but we don't
68d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler     * care about actually interpreting it because the webview does that by itself when it handles
69d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler     * the touch events.
70d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler     *
71d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler     * This might lead to really weird interactions if the two gesture detectors' implementations
72d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler     * drift...
73d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler     */
74d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler    private ScaleGestureDetector mScaleDetector;
75d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler    private boolean mInScaleGesture;
76d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler
7750554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    private final Set<ScrollListener> mScrollListeners =
7850554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon            new CopyOnWriteArraySet<ScrollListener>();
7950554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon
80be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    public static final String LOG_TAG = "MsgScroller";
81be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang
82be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    public MessageScrollView(Context c) {
83be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang        this(c, null);
84be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    }
85be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang
86be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    public MessageScrollView(Context c, AttributeSet attrs) {
87be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang        super(c, attrs);
88d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler        mScaleDetector = new ScaleGestureDetector(c, this);
89be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    }
90be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang
91be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    public void setInnerScrollableView(Touchable child) {
92be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang        mTouchableChild = child;
93be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    }
94be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang
95be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    @Override
96be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    public boolean onInterceptTouchEvent(MotionEvent ev) {
97be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang        if (mInterceptNormally) {
98be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang            LogUtils.d(LOG_TAG, "IN ScrollView.onIntercept, NOW stealing. ev=%s", ev);
99be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang            return true;
100be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang        } else if (mWantToIntercept) {
101be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang            LogUtils.d(LOG_TAG, "IN ScrollView.onIntercept, already stealing. ev=%s", ev);
102be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang            return false;
103be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang        }
104be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang
105be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang        mWantToIntercept = super.onInterceptTouchEvent(ev);
106be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang        LogUtils.d(LOG_TAG, "OUT ScrollView.onIntercept, steal=%s ev=%s", mWantToIntercept, ev);
107be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang        return false;
108be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    }
109be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang
110be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    @Override
111be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    public boolean dispatchTouchEvent(MotionEvent ev) {
112be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang        final int action = ev.getActionMasked();
113be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang        switch (action) {
114be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang            case MotionEvent.ACTION_DOWN:
115be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang                LogUtils.d(LOG_TAG, "IN ScrollView.dispatchTouch, clearing flags");
116be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang                mWantToIntercept = false;
117be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang                mInterceptNormally = false;
118be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang                break;
119be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang        }
120be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang        if (mTouchableChild != null) {
121be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang            mTouchableChild.clearTouched();
122be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang        }
123d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler
124d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler        mScaleDetector.onTouchEvent(ev);
125d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler
126be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang        final boolean handled = super.dispatchTouchEvent(ev);
127be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang        LogUtils.d(LOG_TAG, "OUT ScrollView.dispatchTouch, handled=%s ev=%s", handled, ev);
128be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang
129d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler        if (mWantToIntercept && !mInScaleGesture) {
130be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang            final boolean touchedChild = (mTouchableChild != null && mTouchableChild.wasTouched());
131be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang            if (touchedChild) {
132be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang                // also give the event to this scroll view if the WebView got the event
133be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang                // and didn't stop any parent interception
134be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang                LogUtils.d(LOG_TAG, "IN extra ScrollView.onTouch, ev=%s", ev);
135be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang                onTouchEvent(ev);
136be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang            } else {
137be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang                mInterceptNormally = true;
138be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang                mWantToIntercept = false;
139be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang            }
140be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang        }
141be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang
142be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang        return handled;
143be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang    }
144be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang
14550554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    @Override
146d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler    public boolean onScale(ScaleGestureDetector detector) {
147d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler        return true;
148d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler    }
149d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler
150d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler    @Override
151d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler    public boolean onScaleBegin(ScaleGestureDetector detector) {
152d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler        LogUtils.d(LOG_TAG, "Begin scale gesture");
153d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler        mInScaleGesture = true;
154d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler        return true;
155d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler    }
156d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler
157d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler    @Override
158d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler    public void onScaleEnd(ScaleGestureDetector detector) {
159d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler        LogUtils.d(LOG_TAG, "End scale gesture");
160d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler        mInScaleGesture = false;
161d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler    }
162d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler
163d4dae4817fdddbd7a43f18ddbef38815d6e0a182Tony Mantler    @Override
16450554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    public void addScrollListener(ScrollListener l) {
16550554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon        mScrollListeners.add(l);
16650554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    }
16750554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon
16850554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    @Override
16950554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    public void removeScrollListener(ScrollListener l) {
17050554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon        mScrollListeners.remove(l);
17150554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    }
17250554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon
17350554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    @Override
17450554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
17550554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon        super.onScrollChanged(l, t, oldl, oldt);
17650554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon        for (ScrollListener listener : mScrollListeners) {
17750554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon            listener.onNotifierScroll(t);
17850554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon        }
17950554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    }
18050554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon
18150554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon
18250554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    @Override
18350554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    public int computeVerticalScrollRange() {
18450554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon        return super.computeVerticalScrollRange();
18550554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    }
18650554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon
18750554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    @Override
18850554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    public int computeVerticalScrollOffset() {
18950554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon        return super.computeVerticalScrollOffset();
19050554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    }
19150554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon
19250554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    @Override
19350554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    public int computeVerticalScrollExtent() {
19450554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon        return super.computeVerticalScrollExtent();
19550554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    }
19650554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon
19750554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    @Override
19850554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    public int computeHorizontalScrollRange() {
19950554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon        return super.computeHorizontalScrollRange();
20050554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    }
20150554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon
20250554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    @Override
20350554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    public int computeHorizontalScrollOffset() {
20450554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon        return super.computeHorizontalScrollOffset();
20550554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    }
20650554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon
20750554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    @Override
20850554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    public int computeHorizontalScrollExtent() {
20950554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon        return super.computeHorizontalScrollExtent();
21050554de30bfe85befac6edf5376b600eb3a1f462Martin Hibdon    }
211be7739bf0cebc5018c8ffb0c649fc62a74d74e69Andy Huang}
212