blob: 4e47acb40d4071660bd0cff4f45d274e3be3b2b1 [file] [log] [blame]
Winson Chung321e9ee2010-08-09 13:37:56 -07001/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.launcher2;
18
Winson Chunge3193b92010-09-10 11:44:42 -070019import java.util.ArrayList;
20import java.util.HashMap;
Patrick Dubroy9f7aec82010-09-06 11:03:37 -070021
Winson Chung321e9ee2010-08-09 13:37:56 -070022import android.content.Context;
Winson Chung241c3b42010-08-25 16:53:03 -070023import android.graphics.Bitmap;
Winson Chung321e9ee2010-08-09 13:37:56 -070024import android.graphics.Canvas;
25import android.graphics.Rect;
Winson Chung321e9ee2010-08-09 13:37:56 -070026import android.os.Parcel;
27import android.os.Parcelable;
28import android.util.AttributeSet;
Winson Chunge3193b92010-09-10 11:44:42 -070029import android.util.Log;
Winson Chung5f2aa4e2010-08-20 14:49:25 -070030import android.view.ActionMode;
Winson Chung321e9ee2010-08-09 13:37:56 -070031import android.view.MotionEvent;
32import android.view.VelocityTracker;
33import android.view.View;
34import android.view.ViewConfiguration;
35import android.view.ViewGroup;
36import android.view.ViewParent;
Winson Chung80baf5a2010-08-09 16:03:15 -070037import android.view.animation.Animation;
Michael Jurka5f1c5092010-09-03 14:15:02 -070038import android.view.animation.Animation.AnimationListener;
Winson Chunge3193b92010-09-10 11:44:42 -070039import android.view.animation.AnimationUtils;
Winson Chung5f2aa4e2010-08-20 14:49:25 -070040import android.widget.Checkable;
Winson Chunge3193b92010-09-10 11:44:42 -070041import android.widget.LinearLayout;
Winson Chung321e9ee2010-08-09 13:37:56 -070042import android.widget.Scroller;
43
Winson Chunge3193b92010-09-10 11:44:42 -070044import com.android.launcher.R;
Winson Chung80baf5a2010-08-09 16:03:15 -070045
Winson Chung321e9ee2010-08-09 13:37:56 -070046/**
47 * An abstraction of the original Workspace which supports browsing through a
Michael Jurka0142d492010-08-25 17:46:15 -070048 * sequential list of "pages"
Winson Chung321e9ee2010-08-09 13:37:56 -070049 */
50public abstract class PagedView extends ViewGroup {
51 private static final String TAG = "PagedView";
Michael Jurka0142d492010-08-25 17:46:15 -070052 protected static final int INVALID_PAGE = -1;
Winson Chung321e9ee2010-08-09 13:37:56 -070053
Winson Chung86f77532010-08-24 11:08:22 -070054 // the min drag distance for a fling to register, to prevent random page shifts
Winson Chung321e9ee2010-08-09 13:37:56 -070055 private static final int MIN_LENGTH_FOR_FLING = 50;
56
Winson Chung5f2aa4e2010-08-20 14:49:25 -070057 private static final int PAGE_SNAP_ANIMATION_DURATION = 1000;
Michael Jurka0142d492010-08-25 17:46:15 -070058 protected static final float NANOTIME_DIV = 1000000000.0f;
Winson Chung321e9ee2010-08-09 13:37:56 -070059
Michael Jurka0142d492010-08-25 17:46:15 -070060 // the velocity at which a fling gesture will cause us to snap to the next page
61 protected int mSnapVelocity = 500;
62
63 protected float mSmoothingTime;
64 protected float mTouchX;
65
66 protected boolean mFirstLayout = true;
67
68 protected int mCurrentPage;
69 protected int mNextPage = INVALID_PAGE;
70 protected Scroller mScroller;
Winson Chung321e9ee2010-08-09 13:37:56 -070071 private VelocityTracker mVelocityTracker;
72
73 private float mDownMotionX;
74 private float mLastMotionX;
75 private float mLastMotionY;
76
Michael Jurka0142d492010-08-25 17:46:15 -070077 protected final static int TOUCH_STATE_REST = 0;
78 protected final static int TOUCH_STATE_SCROLLING = 1;
79 protected final static int TOUCH_STATE_PREV_PAGE = 2;
80 protected final static int TOUCH_STATE_NEXT_PAGE = 3;
Adam Cohen9415d872010-09-13 14:49:43 -070081 protected final static float ALPHA_QUANTIZE_LEVEL = 0.01f;
Winson Chung321e9ee2010-08-09 13:37:56 -070082
Michael Jurka0142d492010-08-25 17:46:15 -070083 protected int mTouchState = TOUCH_STATE_REST;
Winson Chung321e9ee2010-08-09 13:37:56 -070084
Michael Jurka0142d492010-08-25 17:46:15 -070085 protected OnLongClickListener mLongClickListener;
Winson Chung321e9ee2010-08-09 13:37:56 -070086
87 private boolean mAllowLongPress = true;
88
89 private int mTouchSlop;
90 private int mPagingTouchSlop;
91 private int mMaximumVelocity;
92
Michael Jurka5f1c5092010-09-03 14:15:02 -070093 protected static final int INVALID_POINTER = -1;
Winson Chung321e9ee2010-08-09 13:37:56 -070094
Michael Jurka5f1c5092010-09-03 14:15:02 -070095 protected int mActivePointerId = INVALID_POINTER;
Winson Chung321e9ee2010-08-09 13:37:56 -070096
Winson Chung86f77532010-08-24 11:08:22 -070097 private PageSwitchListener mPageSwitchListener;
Winson Chung321e9ee2010-08-09 13:37:56 -070098
Winson Chung86f77532010-08-24 11:08:22 -070099 private ArrayList<Boolean> mDirtyPageContent;
100 private boolean mDirtyPageAlpha;
Winson Chung321e9ee2010-08-09 13:37:56 -0700101
Winson Chung5f2aa4e2010-08-20 14:49:25 -0700102 // choice modes
103 protected static final int CHOICE_MODE_NONE = 0;
104 protected static final int CHOICE_MODE_SINGLE = 1;
105 // Multiple selection mode is not supported by all Launcher actions atm
106 protected static final int CHOICE_MODE_MULTIPLE = 2;
Patrick Dubroy9f7aec82010-09-06 11:03:37 -0700107
Michael Jurkae17e19c2010-09-28 11:01:39 -0700108 protected int mChoiceMode;
Winson Chung5f2aa4e2010-08-20 14:49:25 -0700109 private ActionMode mActionMode;
110
Winson Chung241c3b42010-08-25 16:53:03 -0700111 protected PagedViewIconCache mPageViewIconCache;
112
Michael Jurka0142d492010-08-25 17:46:15 -0700113 // If true, syncPages and syncPageItems will be called to refresh pages
114 protected boolean mContentIsRefreshable = true;
115
116 // If true, modify alpha of neighboring pages as user scrolls left/right
117 protected boolean mFadeInAdjacentScreens = true;
118
119 // It true, use a different slop parameter (pagingTouchSlop = 2 * touchSlop) for deciding
120 // to switch to a new page
121 protected boolean mUsePagingTouchSlop = true;
122
123 // If true, the subclass should directly update mScrollX itself in its computeScroll method
124 // (SmoothPagedView does this)
125 protected boolean mDeferScrollUpdate = false;
126
Winson Chung241c3b42010-08-25 16:53:03 -0700127 /**
128 * Simple cache mechanism for PagedViewIcon outlines.
129 */
130 class PagedViewIconCache {
131 private final HashMap<Object, Bitmap> iconOutlineCache = new HashMap<Object, Bitmap>();
132
133 public void clear() {
134 iconOutlineCache.clear();
135 }
136 public void addOutline(Object key, Bitmap b) {
137 iconOutlineCache.put(key, b);
138 }
139 public void removeOutline(Object key) {
140 if (iconOutlineCache.containsKey(key)) {
141 iconOutlineCache.remove(key);
142 }
143 }
144 public Bitmap getOutline(Object key) {
145 return iconOutlineCache.get(key);
146 }
147 }
148
Winson Chung86f77532010-08-24 11:08:22 -0700149 public interface PageSwitchListener {
150 void onPageSwitch(View newPage, int newPageIndex);
Winson Chung321e9ee2010-08-09 13:37:56 -0700151 }
152
Michael Jurka0142d492010-08-25 17:46:15 -0700153 public interface PageMovingListener {
154 void onPageBeginMoving();
155 void onPageEndMoving();
156 }
157
Winson Chung321e9ee2010-08-09 13:37:56 -0700158 public PagedView(Context context) {
159 this(context, null);
160 }
161
162 public PagedView(Context context, AttributeSet attrs) {
163 this(context, attrs, 0);
164 }
165
166 public PagedView(Context context, AttributeSet attrs, int defStyle) {
167 super(context, attrs, defStyle);
Winson Chung5f2aa4e2010-08-20 14:49:25 -0700168 mChoiceMode = CHOICE_MODE_NONE;
Winson Chung321e9ee2010-08-09 13:37:56 -0700169
170 setHapticFeedbackEnabled(false);
Michael Jurka0142d492010-08-25 17:46:15 -0700171 init();
Winson Chung321e9ee2010-08-09 13:37:56 -0700172 }
173
174 /**
175 * Initializes various states for this workspace.
176 */
Michael Jurka0142d492010-08-25 17:46:15 -0700177 protected void init() {
Winson Chung86f77532010-08-24 11:08:22 -0700178 mDirtyPageContent = new ArrayList<Boolean>();
179 mDirtyPageContent.ensureCapacity(32);
Winson Chung241c3b42010-08-25 16:53:03 -0700180 mPageViewIconCache = new PagedViewIconCache();
Winson Chung321e9ee2010-08-09 13:37:56 -0700181 mScroller = new Scroller(getContext());
Winson Chung86f77532010-08-24 11:08:22 -0700182 mCurrentPage = 0;
Winson Chung321e9ee2010-08-09 13:37:56 -0700183
184 final ViewConfiguration configuration = ViewConfiguration.get(getContext());
185 mTouchSlop = configuration.getScaledTouchSlop();
186 mPagingTouchSlop = configuration.getScaledPagingTouchSlop();
187 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
188 }
189
Winson Chung86f77532010-08-24 11:08:22 -0700190 public void setPageSwitchListener(PageSwitchListener pageSwitchListener) {
191 mPageSwitchListener = pageSwitchListener;
192 if (mPageSwitchListener != null) {
193 mPageSwitchListener.onPageSwitch(getPageAt(mCurrentPage), mCurrentPage);
Winson Chung321e9ee2010-08-09 13:37:56 -0700194 }
195 }
196
197 /**
Winson Chung86f77532010-08-24 11:08:22 -0700198 * Returns the index of the currently displayed page.
Winson Chung321e9ee2010-08-09 13:37:56 -0700199 *
Winson Chung86f77532010-08-24 11:08:22 -0700200 * @return The index of the currently displayed page.
Winson Chung321e9ee2010-08-09 13:37:56 -0700201 */
Winson Chung86f77532010-08-24 11:08:22 -0700202 int getCurrentPage() {
203 return mCurrentPage;
Winson Chung321e9ee2010-08-09 13:37:56 -0700204 }
205
Winson Chung86f77532010-08-24 11:08:22 -0700206 int getPageCount() {
Winson Chung321e9ee2010-08-09 13:37:56 -0700207 return getChildCount();
208 }
209
Winson Chung86f77532010-08-24 11:08:22 -0700210 View getPageAt(int index) {
Winson Chung321e9ee2010-08-09 13:37:56 -0700211 return getChildAt(index);
212 }
213
214 int getScrollWidth() {
215 return getWidth();
216 }
217
218 /**
Winson Chung86f77532010-08-24 11:08:22 -0700219 * Sets the current page.
Winson Chung321e9ee2010-08-09 13:37:56 -0700220 */
Winson Chung86f77532010-08-24 11:08:22 -0700221 void setCurrentPage(int currentPage) {
Winson Chung321e9ee2010-08-09 13:37:56 -0700222 if (!mScroller.isFinished()) mScroller.abortAnimation();
223 if (getChildCount() == 0) return;
224
Winson Chung86f77532010-08-24 11:08:22 -0700225 mCurrentPage = Math.max(0, Math.min(currentPage, getPageCount() - 1));
226 scrollTo(getChildOffset(mCurrentPage) - getRelativeChildOffset(mCurrentPage), 0);
Winson Chung80baf5a2010-08-09 16:03:15 -0700227
Winson Chung321e9ee2010-08-09 13:37:56 -0700228 invalidate();
Winson Chung86f77532010-08-24 11:08:22 -0700229 notifyPageSwitchListener();
Winson Chung321e9ee2010-08-09 13:37:56 -0700230 }
231
Michael Jurka0142d492010-08-25 17:46:15 -0700232 protected void notifyPageSwitchListener() {
Winson Chung86f77532010-08-24 11:08:22 -0700233 if (mPageSwitchListener != null) {
234 mPageSwitchListener.onPageSwitch(getPageAt(mCurrentPage), mCurrentPage);
Winson Chung321e9ee2010-08-09 13:37:56 -0700235 }
236 }
237
Michael Jurka0142d492010-08-25 17:46:15 -0700238 // a method that subclasses can override to add behavior
239 protected void pageBeginMoving() {
240 }
241
242 // a method that subclasses can override to add behavior
243 protected void pageEndMoving() {
244 }
245
Winson Chung321e9ee2010-08-09 13:37:56 -0700246 /**
Winson Chung86f77532010-08-24 11:08:22 -0700247 * Registers the specified listener on each page contained in this workspace.
Winson Chung321e9ee2010-08-09 13:37:56 -0700248 *
249 * @param l The listener used to respond to long clicks.
250 */
251 @Override
252 public void setOnLongClickListener(OnLongClickListener l) {
253 mLongClickListener = l;
Winson Chung86f77532010-08-24 11:08:22 -0700254 final int count = getPageCount();
Winson Chung321e9ee2010-08-09 13:37:56 -0700255 for (int i = 0; i < count; i++) {
Winson Chung86f77532010-08-24 11:08:22 -0700256 getPageAt(i).setOnLongClickListener(l);
Winson Chung321e9ee2010-08-09 13:37:56 -0700257 }
258 }
259
260 @Override
Michael Jurka0142d492010-08-25 17:46:15 -0700261 public void scrollTo(int x, int y) {
262 super.scrollTo(x, y);
263 mTouchX = x;
264 mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
265 }
266
267 // we moved this functionality to a helper function so SmoothPagedView can reuse it
268 protected boolean computeScrollHelper() {
Winson Chung321e9ee2010-08-09 13:37:56 -0700269 if (mScroller.computeScrollOffset()) {
Michael Jurka5f1c5092010-09-03 14:15:02 -0700270 mDirtyPageAlpha = true;
Winson Chung321e9ee2010-08-09 13:37:56 -0700271 scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
Michael Jurka0142d492010-08-25 17:46:15 -0700272 invalidate();
273 return true;
Winson Chung86f77532010-08-24 11:08:22 -0700274 } else if (mNextPage != INVALID_PAGE) {
Michael Jurka5f1c5092010-09-03 14:15:02 -0700275 mDirtyPageAlpha = true;
Winson Chung86f77532010-08-24 11:08:22 -0700276 mCurrentPage = Math.max(0, Math.min(mNextPage, getPageCount() - 1));
Winson Chung86f77532010-08-24 11:08:22 -0700277 mNextPage = INVALID_PAGE;
Michael Jurka0142d492010-08-25 17:46:15 -0700278 notifyPageSwitchListener();
279 pageEndMoving();
280 return true;
Winson Chung321e9ee2010-08-09 13:37:56 -0700281 }
Michael Jurka0142d492010-08-25 17:46:15 -0700282 return false;
283 }
284
285 @Override
286 public void computeScroll() {
287 computeScrollHelper();
Winson Chung321e9ee2010-08-09 13:37:56 -0700288 }
289
290 @Override
291 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
292 final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
293 final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
294 if (widthMode != MeasureSpec.EXACTLY) {
295 throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
296 }
297
298 final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
299 final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
300 if (heightMode != MeasureSpec.EXACTLY) {
301 throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
302 }
303
304 // The children are given the same width and height as the workspace
Michael Jurka5f1c5092010-09-03 14:15:02 -0700305 // unless they were set to WRAP_CONTENT
Winson Chung321e9ee2010-08-09 13:37:56 -0700306 final int childCount = getChildCount();
307 for (int i = 0; i < childCount; i++) {
Michael Jurka5f1c5092010-09-03 14:15:02 -0700308 // disallowing padding in paged view (just pass 0)
309 final View child = getChildAt(i);
310 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
311
312 int childWidthMode;
313 if (lp.width == LayoutParams.WRAP_CONTENT) {
314 childWidthMode = MeasureSpec.AT_MOST;
315 } else {
316 childWidthMode = MeasureSpec.EXACTLY;
317 }
318
319 int childHeightMode;
320 if (lp.height == LayoutParams.WRAP_CONTENT) {
321 childHeightMode = MeasureSpec.AT_MOST;
322 } else {
323 childHeightMode = MeasureSpec.EXACTLY;
324 }
325
326 final int childWidthMeasureSpec =
327 MeasureSpec.makeMeasureSpec(widthSize, childWidthMode);
328 final int childHeightMeasureSpec =
329 MeasureSpec.makeMeasureSpec(heightSize, childHeightMode);
330
331 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
Winson Chung321e9ee2010-08-09 13:37:56 -0700332 }
333
334 setMeasuredDimension(widthSize, heightSize);
Winson Chung321e9ee2010-08-09 13:37:56 -0700335 }
336
337 @Override
Michael Jurka28750fb2010-09-24 17:43:49 -0700338 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
Michael Jurkacfc62942010-09-14 14:01:07 -0700339 if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
340 setHorizontalScrollBarEnabled(false);
341 int newX = getChildOffset(mCurrentPage) - getRelativeChildOffset(mCurrentPage);
342 scrollTo(newX, 0);
343 mScroller.setFinalX(newX);
344 setHorizontalScrollBarEnabled(true);
345 mFirstLayout = false;
346 }
347
Winson Chung321e9ee2010-08-09 13:37:56 -0700348 final int childCount = getChildCount();
349 int childLeft = 0;
350 if (childCount > 0) {
Winson Chunge3193b92010-09-10 11:44:42 -0700351 childLeft = getRelativeChildOffset(0);
Winson Chung321e9ee2010-08-09 13:37:56 -0700352 }
353
354 for (int i = 0; i < childCount; i++) {
355 final View child = getChildAt(i);
356 if (child.getVisibility() != View.GONE) {
357 final int childWidth = child.getMeasuredWidth();
Michael Jurka5f1c5092010-09-03 14:15:02 -0700358 final int childHeight = (getMeasuredHeight() - child.getMeasuredHeight()) / 2;
359 child.layout(childLeft, childHeight,
360 childLeft + childWidth, childHeight + child.getMeasuredHeight());
Winson Chung321e9ee2010-08-09 13:37:56 -0700361 childLeft += childWidth;
362 }
363 }
364 }
365
Winson Chunge3193b92010-09-10 11:44:42 -0700366 protected void updateAdjacentPagesAlpha() {
Michael Jurka0142d492010-08-25 17:46:15 -0700367 if (mFadeInAdjacentScreens) {
368 if (mDirtyPageAlpha || (mTouchState == TOUCH_STATE_SCROLLING) || !mScroller.isFinished()) {
Winson Chunge3193b92010-09-10 11:44:42 -0700369 int halfScreenSize = getMeasuredWidth() / 2;
370 int screenCenter = mScrollX + halfScreenSize;
Michael Jurka0142d492010-08-25 17:46:15 -0700371 final int childCount = getChildCount();
372 for (int i = 0; i < childCount; ++i) {
373 View layout = (View) getChildAt(i);
374 int childWidth = layout.getMeasuredWidth();
375 int halfChildWidth = (childWidth / 2);
376 int childCenter = getChildOffset(i) + halfChildWidth;
Winson Chunge8878e32010-09-15 20:37:09 -0700377
378 int d = halfChildWidth;
379 int distanceFromScreenCenter = childCenter - screenCenter;
380 if (distanceFromScreenCenter > 0) {
381 if (i > 0) {
382 d += getChildAt(i - 1).getMeasuredWidth() / 2;
383 }
Michael Jurka0142d492010-08-25 17:46:15 -0700384 } else {
Winson Chunge8878e32010-09-15 20:37:09 -0700385 if (i < childCount - 1) {
386 d += getChildAt(i + 1).getMeasuredWidth() / 2;
387 }
Michael Jurka0142d492010-08-25 17:46:15 -0700388 }
Winson Chunge8878e32010-09-15 20:37:09 -0700389
390 float dimAlpha = (float) (Math.abs(distanceFromScreenCenter)) / d;
391 dimAlpha = Math.max(0.0f, Math.min(1.0f, (dimAlpha * dimAlpha)));
392 float alpha = 1.0f - dimAlpha;
393
Adam Cohen9415d872010-09-13 14:49:43 -0700394 if (alpha < ALPHA_QUANTIZE_LEVEL) {
395 alpha = 0.0f;
396 } else if (alpha > 1.0f - ALPHA_QUANTIZE_LEVEL) {
397 alpha = 1.0f;
398 }
399
Michael Jurka0142d492010-08-25 17:46:15 -0700400 if (Float.compare(alpha, layout.getAlpha()) != 0) {
401 layout.setAlpha(alpha);
402 }
Winson Chung321e9ee2010-08-09 13:37:56 -0700403 }
Michael Jurka0142d492010-08-25 17:46:15 -0700404 mDirtyPageAlpha = false;
Winson Chung321e9ee2010-08-09 13:37:56 -0700405 }
406 }
Winson Chunge3193b92010-09-10 11:44:42 -0700407 }
408
Adam Cohen9415d872010-09-13 14:49:43 -0700409 protected void screenScrolled(int screenCenter) {
410 }
411
Winson Chunge3193b92010-09-10 11:44:42 -0700412 @Override
413 protected void dispatchDraw(Canvas canvas) {
414 updateAdjacentPagesAlpha();
Michael Jurka0142d492010-08-25 17:46:15 -0700415
Adam Cohen9415d872010-09-13 14:49:43 -0700416 int halfScreenSize = getMeasuredWidth() / 2;
417 int screenCenter = mScrollX + halfScreenSize;
418 screenScrolled(screenCenter);
Michael Jurka0142d492010-08-25 17:46:15 -0700419 // Find out which screens are visible; as an optimization we only call draw on them
Michael Jurka0142d492010-08-25 17:46:15 -0700420 // As an optimization, this code assumes that all pages have the same width as the 0th
421 // page.
Michael Jurka0142d492010-08-25 17:46:15 -0700422 final int pageCount = getChildCount();
Michael Jurkac4fb9172010-09-02 17:19:20 -0700423 if (pageCount > 0) {
424 final int pageWidth = getChildAt(0).getMeasuredWidth();
425 final int screenWidth = getMeasuredWidth();
426 int x = getRelativeChildOffset(0) + pageWidth;
427 int leftScreen = 0;
428 int rightScreen = 0;
429 while (x <= mScrollX) {
430 leftScreen++;
431 x += pageWidth;
432 // replace above line with this if you don't assume all pages have same width as 0th
433 // page:
434 // x += getChildAt(leftScreen).getMeasuredWidth();
435 }
436 rightScreen = leftScreen;
437 while (x < mScrollX + screenWidth) {
438 rightScreen++;
439 x += pageWidth;
440 // replace above line with this if you don't assume all pages have same width as 0th
441 // page:
442 //if (rightScreen < pageCount) {
443 // x += getChildAt(rightScreen).getMeasuredWidth();
444 //}
445 }
446 rightScreen = Math.min(getChildCount() - 1, rightScreen);
Michael Jurka0142d492010-08-25 17:46:15 -0700447
Michael Jurkac4fb9172010-09-02 17:19:20 -0700448 final long drawingTime = getDrawingTime();
449 for (int i = leftScreen; i <= rightScreen; i++) {
450 drawChild(canvas, getChildAt(i), drawingTime);
451 }
Michael Jurka0142d492010-08-25 17:46:15 -0700452 }
Winson Chung321e9ee2010-08-09 13:37:56 -0700453 }
454
455 @Override
456 public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
Winson Chung86f77532010-08-24 11:08:22 -0700457 int page = indexOfChild(child);
458 if (page != mCurrentPage || !mScroller.isFinished()) {
459 snapToPage(page);
Winson Chung321e9ee2010-08-09 13:37:56 -0700460 return true;
461 }
462 return false;
463 }
464
465 @Override
466 protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
Winson Chung86f77532010-08-24 11:08:22 -0700467 int focusablePage;
468 if (mNextPage != INVALID_PAGE) {
469 focusablePage = mNextPage;
Winson Chung321e9ee2010-08-09 13:37:56 -0700470 } else {
Winson Chung86f77532010-08-24 11:08:22 -0700471 focusablePage = mCurrentPage;
Winson Chung321e9ee2010-08-09 13:37:56 -0700472 }
Winson Chung86f77532010-08-24 11:08:22 -0700473 View v = getPageAt(focusablePage);
Winson Chung321e9ee2010-08-09 13:37:56 -0700474 if (v != null) {
475 v.requestFocus(direction, previouslyFocusedRect);
476 }
477 return false;
478 }
479
480 @Override
481 public boolean dispatchUnhandledMove(View focused, int direction) {
482 if (direction == View.FOCUS_LEFT) {
Winson Chung86f77532010-08-24 11:08:22 -0700483 if (getCurrentPage() > 0) {
484 snapToPage(getCurrentPage() - 1);
Winson Chung321e9ee2010-08-09 13:37:56 -0700485 return true;
486 }
487 } else if (direction == View.FOCUS_RIGHT) {
Winson Chung86f77532010-08-24 11:08:22 -0700488 if (getCurrentPage() < getPageCount() - 1) {
489 snapToPage(getCurrentPage() + 1);
Winson Chung321e9ee2010-08-09 13:37:56 -0700490 return true;
491 }
492 }
493 return super.dispatchUnhandledMove(focused, direction);
494 }
495
496 @Override
497 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
Winson Chung86f77532010-08-24 11:08:22 -0700498 if (mCurrentPage >= 0 && mCurrentPage < getPageCount()) {
499 getPageAt(mCurrentPage).addFocusables(views, direction);
Winson Chung321e9ee2010-08-09 13:37:56 -0700500 }
501 if (direction == View.FOCUS_LEFT) {
Winson Chung86f77532010-08-24 11:08:22 -0700502 if (mCurrentPage > 0) {
503 getPageAt(mCurrentPage - 1).addFocusables(views, direction);
Winson Chung321e9ee2010-08-09 13:37:56 -0700504 }
505 } else if (direction == View.FOCUS_RIGHT){
Winson Chung86f77532010-08-24 11:08:22 -0700506 if (mCurrentPage < getPageCount() - 1) {
507 getPageAt(mCurrentPage + 1).addFocusables(views, direction);
Winson Chung321e9ee2010-08-09 13:37:56 -0700508 }
509 }
510 }
511
512 /**
513 * If one of our descendant views decides that it could be focused now, only
Winson Chung86f77532010-08-24 11:08:22 -0700514 * pass that along if it's on the current page.
Winson Chung321e9ee2010-08-09 13:37:56 -0700515 *
Winson Chung86f77532010-08-24 11:08:22 -0700516 * This happens when live folders requery, and if they're off page, they
517 * end up calling requestFocus, which pulls it on page.
Winson Chung321e9ee2010-08-09 13:37:56 -0700518 */
519 @Override
520 public void focusableViewAvailable(View focused) {
Winson Chung86f77532010-08-24 11:08:22 -0700521 View current = getPageAt(mCurrentPage);
Winson Chung321e9ee2010-08-09 13:37:56 -0700522 View v = focused;
523 while (true) {
524 if (v == current) {
525 super.focusableViewAvailable(focused);
526 return;
527 }
528 if (v == this) {
529 return;
530 }
531 ViewParent parent = v.getParent();
532 if (parent instanceof View) {
533 v = (View)v.getParent();
534 } else {
535 return;
536 }
537 }
538 }
539
540 /**
541 * {@inheritDoc}
542 */
543 @Override
544 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
545 if (disallowIntercept) {
546 // We need to make sure to cancel our long press if
547 // a scrollable widget takes over touch events
Winson Chung86f77532010-08-24 11:08:22 -0700548 final View currentPage = getChildAt(mCurrentPage);
549 currentPage.cancelLongPress();
Winson Chung321e9ee2010-08-09 13:37:56 -0700550 }
551 super.requestDisallowInterceptTouchEvent(disallowIntercept);
552 }
553
554 @Override
555 public boolean onInterceptTouchEvent(MotionEvent ev) {
556 /*
557 * This method JUST determines whether we want to intercept the motion.
558 * If we return true, onTouchEvent will be called and we do the actual
559 * scrolling there.
560 */
561
562 /*
563 * Shortcut the most recurring case: the user is in the dragging
564 * state and he is moving his finger. We want to intercept this
565 * motion.
566 */
567 final int action = ev.getAction();
568 if ((action == MotionEvent.ACTION_MOVE) &&
569 (mTouchState == TOUCH_STATE_SCROLLING)) {
570 return true;
571 }
572
Winson Chung321e9ee2010-08-09 13:37:56 -0700573 switch (action & MotionEvent.ACTION_MASK) {
574 case MotionEvent.ACTION_MOVE: {
575 /*
576 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
577 * whether the user has moved far enough from his original down touch.
578 */
Michael Jurka1ff706b2010-09-14 17:35:20 -0700579 if (mActivePointerId != INVALID_POINTER) {
580 determineScrollingStart(ev);
581 break;
582 }
583 // if mActivePointerId is INVALID_POINTER, then we must have missed an ACTION_DOWN
584 // event. in that case, treat the first occurence of a move event as a ACTION_DOWN
585 // i.e. fall through to the next case (don't break)
586 // (We sometimes miss ACTION_DOWN events in Workspace because it ignores all events
587 // while it's small- this was causing a crash before we checked for INVALID_POINTER)
Winson Chung321e9ee2010-08-09 13:37:56 -0700588 }
589
590 case MotionEvent.ACTION_DOWN: {
591 final float x = ev.getX();
592 final float y = ev.getY();
593 // Remember location of down touch
594 mDownMotionX = x;
595 mLastMotionX = x;
596 mLastMotionY = y;
597 mActivePointerId = ev.getPointerId(0);
598 mAllowLongPress = true;
599
600 /*
601 * If being flinged and user touches the screen, initiate drag;
602 * otherwise don't. mScroller.isFinished should be false when
603 * being flinged.
604 */
Winson Chung5f2aa4e2010-08-20 14:49:25 -0700605 final int xDist = (mScroller.getFinalX() - mScroller.getCurrX());
606 final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop);
607 if (finishedScrolling) {
608 mTouchState = TOUCH_STATE_REST;
609 mScroller.abortAnimation();
610 } else {
611 mTouchState = TOUCH_STATE_SCROLLING;
612 }
Winson Chung321e9ee2010-08-09 13:37:56 -0700613
Winson Chung86f77532010-08-24 11:08:22 -0700614 // check if this can be the beginning of a tap on the side of the pages
Winson Chung321e9ee2010-08-09 13:37:56 -0700615 // to scroll the current page
616 if ((mTouchState != TOUCH_STATE_PREV_PAGE) &&
617 (mTouchState != TOUCH_STATE_NEXT_PAGE)) {
618 if (getChildCount() > 0) {
Winson Chunge3193b92010-09-10 11:44:42 -0700619 int width = getMeasuredWidth();
620 int offset = getRelativeChildOffset(mCurrentPage);
621 if (x < offset) {
Winson Chung321e9ee2010-08-09 13:37:56 -0700622 mTouchState = TOUCH_STATE_PREV_PAGE;
Winson Chunge3193b92010-09-10 11:44:42 -0700623 } else if (x > (width - offset)) {
Winson Chung321e9ee2010-08-09 13:37:56 -0700624 mTouchState = TOUCH_STATE_NEXT_PAGE;
625 }
626 }
627 }
628 break;
629 }
630
631 case MotionEvent.ACTION_CANCEL:
632 case MotionEvent.ACTION_UP:
633 // Release the drag
Michael Jurka0142d492010-08-25 17:46:15 -0700634 pageEndMoving();
Winson Chung321e9ee2010-08-09 13:37:56 -0700635 mTouchState = TOUCH_STATE_REST;
636 mAllowLongPress = false;
637 mActivePointerId = INVALID_POINTER;
638
639 break;
640
641 case MotionEvent.ACTION_POINTER_UP:
642 onSecondaryPointerUp(ev);
643 break;
644 }
645
646 /*
647 * The only time we want to intercept motion events is if we are in the
648 * drag mode.
649 */
650 return mTouchState != TOUCH_STATE_REST;
651 }
652
Winson Chung80baf5a2010-08-09 16:03:15 -0700653 protected void animateClickFeedback(View v, final Runnable r) {
654 // animate the view slightly to show click feedback running some logic after it is "pressed"
655 Animation anim = AnimationUtils.loadAnimation(getContext(),
656 R.anim.paged_view_click_feedback);
657 anim.setAnimationListener(new AnimationListener() {
658 @Override
659 public void onAnimationStart(Animation animation) {}
660 @Override
661 public void onAnimationRepeat(Animation animation) {
662 r.run();
663 }
664 @Override
665 public void onAnimationEnd(Animation animation) {}
666 });
667 v.startAnimation(anim);
668 }
669
Winson Chung321e9ee2010-08-09 13:37:56 -0700670 /*
671 * Determines if we should change the touch state to start scrolling after the
672 * user moves their touch point too far.
673 */
674 private void determineScrollingStart(MotionEvent ev) {
675 /*
676 * Locally do absolute value. mLastMotionX is set to the y value
677 * of the down event.
678 */
679 final int pointerIndex = ev.findPointerIndex(mActivePointerId);
680 final float x = ev.getX(pointerIndex);
681 final float y = ev.getY(pointerIndex);
682 final int xDiff = (int) Math.abs(x - mLastMotionX);
683 final int yDiff = (int) Math.abs(y - mLastMotionY);
684
685 final int touchSlop = mTouchSlop;
686 boolean xPaged = xDiff > mPagingTouchSlop;
687 boolean xMoved = xDiff > touchSlop;
688 boolean yMoved = yDiff > touchSlop;
689
690 if (xMoved || yMoved) {
Michael Jurka0142d492010-08-25 17:46:15 -0700691 if (mUsePagingTouchSlop ? xPaged : xMoved) {
Winson Chung321e9ee2010-08-09 13:37:56 -0700692 // Scroll if the user moved far enough along the X axis
693 mTouchState = TOUCH_STATE_SCROLLING;
694 mLastMotionX = x;
Michael Jurka0142d492010-08-25 17:46:15 -0700695 mTouchX = mScrollX;
696 mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
697 pageBeginMoving();
Winson Chung321e9ee2010-08-09 13:37:56 -0700698 }
699 // Either way, cancel any pending longpress
700 if (mAllowLongPress) {
701 mAllowLongPress = false;
702 // Try canceling the long press. It could also have been scheduled
703 // by a distant descendant, so use the mAllowLongPress flag to block
704 // everything
Winson Chung86f77532010-08-24 11:08:22 -0700705 final View currentPage = getPageAt(mCurrentPage);
706 currentPage.cancelLongPress();
Winson Chung321e9ee2010-08-09 13:37:56 -0700707 }
708 }
709 }
710
711 @Override
712 public boolean onTouchEvent(MotionEvent ev) {
713 if (mVelocityTracker == null) {
714 mVelocityTracker = VelocityTracker.obtain();
715 }
716 mVelocityTracker.addMovement(ev);
717
718 final int action = ev.getAction();
719
720 switch (action & MotionEvent.ACTION_MASK) {
721 case MotionEvent.ACTION_DOWN:
722 /*
723 * If being flinged and user touches, stop the fling. isFinished
724 * will be false if being flinged.
725 */
726 if (!mScroller.isFinished()) {
727 mScroller.abortAnimation();
728 }
729
730 // Remember where the motion event started
731 mDownMotionX = mLastMotionX = ev.getX();
732 mActivePointerId = ev.getPointerId(0);
Michael Jurka0142d492010-08-25 17:46:15 -0700733 if (mTouchState == TOUCH_STATE_SCROLLING) {
734 pageBeginMoving();
735 }
Winson Chung321e9ee2010-08-09 13:37:56 -0700736 break;
737
738 case MotionEvent.ACTION_MOVE:
739 if (mTouchState == TOUCH_STATE_SCROLLING) {
740 // Scroll to follow the motion event
741 final int pointerIndex = ev.findPointerIndex(mActivePointerId);
742 final float x = ev.getX(pointerIndex);
743 final int deltaX = (int) (mLastMotionX - x);
744 mLastMotionX = x;
745
746 int sx = getScrollX();
747 if (deltaX < 0) {
748 if (sx > 0) {
Michael Jurka0142d492010-08-25 17:46:15 -0700749 mTouchX += Math.max(-mTouchX, deltaX);
750 mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
751 if (!mDeferScrollUpdate) {
752 scrollBy(Math.max(-sx, deltaX), 0);
753 } else {
754 // This will trigger a call to computeScroll() on next drawChild() call
755 invalidate();
756 }
Winson Chung321e9ee2010-08-09 13:37:56 -0700757 }
758 } else if (deltaX > 0) {
759 final int lastChildIndex = getChildCount() - 1;
760 final int availableToScroll = getChildOffset(lastChildIndex) -
761 getRelativeChildOffset(lastChildIndex) - sx;
762 if (availableToScroll > 0) {
Michael Jurka0142d492010-08-25 17:46:15 -0700763 mTouchX += Math.min(availableToScroll, deltaX);
764 mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
765 if (!mDeferScrollUpdate) {
766 scrollBy(Math.min(availableToScroll, deltaX), 0);
767 } else {
768 // This will trigger a call to computeScroll() on next drawChild() call
769 invalidate();
770 }
Winson Chung321e9ee2010-08-09 13:37:56 -0700771 }
772 } else {
773 awakenScrollBars();
774 }
775 } else if ((mTouchState == TOUCH_STATE_PREV_PAGE) ||
776 (mTouchState == TOUCH_STATE_NEXT_PAGE)) {
777 determineScrollingStart(ev);
778 }
779 break;
780
781 case MotionEvent.ACTION_UP:
782 if (mTouchState == TOUCH_STATE_SCROLLING) {
783 final int activePointerId = mActivePointerId;
784 final int pointerIndex = ev.findPointerIndex(activePointerId);
785 final float x = ev.getX(pointerIndex);
786 final VelocityTracker velocityTracker = mVelocityTracker;
787 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
788 int velocityX = (int) velocityTracker.getXVelocity(activePointerId);
789 boolean isfling = Math.abs(mDownMotionX - x) > MIN_LENGTH_FOR_FLING;
790
Michael Jurka0142d492010-08-25 17:46:15 -0700791 final int snapVelocity = mSnapVelocity;
792 if (isfling && velocityX > snapVelocity && mCurrentPage > 0) {
793 snapToPageWithVelocity(mCurrentPage - 1, velocityX);
794 } else if (isfling && velocityX < -snapVelocity &&
Winson Chung86f77532010-08-24 11:08:22 -0700795 mCurrentPage < getChildCount() - 1) {
Michael Jurka0142d492010-08-25 17:46:15 -0700796 snapToPageWithVelocity(mCurrentPage + 1, velocityX);
Winson Chung321e9ee2010-08-09 13:37:56 -0700797 } else {
798 snapToDestination();
799 }
800
801 if (mVelocityTracker != null) {
802 mVelocityTracker.recycle();
803 mVelocityTracker = null;
804 }
805 } else if (mTouchState == TOUCH_STATE_PREV_PAGE) {
806 // at this point we have not moved beyond the touch slop
807 // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
808 // we can just page
Winson Chung86f77532010-08-24 11:08:22 -0700809 int nextPage = Math.max(0, mCurrentPage - 1);
810 if (nextPage != mCurrentPage) {
811 snapToPage(nextPage);
Winson Chung321e9ee2010-08-09 13:37:56 -0700812 } else {
813 snapToDestination();
814 }
815 } else if (mTouchState == TOUCH_STATE_NEXT_PAGE) {
816 // at this point we have not moved beyond the touch slop
817 // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
818 // we can just page
Winson Chung86f77532010-08-24 11:08:22 -0700819 int nextPage = Math.min(getChildCount() - 1, mCurrentPage + 1);
820 if (nextPage != mCurrentPage) {
821 snapToPage(nextPage);
Winson Chung321e9ee2010-08-09 13:37:56 -0700822 } else {
823 snapToDestination();
824 }
825 }
826 mTouchState = TOUCH_STATE_REST;
827 mActivePointerId = INVALID_POINTER;
828 break;
829
830 case MotionEvent.ACTION_CANCEL:
831 mTouchState = TOUCH_STATE_REST;
832 mActivePointerId = INVALID_POINTER;
833 break;
834
835 case MotionEvent.ACTION_POINTER_UP:
836 onSecondaryPointerUp(ev);
837 break;
838 }
839
840 return true;
841 }
842
843 private void onSecondaryPointerUp(MotionEvent ev) {
844 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
845 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
846 final int pointerId = ev.getPointerId(pointerIndex);
847 if (pointerId == mActivePointerId) {
848 // This was our active pointer going up. Choose a new
849 // active pointer and adjust accordingly.
850 // TODO: Make this decision more intelligent.
851 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
852 mLastMotionX = mDownMotionX = ev.getX(newPointerIndex);
853 mLastMotionY = ev.getY(newPointerIndex);
854 mActivePointerId = ev.getPointerId(newPointerIndex);
855 if (mVelocityTracker != null) {
856 mVelocityTracker.clear();
857 }
858 }
859 }
860
861 @Override
862 public void requestChildFocus(View child, View focused) {
863 super.requestChildFocus(child, focused);
Winson Chung86f77532010-08-24 11:08:22 -0700864 int page = indexOfChild(child);
865 if (page >= 0 && !isInTouchMode()) {
866 snapToPage(page);
Winson Chung321e9ee2010-08-09 13:37:56 -0700867 }
868 }
869
Winson Chunge3193b92010-09-10 11:44:42 -0700870 protected int getChildIndexForRelativeOffset(int relativeOffset) {
871 final int childCount = getChildCount();
872 int left = getRelativeChildOffset(0);
873 for (int i = 0; i < childCount; ++i) {
874 final int right = (left + getChildAt(i).getMeasuredWidth());
875 if (left <= relativeOffset && relativeOffset <= right) {
876 return i;
877 }
878 left = right;
879 }
880 return -1;
881 }
882
Winson Chung321e9ee2010-08-09 13:37:56 -0700883 protected int getRelativeChildOffset(int index) {
884 return (getMeasuredWidth() - getChildAt(index).getMeasuredWidth()) / 2;
885 }
886
887 protected int getChildOffset(int index) {
888 if (getChildCount() == 0)
889 return 0;
890
891 int offset = getRelativeChildOffset(0);
892 for (int i = 0; i < index; ++i) {
893 offset += getChildAt(i).getMeasuredWidth();
894 }
895 return offset;
896 }
897
Adam Cohend19d3ca2010-09-15 14:43:42 -0700898 int getPageNearestToCenterOfScreen() {
Winson Chung321e9ee2010-08-09 13:37:56 -0700899 int minDistanceFromScreenCenter = getMeasuredWidth();
900 int minDistanceFromScreenCenterIndex = -1;
901 int screenCenter = mScrollX + (getMeasuredWidth() / 2);
902 final int childCount = getChildCount();
903 for (int i = 0; i < childCount; ++i) {
Michael Jurka0142d492010-08-25 17:46:15 -0700904 View layout = (View) getChildAt(i);
Winson Chung321e9ee2010-08-09 13:37:56 -0700905 int childWidth = layout.getMeasuredWidth();
906 int halfChildWidth = (childWidth / 2);
907 int childCenter = getChildOffset(i) + halfChildWidth;
908 int distanceFromScreenCenter = Math.abs(childCenter - screenCenter);
909 if (distanceFromScreenCenter < minDistanceFromScreenCenter) {
910 minDistanceFromScreenCenter = distanceFromScreenCenter;
911 minDistanceFromScreenCenterIndex = i;
912 }
913 }
Adam Cohend19d3ca2010-09-15 14:43:42 -0700914 return minDistanceFromScreenCenterIndex;
915 }
916
917 protected void snapToDestination() {
918 snapToPage(getPageNearestToCenterOfScreen(), PAGE_SNAP_ANIMATION_DURATION);
Winson Chung321e9ee2010-08-09 13:37:56 -0700919 }
920
Michael Jurka0142d492010-08-25 17:46:15 -0700921 protected void snapToPageWithVelocity(int whichPage, int velocity) {
922 // We ignore velocity in this implementation, but children (e.g. SmoothPagedView)
923 // can use it
924 snapToPage(whichPage);
925 }
926
927 protected void snapToPage(int whichPage) {
Winson Chung5f2aa4e2010-08-20 14:49:25 -0700928 snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION);
Winson Chung321e9ee2010-08-09 13:37:56 -0700929 }
930
Michael Jurka0142d492010-08-25 17:46:15 -0700931 protected void snapToPage(int whichPage, int duration) {
Winson Chung86f77532010-08-24 11:08:22 -0700932 whichPage = Math.max(0, Math.min(whichPage, getPageCount() - 1));
Winson Chung321e9ee2010-08-09 13:37:56 -0700933
Winson Chung86f77532010-08-24 11:08:22 -0700934 int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage);
Winson Chung321e9ee2010-08-09 13:37:56 -0700935 final int sX = getScrollX();
936 final int delta = newX - sX;
Michael Jurka0142d492010-08-25 17:46:15 -0700937 snapToPage(whichPage, delta, duration);
938 }
939
940 protected void snapToPage(int whichPage, int delta, int duration) {
941 mNextPage = whichPage;
942
943 View focusedChild = getFocusedChild();
944 if (focusedChild != null && whichPage != mCurrentPage &&
945 focusedChild == getChildAt(mCurrentPage)) {
946 focusedChild.clearFocus();
947 }
948
949 pageBeginMoving();
Winson Chung321e9ee2010-08-09 13:37:56 -0700950 awakenScrollBars(duration);
951 if (duration == 0) {
952 duration = Math.abs(delta);
953 }
954
955 if (!mScroller.isFinished()) mScroller.abortAnimation();
Michael Jurka0142d492010-08-25 17:46:15 -0700956 mScroller.startScroll(getScrollX(), 0, delta, 0, duration);
Winson Chung80baf5a2010-08-09 16:03:15 -0700957
958 // only load some associated pages
Winson Chung86f77532010-08-24 11:08:22 -0700959 loadAssociatedPages(mNextPage);
Michael Jurka0142d492010-08-25 17:46:15 -0700960 notifyPageSwitchListener();
Winson Chung321e9ee2010-08-09 13:37:56 -0700961 invalidate();
962 }
963
964 @Override
965 protected Parcelable onSaveInstanceState() {
966 final SavedState state = new SavedState(super.onSaveInstanceState());
Winson Chung86f77532010-08-24 11:08:22 -0700967 state.currentPage = mCurrentPage;
Winson Chung321e9ee2010-08-09 13:37:56 -0700968 return state;
969 }
970
971 @Override
972 protected void onRestoreInstanceState(Parcelable state) {
973 SavedState savedState = (SavedState) state;
974 super.onRestoreInstanceState(savedState.getSuperState());
Winson Chung86f77532010-08-24 11:08:22 -0700975 if (savedState.currentPage != -1) {
976 mCurrentPage = savedState.currentPage;
Winson Chung321e9ee2010-08-09 13:37:56 -0700977 }
978 }
979
980 public void scrollLeft() {
981 if (mScroller.isFinished()) {
Winson Chung86f77532010-08-24 11:08:22 -0700982 if (mCurrentPage > 0) snapToPage(mCurrentPage - 1);
Winson Chung321e9ee2010-08-09 13:37:56 -0700983 } else {
Winson Chung86f77532010-08-24 11:08:22 -0700984 if (mNextPage > 0) snapToPage(mNextPage - 1);
Winson Chung321e9ee2010-08-09 13:37:56 -0700985 }
986 }
987
988 public void scrollRight() {
989 if (mScroller.isFinished()) {
Winson Chung86f77532010-08-24 11:08:22 -0700990 if (mCurrentPage < getChildCount() -1) snapToPage(mCurrentPage + 1);
Winson Chung321e9ee2010-08-09 13:37:56 -0700991 } else {
Winson Chung86f77532010-08-24 11:08:22 -0700992 if (mNextPage < getChildCount() -1) snapToPage(mNextPage + 1);
Winson Chung321e9ee2010-08-09 13:37:56 -0700993 }
994 }
995
Winson Chung86f77532010-08-24 11:08:22 -0700996 public int getPageForView(View v) {
Winson Chung321e9ee2010-08-09 13:37:56 -0700997 int result = -1;
998 if (v != null) {
999 ViewParent vp = v.getParent();
1000 int count = getChildCount();
1001 for (int i = 0; i < count; i++) {
1002 if (vp == getChildAt(i)) {
1003 return i;
1004 }
1005 }
1006 }
1007 return result;
1008 }
1009
1010 /**
1011 * @return True is long presses are still allowed for the current touch
1012 */
1013 public boolean allowLongPress() {
1014 return mAllowLongPress;
1015 }
1016
Michael Jurka0142d492010-08-25 17:46:15 -07001017 /**
1018 * Set true to allow long-press events to be triggered, usually checked by
1019 * {@link Launcher} to accept or block dpad-initiated long-presses.
1020 */
1021 public void setAllowLongPress(boolean allowLongPress) {
1022 mAllowLongPress = allowLongPress;
1023 }
1024
Winson Chung321e9ee2010-08-09 13:37:56 -07001025 public static class SavedState extends BaseSavedState {
Winson Chung86f77532010-08-24 11:08:22 -07001026 int currentPage = -1;
Winson Chung321e9ee2010-08-09 13:37:56 -07001027
1028 SavedState(Parcelable superState) {
1029 super(superState);
1030 }
1031
1032 private SavedState(Parcel in) {
1033 super(in);
Winson Chung86f77532010-08-24 11:08:22 -07001034 currentPage = in.readInt();
Winson Chung321e9ee2010-08-09 13:37:56 -07001035 }
1036
1037 @Override
1038 public void writeToParcel(Parcel out, int flags) {
1039 super.writeToParcel(out, flags);
Winson Chung86f77532010-08-24 11:08:22 -07001040 out.writeInt(currentPage);
Winson Chung321e9ee2010-08-09 13:37:56 -07001041 }
1042
1043 public static final Parcelable.Creator<SavedState> CREATOR =
1044 new Parcelable.Creator<SavedState>() {
1045 public SavedState createFromParcel(Parcel in) {
1046 return new SavedState(in);
1047 }
1048
1049 public SavedState[] newArray(int size) {
1050 return new SavedState[size];
1051 }
1052 };
1053 }
1054
Winson Chung86f77532010-08-24 11:08:22 -07001055 public void loadAssociatedPages(int page) {
Michael Jurka0142d492010-08-25 17:46:15 -07001056 if (mContentIsRefreshable) {
1057 final int count = getChildCount();
1058 if (page < count) {
Winson Chunge3193b92010-09-10 11:44:42 -07001059 int lowerPageBound = getAssociatedLowerPageBound(page);
1060 int upperPageBound = getAssociatedUpperPageBound(page);
Michael Jurka0142d492010-08-25 17:46:15 -07001061 for (int i = 0; i < count; ++i) {
1062 final ViewGroup layout = (ViewGroup) getChildAt(i);
1063 final int childCount = layout.getChildCount();
1064 if (lowerPageBound <= i && i <= upperPageBound) {
1065 if (mDirtyPageContent.get(i)) {
1066 syncPageItems(i);
1067 mDirtyPageContent.set(i, false);
1068 }
1069 } else {
1070 if (childCount > 0) {
1071 layout.removeAllViews();
1072 }
1073 mDirtyPageContent.set(i, true);
Winson Chung86f77532010-08-24 11:08:22 -07001074 }
Winson Chung80baf5a2010-08-09 16:03:15 -07001075 }
1076 }
Winson Chung80baf5a2010-08-09 16:03:15 -07001077 }
1078 }
1079
Winson Chunge3193b92010-09-10 11:44:42 -07001080 protected int getAssociatedLowerPageBound(int page) {
1081 return Math.max(0, page - 1);
1082 }
1083 protected int getAssociatedUpperPageBound(int page) {
1084 final int count = getChildCount();
1085 return Math.min(page + 1, count - 1);
1086 }
1087
Winson Chung5f2aa4e2010-08-20 14:49:25 -07001088 protected void startChoiceMode(int mode, ActionMode.Callback callback) {
Patrick Dubroy430c53b2010-09-08 16:01:19 -07001089 if (isChoiceMode(CHOICE_MODE_NONE)) {
1090 mChoiceMode = mode;
1091 mActionMode = startActionMode(callback);
1092 }
Winson Chung5f2aa4e2010-08-20 14:49:25 -07001093 }
1094
Patrick Dubroy2b9ff372010-09-07 17:49:27 -07001095 public void endChoiceMode() {
Winson Chung5f2aa4e2010-08-20 14:49:25 -07001096 if (!isChoiceMode(CHOICE_MODE_NONE)) {
Winson Chung5f2aa4e2010-08-20 14:49:25 -07001097 mChoiceMode = CHOICE_MODE_NONE;
1098 resetCheckedGrandchildren();
Michael Jurkae17e19c2010-09-28 11:01:39 -07001099 if (mActionMode != null) mActionMode.finish();
Patrick Dubroy9f7aec82010-09-06 11:03:37 -07001100 mActionMode = null;
Winson Chung5f2aa4e2010-08-20 14:49:25 -07001101 }
1102 }
1103
1104 protected boolean isChoiceMode(int mode) {
1105 return mChoiceMode == mode;
1106 }
1107
1108 protected ArrayList<Checkable> getCheckedGrandchildren() {
1109 ArrayList<Checkable> checked = new ArrayList<Checkable>();
1110 final int childCount = getChildCount();
1111 for (int i = 0; i < childCount; ++i) {
Winson Chungd0d43012010-09-26 17:26:45 -07001112 final ViewGroup layout = (ViewGroup) getChildAt(i);
Winson Chung5f2aa4e2010-08-20 14:49:25 -07001113 final int grandChildCount = layout.getChildCount();
1114 for (int j = 0; j < grandChildCount; ++j) {
1115 final View v = layout.getChildAt(j);
Patrick Dubroy9f7aec82010-09-06 11:03:37 -07001116 if (v instanceof Checkable && ((Checkable) v).isChecked()) {
Winson Chung5f2aa4e2010-08-20 14:49:25 -07001117 checked.add((Checkable) v);
1118 }
1119 }
1120 }
1121 return checked;
1122 }
1123
Patrick Dubroy9f7aec82010-09-06 11:03:37 -07001124 /**
1125 * If in CHOICE_MODE_SINGLE and an item is checked, returns that item.
1126 * Otherwise, returns null.
1127 */
1128 protected Checkable getSingleCheckedGrandchild() {
1129 if (mChoiceMode == CHOICE_MODE_SINGLE) {
1130 final int childCount = getChildCount();
1131 for (int i = 0; i < childCount; ++i) {
Winson Chungd0d43012010-09-26 17:26:45 -07001132 final ViewGroup layout = (ViewGroup) getChildAt(i);
Patrick Dubroy9f7aec82010-09-06 11:03:37 -07001133 final int grandChildCount = layout.getChildCount();
1134 for (int j = 0; j < grandChildCount; ++j) {
1135 final View v = layout.getChildAt(j);
1136 if (v instanceof Checkable && ((Checkable) v).isChecked()) {
1137 return (Checkable) v;
1138 }
1139 }
1140 }
1141 }
1142 return null;
1143 }
1144
Patrick Dubroy2b9ff372010-09-07 17:49:27 -07001145 public Object getChosenItem() {
1146 View checkedView = (View) getSingleCheckedGrandchild();
1147 if (checkedView != null) {
1148 return checkedView.getTag();
1149 }
1150 return null;
1151 }
1152
Winson Chung5f2aa4e2010-08-20 14:49:25 -07001153 protected void resetCheckedGrandchildren() {
1154 // loop through children, and set all of their children to _not_ be checked
1155 final ArrayList<Checkable> checked = getCheckedGrandchildren();
1156 for (int i = 0; i < checked.size(); ++i) {
1157 final Checkable c = checked.get(i);
1158 c.setChecked(false);
1159 }
1160 }
1161
Winson Chung86f77532010-08-24 11:08:22 -07001162 /**
1163 * This method is called ONLY to synchronize the number of pages that the paged view has.
1164 * To actually fill the pages with information, implement syncPageItems() below. It is
1165 * guaranteed that syncPageItems() will be called for a particular page before it is shown,
1166 * and therefore, individual page items do not need to be updated in this method.
1167 */
Winson Chung321e9ee2010-08-09 13:37:56 -07001168 public abstract void syncPages();
Winson Chung86f77532010-08-24 11:08:22 -07001169
1170 /**
1171 * This method is called to synchronize the items that are on a particular page. If views on
1172 * the page can be reused, then they should be updated within this method.
1173 */
Winson Chung321e9ee2010-08-09 13:37:56 -07001174 public abstract void syncPageItems(int page);
Winson Chung86f77532010-08-24 11:08:22 -07001175
Winson Chung321e9ee2010-08-09 13:37:56 -07001176 public void invalidatePageData() {
Michael Jurka0142d492010-08-25 17:46:15 -07001177 if (mContentIsRefreshable) {
1178 // Update all the pages
1179 syncPages();
Winson Chung86f77532010-08-24 11:08:22 -07001180
Michael Jurka0142d492010-08-25 17:46:15 -07001181 // Mark each of the pages as dirty
1182 final int count = getChildCount();
1183 mDirtyPageContent.clear();
1184 for (int i = 0; i < count; ++i) {
1185 mDirtyPageContent.add(true);
1186 }
1187
1188 // Load any pages that are necessary for the current window of views
1189 loadAssociatedPages(mCurrentPage);
1190 mDirtyPageAlpha = true;
1191 requestLayout();
Winson Chung86f77532010-08-24 11:08:22 -07001192 }
Winson Chung321e9ee2010-08-09 13:37:56 -07001193 }
1194}