1bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook/* 2bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * Copyright (C) 2011 The Android Open Source Project 3bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * 4bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * Licensed under the Apache License, Version 2.0 (the "License"); 5bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * you may not use this file except in compliance with the License. 6bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * You may obtain a copy of the License at 7bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * 8bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * http://www.apache.org/licenses/LICENSE-2.0 9bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * 10bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * Unless required by applicable law or agreed to in writing, software 11bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * distributed under the License is distributed on an "AS IS" BASIS, 12bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * See the License for the specific language governing permissions and 14bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * limitations under the License. 15bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook */ 16bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook 17bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook 18bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrookpackage com.android.email.view; 19bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook 20bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrookimport android.content.Context; 21bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrookimport android.graphics.Rect; 22bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrookimport android.util.AttributeSet; 23bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrookimport android.view.MotionEvent; 24bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrookimport android.view.View; 25bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrookimport android.view.ViewGroup; 26bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrookimport android.webkit.WebView; 27bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrookimport android.widget.ScrollView; 28bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook 29bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrookimport java.util.ArrayList; 30bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook 31bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook/** 32bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * A {@link ScrollView} that will never lock scrolling in a particular direction. 33bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * 34bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * Usually ScrollView will capture all touch events once a drag has begun. In some cases, 35bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * we want to delegate those touches to children as normal, even in the middle of a drag. This is 36bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * useful when there are childviews like a WebView tha handles scrolling in the horizontal direction 37bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * even while the ScrollView drags vertically. 38bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * 39bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * This is only tested to work for ScrollViews where the content scrolls in one direction. 40bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook */ 41bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrookpublic class NonLockingScrollView extends ScrollView { 42bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook public NonLockingScrollView(Context context) { 43bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook super(context); 44bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } 45bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook public NonLockingScrollView(Context context, AttributeSet attrs) { 46bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook super(context, attrs); 47bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } 48bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook public NonLockingScrollView(Context context, AttributeSet attrs, int defStyle) { 49bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook super(context, attrs, defStyle); 50bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } 51bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook 52bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook /** 53bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * Whether or not the contents of this view is being dragged by one of the children in 54bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * {@link #mChildrenNeedingAllTouches}. 55bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook */ 56bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook private boolean mInCustomDrag = false; 57bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook 58bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook /** 59bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * The list of children who should always receive touch events, and not have them intercepted. 60bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook */ 61bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook private final ArrayList<View> mChildrenNeedingAllTouches = new ArrayList<View>(); 62bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook 63bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook @Override 64bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook public boolean onInterceptTouchEvent(MotionEvent ev) { 65bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook final int action = ev.getActionMasked(); 66bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook final boolean isUp = action == MotionEvent.ACTION_UP; 67bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook 68bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook if (isUp && mInCustomDrag) { 69bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook // An up event after a drag should be intercepted so that child views don't handle 70bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook // click events falsely after a drag. 71bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook mInCustomDrag = false; 72bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook onTouchEvent(ev); 73bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook return true; 74bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } 75bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook 76bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook if (!mInCustomDrag && !isEventOverChild(ev, mChildrenNeedingAllTouches)) { 77bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook return super.onInterceptTouchEvent(ev); 78bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } 79bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook 80bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook // Note the normal scrollview implementation is to intercept all touch events after it has 81bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook // detected a drag starting. We will handle this ourselves. 82bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook mInCustomDrag = super.onInterceptTouchEvent(ev); 83bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook if (mInCustomDrag) { 84bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook onTouchEvent(ev); 85bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } 86bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook 87bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook // Don't intercept events - pass them on to children as normal. 88bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook return false; 89bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } 90bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook 91bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook @Override 92bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook protected void onFinishInflate() { 93bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook super.onFinishInflate(); 94bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook excludeChildrenFromInterceptions(this); 95bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } 96bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook 97bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook /** 98bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * Traverses the view tree for {@link WebView}s so they can be excluded from touch 99bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * interceptions and receive all events. 100bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook */ 101bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook private void excludeChildrenFromInterceptions(View node) { 102bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook // If additional types of children should be excluded (e.g. horizontal scrolling banners), 103bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook // this needs to be modified accordingly. 104bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook if (node instanceof WebView) { 105bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook mChildrenNeedingAllTouches.add(node); 106bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } else if (node instanceof ViewGroup) { 107bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook ViewGroup viewGroup = (ViewGroup) node; 108bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook final int childCount = viewGroup.getChildCount(); 109bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook for (int i = 0; i < childCount; i++) { 110bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook final View child = viewGroup.getChildAt(i); 111bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook excludeChildrenFromInterceptions(child); 112bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } 113bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } 114bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } 115bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook 116bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook private static final Rect sHitFrame = new Rect(); 117bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook private static boolean isEventOverChild(MotionEvent ev, ArrayList<View> children) { 118bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook final int actionIndex = ev.getActionIndex(); 119bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook final float x = ev.getX(actionIndex); 120bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook final float y = ev.getY(actionIndex); 121bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook 122bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook for (View child : children) { 123bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook if (!canViewReceivePointerEvents(child)) { 124bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook continue; 125bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } 126bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook child.getHitRect(sHitFrame); 127bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook 128bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook // child can receive the motion event. 129bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook if (sHitFrame.contains((int) x, (int) y)) { 130bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook return true; 131bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } 132bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } 133bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook return false; 134bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } 135bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook 136bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook private static boolean canViewReceivePointerEvents(View child) { 137bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook return child.getVisibility() == VISIBLE || (child.getAnimation() != null); 138bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } 139bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook} 140