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