PagedView.java revision 869390b6abd6e005a5ce85a8ee3c11daeecce919
1321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung/*
2321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung * Copyright (C) 2010 The Android Open Source Project
3321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung *
4321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung * Licensed under the Apache License, Version 2.0 (the "License");
5321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung * you may not use this file except in compliance with the License.
6321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung * You may obtain a copy of the License at
7321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung *
8321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung *      http://www.apache.org/licenses/LICENSE-2.0
9321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung *
10321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung * Unless required by applicable law or agreed to in writing, software
11321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung * distributed under the License is distributed on an "AS IS" BASIS,
12321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung * See the License for the specific language governing permissions and
14321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung * limitations under the License.
15321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung */
16321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
17321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chungpackage com.android.launcher2;
18321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
19228a0faca6fd77f5cdd28a32ff20a311ec1bbffaWinson Chungimport android.animation.Animator;
20228a0faca6fd77f5cdd28a32ff20a311ec1bbffaWinson Chungimport android.animation.AnimatorListenerAdapter;
21228a0faca6fd77f5cdd28a32ff20a311ec1bbffaWinson Chungimport android.animation.ObjectAnimator;
22bb6f6a52b6d176be253b1514af459a7aa4e998f8Winson Chungimport android.animation.ValueAnimator;
23321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chungimport android.content.Context;
249c4949e12c909d5e01d24386147b1c528015b31bAdam Cohenimport android.content.res.TypedArray;
25321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chungimport android.graphics.Canvas;
26321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chungimport android.graphics.Rect;
27321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chungimport android.os.Parcel;
28321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chungimport android.os.Parcelable;
29321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chungimport android.util.AttributeSet;
30785d2eb2b8d7072c8124300dd9168ff51a91cf38Winson Chungimport android.util.Log;
31185d71647c8859cae7a375773b31c03f2f22ade1Winson Chungimport android.view.InputDevice;
32185d71647c8859cae7a375773b31c03f2f22ade1Winson Chungimport android.view.KeyEvent;
33321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chungimport android.view.MotionEvent;
34321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chungimport android.view.VelocityTracker;
35321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chungimport android.view.View;
36321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chungimport android.view.ViewConfiguration;
37321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chungimport android.view.ViewGroup;
38321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chungimport android.view.ViewParent;
396a0f57dfafced837a2a282d8feec28d5418be3b9Winson Chungimport android.view.accessibility.AccessibilityEvent;
40c27d1bbb26f774a14122bc0b1d2ac33675439038Winson Chungimport android.view.accessibility.AccessibilityManager;
416a0f57dfafced837a2a282d8feec28d5418be3b9Winson Chungimport android.view.accessibility.AccessibilityNodeInfo;
42e0f66b546994a9bdee452851c17a148db02ec300Adam Cohenimport android.view.animation.Interpolator;
43321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chungimport android.widget.Scroller;
44321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
450499834db3f9dc6fb0f5f57b5876b8503bce5189Winson Chungimport com.android.launcher.R;
4680baf5a6b3c62a62265f626d43d1167783c94131Winson Chung
476a0f57dfafced837a2a282d8feec28d5418be3b9Winson Chungimport java.util.ArrayList;
486a0f57dfafced837a2a282d8feec28d5418be3b9Winson Chung
49321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung/**
50321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung * An abstraction of the original Workspace which supports browsing through a
510142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka * sequential list of "pages"
52321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung */
538b805b17158886035b38261eb611d8641701ae43Michael Jurkapublic abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeListener {
54321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    private static final String TAG = "PagedView";
55785d2eb2b8d7072c8124300dd9168ff51a91cf38Winson Chung    private static final boolean DEBUG = false;
560142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected static final int INVALID_PAGE = -1;
57321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
5886f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung    // the min drag distance for a fling to register, to prevent random page shifts
599cfd25f16739548111ba8fc6ba8cd83010eccef6Winson Chung    private static final int MIN_LENGTH_FOR_FLING = 25;
60321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
61f0c6ae0e35d3e020db55b5b826955da66b14b7f6Winson Chung    protected static final int PAGE_SNAP_ANIMATION_DURATION = 550;
62f0c6ae0e35d3e020db55b5b826955da66b14b7f6Winson Chung    protected static final int SLOW_PAGE_SNAP_ANIMATION_DURATION = 950;
630142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected static final float NANOTIME_DIV = 1000000000.0f;
640142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka
65b5ba097015c4794fa822f30b38a60a7070a00097Adam Cohen    private static final float OVERSCROLL_ACCELERATE_FACTOR = 2;
66b26f3d6a8c62e7c1a603b6c7979375d8dd4f20d4Winson Chung    private static final float OVERSCROLL_DAMP_FACTOR = 0.14f;
67867ca62c0e4b811d46734ff940d59c7a787525c8Winson Chung
68b64cb5a44bedcff0ea4b09cf8f1f5b6f95b0244eAdam Cohen    private static final float RETURN_TO_ORIGINAL_PAGE_THRESHOLD = 0.33f;
6900481b3ed322b4f36b669201d2287964febda65fAdam Cohen    // The page is moved more than halfway, automatically move to the next page on touch up.
7000481b3ed322b4f36b669201d2287964febda65fAdam Cohen    private static final float SIGNIFICANT_MOVE_THRESHOLD = 0.4f;
7168d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen
72265b9a66542148e0ff13971001cb9461065e1e0eAdam Cohen    // The following constants need to be scaled based on density. The scaled versions will be
73265b9a66542148e0ff13971001cb9461065e1e0eAdam Cohen    // assigned to the corresponding member variables below.
74265b9a66542148e0ff13971001cb9461065e1e0eAdam Cohen    private static final int FLING_THRESHOLD_VELOCITY = 500;
75265b9a66542148e0ff13971001cb9461065e1e0eAdam Cohen    private static final int MIN_SNAP_VELOCITY = 1500;
76265b9a66542148e0ff13971001cb9461065e1e0eAdam Cohen    private static final int MIN_FLING_VELOCITY = 250;
77265b9a66542148e0ff13971001cb9461065e1e0eAdam Cohen
78265b9a66542148e0ff13971001cb9461065e1e0eAdam Cohen    protected int mFlingThresholdVelocity;
79265b9a66542148e0ff13971001cb9461065e1e0eAdam Cohen    protected int mMinFlingVelocity;
80265b9a66542148e0ff13971001cb9461065e1e0eAdam Cohen    protected int mMinSnapVelocity;
810142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka
82b5ba097015c4794fa822f30b38a60a7070a00097Adam Cohen    protected float mDensity;
830142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected float mSmoothingTime;
840142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected float mTouchX;
85321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
860142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected boolean mFirstLayout = true;
870142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka
880142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected int mCurrentPage;
890142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected int mNextPage = INVALID_PAGE;
9068d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen    protected int mMaxScrollX;
910142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected Scroller mScroller;
92321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    private VelocityTracker mVelocityTracker;
93321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
94321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    private float mDownMotionX;
957426c42ce01e132781faa68941d79d23cd7fdf1eMichael Jurka    protected float mLastMotionX;
96c0844aa8a135bbf56aad2f95f57d1354a1260078Winson Chung    protected float mLastMotionXRemainder;
977426c42ce01e132781faa68941d79d23cd7fdf1eMichael Jurka    protected float mLastMotionY;
98aefd4e1f519838852d9e870031a85c9ea55a05f3Adam Cohen    protected float mTotalMotionX;
99f34bab59fc0260f926aec44d044883dce1b4191fAdam Cohen    private int mLastScreenCenter = -1;
10073894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen    private int[] mChildOffsets;
10173894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen    private int[] mChildRelativeOffsets;
10273894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen    private int[] mChildOffsetsWithLayoutScale;
103321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1040142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected final static int TOUCH_STATE_REST = 0;
1050142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected final static int TOUCH_STATE_SCROLLING = 1;
1060142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected final static int TOUCH_STATE_PREV_PAGE = 2;
1070142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected final static int TOUCH_STATE_NEXT_PAGE = 3;
108e45440ef0eb9edcde30767b38099b093c6a0d6b0Adam Cohen    protected final static float ALPHA_QUANTIZE_LEVEL = 0.0001f;
109321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1100142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected int mTouchState = TOUCH_STATE_REST;
1112591f6a5202664faf8cde2b68361e3842ce76ba9Adam Cohen    protected boolean mForceScreenScrolled = false;
112321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1130142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected OnLongClickListener mLongClickListener;
114321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1157426c42ce01e132781faa68941d79d23cd7fdf1eMichael Jurka    protected boolean mAllowLongPress = true;
116321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1177426c42ce01e132781faa68941d79d23cd7fdf1eMichael Jurka    protected int mTouchSlop;
118321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    private int mPagingTouchSlop;
119321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    private int mMaximumVelocity;
1201908d07151e0d3f82899f368874e41ed1e49ba16Winson Chung    private int mMinimumWidth;
1219c4949e12c909d5e01d24386147b1c528015b31bAdam Cohen    protected int mPageSpacing;
1229c4949e12c909d5e01d24386147b1c528015b31bAdam Cohen    protected int mPageLayoutPaddingTop;
1239c4949e12c909d5e01d24386147b1c528015b31bAdam Cohen    protected int mPageLayoutPaddingBottom;
1249c4949e12c909d5e01d24386147b1c528015b31bAdam Cohen    protected int mPageLayoutPaddingLeft;
1259c4949e12c909d5e01d24386147b1c528015b31bAdam Cohen    protected int mPageLayoutPaddingRight;
126df4b83dd9d6380ab963c62d1f9d1312efc87cb0fWinson Chung    protected int mPageLayoutWidthGap;
127df4b83dd9d6380ab963c62d1f9d1312efc87cb0fWinson Chung    protected int mPageLayoutHeightGap;
12887b1490c5a100619648b251cb2be05c457bede08Michael Jurka    protected int mCellCountX = 0;
12987b1490c5a100619648b251cb2be05c457bede08Michael Jurka    protected int mCellCountY = 0;
1307da1025bd7f15b04cf55c79b73e94e5e1bc959d9Winson Chung    protected boolean mCenterPagesVertically;
13168d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen    protected boolean mAllowOverScroll = true;
13268d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen    protected int mUnboundedScrollX;
133dde558b8b8bcf3fac6d2ceceafa1f7546f98a264Michael Jurka    protected int[] mTempVisiblePagesRange = new int[2];
134321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1358b805b17158886035b38261eb611d8641701ae43Michael Jurka    // mOverScrollX is equal to getScrollX() when we're within the normal scroll range. Otherwise
136ebea84d1c95f4c38ba8cee46cd586fd757b4fce2Adam Cohen    // it is equal to the scaled overscroll position. We use a separate value so as to prevent
137ebea84d1c95f4c38ba8cee46cd586fd757b4fce2Adam Cohen    // the screens from continuing to translate beyond the normal bounds.
138ebea84d1c95f4c38ba8cee46cd586fd757b4fce2Adam Cohen    protected int mOverScrollX;
139ebea84d1c95f4c38ba8cee46cd586fd757b4fce2Adam Cohen
1408c920dd3683d752aa4c43e964831ce53f9b72887Michael Jurka    // parameter that adjusts the layout to be optimized for pages with that scale factor
141d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka    protected float mLayoutScale = 1.0f;
142d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka
1435f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka    protected static final int INVALID_POINTER = -1;
144321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1455f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka    protected int mActivePointerId = INVALID_POINTER;
146321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
14786f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung    private PageSwitchListener mPageSwitchListener;
148321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
149e326f186af6b00e4ea32849f1527254c669d0600Michael Jurka    protected ArrayList<Boolean> mDirtyPageContent;
150321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1510142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    // If true, syncPages and syncPageItems will be called to refresh pages
1520142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected boolean mContentIsRefreshable = true;
1530142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka
1540142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    // If true, modify alpha of neighboring pages as user scrolls left/right
1550142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected boolean mFadeInAdjacentScreens = true;
1560142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka
1570142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    // It true, use a different slop parameter (pagingTouchSlop = 2 * touchSlop) for deciding
1580142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    // to switch to a new page
1590142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected boolean mUsePagingTouchSlop = true;
1600142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka
1618b805b17158886035b38261eb611d8641701ae43Michael Jurka    // If true, the subclass should directly update scrollX itself in its computeScroll method
1620142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    // (SmoothPagedView does this)
1630142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected boolean mDeferScrollUpdate = false;
1640142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka
1651262e369484ce7f2565655ed80e6299232c70bd7Patrick Dubroy    protected boolean mIsPageMoving = false;
1661262e369484ce7f2565655ed80e6299232c70bd7Patrick Dubroy
167f0ea4d3378be7b962c8e0bce2392df5e82491fb8Winson Chung    // All syncs and layout passes are deferred until data is ready.
168f0ea4d3378be7b962c8e0bce2392df5e82491fb8Winson Chung    protected boolean mIsDataReady = false;
169f0ea4d3378be7b962c8e0bce2392df5e82491fb8Winson Chung
170007c69867d821ea2b271398577a8b3440b3a7046Winson Chung    // Scrolling indicator
171bb6f6a52b6d176be253b1514af459a7aa4e998f8Winson Chung    private ValueAnimator mScrollIndicatorAnimator;
172afaa05014e0bf3ed1192f9ddec2af4283bc50248Michael Jurka    private View mScrollIndicator;
173f5f8cefb215fad98ae6d4487852e8b948aba3619Winson Chung    private int mScrollIndicatorPaddingLeft;
174f5f8cefb215fad98ae6d4487852e8b948aba3619Winson Chung    private int mScrollIndicatorPaddingRight;
175007c69867d821ea2b271398577a8b3440b3a7046Winson Chung    private boolean mHasScrollIndicator = true;
176bed61d22bdbfcdbe04f92675c7751b83c7a8947cMichael Jurka    private boolean mShouldShowScrollIndicator = false;
177bed61d22bdbfcdbe04f92675c7751b83c7a8947cMichael Jurka    private boolean mShouldShowScrollIndicatorImmediately = false;
178a6427b15c18d5b8f3078f553d78f8432de9f46e9Winson Chung    protected static final int sScrollIndicatorFadeInDuration = 150;
179a6427b15c18d5b8f3078f553d78f8432de9f46e9Winson Chung    protected static final int sScrollIndicatorFadeOutDuration = 650;
180a6427b15c18d5b8f3078f553d78f8432de9f46e9Winson Chung    protected static final int sScrollIndicatorFlashDuration = 650;
181007c69867d821ea2b271398577a8b3440b3a7046Winson Chung
182b44b52439d155f570db7d6d0b80fdd3350e35685Winson Chung    // If set, will defer loading associated pages until the scrolling settles
1834e076545e4ccdbd3c045a3fa33869a2b7519a0ccWinson Chung    private boolean mDeferLoadAssociatedPagesUntilScrollCompletes;
184b44b52439d155f570db7d6d0b80fdd3350e35685Winson Chung
18586f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung    public interface PageSwitchListener {
18686f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        void onPageSwitch(View newPage, int newPageIndex);
187321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
188321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
189321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    public PagedView(Context context) {
190321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        this(context, null);
191321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
192321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
193321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    public PagedView(Context context, AttributeSet attrs) {
194321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        this(context, attrs, 0);
195321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
196321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
197321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    public PagedView(Context context, AttributeSet attrs, int defStyle) {
198321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        super(context, attrs, defStyle);
199321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
2009c4949e12c909d5e01d24386147b1c528015b31bAdam Cohen        TypedArray a = context.obtainStyledAttributes(attrs,
2019c4949e12c909d5e01d24386147b1c528015b31bAdam Cohen                R.styleable.PagedView, defStyle, 0);
20260b0712bf555a32e609271e0b8ea90743a148efbAdam Cohen        setPageSpacing(a.getDimensionPixelSize(R.styleable.PagedView_pageSpacing, 0));
2039c4949e12c909d5e01d24386147b1c528015b31bAdam Cohen        mPageLayoutPaddingTop = a.getDimensionPixelSize(
2041908d07151e0d3f82899f368874e41ed1e49ba16Winson Chung                R.styleable.PagedView_pageLayoutPaddingTop, 0);
2059c4949e12c909d5e01d24386147b1c528015b31bAdam Cohen        mPageLayoutPaddingBottom = a.getDimensionPixelSize(
2061908d07151e0d3f82899f368874e41ed1e49ba16Winson Chung                R.styleable.PagedView_pageLayoutPaddingBottom, 0);
2079c4949e12c909d5e01d24386147b1c528015b31bAdam Cohen        mPageLayoutPaddingLeft = a.getDimensionPixelSize(
2081908d07151e0d3f82899f368874e41ed1e49ba16Winson Chung                R.styleable.PagedView_pageLayoutPaddingLeft, 0);
2099c4949e12c909d5e01d24386147b1c528015b31bAdam Cohen        mPageLayoutPaddingRight = a.getDimensionPixelSize(
2101908d07151e0d3f82899f368874e41ed1e49ba16Winson Chung                R.styleable.PagedView_pageLayoutPaddingRight, 0);
211df4b83dd9d6380ab963c62d1f9d1312efc87cb0fWinson Chung        mPageLayoutWidthGap = a.getDimensionPixelSize(
2127d7541e7b48fdc114c24b3b0aa75e70d7228041eWinson Chung                R.styleable.PagedView_pageLayoutWidthGap, 0);
213df4b83dd9d6380ab963c62d1f9d1312efc87cb0fWinson Chung        mPageLayoutHeightGap = a.getDimensionPixelSize(
2147d7541e7b48fdc114c24b3b0aa75e70d7228041eWinson Chung                R.styleable.PagedView_pageLayoutHeightGap, 0);
215f5f8cefb215fad98ae6d4487852e8b948aba3619Winson Chung        mScrollIndicatorPaddingLeft =
216f5f8cefb215fad98ae6d4487852e8b948aba3619Winson Chung            a.getDimensionPixelSize(R.styleable.PagedView_scrollIndicatorPaddingLeft, 0);
217f5f8cefb215fad98ae6d4487852e8b948aba3619Winson Chung        mScrollIndicatorPaddingRight =
218f5f8cefb215fad98ae6d4487852e8b948aba3619Winson Chung            a.getDimensionPixelSize(R.styleable.PagedView_scrollIndicatorPaddingRight, 0);
2199c4949e12c909d5e01d24386147b1c528015b31bAdam Cohen        a.recycle();
2209c4949e12c909d5e01d24386147b1c528015b31bAdam Cohen
221321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        setHapticFeedbackEnabled(false);
2220142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka        init();
223321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
224321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
225321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    /**
226321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung     * Initializes various states for this workspace.
227321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung     */
2280142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected void init() {
22986f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        mDirtyPageContent = new ArrayList<Boolean>();
23086f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        mDirtyPageContent.ensureCapacity(32);
231e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        mScroller = new Scroller(getContext(), new ScrollInterpolator());
23286f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        mCurrentPage = 0;
2337da1025bd7f15b04cf55c79b73e94e5e1bc959d9Winson Chung        mCenterPagesVertically = true;
234321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
235321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
236321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        mTouchSlop = configuration.getScaledTouchSlop();
237321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        mPagingTouchSlop = configuration.getScaledPagingTouchSlop();
238321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
239b5ba097015c4794fa822f30b38a60a7070a00097Adam Cohen        mDensity = getResources().getDisplayMetrics().density;
240265b9a66542148e0ff13971001cb9461065e1e0eAdam Cohen
241265b9a66542148e0ff13971001cb9461065e1e0eAdam Cohen        mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * mDensity);
242265b9a66542148e0ff13971001cb9461065e1e0eAdam Cohen        mMinFlingVelocity = (int) (MIN_FLING_VELOCITY * mDensity);
243265b9a66542148e0ff13971001cb9461065e1e0eAdam Cohen        mMinSnapVelocity = (int) (MIN_SNAP_VELOCITY * mDensity);
2448b805b17158886035b38261eb611d8641701ae43Michael Jurka        setOnHierarchyChangeListener(this);
245321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
246321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
24786f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung    public void setPageSwitchListener(PageSwitchListener pageSwitchListener) {
24886f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        mPageSwitchListener = pageSwitchListener;
24986f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        if (mPageSwitchListener != null) {
25086f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            mPageSwitchListener.onPageSwitch(getPageAt(mCurrentPage), mCurrentPage);
251321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
252321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
253321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
254321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    /**
255f0ea4d3378be7b962c8e0bce2392df5e82491fb8Winson Chung     * Called by subclasses to mark that data is ready, and that we can begin loading and laying
256f0ea4d3378be7b962c8e0bce2392df5e82491fb8Winson Chung     * out pages.
257f0ea4d3378be7b962c8e0bce2392df5e82491fb8Winson Chung     */
258f0ea4d3378be7b962c8e0bce2392df5e82491fb8Winson Chung    protected void setDataIsReady() {
259f0ea4d3378be7b962c8e0bce2392df5e82491fb8Winson Chung        mIsDataReady = true;
260f0ea4d3378be7b962c8e0bce2392df5e82491fb8Winson Chung    }
261f0ea4d3378be7b962c8e0bce2392df5e82491fb8Winson Chung    protected boolean isDataReady() {
262f0ea4d3378be7b962c8e0bce2392df5e82491fb8Winson Chung        return mIsDataReady;
263f0ea4d3378be7b962c8e0bce2392df5e82491fb8Winson Chung    }
264f0ea4d3378be7b962c8e0bce2392df5e82491fb8Winson Chung
265f0ea4d3378be7b962c8e0bce2392df5e82491fb8Winson Chung    /**
26686f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung     * Returns the index of the currently displayed page.
267321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung     *
26886f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung     * @return The index of the currently displayed page.
269321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung     */
27086f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung    int getCurrentPage() {
27186f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        return mCurrentPage;
272321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
273360e63fd3e77247002b86da2a77bd8dfe8c8a807Winson Chung    int getNextPage() {
274360e63fd3e77247002b86da2a77bd8dfe8c8a807Winson Chung        return (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
275360e63fd3e77247002b86da2a77bd8dfe8c8a807Winson Chung    }
276321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
27786f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung    int getPageCount() {
278321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        return getChildCount();
279321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
280321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
28186f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung    View getPageAt(int index) {
282321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        return getChildAt(index);
283321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
284321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
285ae4f155e090bf387b989fb738ee34ab045585711Adam Cohen    protected int indexToPage(int index) {
286ae4f155e090bf387b989fb738ee34ab045585711Adam Cohen        return index;
287ae4f155e090bf387b989fb738ee34ab045585711Adam Cohen    }
288ae4f155e090bf387b989fb738ee34ab045585711Adam Cohen
289321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    /**
290bbc60d8e79416e37cbede55c159bf6aaa6c171d5Winson Chung     * Updates the scroll of the current page immediately to its final scroll position.  We use this
291bbc60d8e79416e37cbede55c159bf6aaa6c171d5Winson Chung     * in CustomizePagedView to allow tabs to share the same PagedView while resetting the scroll of
292bbc60d8e79416e37cbede55c159bf6aaa6c171d5Winson Chung     * the previous tab page.
293bbc60d8e79416e37cbede55c159bf6aaa6c171d5Winson Chung     */
294bbc60d8e79416e37cbede55c159bf6aaa6c171d5Winson Chung    protected void updateCurrentPageScroll() {
295a128a7b9e5f00559ad6a443f7be8e8d7591942a3Winson Chung        int offset = getChildOffset(mCurrentPage);
296a128a7b9e5f00559ad6a443f7be8e8d7591942a3Winson Chung        int relOffset = getRelativeChildOffset(mCurrentPage);
297a128a7b9e5f00559ad6a443f7be8e8d7591942a3Winson Chung        int newX = offset - relOffset;
298bbc60d8e79416e37cbede55c159bf6aaa6c171d5Winson Chung        scrollTo(newX, 0);
299bbc60d8e79416e37cbede55c159bf6aaa6c171d5Winson Chung        mScroller.setFinalX(newX);
300dd6c09123c2341a51fb285936cfffef48c594356Michael Jurka        mScroller.forceFinished(true);
301bbc60d8e79416e37cbede55c159bf6aaa6c171d5Winson Chung    }
302bbc60d8e79416e37cbede55c159bf6aaa6c171d5Winson Chung
303bbc60d8e79416e37cbede55c159bf6aaa6c171d5Winson Chung    /**
30486f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung     * Sets the current page.
305321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung     */
30686f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung    void setCurrentPage(int currentPage) {
30772e0d34fc5d86260a7bd173aed44acf8b66b1c1dPatrick Dubroy        if (!mScroller.isFinished()) {
30872e0d34fc5d86260a7bd173aed44acf8b66b1c1dPatrick Dubroy            mScroller.abortAnimation();
30972e0d34fc5d86260a7bd173aed44acf8b66b1c1dPatrick Dubroy        }
310d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka        // don't introduce any checks like mCurrentPage == currentPage here-- if we change the
311d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka        // the default
312d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka        if (getChildCount() == 0) {
31372e0d34fc5d86260a7bd173aed44acf8b66b1c1dPatrick Dubroy            return;
31472e0d34fc5d86260a7bd173aed44acf8b66b1c1dPatrick Dubroy        }
315321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
31686f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        mCurrentPage = Math.max(0, Math.min(currentPage, getPageCount() - 1));
317bbc60d8e79416e37cbede55c159bf6aaa6c171d5Winson Chung        updateCurrentPageScroll();
3185a808358f1a773dab8babae44899ffd4ed1810c1Winson Chung        updateScrollingIndicator();
31986f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        notifyPageSwitchListener();
320a12a2502e6a448d36ab7b8de46de0c1afe40b34fWinson Chung        invalidate();
321321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
322321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
3230142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected void notifyPageSwitchListener() {
32486f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        if (mPageSwitchListener != null) {
32586f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            mPageSwitchListener.onPageSwitch(getPageAt(mCurrentPage), mCurrentPage);
326321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
327321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
328321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
329ce7e05fbe8abd5f25ec47e0e05b5cc76ceb39d2eMichael Jurka    protected void pageBeginMoving() {
330d74c984fcc61f86160476a2543faff715e3ee2fdMichael Jurka        if (!mIsPageMoving) {
331d74c984fcc61f86160476a2543faff715e3ee2fdMichael Jurka            mIsPageMoving = true;
332d74c984fcc61f86160476a2543faff715e3ee2fdMichael Jurka            onPageBeginMoving();
333d74c984fcc61f86160476a2543faff715e3ee2fdMichael Jurka        }
3341262e369484ce7f2565655ed80e6299232c70bd7Patrick Dubroy    }
3351262e369484ce7f2565655ed80e6299232c70bd7Patrick Dubroy
336ce7e05fbe8abd5f25ec47e0e05b5cc76ceb39d2eMichael Jurka    protected void pageEndMoving() {
337d74c984fcc61f86160476a2543faff715e3ee2fdMichael Jurka        if (mIsPageMoving) {
338d74c984fcc61f86160476a2543faff715e3ee2fdMichael Jurka            mIsPageMoving = false;
339d74c984fcc61f86160476a2543faff715e3ee2fdMichael Jurka            onPageEndMoving();
340d74c984fcc61f86160476a2543faff715e3ee2fdMichael Jurka        }
3411262e369484ce7f2565655ed80e6299232c70bd7Patrick Dubroy    }
3421262e369484ce7f2565655ed80e6299232c70bd7Patrick Dubroy
34326976d9b53161f80faf822d482ac771b8621cf31Adam Cohen    protected boolean isPageMoving() {
34426976d9b53161f80faf822d482ac771b8621cf31Adam Cohen        return mIsPageMoving;
34526976d9b53161f80faf822d482ac771b8621cf31Adam Cohen    }
34626976d9b53161f80faf822d482ac771b8621cf31Adam Cohen
3470142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    // a method that subclasses can override to add behavior
3481262e369484ce7f2565655ed80e6299232c70bd7Patrick Dubroy    protected void onPageBeginMoving() {
3490142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    }
3500142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka
3510142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    // a method that subclasses can override to add behavior
3521262e369484ce7f2565655ed80e6299232c70bd7Patrick Dubroy    protected void onPageEndMoving() {
3530142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    }
3540142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka
355321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    /**
35686f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung     * Registers the specified listener on each page contained in this workspace.
357321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung     *
358321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung     * @param l The listener used to respond to long clicks.
359321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung     */
360321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    @Override
361321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    public void setOnLongClickListener(OnLongClickListener l) {
362321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        mLongClickListener = l;
36386f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        final int count = getPageCount();
364321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        for (int i = 0; i < count; i++) {
36586f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            getPageAt(i).setOnLongClickListener(l);
366321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
367321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
368321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
369321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    @Override
37068d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen    public void scrollBy(int x, int y) {
3718b805b17158886035b38261eb611d8641701ae43Michael Jurka        scrollTo(mUnboundedScrollX + x, getScrollY() + y);
37268d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen    }
37368d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen
37468d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen    @Override
3750142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    public void scrollTo(int x, int y) {
37668d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen        mUnboundedScrollX = x;
37768d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen
37868d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen        if (x < 0) {
37968d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen            super.scrollTo(0, y);
38068d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen            if (mAllowOverScroll) {
38168d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen                overScroll(x);
38268d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen            }
38368d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen        } else if (x > mMaxScrollX) {
38468d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen            super.scrollTo(mMaxScrollX, y);
38568d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen            if (mAllowOverScroll) {
38668d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen                overScroll(x - mMaxScrollX);
38768d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen            }
38868d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen        } else {
389ebea84d1c95f4c38ba8cee46cd586fd757b4fce2Adam Cohen            mOverScrollX = x;
39068d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen            super.scrollTo(x, y);
39168d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen        }
39268d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen
3930142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka        mTouchX = x;
3940142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka        mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
3950142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    }
3960142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka
3970142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    // we moved this functionality to a helper function so SmoothPagedView can reuse it
3980142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected boolean computeScrollHelper() {
399321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        if (mScroller.computeScrollOffset()) {
400557d6ed50cc6560163440aa134870ef0bb11415bWinson Chung            // Don't bother scrolling if the page does not need to be moved
4018b805b17158886035b38261eb611d8641701ae43Michael Jurka            if (getScrollX() != mScroller.getCurrX()
4028b805b17158886035b38261eb611d8641701ae43Michael Jurka                || getScrollY() != mScroller.getCurrY()
403b06d95fc79389ce49dc97ecaae23e05d931a6ac2Michael Jurka                || mOverScrollX != mScroller.getCurrX()) {
404557d6ed50cc6560163440aa134870ef0bb11415bWinson Chung                scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
405557d6ed50cc6560163440aa134870ef0bb11415bWinson Chung            }
4060142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka            invalidate();
4070142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka            return true;
40886f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        } else if (mNextPage != INVALID_PAGE) {
40986f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            mCurrentPage = Math.max(0, Math.min(mNextPage, getPageCount() - 1));
41086f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            mNextPage = INVALID_PAGE;
4110142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka            notifyPageSwitchListener();
412b44b52439d155f570db7d6d0b80fdd3350e35685Winson Chung
413b44b52439d155f570db7d6d0b80fdd3350e35685Winson Chung            // Load the associated pages if necessary
4144e076545e4ccdbd3c045a3fa33869a2b7519a0ccWinson Chung            if (mDeferLoadAssociatedPagesUntilScrollCompletes) {
415b44b52439d155f570db7d6d0b80fdd3350e35685Winson Chung                loadAssociatedPages(mCurrentPage);
4164e076545e4ccdbd3c045a3fa33869a2b7519a0ccWinson Chung                mDeferLoadAssociatedPagesUntilScrollCompletes = false;
417b44b52439d155f570db7d6d0b80fdd3350e35685Winson Chung            }
418b44b52439d155f570db7d6d0b80fdd3350e35685Winson Chung
41973aa9755d3db1a76e9de0f55271ef5984d78ef6fAdam Cohen            // We don't want to trigger a page end moving unless the page has settled
42073aa9755d3db1a76e9de0f55271ef5984d78ef6fAdam Cohen            // and the user has stopped scrolling
42173aa9755d3db1a76e9de0f55271ef5984d78ef6fAdam Cohen            if (mTouchState == TOUCH_STATE_REST) {
42273aa9755d3db1a76e9de0f55271ef5984d78ef6fAdam Cohen                pageEndMoving();
42373aa9755d3db1a76e9de0f55271ef5984d78ef6fAdam Cohen            }
424c27d1bbb26f774a14122bc0b1d2ac33675439038Winson Chung
425c27d1bbb26f774a14122bc0b1d2ac33675439038Winson Chung            // Notify the user when the page changes
4268b805b17158886035b38261eb611d8641701ae43Michael Jurka            AccessibilityManager accessibilityManager = (AccessibilityManager)
4278b805b17158886035b38261eb611d8641701ae43Michael Jurka                    getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
4288b805b17158886035b38261eb611d8641701ae43Michael Jurka            if (accessibilityManager.isEnabled()) {
429c27d1bbb26f774a14122bc0b1d2ac33675439038Winson Chung                AccessibilityEvent ev =
430c27d1bbb26f774a14122bc0b1d2ac33675439038Winson Chung                    AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED);
431c27d1bbb26f774a14122bc0b1d2ac33675439038Winson Chung                ev.getText().add(getCurrentPageDescription());
432c27d1bbb26f774a14122bc0b1d2ac33675439038Winson Chung                sendAccessibilityEventUnchecked(ev);
433c27d1bbb26f774a14122bc0b1d2ac33675439038Winson Chung            }
4340142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka            return true;
435321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
4360142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka        return false;
4370142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    }
4380142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka
4390142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    @Override
4400142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    public void computeScroll() {
4410142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka        computeScrollHelper();
442321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
443321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
444321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    @Override
445321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
446f0ea4d3378be7b962c8e0bce2392df5e82491fb8Winson Chung        if (!mIsDataReady) {
447f0ea4d3378be7b962c8e0bce2392df5e82491fb8Winson Chung            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
448f0ea4d3378be7b962c8e0bce2392df5e82491fb8Winson Chung            return;
449f0ea4d3378be7b962c8e0bce2392df5e82491fb8Winson Chung        }
450f0ea4d3378be7b962c8e0bce2392df5e82491fb8Winson Chung
451321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
452321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
453321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        if (widthMode != MeasureSpec.EXACTLY) {
454321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
455321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
456321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
4576b879f0a5885274a85333531e091283405d490ccAdam Lesinski        /* Allow the height to be set as WRAP_CONTENT. This allows the particular case
4586b879f0a5885274a85333531e091283405d490ccAdam Lesinski         * of the All apps view on XLarge displays to not take up more space then it needs. Width
4596b879f0a5885274a85333531e091283405d490ccAdam Lesinski         * is still not allowed to be set as WRAP_CONTENT since many parts of the code expect
4606b879f0a5885274a85333531e091283405d490ccAdam Lesinski         * each page to have the same width.
4616b879f0a5885274a85333531e091283405d490ccAdam Lesinski         */
462321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
4636b879f0a5885274a85333531e091283405d490ccAdam Lesinski        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
4646b879f0a5885274a85333531e091283405d490ccAdam Lesinski        int maxChildHeight = 0;
4656b879f0a5885274a85333531e091283405d490ccAdam Lesinski
4668b805b17158886035b38261eb611d8641701ae43Michael Jurka        final int verticalPadding = getPaddingTop() + getPaddingBottom();
4678b805b17158886035b38261eb611d8641701ae43Michael Jurka        final int horizontalPadding = getPaddingLeft() + getPaddingRight();
468321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
46936fcb74425bebc1d88c9e3102484ec902b68f202Michael Jurka
470321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        // The children are given the same width and height as the workspace
4715f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka        // unless they were set to WRAP_CONTENT
472785d2eb2b8d7072c8124300dd9168ff51a91cf38Winson Chung        if (DEBUG) Log.d(TAG, "PagedView.onMeasure(): " + widthSize + ", " + heightSize);
473321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        final int childCount = getChildCount();
474321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        for (int i = 0; i < childCount; i++) {
4755f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka            // disallowing padding in paged view (just pass 0)
47622f823d340710bcde587b18a1542570cbe0360f5Adam Cohen            final View child = getPageAt(i);
4775f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
4785f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka
4795f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka            int childWidthMode;
4805f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka            if (lp.width == LayoutParams.WRAP_CONTENT) {
4815f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka                childWidthMode = MeasureSpec.AT_MOST;
4825f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka            } else {
4835f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka                childWidthMode = MeasureSpec.EXACTLY;
4845f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka            }
4855f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka
4865f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka            int childHeightMode;
4875f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka            if (lp.height == LayoutParams.WRAP_CONTENT) {
4885f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka                childHeightMode = MeasureSpec.AT_MOST;
4895f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka            } else {
4905f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka                childHeightMode = MeasureSpec.EXACTLY;
4915f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka            }
4925f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka
4935f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka            final int childWidthMeasureSpec =
494ea359c6aee44c0fe3bb94f7002c3b49208b32b7fWinson Chung                MeasureSpec.makeMeasureSpec(widthSize - horizontalPadding, childWidthMode);
4955f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka            final int childHeightMeasureSpec =
4966b879f0a5885274a85333531e091283405d490ccAdam Lesinski                MeasureSpec.makeMeasureSpec(heightSize - verticalPadding, childHeightMode);
4975f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka
4985f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
4996b879f0a5885274a85333531e091283405d490ccAdam Lesinski            maxChildHeight = Math.max(maxChildHeight, child.getMeasuredHeight());
500785d2eb2b8d7072c8124300dd9168ff51a91cf38Winson Chung            if (DEBUG) Log.d(TAG, "\tmeasure-child" + i + ": " + child.getMeasuredWidth() + ", "
501785d2eb2b8d7072c8124300dd9168ff51a91cf38Winson Chung                    + child.getMeasuredHeight());
5026b879f0a5885274a85333531e091283405d490ccAdam Lesinski        }
5036b879f0a5885274a85333531e091283405d490ccAdam Lesinski
5046b879f0a5885274a85333531e091283405d490ccAdam Lesinski        if (heightMode == MeasureSpec.AT_MOST) {
5056b879f0a5885274a85333531e091283405d490ccAdam Lesinski            heightSize = maxChildHeight + verticalPadding;
506321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
507ae890b85dfbcdef592f4a97cca819922a07a83caWinson Chung
508ae890b85dfbcdef592f4a97cca819922a07a83caWinson Chung        setMeasuredDimension(widthSize, heightSize);
509ae890b85dfbcdef592f4a97cca819922a07a83caWinson Chung
510a128a7b9e5f00559ad6a443f7be8e8d7591942a3Winson Chung        if (childCount > 0) {
511a128a7b9e5f00559ad6a443f7be8e8d7591942a3Winson Chung            if (DEBUG) Log.d(TAG, "getRelativeChildOffset(): " + getMeasuredWidth() + ", "
512a128a7b9e5f00559ad6a443f7be8e8d7591942a3Winson Chung                    + getChildWidth(0));
513a128a7b9e5f00559ad6a443f7be8e8d7591942a3Winson Chung
514a128a7b9e5f00559ad6a443f7be8e8d7591942a3Winson Chung            // Calculate the variable page spacing if necessary
515a128a7b9e5f00559ad6a443f7be8e8d7591942a3Winson Chung            if (mPageSpacing < 0) {
516a128a7b9e5f00559ad6a443f7be8e8d7591942a3Winson Chung                // The gap between pages in the PagedView should be equal to the gap from the page
517a128a7b9e5f00559ad6a443f7be8e8d7591942a3Winson Chung                // to the edge of the screen (so it is not visible in the current screen).  To
518a128a7b9e5f00559ad6a443f7be8e8d7591942a3Winson Chung                // account for unequal padding on each side of the paged view, we take the maximum
519a128a7b9e5f00559ad6a443f7be8e8d7591942a3Winson Chung                // of the left/right gap and use that as the gap between each page.
520a128a7b9e5f00559ad6a443f7be8e8d7591942a3Winson Chung                int offset = getRelativeChildOffset(0);
521a128a7b9e5f00559ad6a443f7be8e8d7591942a3Winson Chung                int spacing = Math.max(offset, widthSize - offset -
522a128a7b9e5f00559ad6a443f7be8e8d7591942a3Winson Chung                        getChildAt(0).getMeasuredWidth());
523a128a7b9e5f00559ad6a443f7be8e8d7591942a3Winson Chung                setPageSpacing(spacing);
524a128a7b9e5f00559ad6a443f7be8e8d7591942a3Winson Chung            }
525a128a7b9e5f00559ad6a443f7be8e8d7591942a3Winson Chung        }
526a128a7b9e5f00559ad6a443f7be8e8d7591942a3Winson Chung
52725b2995c6723c01fbabcea04636358501bc8d17aAdam Cohen        // We can't call getChildOffset/getRelativeChildOffset until we set the measured dimensions.
52825b2995c6723c01fbabcea04636358501bc8d17aAdam Cohen        // We also wait until we set the measured dimensions before flushing the cache as well, to
52925b2995c6723c01fbabcea04636358501bc8d17aAdam Cohen        // ensure that the cache is filled with good values.
53025b2995c6723c01fbabcea04636358501bc8d17aAdam Cohen        invalidateCachedOffsets();
53125b2995c6723c01fbabcea04636358501bc8d17aAdam Cohen        updateScrollingIndicatorPosition();
53225b2995c6723c01fbabcea04636358501bc8d17aAdam Cohen
533faa28305134b0d391d2baf1bff5bff4710fe819fAdam Cohen        if (childCount > 0) {
534faa28305134b0d391d2baf1bff5bff4710fe819fAdam Cohen            mMaxScrollX = getChildOffset(childCount - 1) - getRelativeChildOffset(childCount - 1);
535faa28305134b0d391d2baf1bff5bff4710fe819fAdam Cohen        } else {
536faa28305134b0d391d2baf1bff5bff4710fe819fAdam Cohen            mMaxScrollX = 0;
537faa28305134b0d391d2baf1bff5bff4710fe819fAdam Cohen        }
538cfc629446ca86366665263dba6520a7b978b7c3eMichael Jurka    }
539321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
5408c920dd3683d752aa4c43e964831ce53f9b72887Michael Jurka    protected void scrollToNewPageWithoutMovingPages(int newCurrentPage) {
541af91de06b99e2d5d41ce79fefa34ce2111e51917Michael Jurka        int newX = getChildOffset(newCurrentPage) - getRelativeChildOffset(newCurrentPage);
5428b805b17158886035b38261eb611d8641701ae43Michael Jurka        int delta = newX - getScrollX();
543af91de06b99e2d5d41ce79fefa34ce2111e51917Michael Jurka
5448c920dd3683d752aa4c43e964831ce53f9b72887Michael Jurka        final int pageCount = getChildCount();
5458c920dd3683d752aa4c43e964831ce53f9b72887Michael Jurka        for (int i = 0; i < pageCount; i++) {
54622f823d340710bcde587b18a1542570cbe0360f5Adam Cohen            View page = (View) getPageAt(i);
5478c920dd3683d752aa4c43e964831ce53f9b72887Michael Jurka            page.setX(page.getX() + delta);
548af91de06b99e2d5d41ce79fefa34ce2111e51917Michael Jurka        }
549af91de06b99e2d5d41ce79fefa34ce2111e51917Michael Jurka        setCurrentPage(newCurrentPage);
550af91de06b99e2d5d41ce79fefa34ce2111e51917Michael Jurka    }
551af91de06b99e2d5d41ce79fefa34ce2111e51917Michael Jurka
5528c920dd3683d752aa4c43e964831ce53f9b72887Michael Jurka    // A layout scale of 1.0f assumes that the pages, in their unshrunken state, have a
5538c920dd3683d752aa4c43e964831ce53f9b72887Michael Jurka    // scale of 1.0f. A layout scale of 0.8f assumes the pages have a scale of 0.8f, and
554d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka    // tightens the layout accordingly
555d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka    public void setLayoutScale(float childrenScale) {
556d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka        mLayoutScale = childrenScale;
55773894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen        invalidateCachedOffsets();
558d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka
559d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka        // Now we need to do a re-layout, but preserving absolute X and Y coordinates
560d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka        int childCount = getChildCount();
561d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka        float childrenX[] = new float[childCount];
562d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka        float childrenY[] = new float[childCount];
563d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka        for (int i = 0; i < childCount; i++) {
56422f823d340710bcde587b18a1542570cbe0360f5Adam Cohen            final View child = getPageAt(i);
565d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka            childrenX[i] = child.getX();
566d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka            childrenY[i] = child.getY();
567d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka        }
568b26f3d6a8c62e7c1a603b6c7979375d8dd4f20d4Winson Chung        // Trigger a full re-layout (never just call onLayout directly!)
569b26f3d6a8c62e7c1a603b6c7979375d8dd4f20d4Winson Chung        int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY);
570b26f3d6a8c62e7c1a603b6c7979375d8dd4f20d4Winson Chung        int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY);
571b26f3d6a8c62e7c1a603b6c7979375d8dd4f20d4Winson Chung        requestLayout();
572b26f3d6a8c62e7c1a603b6c7979375d8dd4f20d4Winson Chung        measure(widthSpec, heightSpec);
5738b805b17158886035b38261eb611d8641701ae43Michael Jurka        layout(getLeft(), getTop(), getRight(), getBottom());
574d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka        for (int i = 0; i < childCount; i++) {
57522f823d340710bcde587b18a1542570cbe0360f5Adam Cohen            final View child = getPageAt(i);
576d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka            child.setX(childrenX[i]);
577d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka            child.setY(childrenY[i]);
578d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka        }
579b26f3d6a8c62e7c1a603b6c7979375d8dd4f20d4Winson Chung
580d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka        // Also, the page offset has changed  (since the pages are now smaller);
581d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka        // update the page offset, but again preserving absolute X and Y coordinates
5828c920dd3683d752aa4c43e964831ce53f9b72887Michael Jurka        scrollToNewPageWithoutMovingPages(mCurrentPage);
583d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka    }
584d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka
58560b0712bf555a32e609271e0b8ea90743a148efbAdam Cohen    public void setPageSpacing(int pageSpacing) {
58660b0712bf555a32e609271e0b8ea90743a148efbAdam Cohen        mPageSpacing = pageSpacing;
58760b0712bf555a32e609271e0b8ea90743a148efbAdam Cohen        invalidateCachedOffsets();
58860b0712bf555a32e609271e0b8ea90743a148efbAdam Cohen    }
58960b0712bf555a32e609271e0b8ea90743a148efbAdam Cohen
590cfc629446ca86366665263dba6520a7b978b7c3eMichael Jurka    @Override
59128750fba6a2d141eb9a1e566718c17236030b815Michael Jurka    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
592f0ea4d3378be7b962c8e0bce2392df5e82491fb8Winson Chung        if (!mIsDataReady) {
593f0ea4d3378be7b962c8e0bce2392df5e82491fb8Winson Chung            return;
594f0ea4d3378be7b962c8e0bce2392df5e82491fb8Winson Chung        }
595f0ea4d3378be7b962c8e0bce2392df5e82491fb8Winson Chung
596785d2eb2b8d7072c8124300dd9168ff51a91cf38Winson Chung        if (DEBUG) Log.d(TAG, "PagedView.onLayout()");
5978b805b17158886035b38261eb611d8641701ae43Michael Jurka        final int verticalPadding = getPaddingTop() + getPaddingBottom();
598321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        final int childCount = getChildCount();
599a128a7b9e5f00559ad6a443f7be8e8d7591942a3Winson Chung        int childLeft = getRelativeChildOffset(0);
600321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
601321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        for (int i = 0; i < childCount; i++) {
60222f823d340710bcde587b18a1542570cbe0360f5Adam Cohen            final View child = getPageAt(i);
603321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            if (child.getVisibility() != View.GONE) {
604d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka                final int childWidth = getScaledMeasuredWidth(child);
6056b879f0a5885274a85333531e091283405d490ccAdam Lesinski                final int childHeight = child.getMeasuredHeight();
6068b805b17158886035b38261eb611d8641701ae43Michael Jurka                int childTop = getPaddingTop();
6076b879f0a5885274a85333531e091283405d490ccAdam Lesinski                if (mCenterPagesVertically) {
6086b879f0a5885274a85333531e091283405d490ccAdam Lesinski                    childTop += ((getMeasuredHeight() - verticalPadding) - childHeight) / 2;
6096b879f0a5885274a85333531e091283405d490ccAdam Lesinski                }
610d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka
611785d2eb2b8d7072c8124300dd9168ff51a91cf38Winson Chung                if (DEBUG) Log.d(TAG, "\tlayout-child" + i + ": " + childLeft + ", " + childTop);
6126b879f0a5885274a85333531e091283405d490ccAdam Lesinski                child.layout(childLeft, childTop,
613d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka                        childLeft + child.getMeasuredWidth(), childTop + childHeight);
6149c4949e12c909d5e01d24386147b1c528015b31bAdam Cohen                childLeft += childWidth + mPageSpacing;
615321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            }
616321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
617c3665faf4525de78c64e1a98106e12f1c23810c9Winson Chung
618c3665faf4525de78c64e1a98106e12f1c23810c9Winson Chung        if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
619c3665faf4525de78c64e1a98106e12f1c23810c9Winson Chung            setHorizontalScrollBarEnabled(false);
620dd6c09123c2341a51fb285936cfffef48c594356Michael Jurka            updateCurrentPageScroll();
621c3665faf4525de78c64e1a98106e12f1c23810c9Winson Chung            setHorizontalScrollBarEnabled(true);
622c3665faf4525de78c64e1a98106e12f1c23810c9Winson Chung            mFirstLayout = false;
623c3665faf4525de78c64e1a98106e12f1c23810c9Winson Chung        }
624321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
625321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
62673894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen    protected void screenScrolled(int screenCenter) {
62773894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen        if (isScrollingIndicatorEnabled()) {
62873894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen            updateScrollingIndicator();
62973894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen        }
630869390b6abd6e005a5ce85a8ee3c11daeecce919Michael Jurka        boolean isInOverscroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX;
631869390b6abd6e005a5ce85a8ee3c11daeecce919Michael Jurka
632869390b6abd6e005a5ce85a8ee3c11daeecce919Michael Jurka        if (mFadeInAdjacentScreens && !isInOverscroll) {
63373894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen            for (int i = 0; i < getChildCount(); i++) {
63473894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen                View child = getChildAt(i);
63573894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen                if (child != null) {
63673894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen                    float scrollProgress = getScrollProgress(screenCenter, child, i);
63773894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen                    float alpha = 1 - Math.abs(scrollProgress);
6387372c59277a23a31eeb1513a554b29d7bf05aedbMichael Jurka                    child.setAlpha(alpha);
639affd7b4d23cecb4ed74133dd8bd9a5ede099c562Winson Chung                }
640321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            }
64173894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen            invalidate();
642321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
643e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung    }
6440142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka
645e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung    @Override
6468b805b17158886035b38261eb611d8641701ae43Michael Jurka    public void onChildViewAdded(View parent, View child) {
6472591f6a5202664faf8cde2b68361e3842ce76ba9Adam Cohen        // This ensures that when children are added, they get the correct transforms / alphas
6482591f6a5202664faf8cde2b68361e3842ce76ba9Adam Cohen        // in accordance with any scroll effects.
6492591f6a5202664faf8cde2b68361e3842ce76ba9Adam Cohen        mForceScreenScrolled = true;
6502591f6a5202664faf8cde2b68361e3842ce76ba9Adam Cohen        invalidate();
65125b2995c6723c01fbabcea04636358501bc8d17aAdam Cohen        invalidateCachedOffsets();
6522591f6a5202664faf8cde2b68361e3842ce76ba9Adam Cohen    }
6532591f6a5202664faf8cde2b68361e3842ce76ba9Adam Cohen
6548b805b17158886035b38261eb611d8641701ae43Michael Jurka    @Override
6558b805b17158886035b38261eb611d8641701ae43Michael Jurka    public void onChildViewRemoved(View parent, View child) {
6568b805b17158886035b38261eb611d8641701ae43Michael Jurka    }
6578b805b17158886035b38261eb611d8641701ae43Michael Jurka
65873894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen    protected void invalidateCachedOffsets() {
65973894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen        int count = getChildCount();
66025b2995c6723c01fbabcea04636358501bc8d17aAdam Cohen        if (count == 0) {
66125b2995c6723c01fbabcea04636358501bc8d17aAdam Cohen            mChildOffsets = null;
66225b2995c6723c01fbabcea04636358501bc8d17aAdam Cohen            mChildRelativeOffsets = null;
66325b2995c6723c01fbabcea04636358501bc8d17aAdam Cohen            mChildOffsetsWithLayoutScale = null;
66425b2995c6723c01fbabcea04636358501bc8d17aAdam Cohen            return;
66525b2995c6723c01fbabcea04636358501bc8d17aAdam Cohen        }
66673894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen
66773894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen        mChildOffsets = new int[count];
66873894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen        mChildRelativeOffsets = new int[count];
66973894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen        mChildOffsetsWithLayoutScale = new int[count];
67073894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen        for (int i = 0; i < count; i++) {
67173894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen            mChildOffsets[i] = -1;
67273894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen            mChildRelativeOffsets[i] = -1;
67373894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen            mChildOffsetsWithLayoutScale[i] = -1;
67473894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen        }
67573894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen    }
67673894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen
67773894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen    protected int getChildOffset(int index) {
67873894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen        int[] childOffsets = Float.compare(mLayoutScale, 1f) == 0 ?
67973894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen                mChildOffsets : mChildOffsetsWithLayoutScale;
68073894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen
68173894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen        if (childOffsets != null && childOffsets[index] != -1) {
68273894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen            return childOffsets[index];
68373894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen        } else {
68473894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen            if (getChildCount() == 0)
68573894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen                return 0;
68673894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen
68773894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen            int offset = getRelativeChildOffset(0);
68873894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen            for (int i = 0; i < index; ++i) {
68973894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen                offset += getScaledMeasuredWidth(getPageAt(i)) + mPageSpacing;
69073894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen            }
69173894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen            if (childOffsets != null) {
69273894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen                childOffsets[index] = offset;
69373894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen            }
69473894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen            return offset;
69573894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen        }
69673894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen    }
69773894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen
69873894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen    protected int getRelativeChildOffset(int index) {
69973894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen        if (mChildRelativeOffsets != null && mChildRelativeOffsets[index] != -1) {
70073894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen            return mChildRelativeOffsets[index];
70173894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen        } else {
7028b805b17158886035b38261eb611d8641701ae43Michael Jurka            final int padding = getPaddingLeft() + getPaddingRight();
7038b805b17158886035b38261eb611d8641701ae43Michael Jurka            final int offset = getPaddingLeft() +
70473894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen                    (getMeasuredWidth() - padding - getChildWidth(index)) / 2;
70573894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen            if (mChildRelativeOffsets != null) {
70673894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen                mChildRelativeOffsets[index] = offset;
70773894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen            }
70873894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen            return offset;
70973894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen        }
71073894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen    }
71173894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen
71273894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen    protected int getScaledMeasuredWidth(View child) {
71373894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen        // This functions are called enough times that it actually makes a difference in the
71473894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen        // profiler -- so just inline the max() here
71573894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen        final int measuredWidth = child.getMeasuredWidth();
71673894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen        final int minWidth = mMinimumWidth;
71773894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen        final int maxWidth = (minWidth > measuredWidth) ? minWidth : measuredWidth;
71873894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen        return (int) (maxWidth * mLayoutScale + 0.5f);
71973894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen    }
72073894964c27f68a3a7ffb45bfba76520613fd3f0Adam Cohen
721dde558b8b8bcf3fac6d2ceceafa1f7546f98a264Michael Jurka    protected void getVisiblePages(int[] range) {
7220142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka        final int pageCount = getChildCount();
7234ff7d79ddcee0a95fe6e7458731decd694b91d03Michael Jurka
724c4fb9173e73c0092a089512734c0d7df13189014Michael Jurka        if (pageCount > 0) {
725c4fb9173e73c0092a089512734c0d7df13189014Michael Jurka            final int screenWidth = getMeasuredWidth();
726c4fb9173e73c0092a089512734c0d7df13189014Michael Jurka            int leftScreen = 0;
727c4fb9173e73c0092a089512734c0d7df13189014Michael Jurka            int rightScreen = 0;
72847f747418efa337e0fc56e10748a6a595b8974f6Michael Jurka            View currPage = getPageAt(leftScreen);
72980c69853d64b73e5d62ed33bea1b10680557ee3eMichael Jurka            while (leftScreen < pageCount - 1 &&
7308b805b17158886035b38261eb611d8641701ae43Michael Jurka                    currPage.getX() + currPage.getWidth() -
7318b805b17158886035b38261eb611d8641701ae43Michael Jurka                    currPage.getPaddingRight() < getScrollX()) {
732c4fb9173e73c0092a089512734c0d7df13189014Michael Jurka                leftScreen++;
73347f747418efa337e0fc56e10748a6a595b8974f6Michael Jurka                currPage = getPageAt(leftScreen);
734c4fb9173e73c0092a089512734c0d7df13189014Michael Jurka            }
735c4fb9173e73c0092a089512734c0d7df13189014Michael Jurka            rightScreen = leftScreen;
73647f747418efa337e0fc56e10748a6a595b8974f6Michael Jurka            currPage = getPageAt(rightScreen + 1);
73780c69853d64b73e5d62ed33bea1b10680557ee3eMichael Jurka            while (rightScreen < pageCount - 1 &&
7388b805b17158886035b38261eb611d8641701ae43Michael Jurka                    currPage.getX() - currPage.getPaddingLeft() < getScrollX() + screenWidth) {
739c4fb9173e73c0092a089512734c0d7df13189014Michael Jurka                rightScreen++;
74047f747418efa337e0fc56e10748a6a595b8974f6Michael Jurka                currPage = getPageAt(rightScreen + 1);
741c4fb9173e73c0092a089512734c0d7df13189014Michael Jurka            }
742dde558b8b8bcf3fac6d2ceceafa1f7546f98a264Michael Jurka            range[0] = leftScreen;
743dde558b8b8bcf3fac6d2ceceafa1f7546f98a264Michael Jurka            range[1] = rightScreen;
744dde558b8b8bcf3fac6d2ceceafa1f7546f98a264Michael Jurka        } else {
745dde558b8b8bcf3fac6d2ceceafa1f7546f98a264Michael Jurka            range[0] = -1;
746dde558b8b8bcf3fac6d2ceceafa1f7546f98a264Michael Jurka            range[1] = -1;
747dde558b8b8bcf3fac6d2ceceafa1f7546f98a264Michael Jurka        }
748dde558b8b8bcf3fac6d2ceceafa1f7546f98a264Michael Jurka    }
749dde558b8b8bcf3fac6d2ceceafa1f7546f98a264Michael Jurka
750dde558b8b8bcf3fac6d2ceceafa1f7546f98a264Michael Jurka    @Override
751dde558b8b8bcf3fac6d2ceceafa1f7546f98a264Michael Jurka    protected void dispatchDraw(Canvas canvas) {
752dde558b8b8bcf3fac6d2ceceafa1f7546f98a264Michael Jurka        int halfScreenSize = getMeasuredWidth() / 2;
7538b805b17158886035b38261eb611d8641701ae43Michael Jurka        // mOverScrollX is equal to getScrollX() when we're within the normal scroll range.
7548b805b17158886035b38261eb611d8641701ae43Michael Jurka        // Otherwise it is equal to the scaled overscroll position.
755ebea84d1c95f4c38ba8cee46cd586fd757b4fce2Adam Cohen        int screenCenter = mOverScrollX + halfScreenSize;
756dde558b8b8bcf3fac6d2ceceafa1f7546f98a264Michael Jurka
757dde558b8b8bcf3fac6d2ceceafa1f7546f98a264Michael Jurka        if (screenCenter != mLastScreenCenter || mForceScreenScrolled) {
758b06d95fc79389ce49dc97ecaae23e05d931a6ac2Michael Jurka            // set mForceScreenScrolled before calling screenScrolled so that screenScrolled can
759b06d95fc79389ce49dc97ecaae23e05d931a6ac2Michael Jurka            // set it for the next frame
760b06d95fc79389ce49dc97ecaae23e05d931a6ac2Michael Jurka            mForceScreenScrolled = false;
761dde558b8b8bcf3fac6d2ceceafa1f7546f98a264Michael Jurka            screenScrolled(screenCenter);
762dde558b8b8bcf3fac6d2ceceafa1f7546f98a264Michael Jurka            mLastScreenCenter = screenCenter;
763dde558b8b8bcf3fac6d2ceceafa1f7546f98a264Michael Jurka        }
764dde558b8b8bcf3fac6d2ceceafa1f7546f98a264Michael Jurka
765dde558b8b8bcf3fac6d2ceceafa1f7546f98a264Michael Jurka        // Find out which screens are visible; as an optimization we only call draw on them
766dde558b8b8bcf3fac6d2ceceafa1f7546f98a264Michael Jurka        final int pageCount = getChildCount();
767dde558b8b8bcf3fac6d2ceceafa1f7546f98a264Michael Jurka        if (pageCount > 0) {
768dde558b8b8bcf3fac6d2ceceafa1f7546f98a264Michael Jurka            getVisiblePages(mTempVisiblePagesRange);
769dde558b8b8bcf3fac6d2ceceafa1f7546f98a264Michael Jurka            final int leftScreen = mTempVisiblePagesRange[0];
770dde558b8b8bcf3fac6d2ceceafa1f7546f98a264Michael Jurka            final int rightScreen = mTempVisiblePagesRange[1];
771c6f10b906fc9c120cafc81046a42b2434ff635a9Winson Chung            if (leftScreen != -1 && rightScreen != -1) {
772c6f10b906fc9c120cafc81046a42b2434ff635a9Winson Chung                final long drawingTime = getDrawingTime();
773c6f10b906fc9c120cafc81046a42b2434ff635a9Winson Chung                // Clip to the bounds
774c6f10b906fc9c120cafc81046a42b2434ff635a9Winson Chung                canvas.save();
7758b805b17158886035b38261eb611d8641701ae43Michael Jurka                canvas.clipRect(getScrollX(), getScrollY(), getScrollX() + getRight() - getLeft(),
7768b805b17158886035b38261eb611d8641701ae43Michael Jurka                        getScrollY() + getBottom() - getTop());
777c6f10b906fc9c120cafc81046a42b2434ff635a9Winson Chung
77880c69853d64b73e5d62ed33bea1b10680557ee3eMichael Jurka                // On certain graphics drivers, if you draw to a off-screen buffer that's not
77980c69853d64b73e5d62ed33bea1b10680557ee3eMichael Jurka                // used, it can lead to poor performance. We were running into this when
78080c69853d64b73e5d62ed33bea1b10680557ee3eMichael Jurka                // setChildrenLayersEnabled was called on a CellLayout; that triggered a re-draw
78180c69853d64b73e5d62ed33bea1b10680557ee3eMichael Jurka                // of that CellLayout's hardware layer, even if that CellLayout wasn't visible.
78280c69853d64b73e5d62ed33bea1b10680557ee3eMichael Jurka                // As a fix, below we set pages that aren't going to be rendered are to be
78380c69853d64b73e5d62ed33bea1b10680557ee3eMichael Jurka                // View.INVISIBLE, preventing re-drawing of their hardware layer
78480c69853d64b73e5d62ed33bea1b10680557ee3eMichael Jurka                for (int i = getChildCount() - 1; i >= 0; i--) {
78580c69853d64b73e5d62ed33bea1b10680557ee3eMichael Jurka                    final View v = getPageAt(i);
7868b805b17158886035b38261eb611d8641701ae43Michael Jurka                    if (leftScreen <= i && i <= rightScreen && v.getAlpha() > 0) {
78780c69853d64b73e5d62ed33bea1b10680557ee3eMichael Jurka                        v.setVisibility(VISIBLE);
78880c69853d64b73e5d62ed33bea1b10680557ee3eMichael Jurka                        drawChild(canvas, v, drawingTime);
78980c69853d64b73e5d62ed33bea1b10680557ee3eMichael Jurka                    } else {
79080c69853d64b73e5d62ed33bea1b10680557ee3eMichael Jurka                        v.setVisibility(INVISIBLE);
79180c69853d64b73e5d62ed33bea1b10680557ee3eMichael Jurka                    }
792c6f10b906fc9c120cafc81046a42b2434ff635a9Winson Chung                }
793c6f10b906fc9c120cafc81046a42b2434ff635a9Winson Chung                canvas.restore();
794c4fb9173e73c0092a089512734c0d7df13189014Michael Jurka            }
7950142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka        }
796321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
797321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
798321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    @Override
799321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
800ae4f155e090bf387b989fb738ee34ab045585711Adam Cohen        int page = indexToPage(indexOfChild(child));
80186f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        if (page != mCurrentPage || !mScroller.isFinished()) {
80286f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            snapToPage(page);
803321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            return true;
804321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
805321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        return false;
806321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
807321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
808321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    @Override
809321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
81086f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        int focusablePage;
81186f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        if (mNextPage != INVALID_PAGE) {
81286f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            focusablePage = mNextPage;
813321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        } else {
81486f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            focusablePage = mCurrentPage;
815321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
81686f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        View v = getPageAt(focusablePage);
817321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        if (v != null) {
81876fc085d28178a5d4fb3787ede956281a2cc3179Adam Cohen            return v.requestFocus(direction, previouslyFocusedRect);
819321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
820321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        return false;
821321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
822321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
823321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    @Override
824321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    public boolean dispatchUnhandledMove(View focused, int direction) {
825321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        if (direction == View.FOCUS_LEFT) {
82686f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            if (getCurrentPage() > 0) {
82786f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung                snapToPage(getCurrentPage() - 1);
828321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                return true;
829321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            }
830321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        } else if (direction == View.FOCUS_RIGHT) {
83186f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            if (getCurrentPage() < getPageCount() - 1) {
83286f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung                snapToPage(getCurrentPage() + 1);
833321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                return true;
834321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            }
835321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
836321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        return super.dispatchUnhandledMove(focused, direction);
837321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
838321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
839321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    @Override
840321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
84186f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        if (mCurrentPage >= 0 && mCurrentPage < getPageCount()) {
84286f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            getPageAt(mCurrentPage).addFocusables(views, direction);
843321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
844321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        if (direction == View.FOCUS_LEFT) {
84586f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            if (mCurrentPage > 0) {
84686f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung                getPageAt(mCurrentPage - 1).addFocusables(views, direction);
847321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            }
848321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        } else if (direction == View.FOCUS_RIGHT){
84986f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            if (mCurrentPage < getPageCount() - 1) {
85086f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung                getPageAt(mCurrentPage + 1).addFocusables(views, direction);
851321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            }
852321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
853321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
854321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
855321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    /**
856321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung     * If one of our descendant views decides that it could be focused now, only
85786f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung     * pass that along if it's on the current page.
858321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung     *
85986f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung     * This happens when live folders requery, and if they're off page, they
86086f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung     * end up calling requestFocus, which pulls it on page.
861321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung     */
862321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    @Override
863321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    public void focusableViewAvailable(View focused) {
86486f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        View current = getPageAt(mCurrentPage);
865321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        View v = focused;
866321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        while (true) {
867321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            if (v == current) {
868321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                super.focusableViewAvailable(focused);
869321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                return;
870321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            }
871321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            if (v == this) {
872321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                return;
873321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            }
874321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            ViewParent parent = v.getParent();
875321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            if (parent instanceof View) {
876321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                v = (View)v.getParent();
877321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            } else {
878321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                return;
879321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            }
880321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
881321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
882321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
883321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    /**
884321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung     * {@inheritDoc}
885321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung     */
886321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    @Override
887321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
888321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        if (disallowIntercept) {
889321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            // We need to make sure to cancel our long press if
890321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            // a scrollable widget takes over touch events
89122f823d340710bcde587b18a1542570cbe0360f5Adam Cohen            final View currentPage = getPageAt(mCurrentPage);
89286f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            currentPage.cancelLongPress();
893321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
894321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        super.requestDisallowInterceptTouchEvent(disallowIntercept);
895321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
896321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
897d0ce1ec2e21ce3bb0ba3549a01d1d06b440a8e45Patrick Dubroy    /**
898d0ce1ec2e21ce3bb0ba3549a01d1d06b440a8e45Patrick Dubroy     * Return true if a tap at (x, y) should trigger a flip to the previous page.
899d0ce1ec2e21ce3bb0ba3549a01d1d06b440a8e45Patrick Dubroy     */
900d0ce1ec2e21ce3bb0ba3549a01d1d06b440a8e45Patrick Dubroy    protected boolean hitsPreviousPage(float x, float y) {
901d0ce1ec2e21ce3bb0ba3549a01d1d06b440a8e45Patrick Dubroy        return (x < getRelativeChildOffset(mCurrentPage) - mPageSpacing);
902d0ce1ec2e21ce3bb0ba3549a01d1d06b440a8e45Patrick Dubroy    }
903d0ce1ec2e21ce3bb0ba3549a01d1d06b440a8e45Patrick Dubroy
904d0ce1ec2e21ce3bb0ba3549a01d1d06b440a8e45Patrick Dubroy    /**
905d0ce1ec2e21ce3bb0ba3549a01d1d06b440a8e45Patrick Dubroy     * Return true if a tap at (x, y) should trigger a flip to the next page.
906d0ce1ec2e21ce3bb0ba3549a01d1d06b440a8e45Patrick Dubroy     */
907d0ce1ec2e21ce3bb0ba3549a01d1d06b440a8e45Patrick Dubroy    protected boolean hitsNextPage(float x, float y) {
908d0ce1ec2e21ce3bb0ba3549a01d1d06b440a8e45Patrick Dubroy        return  (x > (getMeasuredWidth() - getRelativeChildOffset(mCurrentPage) + mPageSpacing));
909d0ce1ec2e21ce3bb0ba3549a01d1d06b440a8e45Patrick Dubroy    }
910d0ce1ec2e21ce3bb0ba3549a01d1d06b440a8e45Patrick Dubroy
911321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    @Override
912321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    public boolean onInterceptTouchEvent(MotionEvent ev) {
913321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        /*
914321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung         * This method JUST determines whether we want to intercept the motion.
915321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung         * If we return true, onTouchEvent will be called and we do the actual
916321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung         * scrolling there.
917321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung         */
9186342bbae1a18f3c1862953e33309481703f541cfAdam Cohen        acquireVelocityTrackerAndAddMovement(ev);
919321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
92045e1d6ec0a213a444d01466c3d4f1ded5508ed63Winson Chung        // Skip touch handling if there are no pages to swipe
92145e1d6ec0a213a444d01466c3d4f1ded5508ed63Winson Chung        if (getChildCount() <= 0) return super.onInterceptTouchEvent(ev);
92245e1d6ec0a213a444d01466c3d4f1ded5508ed63Winson Chung
923321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        /*
924321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung         * Shortcut the most recurring case: the user is in the dragging
925321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung         * state and he is moving his finger.  We want to intercept this
926321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung         * motion.
927321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung         */
928321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        final int action = ev.getAction();
929321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        if ((action == MotionEvent.ACTION_MOVE) &&
930321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                (mTouchState == TOUCH_STATE_SCROLLING)) {
931321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            return true;
932321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
933321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
934321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        switch (action & MotionEvent.ACTION_MASK) {
935321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            case MotionEvent.ACTION_MOVE: {
936321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                /*
937321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
938321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                 * whether the user has moved far enough from his original down touch.
939321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                 */
9401ff706b8c03063740ca74868fea46fa47d71bb27Michael Jurka                if (mActivePointerId != INVALID_POINTER) {
9411ff706b8c03063740ca74868fea46fa47d71bb27Michael Jurka                    determineScrollingStart(ev);
9421ff706b8c03063740ca74868fea46fa47d71bb27Michael Jurka                    break;
9431ff706b8c03063740ca74868fea46fa47d71bb27Michael Jurka                }
9441ff706b8c03063740ca74868fea46fa47d71bb27Michael Jurka                // if mActivePointerId is INVALID_POINTER, then we must have missed an ACTION_DOWN
9451ff706b8c03063740ca74868fea46fa47d71bb27Michael Jurka                // event. in that case, treat the first occurence of a move event as a ACTION_DOWN
9461ff706b8c03063740ca74868fea46fa47d71bb27Michael Jurka                // i.e. fall through to the next case (don't break)
9471ff706b8c03063740ca74868fea46fa47d71bb27Michael Jurka                // (We sometimes miss ACTION_DOWN events in Workspace because it ignores all events
9481ff706b8c03063740ca74868fea46fa47d71bb27Michael Jurka                // while it's small- this was causing a crash before we checked for INVALID_POINTER)
949321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            }
950321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
951321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            case MotionEvent.ACTION_DOWN: {
952321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                final float x = ev.getX();
953321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                final float y = ev.getY();
954321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                // Remember location of down touch
955321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                mDownMotionX = x;
956321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                mLastMotionX = x;
957321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                mLastMotionY = y;
958c0844aa8a135bbf56aad2f95f57d1354a1260078Winson Chung                mLastMotionXRemainder = 0;
959aefd4e1f519838852d9e870031a85c9ea55a05f3Adam Cohen                mTotalMotionX = 0;
960321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                mActivePointerId = ev.getPointerId(0);
961321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                mAllowLongPress = true;
962321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
963321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                /*
964321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                 * If being flinged and user touches the screen, initiate drag;
965321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                 * otherwise don't.  mScroller.isFinished should be false when
966321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                 * being flinged.
967321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                 */
968fd177c1d10085e5e12ff7df27d956a378d1139b1Michael Jurka                final int xDist = Math.abs(mScroller.getFinalX() - mScroller.getCurrX());
9695f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung                final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop);
9705f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung                if (finishedScrolling) {
9715f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung                    mTouchState = TOUCH_STATE_REST;
9725f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung                    mScroller.abortAnimation();
9735f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung                } else {
9745f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung                    mTouchState = TOUCH_STATE_SCROLLING;
9755f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung                }
976321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
97786f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung                // check if this can be the beginning of a tap on the side of the pages
978321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                // to scroll the current page
979d0ce1ec2e21ce3bb0ba3549a01d1d06b440a8e45Patrick Dubroy                if (mTouchState != TOUCH_STATE_PREV_PAGE && mTouchState != TOUCH_STATE_NEXT_PAGE) {
980321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                    if (getChildCount() > 0) {
981d0ce1ec2e21ce3bb0ba3549a01d1d06b440a8e45Patrick Dubroy                        if (hitsPreviousPage(x, y)) {
982321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                            mTouchState = TOUCH_STATE_PREV_PAGE;
983d0ce1ec2e21ce3bb0ba3549a01d1d06b440a8e45Patrick Dubroy                        } else if (hitsNextPage(x, y)) {
984321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                            mTouchState = TOUCH_STATE_NEXT_PAGE;
985321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                        }
986321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                    }
987321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                }
988321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                break;
989321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            }
990321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
991321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            case MotionEvent.ACTION_UP:
9921d0867c8de6a889bccde9b7e768daef182a25e5cJeff Brown            case MotionEvent.ACTION_CANCEL:
993321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                mTouchState = TOUCH_STATE_REST;
994321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                mAllowLongPress = false;
995321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                mActivePointerId = INVALID_POINTER;
9966342bbae1a18f3c1862953e33309481703f541cfAdam Cohen                releaseVelocityTracker();
997321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                break;
998321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
999321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            case MotionEvent.ACTION_POINTER_UP:
1000321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                onSecondaryPointerUp(ev);
10016342bbae1a18f3c1862953e33309481703f541cfAdam Cohen                releaseVelocityTracker();
1002321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                break;
1003321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
1004321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1005321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        /*
1006321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung         * The only time we want to intercept motion events is if we are in the
1007321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung         * drag mode.
1008321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung         */
1009321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        return mTouchState != TOUCH_STATE_REST;
1010321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
1011321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1012f8d2823d885ba682140aee1ae0504c1c5e67a24bAdam Cohen    protected void determineScrollingStart(MotionEvent ev) {
1013f8d2823d885ba682140aee1ae0504c1c5e67a24bAdam Cohen        determineScrollingStart(ev, 1.0f);
1014f8d2823d885ba682140aee1ae0504c1c5e67a24bAdam Cohen    }
1015f8d2823d885ba682140aee1ae0504c1c5e67a24bAdam Cohen
1016321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    /*
1017321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung     * Determines if we should change the touch state to start scrolling after the
1018321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung     * user moves their touch point too far.
1019321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung     */
1020f8d2823d885ba682140aee1ae0504c1c5e67a24bAdam Cohen    protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) {
1021321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        /*
1022321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung         * Locally do absolute value. mLastMotionX is set to the y value
1023321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung         * of the down event.
1024321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung         */
1025321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        final int pointerIndex = ev.findPointerIndex(mActivePointerId);
10262698db4df3d2f9aa2a2c16af6760d63ba1ac354eMichael Jurka        if (pointerIndex == -1) {
10272698db4df3d2f9aa2a2c16af6760d63ba1ac354eMichael Jurka            return;
10282698db4df3d2f9aa2a2c16af6760d63ba1ac354eMichael Jurka        }
1029321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        final float x = ev.getX(pointerIndex);
1030321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        final float y = ev.getY(pointerIndex);
1031321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        final int xDiff = (int) Math.abs(x - mLastMotionX);
1032321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        final int yDiff = (int) Math.abs(y - mLastMotionY);
1033321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1034f8d2823d885ba682140aee1ae0504c1c5e67a24bAdam Cohen        final int touchSlop = Math.round(touchSlopScale * mTouchSlop);
1035321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        boolean xPaged = xDiff > mPagingTouchSlop;
1036321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        boolean xMoved = xDiff > touchSlop;
1037321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        boolean yMoved = yDiff > touchSlop;
1038321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1039f8d2823d885ba682140aee1ae0504c1c5e67a24bAdam Cohen        if (xMoved || xPaged || yMoved) {
10400142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka            if (mUsePagingTouchSlop ? xPaged : xMoved) {
1041321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                // Scroll if the user moved far enough along the X axis
1042321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                mTouchState = TOUCH_STATE_SCROLLING;
10436342bbae1a18f3c1862953e33309481703f541cfAdam Cohen                mTotalMotionX += Math.abs(mLastMotionX - x);
1044321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                mLastMotionX = x;
1045c0844aa8a135bbf56aad2f95f57d1354a1260078Winson Chung                mLastMotionXRemainder = 0;
10468b805b17158886035b38261eb611d8641701ae43Michael Jurka                mTouchX = getScrollX();
10470142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka                mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
10480142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka                pageBeginMoving();
1049321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            }
1050321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            // Either way, cancel any pending longpress
1051f8d2823d885ba682140aee1ae0504c1c5e67a24bAdam Cohen            cancelCurrentPageLongPress();
1052f8d2823d885ba682140aee1ae0504c1c5e67a24bAdam Cohen        }
1053f8d2823d885ba682140aee1ae0504c1c5e67a24bAdam Cohen    }
1054f8d2823d885ba682140aee1ae0504c1c5e67a24bAdam Cohen
1055f8d2823d885ba682140aee1ae0504c1c5e67a24bAdam Cohen    protected void cancelCurrentPageLongPress() {
1056f8d2823d885ba682140aee1ae0504c1c5e67a24bAdam Cohen        if (mAllowLongPress) {
1057f8d2823d885ba682140aee1ae0504c1c5e67a24bAdam Cohen            mAllowLongPress = false;
1058f8d2823d885ba682140aee1ae0504c1c5e67a24bAdam Cohen            // Try canceling the long press. It could also have been scheduled
1059f8d2823d885ba682140aee1ae0504c1c5e67a24bAdam Cohen            // by a distant descendant, so use the mAllowLongPress flag to block
1060f8d2823d885ba682140aee1ae0504c1c5e67a24bAdam Cohen            // everything
1061f8d2823d885ba682140aee1ae0504c1c5e67a24bAdam Cohen            final View currentPage = getPageAt(mCurrentPage);
1062f8d2823d885ba682140aee1ae0504c1c5e67a24bAdam Cohen            if (currentPage != null) {
1063f8d2823d885ba682140aee1ae0504c1c5e67a24bAdam Cohen                currentPage.cancelLongPress();
1064321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            }
1065321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
1066321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
1067321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1068b5ba097015c4794fa822f30b38a60a7070a00097Adam Cohen    protected float getScrollProgress(int screenCenter, View v, int page) {
1069b5ba097015c4794fa822f30b38a60a7070a00097Adam Cohen        final int halfScreenSize = getMeasuredWidth() / 2;
1070b5ba097015c4794fa822f30b38a60a7070a00097Adam Cohen
1071b5ba097015c4794fa822f30b38a60a7070a00097Adam Cohen        int totalDistance = getScaledMeasuredWidth(v) + mPageSpacing;
1072b5ba097015c4794fa822f30b38a60a7070a00097Adam Cohen        int delta = screenCenter - (getChildOffset(page) -
1073b5ba097015c4794fa822f30b38a60a7070a00097Adam Cohen                getRelativeChildOffset(page) + halfScreenSize);
1074b5ba097015c4794fa822f30b38a60a7070a00097Adam Cohen
1075b5ba097015c4794fa822f30b38a60a7070a00097Adam Cohen        float scrollProgress = delta / (totalDistance * 1.0f);
1076b5ba097015c4794fa822f30b38a60a7070a00097Adam Cohen        scrollProgress = Math.min(scrollProgress, 1.0f);
1077b5ba097015c4794fa822f30b38a60a7070a00097Adam Cohen        scrollProgress = Math.max(scrollProgress, -1.0f);
1078b5ba097015c4794fa822f30b38a60a7070a00097Adam Cohen        return scrollProgress;
1079b5ba097015c4794fa822f30b38a60a7070a00097Adam Cohen    }
1080b5ba097015c4794fa822f30b38a60a7070a00097Adam Cohen
1081e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen    // This curve determines how the effect of scrolling over the limits of the page dimishes
1082e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen    // as the user pulls further and further from the bounds
1083e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen    private float overScrollInfluenceCurve(float f) {
1084e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        f -= 1.0f;
1085e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        return f * f * f + 1.0f;
1086e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen    }
1087e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen
1088b5ba097015c4794fa822f30b38a60a7070a00097Adam Cohen    protected void acceleratedOverScroll(float amount) {
1089b5ba097015c4794fa822f30b38a60a7070a00097Adam Cohen        int screenSize = getMeasuredWidth();
1090b5ba097015c4794fa822f30b38a60a7070a00097Adam Cohen
1091b5ba097015c4794fa822f30b38a60a7070a00097Adam Cohen        // We want to reach the max over scroll effect when the user has
1092b5ba097015c4794fa822f30b38a60a7070a00097Adam Cohen        // over scrolled half the size of the screen
1093b5ba097015c4794fa822f30b38a60a7070a00097Adam Cohen        float f = OVERSCROLL_ACCELERATE_FACTOR * (amount / screenSize);
1094b5ba097015c4794fa822f30b38a60a7070a00097Adam Cohen
1095b5ba097015c4794fa822f30b38a60a7070a00097Adam Cohen        if (f == 0) return;
1096b5ba097015c4794fa822f30b38a60a7070a00097Adam Cohen
1097b5ba097015c4794fa822f30b38a60a7070a00097Adam Cohen        // Clamp this factor, f, to -1 < f < 1
1098b5ba097015c4794fa822f30b38a60a7070a00097Adam Cohen        if (Math.abs(f) >= 1) {
1099b5ba097015c4794fa822f30b38a60a7070a00097Adam Cohen            f /= Math.abs(f);
1100b5ba097015c4794fa822f30b38a60a7070a00097Adam Cohen        }
1101b5ba097015c4794fa822f30b38a60a7070a00097Adam Cohen
1102b5ba097015c4794fa822f30b38a60a7070a00097Adam Cohen        int overScrollAmount = (int) Math.round(f * screenSize);
1103b5ba097015c4794fa822f30b38a60a7070a00097Adam Cohen        if (amount < 0) {
1104ebea84d1c95f4c38ba8cee46cd586fd757b4fce2Adam Cohen            mOverScrollX = overScrollAmount;
11058b805b17158886035b38261eb611d8641701ae43Michael Jurka            super.scrollTo(0, getScrollY());
1106b5ba097015c4794fa822f30b38a60a7070a00097Adam Cohen        } else {
1107ebea84d1c95f4c38ba8cee46cd586fd757b4fce2Adam Cohen            mOverScrollX = mMaxScrollX + overScrollAmount;
11088b805b17158886035b38261eb611d8641701ae43Michael Jurka            super.scrollTo(mMaxScrollX, getScrollY());
1109b5ba097015c4794fa822f30b38a60a7070a00097Adam Cohen        }
1110b5ba097015c4794fa822f30b38a60a7070a00097Adam Cohen        invalidate();
1111b5ba097015c4794fa822f30b38a60a7070a00097Adam Cohen    }
1112b5ba097015c4794fa822f30b38a60a7070a00097Adam Cohen
1113b5ba097015c4794fa822f30b38a60a7070a00097Adam Cohen    protected void dampedOverScroll(float amount) {
1114e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        int screenSize = getMeasuredWidth();
1115e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen
1116e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        float f = (amount / screenSize);
1117e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen
1118e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        if (f == 0) return;
1119e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));
1120e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen
11217bfc979594b083c36f6a08e49273ef7c9ad7b13aAdam Cohen        // Clamp this factor, f, to -1 < f < 1
11227bfc979594b083c36f6a08e49273ef7c9ad7b13aAdam Cohen        if (Math.abs(f) >= 1) {
11237bfc979594b083c36f6a08e49273ef7c9ad7b13aAdam Cohen            f /= Math.abs(f);
11247bfc979594b083c36f6a08e49273ef7c9ad7b13aAdam Cohen        }
11257bfc979594b083c36f6a08e49273ef7c9ad7b13aAdam Cohen
1126e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        int overScrollAmount = (int) Math.round(OVERSCROLL_DAMP_FACTOR * f * screenSize);
112768d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen        if (amount < 0) {
1128ebea84d1c95f4c38ba8cee46cd586fd757b4fce2Adam Cohen            mOverScrollX = overScrollAmount;
11298b805b17158886035b38261eb611d8641701ae43Michael Jurka            super.scrollTo(0, getScrollY());
113068d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen        } else {
1131ebea84d1c95f4c38ba8cee46cd586fd757b4fce2Adam Cohen            mOverScrollX = mMaxScrollX + overScrollAmount;
11328b805b17158886035b38261eb611d8641701ae43Michael Jurka            super.scrollTo(mMaxScrollX, getScrollY());
113368d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen        }
113468d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen        invalidate();
113568d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen    }
113668d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen
1137b5ba097015c4794fa822f30b38a60a7070a00097Adam Cohen    protected void overScroll(float amount) {
1138b5ba097015c4794fa822f30b38a60a7070a00097Adam Cohen        dampedOverScroll(amount);
1139b5ba097015c4794fa822f30b38a60a7070a00097Adam Cohen    }
1140b5ba097015c4794fa822f30b38a60a7070a00097Adam Cohen
1141c5b262ccf639fedac2aff5bb8238342f95396338Michael Jurka    protected float maxOverScroll() {
1142c5b262ccf639fedac2aff5bb8238342f95396338Michael Jurka        // Using the formula in overScroll, assuming that f = 1.0 (which it should generally not
1143b5ba097015c4794fa822f30b38a60a7070a00097Adam Cohen        // exceed). Used to find out how much extra wallpaper we need for the over scroll effect
1144c5b262ccf639fedac2aff5bb8238342f95396338Michael Jurka        float f = 1.0f;
1145c5b262ccf639fedac2aff5bb8238342f95396338Michael Jurka        f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));
1146c5b262ccf639fedac2aff5bb8238342f95396338Michael Jurka        return OVERSCROLL_DAMP_FACTOR * f;
1147c5b262ccf639fedac2aff5bb8238342f95396338Michael Jurka    }
1148c5b262ccf639fedac2aff5bb8238342f95396338Michael Jurka
1149321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    @Override
1150321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    public boolean onTouchEvent(MotionEvent ev) {
115145e1d6ec0a213a444d01466c3d4f1ded5508ed63Winson Chung        // Skip touch handling if there are no pages to swipe
115245e1d6ec0a213a444d01466c3d4f1ded5508ed63Winson Chung        if (getChildCount() <= 0) return super.onTouchEvent(ev);
115345e1d6ec0a213a444d01466c3d4f1ded5508ed63Winson Chung
1154b8f0672d7c4b9836f90c1a7517a9e47fec4c7144Michael Jurka        acquireVelocityTrackerAndAddMovement(ev);
1155321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1156321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        final int action = ev.getAction();
1157321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1158321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        switch (action & MotionEvent.ACTION_MASK) {
1159321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        case MotionEvent.ACTION_DOWN:
1160321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            /*
1161321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung             * If being flinged and user touches, stop the fling. isFinished
1162321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung             * will be false if being flinged.
1163321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung             */
1164321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            if (!mScroller.isFinished()) {
1165321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                mScroller.abortAnimation();
1166321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            }
1167321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1168321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            // Remember where the motion event started
1169321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            mDownMotionX = mLastMotionX = ev.getX();
1170c0844aa8a135bbf56aad2f95f57d1354a1260078Winson Chung            mLastMotionXRemainder = 0;
1171aefd4e1f519838852d9e870031a85c9ea55a05f3Adam Cohen            mTotalMotionX = 0;
1172321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            mActivePointerId = ev.getPointerId(0);
11730142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka            if (mTouchState == TOUCH_STATE_SCROLLING) {
11740142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka                pageBeginMoving();
11750142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka            }
1176321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            break;
1177321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1178321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        case MotionEvent.ACTION_MOVE:
1179321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            if (mTouchState == TOUCH_STATE_SCROLLING) {
1180321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                // Scroll to follow the motion event
1181321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                final int pointerIndex = ev.findPointerIndex(mActivePointerId);
1182321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                final float x = ev.getX(pointerIndex);
1183c0844aa8a135bbf56aad2f95f57d1354a1260078Winson Chung                final float deltaX = mLastMotionX + mLastMotionXRemainder - x;
1184321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1185aefd4e1f519838852d9e870031a85c9ea55a05f3Adam Cohen                mTotalMotionX += Math.abs(deltaX);
1186aefd4e1f519838852d9e870031a85c9ea55a05f3Adam Cohen
1187c0844aa8a135bbf56aad2f95f57d1354a1260078Winson Chung                // Only scroll and update mLastMotionX if we have moved some discrete amount.  We
1188c0844aa8a135bbf56aad2f95f57d1354a1260078Winson Chung                // keep the remainder because we are actually testing if we've moved from the last
1189c0844aa8a135bbf56aad2f95f57d1354a1260078Winson Chung                // scrolled position (which is discrete).
1190c0844aa8a135bbf56aad2f95f57d1354a1260078Winson Chung                if (Math.abs(deltaX) >= 1.0f) {
119168d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen                    mTouchX += deltaX;
119268d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen                    mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
119368d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen                    if (!mDeferScrollUpdate) {
1194c0844aa8a135bbf56aad2f95f57d1354a1260078Winson Chung                        scrollBy((int) deltaX, 0);
1195785d2eb2b8d7072c8124300dd9168ff51a91cf38Winson Chung                        if (DEBUG) Log.d(TAG, "onTouchEvent().Scrolling: " + deltaX);
119668d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen                    } else {
119768d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen                        invalidate();
1198321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                    }
1199c0844aa8a135bbf56aad2f95f57d1354a1260078Winson Chung                    mLastMotionX = x;
1200c0844aa8a135bbf56aad2f95f57d1354a1260078Winson Chung                    mLastMotionXRemainder = deltaX - (int) deltaX;
1201321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                } else {
1202321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                    awakenScrollBars();
1203321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                }
1204564976a46ef02d665aa0e455ad7867746a0b5325Adam Cohen            } else {
1205321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                determineScrollingStart(ev);
1206321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            }
1207321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            break;
1208321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1209321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        case MotionEvent.ACTION_UP:
1210321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            if (mTouchState == TOUCH_STATE_SCROLLING) {
1211321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                final int activePointerId = mActivePointerId;
1212321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                final int pointerIndex = ev.findPointerIndex(activePointerId);
1213321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                final float x = ev.getX(pointerIndex);
1214321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                final VelocityTracker velocityTracker = mVelocityTracker;
1215321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
1216321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                int velocityX = (int) velocityTracker.getXVelocity(activePointerId);
12179cfd25f16739548111ba8fc6ba8cd83010eccef6Winson Chung                final int deltaX = (int) (x - mDownMotionX);
121800481b3ed322b4f36b669201d2287964febda65fAdam Cohen                final int pageWidth = getScaledMeasuredWidth(getPageAt(mCurrentPage));
121900481b3ed322b4f36b669201d2287964febda65fAdam Cohen                boolean isSignificantMove = Math.abs(deltaX) > pageWidth *
122000481b3ed322b4f36b669201d2287964febda65fAdam Cohen                        SIGNIFICANT_MOVE_THRESHOLD;
1221aefd4e1f519838852d9e870031a85c9ea55a05f3Adam Cohen
1222b64cb5a44bedcff0ea4b09cf8f1f5b6f95b0244eAdam Cohen                mTotalMotionX += Math.abs(mLastMotionX + mLastMotionXRemainder - x);
1223b64cb5a44bedcff0ea4b09cf8f1f5b6f95b0244eAdam Cohen
122400481b3ed322b4f36b669201d2287964febda65fAdam Cohen                boolean isFling = mTotalMotionX > MIN_LENGTH_FOR_FLING &&
1225265b9a66542148e0ff13971001cb9461065e1e0eAdam Cohen                        Math.abs(velocityX) > mFlingThresholdVelocity;
122600481b3ed322b4f36b669201d2287964febda65fAdam Cohen
1227aefd4e1f519838852d9e870031a85c9ea55a05f3Adam Cohen                // In the case that the page is moved far to one direction and then is flung
1228aefd4e1f519838852d9e870031a85c9ea55a05f3Adam Cohen                // in the opposite direction, we use a threshold to determine whether we should
1229aefd4e1f519838852d9e870031a85c9ea55a05f3Adam Cohen                // just return to the starting page, or if we should skip one further.
1230aefd4e1f519838852d9e870031a85c9ea55a05f3Adam Cohen                boolean returnToOriginalPage = false;
1231b64cb5a44bedcff0ea4b09cf8f1f5b6f95b0244eAdam Cohen                if (Math.abs(deltaX) > pageWidth * RETURN_TO_ORIGINAL_PAGE_THRESHOLD &&
123200481b3ed322b4f36b669201d2287964febda65fAdam Cohen                        Math.signum(velocityX) != Math.signum(deltaX) && isFling) {
1233aefd4e1f519838852d9e870031a85c9ea55a05f3Adam Cohen                    returnToOriginalPage = true;
1234aefd4e1f519838852d9e870031a85c9ea55a05f3Adam Cohen                }
1235aefd4e1f519838852d9e870031a85c9ea55a05f3Adam Cohen
1236aefd4e1f519838852d9e870031a85c9ea55a05f3Adam Cohen                int finalPage;
1237aefd4e1f519838852d9e870031a85c9ea55a05f3Adam Cohen                // We give flings precedence over large moves, which is why we short-circuit our
1238aefd4e1f519838852d9e870031a85c9ea55a05f3Adam Cohen                // test for a large move if a fling has been registered. That is, a large
1239aefd4e1f519838852d9e870031a85c9ea55a05f3Adam Cohen                // move to the left and fling to the right will register as a fling to the right.
1240aefd4e1f519838852d9e870031a85c9ea55a05f3Adam Cohen                if (((isSignificantMove && deltaX > 0 && !isFling) ||
1241aefd4e1f519838852d9e870031a85c9ea55a05f3Adam Cohen                        (isFling && velocityX > 0)) && mCurrentPage > 0) {
1242aefd4e1f519838852d9e870031a85c9ea55a05f3Adam Cohen                    finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage - 1;
1243aefd4e1f519838852d9e870031a85c9ea55a05f3Adam Cohen                    snapToPageWithVelocity(finalPage, velocityX);
1244aefd4e1f519838852d9e870031a85c9ea55a05f3Adam Cohen                } else if (((isSignificantMove && deltaX < 0 && !isFling) ||
1245aefd4e1f519838852d9e870031a85c9ea55a05f3Adam Cohen                        (isFling && velocityX < 0)) &&
124686f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung                        mCurrentPage < getChildCount() - 1) {
1247aefd4e1f519838852d9e870031a85c9ea55a05f3Adam Cohen                    finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage + 1;
1248aefd4e1f519838852d9e870031a85c9ea55a05f3Adam Cohen                    snapToPageWithVelocity(finalPage, velocityX);
1249321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                } else {
1250321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                    snapToDestination();
1251321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                }
1252d0ce1ec2e21ce3bb0ba3549a01d1d06b440a8e45Patrick Dubroy            } else if (mTouchState == TOUCH_STATE_PREV_PAGE) {
1253321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                // at this point we have not moved beyond the touch slop
1254321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
1255321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                // we can just page
125686f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung                int nextPage = Math.max(0, mCurrentPage - 1);
125786f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung                if (nextPage != mCurrentPage) {
125886f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung                    snapToPage(nextPage);
1259321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                } else {
1260321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                    snapToDestination();
1261321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                }
1262d0ce1ec2e21ce3bb0ba3549a01d1d06b440a8e45Patrick Dubroy            } else if (mTouchState == TOUCH_STATE_NEXT_PAGE) {
1263321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                // at this point we have not moved beyond the touch slop
1264321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
1265321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                // we can just page
126686f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung                int nextPage = Math.min(getChildCount() - 1, mCurrentPage + 1);
126786f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung                if (nextPage != mCurrentPage) {
126886f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung                    snapToPage(nextPage);
1269321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                } else {
1270321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                    snapToDestination();
1271321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                }
12721d0867c8de6a889bccde9b7e768daef182a25e5cJeff Brown            } else {
1273d771c96e5d156ffde5d35ee13ce053de60dc3163Michael Jurka                onUnhandledTap(ev);
1274321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            }
1275321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            mTouchState = TOUCH_STATE_REST;
1276321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            mActivePointerId = INVALID_POINTER;
1277b8f0672d7c4b9836f90c1a7517a9e47fec4c7144Michael Jurka            releaseVelocityTracker();
1278321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            break;
1279321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1280321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        case MotionEvent.ACTION_CANCEL:
1281b8f0672d7c4b9836f90c1a7517a9e47fec4c7144Michael Jurka            if (mTouchState == TOUCH_STATE_SCROLLING) {
1282b8f0672d7c4b9836f90c1a7517a9e47fec4c7144Michael Jurka                snapToDestination();
1283b8f0672d7c4b9836f90c1a7517a9e47fec4c7144Michael Jurka            }
1284321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            mTouchState = TOUCH_STATE_REST;
1285321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            mActivePointerId = INVALID_POINTER;
1286b8f0672d7c4b9836f90c1a7517a9e47fec4c7144Michael Jurka            releaseVelocityTracker();
1287321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            break;
1288321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1289321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        case MotionEvent.ACTION_POINTER_UP:
1290321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            onSecondaryPointerUp(ev);
1291321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            break;
1292321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
1293321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1294321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        return true;
1295321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
1296321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1297185d71647c8859cae7a375773b31c03f2f22ade1Winson Chung    @Override
1298185d71647c8859cae7a375773b31c03f2f22ade1Winson Chung    public boolean onGenericMotionEvent(MotionEvent event) {
1299185d71647c8859cae7a375773b31c03f2f22ade1Winson Chung        if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
1300185d71647c8859cae7a375773b31c03f2f22ade1Winson Chung            switch (event.getAction()) {
1301185d71647c8859cae7a375773b31c03f2f22ade1Winson Chung                case MotionEvent.ACTION_SCROLL: {
1302185d71647c8859cae7a375773b31c03f2f22ade1Winson Chung                    // Handle mouse (or ext. device) by shifting the page depending on the scroll
1303185d71647c8859cae7a375773b31c03f2f22ade1Winson Chung                    final float vscroll;
1304185d71647c8859cae7a375773b31c03f2f22ade1Winson Chung                    final float hscroll;
1305185d71647c8859cae7a375773b31c03f2f22ade1Winson Chung                    if ((event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0) {
1306185d71647c8859cae7a375773b31c03f2f22ade1Winson Chung                        vscroll = 0;
1307185d71647c8859cae7a375773b31c03f2f22ade1Winson Chung                        hscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
1308185d71647c8859cae7a375773b31c03f2f22ade1Winson Chung                    } else {
1309185d71647c8859cae7a375773b31c03f2f22ade1Winson Chung                        vscroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL);
1310185d71647c8859cae7a375773b31c03f2f22ade1Winson Chung                        hscroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL);
1311185d71647c8859cae7a375773b31c03f2f22ade1Winson Chung                    }
1312185d71647c8859cae7a375773b31c03f2f22ade1Winson Chung                    if (hscroll != 0 || vscroll != 0) {
1313185d71647c8859cae7a375773b31c03f2f22ade1Winson Chung                        if (hscroll > 0 || vscroll > 0) {
1314185d71647c8859cae7a375773b31c03f2f22ade1Winson Chung                            scrollRight();
1315185d71647c8859cae7a375773b31c03f2f22ade1Winson Chung                        } else {
1316185d71647c8859cae7a375773b31c03f2f22ade1Winson Chung                            scrollLeft();
1317185d71647c8859cae7a375773b31c03f2f22ade1Winson Chung                        }
1318185d71647c8859cae7a375773b31c03f2f22ade1Winson Chung                        return true;
1319185d71647c8859cae7a375773b31c03f2f22ade1Winson Chung                    }
1320185d71647c8859cae7a375773b31c03f2f22ade1Winson Chung                }
1321185d71647c8859cae7a375773b31c03f2f22ade1Winson Chung            }
1322185d71647c8859cae7a375773b31c03f2f22ade1Winson Chung        }
1323185d71647c8859cae7a375773b31c03f2f22ade1Winson Chung        return super.onGenericMotionEvent(event);
1324185d71647c8859cae7a375773b31c03f2f22ade1Winson Chung    }
1325185d71647c8859cae7a375773b31c03f2f22ade1Winson Chung
1326b8f0672d7c4b9836f90c1a7517a9e47fec4c7144Michael Jurka    private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) {
1327b8f0672d7c4b9836f90c1a7517a9e47fec4c7144Michael Jurka        if (mVelocityTracker == null) {
1328b8f0672d7c4b9836f90c1a7517a9e47fec4c7144Michael Jurka            mVelocityTracker = VelocityTracker.obtain();
1329b8f0672d7c4b9836f90c1a7517a9e47fec4c7144Michael Jurka        }
1330b8f0672d7c4b9836f90c1a7517a9e47fec4c7144Michael Jurka        mVelocityTracker.addMovement(ev);
1331b8f0672d7c4b9836f90c1a7517a9e47fec4c7144Michael Jurka    }
1332b8f0672d7c4b9836f90c1a7517a9e47fec4c7144Michael Jurka
1333b8f0672d7c4b9836f90c1a7517a9e47fec4c7144Michael Jurka    private void releaseVelocityTracker() {
1334b8f0672d7c4b9836f90c1a7517a9e47fec4c7144Michael Jurka        if (mVelocityTracker != null) {
1335b8f0672d7c4b9836f90c1a7517a9e47fec4c7144Michael Jurka            mVelocityTracker.recycle();
1336b8f0672d7c4b9836f90c1a7517a9e47fec4c7144Michael Jurka            mVelocityTracker = null;
1337b8f0672d7c4b9836f90c1a7517a9e47fec4c7144Michael Jurka        }
1338b8f0672d7c4b9836f90c1a7517a9e47fec4c7144Michael Jurka    }
1339b8f0672d7c4b9836f90c1a7517a9e47fec4c7144Michael Jurka
1340321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    private void onSecondaryPointerUp(MotionEvent ev) {
1341321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
1342321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                MotionEvent.ACTION_POINTER_INDEX_SHIFT;
1343321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        final int pointerId = ev.getPointerId(pointerIndex);
1344321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        if (pointerId == mActivePointerId) {
1345321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            // This was our active pointer going up. Choose a new
1346321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            // active pointer and adjust accordingly.
1347321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            // TODO: Make this decision more intelligent.
1348321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
1349321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            mLastMotionX = mDownMotionX = ev.getX(newPointerIndex);
1350321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            mLastMotionY = ev.getY(newPointerIndex);
1351c0844aa8a135bbf56aad2f95f57d1354a1260078Winson Chung            mLastMotionXRemainder = 0;
1352321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            mActivePointerId = ev.getPointerId(newPointerIndex);
1353321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            if (mVelocityTracker != null) {
1354321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                mVelocityTracker.clear();
1355321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            }
1356321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
13571d0867c8de6a889bccde9b7e768daef182a25e5cJeff Brown    }
13581d0867c8de6a889bccde9b7e768daef182a25e5cJeff Brown
1359d771c96e5d156ffde5d35ee13ce053de60dc3163Michael Jurka    protected void onUnhandledTap(MotionEvent ev) {}
1360321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1361321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    @Override
1362321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    public void requestChildFocus(View child, View focused) {
1363321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        super.requestChildFocus(child, focused);
1364ae4f155e090bf387b989fb738ee34ab045585711Adam Cohen        int page = indexToPage(indexOfChild(child));
136597d85d23b013347bead4e2f5fa430a79ce69431eWinson Chung        if (page >= 0 && page != getCurrentPage() && !isInTouchMode()) {
136686f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            snapToPage(page);
1367321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
1368321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
1369321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1370e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung    protected int getChildIndexForRelativeOffset(int relativeOffset) {
1371e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung        final int childCount = getChildCount();
13729c4949e12c909d5e01d24386147b1c528015b31bAdam Cohen        int left;
13739c4949e12c909d5e01d24386147b1c528015b31bAdam Cohen        int right;
1374e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung        for (int i = 0; i < childCount; ++i) {
13759c4949e12c909d5e01d24386147b1c528015b31bAdam Cohen            left = getRelativeChildOffset(i);
137622f823d340710bcde587b18a1542570cbe0360f5Adam Cohen            right = (left + getScaledMeasuredWidth(getPageAt(i)));
1377e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung            if (left <= relativeOffset && relativeOffset <= right) {
1378e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung                return i;
1379e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung            }
1380e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung        }
1381e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung        return -1;
1382e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung    }
1383e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung
13841908d07151e0d3f82899f368874e41ed1e49ba16Winson Chung    protected int getChildWidth(int index) {
138563257c110a4ee54d5e8872c471cce254cf613c7aWinson Chung        // This functions are called enough times that it actually makes a difference in the
138663257c110a4ee54d5e8872c471cce254cf613c7aWinson Chung        // profiler -- so just inline the max() here
138722f823d340710bcde587b18a1542570cbe0360f5Adam Cohen        final int measuredWidth = getPageAt(index).getMeasuredWidth();
138863257c110a4ee54d5e8872c471cce254cf613c7aWinson Chung        final int minWidth = mMinimumWidth;
138963257c110a4ee54d5e8872c471cce254cf613c7aWinson Chung        return (minWidth > measuredWidth) ? minWidth : measuredWidth;
13901908d07151e0d3f82899f368874e41ed1e49ba16Winson Chung    }
13911908d07151e0d3f82899f368874e41ed1e49ba16Winson Chung
1392d19d3ca3ec22aeec48b8e555e9764b98ff8cae5fAdam Cohen    int getPageNearestToCenterOfScreen() {
139322f823d340710bcde587b18a1542570cbe0360f5Adam Cohen        int minDistanceFromScreenCenter = Integer.MAX_VALUE;
1394321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        int minDistanceFromScreenCenterIndex = -1;
13958b805b17158886035b38261eb611d8641701ae43Michael Jurka        int screenCenter = getScrollX() + (getMeasuredWidth() / 2);
1396321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        final int childCount = getChildCount();
1397321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        for (int i = 0; i < childCount; ++i) {
139822f823d340710bcde587b18a1542570cbe0360f5Adam Cohen            View layout = (View) getPageAt(i);
1399d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka            int childWidth = getScaledMeasuredWidth(layout);
1400321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            int halfChildWidth = (childWidth / 2);
1401321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            int childCenter = getChildOffset(i) + halfChildWidth;
1402321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            int distanceFromScreenCenter = Math.abs(childCenter - screenCenter);
1403321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            if (distanceFromScreenCenter < minDistanceFromScreenCenter) {
1404321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                minDistanceFromScreenCenter = distanceFromScreenCenter;
1405321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                minDistanceFromScreenCenterIndex = i;
1406321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            }
1407321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
1408d19d3ca3ec22aeec48b8e555e9764b98ff8cae5fAdam Cohen        return minDistanceFromScreenCenterIndex;
1409d19d3ca3ec22aeec48b8e555e9764b98ff8cae5fAdam Cohen    }
1410d19d3ca3ec22aeec48b8e555e9764b98ff8cae5fAdam Cohen
1411d19d3ca3ec22aeec48b8e555e9764b98ff8cae5fAdam Cohen    protected void snapToDestination() {
1412d19d3ca3ec22aeec48b8e555e9764b98ff8cae5fAdam Cohen        snapToPage(getPageNearestToCenterOfScreen(), PAGE_SNAP_ANIMATION_DURATION);
1413321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
1414321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1415e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen    private static class ScrollInterpolator implements Interpolator {
1416e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        public ScrollInterpolator() {
1417e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        }
1418e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen
1419e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        public float getInterpolation(float t) {
1420e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen            t -= 1.0f;
1421e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen            return t*t*t*t*t + 1;
1422e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        }
1423e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen    }
1424e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen
1425e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen    // We want the duration of the page snap animation to be influenced by the distance that
1426e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen    // the screen has to travel, however, we don't want this duration to be effected in a
1427e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen    // purely linear fashion. Instead, we use this method to moderate the effect that the distance
1428e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen    // of travel has on the overall snap duration.
1429e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen    float distanceInfluenceForSnapDuration(float f) {
1430e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        f -= 0.5f; // center the values about 0.
1431e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        f *= 0.3f * Math.PI / 2.0f;
1432e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        return (float) Math.sin(f);
1433e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen    }
1434e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen
14350142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected void snapToPageWithVelocity(int whichPage, int velocity) {
1436e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        whichPage = Math.max(0, Math.min(whichPage, getChildCount() - 1));
1437e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        int halfScreenSize = getMeasuredWidth() / 2;
1438e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen
1439785d2eb2b8d7072c8124300dd9168ff51a91cf38Winson Chung        if (DEBUG) Log.d(TAG, "snapToPage.getChildOffset(): " + getChildOffset(whichPage));
1440785d2eb2b8d7072c8124300dd9168ff51a91cf38Winson Chung        if (DEBUG) Log.d(TAG, "snapToPageWithVelocity.getRelativeChildOffset(): "
1441785d2eb2b8d7072c8124300dd9168ff51a91cf38Winson Chung                + getMeasuredWidth() + ", " + getChildWidth(whichPage));
1442e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        final int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage);
1443e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        int delta = newX - mUnboundedScrollX;
1444e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        int duration = 0;
1445e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen
1446265b9a66542148e0ff13971001cb9461065e1e0eAdam Cohen        if (Math.abs(velocity) < mMinFlingVelocity) {
1447e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen            // If the velocity is low enough, then treat this more as an automatic page advance
1448e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen            // as opposed to an apparent physical response to flinging
1449e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen            snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION);
1450e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen            return;
1451e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        }
1452e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen
1453e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        // Here we compute a "distance" that will be used in the computation of the overall
1454e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        // snap duration. This is a function of the actual distance that needs to be traveled;
1455e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        // we keep this value close to half screen size in order to reduce the variance in snap
1456e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        // duration as a function of the distance the page needs to travel.
145720b7ca91b277e9668b6d4b4b3c9116b6778d22a5Michael Jurka        float distanceRatio = Math.min(1f, 1.0f * Math.abs(delta) / (2 * halfScreenSize));
1458e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        float distance = halfScreenSize + halfScreenSize *
1459e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen                distanceInfluenceForSnapDuration(distanceRatio);
1460e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen
1461e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        velocity = Math.abs(velocity);
1462265b9a66542148e0ff13971001cb9461065e1e0eAdam Cohen        velocity = Math.max(mMinSnapVelocity, velocity);
1463e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen
1464e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        // we want the page's snap velocity to approximately match the velocity at which the
1465e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        // user flings, so we scale the duration by a value near to the derivative of the scroll
146620b7ca91b277e9668b6d4b4b3c9116b6778d22a5Michael Jurka        // interpolator at zero, ie. 5. We use 4 to make it a little slower.
146720b7ca91b277e9668b6d4b4b3c9116b6778d22a5Michael Jurka        duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
1468e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen
1469e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        snapToPage(whichPage, delta, duration);
14700142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    }
14710142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka
14720142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected void snapToPage(int whichPage) {
14735f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung        snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION);
1474321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
1475321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
14760142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected void snapToPage(int whichPage, int duration) {
147786f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        whichPage = Math.max(0, Math.min(whichPage, getPageCount() - 1));
1478321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1479785d2eb2b8d7072c8124300dd9168ff51a91cf38Winson Chung        if (DEBUG) Log.d(TAG, "snapToPage.getChildOffset(): " + getChildOffset(whichPage));
1480785d2eb2b8d7072c8124300dd9168ff51a91cf38Winson Chung        if (DEBUG) Log.d(TAG, "snapToPage.getRelativeChildOffset(): " + getMeasuredWidth() + ", "
1481785d2eb2b8d7072c8124300dd9168ff51a91cf38Winson Chung                + getChildWidth(whichPage));
148286f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage);
148368d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen        final int sX = mUnboundedScrollX;
1484321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        final int delta = newX - sX;
14850142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka        snapToPage(whichPage, delta, duration);
14860142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    }
14870142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka
14880142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected void snapToPage(int whichPage, int delta, int duration) {
14890142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka        mNextPage = whichPage;
14900142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka
14910142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka        View focusedChild = getFocusedChild();
14920142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka        if (focusedChild != null && whichPage != mCurrentPage &&
149322f823d340710bcde587b18a1542570cbe0360f5Adam Cohen                focusedChild == getPageAt(mCurrentPage)) {
14940142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka            focusedChild.clearFocus();
14950142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka        }
14960142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka
14970142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka        pageBeginMoving();
1498321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        awakenScrollBars(duration);
1499321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        if (duration == 0) {
1500321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            duration = Math.abs(delta);
1501321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
1502321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1503321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        if (!mScroller.isFinished()) mScroller.abortAnimation();
150468d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen        mScroller.startScroll(mUnboundedScrollX, 0, delta, 0, duration);
150580baf5a6b3c62a62265f626d43d1167783c94131Winson Chung
1506b44b52439d155f570db7d6d0b80fdd3350e35685Winson Chung        // Load associated pages immediately if someone else is handling the scroll, otherwise defer
1507b44b52439d155f570db7d6d0b80fdd3350e35685Winson Chung        // loading associated pages until the scroll settles
1508b44b52439d155f570db7d6d0b80fdd3350e35685Winson Chung        if (mDeferScrollUpdate) {
1509b44b52439d155f570db7d6d0b80fdd3350e35685Winson Chung            loadAssociatedPages(mNextPage);
1510b44b52439d155f570db7d6d0b80fdd3350e35685Winson Chung        } else {
15114e076545e4ccdbd3c045a3fa33869a2b7519a0ccWinson Chung            mDeferLoadAssociatedPagesUntilScrollCompletes = true;
1512b44b52439d155f570db7d6d0b80fdd3350e35685Winson Chung        }
15130142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka        notifyPageSwitchListener();
1514321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        invalidate();
1515321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
1516321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1517321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    public void scrollLeft() {
1518321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        if (mScroller.isFinished()) {
151986f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            if (mCurrentPage > 0) snapToPage(mCurrentPage - 1);
1520321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        } else {
152186f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            if (mNextPage > 0) snapToPage(mNextPage - 1);
1522321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
1523321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
1524321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1525321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    public void scrollRight() {
1526321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        if (mScroller.isFinished()) {
152786f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            if (mCurrentPage < getChildCount() -1) snapToPage(mCurrentPage + 1);
1528321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        } else {
152986f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            if (mNextPage < getChildCount() -1) snapToPage(mNextPage + 1);
1530321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
1531321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
1532321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
153386f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung    public int getPageForView(View v) {
1534321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        int result = -1;
1535321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        if (v != null) {
1536321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            ViewParent vp = v.getParent();
1537321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            int count = getChildCount();
1538321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            for (int i = 0; i < count; i++) {
153922f823d340710bcde587b18a1542570cbe0360f5Adam Cohen                if (vp == getPageAt(i)) {
1540321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                    return i;
1541321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                }
1542321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            }
1543321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
1544321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        return result;
1545321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
1546321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1547321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    /**
1548321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung     * @return True is long presses are still allowed for the current touch
1549321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung     */
1550321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    public boolean allowLongPress() {
1551321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        return mAllowLongPress;
1552321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
1553321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
15540142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    /**
15550142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka     * Set true to allow long-press events to be triggered, usually checked by
15560142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka     * {@link Launcher} to accept or block dpad-initiated long-presses.
15570142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka     */
15580142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    public void setAllowLongPress(boolean allowLongPress) {
15590142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka        mAllowLongPress = allowLongPress;
15600142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    }
15610142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka
1562321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    public static class SavedState extends BaseSavedState {
156386f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        int currentPage = -1;
1564321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1565321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        SavedState(Parcelable superState) {
1566321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            super(superState);
1567321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
1568321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1569321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        private SavedState(Parcel in) {
1570321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            super(in);
157186f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            currentPage = in.readInt();
1572321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
1573321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1574321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        @Override
1575321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        public void writeToParcel(Parcel out, int flags) {
1576321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            super.writeToParcel(out, flags);
157786f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            out.writeInt(currentPage);
1578321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
1579321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1580321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        public static final Parcelable.Creator<SavedState> CREATOR =
1581321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                new Parcelable.Creator<SavedState>() {
1582321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            public SavedState createFromParcel(Parcel in) {
1583321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                return new SavedState(in);
1584321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            }
1585321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1586321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            public SavedState[] newArray(int size) {
1587321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                return new SavedState[size];
1588321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            }
1589321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        };
1590321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
1591321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1592f314b0eab03bb354c14b6c89f694a6685bd246d9Winson Chung    protected void loadAssociatedPages(int page) {
1593f314b0eab03bb354c14b6c89f694a6685bd246d9Winson Chung        loadAssociatedPages(page, false);
1594f314b0eab03bb354c14b6c89f694a6685bd246d9Winson Chung    }
1595f314b0eab03bb354c14b6c89f694a6685bd246d9Winson Chung    protected void loadAssociatedPages(int page, boolean immediateAndOnly) {
15960142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka        if (mContentIsRefreshable) {
15970142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka            final int count = getChildCount();
15980142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka            if (page < count) {
1599e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung                int lowerPageBound = getAssociatedLowerPageBound(page);
1600e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung                int upperPageBound = getAssociatedUpperPageBound(page);
1601785d2eb2b8d7072c8124300dd9168ff51a91cf38Winson Chung                if (DEBUG) Log.d(TAG, "loadAssociatedPages: " + lowerPageBound + "/"
1602785d2eb2b8d7072c8124300dd9168ff51a91cf38Winson Chung                        + upperPageBound);
16030cad1116add5303d7c06cc21182527cebc42aff6Michael Jurka                // First, clear any pages that should no longer be loaded
16040cad1116add5303d7c06cc21182527cebc42aff6Michael Jurka                for (int i = 0; i < count; ++i) {
16050cad1116add5303d7c06cc21182527cebc42aff6Michael Jurka                    Page layout = (Page) getPageAt(i);
16062a4b1a809c6bc0af9ae3ad974fe50e2a91569543Michael Jurka                    if ((i < lowerPageBound) || (i > upperPageBound)) {
16070cad1116add5303d7c06cc21182527cebc42aff6Michael Jurka                        if (layout.getPageChildCount() > 0) {
16080cad1116add5303d7c06cc21182527cebc42aff6Michael Jurka                            layout.removeAllViewsOnPage();
16090cad1116add5303d7c06cc21182527cebc42aff6Michael Jurka                        }
16100cad1116add5303d7c06cc21182527cebc42aff6Michael Jurka                        mDirtyPageContent.set(i, true);
16110cad1116add5303d7c06cc21182527cebc42aff6Michael Jurka                    }
16120cad1116add5303d7c06cc21182527cebc42aff6Michael Jurka                }
16130cad1116add5303d7c06cc21182527cebc42aff6Michael Jurka                // Next, load any new pages
16140142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka                for (int i = 0; i < count; ++i) {
1615f314b0eab03bb354c14b6c89f694a6685bd246d9Winson Chung                    if ((i != page) && immediateAndOnly) {
1616f314b0eab03bb354c14b6c89f694a6685bd246d9Winson Chung                        continue;
1617f314b0eab03bb354c14b6c89f694a6685bd246d9Winson Chung                    }
16180142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka                    if (lowerPageBound <= i && i <= upperPageBound) {
16190142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka                        if (mDirtyPageContent.get(i)) {
1620f314b0eab03bb354c14b6c89f694a6685bd246d9Winson Chung                            syncPageItems(i, (i == page) && immediateAndOnly);
16210142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka                            mDirtyPageContent.set(i, false);
16220142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka                        }
162380baf5a6b3c62a62265f626d43d1167783c94131Winson Chung                    }
162480baf5a6b3c62a62265f626d43d1167783c94131Winson Chung                }
162580baf5a6b3c62a62265f626d43d1167783c94131Winson Chung            }
162680baf5a6b3c62a62265f626d43d1167783c94131Winson Chung        }
162780baf5a6b3c62a62265f626d43d1167783c94131Winson Chung    }
162880baf5a6b3c62a62265f626d43d1167783c94131Winson Chung
1629e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung    protected int getAssociatedLowerPageBound(int page) {
1630e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung        return Math.max(0, page - 1);
1631e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung    }
1632e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung    protected int getAssociatedUpperPageBound(int page) {
1633e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung        final int count = getChildCount();
1634e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung        return Math.min(page + 1, count - 1);
1635e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung    }
1636e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung
163786f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung    /**
163886f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung     * This method is called ONLY to synchronize the number of pages that the paged view has.
163986f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung     * To actually fill the pages with information, implement syncPageItems() below.  It is
164086f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung     * guaranteed that syncPageItems() will be called for a particular page before it is shown,
164186f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung     * and therefore, individual page items do not need to be updated in this method.
164286f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung     */
1643321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    public abstract void syncPages();
164486f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung
164586f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung    /**
164686f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung     * This method is called to synchronize the items that are on a particular page.  If views on
164786f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung     * the page can be reused, then they should be updated within this method.
164886f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung     */
1649f314b0eab03bb354c14b6c89f694a6685bd246d9Winson Chung    public abstract void syncPageItems(int page, boolean immediate);
165086f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung
1651244d74cb353f1260c4d633e719bf84bb3b6e52bcPatrick Dubroy    protected void invalidatePageData() {
1652f314b0eab03bb354c14b6c89f694a6685bd246d9Winson Chung        invalidatePageData(-1, false);
16535a808358f1a773dab8babae44899ffd4ed1810c1Winson Chung    }
16545a808358f1a773dab8babae44899ffd4ed1810c1Winson Chung    protected void invalidatePageData(int currentPage) {
1655f314b0eab03bb354c14b6c89f694a6685bd246d9Winson Chung        invalidatePageData(currentPage, false);
1656f314b0eab03bb354c14b6c89f694a6685bd246d9Winson Chung    }
1657f314b0eab03bb354c14b6c89f694a6685bd246d9Winson Chung    protected void invalidatePageData(int currentPage, boolean immediateAndOnly) {
1658f0ea4d3378be7b962c8e0bce2392df5e82491fb8Winson Chung        if (!mIsDataReady) {
1659f0ea4d3378be7b962c8e0bce2392df5e82491fb8Winson Chung            return;
1660f0ea4d3378be7b962c8e0bce2392df5e82491fb8Winson Chung        }
1661f0ea4d3378be7b962c8e0bce2392df5e82491fb8Winson Chung
16620142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka        if (mContentIsRefreshable) {
16630cd3b64e607cc1f81ed49b00ef7398c2a2b7a48dAdam Cohen            // Force all scrolling-related behavior to end
16640cd3b64e607cc1f81ed49b00ef7398c2a2b7a48dAdam Cohen            mScroller.forceFinished(true);
16650cd3b64e607cc1f81ed49b00ef7398c2a2b7a48dAdam Cohen            mNextPage = INVALID_PAGE;
16660cd3b64e607cc1f81ed49b00ef7398c2a2b7a48dAdam Cohen
16670142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka            // Update all the pages
16680142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka            syncPages();
166986f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung
16705a808358f1a773dab8babae44899ffd4ed1810c1Winson Chung            // We must force a measure after we've loaded the pages to update the content width and
16715a808358f1a773dab8babae44899ffd4ed1810c1Winson Chung            // to determine the full scroll width
16725a808358f1a773dab8babae44899ffd4ed1810c1Winson Chung            measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
16735a808358f1a773dab8babae44899ffd4ed1810c1Winson Chung                    MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
16745a808358f1a773dab8babae44899ffd4ed1810c1Winson Chung
16755a808358f1a773dab8babae44899ffd4ed1810c1Winson Chung            // Set a new page as the current page if necessary
16765a808358f1a773dab8babae44899ffd4ed1810c1Winson Chung            if (currentPage > -1) {
16775afbf7bdd6f6879124c9b8283e005fe57f310d02Winson Chung                setCurrentPage(Math.min(getPageCount() - 1, currentPage));
16785a808358f1a773dab8babae44899ffd4ed1810c1Winson Chung            }
16795a808358f1a773dab8babae44899ffd4ed1810c1Winson Chung
16800142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka            // Mark each of the pages as dirty
16810142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka            final int count = getChildCount();
16820142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka            mDirtyPageContent.clear();
16830142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka            for (int i = 0; i < count; ++i) {
16840142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka                mDirtyPageContent.add(true);
16850142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka            }
168686f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung
16870142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka            // Load any pages that are necessary for the current window of views
1688f314b0eab03bb354c14b6c89f694a6685bd246d9Winson Chung            loadAssociatedPages(mCurrentPage, immediateAndOnly);
16890142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka            requestLayout();
16900142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka        }
1691321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
1692007c69867d821ea2b271398577a8b3440b3a7046Winson Chung
1693afaa05014e0bf3ed1192f9ddec2af4283bc50248Michael Jurka    protected View getScrollingIndicator() {
1694007c69867d821ea2b271398577a8b3440b3a7046Winson Chung        // We use mHasScrollIndicator to prevent future lookups if there is no sibling indicator
1695007c69867d821ea2b271398577a8b3440b3a7046Winson Chung        // found
1696007c69867d821ea2b271398577a8b3440b3a7046Winson Chung        if (mHasScrollIndicator && mScrollIndicator == null) {
1697007c69867d821ea2b271398577a8b3440b3a7046Winson Chung            ViewGroup parent = (ViewGroup) getParent();
1698a128a7b9e5f00559ad6a443f7be8e8d7591942a3Winson Chung            if (parent != null) {
1699a128a7b9e5f00559ad6a443f7be8e8d7591942a3Winson Chung                mScrollIndicator = (View) (parent.findViewById(R.id.paged_view_indicator));
1700a128a7b9e5f00559ad6a443f7be8e8d7591942a3Winson Chung                mHasScrollIndicator = mScrollIndicator != null;
1701a128a7b9e5f00559ad6a443f7be8e8d7591942a3Winson Chung                if (mHasScrollIndicator) {
1702a128a7b9e5f00559ad6a443f7be8e8d7591942a3Winson Chung                    mScrollIndicator.setVisibility(View.VISIBLE);
1703a128a7b9e5f00559ad6a443f7be8e8d7591942a3Winson Chung                }
1704007c69867d821ea2b271398577a8b3440b3a7046Winson Chung            }
1705007c69867d821ea2b271398577a8b3440b3a7046Winson Chung        }
1706007c69867d821ea2b271398577a8b3440b3a7046Winson Chung        return mScrollIndicator;
1707007c69867d821ea2b271398577a8b3440b3a7046Winson Chung    }
1708007c69867d821ea2b271398577a8b3440b3a7046Winson Chung
1709007c69867d821ea2b271398577a8b3440b3a7046Winson Chung    protected boolean isScrollingIndicatorEnabled() {
1710649723cfb3d73af16dd02462725700897ca60e38Winson Chung        return !LauncherApplication.isScreenLarge();
1711007c69867d821ea2b271398577a8b3440b3a7046Winson Chung    }
1712007c69867d821ea2b271398577a8b3440b3a7046Winson Chung
17135a808358f1a773dab8babae44899ffd4ed1810c1Winson Chung    Runnable hideScrollingIndicatorRunnable = new Runnable() {
17145a808358f1a773dab8babae44899ffd4ed1810c1Winson Chung        @Override
17155a808358f1a773dab8babae44899ffd4ed1810c1Winson Chung        public void run() {
17165a808358f1a773dab8babae44899ffd4ed1810c1Winson Chung            hideScrollingIndicator(false);
17175a808358f1a773dab8babae44899ffd4ed1810c1Winson Chung        }
17185a808358f1a773dab8babae44899ffd4ed1810c1Winson Chung    };
1719b737ee6ab2dc4938a1b2a574c336af0eab826712Michael Jurka    protected void flashScrollingIndicator(boolean animated) {
17205a808358f1a773dab8babae44899ffd4ed1810c1Winson Chung        removeCallbacks(hideScrollingIndicatorRunnable);
1721b737ee6ab2dc4938a1b2a574c336af0eab826712Michael Jurka        showScrollingIndicator(!animated);
17225a808358f1a773dab8babae44899ffd4ed1810c1Winson Chung        postDelayed(hideScrollingIndicatorRunnable, sScrollIndicatorFlashDuration);
17233ac74c55cf8baef29db80e8c67ab4ab033b04417Winson Chung    }
17243ac74c55cf8baef29db80e8c67ab4ab033b04417Winson Chung
1725430e8a5f5b06181addfd37223518fc21c5c41b69Michael Jurka    protected void showScrollingIndicator(boolean immediately) {
1726bed61d22bdbfcdbe04f92675c7751b83c7a8947cMichael Jurka        mShouldShowScrollIndicator = true;
1727bed61d22bdbfcdbe04f92675c7751b83c7a8947cMichael Jurka        mShouldShowScrollIndicatorImmediately = true;
1728007c69867d821ea2b271398577a8b3440b3a7046Winson Chung        if (getChildCount() <= 1) return;
1729007c69867d821ea2b271398577a8b3440b3a7046Winson Chung        if (!isScrollingIndicatorEnabled()) return;
1730007c69867d821ea2b271398577a8b3440b3a7046Winson Chung
1731bed61d22bdbfcdbe04f92675c7751b83c7a8947cMichael Jurka        mShouldShowScrollIndicator = false;
1732007c69867d821ea2b271398577a8b3440b3a7046Winson Chung        getScrollingIndicator();
1733007c69867d821ea2b271398577a8b3440b3a7046Winson Chung        if (mScrollIndicator != null) {
1734007c69867d821ea2b271398577a8b3440b3a7046Winson Chung            // Fade the indicator in
1735007c69867d821ea2b271398577a8b3440b3a7046Winson Chung            updateScrollingIndicatorPosition();
173632174c878c77b4e0f13449f4c26ff6b487bc7ca6Winson Chung            mScrollIndicator.setVisibility(View.VISIBLE);
173721b411074e39fd10cbc8dd0220bb159da4af2c76Adam Cohen            cancelScrollingIndicatorAnimations();
1738430e8a5f5b06181addfd37223518fc21c5c41b69Michael Jurka            if (immediately) {
1739430e8a5f5b06181addfd37223518fc21c5c41b69Michael Jurka                mScrollIndicator.setAlpha(1f);
1740430e8a5f5b06181addfd37223518fc21c5c41b69Michael Jurka            } else {
1741430e8a5f5b06181addfd37223518fc21c5c41b69Michael Jurka                mScrollIndicatorAnimator = ObjectAnimator.ofFloat(mScrollIndicator, "alpha", 1f);
1742430e8a5f5b06181addfd37223518fc21c5c41b69Michael Jurka                mScrollIndicatorAnimator.setDuration(sScrollIndicatorFadeInDuration);
1743430e8a5f5b06181addfd37223518fc21c5c41b69Michael Jurka                mScrollIndicatorAnimator.start();
1744430e8a5f5b06181addfd37223518fc21c5c41b69Michael Jurka            }
1745007c69867d821ea2b271398577a8b3440b3a7046Winson Chung        }
1746007c69867d821ea2b271398577a8b3440b3a7046Winson Chung    }
1747007c69867d821ea2b271398577a8b3440b3a7046Winson Chung
174821b411074e39fd10cbc8dd0220bb159da4af2c76Adam Cohen    protected void cancelScrollingIndicatorAnimations() {
174921b411074e39fd10cbc8dd0220bb159da4af2c76Adam Cohen        if (mScrollIndicatorAnimator != null) {
175021b411074e39fd10cbc8dd0220bb159da4af2c76Adam Cohen            mScrollIndicatorAnimator.cancel();
175121b411074e39fd10cbc8dd0220bb159da4af2c76Adam Cohen        }
175221b411074e39fd10cbc8dd0220bb159da4af2c76Adam Cohen    }
175321b411074e39fd10cbc8dd0220bb159da4af2c76Adam Cohen
1754007c69867d821ea2b271398577a8b3440b3a7046Winson Chung    protected void hideScrollingIndicator(boolean immediately) {
1755007c69867d821ea2b271398577a8b3440b3a7046Winson Chung        if (getChildCount() <= 1) return;
1756007c69867d821ea2b271398577a8b3440b3a7046Winson Chung        if (!isScrollingIndicatorEnabled()) return;
1757007c69867d821ea2b271398577a8b3440b3a7046Winson Chung
1758007c69867d821ea2b271398577a8b3440b3a7046Winson Chung        getScrollingIndicator();
1759007c69867d821ea2b271398577a8b3440b3a7046Winson Chung        if (mScrollIndicator != null) {
1760007c69867d821ea2b271398577a8b3440b3a7046Winson Chung            // Fade the indicator out
1761007c69867d821ea2b271398577a8b3440b3a7046Winson Chung            updateScrollingIndicatorPosition();
176221b411074e39fd10cbc8dd0220bb159da4af2c76Adam Cohen            cancelScrollingIndicatorAnimations();
176332174c878c77b4e0f13449f4c26ff6b487bc7ca6Winson Chung            if (immediately) {
176481efbad05e0ce69b06d7323749ef2e200b84b98eMichael Jurka                mScrollIndicator.setVisibility(View.INVISIBLE);
176532174c878c77b4e0f13449f4c26ff6b487bc7ca6Winson Chung                mScrollIndicator.setAlpha(0f);
176632174c878c77b4e0f13449f4c26ff6b487bc7ca6Winson Chung            } else {
176732174c878c77b4e0f13449f4c26ff6b487bc7ca6Winson Chung                mScrollIndicatorAnimator = ObjectAnimator.ofFloat(mScrollIndicator, "alpha", 0f);
176832174c878c77b4e0f13449f4c26ff6b487bc7ca6Winson Chung                mScrollIndicatorAnimator.setDuration(sScrollIndicatorFadeOutDuration);
176932174c878c77b4e0f13449f4c26ff6b487bc7ca6Winson Chung                mScrollIndicatorAnimator.addListener(new AnimatorListenerAdapter() {
177032174c878c77b4e0f13449f4c26ff6b487bc7ca6Winson Chung                    private boolean cancelled = false;
177132174c878c77b4e0f13449f4c26ff6b487bc7ca6Winson Chung                    @Override
177232174c878c77b4e0f13449f4c26ff6b487bc7ca6Winson Chung                    public void onAnimationCancel(android.animation.Animator animation) {
177332174c878c77b4e0f13449f4c26ff6b487bc7ca6Winson Chung                        cancelled = true;
177432174c878c77b4e0f13449f4c26ff6b487bc7ca6Winson Chung                    }
177532174c878c77b4e0f13449f4c26ff6b487bc7ca6Winson Chung                    @Override
177632174c878c77b4e0f13449f4c26ff6b487bc7ca6Winson Chung                    public void onAnimationEnd(Animator animation) {
177732174c878c77b4e0f13449f4c26ff6b487bc7ca6Winson Chung                        if (!cancelled) {
177881efbad05e0ce69b06d7323749ef2e200b84b98eMichael Jurka                            mScrollIndicator.setVisibility(View.INVISIBLE);
177932174c878c77b4e0f13449f4c26ff6b487bc7ca6Winson Chung                        }
178032174c878c77b4e0f13449f4c26ff6b487bc7ca6Winson Chung                    }
178132174c878c77b4e0f13449f4c26ff6b487bc7ca6Winson Chung                });
178232174c878c77b4e0f13449f4c26ff6b487bc7ca6Winson Chung                mScrollIndicatorAnimator.start();
178332174c878c77b4e0f13449f4c26ff6b487bc7ca6Winson Chung            }
1784007c69867d821ea2b271398577a8b3440b3a7046Winson Chung        }
1785007c69867d821ea2b271398577a8b3440b3a7046Winson Chung    }
1786007c69867d821ea2b271398577a8b3440b3a7046Winson Chung
178732174c878c77b4e0f13449f4c26ff6b487bc7ca6Winson Chung    /**
178832174c878c77b4e0f13449f4c26ff6b487bc7ca6Winson Chung     * To be overridden by subclasses to determine whether the scroll indicator should stretch to
178932174c878c77b4e0f13449f4c26ff6b487bc7ca6Winson Chung     * fill its space on the track or not.
179032174c878c77b4e0f13449f4c26ff6b487bc7ca6Winson Chung     */
179132174c878c77b4e0f13449f4c26ff6b487bc7ca6Winson Chung    protected boolean hasElasticScrollIndicator() {
1792dea74b7d12b0fcd50bfdb4274f9867ba76d75238Winson Chung        return true;
179332174c878c77b4e0f13449f4c26ff6b487bc7ca6Winson Chung    }
179432174c878c77b4e0f13449f4c26ff6b487bc7ca6Winson Chung
1795007c69867d821ea2b271398577a8b3440b3a7046Winson Chung    private void updateScrollingIndicator() {
1796007c69867d821ea2b271398577a8b3440b3a7046Winson Chung        if (getChildCount() <= 1) return;
1797007c69867d821ea2b271398577a8b3440b3a7046Winson Chung        if (!isScrollingIndicatorEnabled()) return;
1798007c69867d821ea2b271398577a8b3440b3a7046Winson Chung
1799007c69867d821ea2b271398577a8b3440b3a7046Winson Chung        getScrollingIndicator();
1800007c69867d821ea2b271398577a8b3440b3a7046Winson Chung        if (mScrollIndicator != null) {
1801007c69867d821ea2b271398577a8b3440b3a7046Winson Chung            updateScrollingIndicatorPosition();
1802007c69867d821ea2b271398577a8b3440b3a7046Winson Chung        }
1803bed61d22bdbfcdbe04f92675c7751b83c7a8947cMichael Jurka        if (mShouldShowScrollIndicator) {
1804bed61d22bdbfcdbe04f92675c7751b83c7a8947cMichael Jurka            showScrollingIndicator(mShouldShowScrollIndicatorImmediately);
1805bed61d22bdbfcdbe04f92675c7751b83c7a8947cMichael Jurka        }
1806007c69867d821ea2b271398577a8b3440b3a7046Winson Chung    }
1807007c69867d821ea2b271398577a8b3440b3a7046Winson Chung
1808007c69867d821ea2b271398577a8b3440b3a7046Winson Chung    private void updateScrollingIndicatorPosition() {
1809649723cfb3d73af16dd02462725700897ca60e38Winson Chung        if (!isScrollingIndicatorEnabled()) return;
1810430e8a5f5b06181addfd37223518fc21c5c41b69Michael Jurka        if (mScrollIndicator == null) return;
181132174c878c77b4e0f13449f4c26ff6b487bc7ca6Winson Chung        int numPages = getChildCount();
181232174c878c77b4e0f13449f4c26ff6b487bc7ca6Winson Chung        int pageWidth = getMeasuredWidth();
1813ae890b85dfbcdef592f4a97cca819922a07a83caWinson Chung        int lastChildIndex = Math.max(0, getChildCount() - 1);
1814ae890b85dfbcdef592f4a97cca819922a07a83caWinson Chung        int maxScrollX = getChildOffset(lastChildIndex) - getRelativeChildOffset(lastChildIndex);
1815f5f8cefb215fad98ae6d4487852e8b948aba3619Winson Chung        int trackWidth = pageWidth - mScrollIndicatorPaddingLeft - mScrollIndicatorPaddingRight;
181632174c878c77b4e0f13449f4c26ff6b487bc7ca6Winson Chung        int indicatorWidth = mScrollIndicator.getMeasuredWidth() -
181732174c878c77b4e0f13449f4c26ff6b487bc7ca6Winson Chung                mScrollIndicator.getPaddingLeft() - mScrollIndicator.getPaddingRight();
181832174c878c77b4e0f13449f4c26ff6b487bc7ca6Winson Chung
1819ae890b85dfbcdef592f4a97cca819922a07a83caWinson Chung        float offset = Math.max(0f, Math.min(1f, (float) getScrollX() / maxScrollX));
182032174c878c77b4e0f13449f4c26ff6b487bc7ca6Winson Chung        int indicatorSpace = trackWidth / numPages;
1821ae890b85dfbcdef592f4a97cca819922a07a83caWinson Chung        int indicatorPos = (int) (offset * (trackWidth - indicatorSpace)) + mScrollIndicatorPaddingLeft;
182232174c878c77b4e0f13449f4c26ff6b487bc7ca6Winson Chung        if (hasElasticScrollIndicator()) {
182332174c878c77b4e0f13449f4c26ff6b487bc7ca6Winson Chung            if (mScrollIndicator.getMeasuredWidth() != indicatorSpace) {
182432174c878c77b4e0f13449f4c26ff6b487bc7ca6Winson Chung                mScrollIndicator.getLayoutParams().width = indicatorSpace;
182532174c878c77b4e0f13449f4c26ff6b487bc7ca6Winson Chung                mScrollIndicator.requestLayout();
182632174c878c77b4e0f13449f4c26ff6b487bc7ca6Winson Chung            }
182732174c878c77b4e0f13449f4c26ff6b487bc7ca6Winson Chung        } else {
182832174c878c77b4e0f13449f4c26ff6b487bc7ca6Winson Chung            int indicatorCenterOffset = indicatorSpace / 2 - indicatorWidth / 2;
182932174c878c77b4e0f13449f4c26ff6b487bc7ca6Winson Chung            indicatorPos += indicatorCenterOffset;
183032174c878c77b4e0f13449f4c26ff6b487bc7ca6Winson Chung        }
1831007c69867d821ea2b271398577a8b3440b3a7046Winson Chung        mScrollIndicator.setTranslationX(indicatorPos);
18323ac74c55cf8baef29db80e8c67ab4ab033b04417Winson Chung    }
18333ac74c55cf8baef29db80e8c67ab4ab033b04417Winson Chung
18343ac74c55cf8baef29db80e8c67ab4ab033b04417Winson Chung    public void showScrollIndicatorTrack() {
18353ac74c55cf8baef29db80e8c67ab4ab033b04417Winson Chung    }
18363ac74c55cf8baef29db80e8c67ab4ab033b04417Winson Chung
18373ac74c55cf8baef29db80e8c67ab4ab033b04417Winson Chung    public void hideScrollIndicatorTrack() {
1838007c69867d821ea2b271398577a8b3440b3a7046Winson Chung    }
18396a0f57dfafced837a2a282d8feec28d5418be3b9Winson Chung
18406a0f57dfafced837a2a282d8feec28d5418be3b9Winson Chung    /* Accessibility */
18416a0f57dfafced837a2a282d8feec28d5418be3b9Winson Chung    @Override
18426a0f57dfafced837a2a282d8feec28d5418be3b9Winson Chung    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
18436a0f57dfafced837a2a282d8feec28d5418be3b9Winson Chung        super.onInitializeAccessibilityNodeInfo(info);
18446a0f57dfafced837a2a282d8feec28d5418be3b9Winson Chung        info.setScrollable(true);
18456a0f57dfafced837a2a282d8feec28d5418be3b9Winson Chung    }
18466a0f57dfafced837a2a282d8feec28d5418be3b9Winson Chung
18476a0f57dfafced837a2a282d8feec28d5418be3b9Winson Chung    @Override
18486a0f57dfafced837a2a282d8feec28d5418be3b9Winson Chung    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
18496a0f57dfafced837a2a282d8feec28d5418be3b9Winson Chung        super.onInitializeAccessibilityEvent(event);
18506a0f57dfafced837a2a282d8feec28d5418be3b9Winson Chung        event.setScrollable(true);
18516a0f57dfafced837a2a282d8feec28d5418be3b9Winson Chung        if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
18526a0f57dfafced837a2a282d8feec28d5418be3b9Winson Chung            event.setFromIndex(mCurrentPage);
18536a0f57dfafced837a2a282d8feec28d5418be3b9Winson Chung            event.setToIndex(mCurrentPage);
18546a0f57dfafced837a2a282d8feec28d5418be3b9Winson Chung            event.setItemCount(getChildCount());
18556a0f57dfafced837a2a282d8feec28d5418be3b9Winson Chung        }
18566a0f57dfafced837a2a282d8feec28d5418be3b9Winson Chung    }
18576a0f57dfafced837a2a282d8feec28d5418be3b9Winson Chung
18586a0f57dfafced837a2a282d8feec28d5418be3b9Winson Chung    protected String getCurrentPageDescription() {
18598b805b17158886035b38261eb611d8641701ae43Michael Jurka        return String.format(getContext().getString(R.string.default_scroll_format),
1860360e63fd3e77247002b86da2a77bd8dfe8c8a807Winson Chung                 getNextPage() + 1, getChildCount());
18616a0f57dfafced837a2a282d8feec28d5418be3b9Winson Chung    }
1862d11265e3738d93df3d8f76732e027f4d9be2a0abWinson Chung
1863d11265e3738d93df3d8f76732e027f4d9be2a0abWinson Chung    @Override
1864d11265e3738d93df3d8f76732e027f4d9be2a0abWinson Chung    public boolean onHoverEvent(android.view.MotionEvent event) {
1865d11265e3738d93df3d8f76732e027f4d9be2a0abWinson Chung        return true;
1866d11265e3738d93df3d8f76732e027f4d9be2a0abWinson Chung    }
1867321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung}
1868