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