1a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb/*
2a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb * Copyright (C) 2006 The Android Open Source Project
3a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb *
4a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb * Licensed under the Apache License, Version 2.0 (the "License");
5a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb * you may not use this file except in compliance with the License.
6a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb * You may obtain a copy of the License at
7a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb *
8a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb *      http://www.apache.org/licenses/LICENSE-2.0
9a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb *
10a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb * Unless required by applicable law or agreed to in writing, software
11a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb * distributed under the License is distributed on an "AS IS" BASIS,
12a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb * See the License for the specific language governing permissions and
14a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb * limitations under the License.
15a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb */
16a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
17a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolbpackage com.android.browser.view;
18a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
19a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolbimport android.content.Context;
20a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolbimport android.content.res.TypedArray;
21a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolbimport android.graphics.PointF;
22a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolbimport android.graphics.Rect;
23a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolbimport android.os.StrictMode;
24a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolbimport android.util.AttributeSet;
25a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolbimport android.view.FocusFinder;
26a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolbimport android.view.InputDevice;
27a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolbimport android.view.KeyEvent;
28a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolbimport android.view.MotionEvent;
29a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolbimport android.view.VelocityTracker;
30a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolbimport android.view.View;
31a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolbimport android.view.ViewConfiguration;
32a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolbimport android.view.ViewDebug;
33a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolbimport android.view.ViewGroup;
34a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolbimport android.view.ViewParent;
35a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolbimport android.view.accessibility.AccessibilityEvent;
36a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolbimport android.view.accessibility.AccessibilityNodeInfo;
37a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolbimport android.view.animation.AnimationUtils;
38a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolbimport android.widget.FrameLayout;
39a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolbimport android.widget.LinearLayout;
40a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolbimport android.widget.OverScroller;
41a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolbimport android.widget.TextView;
42a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
43a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolbimport com.android.internal.R;
44a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
45a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolbimport java.util.List;
46a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
47a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb/**
48a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb * Layout container for a view hierarchy that can be scrolled by the user,
49a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb * allowing it to be larger than the physical display.  A ScrollView
50a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb * is a {@link FrameLayout}, meaning you should place one child in it
51a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb * containing the entire contents to scroll; this child may itself be a layout
52a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb * manager with a complex hierarchy of objects.  A child that is often used
53a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb * is a {@link LinearLayout} in a vertical orientation, presenting a vertical
54a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb * array of top-level items that the user can scroll through.
55a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb *
56a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb * <p>The {@link TextView} class also
57a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb * takes care of its own scrolling, so does not require a ScrollView, but
58a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb * using the two together is possible to achieve the effect of a text view
59a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb * within a larger container.
60a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb *
61a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb * <p>ScrollView only supports vertical scrolling.
62a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb *
63a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb * @attr ref android.R.styleable#ScrollView_fillViewport
64a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb */
65a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolbpublic class ScrollerView extends FrameLayout {
66a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    static final int ANIMATED_SCROLL_GAP = 250;
67a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
68a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    static final float MAX_SCROLL_FACTOR = 0.5f;
69a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
70a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    private long mLastScroll;
71a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
72a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    private final Rect mTempRect = new Rect();
73a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    protected OverScroller mScroller;
74a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
75a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    /**
76a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * Position of the last motion event.
77a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     */
78a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    private float mLastMotionY;
79a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
80a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    /**
81a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * True when the layout has changed but the traversal has not come through yet.
82a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * Ideally the view hierarchy would keep track of this for us.
83a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     */
84a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    private boolean mIsLayoutDirty = true;
85a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
86a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    /**
87a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * The child to give focus to in the event that a child has requested focus while the
88a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * layout is dirty. This prevents the scroll from being wrong if the child has not been
89a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * laid out before requesting focus.
90a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     */
91a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    protected View mChildToScrollTo = null;
92a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
93a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    /**
94a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * True if the user is currently dragging this ScrollView around. This is
95a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * not the same as 'is being flinged', which can be checked by
96a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * mScroller.isFinished() (flinging begins when the user lifts his finger).
97a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     */
98a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    protected boolean mIsBeingDragged = false;
99a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
100a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    /**
101a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * Determines speed during touch scrolling
102a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     */
103a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    private VelocityTracker mVelocityTracker;
104a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
105a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    /**
106a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * When set to true, the scroll view measure its child to make it fill the currently
107a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * visible area.
108a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     */
109a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    @ViewDebug.ExportedProperty(category = "layout")
110a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    private boolean mFillViewport;
111a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
112a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    /**
113a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * Whether arrow scrolling is animated.
114a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     */
115a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    private boolean mSmoothScrollingEnabled = true;
116a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
117a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    private int mTouchSlop;
118a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    protected int mMinimumVelocity;
119a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    private int mMaximumVelocity;
120a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
121a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    private int mOverscrollDistance;
122a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    private int mOverflingDistance;
123a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
124a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    /**
125a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * ID of the active pointer. This is used to retain consistency during
126a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * drags/flings if multiple pointers are used.
127a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     */
128a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    private int mActivePointerId = INVALID_POINTER;
129a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
130a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    /**
131a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * The StrictMode "critical time span" objects to catch animation
132a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * stutters.  Non-null when a time-sensitive animation is
133a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * in-flight.  Must call finish() on them when done animating.
134a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * These are no-ops on user builds.
135a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     */
136a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    private StrictMode.Span mScrollStrictSpan = null;  // aka "drag"
137a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    private StrictMode.Span mFlingStrictSpan = null;
138a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
139a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    /**
140a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * Sentinel value for no current active pointer.
141a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * Used by {@link #mActivePointerId}.
142a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     */
143a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    private static final int INVALID_POINTER = -1;
144a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
145a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    /**
146a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * orientation of the scrollview
147a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     */
148a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    protected boolean mHorizontal;
149a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
150a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    protected boolean mIsOrthoDragged;
151a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    private float mLastOrthoCoord;
152a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    private View mDownView;
153a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    private PointF mDownCoords;
154a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
155a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
156a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    public ScrollerView(Context context) {
157a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        this(context, null);
158a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
159a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
160a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    public ScrollerView(Context context, AttributeSet attrs) {
161a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        this(context, attrs, com.android.internal.R.attr.scrollViewStyle);
162a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
163a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
164a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    public ScrollerView(Context context, AttributeSet attrs, int defStyle) {
165a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        super(context, attrs, defStyle);
166a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        initScrollView();
167a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
168a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        TypedArray a =
169a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.ScrollView, defStyle, 0);
170a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
171a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        setFillViewport(a.getBoolean(R.styleable.ScrollView_fillViewport, false));
172a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
173a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        a.recycle();
174a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
175a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
176a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    private void initScrollView() {
177a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        mScroller = new OverScroller(getContext());
178a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        setFocusable(true);
179a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
180a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        setWillNotDraw(false);
181a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        final ViewConfiguration configuration = ViewConfiguration.get(mContext);
182a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        mTouchSlop = configuration.getScaledTouchSlop();
183a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
184a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
185a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        mOverscrollDistance = configuration.getScaledOverscrollDistance();
186a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        mOverflingDistance = configuration.getScaledOverflingDistance();
187a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        mDownCoords = new PointF();
188a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
189a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
190a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    public void setOrientation(int orientation) {
191a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        mHorizontal = (orientation == LinearLayout.HORIZONTAL);
192a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        requestLayout();
193a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
194a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
195a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    @Override
196a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    public boolean shouldDelayChildPressedState() {
197a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        return true;
198a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
199a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
200a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    @Override
201a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    protected float getTopFadingEdgeStrength() {
202a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (getChildCount() == 0) {
203a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            return 0.0f;
204a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
205a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (mHorizontal) {
206a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            final int length = getHorizontalFadingEdgeLength();
207a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            if (mScrollX < length) {
208a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                return mScrollX / (float) length;
209a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            }
210a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        } else {
211a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            final int length = getVerticalFadingEdgeLength();
212a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            if (mScrollY < length) {
213a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                return mScrollY / (float) length;
214a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            }
215a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
216a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        return 1.0f;
217a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
218a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
219a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    @Override
220a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    protected float getBottomFadingEdgeStrength() {
221a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (getChildCount() == 0) {
222a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            return 0.0f;
223a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
224a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (mHorizontal) {
225a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            final int length = getHorizontalFadingEdgeLength();
226a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            final int bottomEdge = getWidth() - mPaddingRight;
227a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            final int span = getChildAt(0).getRight() - mScrollX - bottomEdge;
228a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            if (span < length) {
229a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                return span / (float) length;
230a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            }
231a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        } else {
232a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            final int length = getVerticalFadingEdgeLength();
233a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            final int bottomEdge = getHeight() - mPaddingBottom;
234a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            final int span = getChildAt(0).getBottom() - mScrollY - bottomEdge;
235a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            if (span < length) {
236a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                return span / (float) length;
237a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            }
238a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
239a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        return 1.0f;
240a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
241a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
242a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    /**
243a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * @return The maximum amount this scroll view will scroll in response to
244a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     *   an arrow event.
245a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     */
246a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    public int getMaxScrollAmount() {
247a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        return (int) (MAX_SCROLL_FACTOR * (mHorizontal
248a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                ? (mRight - mLeft) : (mBottom - mTop)));
249a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
250a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
251a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
252a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    @Override
253a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    public void addView(View child) {
254a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (getChildCount() > 0) {
255a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            throw new IllegalStateException("ScrollView can host only one direct child");
256a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
257a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
258a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        super.addView(child);
259a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
260a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
261a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    @Override
262a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    public void addView(View child, int index) {
263a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (getChildCount() > 0) {
264a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            throw new IllegalStateException("ScrollView can host only one direct child");
265a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
266a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
267a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        super.addView(child, index);
268a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
269a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
270a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    @Override
271a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    public void addView(View child, ViewGroup.LayoutParams params) {
272a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (getChildCount() > 0) {
273a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            throw new IllegalStateException("ScrollView can host only one direct child");
274a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
275a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
276a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        super.addView(child, params);
277a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
278a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
279a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    @Override
280a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    public void addView(View child, int index, ViewGroup.LayoutParams params) {
281a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (getChildCount() > 0) {
282a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            throw new IllegalStateException("ScrollView can host only one direct child");
283a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
284a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
285a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        super.addView(child, index, params);
286a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
287a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
288a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    /**
289a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * @return Returns true this ScrollView can be scrolled
290a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     */
291a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    private boolean canScroll() {
292a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        View child = getChildAt(0);
293a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (child != null) {
294a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            if (mHorizontal) {
295a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                return getWidth() < child.getWidth() + mPaddingLeft + mPaddingRight;
296a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            } else {
297a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                return getHeight() < child.getHeight() + mPaddingTop + mPaddingBottom;
298a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            }
299a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
300a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        return false;
301a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
302a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
303a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    /**
304a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * Indicates whether this ScrollView's content is stretched to fill the viewport.
305a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     *
306a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * @return True if the content fills the viewport, false otherwise.
307a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     *
308a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * @attr ref android.R.styleable#ScrollView_fillViewport
309a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     */
310a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    public boolean isFillViewport() {
311a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        return mFillViewport;
312a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
313a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
314a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    /**
315a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * Indicates this ScrollView whether it should stretch its content height to fill
316a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * the viewport or not.
317a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     *
318a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * @param fillViewport True to stretch the content's height to the viewport's
319a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     *        boundaries, false otherwise.
320a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     *
321a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * @attr ref android.R.styleable#ScrollView_fillViewport
322a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     */
323a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    public void setFillViewport(boolean fillViewport) {
324a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (fillViewport != mFillViewport) {
325a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            mFillViewport = fillViewport;
326a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            requestLayout();
327a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
328a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
329a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
330a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    /**
331a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * @return Whether arrow scrolling will animate its transition.
332a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     */
333a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    public boolean isSmoothScrollingEnabled() {
334a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        return mSmoothScrollingEnabled;
335a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
336a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
337a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    /**
338a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * Set whether arrow scrolling will animate its transition.
339a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * @param smoothScrollingEnabled whether arrow scrolling will animate its transition
340a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     */
341a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    public void setSmoothScrollingEnabled(boolean smoothScrollingEnabled) {
342a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        mSmoothScrollingEnabled = smoothScrollingEnabled;
343a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
344a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
345a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    @Override
346a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
347a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
348a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
349a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (!mFillViewport) {
350a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            return;
351a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
352a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
353a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
354a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (heightMode == MeasureSpec.UNSPECIFIED) {
355a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            return;
356a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
357a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
358a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (getChildCount() > 0) {
359a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            final View child = getChildAt(0);
360a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            if (mHorizontal) {
361a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                int width = getMeasuredWidth();
362a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                if (child.getMeasuredWidth() < width) {
363a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    final FrameLayout.LayoutParams lp = (LayoutParams) child
364a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            .getLayoutParams();
365a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
366a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    int childHeightMeasureSpec = getChildMeasureSpec(
367a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            heightMeasureSpec, mPaddingTop + mPaddingBottom,
368a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            lp.height);
369a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    width -= mPaddingLeft;
370a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    width -= mPaddingRight;
371a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
372a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            width, MeasureSpec.EXACTLY);
373a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
374a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
375a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                }
376a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            } else {
377a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                int height = getMeasuredHeight();
378a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                if (child.getMeasuredHeight() < height) {
379a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    final FrameLayout.LayoutParams lp = (LayoutParams) child
380a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            .getLayoutParams();
381a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
382a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    int childWidthMeasureSpec = getChildMeasureSpec(
383a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            widthMeasureSpec, mPaddingLeft + mPaddingRight,
384a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            lp.width);
385a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    height -= mPaddingTop;
386a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    height -= mPaddingBottom;
387a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
388a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            height, MeasureSpec.EXACTLY);
389a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
390a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
391a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                }
392a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            }
393a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
394a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
395a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
396a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    @Override
397a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    public boolean dispatchKeyEvent(KeyEvent event) {
398a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        // Let the focused view and/or our descendants get the key first
399a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        return super.dispatchKeyEvent(event) || executeKeyEvent(event);
400a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
401a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
402a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    /**
403a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * You can call this function yourself to have the scroll view perform
404a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * scrolling from a key event, just as if the event had been dispatched to
405a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * it by the view hierarchy.
406a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     *
407a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * @param event The key event to execute.
408a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * @return Return true if the event was handled, else false.
409a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     */
410a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    public boolean executeKeyEvent(KeyEvent event) {
411a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        mTempRect.setEmpty();
412a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
413a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (!canScroll()) {
414a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            if (isFocused() && event.getKeyCode() != KeyEvent.KEYCODE_BACK) {
415a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                View currentFocused = findFocus();
416a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                if (currentFocused == this) currentFocused = null;
417a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                View nextFocused = FocusFinder.getInstance().findNextFocus(this,
418a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        currentFocused, View.FOCUS_DOWN);
419a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                return nextFocused != null
420a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        && nextFocused != this
421a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        && nextFocused.requestFocus(View.FOCUS_DOWN);
422a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            }
423a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            return false;
424a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
425a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
426a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        boolean handled = false;
427a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (event.getAction() == KeyEvent.ACTION_DOWN) {
428a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            switch (event.getKeyCode()) {
429a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                case KeyEvent.KEYCODE_DPAD_UP:
430a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    if (!event.isAltPressed()) {
431a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        handled = arrowScroll(View.FOCUS_UP);
432a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    } else {
433a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        handled = fullScroll(View.FOCUS_UP);
434a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    }
435a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    break;
436a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                case KeyEvent.KEYCODE_DPAD_DOWN:
437a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    if (!event.isAltPressed()) {
438a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        handled = arrowScroll(View.FOCUS_DOWN);
439a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    } else {
440a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        handled = fullScroll(View.FOCUS_DOWN);
441a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    }
442a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    break;
443a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                case KeyEvent.KEYCODE_SPACE:
444a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    pageScroll(event.isShiftPressed() ? View.FOCUS_UP : View.FOCUS_DOWN);
445a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    break;
446a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            }
447a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
448a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
449a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        return handled;
450a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
451a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
452a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    private boolean inChild(int x, int y) {
453a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (getChildCount() > 0) {
454a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            final int scrollY = mScrollY;
455a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            final View child = getChildAt(0);
456a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            return !(y < child.getTop() - scrollY
457a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    || y >= child.getBottom() - scrollY
458a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    || x < child.getLeft()
459a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    || x >= child.getRight());
460a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
461a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        return false;
462a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
463a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
464a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    private void initOrResetVelocityTracker() {
465a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (mVelocityTracker == null) {
466a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            mVelocityTracker = VelocityTracker.obtain();
467a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        } else {
468a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            mVelocityTracker.clear();
469a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
470a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
471a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
472a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    private void initVelocityTrackerIfNotExists() {
473a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (mVelocityTracker == null) {
474a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            mVelocityTracker = VelocityTracker.obtain();
475a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
476a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
477a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
478a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    private void recycleVelocityTracker() {
479a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (mVelocityTracker != null) {
480a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            mVelocityTracker.recycle();
481a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            mVelocityTracker = null;
482a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
483a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
484a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
485a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    @Override
486a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
487a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (disallowIntercept) {
488a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            recycleVelocityTracker();
489a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
490a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        super.requestDisallowInterceptTouchEvent(disallowIntercept);
491a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
492a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
493a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    @Override
494a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    public boolean onInterceptTouchEvent(MotionEvent ev) {
495a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        /*
496a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb         * This method JUST determines whether we want to intercept the motion.
497a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb         * If we return true, onMotionEvent will be called and we do the actual
498a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb         * scrolling there.
499a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb         */
500a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
501a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        /*
502a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb         * Shortcut the most recurring case: the user is in the dragging state
503a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb         * and he is moving his finger. We want to intercept this motion.
504a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb         */
505a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        final int action = ev.getAction();
506a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
507a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            return true;
508a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
509a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if ((action == MotionEvent.ACTION_MOVE) && (mIsOrthoDragged)) {
510a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            return true;
511a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
512a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        switch (action & MotionEvent.ACTION_MASK) {
513a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        case MotionEvent.ACTION_MOVE: {
514a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            /*
515a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb             * mIsBeingDragged == false, otherwise the shortcut would have
516a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb             * caught it. Check whether the user has moved far enough from his
517a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb             * original down touch.
518a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb             */
519a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
520a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            /*
521a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb             * Locally do absolute value. mLastMotionY is set to the y value of
522a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb             * the down event.
523a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb             */
524a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            final int activePointerId = mActivePointerId;
525a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            if (activePointerId == INVALID_POINTER) {
526a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                // If we don't have a valid id, the touch down wasn't on
527a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                // content.
528a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                break;
529a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            }
530a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
531a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            final int pointerIndex = ev.findPointerIndex(activePointerId);
532a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            final float y = mHorizontal ? ev.getX(pointerIndex) : ev
533a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    .getY(pointerIndex);
534a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            final int yDiff = (int) Math.abs(y - mLastMotionY);
535a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            if (yDiff > mTouchSlop) {
536a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                mIsBeingDragged = true;
537a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                mLastMotionY = y;
538a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                initVelocityTrackerIfNotExists();
539a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                mVelocityTracker.addMovement(ev);
540a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                if (mScrollStrictSpan == null) {
541a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    mScrollStrictSpan = StrictMode
542a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            .enterCriticalSpan("ScrollView-scroll");
543a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                }
544a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            } else {
545a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                final float ocoord = mHorizontal ? ev.getY(pointerIndex) : ev
546a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        .getX(pointerIndex);
547a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                if (Math.abs(ocoord - mLastOrthoCoord) > mTouchSlop) {
548a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    mIsOrthoDragged = true;
549a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    mLastOrthoCoord = ocoord;
550a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    initVelocityTrackerIfNotExists();
551a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    mVelocityTracker.addMovement(ev);
552a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                }
553a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            }
554a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            break;
555a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
556a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
557a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        case MotionEvent.ACTION_DOWN: {
558a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            final float y = mHorizontal ? ev.getX() : ev.getY();
559a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            mDownCoords.x = ev.getX();
560a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            mDownCoords.y = ev.getY();
561a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            if (!inChild((int) ev.getX(), (int) ev.getY())) {
562a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                mIsBeingDragged = false;
563a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                recycleVelocityTracker();
564a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                break;
565a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            }
566a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
567a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            /*
568a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb             * Remember location of down touch. ACTION_DOWN always refers to
569a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb             * pointer index 0.
570a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb             */
571a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            mLastMotionY = y;
572a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            mActivePointerId = ev.getPointerId(0);
573a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
574a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            initOrResetVelocityTracker();
575a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            mVelocityTracker.addMovement(ev);
576a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            /*
577a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb             * If being flinged and user touches the screen, initiate drag;
578a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb             * otherwise don't. mScroller.isFinished should be false when being
579a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb             * flinged.
580a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb             */
581a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            mIsBeingDragged = !mScroller.isFinished();
582a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            if (mIsBeingDragged && mScrollStrictSpan == null) {
583a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                mScrollStrictSpan = StrictMode
584a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        .enterCriticalSpan("ScrollView-scroll");
585a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            }
586a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            mIsOrthoDragged = false;
587a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            final float ocoord = mHorizontal ? ev.getY() : ev.getX();
588a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            mLastOrthoCoord = ocoord;
589a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            mDownView = findViewAt((int) ev.getX(), (int) ev.getY());
590a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            break;
591a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
592a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
593a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        case MotionEvent.ACTION_CANCEL:
594a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        case MotionEvent.ACTION_UP:
595a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            /* Release the drag */
596a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            mIsBeingDragged = false;
597a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            mIsOrthoDragged = false;
598a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            mActivePointerId = INVALID_POINTER;
599a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            recycleVelocityTracker();
600a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0,
601a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    getScrollRange())) {
602a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                invalidate();
603a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            }
604a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            break;
605a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        case MotionEvent.ACTION_POINTER_UP:
606a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            onSecondaryPointerUp(ev);
607a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            break;
608a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
609a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
610a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        /*
611a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb         * The only time we want to intercept motion events is if we are in the
612a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb         * drag mode.
613a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb         */
614a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        return mIsBeingDragged || mIsOrthoDragged;
615a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
616a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
617a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    @Override
618a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    public boolean onTouchEvent(MotionEvent ev) {
619a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        initVelocityTrackerIfNotExists();
620a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        mVelocityTracker.addMovement(ev);
621a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
622a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        final int action = ev.getAction();
623a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        switch (action & MotionEvent.ACTION_MASK) {
624a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            case MotionEvent.ACTION_DOWN: {
625a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                mIsBeingDragged = getChildCount() != 0;
626a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                if (!mIsBeingDragged) {
627a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    return false;
628a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                }
629a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
630a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                /*
631a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                 * If being flinged and user touches, stop the fling. isFinished
632a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                 * will be false if being flinged.
633a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                 */
634a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                if (!mScroller.isFinished()) {
635a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    mScroller.abortAnimation();
636a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    if (mFlingStrictSpan != null) {
637a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        mFlingStrictSpan.finish();
638a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        mFlingStrictSpan = null;
639a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    }
640a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                }
641a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
642a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                // Remember where the motion event started
643a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                mLastMotionY = mHorizontal ? ev.getX() : ev.getY();
644a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                mActivePointerId = ev.getPointerId(0);
645a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                break;
646a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            }
647a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            case MotionEvent.ACTION_MOVE:
648a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                if (mIsOrthoDragged) {
649a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    // Scroll to follow the motion event
650a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
651a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    final float x = ev.getX(activePointerIndex);
652a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    final float y = ev.getY(activePointerIndex);
653a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    if (isOrthoMove(x - mDownCoords.x, y - mDownCoords.y)) {
654a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        onOrthoDrag(mDownView, mHorizontal
655a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                                ? y - mDownCoords.y
656a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                                : x - mDownCoords.x);
657a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    }
658a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                } else if (mIsBeingDragged) {
659a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    // Scroll to follow the motion event
660a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
661a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    final float y = mHorizontal ? ev.getX(activePointerIndex)
662a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            : ev.getY(activePointerIndex);
663a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    final int deltaY = (int) (mLastMotionY - y);
664a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    mLastMotionY = y;
665a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
666a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    final int oldX = mScrollX;
667a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    final int oldY = mScrollY;
668a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    final int range = getScrollRange();
669a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    if (mHorizontal) {
670a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        if (overScrollBy(deltaY, 0, mScrollX, 0, range, 0,
671a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                                mOverscrollDistance, 0, true)) {
672a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            // Break our velocity if we hit a scroll barrier.
673a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            mVelocityTracker.clear();
674a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        }
675a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    } else {
676a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        if (overScrollBy(0, deltaY, 0, mScrollY, 0, range,
677a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                                0, mOverscrollDistance, true)) {
678a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            // Break our velocity if we hit a scroll barrier.
679a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            mVelocityTracker.clear();
680a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        }
681a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    }
682a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    onScrollChanged(mScrollX, mScrollY, oldX, oldY);
683a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
684a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    final int overscrollMode = getOverScrollMode();
685a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    if (overscrollMode == OVER_SCROLL_ALWAYS ||
686a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0)) {
687a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        final int pulledToY = mHorizontal ? oldX + deltaY : oldY + deltaY;
688a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        if (pulledToY < 0) {
689a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            onPull(pulledToY);
690a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        } else if (pulledToY > range) {
691a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            onPull(pulledToY - range);
692a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        } else {
693a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            onPull(0);
694a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        }
695a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    }
696a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                }
697a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                break;
698a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            case MotionEvent.ACTION_UP:
699a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                final VelocityTracker vtracker = mVelocityTracker;
700a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                vtracker.computeCurrentVelocity(1000, mMaximumVelocity);
701a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                if (isOrthoMove(vtracker.getXVelocity(mActivePointerId),
702a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        vtracker.getYVelocity(mActivePointerId))
703a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        && mMinimumVelocity < Math.abs((mHorizontal ? vtracker.getYVelocity()
704a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                                : vtracker.getXVelocity()))) {
705a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    onOrthoFling(mDownView, mHorizontal ? vtracker.getYVelocity()
706a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            : vtracker.getXVelocity());
707a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    break;
708a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                }
709a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                if (mIsOrthoDragged) {
710a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    onOrthoDragFinished(mDownView);
711a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    mActivePointerId = INVALID_POINTER;
712a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    endDrag();
713a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                } else if (mIsBeingDragged) {
714a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    final VelocityTracker velocityTracker = mVelocityTracker;
715a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
716a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    int initialVelocity = mHorizontal
717a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            ? (int) velocityTracker.getXVelocity(mActivePointerId)
718a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            : (int) velocityTracker.getYVelocity(mActivePointerId);
719a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
720a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    if (getChildCount() > 0) {
721a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
722a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            fling(-initialVelocity);
723a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        } else {
724a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            final int bottom = getScrollRange();
725a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            if (mHorizontal) {
726a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                                if (mScroller.springBack(mScrollX, mScrollY, 0, bottom, 0, 0)) {
727a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                                    invalidate();
728a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                                }
729a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            } else {
730a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                                if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, bottom)) {
731a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                                    invalidate();
732a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                                }
733a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            }
734a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        }
735a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        onPull(0);
736a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    }
737a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
738a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    mActivePointerId = INVALID_POINTER;
739a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    endDrag();
740a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                }
741a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                break;
742a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            case MotionEvent.ACTION_CANCEL:
743a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                if (mIsOrthoDragged) {
744a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    onOrthoDragFinished(mDownView);
745a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    mActivePointerId = INVALID_POINTER;
746a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    endDrag();
747a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                } else if (mIsBeingDragged && getChildCount() > 0) {
748a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    if (mHorizontal) {
749a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        if (mScroller.springBack(mScrollX, mScrollY, 0, getScrollRange(), 0, 0)) {
750a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            invalidate();
751a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        }
752a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    } else {
753a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) {
754a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            invalidate();
755a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        }
756a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    }
757a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    mActivePointerId = INVALID_POINTER;
758a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    endDrag();
759a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                }
760a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                break;
761a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            case MotionEvent.ACTION_POINTER_DOWN: {
762a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                final int index = ev.getActionIndex();
763a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                final float y = mHorizontal ? ev.getX(index) : ev.getY(index);
764a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                mLastMotionY = y;
765a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                mLastOrthoCoord = mHorizontal ? ev.getY(index) : ev.getX(index);
766a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                mActivePointerId = ev.getPointerId(index);
767a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                break;
768a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            }
769a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            case MotionEvent.ACTION_POINTER_UP:
770a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                onSecondaryPointerUp(ev);
771a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                mLastMotionY = mHorizontal
772a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        ? ev.getX(ev.findPointerIndex(mActivePointerId))
773a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        : ev.getY(ev.findPointerIndex(mActivePointerId));
774a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                break;
775a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
776a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        return true;
777a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
778a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
779a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    protected View findViewAt(int x, int y) {
780a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        // subclass responsibility
781a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        return null;
782a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
783a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
784a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    protected void onPull(int delta) {
785a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
786a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
787a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    private void onSecondaryPointerUp(MotionEvent ev) {
788a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
789a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                MotionEvent.ACTION_POINTER_INDEX_SHIFT;
790a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        final int pointerId = ev.getPointerId(pointerIndex);
791a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (pointerId == mActivePointerId) {
792a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            // This was our active pointer going up. Choose a new
793a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            // active pointer and adjust accordingly.
794a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            // TODO: Make this decision more intelligent.
795a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
796a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            mLastMotionY = mHorizontal ? ev.getX(newPointerIndex) : ev.getY(newPointerIndex);
797a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            mActivePointerId = ev.getPointerId(newPointerIndex);
798a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            if (mVelocityTracker != null) {
799a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                mVelocityTracker.clear();
800a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            }
801a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            mLastOrthoCoord = mHorizontal ? ev.getY(newPointerIndex)
802a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    : ev.getX(newPointerIndex);
803a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
804a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
805a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
806a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    @Override
807a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    public boolean onGenericMotionEvent(MotionEvent event) {
808a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
809a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            switch (event.getAction()) {
810a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            case MotionEvent.ACTION_SCROLL: {
811a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                if (!mIsBeingDragged) {
812a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    if (mHorizontal) {
813a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        final float hscroll = event
814a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                                .getAxisValue(MotionEvent.AXIS_HSCROLL);
815a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        if (hscroll != 0) {
816a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            final int delta = (int) (hscroll * getHorizontalScrollFactor());
817a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            final int range = getScrollRange();
818a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            int oldScrollX = mScrollX;
819a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            int newScrollX = oldScrollX - delta;
820a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            if (newScrollX < 0) {
821a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                                newScrollX = 0;
822a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            } else if (newScrollX > range) {
823a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                                newScrollX = range;
824a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            }
825a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            if (newScrollX != oldScrollX) {
826a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                                super.scrollTo(newScrollX, mScrollY);
827a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                                return true;
828a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            }
829a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        }
830a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    } else {
831a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        final float vscroll = event
832a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                                .getAxisValue(MotionEvent.AXIS_VSCROLL);
833a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        if (vscroll != 0) {
834a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            final int delta = (int) (vscroll * getVerticalScrollFactor());
835a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            final int range = getScrollRange();
836a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            int oldScrollY = mScrollY;
837a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            int newScrollY = oldScrollY - delta;
838a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            if (newScrollY < 0) {
839a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                                newScrollY = 0;
840a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            } else if (newScrollY > range) {
841a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                                newScrollY = range;
842a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            }
843a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            if (newScrollY != oldScrollY) {
844a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                                super.scrollTo(mScrollX, newScrollY);
845a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                                return true;
846a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            }
847a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        }
848a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    }
849a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                }
850a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            }
851a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            }
852a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
853a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        return super.onGenericMotionEvent(event);
854a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
855a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
856a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    protected void onOrthoDrag(View draggedView, float distance) {
857a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
858a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
859a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    protected void onOrthoDragFinished(View draggedView) {
860a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
861a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
862a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    protected void onOrthoFling(View draggedView, float velocity) {
863a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
864a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
865a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    @Override
866a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    protected void onOverScrolled(int scrollX, int scrollY,
867a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            boolean clampedX, boolean clampedY) {
868a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        // Treat animating scrolls differently; see #computeScroll() for why.
869a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (!mScroller.isFinished()) {
870a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            mScrollX = scrollX;
871a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            mScrollY = scrollY;
872a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            invalidateParentIfNeeded();
873a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            if (mHorizontal && clampedX) {
874a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                mScroller.springBack(mScrollX, mScrollY, 0, getScrollRange(), 0, 0);
875a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            } else if (!mHorizontal && clampedY) {
876a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange());
877a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            }
878a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        } else {
879a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            super.scrollTo(scrollX, scrollY);
880a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
881a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        awakenScrollBars();
882a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
883a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
884a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    @Override
885a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
886a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        super.onInitializeAccessibilityNodeInfo(info);
887a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        info.setScrollable(true);
888a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
889a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
890a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    @Override
891a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
892a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        super.onInitializeAccessibilityEvent(event);
893a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        event.setScrollable(true);
894a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
895a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
896a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    @Override
897a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
898a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        // Do not append text content to scroll events they are fired frequently
899a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        // and the client has already received another event type with the text.
900a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (event.getEventType() != AccessibilityEvent.TYPE_VIEW_SCROLLED) {
901a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            super.dispatchPopulateAccessibilityEvent(event);
902a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
903a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        return false;
904a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
905a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
906a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    private int getScrollRange() {
907a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        int scrollRange = 0;
908a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (getChildCount() > 0) {
909a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            View child = getChildAt(0);
910a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            if (mHorizontal) {
911a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                scrollRange = Math.max(0,
912a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        child.getWidth() - (getWidth() - mPaddingRight - mPaddingLeft));
913a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            } else {
914a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                scrollRange = Math.max(0,
915a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        child.getHeight() - (getHeight() - mPaddingBottom - mPaddingTop));
916a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            }
917a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
918a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        return scrollRange;
919a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
920a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
921a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    /**
922a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * <p>
923a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * Finds the next focusable component that fits in this View's bounds
924a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * (excluding fading edges) pretending that this View's top is located at
925a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * the parameter top.
926a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * </p>
927a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     *
928a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * @param topFocus           look for a candidate at the top of the bounds if topFocus is true,
929a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     *                           or at the bottom of the bounds if topFocus is false
930a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * @param top                the top offset of the bounds in which a focusable must be
931a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     *                           found (the fading edge is assumed to start at this position)
932a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * @param preferredFocusable the View that has highest priority and will be
933a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     *                           returned if it is within my bounds (null is valid)
934a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * @return the next focusable component in the bounds or null if none can be found
935a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     */
936a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    private View findFocusableViewInMyBounds(final boolean topFocus,
937a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            final int top, View preferredFocusable) {
938a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        /*
939a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb         * The fading edge's transparent side should be considered for focus
940a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb         * since it's mostly visible, so we divide the actual fading edge length
941a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb         * by 2.
942a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb         */
943a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        final int fadingEdgeLength = (mHorizontal
944a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                ? getHorizontalFadingEdgeLength()
945a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                : getVerticalFadingEdgeLength()) / 2;
946a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        final int topWithoutFadingEdge = top + fadingEdgeLength;
947a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        final int bottomWithoutFadingEdge = top + (mHorizontal ? getWidth() : getHeight()) - fadingEdgeLength;
948a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
949a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if ((preferredFocusable != null)
950a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                && ((mHorizontal ? preferredFocusable.getLeft() : preferredFocusable.getTop())
951a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        < bottomWithoutFadingEdge)
952a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                && ((mHorizontal ? preferredFocusable.getRight() : preferredFocusable.getBottom()) > topWithoutFadingEdge)) {
953a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            return preferredFocusable;
954a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
955a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
956a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        return findFocusableViewInBounds(topFocus, topWithoutFadingEdge,
957a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                bottomWithoutFadingEdge);
958a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
959a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
960a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    /**
961a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * <p>
962a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * Finds the next focusable component that fits in the specified bounds.
963a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * </p>
964a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     *
965a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * @param topFocus look for a candidate is the one at the top of the bounds
966a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     *                 if topFocus is true, or at the bottom of the bounds if topFocus is
967a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     *                 false
968a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * @param top      the top offset of the bounds in which a focusable must be
969a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     *                 found
970a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * @param bottom   the bottom offset of the bounds in which a focusable must
971a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     *                 be found
972a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * @return the next focusable component in the bounds or null if none can
973a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     *         be found
974a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     */
975a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    private View findFocusableViewInBounds(boolean topFocus, int top, int bottom) {
976a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
977a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        List<View> focusables = getFocusables(View.FOCUS_FORWARD);
978a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        View focusCandidate = null;
979a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
980a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        /*
981a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb         * A fully contained focusable is one where its top is below the bound's
982a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb         * top, and its bottom is above the bound's bottom. A partially
983a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb         * contained focusable is one where some part of it is within the
984a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb         * bounds, but it also has some part that is not within bounds.  A fully contained
985a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb         * focusable is preferred to a partially contained focusable.
986a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb         */
987a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        boolean foundFullyContainedFocusable = false;
988a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
989a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        int count = focusables.size();
990a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        for (int i = 0; i < count; i++) {
991a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            View view = focusables.get(i);
992a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            int viewTop = mHorizontal ? view.getLeft() : view.getTop();
993a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            int viewBottom = mHorizontal ? view.getRight() : view.getBottom();
994a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
995a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            if (top < viewBottom && viewTop < bottom) {
996a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                /*
997a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                 * the focusable is in the target area, it is a candidate for
998a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                 * focusing
999a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                 */
1000a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1001a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                final boolean viewIsFullyContained = (top < viewTop) &&
1002a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        (viewBottom < bottom);
1003a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1004a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                if (focusCandidate == null) {
1005a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    /* No candidate, take this one */
1006a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    focusCandidate = view;
1007a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    foundFullyContainedFocusable = viewIsFullyContained;
1008a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                } else {
1009a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    final int ctop = mHorizontal ? focusCandidate.getLeft() : focusCandidate.getTop();
1010a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    final int cbot = mHorizontal ? focusCandidate.getRight() : focusCandidate.getBottom();
1011a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    final boolean viewIsCloserToBoundary =
1012a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            (topFocus && viewTop < ctop) ||
1013a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                                    (!topFocus && viewBottom > cbot);
1014a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1015a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    if (foundFullyContainedFocusable) {
1016a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        if (viewIsFullyContained && viewIsCloserToBoundary) {
1017a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            /*
1018a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                             * We're dealing with only fully contained views, so
1019a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                             * it has to be closer to the boundary to beat our
1020a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                             * candidate
1021a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                             */
1022a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            focusCandidate = view;
1023a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        }
1024a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    } else {
1025a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        if (viewIsFullyContained) {
1026a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            /* Any fully contained view beats a partially contained view */
1027a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            focusCandidate = view;
1028a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            foundFullyContainedFocusable = true;
1029a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        } else if (viewIsCloserToBoundary) {
1030a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            /*
1031a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                             * Partially contained view beats another partially
1032a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                             * contained view if it's closer
1033a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                             */
1034a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            focusCandidate = view;
1035a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        }
1036a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    }
1037a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                }
1038a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            }
1039a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
1040a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1041a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        return focusCandidate;
1042a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
1043a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1044a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    // i was here
1045a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1046a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    /**
1047a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * <p>Handles scrolling in response to a "page up/down" shortcut press. This
1048a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * method will scroll the view by one page up or down and give the focus
1049a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * to the topmost/bottommost component in the new visible area. If no
1050a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * component is a good candidate for focus, this scrollview reclaims the
1051a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * focus.</p>
1052a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     *
1053a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * @param direction the scroll direction: {@link android.view.View#FOCUS_UP}
1054a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     *                  to go one page up or
1055a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     *                  {@link android.view.View#FOCUS_DOWN} to go one page down
1056a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * @return true if the key event is consumed by this method, false otherwise
1057a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     */
1058a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    public boolean pageScroll(int direction) {
1059a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        boolean down = direction == View.FOCUS_DOWN;
1060a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        int height = getHeight();
1061a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1062a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (down) {
1063a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            mTempRect.top = getScrollY() + height;
1064a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            int count = getChildCount();
1065a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            if (count > 0) {
1066a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                View view = getChildAt(count - 1);
1067a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                if (mTempRect.top + height > view.getBottom()) {
1068a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    mTempRect.top = view.getBottom() - height;
1069a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                }
1070a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            }
1071a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        } else {
1072a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            mTempRect.top = getScrollY() - height;
1073a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            if (mTempRect.top < 0) {
1074a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                mTempRect.top = 0;
1075a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            }
1076a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
1077a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        mTempRect.bottom = mTempRect.top + height;
1078a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1079a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        return scrollAndFocus(direction, mTempRect.top, mTempRect.bottom);
1080a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
1081a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1082a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    /**
1083a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * <p>Handles scrolling in response to a "home/end" shortcut press. This
1084a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * method will scroll the view to the top or bottom and give the focus
1085a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * to the topmost/bottommost component in the new visible area. If no
1086a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * component is a good candidate for focus, this scrollview reclaims the
1087a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * focus.</p>
1088a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     *
1089a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * @param direction the scroll direction: {@link android.view.View#FOCUS_UP}
1090a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     *                  to go the top of the view or
1091a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     *                  {@link android.view.View#FOCUS_DOWN} to go the bottom
1092a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * @return true if the key event is consumed by this method, false otherwise
1093a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     */
1094a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    public boolean fullScroll(int direction) {
1095a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        boolean down = direction == View.FOCUS_DOWN;
1096a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        int height = getHeight();
1097a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1098a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        mTempRect.top = 0;
1099a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        mTempRect.bottom = height;
1100a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1101a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (down) {
1102a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            int count = getChildCount();
1103a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            if (count > 0) {
1104a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                View view = getChildAt(count - 1);
1105a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                mTempRect.bottom = view.getBottom() + mPaddingBottom;
1106a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                mTempRect.top = mTempRect.bottom - height;
1107a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            }
1108a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
1109a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1110a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        return scrollAndFocus(direction, mTempRect.top, mTempRect.bottom);
1111a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
1112a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1113a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    /**
1114a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * <p>Scrolls the view to make the area defined by <code>top</code> and
1115a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * <code>bottom</code> visible. This method attempts to give the focus
1116a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * to a component visible in this area. If no component can be focused in
1117a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * the new visible area, the focus is reclaimed by this ScrollView.</p>
1118a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     *
1119a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * @param direction the scroll direction: {@link android.view.View#FOCUS_UP}
1120a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     *                  to go upward, {@link android.view.View#FOCUS_DOWN} to downward
1121a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * @param top       the top offset of the new area to be made visible
1122a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * @param bottom    the bottom offset of the new area to be made visible
1123a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * @return true if the key event is consumed by this method, false otherwise
1124a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     */
1125a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    private boolean scrollAndFocus(int direction, int top, int bottom) {
1126a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        boolean handled = true;
1127a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1128a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        int height = getHeight();
1129a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        int containerTop = getScrollY();
1130a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        int containerBottom = containerTop + height;
1131a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        boolean up = direction == View.FOCUS_UP;
1132a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1133a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        View newFocused = findFocusableViewInBounds(up, top, bottom);
1134a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (newFocused == null) {
1135a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            newFocused = this;
1136a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
1137a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1138a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (top >= containerTop && bottom <= containerBottom) {
1139a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            handled = false;
1140a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        } else {
1141a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            int delta = up ? (top - containerTop) : (bottom - containerBottom);
1142a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            doScrollY(delta);
1143a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
1144a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1145a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (newFocused != findFocus()) newFocused.requestFocus(direction);
1146a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1147a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        return handled;
1148a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
1149a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1150a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    /**
1151a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * Handle scrolling in response to an up or down arrow click.
1152a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     *
1153a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * @param direction The direction corresponding to the arrow key that was
1154a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     *                  pressed
1155a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * @return True if we consumed the event, false otherwise
1156a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     */
1157a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    public boolean arrowScroll(int direction) {
1158a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1159a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        View currentFocused = findFocus();
1160a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (currentFocused == this) currentFocused = null;
1161a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1162a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, direction);
1163a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1164a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        final int maxJump = getMaxScrollAmount();
1165a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1166a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (nextFocused != null && isWithinDeltaOfScreen(nextFocused, maxJump, getHeight())) {
1167a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            nextFocused.getDrawingRect(mTempRect);
1168a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            offsetDescendantRectToMyCoords(nextFocused, mTempRect);
1169a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
1170a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            doScrollY(scrollDelta);
1171a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            nextFocused.requestFocus(direction);
1172a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        } else {
1173a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            // no new focus
1174a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            int scrollDelta = maxJump;
1175a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1176a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            if (direction == View.FOCUS_UP && getScrollY() < scrollDelta) {
1177a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                scrollDelta = getScrollY();
1178a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            } else if (direction == View.FOCUS_DOWN) {
1179a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                if (getChildCount() > 0) {
1180a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    int daBottom = getChildAt(0).getBottom();
1181a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    int screenBottom = getScrollY() + getHeight() - mPaddingBottom;
1182a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    if (daBottom - screenBottom < maxJump) {
1183a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        scrollDelta = daBottom - screenBottom;
1184a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    }
1185a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                }
1186a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            }
1187a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            if (scrollDelta == 0) {
1188a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                return false;
1189a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            }
1190a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            doScrollY(direction == View.FOCUS_DOWN ? scrollDelta : -scrollDelta);
1191a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
1192a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1193a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (currentFocused != null && currentFocused.isFocused()
1194a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                && isOffScreen(currentFocused)) {
1195a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            // previously focused item still has focus and is off screen, give
1196a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            // it up (take it back to ourselves)
1197a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            // (also, need to temporarily force FOCUS_BEFORE_DESCENDANTS so we are
1198a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            // sure to
1199a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            // get it)
1200a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            final int descendantFocusability = getDescendantFocusability();  // save
1201a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
1202a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            requestFocus();
1203a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            setDescendantFocusability(descendantFocusability);  // restore
1204a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
1205a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        return true;
1206a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
1207a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1208a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    private boolean isOrthoMove(float moveX, float moveY) {
1209a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        return mHorizontal && Math.abs(moveY) > Math.abs(moveX)
1210a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                || !mHorizontal && Math.abs(moveX) > Math.abs(moveY);
1211a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
1212a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1213a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    /**
1214a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * @return whether the descendant of this scroll view is scrolled off
1215a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     *  screen.
1216a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     */
1217a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    private boolean isOffScreen(View descendant) {
1218a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (mHorizontal) {
1219a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            return !isWithinDeltaOfScreen(descendant, getWidth(), 0);
1220a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        } else {
1221a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            return !isWithinDeltaOfScreen(descendant, 0, getHeight());
1222a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
1223a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
1224a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1225a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    /**
1226a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * @return whether the descendant of this scroll view is within delta
1227a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     *  pixels of being on the screen.
1228a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     */
1229a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    private boolean isWithinDeltaOfScreen(View descendant, int delta, int height) {
1230a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        descendant.getDrawingRect(mTempRect);
1231a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        offsetDescendantRectToMyCoords(descendant, mTempRect);
1232a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (mHorizontal) {
1233a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            return (mTempRect.right + delta) >= getScrollX()
1234a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            && (mTempRect.left - delta) <= (getScrollX() + height);
1235a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        } else {
1236a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            return (mTempRect.bottom + delta) >= getScrollY()
1237a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            && (mTempRect.top - delta) <= (getScrollY() + height);
1238a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
1239a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
1240a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1241a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    /**
1242a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * Smooth scroll by a Y delta
1243a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     *
1244a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * @param delta the number of pixels to scroll by on the Y axis
1245a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     */
1246a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    private void doScrollY(int delta) {
1247a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (delta != 0) {
1248a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            if (mSmoothScrollingEnabled) {
1249a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                if (mHorizontal) {
1250a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    smoothScrollBy(0, delta);
1251a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                } else {
1252a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    smoothScrollBy(delta, 0);
1253a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                }
1254a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            } else {
1255a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                if (mHorizontal) {
1256a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    scrollBy(0, delta);
1257a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                } else {
1258a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    scrollBy(delta, 0);
1259a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                }
1260a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            }
1261a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
1262a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
1263a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1264a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    /**
1265a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
1266a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     *
1267a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * @param dx the number of pixels to scroll by on the X axis
1268a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * @param dy the number of pixels to scroll by on the Y axis
1269a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     */
1270a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    public final void smoothScrollBy(int dx, int dy) {
1271a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (getChildCount() == 0) {
1272a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            // Nothing to do.
1273a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            return;
1274a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
1275a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
1276a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (duration > ANIMATED_SCROLL_GAP) {
1277a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            if (mHorizontal) {
1278a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                final int width = getWidth() - mPaddingRight - mPaddingLeft;
1279a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                final int right = getChildAt(0).getWidth();
1280a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                final int maxX = Math.max(0, right - width);
1281a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                final int scrollX = mScrollX;
1282a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                dx = Math.max(0, Math.min(scrollX + dx, maxX)) - scrollX;
1283a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                mScroller.startScroll(scrollX, mScrollY, dx, 0);
1284a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            } else {
1285a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                final int height = getHeight() - mPaddingBottom - mPaddingTop;
1286a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                final int bottom = getChildAt(0).getHeight();
1287a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                final int maxY = Math.max(0, bottom - height);
1288a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                final int scrollY = mScrollY;
1289a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                dy = Math.max(0, Math.min(scrollY + dy, maxY)) - scrollY;
1290a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                mScroller.startScroll(mScrollX, scrollY, 0, dy);
1291a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            }
1292a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            invalidate();
1293a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        } else {
1294a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            if (!mScroller.isFinished()) {
1295a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                mScroller.abortAnimation();
1296a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                if (mFlingStrictSpan != null) {
1297a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    mFlingStrictSpan.finish();
1298a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    mFlingStrictSpan = null;
1299a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                }
1300a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            }
1301a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            scrollBy(dx, dy);
1302a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
1303a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        mLastScroll = AnimationUtils.currentAnimationTimeMillis();
1304a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
1305a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1306a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    /**
1307a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * Like {@link #scrollTo}, but scroll smoothly instead of immediately.
1308a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     *
1309a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * @param x the position where to scroll on the X axis
1310a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * @param y the position where to scroll on the Y axis
1311a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     */
1312a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    public final void smoothScrollTo(int x, int y) {
1313a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        smoothScrollBy(x - mScrollX, y - mScrollY);
1314a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
1315a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1316a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    /**
1317a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * <p>
1318a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * The scroll range of a scroll view is the overall height of all of its
1319a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * children.
1320a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * </p>
1321a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     */
1322a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    @Override
1323a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    protected int computeVerticalScrollRange() {
1324a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (mHorizontal) {
1325a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            return super.computeVerticalScrollRange();
1326a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
1327a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        final int count = getChildCount();
1328a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        final int contentHeight = getHeight() - mPaddingBottom - mPaddingTop;
1329a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (count == 0) {
1330a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            return contentHeight;
1331a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
1332a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1333a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        int scrollRange = getChildAt(0).getBottom();
1334a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        final int scrollY = mScrollY;
1335a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        final int overscrollBottom = Math.max(0, scrollRange - contentHeight);
1336a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (scrollY < 0) {
1337a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            scrollRange -= scrollY;
1338a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        } else if (scrollY > overscrollBottom) {
1339a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            scrollRange += scrollY - overscrollBottom;
1340a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
1341a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1342a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        return scrollRange;
1343a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
1344a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1345a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    /**
1346a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * <p>
1347a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * The scroll range of a scroll view is the overall height of all of its
1348a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * children.
1349a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * </p>
1350a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     */
1351a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    @Override
1352a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    protected int computeHorizontalScrollRange() {
1353a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (!mHorizontal) {
1354a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            return super.computeHorizontalScrollRange();
1355a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
1356a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        final int count = getChildCount();
1357a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        final int contentWidth = getWidth() - mPaddingRight - mPaddingLeft;
1358a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (count == 0) {
1359a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            return contentWidth;
1360a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
1361a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1362a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        int scrollRange = getChildAt(0).getRight();
1363a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        final int scrollX = mScrollX;
1364a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        final int overscrollBottom = Math.max(0, scrollRange - contentWidth);
1365a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (scrollX < 0) {
1366a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            scrollRange -= scrollX;
1367a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        } else if (scrollX > overscrollBottom) {
1368a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            scrollRange += scrollX - overscrollBottom;
1369a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
1370a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1371a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        return scrollRange;
1372a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
1373a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1374a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    @Override
1375a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    protected int computeVerticalScrollOffset() {
1376a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        return Math.max(0, super.computeVerticalScrollOffset());
1377a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
1378a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1379a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    @Override
1380a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    protected int computeHorizontalScrollOffset() {
1381a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        return Math.max(0, super.computeHorizontalScrollOffset());
1382a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
1383a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1384a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    @Override
1385a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
1386a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        ViewGroup.LayoutParams lp = child.getLayoutParams();
1387a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1388a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        int childWidthMeasureSpec;
1389a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        int childHeightMeasureSpec;
1390a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1391a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (mHorizontal) {
1392a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop
1393a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    + mPaddingBottom, lp.height);
1394a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1395a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
1396a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        } else {
1397a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft
1398a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    + mPaddingRight, lp.width);
1399a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1400a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
1401a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
1402a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1403a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
1404a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
1405a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1406a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    @Override
1407a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
1408a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            int parentHeightMeasureSpec, int heightUsed) {
1409a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
1410a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1411a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        int childWidthMeasureSpec;
1412a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        int childHeightMeasureSpec;
1413a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (mHorizontal) {
1414a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
1415a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
1416a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            + heightUsed, lp.height);
1417a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
1418a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    lp.leftMargin + lp.rightMargin, MeasureSpec.UNSPECIFIED);
1419a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        } else {
1420a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
1421a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
1422a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            + widthUsed, lp.width);
1423a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
1424a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    lp.topMargin + lp.bottomMargin, MeasureSpec.UNSPECIFIED);
1425a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
1426a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
1427a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
1428a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1429a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    @Override
1430a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    public void computeScroll() {
1431a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (mScroller.computeScrollOffset()) {
1432a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            // This is called at drawing time by ViewGroup.  We don't want to
1433a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            // re-show the scrollbars at this point, which scrollTo will do,
1434a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            // so we replicate most of scrollTo here.
1435a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            //
1436a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            //         It's a little odd to call onScrollChanged from inside the drawing.
1437a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            //
1438a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            //         It is, except when you remember that computeScroll() is used to
1439a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            //         animate scrolling. So unless we want to defer the onScrollChanged()
1440a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            //         until the end of the animated scrolling, we don't really have a
1441a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            //         choice here.
1442a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            //
1443a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            //         I agree.  The alternative, which I think would be worse, is to post
1444a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            //         something and tell the subclasses later.  This is bad because there
1445a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            //         will be a window where mScrollX/Y is different from what the app
1446a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            //         thinks it is.
1447a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            //
1448a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            int oldX = mScrollX;
1449a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            int oldY = mScrollY;
1450a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            int x = mScroller.getCurrX();
1451a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            int y = mScroller.getCurrY();
1452a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1453a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            if (oldX != x || oldY != y) {
1454a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                if (mHorizontal) {
1455a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    overScrollBy(x - oldX, y - oldY, oldX, oldY, getScrollRange(), 0,
1456a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            mOverflingDistance, 0, false);
1457a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                } else {
1458a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, getScrollRange(),
1459a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                            0, mOverflingDistance, false);
1460a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                }
1461a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                onScrollChanged(mScrollX, mScrollY, oldX, oldY);
1462a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            }
1463a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            awakenScrollBars();
1464a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1465a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            // Keep on drawing until the animation has finished.
1466a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            postInvalidate();
1467a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        } else {
1468a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            if (mFlingStrictSpan != null) {
1469a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                mFlingStrictSpan.finish();
1470a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                mFlingStrictSpan = null;
1471a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            }
1472a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
1473a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
1474a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1475a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    /**
1476a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * Scrolls the view to the given child.
1477a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     *
1478a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * @param child the View to scroll to
1479a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     */
1480a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    private void scrollToChild(View child) {
1481a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        child.getDrawingRect(mTempRect);
1482a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1483a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        /* Offset from child's local coordinates to ScrollView coordinates */
1484a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        offsetDescendantRectToMyCoords(child, mTempRect);
1485a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        scrollToChildRect(mTempRect, true);
1486a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
1487a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1488a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    /**
1489a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * If rect is off screen, scroll just enough to get it (or at least the
1490a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * first screen size chunk of it) on screen.
1491a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     *
1492a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * @param rect      The rectangle.
1493a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * @param immediate True to scroll immediately without animation
1494a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * @return true if scrolling was performed
1495a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     */
1496a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    private boolean scrollToChildRect(Rect rect, boolean immediate) {
1497a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        final int delta = computeScrollDeltaToGetChildRectOnScreen(rect);
1498a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        final boolean scroll = delta != 0;
1499a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (scroll) {
1500a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            if (immediate) {
1501a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                if (mHorizontal) {
1502a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    scrollBy(delta, 0);
1503a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                } else {
1504a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    scrollBy(0, delta);
1505a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                }
1506a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            } else {
1507a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                if (mHorizontal) {
1508a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    smoothScrollBy(delta, 0);
1509a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                } else {
1510a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                    smoothScrollBy(0, delta);
1511a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                }
1512a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            }
1513a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
1514a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        return scroll;
1515a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
1516a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1517a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    /**
1518a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * Compute the amount to scroll in the Y direction in order to get
1519a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * a rectangle completely on the screen (or, if taller than the screen,
1520a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * at least the first screen size chunk of it).
1521a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     *
1522a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * @param rect The rect.
1523a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * @return The scroll delta.
1524a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     */
1525a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect) {
1526a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (mHorizontal) {
1527a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            return computeScrollDeltaToGetChildRectOnScreenHorizontal(rect);
1528a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        } else {
1529a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            return computeScrollDeltaToGetChildRectOnScreenVertical(rect);
1530a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
1531a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
1532a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1533a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    private int computeScrollDeltaToGetChildRectOnScreenVertical(Rect rect) {
1534a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (getChildCount() == 0) return 0;
1535a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1536a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        int height = getHeight();
1537a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        int screenTop = getScrollY();
1538a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        int screenBottom = screenTop + height;
1539a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1540a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        int fadingEdge = getVerticalFadingEdgeLength();
1541a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1542a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        // leave room for top fading edge as long as rect isn't at very top
1543a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (rect.top > 0) {
1544a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            screenTop += fadingEdge;
1545a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
1546a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1547a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        // leave room for bottom fading edge as long as rect isn't at very bottom
1548a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (rect.bottom < getChildAt(0).getHeight()) {
1549a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            screenBottom -= fadingEdge;
1550a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
1551a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1552a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        int scrollYDelta = 0;
1553a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1554a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (rect.bottom > screenBottom && rect.top > screenTop) {
1555a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            // need to move down to get it in view: move down just enough so
1556a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            // that the entire rectangle is in view (or at least the first
1557a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            // screen size chunk).
1558a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1559a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            if (rect.height() > height) {
1560a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                // just enough to get screen size chunk on
1561a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                scrollYDelta += (rect.top - screenTop);
1562a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            } else {
1563a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                // get entire rect at bottom of screen
1564a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                scrollYDelta += (rect.bottom - screenBottom);
1565a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            }
1566a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1567a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            // make sure we aren't scrolling beyond the end of our content
1568a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            int bottom = getChildAt(0).getBottom();
1569a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            int distanceToBottom = bottom - screenBottom;
1570a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            scrollYDelta = Math.min(scrollYDelta, distanceToBottom);
1571a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1572a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        } else if (rect.top < screenTop && rect.bottom < screenBottom) {
1573a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            // need to move up to get it in view: move up just enough so that
1574a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            // entire rectangle is in view (or at least the first screen
1575a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            // size chunk of it).
1576a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1577a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            if (rect.height() > height) {
1578a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                // screen size chunk
1579a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                scrollYDelta -= (screenBottom - rect.bottom);
1580a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            } else {
1581a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                // entire rect at top
1582a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                scrollYDelta -= (screenTop - rect.top);
1583a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            }
1584a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1585a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            // make sure we aren't scrolling any further than the top our content
1586a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            scrollYDelta = Math.max(scrollYDelta, -getScrollY());
1587a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
1588a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        return scrollYDelta;
1589a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
1590a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1591a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    private int computeScrollDeltaToGetChildRectOnScreenHorizontal(Rect rect) {
1592a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (getChildCount() == 0) return 0;
1593a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1594a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        int width = getWidth();
1595a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        int screenLeft = getScrollX();
1596a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        int screenRight = screenLeft + width;
1597a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1598a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        int fadingEdge = getHorizontalFadingEdgeLength();
1599a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1600a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        // leave room for left fading edge as long as rect isn't at very left
1601a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (rect.left > 0) {
1602a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            screenLeft += fadingEdge;
1603a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
1604a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1605a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        // leave room for right fading edge as long as rect isn't at very right
1606a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (rect.right < getChildAt(0).getWidth()) {
1607a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            screenRight -= fadingEdge;
1608a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
1609a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1610a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        int scrollXDelta = 0;
1611a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1612a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (rect.right > screenRight && rect.left > screenLeft) {
1613a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            // need to move right to get it in view: move right just enough so
1614a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            // that the entire rectangle is in view (or at least the first
1615a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            // screen size chunk).
1616a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1617a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            if (rect.width() > width) {
1618a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                // just enough to get screen size chunk on
1619a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                scrollXDelta += (rect.left - screenLeft);
1620a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            } else {
1621a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                // get entire rect at right of screen
1622a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                scrollXDelta += (rect.right - screenRight);
1623a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            }
1624a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1625a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            // make sure we aren't scrolling beyond the end of our content
1626a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            int right = getChildAt(0).getRight();
1627a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            int distanceToRight = right - screenRight;
1628a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            scrollXDelta = Math.min(scrollXDelta, distanceToRight);
1629a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1630a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        } else if (rect.left < screenLeft && rect.right < screenRight) {
1631a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            // need to move right to get it in view: move right just enough so that
1632a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            // entire rectangle is in view (or at least the first screen
1633a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            // size chunk of it).
1634a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1635a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            if (rect.width() > width) {
1636a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                // screen size chunk
1637a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                scrollXDelta -= (screenRight - rect.right);
1638a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            } else {
1639a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                // entire rect at left
1640a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                scrollXDelta -= (screenLeft - rect.left);
1641a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            }
1642a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1643a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            // make sure we aren't scrolling any further than the left our content
1644a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            scrollXDelta = Math.max(scrollXDelta, -getScrollX());
1645a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
1646a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        return scrollXDelta;
1647a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
1648a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1649a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1650a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    @Override
1651a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    public void requestChildFocus(View child, View focused) {
1652a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (!mIsLayoutDirty) {
1653a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            scrollToChild(focused);
1654a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        } else {
1655a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            // The child may not be laid out yet, we can't compute the scroll yet
1656a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            mChildToScrollTo = focused;
1657a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
1658a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        super.requestChildFocus(child, focused);
1659a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
1660a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1661a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1662a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    /**
1663a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * When looking for focus in children of a scroll view, need to be a little
1664a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * more careful not to give focus to something that is scrolled off screen.
1665a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     *
1666a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * This is more expensive than the default {@link android.view.ViewGroup}
1667a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * implementation, otherwise this behavior might have been made the default.
1668a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     */
1669a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    @Override
1670a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    protected boolean onRequestFocusInDescendants(int direction,
1671a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            Rect previouslyFocusedRect) {
1672a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1673a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        // convert from forward / backward notation to up / down / left / right
1674a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        // (ugh).
1675a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (mHorizontal) {
1676a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            if (direction == View.FOCUS_FORWARD) {
1677a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                direction = View.FOCUS_RIGHT;
1678a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            } else if (direction == View.FOCUS_BACKWARD) {
1679a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                direction = View.FOCUS_LEFT;
1680a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            }
1681a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        } else {
1682a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            if (direction == View.FOCUS_FORWARD) {
1683a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                direction = View.FOCUS_DOWN;
1684a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            } else if (direction == View.FOCUS_BACKWARD) {
1685a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                direction = View.FOCUS_UP;
1686a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            }
1687a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
1688a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1689a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        final View nextFocus = previouslyFocusedRect == null ?
1690a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                FocusFinder.getInstance().findNextFocus(this, null, direction) :
1691a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                FocusFinder.getInstance().findNextFocusFromRect(this,
1692a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        previouslyFocusedRect, direction);
1693a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1694a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (nextFocus == null) {
1695a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            return false;
1696a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
1697a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1698a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (isOffScreen(nextFocus)) {
1699a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            return false;
1700a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
1701a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1702a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        return nextFocus.requestFocus(direction, previouslyFocusedRect);
1703a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
1704a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1705a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    @Override
1706a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    public boolean requestChildRectangleOnScreen(View child, Rect rectangle,
1707a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            boolean immediate) {
1708a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        // offset into coordinate space of this scroll view
1709a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        rectangle.offset(child.getLeft() - child.getScrollX(),
1710a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                child.getTop() - child.getScrollY());
1711a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1712a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        return scrollToChildRect(rectangle, immediate);
1713a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
1714a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1715a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    @Override
1716a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    public void requestLayout() {
1717a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        mIsLayoutDirty = true;
1718a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        super.requestLayout();
1719a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
1720a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1721a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    @Override
1722a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    protected void onDetachedFromWindow() {
1723a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        super.onDetachedFromWindow();
1724a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1725a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (mScrollStrictSpan != null) {
1726a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            mScrollStrictSpan.finish();
1727a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            mScrollStrictSpan = null;
1728a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
1729a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (mFlingStrictSpan != null) {
1730a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            mFlingStrictSpan.finish();
1731a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            mFlingStrictSpan = null;
1732a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
1733a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
1734a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1735a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    @Override
1736a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    protected void onLayout(boolean changed, int l, int t, int r, int b) {
1737a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        super.onLayout(changed, l, t, r, b);
1738a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        mIsLayoutDirty = false;
1739a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        // Give a child focus if it needs it
1740a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (mChildToScrollTo != null && isViewDescendantOf(mChildToScrollTo, this)) {
1741a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            scrollToChild(mChildToScrollTo);
1742a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
1743a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        mChildToScrollTo = null;
1744a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1745a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        // Calling this with the present values causes it to re-clam them
1746a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        scrollTo(mScrollX, mScrollY);
1747a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
1748a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1749a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    @Override
1750a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
1751a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        super.onSizeChanged(w, h, oldw, oldh);
1752a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1753a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        View currentFocused = findFocus();
1754a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (null == currentFocused || this == currentFocused)
1755a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            return;
1756a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1757a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        // If the currently-focused view was visible on the screen when the
1758a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        // screen was at the old height, then scroll the screen to make that
1759a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        // view visible with the new screen height.
1760a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (isWithinDeltaOfScreen(currentFocused, 0, oldh)) {
1761a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            currentFocused.getDrawingRect(mTempRect);
1762a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            offsetDescendantRectToMyCoords(currentFocused, mTempRect);
1763a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
1764a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            doScrollY(scrollDelta);
1765a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
1766a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
1767a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1768a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    /**
1769a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * Return true if child is an descendant of parent, (or equal to the parent).
1770a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     */
1771a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    private boolean isViewDescendantOf(View child, View parent) {
1772a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (child == parent) {
1773a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            return true;
1774a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
1775a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1776a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        final ViewParent theParent = child.getParent();
1777a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        return (theParent instanceof ViewGroup) && isViewDescendantOf((View) theParent, parent);
1778a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
1779a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1780a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    /**
1781a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * Fling the scroll view
1782a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     *
1783a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * @param velocityY The initial velocity in the Y direction. Positive
1784a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     *                  numbers mean that the finger/cursor is moving down the screen,
1785a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     *                  which means we want to scroll towards the top.
1786a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     */
1787a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    public void fling(int velocityY) {
1788a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (getChildCount() > 0) {
1789a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            if (mHorizontal) {
1790a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                int width = getWidth() - mPaddingRight - mPaddingLeft;
1791a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                int right = getChildAt(0).getWidth();
1792a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1793a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                mScroller.fling(mScrollX, mScrollY, velocityY, 0,
1794a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        0, Math.max(0, right - width), 0, 0, width/2, 0);
1795a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            } else {
1796a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                int height = getHeight() - mPaddingBottom - mPaddingTop;
1797a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                int bottom = getChildAt(0).getHeight();
1798a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1799a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0,
1800a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                        Math.max(0, bottom - height), 0, height/2);
1801a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            }
1802a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            if (mFlingStrictSpan == null) {
1803a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                mFlingStrictSpan = StrictMode.enterCriticalSpan("ScrollView-fling");
1804a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            }
1805a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1806a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            invalidate();
1807a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
1808a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
1809a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1810a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    private void endDrag() {
1811a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        mIsBeingDragged = false;
1812a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        mIsOrthoDragged = false;
1813a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        mDownView = null;
1814a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        recycleVelocityTracker();
1815a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (mScrollStrictSpan != null) {
1816a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            mScrollStrictSpan.finish();
1817a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            mScrollStrictSpan = null;
1818a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
1819a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
1820a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1821a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    /**
1822a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * {@inheritDoc}
1823a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     *
1824a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     * <p>This version also clamps the scrolling to the bounds of our child.
1825a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb     */
1826a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    @Override
1827a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    public void scrollTo(int x, int y) {
1828a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        // we rely on the fact the View.scrollBy calls scrollTo.
1829a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (getChildCount() > 0) {
1830a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            View child = getChildAt(0);
1831a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            x = clamp(x, getWidth() - mPaddingRight - mPaddingLeft, child.getWidth());
1832a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            y = clamp(y, getHeight() - mPaddingBottom - mPaddingTop, child.getHeight());
1833a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            if (x != mScrollX || y != mScrollY) {
1834a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb                super.scrollTo(x, y);
1835a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            }
1836a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
1837a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
1838a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1839a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    private int clamp(int n, int my, int child) {
1840a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if (my >= child || n < 0) {
1841a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            /* my >= child is this case:
1842a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb             *                    |--------------- me ---------------|
1843a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb             *     |------ child ------|
1844a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb             * or
1845a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb             *     |--------------- me ---------------|
1846a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb             *            |------ child ------|
1847a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb             * or
1848a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb             *     |--------------- me ---------------|
1849a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb             *                                  |------ child ------|
1850a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb             *
1851a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb             * n < 0 is this case:
1852a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb             *     |------ me ------|
1853a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb             *                    |-------- child --------|
1854a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb             *     |-- mScrollX --|
1855a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb             */
1856a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            return 0;
1857a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
1858a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        if ((my+n) > child) {
1859a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            /* this case:
1860a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb             *                    |------ me ------|
1861a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb             *     |------ child ------|
1862a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb             *     |-- mScrollX --|
1863a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb             */
1864a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb            return child-my;
1865a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        }
1866a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb        return n;
1867a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb    }
1868a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb
1869a3194d0b9c9c36be29598cac8faf8453cdaebe55Michael Kolb}
1870