PagedView.java revision 321e9ee68848d9e782fd557f69cc070308ffbc9c
1/* 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 19import java.util.ArrayList; 20 21import android.content.Context; 22import android.graphics.Canvas; 23import android.graphics.Rect; 24import android.graphics.drawable.Drawable; 25import android.os.Parcel; 26import android.os.Parcelable; 27import android.util.AttributeSet; 28import android.util.Log; 29import android.view.MotionEvent; 30import android.view.VelocityTracker; 31import android.view.View; 32import android.view.ViewConfiguration; 33import android.view.ViewGroup; 34import android.view.ViewParent; 35import android.widget.Scroller; 36 37/** 38 * An abstraction of the original Workspace which supports browsing through a 39 * sequential list of "pages" (or PagedViewCellLayouts). 40 */ 41public abstract class PagedView extends ViewGroup { 42 private static final String TAG = "PagedView"; 43 private static final int INVALID_SCREEN = -1; 44 45 // the velocity at which a fling gesture will cause us to snap to the next screen 46 private static final int SNAP_VELOCITY = 500; 47 48 // the min drag distance for a fling to register, to prevent random screen shifts 49 private static final int MIN_LENGTH_FOR_FLING = 50; 50 51 private boolean mFirstLayout = true; 52 53 private int mCurrentScreen; 54 private int mNextScreen = INVALID_SCREEN; 55 private Scroller mScroller; 56 private VelocityTracker mVelocityTracker; 57 58 private float mDownMotionX; 59 private float mLastMotionX; 60 private float mLastMotionY; 61 62 private final static int TOUCH_STATE_REST = 0; 63 private final static int TOUCH_STATE_SCROLLING = 1; 64 private final static int TOUCH_STATE_PREV_PAGE = 2; 65 private final static int TOUCH_STATE_NEXT_PAGE = 3; 66 67 private int mTouchState = TOUCH_STATE_REST; 68 69 private OnLongClickListener mLongClickListener; 70 71 private boolean mAllowLongPress = true; 72 73 private int mTouchSlop; 74 private int mPagingTouchSlop; 75 private int mMaximumVelocity; 76 77 private static final int INVALID_POINTER = -1; 78 79 private int mActivePointerId = INVALID_POINTER; 80 81 private ScreenSwitchListener mScreenSwitchListener; 82 83 private boolean mDimmedPagesDirty; 84 85 public interface ScreenSwitchListener { 86 void onScreenSwitch(View newScreen, int newScreenIndex); 87 } 88 89 /** 90 * Constructor 91 * 92 * @param context The application's context. 93 */ 94 public PagedView(Context context) { 95 this(context, null); 96 } 97 98 public PagedView(Context context, AttributeSet attrs) { 99 this(context, attrs, 0); 100 } 101 102 public PagedView(Context context, AttributeSet attrs, int defStyle) { 103 super(context, attrs, defStyle); 104 105 setHapticFeedbackEnabled(false); 106 initWorkspace(); 107 } 108 109 /** 110 * Initializes various states for this workspace. 111 */ 112 private void initWorkspace() { 113 mScroller = new Scroller(getContext()); 114 mCurrentScreen = 0; 115 116 final ViewConfiguration configuration = ViewConfiguration.get(getContext()); 117 mTouchSlop = configuration.getScaledTouchSlop(); 118 mPagingTouchSlop = configuration.getScaledPagingTouchSlop(); 119 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); 120 } 121 122 public void setScreenSwitchListener(ScreenSwitchListener screenSwitchListener) { 123 mScreenSwitchListener = screenSwitchListener; 124 if (mScreenSwitchListener != null) { 125 mScreenSwitchListener.onScreenSwitch(getScreenAt(mCurrentScreen), mCurrentScreen); 126 } 127 } 128 129 /** 130 * Returns the index of the currently displayed screen. 131 * 132 * @return The index of the currently displayed screen. 133 */ 134 int getCurrentScreen() { 135 return mCurrentScreen; 136 } 137 138 int getScreenCount() { 139 return getChildCount(); 140 } 141 142 View getScreenAt(int index) { 143 return getChildAt(index); 144 } 145 146 int getScrollWidth() { 147 return getWidth(); 148 } 149 150 /** 151 * Sets the current screen. 152 * 153 * @param currentScreen 154 */ 155 void setCurrentScreen(int currentScreen) { 156 if (!mScroller.isFinished()) mScroller.abortAnimation(); 157 if (getChildCount() == 0) return; 158 159 mCurrentScreen = Math.max(0, Math.min(currentScreen, getScreenCount() - 1)); 160 scrollTo(getChildOffset(mCurrentScreen) - getRelativeChildOffset(mCurrentScreen), 0); 161 invalidate(); 162 notifyScreenSwitchListener(); 163 } 164 165 private void notifyScreenSwitchListener() { 166 if (mScreenSwitchListener != null) { 167 mScreenSwitchListener.onScreenSwitch(getScreenAt(mCurrentScreen), mCurrentScreen); 168 } 169 } 170 171 /** 172 * Registers the specified listener on each screen contained in this workspace. 173 * 174 * @param l The listener used to respond to long clicks. 175 */ 176 @Override 177 public void setOnLongClickListener(OnLongClickListener l) { 178 mLongClickListener = l; 179 final int count = getScreenCount(); 180 for (int i = 0; i < count; i++) { 181 getScreenAt(i).setOnLongClickListener(l); 182 } 183 } 184 185 @Override 186 public void computeScroll() { 187 if (mScroller.computeScrollOffset()) { 188 scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); 189 postInvalidate(); 190 } else if (mNextScreen != INVALID_SCREEN) { 191 mCurrentScreen = Math.max(0, Math.min(mNextScreen, getScreenCount() - 1)); 192 notifyScreenSwitchListener(); 193 mNextScreen = INVALID_SCREEN; 194 } 195 } 196 197 @Override 198 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 199 final int widthMode = MeasureSpec.getMode(widthMeasureSpec); 200 final int widthSize = MeasureSpec.getSize(widthMeasureSpec); 201 if (widthMode != MeasureSpec.EXACTLY) { 202 throw new IllegalStateException("Workspace can only be used in EXACTLY mode."); 203 } 204 205 final int heightMode = MeasureSpec.getMode(heightMeasureSpec); 206 final int heightSize = MeasureSpec.getSize(heightMeasureSpec); 207 if (heightMode != MeasureSpec.EXACTLY) { 208 throw new IllegalStateException("Workspace can only be used in EXACTLY mode."); 209 } 210 211 // The children are given the same width and height as the workspace 212 final int childCount = getChildCount(); 213 for (int i = 0; i < childCount; i++) { 214 getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec); 215 } 216 217 setMeasuredDimension(widthSize, heightSize); 218 219 if (mFirstLayout) { 220 setHorizontalScrollBarEnabled(false); 221 scrollTo(mCurrentScreen * widthSize, 0); 222 setHorizontalScrollBarEnabled(true); 223 mFirstLayout = false; 224 } 225 } 226 227 @Override 228 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 229 final int childCount = getChildCount(); 230 int childLeft = 0; 231 if (childCount > 0) { 232 childLeft = (getMeasuredWidth() - getChildAt(0).getMeasuredWidth()) / 2; 233 } 234 235 for (int i = 0; i < childCount; i++) { 236 final View child = getChildAt(i); 237 if (child.getVisibility() != View.GONE) { 238 final int childWidth = child.getMeasuredWidth(); 239 child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight()); 240 childLeft += childWidth; 241 } 242 } 243 } 244 245 protected void invalidateDimmedPages() { 246 mDimmedPagesDirty = true; 247 } 248 249 @Override 250 protected void dispatchDraw(Canvas canvas) { 251 if (mDimmedPagesDirty || (mTouchState == TOUCH_STATE_SCROLLING) || 252 !mScroller.isFinished()) { 253 int screenCenter = mScrollX + (getMeasuredWidth() / 2); 254 final int childCount = getChildCount(); 255 for (int i = 0; i < childCount; ++i) { 256 PagedViewCellLayout layout = (PagedViewCellLayout) getChildAt(i); 257 int childWidth = layout.getMeasuredWidth(); 258 int halfChildWidth = (childWidth / 2); 259 int childCenter = getChildOffset(i) + halfChildWidth; 260 int distanceFromScreenCenter = Math.abs(childCenter - screenCenter); 261 float dimAlpha = 0.0f; 262 if (distanceFromScreenCenter < halfChildWidth) { 263 dimAlpha = 0.0f; 264 } else if (distanceFromScreenCenter > childWidth) { 265 dimAlpha = 1.0f; 266 } else { 267 dimAlpha = (float) (distanceFromScreenCenter - halfChildWidth) / halfChildWidth; 268 dimAlpha = (dimAlpha * dimAlpha); 269 } 270 layout.setDimmedBitmapAlpha(Math.max(0.0f, Math.min(1.0f, dimAlpha))); 271 } 272 } 273 super.dispatchDraw(canvas); 274 } 275 276 @Override 277 public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) { 278 int screen = indexOfChild(child); 279 if (screen != mCurrentScreen || !mScroller.isFinished()) { 280 snapToScreen(screen); 281 return true; 282 } 283 return false; 284 } 285 286 @Override 287 protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { 288 int focusableScreen; 289 if (mNextScreen != INVALID_SCREEN) { 290 focusableScreen = mNextScreen; 291 } else { 292 focusableScreen = mCurrentScreen; 293 } 294 View v = getScreenAt(focusableScreen); 295 if (v != null) { 296 v.requestFocus(direction, previouslyFocusedRect); 297 } 298 return false; 299 } 300 301 @Override 302 public boolean dispatchUnhandledMove(View focused, int direction) { 303 if (direction == View.FOCUS_LEFT) { 304 if (getCurrentScreen() > 0) { 305 snapToScreen(getCurrentScreen() - 1); 306 return true; 307 } 308 } else if (direction == View.FOCUS_RIGHT) { 309 if (getCurrentScreen() < getScreenCount() - 1) { 310 snapToScreen(getCurrentScreen() + 1); 311 return true; 312 } 313 } 314 return super.dispatchUnhandledMove(focused, direction); 315 } 316 317 @Override 318 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { 319 if (mCurrentScreen >= 0 && mCurrentScreen < getScreenCount()) { 320 getScreenAt(mCurrentScreen).addFocusables(views, direction); 321 } 322 if (direction == View.FOCUS_LEFT) { 323 if (mCurrentScreen > 0) { 324 getScreenAt(mCurrentScreen - 1).addFocusables(views, direction); 325 } 326 } else if (direction == View.FOCUS_RIGHT){ 327 if (mCurrentScreen < getScreenCount() - 1) { 328 getScreenAt(mCurrentScreen + 1).addFocusables(views, direction); 329 } 330 } 331 } 332 333 /** 334 * If one of our descendant views decides that it could be focused now, only 335 * pass that along if it's on the current screen. 336 * 337 * This happens when live folders requery, and if they're off screen, they 338 * end up calling requestFocus, which pulls it on screen. 339 */ 340 @Override 341 public void focusableViewAvailable(View focused) { 342 View current = getScreenAt(mCurrentScreen); 343 View v = focused; 344 while (true) { 345 if (v == current) { 346 super.focusableViewAvailable(focused); 347 return; 348 } 349 if (v == this) { 350 return; 351 } 352 ViewParent parent = v.getParent(); 353 if (parent instanceof View) { 354 v = (View)v.getParent(); 355 } else { 356 return; 357 } 358 } 359 } 360 361 /** 362 * {@inheritDoc} 363 */ 364 @Override 365 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { 366 if (disallowIntercept) { 367 // We need to make sure to cancel our long press if 368 // a scrollable widget takes over touch events 369 final View currentScreen = getChildAt(mCurrentScreen); 370 currentScreen.cancelLongPress(); 371 } 372 super.requestDisallowInterceptTouchEvent(disallowIntercept); 373 } 374 375 @Override 376 public boolean onInterceptTouchEvent(MotionEvent ev) { 377 /* 378 * This method JUST determines whether we want to intercept the motion. 379 * If we return true, onTouchEvent will be called and we do the actual 380 * scrolling there. 381 */ 382 383 /* 384 * Shortcut the most recurring case: the user is in the dragging 385 * state and he is moving his finger. We want to intercept this 386 * motion. 387 */ 388 final int action = ev.getAction(); 389 if ((action == MotionEvent.ACTION_MOVE) && 390 (mTouchState == TOUCH_STATE_SCROLLING)) { 391 return true; 392 } 393 394 395 switch (action & MotionEvent.ACTION_MASK) { 396 case MotionEvent.ACTION_MOVE: { 397 /* 398 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check 399 * whether the user has moved far enough from his original down touch. 400 */ 401 determineScrollingStart(ev); 402 break; 403 } 404 405 case MotionEvent.ACTION_DOWN: { 406 final float x = ev.getX(); 407 final float y = ev.getY(); 408 // Remember location of down touch 409 mDownMotionX = x; 410 mLastMotionX = x; 411 mLastMotionY = y; 412 mActivePointerId = ev.getPointerId(0); 413 mAllowLongPress = true; 414 415 /* 416 * If being flinged and user touches the screen, initiate drag; 417 * otherwise don't. mScroller.isFinished should be false when 418 * being flinged. 419 */ 420 mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING; 421 422 // check if this can be the beginning of a tap on the side of the screens 423 // to scroll the current page 424 if ((mTouchState != TOUCH_STATE_PREV_PAGE) && 425 (mTouchState != TOUCH_STATE_NEXT_PAGE)) { 426 if (getChildCount() > 0) { 427 int relativeChildLeft = getChildOffset(0); 428 int relativeChildRight = relativeChildLeft + getChildAt(0).getMeasuredWidth(); 429 if (x < relativeChildLeft) { 430 mTouchState = TOUCH_STATE_PREV_PAGE; 431 } else if (x > relativeChildRight) { 432 mTouchState = TOUCH_STATE_NEXT_PAGE; 433 } 434 } 435 } 436 break; 437 } 438 439 case MotionEvent.ACTION_CANCEL: 440 case MotionEvent.ACTION_UP: 441 // Release the drag 442 mTouchState = TOUCH_STATE_REST; 443 mAllowLongPress = false; 444 mActivePointerId = INVALID_POINTER; 445 446 break; 447 448 case MotionEvent.ACTION_POINTER_UP: 449 onSecondaryPointerUp(ev); 450 break; 451 } 452 453 /* 454 * The only time we want to intercept motion events is if we are in the 455 * drag mode. 456 */ 457 return mTouchState != TOUCH_STATE_REST; 458 } 459 460 /* 461 * Determines if we should change the touch state to start scrolling after the 462 * user moves their touch point too far. 463 */ 464 private void determineScrollingStart(MotionEvent ev) { 465 /* 466 * Locally do absolute value. mLastMotionX is set to the y value 467 * of the down event. 468 */ 469 final int pointerIndex = ev.findPointerIndex(mActivePointerId); 470 final float x = ev.getX(pointerIndex); 471 final float y = ev.getY(pointerIndex); 472 final int xDiff = (int) Math.abs(x - mLastMotionX); 473 final int yDiff = (int) Math.abs(y - mLastMotionY); 474 475 final int touchSlop = mTouchSlop; 476 boolean xPaged = xDiff > mPagingTouchSlop; 477 boolean xMoved = xDiff > touchSlop; 478 boolean yMoved = yDiff > touchSlop; 479 480 if (xMoved || yMoved) { 481 if (xPaged) { 482 // Scroll if the user moved far enough along the X axis 483 mTouchState = TOUCH_STATE_SCROLLING; 484 mLastMotionX = x; 485 } 486 // Either way, cancel any pending longpress 487 if (mAllowLongPress) { 488 mAllowLongPress = false; 489 // Try canceling the long press. It could also have been scheduled 490 // by a distant descendant, so use the mAllowLongPress flag to block 491 // everything 492 final View currentScreen = getScreenAt(mCurrentScreen); 493 currentScreen.cancelLongPress(); 494 } 495 } 496 } 497 498 @Override 499 public boolean onTouchEvent(MotionEvent ev) { 500 if (mVelocityTracker == null) { 501 mVelocityTracker = VelocityTracker.obtain(); 502 } 503 mVelocityTracker.addMovement(ev); 504 505 final int action = ev.getAction(); 506 507 switch (action & MotionEvent.ACTION_MASK) { 508 case MotionEvent.ACTION_DOWN: 509 /* 510 * If being flinged and user touches, stop the fling. isFinished 511 * will be false if being flinged. 512 */ 513 if (!mScroller.isFinished()) { 514 mScroller.abortAnimation(); 515 } 516 517 // Remember where the motion event started 518 mDownMotionX = mLastMotionX = ev.getX(); 519 mActivePointerId = ev.getPointerId(0); 520 break; 521 522 case MotionEvent.ACTION_MOVE: 523 if (mTouchState == TOUCH_STATE_SCROLLING) { 524 // Scroll to follow the motion event 525 final int pointerIndex = ev.findPointerIndex(mActivePointerId); 526 final float x = ev.getX(pointerIndex); 527 final int deltaX = (int) (mLastMotionX - x); 528 mLastMotionX = x; 529 530 int sx = getScrollX(); 531 if (deltaX < 0) { 532 if (sx > 0) { 533 scrollBy(Math.max(-sx, deltaX), 0); 534 } 535 } else if (deltaX > 0) { 536 final int lastChildIndex = getChildCount() - 1; 537 final int availableToScroll = getChildOffset(lastChildIndex) - 538 getRelativeChildOffset(lastChildIndex) - sx; 539 if (availableToScroll > 0) { 540 scrollBy(Math.min(availableToScroll, deltaX), 0); 541 } 542 } else { 543 awakenScrollBars(); 544 } 545 } else if ((mTouchState == TOUCH_STATE_PREV_PAGE) || 546 (mTouchState == TOUCH_STATE_NEXT_PAGE)) { 547 determineScrollingStart(ev); 548 } 549 break; 550 551 case MotionEvent.ACTION_UP: 552 if (mTouchState == TOUCH_STATE_SCROLLING) { 553 final int activePointerId = mActivePointerId; 554 final int pointerIndex = ev.findPointerIndex(activePointerId); 555 final float x = ev.getX(pointerIndex); 556 final VelocityTracker velocityTracker = mVelocityTracker; 557 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 558 int velocityX = (int) velocityTracker.getXVelocity(activePointerId); 559 boolean isfling = Math.abs(mDownMotionX - x) > MIN_LENGTH_FOR_FLING; 560 561 if (isfling && velocityX > SNAP_VELOCITY && mCurrentScreen > 0) { 562 snapToScreen(mCurrentScreen - 1); 563 } else if (isfling && velocityX < -SNAP_VELOCITY && 564 mCurrentScreen < getChildCount() - 1) { 565 snapToScreen(mCurrentScreen + 1); 566 } else { 567 snapToDestination(); 568 } 569 570 if (mVelocityTracker != null) { 571 mVelocityTracker.recycle(); 572 mVelocityTracker = null; 573 } 574 } else if (mTouchState == TOUCH_STATE_PREV_PAGE) { 575 // at this point we have not moved beyond the touch slop 576 // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so 577 // we can just page 578 int nextScreen = Math.max(0, mCurrentScreen - 1); 579 if (nextScreen != mCurrentScreen) { 580 snapToScreen(nextScreen); 581 } else { 582 snapToDestination(); 583 } 584 } else if (mTouchState == TOUCH_STATE_NEXT_PAGE) { 585 // at this point we have not moved beyond the touch slop 586 // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so 587 // we can just page 588 int nextScreen = Math.min(getChildCount() - 1, mCurrentScreen + 1); 589 if (nextScreen != mCurrentScreen) { 590 snapToScreen(nextScreen); 591 } else { 592 snapToDestination(); 593 } 594 } 595 mTouchState = TOUCH_STATE_REST; 596 mActivePointerId = INVALID_POINTER; 597 break; 598 599 case MotionEvent.ACTION_CANCEL: 600 mTouchState = TOUCH_STATE_REST; 601 mActivePointerId = INVALID_POINTER; 602 break; 603 604 case MotionEvent.ACTION_POINTER_UP: 605 onSecondaryPointerUp(ev); 606 break; 607 } 608 609 return true; 610 } 611 612 private void onSecondaryPointerUp(MotionEvent ev) { 613 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> 614 MotionEvent.ACTION_POINTER_INDEX_SHIFT; 615 final int pointerId = ev.getPointerId(pointerIndex); 616 if (pointerId == mActivePointerId) { 617 // This was our active pointer going up. Choose a new 618 // active pointer and adjust accordingly. 619 // TODO: Make this decision more intelligent. 620 final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 621 mLastMotionX = mDownMotionX = ev.getX(newPointerIndex); 622 mLastMotionY = ev.getY(newPointerIndex); 623 mActivePointerId = ev.getPointerId(newPointerIndex); 624 if (mVelocityTracker != null) { 625 mVelocityTracker.clear(); 626 } 627 } 628 } 629 630 @Override 631 public void requestChildFocus(View child, View focused) { 632 super.requestChildFocus(child, focused); 633 int screen = indexOfChild(child); 634 if (screen >= 0 && !isInTouchMode()) { 635 snapToScreen(screen); 636 } 637 } 638 639 protected int getRelativeChildOffset(int index) { 640 return (getMeasuredWidth() - getChildAt(index).getMeasuredWidth()) / 2; 641 } 642 643 protected int getChildOffset(int index) { 644 if (getChildCount() == 0) 645 return 0; 646 647 int offset = getRelativeChildOffset(0); 648 for (int i = 0; i < index; ++i) { 649 offset += getChildAt(i).getMeasuredWidth(); 650 } 651 return offset; 652 } 653 654 protected void snapToDestination() { 655 int minDistanceFromScreenCenter = getMeasuredWidth(); 656 int minDistanceFromScreenCenterIndex = -1; 657 int screenCenter = mScrollX + (getMeasuredWidth() / 2); 658 final int childCount = getChildCount(); 659 for (int i = 0; i < childCount; ++i) { 660 PagedViewCellLayout layout = (PagedViewCellLayout) getChildAt(i); 661 int childWidth = layout.getMeasuredWidth(); 662 int halfChildWidth = (childWidth / 2); 663 int childCenter = getChildOffset(i) + halfChildWidth; 664 int distanceFromScreenCenter = Math.abs(childCenter - screenCenter); 665 if (distanceFromScreenCenter < minDistanceFromScreenCenter) { 666 minDistanceFromScreenCenter = distanceFromScreenCenter; 667 minDistanceFromScreenCenterIndex = i; 668 } 669 } 670 snapToScreen(minDistanceFromScreenCenterIndex, 1000); 671 } 672 673 void snapToScreen(int whichScreen) { 674 snapToScreen(whichScreen, 1000); 675 } 676 677 void snapToScreen(int whichScreen, int duration) { 678 whichScreen = Math.max(0, Math.min(whichScreen, getScreenCount() - 1)); 679 680 mNextScreen = whichScreen; 681 682 int newX = getChildOffset(whichScreen) - getRelativeChildOffset(whichScreen); 683 final int sX = getScrollX(); 684 final int delta = newX - sX; 685 awakenScrollBars(duration); 686 if (duration == 0) { 687 duration = Math.abs(delta); 688 } 689 690 if (!mScroller.isFinished()) mScroller.abortAnimation(); 691 mScroller.startScroll(sX, 0, delta, 0, duration); 692 invalidate(); 693 } 694 695 @Override 696 protected Parcelable onSaveInstanceState() { 697 final SavedState state = new SavedState(super.onSaveInstanceState()); 698 state.currentScreen = mCurrentScreen; 699 return state; 700 } 701 702 @Override 703 protected void onRestoreInstanceState(Parcelable state) { 704 SavedState savedState = (SavedState) state; 705 super.onRestoreInstanceState(savedState.getSuperState()); 706 if (savedState.currentScreen != -1) { 707 mCurrentScreen = savedState.currentScreen; 708 } 709 } 710 711 public void scrollLeft() { 712 if (mScroller.isFinished()) { 713 if (mCurrentScreen > 0) snapToScreen(mCurrentScreen - 1); 714 } else { 715 if (mNextScreen > 0) snapToScreen(mNextScreen - 1); 716 } 717 } 718 719 public void scrollRight() { 720 if (mScroller.isFinished()) { 721 if (mCurrentScreen < getChildCount() -1) snapToScreen(mCurrentScreen + 1); 722 } else { 723 if (mNextScreen < getChildCount() -1) snapToScreen(mNextScreen + 1); 724 } 725 } 726 727 public int getScreenForView(View v) { 728 int result = -1; 729 if (v != null) { 730 ViewParent vp = v.getParent(); 731 int count = getChildCount(); 732 for (int i = 0; i < count; i++) { 733 if (vp == getChildAt(i)) { 734 return i; 735 } 736 } 737 } 738 return result; 739 } 740 741 /** 742 * @return True is long presses are still allowed for the current touch 743 */ 744 public boolean allowLongPress() { 745 return mAllowLongPress; 746 } 747 748 public static class SavedState extends BaseSavedState { 749 int currentScreen = -1; 750 751 SavedState(Parcelable superState) { 752 super(superState); 753 } 754 755 private SavedState(Parcel in) { 756 super(in); 757 currentScreen = in.readInt(); 758 } 759 760 @Override 761 public void writeToParcel(Parcel out, int flags) { 762 super.writeToParcel(out, flags); 763 out.writeInt(currentScreen); 764 } 765 766 public static final Parcelable.Creator<SavedState> CREATOR = 767 new Parcelable.Creator<SavedState>() { 768 public SavedState createFromParcel(Parcel in) { 769 return new SavedState(in); 770 } 771 772 public SavedState[] newArray(int size) { 773 return new SavedState[size]; 774 } 775 }; 776 } 777 778 public abstract void syncPages(); 779 public abstract void syncPageItems(int page); 780 public void invalidatePageData() { 781 syncPages(); 782 for (int i = 0; i < getChildCount(); ++i) { 783 syncPageItems(i); 784 } 785 invalidateDimmedPages(); 786 requestLayout(); 787 } 788} 789