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