1 package com.folioreader.view;
\r
4 * Created by mobisys on 10/10/2016.
\r
8 import android.content.Context;
\r
9 import android.content.res.Resources;
\r
10 import android.content.res.TypedArray;
\r
11 import android.database.DataSetObserver;
\r
12 import android.graphics.Canvas;
\r
13 import android.graphics.Rect;
\r
14 import android.graphics.drawable.Drawable;
\r
15 import android.os.Build;
\r
16 import android.os.Bundle;
\r
17 import android.os.Parcel;
\r
18 import android.os.Parcelable;
\r
19 import android.os.SystemClock;
\r
20 import android.support.annotation.CallSuper;
\r
21 import android.support.annotation.DrawableRes;
\r
22 import android.support.v4.os.ParcelableCompat;
\r
23 import android.support.v4.os.ParcelableCompatCreatorCallbacks;
\r
24 import android.support.v4.view.AccessibilityDelegateCompat;
\r
25 import android.support.v4.view.MotionEventCompat;
\r
26 import android.support.v4.view.PagerAdapter;
\r
27 import android.support.v4.view.VelocityTrackerCompat;
\r
28 import android.support.v4.view.ViewCompat;
\r
29 import android.support.v4.view.ViewConfigurationCompat;
\r
30 import android.support.v4.view.WindowInsetsCompat;
\r
31 import android.support.v4.view.accessibility.AccessibilityEventCompat;
\r
32 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
\r
33 import android.support.v4.view.accessibility.AccessibilityRecordCompat;
\r
34 import android.support.v4.widget.EdgeEffectCompat;
\r
35 import android.util.AttributeSet;
\r
36 import android.util.Log;
\r
37 import android.view.FocusFinder;
\r
38 import android.view.Gravity;
\r
39 import android.view.KeyEvent;
\r
40 import android.view.MotionEvent;
\r
41 import android.view.SoundEffectConstants;
\r
42 import android.view.VelocityTracker;
\r
43 import android.view.View;
\r
44 import android.view.ViewConfiguration;
\r
45 import android.view.ViewGroup;
\r
46 import android.view.ViewParent;
\r
47 import android.view.accessibility.AccessibilityEvent;
\r
48 import android.view.animation.Interpolator;
\r
49 import android.widget.Scroller;
\r
51 import com.folioreader.R;
\r
53 import java.lang.reflect.Method;
\r
54 import java.util.ArrayList;
\r
55 import java.util.Collections;
\r
56 import java.util.Comparator;
\r
57 import java.util.List;
\r
59 public class DirectionalViewpager extends ViewGroup {
\r
60 private static final String TAG = "ViewPager";
\r
61 private static final boolean DEBUG = false;
\r
63 private static final boolean USE_CACHE = false;
\r
65 private static final int DEFAULT_OFFSCREEN_PAGES = 1;
\r
66 private static final int MAX_SETTLE_DURATION = 600; // ms
\r
67 private static final int MIN_DISTANCE_FOR_FLING = 25; // dips
\r
69 private static final int DEFAULT_GUTTER_SIZE = 16; // dips
\r
71 private static final int MIN_FLING_VELOCITY = 400; // dips
\r
73 private static final int[] LAYOUT_ATTRS = new int[]{
\r
74 android.R.attr.layout_gravity
\r
77 public static enum Direction {
\r
83 * Used to track what the expected number of items in the adapter should be.
\r
84 * If the app changes this when we don't expect it, we'll throw a big obnoxious exception.
\r
86 private int mExpectedAdapterCount;
\r
87 public String mDirection = Direction.VERTICAL.name();
\r
89 static class ItemInfo {
\r
98 private static final Comparator<ItemInfo> COMPARATOR = new Comparator<ItemInfo>() {
\r
100 public int compare(ItemInfo lhs, ItemInfo rhs) {
\r
101 return lhs.position - rhs.position;
\r
105 private static final Interpolator sInterpolator = new Interpolator() {
\r
106 public float getInterpolation(float t) {
\r
108 return t * t * t * t * t + 1.0f;
\r
112 private final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>();
\r
113 private final ItemInfo mTempItem = new ItemInfo();
\r
115 private final Rect mTempRect = new Rect();
\r
117 private PagerAdapter mAdapter;
\r
118 private int mCurItem; // Index of currently displayed page.
\r
119 private int mRestoredCurItem = -1;
\r
120 private Parcelable mRestoredAdapterState = null;
\r
121 private ClassLoader mRestoredClassLoader = null;
\r
123 private Scroller mScroller;
\r
124 private boolean mIsScrollStarted;
\r
126 private PagerObserver mObserver;
\r
128 private int mPageMargin;
\r
129 private Drawable mMarginDrawable;
\r
130 private int mTopPageBounds;
\r
131 private int mBottomPageBounds;
\r
132 private int mLeftPageBounds;
\r
133 private int mRightPageBounds;
\r
135 // Offsets of the first and last items, if known.
\r
136 // Set during population, used to determine if we are at the beginning
\r
137 // or end of the pager data set during touch scrolling.
\r
138 private float mFirstOffset = -Float.MAX_VALUE;
\r
139 private float mLastOffset = Float.MAX_VALUE;
\r
141 private int mChildWidthMeasureSpec;
\r
142 private int mChildHeightMeasureSpec;
\r
143 private boolean mInLayout;
\r
145 private boolean mScrollingCacheEnabled;
\r
147 private boolean mPopulatePending;
\r
148 private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES;
\r
150 private boolean mIsBeingDragged;
\r
151 private boolean mIsUnableToDrag;
\r
152 private boolean mIgnoreGutter;
\r
153 private int mDefaultGutterSize;
\r
154 private int mGutterSize;
\r
155 private int mTouchSlop;
\r
157 * Position of the last motion event.
\r
159 private float mLastMotionX;
\r
160 private float mLastMotionY;
\r
161 private float mInitialMotionX;
\r
162 private float mInitialMotionY;
\r
164 * ID of the active pointer. This is used to retain consistency during
\r
165 * drags/flings if multiple pointers are used.
\r
167 private int mActivePointerId = INVALID_POINTER;
\r
169 * Sentinel value for no current active pointer.
\r
170 * Used by {@link #mActivePointerId}.
\r
172 private static final int INVALID_POINTER = -1;
\r
175 * Determines speed during touch scrolling
\r
177 private VelocityTracker mVelocityTracker;
\r
178 private int mMinimumVelocity;
\r
179 private int mMaximumVelocity;
\r
180 private int mFlingDistance;
\r
181 private int mCloseEnough;
\r
183 // If the pager is at least this close to its final position, complete the scroll
\r
184 // on touch down and let the user interact with the content inside instead of
\r
185 // "catching" the flinging pager.
\r
186 private static final int CLOSE_ENOUGH = 2; // dp
\r
188 private boolean mFakeDragging;
\r
189 private long mFakeDragBeginTime;
\r
191 private EdgeEffectCompat mLeftEdge;
\r
192 private EdgeEffectCompat mRightEdge;
\r
193 private EdgeEffectCompat mTopEdge;
\r
194 private EdgeEffectCompat mBottomEdge;
\r
196 private boolean mFirstLayout = true;
\r
197 private boolean mNeedCalculatePageOffsets = false;
\r
198 private boolean mCalledSuper;
\r
199 private int mDecorChildCount;
\r
201 private List<OnPageChangeListener> mOnPageChangeListeners;
\r
202 private OnPageChangeListener mOnPageChangeListener;
\r
203 private OnPageChangeListener mInternalPageChangeListener;
\r
204 private OnAdapterChangeListener mAdapterChangeListener;
\r
205 private PageTransformer mPageTransformer;
\r
206 private Method mSetChildrenDrawingOrderEnabled;
\r
208 private static final int DRAW_ORDER_DEFAULT = 0;
\r
209 private static final int DRAW_ORDER_FORWARD = 1;
\r
210 private static final int DRAW_ORDER_REVERSE = 2;
\r
211 private int mDrawingOrder;
\r
212 private ArrayList<View> mDrawingOrderedChildren;
\r
213 private static final ViewPositionComparator sPositionComparator = new ViewPositionComparator();
\r
216 * Indicates that the pager is in an idle, settled state. The current page
\r
217 * is fully in view and no animation is in progress.
\r
219 public static final int SCROLL_STATE_IDLE = 0;
\r
222 * Indicates that the pager is currently being dragged by the user.
\r
224 public static final int SCROLL_STATE_DRAGGING = 1;
\r
227 * Indicates that the pager is in the process of settling to a final position.
\r
229 public static final int SCROLL_STATE_SETTLING = 2;
\r
231 private final Runnable mEndScrollRunnable = new Runnable() {
\r
232 public void run() {
\r
233 setScrollState(SCROLL_STATE_IDLE);
\r
238 private int mScrollState = SCROLL_STATE_IDLE;
\r
241 * Callback interface for responding to changing state of the selected page.
\r
243 public interface OnPageChangeListener {
\r
246 * This method will be invoked when the current
\r
247 * page is scrolled, either as part
\r
248 * of a programmatically initiated
\r
249 * smooth scroll or a user initiated touch scroll.
\r
251 * @param position Position index of the first page currently being displayed.
\r
253 * Page position+1 will be visible if positionOffset is nonzero.
\r
254 * @param positionOffset
\r
255 * Value from [0, 1) indicating the offset from the page at position.
\r
256 * @param positionOffsetPixels
\r
257 * Value in pixels indicating the offset from position.
\r
259 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels);
\r
262 * This method will be invoked when a new page becomes selected. Animation is not
\r
263 * necessarily complete.
\r
265 * @param position Position index of the new selected page.
\r
267 public void onPageSelected(int position);
\r
270 * Called when the scroll state changes. Useful for discovering when the user
\r
271 * begins dragging, when the pager is automatically settling to the current page,
\r
272 * or when it is fully stopped/idle.
\r
274 * @param state The new scroll state.
\r
275 * @see ViewPager#SCROLL_STATE_IDLE
\r
276 * @see ViewPager#SCROLL_STATE_DRAGGING
\r
277 * @see ViewPager#SCROLL_STATE_SETTLING
\r
279 public void onPageScrollStateChanged(int state);
\r
283 * Simple implementation of the {@link OnPageChangeListener}
\r
284 * interface with stub
\r
285 * implementations of each method.
\r
286 * Extend this if you do not intend to override
\r
287 * every method of {@link OnPageChangeListener}.
\r
289 public static class SimpleOnPageChangeListener implements OnPageChangeListener {
\r
291 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
\r
292 // This space for rent
\r
296 public void onPageSelected(int position) {
\r
297 // This space for rent
\r
301 public void onPageScrollStateChanged(int state) {
\r
302 // This space for rent
\r
307 * A PageTransformer is invoked whenever a visible/attached page is scrolled.
\r
308 * This offers an opportunity for the application to apply a custom transformation
\r
309 * to the page views using animation properties.
\r
311 * <p>As property animation is only supported as of Android 3.0 and forward,
\r
312 * setting a PageTransformer on a ViewPager on earlier platform versions will
\r
315 public interface PageTransformer {
\r
317 * Apply a property transformation to the given page.
\r
319 * @param page Apply the transformation to this page
\r
320 * @param position Position of page relative to the current front-and-center
\r
321 * position of the pager. 0 is front and center. 1 is one full
\r
322 * page position to the right, and -1 is one page position to the left.
\r
324 public void transformPage(View page, float position);
\r
328 * Used internally to monitor when adapters are switched.
\r
330 interface OnAdapterChangeListener {
\r
331 public void onAdapterChanged(PagerAdapter oldAdapter, PagerAdapter newAdapter);
\r
335 * Used internally to tag special types of child views that should be added as
\r
336 * pager decorations by default.
\r
341 public DirectionalViewpager(Context context) {
\r
346 public DirectionalViewpager(Context context, AttributeSet attrs) {
\r
347 super(context, attrs);
\r
348 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DirectionalViewpager);
\r
349 if (a.getString(R.styleable.DirectionalViewpager_direction) != null) {
\r
350 mDirection = a.getString(R.styleable.DirectionalViewpager_direction);
\r
355 void initViewPager() {
\r
356 setWillNotDraw(false);
\r
357 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
\r
358 setFocusable(true);
\r
359 final Context context = getContext();
\r
360 mScroller = new Scroller(context, sInterpolator);
\r
361 final ViewConfiguration configuration = ViewConfiguration.get(context);
\r
362 final float density = context.getResources().getDisplayMetrics().density;
\r
364 mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
\r
365 mMinimumVelocity = (int) (MIN_FLING_VELOCITY * density);
\r
366 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
\r
367 mLeftEdge = new EdgeEffectCompat(context);
\r
368 mRightEdge = new EdgeEffectCompat(context);
\r
369 mTopEdge = new EdgeEffectCompat(context);
\r
370 mBottomEdge = new EdgeEffectCompat(context);
\r
372 mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density);
\r
373 mCloseEnough = (int) (CLOSE_ENOUGH * density);
\r
374 mDefaultGutterSize = (int) (DEFAULT_GUTTER_SIZE * density);
\r
376 ViewCompat.setAccessibilityDelegate(this, new MyAccessibilityDelegate());
\r
378 if (ViewCompat.getImportantForAccessibility(this)
\r
379 == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
\r
380 ViewCompat.setImportantForAccessibility(this,
\r
381 ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
\r
384 ViewCompat.setOnApplyWindowInsetsListener(this,
\r
385 new android.support
\r
386 .v4.view.OnApplyWindowInsetsListener() {
\r
387 private final Rect mTempRect = new Rect();
\r
390 public WindowInsetsCompat
\r
391 onApplyWindowInsets(final View v,
\r
392 final WindowInsetsCompat originalInsets) {
\r
393 // First let the ViewPager itself try and consume them...
\r
394 final WindowInsetsCompat applied =
\r
395 ViewCompat.onApplyWindowInsets(v, originalInsets);
\r
396 if (applied.isConsumed()) {
\r
397 // If the ViewPager consumed all insets, return now
\r
401 // Now we'll manually dispatch the insets to our children. Since ViewPager
\r
402 // children are always full-height, we do not want to use the standard
\r
403 // ViewGroup dispatchApplyWindowInsets since if child 0 consumes them,
\r
404 // the rest of the children will not receive any insets. To workaround this
\r
405 // we manually dispatch the applied insets, not allowing children to
\r
406 // consume them from each other. We do however keep track of any insets
\r
407 // which are consumed, returning the union of our children's consumption
\r
408 final Rect res = mTempRect;
\r
409 res.left = applied.getSystemWindowInsetLeft();
\r
410 res.top = applied.getSystemWindowInsetTop();
\r
411 res.right = applied.getSystemWindowInsetRight();
\r
412 res.bottom = applied.getSystemWindowInsetBottom();
\r
414 for (int i = 0, count = getChildCount(); i < count; i++) {
\r
415 final WindowInsetsCompat childInsets = ViewCompat
\r
416 .dispatchApplyWindowInsets(getChildAt(i), applied);
\r
417 // Now keep track of any consumed by tracking each dimension's min
\r
420 = Math.min(childInsets.getSystemWindowInsetLeft(),
\r
422 res.top = Math.min(childInsets.getSystemWindowInsetTop(),
\r
424 res.right = Math.min(childInsets.getSystemWindowInsetRight(),
\r
426 res.bottom = Math.min(childInsets.getSystemWindowInsetBottom(),
\r
430 // Now return a new WindowInsets, using the consumed window insets
\r
431 return applied.replaceSystemWindowInsets(
\r
432 res.left, res.top, res.right, res.bottom);
\r
438 protected void onDetachedFromWindow() {
\r
439 removeCallbacks(mEndScrollRunnable);
\r
440 // To be on the safe side, abort the scroller
\r
441 if ((mScroller != null) && !mScroller.isFinished()) {
\r
442 mScroller.abortAnimation();
\r
444 super.onDetachedFromWindow();
\r
447 private void setScrollState(int newState) {
\r
448 if (mScrollState == newState) {
\r
452 mScrollState = newState;
\r
453 if (mPageTransformer != null) {
\r
454 // PageTransformers can do complex things that benefit from hardware layers.
\r
455 enableLayers(newState != SCROLL_STATE_IDLE);
\r
457 dispatchOnScrollStateChanged(newState);
\r
461 * Set a PagerAdapter that will supply views for this pager as needed.
\r
463 * @param adapter Adapter to use
\r
465 public void setAdapter(PagerAdapter adapter) {
\r
466 if (mAdapter != null) {
\r
467 mAdapter.unregisterDataSetObserver(mObserver);
\r
468 mAdapter.startUpdate(this);
\r
469 for (int i = 0; i < mItems.size(); i++) {
\r
470 final ItemInfo ii = mItems.get(i);
\r
471 mAdapter.destroyItem(this, ii.position, ii.object);
\r
473 mAdapter.finishUpdate(this);
\r
475 removeNonDecorViews();
\r
480 final PagerAdapter oldAdapter = mAdapter;
\r
481 mAdapter = adapter;
\r
482 mExpectedAdapterCount = 0;
\r
484 if (mAdapter != null) {
\r
485 if (mObserver == null) {
\r
486 mObserver = new PagerObserver();
\r
488 mAdapter.registerDataSetObserver(mObserver);
\r
489 mPopulatePending = false;
\r
490 final boolean wasFirstLayout = mFirstLayout;
\r
491 mFirstLayout = true;
\r
492 mExpectedAdapterCount = mAdapter.getCount();
\r
493 if (mRestoredCurItem >= 0) {
\r
494 mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader);
\r
495 setCurrentItemInternal(mRestoredCurItem, false, true);
\r
496 mRestoredCurItem = -1;
\r
497 mRestoredAdapterState = null;
\r
498 mRestoredClassLoader = null;
\r
499 } else if (!wasFirstLayout) {
\r
506 if (mAdapterChangeListener != null && oldAdapter != adapter) {
\r
507 mAdapterChangeListener.onAdapterChanged(oldAdapter, adapter);
\r
511 private void removeNonDecorViews() {
\r
512 for (int i = 0; i < getChildCount(); i++) {
\r
513 final View child = getChildAt(i);
\r
514 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
\r
523 * Retrieve the current adapter supplying pages.
\r
525 * @return The currently registered PagerAdapter
\r
527 public PagerAdapter getAdapter() {
\r
531 void setOnAdapterChangeListener(OnAdapterChangeListener listener) {
\r
532 mAdapterChangeListener = listener;
\r
535 private int getClientWidth() {
\r
536 return getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
\r
539 private int getClientHeight() {
\r
540 return getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
\r
544 * Set the currently selected page. If the ViewPager has already been through its first
\r
545 * layout with its current adapter there will be a smooth animated transition between
\r
546 * the current item and the specified item.
\r
548 * @param item Item index to select
\r
550 public void setCurrentItem(int item) {
\r
551 mPopulatePending = false;
\r
552 setCurrentItemInternal(item, !mFirstLayout, false);
\r
556 * Set the currently selected page.
\r
558 * @param item Item index to select
\r
559 * @param smoothScroll True to smoothly scroll to the new item, false to transition immediately
\r
561 public void setCurrentItem(int item, boolean smoothScroll) {
\r
562 mPopulatePending = false;
\r
563 setCurrentItemInternal(item, smoothScroll, false);
\r
566 public int getCurrentItem() {
\r
570 public int getExpectedAdapterCount() {
\r
571 return mExpectedAdapterCount;
\r
574 void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) {
\r
575 setCurrentItemInternal(item, smoothScroll, always, 0);
\r
578 void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) {
\r
579 if (mAdapter == null || mAdapter.getCount() <= 0) {
\r
580 setScrollingCacheEnabled(false);
\r
583 if (!always && mCurItem == item && mItems.size() != 0) {
\r
584 setScrollingCacheEnabled(false);
\r
590 } else if (item >= mAdapter.getCount()) {
\r
591 item = mAdapter.getCount() - 1;
\r
593 final int pageLimit = mOffscreenPageLimit;
\r
594 if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) {
\r
595 // We are doing a jump by more than one page. To avoid
\r
596 // glitches, we want to keep all current pages in the view
\r
597 // until the scroll ends.
\r
598 for (int i = 0; i < mItems.size(); i++) {
\r
599 mItems.get(i).scrolling = true;
\r
602 final boolean dispatchSelected = mCurItem != item;
\r
604 if (mFirstLayout) {
\r
605 // We don't have any idea how big we are yet and shouldn't have any pages either.
\r
606 // Just set things up and let the pending layout handle things.
\r
608 if (dispatchSelected) {
\r
609 dispatchOnPageSelected(item);
\r
614 scrollToItem(item, smoothScroll, velocity, dispatchSelected);
\r
618 private void scrollToItem(int item, boolean smoothScroll, int velocity,
\r
619 boolean dispatchSelected) {
\r
620 final ItemInfo curInfo = infoForPosition(item);
\r
623 if (isHorizontal()) {
\r
624 if (curInfo != null) {
\r
625 final int width = getClientWidth();
\r
626 destX = (int) (width * Math.max(mFirstOffset,
\r
627 Math.min(curInfo.offset, mLastOffset)));
\r
629 if (smoothScroll) {
\r
630 smoothScrollTo(destX, 0, velocity);
\r
631 if (dispatchSelected) {
\r
632 dispatchOnPageSelected(item);
\r
635 if (dispatchSelected) {
\r
636 dispatchOnPageSelected(item);
\r
638 completeScroll(false);
\r
639 scrollTo(destX, 0);
\r
640 pageScrolled(destX, 0);
\r
643 if (curInfo != null) {
\r
644 final int height = getClientHeight();
\r
645 destY = (int) (height * Math.max(mFirstOffset,
\r
646 Math.min(curInfo.offset, mLastOffset)));
\r
648 if (smoothScroll) {
\r
649 smoothScrollTo(0, destY, velocity);
\r
650 if (dispatchSelected && mOnPageChangeListener != null) {
\r
651 mOnPageChangeListener.onPageSelected(item);
\r
653 if (dispatchSelected && mInternalPageChangeListener != null) {
\r
654 mInternalPageChangeListener.onPageSelected(item);
\r
657 if (dispatchSelected && mOnPageChangeListener != null) {
\r
658 mOnPageChangeListener.onPageSelected(item);
\r
660 if (dispatchSelected && mInternalPageChangeListener != null) {
\r
661 mInternalPageChangeListener.onPageSelected(item);
\r
663 completeScroll(false);
\r
664 scrollTo(0, destY);
\r
665 pageScrolled(0, destY);
\r
671 * Set a listener that will be invoked whenever the page changes or is incrementally
\r
672 * scrolled. See {@link OnPageChangeListener}.
\r
674 * @param listener Listener to set
\r
675 * @deprecated Use {@link #addOnPageChangeListener(OnPageChangeListener)}
\r
676 * and {@link #removeOnPageChangeListener(OnPageChangeListener)} instead.
\r
679 public void setOnPageChangeListener(OnPageChangeListener listener) {
\r
680 mOnPageChangeListener = listener;
\r
684 * Add a listener that will be invoked whenever the page changes or is incrementally
\r
685 * scrolled. See {@link OnPageChangeListener}.
\r
687 * <p>Components that add a listener should take care to remove it when finished.
\r
688 * Other components that take ownership of a view may call {@link #clearOnPageChangeListeners()}
\r
689 * to remove all attached listeners.</p>
\r
691 * @param listener listener to add
\r
693 public void addOnPageChangeListener(OnPageChangeListener listener) {
\r
694 if (mOnPageChangeListeners == null) {
\r
695 mOnPageChangeListeners = new ArrayList<>();
\r
697 mOnPageChangeListeners.add(listener);
\r
701 * Remove a listener that was previously added via
\r
702 * {@link #addOnPageChangeListener(OnPageChangeListener)}.
\r
704 * @param listener listener to remove
\r
706 public void removeOnPageChangeListener(OnPageChangeListener listener) {
\r
707 if (mOnPageChangeListeners != null) {
\r
708 mOnPageChangeListeners.remove(listener);
\r
713 * Remove all listeners that are notified of any changes in scroll state or position.
\r
715 public void clearOnPageChangeListeners() {
\r
716 if (mOnPageChangeListeners != null) {
\r
717 mOnPageChangeListeners.clear();
\r
722 * Set a {@link PageTransformer} that will be called for each attached page whenever
\r
723 * the scroll position is changed. This allows the application to apply custom property
\r
724 * transformations to each page, overriding the default sliding look and feel.
\r
726 * <p><em>Note:</em> Prior to Android 3.0 the property animation APIs did not exist.
\r
727 * As a result, setting a PageTransformer prior to Android 3.0 (API 11) will have no effect.</p>
\r
729 * @param reverseDrawingOrder true if the supplied PageTransformer requires page views
\r
730 * to be drawn from last to first instead of first to last.
\r
731 * @param transformer PageTransformer that will modify each page's animation properties
\r
733 public void setPageTransformer(boolean reverseDrawingOrder, PageTransformer transformer) {
\r
734 if (Build.VERSION.SDK_INT >= 11) {
\r
735 final boolean hasTransformer = transformer != null;
\r
736 final boolean needsPopulate = hasTransformer != (mPageTransformer != null);
\r
737 mPageTransformer = transformer;
\r
738 setChildrenDrawingOrderEnabledCompat(hasTransformer);
\r
739 if (hasTransformer) {
\r
740 mDrawingOrder = reverseDrawingOrder ? DRAW_ORDER_REVERSE : DRAW_ORDER_FORWARD;
\r
742 mDrawingOrder = DRAW_ORDER_DEFAULT;
\r
744 if (needsPopulate) populate();
\r
748 void setChildrenDrawingOrderEnabledCompat(boolean enable) {
\r
749 if (Build.VERSION.SDK_INT >= 7) {
\r
750 if (mSetChildrenDrawingOrderEnabled == null) {
\r
752 mSetChildrenDrawingOrderEnabled = ViewGroup.class.getDeclaredMethod(
\r
753 "setChildrenDrawingOrderEnabled", new Class[]{Boolean.TYPE});
\r
754 } catch (NoSuchMethodException e) {
\r
755 Log.e(TAG, "Can't find setChildrenDrawingOrderEnabled", e);
\r
759 mSetChildrenDrawingOrderEnabled
\r
760 .invoke(this, enable);
\r
761 } catch (Exception e) {
\r
762 Log.e(TAG, "Error changing children drawing order", e);
\r
768 protected int getChildDrawingOrder(int childCount, int i) {
\r
769 final int index = mDrawingOrder
\r
770 == DRAW_ORDER_REVERSE ? childCount - 1 - i : i;
\r
772 = ((LayoutParams) mDrawingOrderedChildren.get(index).getLayoutParams()).childIndex;
\r
777 * Set a separate OnPageChangeListener for internal use by the support library.
\r
779 * @param listener Listener to set
\r
780 * @return The old listener that was set, if any.
\r
782 OnPageChangeListener setInternalPageChangeListener(OnPageChangeListener listener) {
\r
783 OnPageChangeListener oldListener = mInternalPageChangeListener;
\r
784 mInternalPageChangeListener = listener;
\r
785 return oldListener;
\r
789 * Returns the number of pages that will be retained to either side of the
\r
790 * current page in the view hierarchy in an idle state. Defaults to 1.
\r
792 * @return How many pages will be kept offscreen on either side
\r
793 * @see #setOffscreenPageLimit(int)
\r
795 public int getOffscreenPageLimit() {
\r
796 return mOffscreenPageLimit;
\r
800 * Set the number of pages that should be
\r
801 * retained to either side of the
\r
802 * current page in the view hierarchy
\r
803 * in an idle state. Pages beyond this
\r
804 * limit will be recreated from the adapter when needed.
\r
806 * <p>This is offered as an optimization.
\r
807 * If you know in advance the number
\r
808 * of pages you will need to support or
\r
809 * have lazy-loading mechanisms in place
\r
810 * on your pages, tweaking this setting
\r
811 * can have benefits in perceived smoothness
\r
812 * of paging animations and interaction.
\r
813 * If you have a small number of pages (3-4)
\r
814 * that you can keep active all at once,
\r
815 * less time will be spent in layout for
\r
816 * newly created view subtrees as the
\r
817 * user pages back and forth.</p>
\r
819 * <p>You should keep this limit low,
\r
820 * especially if your pages have complex layouts.
\r
821 * This setting defaults to 1.</p>
\r
823 * @param limit How many pages will be kept offscreen in an idle state.
\r
825 public void setOffscreenPageLimit(int limit) {
\r
826 if (limit < DEFAULT_OFFSCREEN_PAGES) {
\r
827 Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " +
\r
828 DEFAULT_OFFSCREEN_PAGES);
\r
829 limit = DEFAULT_OFFSCREEN_PAGES;
\r
831 if (limit != mOffscreenPageLimit) {
\r
832 mOffscreenPageLimit = limit;
\r
838 * Set the margin between pages.
\r
840 * @param marginPixels Distance between adjacent pages in pixels
\r
841 * @see #getPageMargin()
\r
842 * @see #setPageMarginDrawable(Drawable)
\r
843 * @see #setPageMarginDrawable(int)
\r
845 public void setPageMargin(int marginPixels) {
\r
846 final int oldMargin = mPageMargin;
\r
847 mPageMargin = marginPixels;
\r
849 if (isHorizontal()) {
\r
850 int width = getWidth();
\r
851 recomputeScrollPosition(width, width, marginPixels, oldMargin, 0, 0);
\r
853 int height = getHeight();
\r
854 recomputeScrollPosition(0, 0, marginPixels, oldMargin, height, height);
\r
861 * Return the margin between pages.
\r
863 * @return The size of the margin in pixels
\r
865 public int getPageMargin() {
\r
866 return mPageMargin;
\r
870 * Set a drawable that will be used to fill the margin between pages.
\r
872 * @param d Drawable to display between pages
\r
874 public void setPageMarginDrawable(Drawable d) {
\r
875 mMarginDrawable = d;
\r
876 if (d != null) refreshDrawableState();
\r
877 setWillNotDraw(d == null);
\r
882 * Set a drawable that will be used to fill the margin between pages.
\r
884 * @param resId Resource ID of a drawable to display between pages
\r
886 public void setPageMarginDrawable(@DrawableRes int resId) {
\r
887 setPageMarginDrawable(getContext().getResources().getDrawable(resId));
\r
891 protected boolean verifyDrawable(Drawable who) {
\r
892 return super.verifyDrawable(who) || who == mMarginDrawable;
\r
896 protected void drawableStateChanged() {
\r
897 super.drawableStateChanged();
\r
898 final Drawable d = mMarginDrawable;
\r
899 if (d != null && d.isStateful()) {
\r
900 d.setState(getDrawableState());
\r
904 // We want the duration of the page snap animation to be influenced by the distance that
\r
905 // the screen has to travel, however, we don't want this duration to be effected in a
\r
906 // purely linear fashion. Instead, we use this method to moderate the effect that the distance
\r
907 // of travel has on the overall snap duration.
\r
908 float distanceInfluenceForSnapDuration(float f) {
\r
909 f -= 0.5f; // center the values about 0.
\r
910 f *= 0.3f * Math.PI / 2.0f;
\r
911 return (float) Math.sin(f);
\r
915 * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
\r
917 * @param x the number of pixels to scroll by on the X axis
\r
918 * @param y the number of pixels to scroll by on the Y axis
\r
920 void smoothScrollTo(int x, int y) {
\r
921 smoothScrollTo(x, y, 0);
\r
925 * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
\r
927 * @param x the number of pixels to scroll by on the X axis
\r
928 * @param y the number of pixels to scroll by on the Y axis
\r
929 * @param velocity the velocity associated with a fling, if applicable. (0 otherwise)
\r
931 void smoothScrollTo(int x, int y, int velocity) {
\r
932 if (getChildCount() == 0) {
\r
934 setScrollingCacheEnabled(false);
\r
939 if (isHorizontal()) {
\r
940 boolean wasScrolling = (mScroller != null) && !mScroller.isFinished();
\r
941 if (wasScrolling) {
\r
942 // We're in the middle of a previously initiated scrolling. Check to see
\r
943 // whether that scrolling has actually started (if we always call getStartX
\r
944 // we can get a stale value from the scroller if it hadn't yet had its first
\r
945 // computeScrollOffset call) to decide what is the current scrolling position.
\r
946 sx = mIsScrollStarted ? mScroller.getCurrX() : mScroller.getStartX();
\r
947 // And abort the current scrolling.
\r
948 mScroller.abortAnimation();
\r
949 setScrollingCacheEnabled(false);
\r
956 int sy = getScrollY();
\r
959 if (dx == 0 && dy == 0) {
\r
960 completeScroll(false);
\r
962 setScrollState(SCROLL_STATE_IDLE);
\r
966 setScrollingCacheEnabled(true);
\r
967 setScrollState(SCROLL_STATE_SETTLING);
\r
969 if (isHorizontal()) {
\r
970 final int width = getClientWidth();
\r
971 final int halfWidth = width / 2;
\r
972 final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / width);
\r
973 final float distance = halfWidth + halfWidth *
\r
974 distanceInfluenceForSnapDuration(distanceRatio);
\r
975 velocity = Math.abs(velocity);
\r
976 if (velocity > 0) {
\r
977 duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
\r
979 final float pageWidth = width * mAdapter.getPageWidth(mCurItem);
\r
980 final float pageDelta = (float) Math.abs(dx) / (pageWidth + mPageMargin);
\r
981 duration = (int) ((pageDelta + 1) * 100);
\r
984 final int height = getClientHeight();
\r
985 final int halfHeight = height / 2;
\r
986 final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / height);
\r
987 final float distance = halfHeight + halfHeight *
\r
988 distanceInfluenceForSnapDuration(distanceRatio);
\r
991 velocity = Math.abs(velocity);
\r
992 if (velocity > 0) {
\r
993 duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
\r
995 final float pageHeight = height * mAdapter.getPageWidth(mCurItem);
\r
996 final float pageDelta = (float) Math.abs(dx) / (pageHeight + mPageMargin);
\r
997 duration = (int) ((pageDelta + 1) * 100);
\r
1000 duration = Math.min(duration, MAX_SETTLE_DURATION);
\r
1002 // Reset the "scroll started" flag. It will be flipped to true in all places
\r
1003 // where we call computeScrollOffset().
\r
1004 if (isHorizontal()) {
\r
1005 mIsScrollStarted = false;
\r
1007 mScroller.startScroll(sx, sy, dx, dy, duration);
\r
1008 ViewCompat.postInvalidateOnAnimation(this);
\r
1011 private boolean isHorizontal() {
\r
1012 return mDirection.equalsIgnoreCase(Direction.HORIZONTAL.name());
\r
1015 ItemInfo addNewItem(int position, int index) {
\r
1016 ItemInfo ii = new ItemInfo();
\r
1017 ii.position = position;
\r
1018 ii.object = mAdapter.instantiateItem(this, position);
\r
1019 if (isHorizontal()) {
\r
1020 ii.widthFactor = mAdapter.getPageWidth(position);
\r
1022 ii.heightFactor = mAdapter.getPageWidth(position);
\r
1024 if (index < 0 || index >= mItems.size()) {
\r
1027 mItems.add(index, ii);
\r
1032 void dataSetChanged() {
\r
1033 // This method only gets called if our observer is attached, so mAdapter is non-null.
\r
1035 final int adapterCount = mAdapter.getCount();
\r
1036 mExpectedAdapterCount = adapterCount;
\r
1037 boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1 &&
\r
1038 mItems.size() < adapterCount;
\r
1039 int newCurrItem = mCurItem;
\r
1041 boolean isUpdating = false;
\r
1042 for (int i = 0; i < mItems.size(); i++) {
\r
1043 final ItemInfo ii = mItems.get(i);
\r
1044 final int newPos = mAdapter.getItemPosition(ii.object);
\r
1046 if (newPos == PagerAdapter.POSITION_UNCHANGED) {
\r
1050 if (newPos == PagerAdapter.POSITION_NONE) {
\r
1054 if (!isUpdating) {
\r
1055 mAdapter.startUpdate(this);
\r
1056 isUpdating = true;
\r
1059 mAdapter.destroyItem(this, ii.position, ii.object);
\r
1060 needPopulate = true;
\r
1062 if (mCurItem == ii.position) {
\r
1063 // Keep the current item in the valid range
\r
1064 newCurrItem = Math.max(0, Math.min(mCurItem, adapterCount - 1));
\r
1065 needPopulate = true;
\r
1070 if (ii.position != newPos) {
\r
1071 if (ii.position == mCurItem) {
\r
1072 // Our current item changed position. Follow it.
\r
1073 newCurrItem = newPos;
\r
1076 ii.position = newPos;
\r
1077 needPopulate = true;
\r
1082 mAdapter.finishUpdate(this);
\r
1085 Collections.sort(mItems, COMPARATOR);
\r
1087 if (needPopulate) {
\r
1088 // Reset our known page widths; populate will recompute them.
\r
1089 final int childCount = getChildCount();
\r
1090 for (int i = 0; i < childCount; i++) {
\r
1091 final View child = getChildAt(i);
\r
1092 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
\r
1093 if (!lp.isDecor) {
\r
1094 if (isHorizontal()) {
\r
1095 lp.widthFactor = 0.f;
\r
1097 lp.heightFactor = 0.f;
\r
1102 setCurrentItemInternal(newCurrItem, false, true);
\r
1108 populate(mCurItem);
\r
1111 void populate(int newCurrentItem) {
\r
1112 ItemInfo oldCurInfo = null;
\r
1113 int focusDirection = View.FOCUS_FORWARD;
\r
1114 if (mCurItem != newCurrentItem) {
\r
1115 focusDirection = mCurItem < newCurrentItem ? View.FOCUS_DOWN : View.FOCUS_UP;
\r
1116 oldCurInfo = infoForPosition(mCurItem);
\r
1117 mCurItem = newCurrentItem;
\r
1120 if (mAdapter == null) {
\r
1121 sortChildDrawingOrder();
\r
1125 // Bail now if we are waiting to populate. This is to hold off
\r
1126 // on creating views from the time the user releases their finger to
\r
1127 // fling to a new position until we have finished the scroll to
\r
1128 // that position, avoiding glitches from happening at that point.
\r
1129 if (mPopulatePending) {
\r
1130 if (DEBUG) Log.i(TAG, "populate is pending, skipping for now...");
\r
1131 sortChildDrawingOrder();
\r
1135 // Also, don't populate until we are attached to a window. This is to
\r
1136 // avoid trying to populate before we have restored our view hierarchy
\r
1137 // state and conflicting with what is restored.
\r
1138 if (getWindowToken() == null) {
\r
1142 mAdapter.startUpdate(this);
\r
1144 final int pageLimit = mOffscreenPageLimit;
\r
1145 final int startPos = Math.max(0, mCurItem - pageLimit);
\r
1146 final int N = mAdapter.getCount();
\r
1147 final int endPos = Math.min(N - 1, mCurItem + pageLimit);
\r
1149 if (N != mExpectedAdapterCount) {
\r
1152 resName = getResources().getResourceName(getId());
\r
1153 } catch (Resources.NotFoundException e) {
\r
1154 resName = Integer.toHexString(getId());
\r
1156 throw new IllegalStateException("The application's PagerAdapter changed the adapter's" +
\r
1157 " contents without calling PagerAdapter#notifyDataSetChanged!" +
\r
1158 " Expected adapter item count: " + mExpectedAdapterCount + ", found: " + N +
\r
1159 " Pager id: " + resName +
\r
1160 " Pager class: " + getClass() +
\r
1161 " Problematic adapter: " + mAdapter.getClass());
\r
1164 // Locate the currently focused item or add it if needed.
\r
1165 int curIndex = -1;
\r
1166 ItemInfo curItem = null;
\r
1167 for (curIndex = 0; curIndex < mItems.size(); curIndex++) {
\r
1168 final ItemInfo ii = mItems.get(curIndex);
\r
1169 if (ii.position >= mCurItem) {
\r
1170 if (ii.position == mCurItem) curItem = ii;
\r
1175 if (curItem == null && N > 0) {
\r
1176 curItem = addNewItem(mCurItem, curIndex);
\r
1179 // Fill 3x the available width or up to the number of offscreen
\r
1180 // pages requested to either side, whichever is larger.
\r
1181 // If we have no current item we have no work to do.
\r
1182 if (curItem != null) {
\r
1183 if (isHorizontal()) {
\r
1184 float extraWidthLeft = 0.f;
\r
1185 int itemIndex = curIndex - 1;
\r
1186 ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
\r
1187 final int clientWidth = getClientWidth();
\r
1188 final float leftWidthNeeded = clientWidth <= 0 ? 0 :
\r
1189 2.f - curItem.widthFactor + (float) getPaddingLeft() / (float) clientWidth;
\r
1190 for (int pos = mCurItem - 1; pos >= 0; pos--) {
\r
1191 if (extraWidthLeft >= leftWidthNeeded && pos < startPos) {
\r
1195 if (pos == ii.position && !ii.scrolling) {
\r
1196 mItems.remove(itemIndex);
\r
1197 mAdapter.destroyItem(this, pos, ii.object);
\r
1199 Log.i(TAG, logDestroyItem(pos, ((View) ii.object)));
\r
1203 ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
\r
1205 } else if (ii != null && pos == ii.position) {
\r
1206 extraWidthLeft += ii.widthFactor;
\r
1208 ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
\r
1210 ii = addNewItem(pos, itemIndex + 1);
\r
1211 extraWidthLeft += ii.widthFactor;
\r
1213 ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
\r
1217 float extraWidthRight = curItem.widthFactor;
\r
1218 itemIndex = curIndex + 1;
\r
1219 if (extraWidthRight < 2.f) {
\r
1220 ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
\r
1221 final float rightWidthNeeded = clientWidth <= 0 ? 0 :
\r
1222 (float) getPaddingRight() / (float) clientWidth + 2.f;
\r
1223 for (int pos = mCurItem + 1; pos < N; pos++) {
\r
1224 if (extraWidthRight >= rightWidthNeeded && pos > endPos) {
\r
1228 if (pos == ii.position && !ii.scrolling) {
\r
1229 mItems.remove(itemIndex);
\r
1230 mAdapter.destroyItem(this, pos, ii.object);
\r
1232 Log.i(TAG, logDestroyItem(pos, ((View) ii.object)));
\r
1234 ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
\r
1236 } else if (ii != null && pos == ii.position) {
\r
1237 extraWidthRight += ii.widthFactor;
\r
1239 ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
\r
1241 ii = addNewItem(pos, itemIndex);
\r
1243 extraWidthRight += ii.widthFactor;
\r
1244 ii = itemIndex < mItems.size()
\r
1245 ? mItems.get(itemIndex) : null;
\r
1250 float extraHeightTop = 0.f;
\r
1251 int itemIndex = curIndex - 1;
\r
1253 = itemIndex >= 0 ? mItems.get(itemIndex) : null;
\r
1254 final int clientHeight = getClientHeight();
\r
1255 final float topHeightNeeded = clientHeight <= 0 ? 0 :
\r
1256 2.f - curItem.heightFactor
\r
1257 + (float) getPaddingLeft() / (float) clientHeight;
\r
1258 for (int pos = mCurItem - 1; pos >= 0; pos--) {
\r
1259 if (extraHeightTop >= topHeightNeeded && pos < startPos) {
\r
1263 if (pos == ii.position && !ii.scrolling) {
\r
1264 mItems.remove(itemIndex);
\r
1265 mAdapter.destroyItem(this, pos, ii.object);
\r
1267 Log.i(TAG, logDestroyItem(pos, ((View) ii.object)));
\r
1271 ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
\r
1273 } else if (ii != null && pos == ii.position) {
\r
1274 extraHeightTop += ii.heightFactor;
\r
1276 ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
\r
1278 ii = addNewItem(pos, itemIndex + 1);
\r
1279 extraHeightTop += ii.heightFactor;
\r
1281 ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
\r
1285 float extraHeightBottom = curItem.heightFactor;
\r
1286 itemIndex = curIndex + 1;
\r
1287 if (extraHeightBottom < 2.f) {
\r
1288 ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
\r
1289 final float bottomHeightNeeded = clientHeight <= 0 ? 0 :
\r
1290 (float) getPaddingRight() / (float) clientHeight + 2.f;
\r
1291 for (int pos = mCurItem + 1; pos < N; pos++) {
\r
1292 if (extraHeightBottom >= bottomHeightNeeded && pos > endPos) {
\r
1296 if (pos == ii.position && !ii.scrolling) {
\r
1297 mItems.remove(itemIndex);
\r
1298 mAdapter.destroyItem(this, pos, ii.object);
\r
1300 Log.i(TAG, logDestroyItem(pos, ((View) ii.object)));
\r
1302 ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
\r
1304 } else if (ii != null && pos == ii.position) {
\r
1305 extraHeightBottom += ii.heightFactor;
\r
1307 ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
\r
1309 ii = addNewItem(pos, itemIndex);
\r
1311 extraHeightBottom += ii.heightFactor;
\r
1312 ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
\r
1318 calculatePageOffsets(curItem, curIndex, oldCurInfo);
\r
1322 Log.i(TAG, "Current page list:");
\r
1323 for (int i = 0; i < mItems.size(); i++) {
\r
1324 Log.i(TAG, "#" + i + ": page " + mItems.get(i).position);
\r
1328 mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null);
\r
1330 mAdapter.finishUpdate(this);
\r
1332 // Check width measurement of current pages and drawing sort order.
\r
1333 // Update LayoutParams as needed.
\r
1334 final int childCount = getChildCount();
\r
1335 if (isHorizontal()) {
\r
1336 for (int i = 0; i < childCount; i++) {
\r
1337 final View child = getChildAt(i);
\r
1338 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
\r
1339 lp.childIndex = i;
\r
1340 if (!lp.isDecor && lp.widthFactor == 0.f) {
\r
1341 // 0 means requery the adapter for this, it doesn't have a valid width.
\r
1342 final ItemInfo ii = infoForChild(child);
\r
1344 lp.widthFactor = ii.widthFactor;
\r
1345 lp.position = ii.position;
\r
1349 sortChildDrawingOrder();
\r
1352 View currentFocused = findFocus();
\r
1354 = currentFocused != null ? infoForAnyChild(currentFocused) : null;
\r
1355 if (ii == null || ii.position != mCurItem) {
\r
1356 for (int i = 0; i < getChildCount(); i++) {
\r
1357 View child = getChildAt(i);
\r
1358 ii = infoForChild(child);
\r
1360 && ii.position == mCurItem &&
\r
1361 child.requestFocus(View.FOCUS_FORWARD)) {
\r
1368 for (int i = 0; i < childCount; i++) {
\r
1369 final View child = getChildAt(i);
\r
1370 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
\r
1371 lp.childIndex = i;
\r
1372 if (!lp.isDecor && lp.heightFactor == 0.f) {
\r
1373 final ItemInfo ii = infoForChild(child);
\r
1375 lp.heightFactor = ii.heightFactor;
\r
1376 lp.position = ii.position;
\r
1380 sortChildDrawingOrder();
\r
1383 View currentFocused = findFocus();
\r
1384 ItemInfo ii = currentFocused != null ? infoForAnyChild(currentFocused) : null;
\r
1385 if (ii == null || ii.position != mCurItem) {
\r
1386 for (int i = 0; i < getChildCount(); i++) {
\r
1387 View child = getChildAt(i);
\r
1388 ii = infoForChild(child);
\r
1389 if (ii != null && ii.position == mCurItem
\r
1390 && child.requestFocus(focusDirection)) {
\r
1391 // if (child.requestFocus(focusDirection)) {
\r
1401 private void sortChildDrawingOrder() {
\r
1402 if (mDrawingOrder != DRAW_ORDER_DEFAULT) {
\r
1403 if (mDrawingOrderedChildren == null) {
\r
1404 mDrawingOrderedChildren = new ArrayList<View>();
\r
1406 mDrawingOrderedChildren.clear();
\r
1408 final int childCount = getChildCount();
\r
1409 for (int i = 0; i < childCount; i++) {
\r
1410 final View child = getChildAt(i);
\r
1411 mDrawingOrderedChildren.add(child);
\r
1413 Collections.sort(mDrawingOrderedChildren, sPositionComparator);
\r
1417 private void calculatePageOffsets(ItemInfo curItem, int curIndex, ItemInfo oldCurInfo) {
\r
1418 final int N = mAdapter.getCount();
\r
1419 if (isHorizontal()) {
\r
1420 final int width = getClientWidth();
\r
1421 final float marginOffset = width > 0 ? (float) mPageMargin / width : 0;
\r
1422 // Fix up offsets for later layout.
\r
1423 if (oldCurInfo != null) {
\r
1424 final int oldCurPosition = oldCurInfo.position;
\r
1425 // Base offsets off of oldCurInfo.
\r
1426 if (oldCurPosition < curItem.position) {
\r
1427 int itemIndex = 0;
\r
1428 ItemInfo ii = null;
\r
1429 float offset = oldCurInfo.offset + oldCurInfo.widthFactor + marginOffset;
\r
1430 for (int pos = oldCurPosition + 1;
\r
1431 pos <= curItem.position && itemIndex < mItems.size(); pos++) {
\r
1432 ii = mItems.get(itemIndex);
\r
1433 while (pos > ii.position && itemIndex < mItems.size() - 1) {
\r
1435 ii = mItems.get(itemIndex);
\r
1437 while (pos < ii.position) {
\r
1438 // We don't have an item populated for this,
\r
1439 // ask the adapter for an offset.
\r
1440 offset += mAdapter.getPageWidth(pos) + marginOffset;
\r
1443 ii.offset = offset;
\r
1444 offset += ii.widthFactor + marginOffset;
\r
1446 } else if (oldCurPosition > curItem.position) {
\r
1447 int itemIndex = mItems.size() - 1;
\r
1448 ItemInfo ii = null;
\r
1449 float offset = oldCurInfo.offset;
\r
1450 for (int pos = oldCurPosition - 1;
\r
1451 pos >= curItem.position && itemIndex >= 0; pos--) {
\r
1452 ii = mItems.get(itemIndex);
\r
1453 while (pos < ii.position && itemIndex > 0) {
\r
1455 ii = mItems.get(itemIndex);
\r
1457 while (pos > ii.position) {
\r
1458 // We don't have an item populated for this,
\r
1459 // ask the adapter for an offset.
\r
1460 offset -= mAdapter.getPageWidth(pos) + marginOffset;
\r
1463 offset -= ii.widthFactor + marginOffset;
\r
1464 ii.offset = offset;
\r
1469 // Base all offsets off of curItem.
\r
1470 final int itemCount = mItems.size();
\r
1471 float offset = curItem.offset;
\r
1472 int pos = curItem.position - 1;
\r
1473 mFirstOffset = curItem.position == 0 ? curItem.offset : -Float.MAX_VALUE;
\r
1474 mLastOffset = curItem.position == N - 1 ?
\r
1475 curItem.offset + curItem.widthFactor - 1 : Float.MAX_VALUE;
\r
1477 for (int i = curIndex - 1; i >= 0; i--, pos--) {
\r
1478 final ItemInfo ii = mItems.get(i);
\r
1479 while (pos > ii.position) {
\r
1480 offset -= mAdapter.getPageWidth(pos--) + marginOffset;
\r
1482 offset -= ii.widthFactor + marginOffset;
\r
1483 ii.offset = offset;
\r
1484 if (ii.position == 0) mFirstOffset = offset;
\r
1486 offset = curItem.offset + curItem.widthFactor + marginOffset;
\r
1487 pos = curItem.position + 1;
\r
1489 for (int i = curIndex + 1; i < itemCount; i++, pos++) {
\r
1490 final ItemInfo ii = mItems.get(i);
\r
1491 while (pos < ii.position) {
\r
1492 offset += mAdapter.getPageWidth(pos++) + marginOffset;
\r
1494 if (ii.position == N - 1) {
\r
1495 mLastOffset = offset + ii.widthFactor - 1;
\r
1497 ii.offset = offset;
\r
1498 offset += ii.widthFactor + marginOffset;
\r
1501 final int height = getClientHeight();
\r
1502 final float marginOffset = height > 0 ? (float) mPageMargin / height : 0;
\r
1503 // Fix up offsets for later layout.
\r
1504 if (oldCurInfo != null) {
\r
1505 final int oldCurPosition = oldCurInfo.position;
\r
1506 // Base offsets off of oldCurInfo.
\r
1507 if (oldCurPosition < curItem.position) {
\r
1508 int itemIndex = 0;
\r
1509 ItemInfo ii = null;
\r
1510 float offset = oldCurInfo.offset + oldCurInfo.heightFactor + marginOffset;
\r
1511 for (int pos = oldCurPosition + 1;
\r
1512 pos <= curItem.position && itemIndex < mItems.size(); pos++) {
\r
1513 ii = mItems.get(itemIndex);
\r
1514 while (pos > ii.position && itemIndex < mItems.size() - 1) {
\r
1516 ii = mItems.get(itemIndex);
\r
1518 while (pos < ii.position) {
\r
1519 // We don't have an item populated for this,
\r
1520 // ask the adapter for an offset.
\r
1521 offset += mAdapter.getPageWidth(pos) + marginOffset;
\r
1524 ii.offset = offset;
\r
1525 offset += ii.heightFactor + marginOffset;
\r
1527 } else if (oldCurPosition > curItem.position) {
\r
1528 int itemIndex = mItems.size() - 1;
\r
1529 ItemInfo ii = null;
\r
1530 float offset = oldCurInfo.offset;
\r
1531 for (int pos = oldCurPosition - 1;
\r
1532 pos >= curItem.position && itemIndex >= 0;
\r
1534 ii = mItems.get(itemIndex);
\r
1535 while (pos < ii.position && itemIndex > 0) {
\r
1537 ii = mItems.get(itemIndex);
\r
1539 while (pos > ii.position) {
\r
1540 // We don't have an item populated for this,
\r
1541 // ask the adapter for an offset.
\r
1542 offset -= mAdapter.getPageWidth(pos) + marginOffset;
\r
1545 offset -= ii.heightFactor + marginOffset;
\r
1546 ii.offset = offset;
\r
1551 // Base all offsets off of curItem.
\r
1552 final int itemCount = mItems.size();
\r
1553 float offset = curItem.offset;
\r
1554 int pos = curItem.position - 1;
\r
1555 mFirstOffset = curItem.position == 0 ? curItem.offset : -Float.MAX_VALUE;
\r
1556 mLastOffset = curItem.position == N - 1 ?
\r
1557 curItem.offset + curItem.heightFactor - 1 : Float.MAX_VALUE;
\r
1559 for (int i = curIndex - 1; i >= 0; i--, pos--) {
\r
1560 final ItemInfo ii = mItems.get(i);
\r
1561 while (pos > ii.position) {
\r
1562 offset -= mAdapter.getPageWidth(pos--) + marginOffset;
\r
1564 offset -= ii.heightFactor + marginOffset;
\r
1565 ii.offset = offset;
\r
1566 if (ii.position == 0) mFirstOffset = offset;
\r
1568 offset = curItem.offset + curItem.heightFactor + marginOffset;
\r
1569 pos = curItem.position + 1;
\r
1571 for (int i = curIndex + 1; i < itemCount; i++, pos++) {
\r
1572 final ItemInfo ii = mItems.get(i);
\r
1573 while (pos < ii.position) {
\r
1574 offset += mAdapter.getPageWidth(pos++) + marginOffset;
\r
1576 if (ii.position == N - 1) {
\r
1577 mLastOffset = offset + ii.heightFactor - 1;
\r
1579 ii.offset = offset;
\r
1580 offset += ii.heightFactor + marginOffset;
\r
1584 mNeedCalculatePageOffsets = false;
\r
1588 * This is the persistent state that is saved by ViewPager. Only needed
\r
1589 * if you are creating a sublass of ViewPager that must save its own
\r
1590 * state, in which case it should implement a subclass of this which
\r
1591 * contains that state.
\r
1593 public static class SavedState extends BaseSavedState {
\r
1595 Parcelable adapterState;
\r
1596 ClassLoader loader;
\r
1598 public SavedState(Parcelable superState) {
\r
1599 super(superState);
\r
1603 public void writeToParcel(Parcel out, int flags) {
\r
1604 super.writeToParcel(out, flags);
\r
1605 out.writeInt(position);
\r
1606 out.writeParcelable(adapterState, flags);
\r
1610 public String toString() {
\r
1611 return "FragmentPager.SavedState{"
\r
1612 + Integer.toHexString(System.identityHashCode(this))
\r
1613 + " position=" + position + "}";
\r
1616 public static final Parcelable.Creator<SavedState> CREATOR
\r
1617 = ParcelableCompat
\r
1618 .newCreator(new ParcelableCompatCreatorCallbacks<SavedState>() {
\r
1621 createFromParcel(Parcel in, ClassLoader loader) {
\r
1622 return new SavedState(in, loader);
\r
1626 public SavedState[] newArray(int size) {
\r
1627 return new SavedState[size];
\r
1631 SavedState(Parcel in, ClassLoader loader) {
\r
1633 if (loader == null) {
\r
1634 loader = getClass().getClassLoader();
\r
1636 position = in.readInt();
\r
1637 adapterState = in.readParcelable(loader);
\r
1638 this.loader = loader;
\r
1643 public Parcelable onSaveInstanceState() {
\r
1644 Parcelable superState = super.onSaveInstanceState();
\r
1645 SavedState ss = new SavedState(superState);
\r
1646 ss.position = mCurItem;
\r
1647 if (mAdapter != null) {
\r
1648 ss.adapterState = mAdapter.saveState();
\r
1654 public void onRestoreInstanceState(Parcelable state) {
\r
1655 if (!(state instanceof SavedState)) {
\r
1656 super.onRestoreInstanceState(state);
\r
1660 SavedState ss = (SavedState) state;
\r
1661 super.onRestoreInstanceState(ss.getSuperState());
\r
1663 if (mAdapter != null) {
\r
1664 mAdapter.restoreState(ss.adapterState, ss.loader);
\r
1665 setCurrentItemInternal(ss.position, false, true);
\r
1667 mRestoredCurItem = ss.position;
\r
1668 mRestoredAdapterState = ss.adapterState;
\r
1669 mRestoredClassLoader = ss.loader;
\r
1674 public void addView(View child, int index, ViewGroup.LayoutParams params) {
\r
1675 if (!checkLayoutParams(params)) {
\r
1676 params = generateLayoutParams(params);
\r
1678 final LayoutParams lp = (LayoutParams) params;
\r
1679 lp.isDecor |= child instanceof Decor;
\r
1681 if (lp != null && lp.isDecor) {
\r
1682 throw new IllegalStateException("Cannot add pager decor view during layout");
\r
1684 lp.needsMeasure = true;
\r
1685 addViewInLayout(child, index, params);
\r
1687 super.addView(child, index, params);
\r
1691 if (child.getVisibility() != GONE) {
\r
1692 child.setDrawingCacheEnabled(mScrollingCacheEnabled);
\r
1694 child.setDrawingCacheEnabled(false);
\r
1700 public void removeView(View view) {
\r
1702 removeViewInLayout(view);
\r
1704 super.removeView(view);
\r
1708 ItemInfo infoForChild(View child) {
\r
1709 for (int i = 0; i < mItems.size(); i++) {
\r
1710 ItemInfo ii = mItems.get(i);
\r
1711 if (mAdapter.isViewFromObject(child, ii.object)) {
\r
1718 ItemInfo infoForAnyChild(View child) {
\r
1719 ViewParent parent;
\r
1720 while ((parent = child.getParent()) != this) {
\r
1721 if (parent == null || !(parent instanceof View)) {
\r
1724 child = (View) parent;
\r
1726 return infoForChild(child);
\r
1729 ItemInfo infoForPosition(int position) {
\r
1730 for (int i = 0; i < mItems.size(); i++) {
\r
1731 ItemInfo ii = mItems.get(i);
\r
1732 if (ii.position == position) {
\r
1740 protected void onAttachedToWindow() {
\r
1741 super.onAttachedToWindow();
\r
1742 mFirstLayout = true;
\r
1746 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
\r
1747 // For simple implementation, our internal size is always 0.
\r
1748 // We depend on the container to specify the layout size of
\r
1749 // our view. We can't really know what it is since we will be
\r
1750 // adding and removing different arbitrary views and do not
\r
1751 // want the layout to change as this happens.
\r
1752 setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
\r
1753 getDefaultSize(0, heightMeasureSpec));
\r
1755 int childWidthSize = 0;
\r
1756 int childHeightSize = 0;
\r
1757 if (isHorizontal()) {
\r
1758 int measuredWidth = getMeasuredWidth();
\r
1759 int maxGutterSize = measuredWidth / 10;
\r
1760 mGutterSize = Math.min(maxGutterSize, mDefaultGutterSize);
\r
1762 // Children are just made to fill our space.
\r
1763 childWidthSize = measuredWidth - getPaddingLeft() - getPaddingRight();
\r
1764 childHeightSize = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
\r
1766 int measuredHeight = getMeasuredHeight();
\r
1767 int maxGutterSize = measuredHeight / 10;
\r
1768 mGutterSize = Math.min(maxGutterSize, mDefaultGutterSize);
\r
1770 // Children are just made to fill our space.
\r
1771 childWidthSize = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
\r
1772 childHeightSize = measuredHeight - getPaddingTop() - getPaddingBottom();
\r
1776 * Make sure all children have been properly measured. Decor views first.
\r
1777 * Right now we cheat and make this less complicated by assuming decor
\r
1778 * views won't intersect. We will pin to edges based on gravity.
\r
1780 int size = getChildCount();
\r
1781 for (int i = 0; i < size; ++i) {
\r
1782 final View child = getChildAt(i);
\r
1783 if (child.getVisibility() != GONE) {
\r
1784 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
\r
1785 if (lp != null && lp.isDecor) {
\r
1786 final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
\r
1787 final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
\r
1788 int widthMode = MeasureSpec.AT_MOST;
\r
1789 int heightMode = MeasureSpec.AT_MOST;
\r
1790 boolean consumeVertical = vgrav == Gravity.TOP || vgrav == Gravity.BOTTOM;
\r
1791 boolean consumeHorizontal = hgrav == Gravity.LEFT || hgrav == Gravity.RIGHT;
\r
1793 if (consumeVertical) {
\r
1794 widthMode = MeasureSpec.EXACTLY;
\r
1795 } else if (consumeHorizontal) {
\r
1796 heightMode = MeasureSpec.EXACTLY;
\r
1799 int widthSize = childWidthSize;
\r
1800 int heightSize = childHeightSize;
\r
1801 if (lp.width != LayoutParams.WRAP_CONTENT) {
\r
1802 widthMode = MeasureSpec.EXACTLY;
\r
1803 if (lp.width != LayoutParams.FILL_PARENT) {
\r
1804 widthSize = lp.width;
\r
1807 if (lp.height != LayoutParams.WRAP_CONTENT) {
\r
1808 heightMode = MeasureSpec.EXACTLY;
\r
1809 if (lp.height != LayoutParams.FILL_PARENT) {
\r
1810 heightSize = lp.height;
\r
1813 final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode);
\r
1814 final int heightSpec = MeasureSpec.makeMeasureSpec(heightSize, heightMode);
\r
1815 child.measure(widthSpec, heightSpec);
\r
1817 if (consumeVertical) {
\r
1818 childHeightSize -= child.getMeasuredHeight();
\r
1819 } else if (consumeHorizontal) {
\r
1820 childWidthSize -= child.getMeasuredWidth();
\r
1826 mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthSize, MeasureSpec.EXACTLY);
\r
1827 mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeightSize, MeasureSpec.EXACTLY);
\r
1829 // Make sure we have created all fragments that we need to have shown.
\r
1832 mInLayout = false;
\r
1834 // Page views next.
\r
1835 size = getChildCount();
\r
1836 for (int i = 0; i < size; ++i) {
\r
1837 final View child = getChildAt(i);
\r
1838 if (child.getVisibility() != GONE) {
\r
1839 if (DEBUG) Log.v(TAG, "Measuring #" + i + " " + child
\r
1840 + ": " + mChildWidthMeasureSpec);
\r
1842 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
\r
1843 if (lp == null || !lp.isDecor) {
\r
1844 if (isHorizontal()) {
\r
1845 int widthSpec = MeasureSpec.makeMeasureSpec(
\r
1846 (int) (childWidthSize * lp.widthFactor), MeasureSpec.EXACTLY);
\r
1847 child.measure(widthSpec, mChildHeightMeasureSpec);
\r
1849 int heightSpec = MeasureSpec.makeMeasureSpec(
\r
1850 (int) (childHeightSize * lp.heightFactor), MeasureSpec.EXACTLY);
\r
1851 child.measure(mChildWidthMeasureSpec, heightSpec);
\r
1860 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
\r
1861 super.onSizeChanged(w, h, oldw, oldh);
\r
1863 // Make sure scroll position is set correctly.
\r
1865 if (isHorizontal()) {
\r
1867 recomputeScrollPosition(w, oldw, mPageMargin, mPageMargin, 0, 0);
\r
1871 recomputeScrollPosition(0, 0, mPageMargin, mPageMargin, h, oldh);
\r
1877 private void recomputeScrollPosition(int width, int oldWidth, int margin,
\r
1878 int oldMargin, int height, int oldHeight) {
\r
1879 if (isHorizontal()) {
\r
1880 if (oldWidth > 0 && !mItems.isEmpty()) {
\r
1881 if (!mScroller.isFinished()) {
\r
1882 mScroller.setFinalX(
\r
1883 getCurrentItem() * getClientWidth());
\r
1885 final int widthWithMargin
\r
1886 = width - getPaddingLeft() - getPaddingRight() + margin;
\r
1887 final int oldWidthWithMargin
\r
1888 = oldWidth - getPaddingLeft() - getPaddingRight()
\r
1890 final int xpos = getScrollX();
\r
1891 final float pageOffset = (float) xpos / oldWidthWithMargin;
\r
1892 final int newOffsetPixels = (int) (pageOffset * widthWithMargin);
\r
1894 scrollTo(newOffsetPixels, getScrollY());
\r
1897 final ItemInfo ii = infoForPosition(mCurItem);
\r
1898 final float scrollOffset = ii != null ? Math.min(ii.offset, mLastOffset) : 0;
\r
1899 final int scrollPos = (int) (scrollOffset *
\r
1900 (width - getPaddingLeft() - getPaddingRight()));
\r
1901 if (scrollPos != getScrollX()) {
\r
1902 completeScroll(false);
\r
1903 scrollTo(scrollPos, getScrollY());
\r
1907 final int heightWithMargin = height - getPaddingTop() - getPaddingBottom() + margin;
\r
1908 final int oldHeightWithMargin = oldHeight - getPaddingTop() - getPaddingBottom()
\r
1910 final int ypos = getScrollY();
\r
1911 final float pageOffset = (float) ypos / oldHeightWithMargin;
\r
1912 final int newOffsetPixels = (int) (pageOffset * heightWithMargin);
\r
1914 scrollTo(getScrollX(), newOffsetPixels);
\r
1915 if (!mScroller.isFinished()) {
\r
1916 // We now return to your regularly scheduled scroll, already in progress.
\r
1917 final int newDuration = mScroller.getDuration() - mScroller.timePassed();
\r
1918 ItemInfo targetInfo = infoForPosition(mCurItem);
\r
1919 mScroller.startScroll(0, newOffsetPixels,
\r
1920 0, (int) (targetInfo.offset * height), newDuration);
\r
1922 final ItemInfo ii = infoForPosition(mCurItem);
\r
1923 final float scrollOffset = ii != null ? Math.min(ii.offset, mLastOffset) : 0;
\r
1924 final int scrollPos = (int) (scrollOffset *
\r
1925 (height - getPaddingTop() - getPaddingBottom()));
\r
1926 if (scrollPos != getScrollY()) {
\r
1927 completeScroll(false);
\r
1928 scrollTo(getScrollX(), scrollPos);
\r
1936 protected void onLayout(boolean changed, int l, int t, int r, int b) {
\r
1937 final int count = getChildCount();
\r
1938 int width = r - l;
\r
1939 int height = b - t;
\r
1940 int paddingLeft = getPaddingLeft();
\r
1941 int paddingTop = getPaddingTop();
\r
1942 int paddingRight = getPaddingRight();
\r
1943 int paddingBottom = getPaddingBottom();
\r
1944 final int scrollX = getScrollX();
\r
1945 final int scrollY = getScrollY();
\r
1947 int decorCount = 0;
\r
1949 // First pass - decor views. We need to do this in two passes so that
\r
1950 // we have the proper offsets for non-decor views later.
\r
1951 for (int i = 0; i < count; i++) {
\r
1952 final View child = getChildAt(i);
\r
1953 if (child.getVisibility() != GONE) {
\r
1954 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
\r
1955 int childLeft = 0;
\r
1958 final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
\r
1959 final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
\r
1962 childLeft = paddingLeft;
\r
1964 case Gravity.LEFT:
\r
1965 childLeft = paddingLeft;
\r
1966 paddingLeft += child.getMeasuredWidth();
\r
1968 case Gravity.CENTER_HORIZONTAL:
\r
1969 childLeft = Math.max((width - child.getMeasuredWidth()) / 2,
\r
1972 case Gravity.RIGHT:
\r
1973 childLeft = width - paddingRight - child.getMeasuredWidth();
\r
1974 paddingRight += child.getMeasuredWidth();
\r
1979 childTop = paddingTop;
\r
1982 childTop = paddingTop;
\r
1983 paddingTop += child.getMeasuredHeight();
\r
1985 case Gravity.CENTER_VERTICAL:
\r
1986 childTop = Math.max((height - child.getMeasuredHeight()) / 2,
\r
1989 case Gravity.BOTTOM:
\r
1990 childTop = height - paddingBottom - child.getMeasuredHeight();
\r
1991 paddingBottom += child.getMeasuredHeight();
\r
1994 if (isHorizontal()) {
\r
1995 childLeft += scrollX;
\r
1997 childTop += scrollY;
\r
1999 child.layout(childLeft, childTop,
\r
2000 childLeft + child.getMeasuredWidth(),
\r
2001 childTop + child.getMeasuredHeight());
\r
2007 if (isHorizontal()) {
\r
2008 final int childWidth = width - paddingLeft - paddingRight;
\r
2009 // Page views. Do this once we have the right padding offsets from above.
\r
2010 for (int i = 0; i < count; i++) {
\r
2011 final View child = getChildAt(i);
\r
2012 if (child.getVisibility() != GONE) {
\r
2013 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
\r
2015 if (!lp.isDecor && (ii = infoForChild(child)) != null) {
\r
2016 int loff = (int) (childWidth * ii.offset);
\r
2017 int childLeft = paddingLeft + loff;
\r
2018 int childTop = paddingTop;
\r
2019 if (lp.needsMeasure) {
\r
2020 // This was added during layout and needs measurement.
\r
2021 // Do it now that we know what we're working with.
\r
2022 lp.needsMeasure = false;
\r
2023 final int widthSpec = MeasureSpec.makeMeasureSpec(
\r
2024 (int) (childWidth * lp.widthFactor),
\r
2025 MeasureSpec.EXACTLY);
\r
2026 final int heightSpec = MeasureSpec.makeMeasureSpec(
\r
2027 (int) (height - paddingTop - paddingBottom),
\r
2028 MeasureSpec.EXACTLY);
\r
2029 child.measure(widthSpec, heightSpec);
\r
2031 if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object
\r
2032 + ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth()
\r
2033 + "x" + child.getMeasuredHeight());
\r
2034 child.layout(childLeft, childTop,
\r
2035 childLeft + child.getMeasuredWidth(),
\r
2036 childTop + child.getMeasuredHeight());
\r
2040 mTopPageBounds = paddingTop;
\r
2041 mBottomPageBounds = height - paddingBottom;
\r
2043 final int childHeight = height - paddingTop - paddingBottom;
\r
2044 // Page views. Do this once we have the right padding offsets from above.
\r
2045 for (int i = 0; i < count; i++) {
\r
2046 final View child = getChildAt(i);
\r
2047 if (child.getVisibility() != GONE) {
\r
2048 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
\r
2050 if (!lp.isDecor && (ii = infoForChild(child)) != null) {
\r
2051 int toff = (int) (childHeight * ii.offset);
\r
2052 int childLeft = paddingLeft;
\r
2053 int childTop = paddingTop + toff;
\r
2054 if (lp.needsMeasure) {
\r
2055 // This was added during layout and needs measurement.
\r
2056 // Do it now that we know what we're working with.
\r
2057 lp.needsMeasure = false;
\r
2058 final int widthSpec = MeasureSpec.makeMeasureSpec(
\r
2059 (int) (width - paddingLeft - paddingRight),
\r
2060 MeasureSpec.EXACTLY);
\r
2061 final int heightSpec = MeasureSpec.makeMeasureSpec(
\r
2062 (int) (childHeight * lp.heightFactor),
\r
2063 MeasureSpec.EXACTLY);
\r
2064 child.measure(widthSpec, heightSpec);
\r
2066 if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object
\r
2067 + ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth()
\r
2068 + "x" + child.getMeasuredHeight());
\r
2069 child.layout(childLeft, childTop,
\r
2070 childLeft + child.getMeasuredWidth(),
\r
2071 childTop + child.getMeasuredHeight());
\r
2075 mLeftPageBounds = paddingLeft;
\r
2076 mRightPageBounds = width - paddingRight;
\r
2078 mDecorChildCount = decorCount;
\r
2080 if (mFirstLayout) {
\r
2081 scrollToItem(mCurItem, false, 0, false);
\r
2083 mFirstLayout = false;
\r
2087 public void computeScroll() {
\r
2088 mIsScrollStarted = true;
\r
2089 if (!mScroller.isFinished() && mScroller.computeScrollOffset()) {
\r
2090 int oldX = getScrollX();
\r
2091 int oldY = getScrollY();
\r
2092 int x = mScroller.getCurrX();
\r
2093 int y = mScroller.getCurrY();
\r
2095 if (oldX != x || oldY != y) {
\r
2097 if (isHorizontal()) {
\r
2098 if (!pageScrolled(x, 0)) {
\r
2099 mScroller.abortAnimation();
\r
2103 if (!pageScrolled(0, y)) {
\r
2104 mScroller.abortAnimation();
\r
2110 // Keep on drawing until the animation has finished.
\r
2111 ViewCompat.postInvalidateOnAnimation(this);
\r
2115 // Done with scroll, clean up state.
\r
2116 completeScroll(true);
\r
2119 private boolean pageScrolled(int xpos, int ypos) {
\r
2120 if (mItems.size() == 0) {
\r
2121 if (mFirstLayout) {
\r
2122 // If we haven't been laid out yet, we probably just haven't been populated yet.
\r
2123 // Let's skip this call since it doesn't make sense in this state
\r
2126 mCalledSuper = false;
\r
2127 onPageScrolled(0, 0, 0);
\r
2128 if (!mCalledSuper) {
\r
2129 throw new IllegalStateException(
\r
2130 "onPageScrolled did not call superclass implementation");
\r
2134 final ItemInfo ii = infoForCurrentScrollPosition();
\r
2135 int currentPage = 0;
\r
2136 float pageOffset = 0;
\r
2137 int offsetPixels = 0;
\r
2138 if (isHorizontal()) {
\r
2139 int width = getClientWidth();
\r
2140 int widthWithMargin = width + mPageMargin;
\r
2141 float marginOffset = (float) mPageMargin / width;
\r
2142 currentPage = ii.position;
\r
2143 pageOffset = (((float) xpos / width) - ii.offset) /
\r
2144 (ii.widthFactor + marginOffset);
\r
2145 offsetPixels = (int) (pageOffset * widthWithMargin);
\r
2147 int height = getClientHeight();
\r
2148 int heightWithMargin = height + mPageMargin;
\r
2149 float marginOffset = (float) mPageMargin / height;
\r
2150 currentPage = ii.position;
\r
2151 pageOffset = (((float) ypos / height) - ii.offset) /
\r
2152 (ii.heightFactor + marginOffset);
\r
2153 offsetPixels = (int) (pageOffset * heightWithMargin);
\r
2156 mCalledSuper = false;
\r
2157 onPageScrolled(currentPage, pageOffset, offsetPixels);
\r
2158 if (!mCalledSuper) {
\r
2159 throw new IllegalStateException(
\r
2160 "onPageScrolled did not call superclass implementation");
\r
2166 * This method will be invoked when the current page is scrolled, either as part
\r
2167 * of a programmatically initiated smooth scroll or a user initiated touch scroll.
\r
2168 * If you override this method you must call through to the superclass implementation
\r
2169 * (e.g. super.onPageScrolled(position, offset, offsetPixels)) before onPageScrolled
\r
2172 * @param position Position index of the first page currently being displayed.
\r
2173 * Page position+1 will be visible if positionOffset is nonzero.
\r
2174 * @param offset Value from [0, 1) indicating the offset from the page at position.
\r
2175 * @param offsetPixels Value in pixels indicating the offset from position.
\r
2178 protected void onPageScrolled(int position, float offset, int offsetPixels) {
\r
2179 // Offset any decor views if needed - keep them on-screen at all times.
\r
2180 if (isHorizontal()) {
\r
2181 if (mDecorChildCount > 0) {
\r
2182 final int scrollX = getScrollX();
\r
2183 int paddingLeft = getPaddingLeft();
\r
2184 int paddingRight = getPaddingRight();
\r
2185 final int width = getWidth();
\r
2186 final int childCount = getChildCount();
\r
2187 for (int i = 0; i < childCount; i++) {
\r
2188 final View child = getChildAt(i);
\r
2189 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
\r
2190 if (!lp.isDecor) continue;
\r
2192 final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
\r
2193 int childLeft = 0;
\r
2196 childLeft = paddingLeft;
\r
2198 case Gravity.LEFT:
\r
2199 childLeft = paddingLeft;
\r
2200 paddingLeft += child.getWidth();
\r
2202 case Gravity.CENTER_HORIZONTAL:
\r
2203 childLeft = Math.max((width - child.getMeasuredWidth()) / 2,
\r
2206 case Gravity.RIGHT:
\r
2207 childLeft = width - paddingRight - child.getMeasuredWidth();
\r
2208 paddingRight += child.getMeasuredWidth();
\r
2211 childLeft += scrollX;
\r
2213 final int childOffset = childLeft - child.getLeft();
\r
2214 if (childOffset != 0) {
\r
2215 child.offsetLeftAndRight(childOffset);
\r
2220 dispatchOnPageScrolled(position, offset, offsetPixels);
\r
2222 if (mPageTransformer != null) {
\r
2223 final int scrollX = getScrollX();
\r
2224 final int childCount = getChildCount();
\r
2225 for (int i = 0; i < childCount; i++) {
\r
2226 final View child = getChildAt(i);
\r
2227 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
\r
2229 if (lp.isDecor) continue;
\r
2230 final float transformPos
\r
2231 = (float) (child.getLeft() - scrollX) / getClientWidth();
\r
2232 mPageTransformer.transformPage(child, transformPos);
\r
2236 if (mDecorChildCount > 0) {
\r
2237 final int scrollY = getScrollY();
\r
2238 int paddingTop = getPaddingTop();
\r
2239 int paddingBottom = getPaddingBottom();
\r
2240 final int height = getHeight();
\r
2241 final int childCount = getChildCount();
\r
2242 for (int i = 0; i < childCount; i++) {
\r
2243 final View child = getChildAt(i);
\r
2244 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
\r
2245 if (!lp.isDecor) continue;
\r
2247 final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
\r
2251 childTop = paddingTop;
\r
2254 childTop = paddingTop;
\r
2255 paddingTop += child.getHeight();
\r
2257 case Gravity.CENTER_VERTICAL:
\r
2258 childTop = Math.max((height - child.getMeasuredHeight()) / 2,
\r
2261 case Gravity.BOTTOM:
\r
2262 childTop = height - paddingBottom - child.getMeasuredHeight();
\r
2263 paddingBottom += child.getMeasuredHeight();
\r
2266 childTop += scrollY;
\r
2268 final int childOffset = childTop - child.getTop();
\r
2269 if (childOffset != 0) {
\r
2270 child.offsetTopAndBottom(childOffset);
\r
2275 if (mOnPageChangeListener != null) {
\r
2276 mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels);
\r
2278 if (mInternalPageChangeListener != null) {
\r
2279 mInternalPageChangeListener.onPageScrolled(position, offset, offsetPixels);
\r
2282 if (mPageTransformer != null) {
\r
2283 final int scrollY = getScrollY();
\r
2284 final int childCount = getChildCount();
\r
2285 for (int i = 0; i < childCount; i++) {
\r
2286 final View child = getChildAt(i);
\r
2287 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
\r
2289 if (lp.isDecor) continue;
\r
2291 final float transformPos
\r
2292 = (float) (child.getTop() - scrollY) / getClientHeight();
\r
2293 mPageTransformer.transformPage(child, transformPos);
\r
2298 mCalledSuper = true;
\r
2301 private void dispatchOnPageScrolled(int position, float offset, int offsetPixels) {
\r
2302 if (mOnPageChangeListener != null) {
\r
2303 mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels);
\r
2305 if (mOnPageChangeListeners != null) {
\r
2306 for (int i = 0, z = mOnPageChangeListeners.size(); i < z; i++) {
\r
2307 OnPageChangeListener listener = mOnPageChangeListeners.get(i);
\r
2308 if (listener != null) {
\r
2309 listener.onPageScrolled(position, offset, offsetPixels);
\r
2313 if (mInternalPageChangeListener != null) {
\r
2314 mInternalPageChangeListener.onPageScrolled(position, offset, offsetPixels);
\r
2318 private void dispatchOnPageSelected(int position) {
\r
2319 if (mOnPageChangeListener != null) {
\r
2320 mOnPageChangeListener.onPageSelected(position);
\r
2322 if (mOnPageChangeListeners != null) {
\r
2323 for (int i = 0, z = mOnPageChangeListeners.size(); i < z; i++) {
\r
2324 OnPageChangeListener listener = mOnPageChangeListeners.get(i);
\r
2325 if (listener != null) {
\r
2326 listener.onPageSelected(position);
\r
2330 if (mInternalPageChangeListener != null) {
\r
2331 mInternalPageChangeListener.onPageSelected(position);
\r
2335 private void dispatchOnScrollStateChanged(int state) {
\r
2336 if (mOnPageChangeListener != null) {
\r
2337 mOnPageChangeListener.onPageScrollStateChanged(state);
\r
2339 if (mOnPageChangeListeners != null) {
\r
2340 for (int i = 0, z = mOnPageChangeListeners.size(); i < z; i++) {
\r
2341 OnPageChangeListener listener = mOnPageChangeListeners.get(i);
\r
2342 if (listener != null) {
\r
2343 listener.onPageScrollStateChanged(state);
\r
2347 if (mInternalPageChangeListener != null) {
\r
2348 mInternalPageChangeListener.onPageScrollStateChanged(state);
\r
2352 private void completeScroll(boolean postEvents) {
\r
2353 boolean needPopulate = mScrollState == SCROLL_STATE_SETTLING;
\r
2354 if (needPopulate) {
\r
2355 // Done with scroll, no longer want to cache view drawing.
\r
2356 setScrollingCacheEnabled(false);
\r
2357 boolean wasScrolling = !mScroller.isFinished();
\r
2358 if (wasScrolling) {
\r
2359 mScroller.abortAnimation();
\r
2360 int oldX = getScrollX();
\r
2361 int oldY = getScrollY();
\r
2362 int x = mScroller.getCurrX();
\r
2363 int y = mScroller.getCurrY();
\r
2364 if (oldX != x || oldY != y) {
\r
2366 if (isHorizontal() && x != oldX) {
\r
2367 pageScrolled(x, 0);
\r
2372 mPopulatePending = false;
\r
2373 for (int i = 0; i < mItems.size(); i++) {
\r
2374 ItemInfo ii = mItems.get(i);
\r
2375 if (ii.scrolling) {
\r
2376 needPopulate = true;
\r
2377 ii.scrolling = false;
\r
2380 if (needPopulate) {
\r
2382 ViewCompat.postOnAnimation(this, mEndScrollRunnable);
\r
2384 mEndScrollRunnable.run();
\r
2389 private boolean isGutterDrag(float x, float dx, float y, float dy) {
\r
2390 if (isHorizontal()) {
\r
2391 return (x < mGutterSize && dx > 0) || (x > getWidth() - mGutterSize && dx < 0);
\r
2393 return (y < mGutterSize && dy > 0) || (y > getHeight() - mGutterSize && dy < 0);
\r
2397 private void enableLayers(boolean enable) {
\r
2398 final int childCount = getChildCount();
\r
2399 for (int i = 0; i < childCount; i++) {
\r
2400 final int layerType = enable ?
\r
2401 ViewCompat.LAYER_TYPE_HARDWARE : ViewCompat.LAYER_TYPE_NONE;
\r
2402 ViewCompat.setLayerType(getChildAt(i), layerType, null);
\r
2407 public boolean onInterceptTouchEvent(MotionEvent ev) {
\r
2409 * This method JUST determines whether we want to intercept the motion.
\r
2410 * If we return true, onMotionEvent will be called and we do the actual
\r
2411 * scrolling there.
\r
2414 final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
\r
2416 // Always take care of the touch gesture being complete.
\r
2417 if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
\r
2418 // Release the drag.
\r
2419 if (DEBUG) Log.v(TAG, "Intercept done!");
\r
2420 if (isHorizontal()) {
\r
2423 mIsBeingDragged = false;
\r
2424 mIsUnableToDrag = false;
\r
2425 mActivePointerId = INVALID_POINTER;
\r
2426 if (mVelocityTracker != null) {
\r
2427 mVelocityTracker.recycle();
\r
2428 mVelocityTracker = null;
\r
2434 // Nothing more to do here if we have decided whether or not we
\r
2436 if (action != MotionEvent.ACTION_DOWN) {
\r
2437 if (mIsBeingDragged) {
\r
2438 if (DEBUG) Log.v(TAG, "Intercept returning true!");
\r
2441 if (mIsUnableToDrag) {
\r
2442 if (DEBUG) Log.v(TAG, "Intercept returning false!");
\r
2447 if (isHorizontal()) {
\r
2450 case MotionEvent.ACTION_MOVE: {
\r
2452 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
\r
2453 * whether the user has moved far enough from his original down touch.
\r
2457 * Locally do absolute value. mLastMotionY is set to the y value
\r
2458 * of the down event.
\r
2460 final int activePointerId = mActivePointerId;
\r
2461 if (activePointerId == INVALID_POINTER) {
\r
2465 final int pointerIndex
\r
2466 = MotionEventCompat.findPointerIndex(ev, activePointerId);
\r
2467 final float x = MotionEventCompat.getX(ev, pointerIndex);
\r
2468 final float dx = x - mLastMotionX;
\r
2469 final float xDiff = Math.abs(dx);
\r
2470 final float y = MotionEventCompat.getY(ev, pointerIndex);
\r
2471 final float yDiff = Math.abs(y - mInitialMotionY);
\r
2473 if (dx != 0 && !isGutterDrag(mLastMotionX, dx, 0, 0) &&
\r
2474 canScroll(this, false, (int) dx, 0, (int) x, (int) y)) {
\r
2475 // Nested view has scrollable
\r
2476 // area under this point. Let it be handled there.
\r
2479 mIsUnableToDrag = true;
\r
2482 if (xDiff > mTouchSlop && xDiff * 0.5f > yDiff) {
\r
2483 if (DEBUG) Log.v(TAG, getContext().getString(R.string.debug_start_drag));
\r
2484 mIsBeingDragged = true;
\r
2485 requestParentDisallowInterceptTouchEvent(true);
\r
2486 setScrollState(SCROLL_STATE_DRAGGING);
\r
2487 mLastMotionX = dx > 0 ? mInitialMotionX + mTouchSlop :
\r
2488 mInitialMotionX - mTouchSlop;
\r
2490 setScrollingCacheEnabled(true);
\r
2491 } else if (yDiff > mTouchSlop) {
\r
2492 // The finger has moved enough in the vertical
\r
2493 // direction to be counted as a drag... abort
\r
2494 // any attempt to drag horizontally, to work correctly
\r
2495 // with children that have scrolling containers.
\r
2497 Log.v(TAG, getContext().getString(R.string.debug_start_unable_drag));
\r
2498 mIsUnableToDrag = true;
\r
2500 if (mIsBeingDragged && performDrag(x, 0)) {
\r
2501 // Scroll to follow the motion event
\r
2502 ViewCompat.postInvalidateOnAnimation(this);
\r
2507 case MotionEvent.ACTION_DOWN: {
\r
2509 * Remember location of down touch.
\r
2510 * ACTION_DOWN always refers to pointer index 0.
\r
2512 mLastMotionX = mInitialMotionX = ev.getX();
\r
2513 mLastMotionY = mInitialMotionY = ev.getY();
\r
2514 mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
\r
2515 mIsUnableToDrag = false;
\r
2517 mIsScrollStarted = true;
\r
2518 mScroller.computeScrollOffset();
\r
2519 if (mScrollState == SCROLL_STATE_SETTLING &&
\r
2520 Math.abs(mScroller.getFinalX() - mScroller.getCurrX()) > mCloseEnough) {
\r
2521 // Let the user 'catch' the pager as it animates.
\r
2522 mScroller.abortAnimation();
\r
2523 mPopulatePending = false;
\r
2525 mIsBeingDragged = true;
\r
2526 requestParentDisallowInterceptTouchEvent(true);
\r
2527 setScrollState(SCROLL_STATE_DRAGGING);
\r
2529 completeScroll(false);
\r
2530 mIsBeingDragged = false;
\r
2533 if (DEBUG) Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY
\r
2534 + " mIsBeingDragged=" + mIsBeingDragged
\r
2535 + "mIsUnableToDrag=" + mIsUnableToDrag);
\r
2539 case MotionEventCompat.ACTION_POINTER_UP:
\r
2540 onSecondaryPointerUp(ev);
\r
2544 /* if (mVelocityTracker == null) {
\r
2545 mVelocityTracker = VelocityTracker.obtain();
\r
2547 mVelocityTracker.addMovement(ev);
\r
2550 * The only time we want to intercept motion events is if we are in the
\r
2555 case MotionEvent.ACTION_MOVE: {
\r
2557 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
\r
2558 * whether the user has moved far enough from his original down touch.
\r
2562 * Locally do absolute value. mLastMotionY is set to the y value
\r
2563 * of the down event.
\r
2565 final int activePointerId = mActivePointerId;
\r
2566 if (activePointerId == INVALID_POINTER) {
\r
2567 // If we don't have a valid id, the touch down wasn't on content.
\r
2571 final int pointerIndex
\r
2572 = MotionEventCompat.findPointerIndex(ev, activePointerId);
\r
2573 final float y = MotionEventCompat.getY(ev, pointerIndex);
\r
2574 final float dy = y - mLastMotionY;
\r
2575 final float yDiff = Math.abs(dy);
\r
2577 = MotionEventCompat.getX(ev, pointerIndex);
\r
2578 final float xDiff = Math.abs(x - mInitialMotionX);
\r
2580 if (dy != 0 && !isGutterDrag(0, 0, mLastMotionY, dy) &&
\r
2581 canScroll(this, false, 0,
\r
2582 (int) dy, (int) x, (int) y)) {
\r
2583 // Nested view has scrollable
\r
2584 // area under this point.
\r
2585 // Let it be handled there.
\r
2588 mIsUnableToDrag = true;
\r
2591 if (yDiff > mTouchSlop && yDiff * 0.5f > xDiff) {
\r
2592 if (DEBUG) Log.v(TAG, getContext().getString(R.string.debug_start_drag));
\r
2593 mIsBeingDragged = true;
\r
2594 requestParentDisallowInterceptTouchEvent(true);
\r
2595 setScrollState(SCROLL_STATE_DRAGGING);
\r
2596 mLastMotionY = dy > 0 ? mInitialMotionY + mTouchSlop :
\r
2597 mInitialMotionY - mTouchSlop;
\r
2599 setScrollingCacheEnabled(true);
\r
2600 } else if (xDiff > mTouchSlop) {
\r
2601 // The finger has moved enough in the vertical
\r
2602 // direction to be counted as a drag... abort
\r
2603 // any attempt to drag horizontally, to work correctly
\r
2604 // with children that have scrolling containers.
\r
2606 Log.v(TAG, getContext().getString(R.string.debug_start_unable_drag));
\r
2607 mIsUnableToDrag = true;
\r
2609 if (mIsBeingDragged && performDrag(0, y)) {
\r
2610 // Scroll to follow the motion event
\r
2611 ViewCompat.postInvalidateOnAnimation(this);
\r
2616 case MotionEvent.ACTION_DOWN: {
\r
2618 * Remember location of down touch.
\r
2619 * ACTION_DOWN always refers to pointer index 0.
\r
2621 mLastMotionX = mInitialMotionX = ev.getX();
\r
2622 mLastMotionY = mInitialMotionY = ev.getY();
\r
2623 mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
\r
2624 mIsUnableToDrag = false;
\r
2626 mScroller.computeScrollOffset();
\r
2627 if (mScrollState == SCROLL_STATE_SETTLING &&
\r
2628 Math.abs(mScroller.getFinalY() - mScroller.getCurrY()) > mCloseEnough) {
\r
2629 // Let the user 'catch' the pager as it animates.
\r
2630 mScroller.abortAnimation();
\r
2631 mPopulatePending = false;
\r
2633 mIsBeingDragged = true;
\r
2634 requestParentDisallowInterceptTouchEvent(true);
\r
2635 setScrollState(SCROLL_STATE_DRAGGING);
\r
2637 completeScroll(false);
\r
2638 mIsBeingDragged = false;
\r
2641 if (DEBUG) Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY
\r
2642 + " mIsBeingDragged=" + mIsBeingDragged
\r
2643 + "mIsUnableToDrag=" + mIsUnableToDrag);
\r
2647 case MotionEventCompat.ACTION_POINTER_UP:
\r
2648 onSecondaryPointerUp(ev);
\r
2653 if (mVelocityTracker == null) {
\r
2654 mVelocityTracker = VelocityTracker.obtain();
\r
2656 mVelocityTracker.addMovement(ev);
\r
2657 return mIsBeingDragged;
\r
2661 public boolean onTouchEvent(MotionEvent ev) {
\r
2662 if (mFakeDragging) {
\r
2663 // A fake drag is in progress already, ignore this real one
\r
2664 // but still eat the touch events.
\r
2665 // (It is likely that the user is multi-touching the screen.)
\r
2669 if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
\r
2670 // Don't handle edge touches immediately -- they may actually belong to one of our
\r
2675 if (mAdapter == null || mAdapter.getCount() == 0) {
\r
2676 // Nothing to present or scroll; nothing to touch.
\r
2680 if (mVelocityTracker == null) {
\r
2681 mVelocityTracker = VelocityTracker.obtain();
\r
2683 mVelocityTracker.addMovement(ev);
\r
2685 final int action = ev.getAction();
\r
2686 boolean needsInvalidate = false;
\r
2688 if (isHorizontal()) {
\r
2689 switch (action & MotionEventCompat.ACTION_MASK) {
\r
2690 case MotionEvent.ACTION_DOWN: {
\r
2691 mScroller.abortAnimation();
\r
2692 mPopulatePending = false;
\r
2695 // Remember where the motion event started
\r
2696 mLastMotionX = mInitialMotionX = ev.getX();
\r
2697 mLastMotionY = mInitialMotionY = ev.getY();
\r
2698 mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
\r
2701 case MotionEvent.ACTION_MOVE:
\r
2702 if (!mIsBeingDragged) {
\r
2703 final int pointerIndex
\r
2704 = MotionEventCompat.findPointerIndex(ev,
\r
2705 mActivePointerId);
\r
2706 if (pointerIndex == -1) {
\r
2707 // A child has consumed some
\r
2708 // touch events and put us into an inconsistent state.
\r
2709 needsInvalidate = resetTouch();
\r
2712 final float x = MotionEventCompat.getX(ev, pointerIndex);
\r
2713 final float xDiff = Math.abs(x - mLastMotionX);
\r
2715 = MotionEventCompat.getY(ev,
\r
2717 final float yDiff = Math.abs(y - mLastMotionY);
\r
2718 if (xDiff > mTouchSlop && xDiff > yDiff) {
\r
2720 Log.v(TAG, getContext().getString(R.string.debug_start_drag));
\r
2721 mIsBeingDragged = true;
\r
2722 requestParentDisallowInterceptTouchEvent(true);
\r
2723 mLastMotionX = x - mInitialMotionX > 0 ? mInitialMotionX + mTouchSlop :
\r
2724 mInitialMotionX - mTouchSlop;
\r
2726 setScrollState(SCROLL_STATE_DRAGGING);
\r
2727 setScrollingCacheEnabled(true);
\r
2729 // Disallow Parent Intercept, just in case
\r
2730 ViewParent parent = getParent();
\r
2731 if (parent != null) {
\r
2732 parent.requestDisallowInterceptTouchEvent(true);
\r
2736 // Not else! Note that mIsBeingDragged can be set above.
\r
2737 if (mIsBeingDragged) {
\r
2738 // Scroll to follow the motion event
\r
2739 final int activePointerIndex = MotionEventCompat.findPointerIndex(
\r
2740 ev, mActivePointerId);
\r
2741 final float x = MotionEventCompat.getX(ev, activePointerIndex);
\r
2742 needsInvalidate |= performDrag(x, 0);
\r
2745 case MotionEvent.ACTION_UP:
\r
2746 if (mIsBeingDragged) {
\r
2747 final VelocityTracker velocityTracker = mVelocityTracker;
\r
2748 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
\r
2749 int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(
\r
2750 velocityTracker, mActivePointerId);
\r
2751 mPopulatePending = true;
\r
2752 final int width = getClientWidth();
\r
2753 final int scrollX = getScrollX();
\r
2754 final ItemInfo ii = infoForCurrentScrollPosition();
\r
2755 final float marginOffset = (float) mPageMargin / width;
\r
2756 final int currentPage = ii.position;
\r
2757 final float pageOffset = (((float) scrollX / width) - ii.offset)
\r
2758 / (ii.widthFactor + marginOffset);
\r
2759 final int activePointerIndex =
\r
2760 MotionEventCompat.findPointerIndex(ev, mActivePointerId);
\r
2761 final float x = MotionEventCompat.getX(ev, activePointerIndex);
\r
2762 final int totalDelta = (int) (x - mInitialMotionX);
\r
2763 int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
\r
2765 setCurrentItemInternal(nextPage, true, true, initialVelocity);
\r
2767 needsInvalidate = resetTouch();
\r
2770 case MotionEvent.ACTION_CANCEL:
\r
2771 if (mIsBeingDragged) {
\r
2772 scrollToItem(mCurItem, true, 0, false);
\r
2773 needsInvalidate = resetTouch();
\r
2776 case MotionEventCompat.ACTION_POINTER_DOWN: {
\r
2777 final int index = MotionEventCompat.getActionIndex(ev);
\r
2778 final float x = MotionEventCompat.getX(ev, index);
\r
2780 mActivePointerId = MotionEventCompat.getPointerId(ev, index);
\r
2783 case MotionEventCompat.ACTION_POINTER_UP:
\r
2784 onSecondaryPointerUp(ev);
\r
2785 mLastMotionX = MotionEventCompat.getX(ev,
\r
2786 MotionEventCompat.findPointerIndex(ev, mActivePointerId));
\r
2790 switch (action & MotionEventCompat.ACTION_MASK) {
\r
2791 case MotionEvent.ACTION_DOWN: {
\r
2792 mScroller.abortAnimation();
\r
2793 mPopulatePending = false;
\r
2796 // Remember where the motion event started
\r
2797 mLastMotionX = mInitialMotionX = ev.getX();
\r
2798 mLastMotionY = mInitialMotionY = ev.getY();
\r
2799 mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
\r
2802 case MotionEvent.ACTION_MOVE:
\r
2803 if (!mIsBeingDragged) {
\r
2804 final int pointerIndex =
\r
2805 MotionEventCompat.findPointerIndex(ev, mActivePointerId);
\r
2806 final float y = MotionEventCompat.getY(ev, pointerIndex);
\r
2808 = Math.abs(y - mLastMotionY);
\r
2810 = MotionEventCompat.getX(ev, pointerIndex);
\r
2811 final float xDiff = Math.abs(x - mLastMotionX);
\r
2813 if (yDiff > mTouchSlop && yDiff > xDiff) {
\r
2815 Log.v(TAG, getContext().getString(R.string.debug_start_drag));
\r
2816 mIsBeingDragged = true;
\r
2817 requestParentDisallowInterceptTouchEvent(true);
\r
2818 mLastMotionY = y - mInitialMotionY > 0 ? mInitialMotionY + mTouchSlop :
\r
2819 mInitialMotionY - mTouchSlop;
\r
2821 setScrollState(SCROLL_STATE_DRAGGING);
\r
2822 setScrollingCacheEnabled(true);
\r
2824 // Disallow Parent Intercept, just in case
\r
2825 ViewParent parent = getParent();
\r
2826 if (parent != null) {
\r
2827 parent.requestDisallowInterceptTouchEvent(true);
\r
2831 // Not else! Note that mIsBeingDragged can be set above.
\r
2832 if (mIsBeingDragged) {
\r
2833 // Scroll to follow the motion event
\r
2834 final int activePointerIndex = MotionEventCompat.findPointerIndex(
\r
2835 ev, mActivePointerId);
\r
2836 final float y = MotionEventCompat.getY(ev, activePointerIndex);
\r
2837 needsInvalidate |= performDrag(0, y);
\r
2840 case MotionEvent.ACTION_UP:
\r
2841 if (mIsBeingDragged) {
\r
2842 final VelocityTracker velocityTracker = mVelocityTracker;
\r
2843 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
\r
2844 int initialVelocity = (int) VelocityTrackerCompat.getYVelocity(
\r
2845 velocityTracker, mActivePointerId);
\r
2846 mPopulatePending = true;
\r
2847 final int height = getClientHeight();
\r
2848 final int scrollY = getScrollY();
\r
2849 final ItemInfo ii = infoForCurrentScrollPosition();
\r
2850 final int currentPage = ii.position;
\r
2851 final float pageOffset =
\r
2852 (((float) scrollY / height) - ii.offset) / ii.heightFactor;
\r
2853 final int activePointerIndex =
\r
2854 MotionEventCompat.findPointerIndex(ev, mActivePointerId);
\r
2855 final float y = MotionEventCompat.getY(ev, activePointerIndex);
\r
2856 final int totalDelta = (int) (y - mInitialMotionY);
\r
2857 int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
\r
2859 setCurrentItemInternal(nextPage, true, true, initialVelocity);
\r
2861 mActivePointerId = INVALID_POINTER;
\r
2863 needsInvalidate = mTopEdge.onRelease() | mBottomEdge.onRelease();
\r
2866 case MotionEvent.ACTION_CANCEL:
\r
2867 if (mIsBeingDragged) {
\r
2868 scrollToItem(mCurItem, true, 0, false);
\r
2869 mActivePointerId = INVALID_POINTER;
\r
2871 needsInvalidate = mTopEdge.onRelease() | mBottomEdge.onRelease();
\r
2874 case MotionEventCompat.ACTION_POINTER_DOWN: {
\r
2875 final int index = MotionEventCompat.getActionIndex(ev);
\r
2876 final float y = MotionEventCompat.getY(ev, index);
\r
2878 mActivePointerId = MotionEventCompat.getPointerId(ev, index);
\r
2881 case MotionEventCompat.ACTION_POINTER_UP:
\r
2882 onSecondaryPointerUp(ev);
\r
2883 mLastMotionY = MotionEventCompat.getY(ev,
\r
2884 MotionEventCompat.findPointerIndex(ev, mActivePointerId));
\r
2888 if (needsInvalidate) {
\r
2889 ViewCompat.postInvalidateOnAnimation(this);
\r
2894 private boolean resetTouch() {
\r
2895 boolean needsInvalidate;
\r
2896 mActivePointerId = INVALID_POINTER;
\r
2898 needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease();
\r
2899 return needsInvalidate;
\r
2902 private void requestParentDisallowInterceptTouchEvent(boolean disallowIntercept) {
\r
2903 final ViewParent parent = getParent();
\r
2904 if (parent != null) {
\r
2905 parent.requestDisallowInterceptTouchEvent(disallowIntercept);
\r
2909 private boolean performDrag(float x, float y) {
\r
2910 boolean needsInvalidate = false;
\r
2911 if (isHorizontal()) {
\r
2912 final float deltaX = mLastMotionX - x;
\r
2915 float oldScrollX = getScrollX();
\r
2916 float scrollX = oldScrollX + deltaX;
\r
2917 final int width = getClientWidth();
\r
2919 float leftBound = width * mFirstOffset;
\r
2920 float rightBound = width * mLastOffset;
\r
2921 boolean leftAbsolute = true;
\r
2922 boolean rightAbsolute = true;
\r
2924 final ItemInfo firstItem = mItems.get(0);
\r
2925 final ItemInfo lastItem = mItems.get(mItems.size() - 1);
\r
2926 if (firstItem.position != 0) {
\r
2927 leftAbsolute = false;
\r
2928 leftBound = firstItem.offset * width;
\r
2930 if (lastItem.position != mAdapter.getCount() - 1) {
\r
2931 rightAbsolute = false;
\r
2932 rightBound = lastItem.offset * width;
\r
2935 if (scrollX < leftBound) {
\r
2936 if (leftAbsolute) {
\r
2937 float over = leftBound - scrollX;
\r
2938 needsInvalidate = mLeftEdge.onPull(Math.abs(over) / width);
\r
2940 scrollX = leftBound;
\r
2941 } else if (scrollX > rightBound) {
\r
2942 if (rightAbsolute) {
\r
2943 float over = scrollX - rightBound;
\r
2944 needsInvalidate = mRightEdge.onPull(Math.abs(over) / width);
\r
2946 scrollX = rightBound;
\r
2948 // Don't lose the rounded component
\r
2949 mLastMotionX += scrollX - (int) scrollX;
\r
2950 scrollTo((int) scrollX, getScrollY());
\r
2951 pageScrolled((int) scrollX, 0);
\r
2954 final float deltaY = mLastMotionY - y;
\r
2957 float oldScrollY = getScrollY();
\r
2958 float scrollY = oldScrollY + deltaY;
\r
2959 final int height = getClientHeight();
\r
2961 float topBound = height * mFirstOffset;
\r
2962 float bottomBound = height * mLastOffset;
\r
2963 boolean topAbsolute = true;
\r
2964 boolean bottomAbsolute = true;
\r
2966 final ItemInfo firstItem = mItems.get(0);
\r
2967 final ItemInfo lastItem = mItems.get(mItems.size() - 1);
\r
2968 if (firstItem.position != 0) {
\r
2969 topAbsolute = false;
\r
2970 topBound = firstItem.offset * height;
\r
2972 if (lastItem.position != mAdapter.getCount() - 1) {
\r
2973 bottomAbsolute = false;
\r
2974 bottomBound = lastItem.offset * height;
\r
2977 if (scrollY < topBound) {
\r
2978 if (topAbsolute) {
\r
2979 float over = topBound - scrollY;
\r
2980 needsInvalidate = mTopEdge.onPull(Math.abs(over) / height);
\r
2982 scrollY = topBound;
\r
2983 } else if (scrollY > bottomBound) {
\r
2984 if (bottomAbsolute) {
\r
2985 float over = scrollY - bottomBound;
\r
2986 needsInvalidate = mBottomEdge.onPull(Math.abs(over) / height);
\r
2988 scrollY = bottomBound;
\r
2990 // Don't lose the rounded component
\r
2991 mLastMotionX += scrollY - (int) scrollY;
\r
2992 scrollTo(getScrollX(), (int) scrollY);
\r
2993 pageScrolled(0, (int) scrollY);
\r
2996 return needsInvalidate;
\r
3000 * @return Info about the page at the current scroll position.
\r
3001 * This can be synthetic for a missing middle page; the 'object' field can be null.
\r
3003 private ItemInfo infoForCurrentScrollPosition() {
\r
3004 ItemInfo lastItem = null;
\r
3005 if (isHorizontal()) {
\r
3006 final int width = getClientWidth();
\r
3007 final float scrollOffset = width > 0 ? (float) getScrollX() / width : 0;
\r
3008 final float marginOffset = width > 0 ? (float) mPageMargin / width : 0;
\r
3010 float lastOffset = 0.f;
\r
3011 float lastWidth = 0.f;
\r
3012 boolean first = true;
\r
3014 for (int i = 0; i < mItems.size(); i++) {
\r
3015 ItemInfo ii = mItems.get(i);
\r
3017 if (!first && ii.position != lastPos + 1) {
\r
3018 // Create a synthetic item for a missing page.
\r
3020 ii.offset = lastOffset + lastWidth + marginOffset;
\r
3021 ii.position = lastPos + 1;
\r
3022 ii.widthFactor = mAdapter.getPageWidth(ii.position);
\r
3025 offset = ii.offset;
\r
3027 final float leftBound = offset;
\r
3028 final float rightBound = offset + ii.widthFactor + marginOffset;
\r
3029 if (first || scrollOffset >= leftBound) {
\r
3030 if (scrollOffset < rightBound || i == mItems.size() - 1) {
\r
3037 lastPos = ii.position;
\r
3038 lastOffset = offset;
\r
3039 lastWidth = ii.widthFactor;
\r
3043 final int height = getClientHeight();
\r
3044 final float scrollOffset = height > 0 ? (float) getScrollY() / height : 0;
\r
3045 final float marginOffset = height > 0 ? (float) mPageMargin / height : 0;
\r
3047 float lastOffset = 0.f;
\r
3048 float lastHeight = 0.f;
\r
3049 boolean first = true;
\r
3051 for (int i = 0; i < mItems.size(); i++) {
\r
3052 ItemInfo ii = mItems.get(i);
\r
3054 if (!first && ii.position != lastPos + 1) {
\r
3055 // Create a synthetic item for a missing page.
\r
3057 ii.offset = lastOffset + lastHeight + marginOffset;
\r
3058 ii.position = lastPos + 1;
\r
3059 ii.heightFactor = mAdapter.getPageWidth(ii.position);
\r
3062 offset = ii.offset;
\r
3064 final float topBound = offset;
\r
3065 final float bottomBound = offset + ii.heightFactor + marginOffset;
\r
3066 if (first || scrollOffset >= topBound) {
\r
3067 if (scrollOffset < bottomBound || i == mItems.size() - 1) {
\r
3074 lastPos = ii.position;
\r
3075 lastOffset = offset;
\r
3076 lastHeight = ii.heightFactor;
\r
3084 private int determineTargetPage(int currentPage, float pageOffset,
\r
3085 int velocity, int deltaX, int deltaY) {
\r
3087 if (isHorizontal()) {
\r
3088 if (Math.abs(deltaX) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) {
\r
3089 targetPage = velocity > 0 ? currentPage : currentPage + 1;
\r
3091 final float truncator = currentPage >= mCurItem ? 0.4f : 0.6f;
\r
3092 targetPage = (int) (currentPage + pageOffset + truncator);
\r
3095 if (Math.abs(deltaY) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) {
\r
3096 targetPage = velocity > 0 ? currentPage : currentPage + 1;
\r
3098 final float truncator = currentPage >= mCurItem ? 0.4f : 0.6f;
\r
3099 targetPage = (int) (currentPage + pageOffset + truncator);
\r
3103 if (mItems.size() > 0) {
\r
3104 final ItemInfo firstItem = mItems.get(0);
\r
3105 final ItemInfo lastItem = mItems.get(mItems.size() - 1);
\r
3107 // Only let the user target pages we have items for
\r
3108 targetPage = Math.max(firstItem.position, Math.min(targetPage, lastItem.position));
\r
3111 return targetPage;
\r
3115 public void draw(Canvas canvas) {
\r
3116 super.draw(canvas);
\r
3117 boolean needsInvalidate = false;
\r
3119 final int overScrollMode = ViewCompat.getOverScrollMode(this);
\r
3120 if (overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS ||
\r
3121 (overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS &&
\r
3122 mAdapter != null && mAdapter.getCount() > 1)) {
\r
3123 if (isHorizontal()) {
\r
3124 if (!mLeftEdge.isFinished()) {
\r
3125 final int restoreCount = canvas.save();
\r
3126 final int height = getHeight() - getPaddingTop() - getPaddingBottom();
\r
3127 final int width = getWidth();
\r
3129 canvas.rotate(270);
\r
3130 canvas.translate(-height + getPaddingTop(), mFirstOffset * width);
\r
3131 mLeftEdge.setSize(height, width);
\r
3132 needsInvalidate |= mLeftEdge.draw(canvas);
\r
3133 canvas.restoreToCount(restoreCount);
\r
3135 if (!mRightEdge.isFinished()) {
\r
3136 final int restoreCount = canvas.save();
\r
3137 final int width = getWidth();
\r
3138 final int height = getHeight() - getPaddingTop() - getPaddingBottom();
\r
3140 canvas.rotate(90);
\r
3141 canvas.translate(-getPaddingTop(), -(mLastOffset + 1) * width);
\r
3142 mRightEdge.setSize(height, width);
\r
3143 needsInvalidate |= mRightEdge.draw(canvas);
\r
3144 canvas.restoreToCount(restoreCount);
\r
3146 mLeftEdge.finish();
\r
3147 mRightEdge.finish();
\r
3150 if (!mTopEdge.isFinished()) {
\r
3151 final int restoreCount = canvas.save();
\r
3152 final int height = getHeight();
\r
3153 final int width = getWidth() - getPaddingLeft() - getPaddingRight();
\r
3155 canvas.translate(getPaddingLeft(), mFirstOffset * height);
\r
3156 mTopEdge.setSize(width, height);
\r
3157 needsInvalidate |= mTopEdge.draw(canvas);
\r
3158 canvas.restoreToCount(restoreCount);
\r
3160 if (!mBottomEdge.isFinished()) {
\r
3161 final int restoreCount = canvas.save();
\r
3162 final int height = getHeight();
\r
3163 final int width = getWidth() - getPaddingLeft() - getPaddingRight();
\r
3165 canvas.rotate(180);
\r
3166 canvas.translate(-width - getPaddingLeft(), -(mLastOffset + 1) * height);
\r
3167 mBottomEdge.setSize(width, height);
\r
3168 needsInvalidate |= mBottomEdge.draw(canvas);
\r
3169 canvas.restoreToCount(restoreCount);
\r
3171 mTopEdge.finish();
\r
3172 mBottomEdge.finish();
\r
3178 if (needsInvalidate) {
\r
3180 ViewCompat.postInvalidateOnAnimation(this);
\r
3185 protected void onDraw(Canvas canvas) {
\r
3186 super.onDraw(canvas);
\r
3188 // Draw the margin drawable between pages if needed.
\r
3189 if (mPageMargin > 0 && mMarginDrawable != null && mItems.size() > 0 && mAdapter != null) {
\r
3190 if (isHorizontal()) {
\r
3191 final int scrollX = getScrollX();
\r
3192 final int width = getWidth();
\r
3194 final float marginOffset = (float) mPageMargin / width;
\r
3195 int itemIndex = 0;
\r
3196 ItemInfo ii = mItems.get(0);
\r
3197 float offset = ii.offset;
\r
3198 final int itemCount = mItems.size();
\r
3199 final int firstPos = ii.position;
\r
3200 final int lastPos = mItems.get(itemCount - 1).position;
\r
3201 for (int pos = firstPos; pos < lastPos; pos++) {
\r
3202 while (pos > ii.position && itemIndex < itemCount) {
\r
3203 ii = mItems.get(++itemIndex);
\r
3207 if (pos == ii.position) {
\r
3208 drawAt = (ii.offset + ii.widthFactor) * width;
\r
3209 offset = ii.offset + ii.widthFactor + marginOffset;
\r
3211 float widthFactor = mAdapter.getPageWidth(pos);
\r
3212 drawAt = (offset + widthFactor) * width;
\r
3213 offset += widthFactor + marginOffset;
\r
3216 if (drawAt + mPageMargin > scrollX) {
\r
3217 mMarginDrawable.setBounds(Math.round(drawAt), mTopPageBounds,
\r
3218 Math.round(drawAt + mPageMargin), mBottomPageBounds);
\r
3219 mMarginDrawable.draw(canvas);
\r
3222 if (drawAt > scrollX + width) {
\r
3223 break; // No more visible, no sense in continuing
\r
3227 final int scrollY = getScrollY();
\r
3228 final int height = getHeight();
\r
3230 final float marginOffset = (float) mPageMargin / height;
\r
3231 int itemIndex = 0;
\r
3232 ItemInfo ii = mItems.get(0);
\r
3233 float offset = ii.offset;
\r
3234 final int itemCount = mItems.size();
\r
3235 final int firstPos = ii.position;
\r
3236 final int lastPos = mItems.get(itemCount - 1).position;
\r
3237 for (int pos = firstPos; pos < lastPos; pos++) {
\r
3238 while (pos > ii.position && itemIndex < itemCount) {
\r
3239 ii = mItems.get(++itemIndex);
\r
3243 if (pos == ii.position) {
\r
3244 drawAt = (ii.offset + ii.heightFactor) * height;
\r
3245 offset = ii.offset + ii.heightFactor + marginOffset;
\r
3247 float heightFactor = mAdapter.getPageWidth(pos);
\r
3248 drawAt = (offset + heightFactor) * height;
\r
3249 offset += heightFactor + marginOffset;
\r
3252 if (drawAt + mPageMargin > scrollY) {
\r
3253 mMarginDrawable.setBounds(mLeftPageBounds, (int) drawAt,
\r
3254 mRightPageBounds, (int) (drawAt + mPageMargin + 0.5f));
\r
3255 mMarginDrawable.draw(canvas);
\r
3258 if (drawAt > scrollY + height) {
\r
3259 break; // No more visible, no sense in continuing
\r
3267 * Start a fake drag of the pager.
\r
3269 * <p>A fake drag can be useful if you want to synchronize the motion of the ViewPager
\r
3270 * with the touch scrolling of another view, while still letting the ViewPager
\r
3271 * control the snapping motion and fling behavior. (e.g. parallax-scrolling tabs.)
\r
3272 * Call {@link #fakeDragBy(float)} to simulate the actual drag motion. Call
\r
3273 * {@link #endFakeDrag()} to complete the fake drag and fling as necessary.
\r
3275 * <p>During a fake drag the ViewPager will ignore all touch events. If a real drag
\r
3276 * is already in progress, this method will return false.
\r
3278 * @return true if the fake drag began successfully, false if it could not be started.
\r
3279 * @see #fakeDragBy(float)
\r
3280 * @see #endFakeDrag()
\r
3282 public boolean beginFakeDrag() {
\r
3283 if (mIsBeingDragged) {
\r
3286 mFakeDragging = true;
\r
3287 setScrollState(SCROLL_STATE_DRAGGING);
\r
3288 if (isHorizontal()) {
\r
3289 mInitialMotionX = mLastMotionX = 0;
\r
3291 mInitialMotionY = mLastMotionY = 0;
\r
3293 if (mVelocityTracker == null) {
\r
3294 mVelocityTracker = VelocityTracker.obtain();
\r
3296 mVelocityTracker.clear();
\r
3298 final long time = SystemClock.uptimeMillis();
\r
3299 final MotionEvent ev = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 0, 0, 0);
\r
3300 mVelocityTracker.addMovement(ev);
\r
3302 mFakeDragBeginTime = time;
\r
3307 * End a fake drag of the pager.
\r
3309 * @see #beginFakeDrag()
\r
3310 * @see #endFakeDrag()
\r
3312 public void endFakeDrag() {
\r
3313 if (!mFakeDragging) {
\r
3314 throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
\r
3317 if (mAdapter != null) {
\r
3318 if (isHorizontal()) {
\r
3319 final VelocityTracker velocityTracker = mVelocityTracker;
\r
3320 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
\r
3321 int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(
\r
3322 velocityTracker, mActivePointerId);
\r
3323 mPopulatePending = true;
\r
3324 final int width = getClientWidth();
\r
3325 final int scrollX = getScrollX();
\r
3326 final ItemInfo ii = infoForCurrentScrollPosition();
\r
3327 final int currentPage = ii.position;
\r
3328 final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor;
\r
3329 final int totalDelta = (int) (mLastMotionX - mInitialMotionX);
\r
3330 int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
\r
3332 setCurrentItemInternal(nextPage, true, true, initialVelocity);
\r
3334 final VelocityTracker velocityTracker = mVelocityTracker;
\r
3335 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
\r
3336 int initialVelocity = (int) VelocityTrackerCompat.getYVelocity(
\r
3337 velocityTracker, mActivePointerId);
\r
3338 mPopulatePending = true;
\r
3339 final int height = getClientHeight();
\r
3340 final int scrollY = getScrollY();
\r
3341 final ItemInfo ii = infoForCurrentScrollPosition();
\r
3342 final int currentPage = ii.position;
\r
3343 final float pageOffset = (((float) scrollY / height) - ii.offset) / ii.heightFactor;
\r
3344 final int totalDelta = (int) (mLastMotionY - mInitialMotionY);
\r
3345 int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
\r
3347 setCurrentItemInternal(nextPage, true, true, initialVelocity);
\r
3352 mFakeDragging = false;
\r
3356 * Fake drag by an offset in pixels. You must have called {@link #beginFakeDrag()} first.
\r
3358 * @param xOffset Offset in pixels to drag by.
\r
3359 * @see #beginFakeDrag()
\r
3360 * @see #endFakeDrag()
\r
3362 public void fakeDragBy(float xOffset, float yOffset) {
\r
3363 MotionEvent ev = null;
\r
3364 if (!mFakeDragging) {
\r
3365 throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
\r
3368 if (mAdapter == null) {
\r
3372 if (isHorizontal()) {
\r
3373 mLastMotionX += xOffset;
\r
3375 float oldScrollX = getScrollX();
\r
3376 float scrollX = oldScrollX - xOffset;
\r
3377 final int width = getClientWidth();
\r
3379 float leftBound = width * mFirstOffset;
\r
3380 float rightBound = width * mLastOffset;
\r
3382 final ItemInfo firstItem = mItems.get(0);
\r
3383 final ItemInfo lastItem = mItems.get(mItems.size() - 1);
\r
3384 if (firstItem.position != 0) {
\r
3385 leftBound = firstItem.offset * width;
\r
3387 if (lastItem.position != mAdapter.getCount() - 1) {
\r
3388 rightBound = lastItem.offset * width;
\r
3391 if (scrollX < leftBound) {
\r
3392 scrollX = leftBound;
\r
3393 } else if (scrollX > rightBound) {
\r
3394 scrollX = rightBound;
\r
3396 // Don't lose the rounded component
\r
3397 mLastMotionX += scrollX - (int) scrollX;
\r
3398 scrollTo((int) scrollX, getScrollY());
\r
3399 pageScrolled((int) scrollX, 0);
\r
3401 // Synthesize an event for the VelocityTracker.
\r
3402 final long time = SystemClock.uptimeMillis();
\r
3403 ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE,
\r
3404 mLastMotionX, 0, 0);
\r
3406 mLastMotionY += yOffset;
\r
3408 float oldScrollY = getScrollY();
\r
3409 float scrollY = oldScrollY - yOffset;
\r
3410 final int height = getClientHeight();
\r
3412 float topBound = height * mFirstOffset;
\r
3413 float bottomBound = height * mLastOffset;
\r
3415 final ItemInfo firstItem = mItems.get(0);
\r
3416 final ItemInfo lastItem = mItems.get(mItems.size() - 1);
\r
3417 if (firstItem.position != 0) {
\r
3418 topBound = firstItem.offset * height;
\r
3420 if (lastItem.position != mAdapter.getCount() - 1) {
\r
3421 bottomBound = lastItem.offset * height;
\r
3424 if (scrollY < topBound) {
\r
3425 scrollY = topBound;
\r
3426 } else if (scrollY > bottomBound) {
\r
3427 scrollY = bottomBound;
\r
3429 // Don't lose the rounded component
\r
3430 mLastMotionY += scrollY - (int) scrollY;
\r
3431 scrollTo(getScrollX(), (int) scrollY);
\r
3432 pageScrolled(0, (int) scrollY);
\r
3434 // Synthesize an event for the VelocityTracker.
\r
3435 final long time = SystemClock.uptimeMillis();
\r
3436 ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE,
\r
3437 0, mLastMotionY, 0);
\r
3439 mVelocityTracker.addMovement(ev);
\r
3444 * Returns true if a fake drag is in progress.
\r
3446 * @return true if currently in a fake drag, false otherwise.
\r
3447 * @see #beginFakeDrag()
\r
3448 * @see #fakeDragBy(float)
\r
3449 * @see #endFakeDrag()
\r
3451 public boolean isFakeDragging() {
\r
3452 return mFakeDragging;
\r
3455 private void onSecondaryPointerUp(MotionEvent ev) {
\r
3456 final int pointerIndex = MotionEventCompat.getActionIndex(ev);
\r
3457 final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
\r
3458 if (pointerId == mActivePointerId) {
\r
3459 // This was our active pointer going up. Choose a new
\r
3460 // active pointer and adjust accordingly.
\r
3461 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
\r
3462 if (isHorizontal()) {
\r
3463 mLastMotionX = MotionEventCompat.getX(ev, newPointerIndex);
\r
3465 mLastMotionY = MotionEventCompat.getY(ev, newPointerIndex);
\r
3467 mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
\r
3468 if (mVelocityTracker != null) {
\r
3469 mVelocityTracker.clear();
\r
3474 private void endDrag() {
\r
3475 mIsBeingDragged = false;
\r
3476 mIsUnableToDrag = false;
\r
3478 if (mVelocityTracker != null) {
\r
3479 mVelocityTracker.recycle();
\r
3480 mVelocityTracker = null;
\r
3484 private void setScrollingCacheEnabled(boolean enabled) {
\r
3485 if (mScrollingCacheEnabled != enabled) {
\r
3486 mScrollingCacheEnabled = enabled;
\r
3488 final int size = getChildCount();
\r
3489 for (int i = 0; i < size; ++i) {
\r
3490 final View child = getChildAt(i);
\r
3491 if (child.getVisibility() != GONE) {
\r
3492 child.setDrawingCacheEnabled(enabled);
\r
3499 public boolean canScrollHorizontally(int direction) {
\r
3500 if (mAdapter == null) {
\r
3504 final int width = getClientWidth();
\r
3505 final int scrollX = getScrollX();
\r
3506 if (direction < 0) {
\r
3507 return (scrollX > (int) (width * mFirstOffset));
\r
3508 } else if (direction > 0) {
\r
3509 return (scrollX < (int) (width * mLastOffset));
\r
3515 public boolean internalCanScrollVertically(int direction) {
\r
3516 if (mAdapter == null) {
\r
3520 final int height = getClientHeight();
\r
3521 final int scrollY = getScrollY();
\r
3522 if (direction < 0) {
\r
3523 return (scrollY > (int) (height * mFirstOffset));
\r
3524 } else if (direction > 0) {
\r
3525 return (scrollY < (int) (height * mLastOffset));
\r
3532 * Tests scrollability within child views of v given a delta of dx.
\r
3534 * @param v View to test for horizontal scrollability
\r
3535 * @param checkV Whether the view v passed should itself be checked for scrollability (true),
\r
3536 * or just its children (false).
\r
3537 * @param dx Delta scrolled in pixels
\r
3538 * @param x X coordinate of the active touch point
\r
3539 * @param y Y coordinate of the active touch point
\r
3540 * @return true if child views of v can be scrolled by delta of dx.
\r
3542 protected boolean canScroll(View v, boolean checkV, int dx, int dy, int x, int y) {
\r
3543 if (v instanceof ViewGroup) {
\r
3544 if (isHorizontal()) {
\r
3545 final ViewGroup group = (ViewGroup) v;
\r
3546 final int scrollX = v.getScrollX();
\r
3547 final int scrollY = v.getScrollY();
\r
3548 final int count = group.getChildCount();
\r
3549 // Count backwards - let topmost views consume scroll distance first.
\r
3550 for (int i = count - 1; i >= 0; i--) {
\r
3551 // TODO: Add versioned support here for transformed views.
\r
3552 // This will not work for transformed views in Honeycomb+
\r
3553 final View child = group.getChildAt(i);
\r
3554 if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
\r
3555 y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
\r
3556 canScroll(child, true, dx, 0, x + scrollX - child.getLeft(),
\r
3557 y + scrollY - child.getTop())) {
\r
3561 return checkV && ViewCompat.canScrollHorizontally(v, -dx);
\r
3563 final ViewGroup group = (ViewGroup) v;
\r
3564 final int scrollX = v.getScrollX();
\r
3565 final int scrollY = v.getScrollY();
\r
3566 final int count = group.getChildCount();
\r
3567 // Count backwards - let topmost views consume scroll distance first.
\r
3568 for (int i = count - 1; i >= 0; i--) {
\r
3569 // TODO: Add versioned support here for transformed views.
\r
3570 // This will not work for transformed views in Honeycomb+
\r
3571 final View child = group.getChildAt(i);
\r
3572 if (y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
\r
3573 x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
\r
3574 canScroll(child, true, 0, dy, x + scrollX - child.getLeft(),
\r
3575 y + scrollY - child.getTop())) {
\r
3580 return checkV && ViewCompat.canScrollVertically(v, -dy);
\r
3588 public boolean dispatchKeyEvent(KeyEvent event) {
\r
3589 // Let the focused view and/or our descendants get the key first
\r
3590 return super.dispatchKeyEvent(event) || executeKeyEvent(event);
\r
3594 * You can call this function yourself to have the scroll view perform
\r
3595 * scrolling from a key event, just as if the event had been dispatched to
\r
3596 * it by the view hierarchy.
\r
3598 * @param event The key event to execute.
\r
3599 * @return Return true if the event was handled, else false.
\r
3601 public boolean executeKeyEvent(KeyEvent event) {
\r
3602 boolean handled = false;
\r
3603 if (event.getAction() == KeyEvent.ACTION_DOWN) {
\r
3604 switch (event.getKeyCode()) {
\r
3605 case KeyEvent.KEYCODE_DPAD_LEFT:
\r
3606 handled = arrowScroll(FOCUS_LEFT);
\r
3608 case KeyEvent.KEYCODE_DPAD_RIGHT:
\r
3609 handled = arrowScroll(FOCUS_RIGHT);
\r
3611 case KeyEvent.KEYCODE_TAB:
\r
3612 if (Build.VERSION.SDK_INT >= 11) {
\r
3613 // The focus finder had a bug handling FOCUS_FORWARD and FOCUS_BACKWARD
\r
3614 // before Android 3.0. Ignore the tab key on those devices.
\r
3615 if (KeyEvent.metaStateHasNoModifiers(event.getMetaState())) {
\r
3616 handled = arrowScroll(FOCUS_FORWARD);
\r
3617 } else if (KeyEvent.metaStateHasNoModifiers(event.getMetaState())) {
\r
3618 handled = arrowScroll(FOCUS_BACKWARD);
\r
3627 public boolean arrowScroll(int direction) {
\r
3628 View currentFocused = findFocus();
\r
3629 if (currentFocused == this) {
\r
3630 currentFocused = null;
\r
3631 } else if (currentFocused != null) {
\r
3632 boolean isChild = false;
\r
3633 for (ViewParent parent
\r
3634 = currentFocused.getParent();
\r
3635 parent instanceof ViewGroup;
\r
3636 parent = parent.getParent()) {
\r
3637 if (parent == this) {
\r
3643 // This would cause the focus search down below to fail in fun ways.
\r
3644 final StringBuilder sb = new StringBuilder();
\r
3645 sb.append(currentFocused.getClass().getSimpleName());
\r
3646 for (ViewParent parent
\r
3647 = currentFocused.getParent();
\r
3648 parent instanceof ViewGroup;
\r
3649 parent = parent.getParent()) {
\r
3650 sb.append(" => ").append(parent.getClass().getSimpleName());
\r
3652 Log.e(TAG, "arrowScroll tried to find focus based on non-child " +
\r
3653 "current focused view " + sb.toString());
\r
3654 currentFocused = null;
\r
3658 boolean handled = false;
\r
3661 = FocusFinder.getInstance().findNextFocus(this, currentFocused,
\r
3663 if (nextFocused != null && nextFocused != currentFocused) {
\r
3664 if (isHorizontal()) {
\r
3665 if (direction == View.FOCUS_LEFT) {
\r
3666 // If there is nothing
\r
3667 // to the left, or this is causing us to
\r
3668 // jump to the right,
\r
3669 // then what we really want to do is page left.
\r
3670 final int nextLeft
\r
3671 = getChildRectInPagerCoordinates(mTempRect, nextFocused).left;
\r
3672 final int currLeft
\r
3673 = getChildRectInPagerCoordinates(mTempRect, currentFocused).left;
\r
3674 if (currentFocused != null && nextLeft >= currLeft) {
\r
3675 handled = pageLeft();
\r
3677 handled = nextFocused.requestFocus();
\r
3679 } else if (direction == View.FOCUS_RIGHT) {
\r
3680 // If there is nothing to the right,
\r
3681 // or this is causing us to
\r
3682 // jump to the left,
\r
3683 // then what we really
\r
3684 // want to do is page right.
\r
3685 final int nextLeft
\r
3686 = getChildRectInPagerCoordinates(mTempRect, nextFocused).left;
\r
3687 final int currLeft
\r
3688 = getChildRectInPagerCoordinates(mTempRect, currentFocused).left;
\r
3689 if (currentFocused != null && nextLeft <= currLeft) {
\r
3690 handled = pageRight();
\r
3692 handled = nextFocused.requestFocus();
\r
3694 } else if (direction == FOCUS_LEFT || direction == FOCUS_BACKWARD) {
\r
3695 // Trying to move left and nothing there; try to page.
\r
3696 handled = pageLeft();
\r
3697 } else if (direction == FOCUS_RIGHT || direction == FOCUS_FORWARD) {
\r
3698 // Trying to move right and nothing there; try to page.
\r
3699 handled = pageRight();
\r
3702 if (direction == View.FOCUS_UP) {
\r
3703 // If there is nothing to the left,
\r
3704 // or this is causing us to
\r
3705 // jump to the right,
\r
3706 // then what we really want to do is page left.
\r
3708 = getChildRectInPagerCoordinates(mTempRect, nextFocused).top;
\r
3710 = getChildRectInPagerCoordinates(mTempRect, currentFocused).top;
\r
3711 if (currentFocused != null && nextTop >= currTop) {
\r
3712 handled = pageUp();
\r
3714 handled = nextFocused.requestFocus();
\r
3716 } else if (direction == View.FOCUS_DOWN) {
\r
3717 final int nextDown =
\r
3718 getChildRectInPagerCoordinates(mTempRect, nextFocused).bottom;
\r
3719 final int currDown =
\r
3720 getChildRectInPagerCoordinates(mTempRect, currentFocused).bottom;
\r
3721 if (currentFocused != null && nextDown <= currDown) {
\r
3722 handled = pageDown();
\r
3724 handled = nextFocused.requestFocus();
\r
3726 } else if (direction == FOCUS_UP || direction == FOCUS_BACKWARD) {
\r
3727 // Trying to move left and nothing there; try to page.
\r
3728 handled = pageUp();
\r
3729 } else if (direction == FOCUS_DOWN || direction == FOCUS_FORWARD) {
\r
3730 // Trying to move right and nothing there; try to page.
\r
3731 handled = pageDown();
\r
3736 playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
\r
3744 private Rect getChildRectInPagerCoordinates(Rect outRect, View child) {
\r
3745 if (outRect == null) {
\r
3746 outRect = new Rect();
\r
3748 if (child == null) {
\r
3749 outRect.set(0, 0, 0, 0);
\r
3752 outRect.left = child.getLeft();
\r
3753 outRect.right = child.getRight();
\r
3754 outRect.top = child.getTop();
\r
3755 outRect.bottom = child.getBottom();
\r
3757 ViewParent parent = child.getParent();
\r
3758 while (parent instanceof ViewGroup && parent != this) {
\r
3759 final ViewGroup group = (ViewGroup) parent;
\r
3760 outRect.left += group.getLeft();
\r
3761 outRect.right += group.getRight();
\r
3762 outRect.top += group.getTop();
\r
3763 outRect.bottom += group.getBottom();
\r
3765 parent = group.getParent();
\r
3770 boolean pageLeft() {
\r
3771 if (mCurItem > 0) {
\r
3772 setCurrentItem(mCurItem - 1, true);
\r
3778 boolean pageRight() {
\r
3779 if (mAdapter != null && mCurItem < (mAdapter.getCount() - 1)) {
\r
3780 setCurrentItem(mCurItem + 1, true);
\r
3786 boolean pageUp() {
\r
3787 if (mCurItem > 0) {
\r
3788 setCurrentItem(mCurItem - 1, true);
\r
3794 boolean pageDown() {
\r
3795 if (mAdapter != null && mCurItem < (mAdapter.getCount() - 1)) {
\r
3796 setCurrentItem(mCurItem + 1, true);
\r
3803 * We only want the current page that is being shown to be focusable.
\r
3806 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
\r
3807 final int focusableCount = views.size();
\r
3809 final int descendantFocusability = getDescendantFocusability();
\r
3811 if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
\r
3812 for (int i = 0; i < getChildCount(); i++) {
\r
3813 final View child = getChildAt(i);
\r
3814 if (child.getVisibility() == VISIBLE) {
\r
3815 ItemInfo ii = infoForChild(child);
\r
3816 if (ii != null && ii.position == mCurItem) {
\r
3817 child.addFocusables(views, direction, focusableMode);
\r
3823 // we add ourselves (if focusable) in all cases except for when we are
\r
3824 // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable. this is
\r
3825 // to avoid the focus search finding layouts when a more precise search
\r
3826 // among the focusable children would be more interesting.
\r
3828 descendantFocusability != FOCUS_AFTER_DESCENDANTS ||
\r
3829 // No focusable descendants
\r
3830 (focusableCount == views.size())) {
\r
3831 // Note that we can't call the superclass here, because it will
\r
3832 // add all views in. So we need to do the same thing View does.
\r
3833 if (!isFocusable()) {
\r
3836 if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE &&
\r
3837 isInTouchMode() && !isFocusableInTouchMode()) {
\r
3840 if (views != null) {
\r
3847 * We only want the current page that is being shown to be touchable.
\r
3850 public void addTouchables(ArrayList<View> views) {
\r
3851 // Note that we don't call super.addTouchables(), which means that
\r
3852 // we don't call View.addTouchables(). This is okay because a ViewPager
\r
3853 // is itself not touchable.
\r
3854 for (int i = 0; i < getChildCount(); i++) {
\r
3855 final View child = getChildAt(i);
\r
3856 if (child.getVisibility() == VISIBLE) {
\r
3857 ItemInfo ii = infoForChild(child);
\r
3858 if (ii != null && ii.position == mCurItem) {
\r
3859 child.addTouchables(views);
\r
3866 * We only want the current page that is being shown to be focusable.
\r
3869 protected boolean onRequestFocusInDescendants(int direction,
\r
3870 Rect previouslyFocusedRect) {
\r
3874 int count = getChildCount();
\r
3875 if ((direction & FOCUS_FORWARD) != 0) {
\r
3880 index = count - 1;
\r
3884 for (int i = index; i != end; i += increment) {
\r
3885 View child = getChildAt(i);
\r
3886 if (child.getVisibility() == VISIBLE) {
\r
3887 ItemInfo ii = infoForChild(child);
\r
3888 if (ii != null && ii.position ==
\r
3889 mCurItem && child.requestFocus(direction, previouslyFocusedRect)) {
\r
3898 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
\r
3899 // Dispatch scroll events from this ViewPager.
\r
3900 if (event.getEventType() == AccessibilityEventCompat.TYPE_VIEW_SCROLLED) {
\r
3901 return super.dispatchPopulateAccessibilityEvent(event);
\r
3904 // Dispatch all other accessibility events from the current page.
\r
3905 final int childCount = getChildCount();
\r
3906 for (int i = 0; i < childCount; i++) {
\r
3907 final View child = getChildAt(i);
\r
3908 if (child.getVisibility() == VISIBLE) {
\r
3909 final ItemInfo ii = infoForChild(child);
\r
3910 if (ii != null && ii.position == mCurItem &&
\r
3911 child.dispatchPopulateAccessibilityEvent(event)) {
\r
3921 protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
\r
3922 return new LayoutParams();
\r
3926 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
\r
3927 return generateDefaultLayoutParams();
\r
3931 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
\r
3932 return p instanceof LayoutParams && super.checkLayoutParams(p);
\r
3936 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
\r
3937 return new LayoutParams(getContext(), attrs);
\r
3940 class MyAccessibilityDelegate extends AccessibilityDelegateCompat {
\r
3943 public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
\r
3944 super.onInitializeAccessibilityEvent(host, event);
\r
3945 event.setClassName(DirectionalViewpager.class.getName());
\r
3946 AccessibilityRecordCompat recordCompat = null;
\r
3947 if (isHorizontal()) {
\r
3949 AccessibilityEventCompat.asRecord(event);
\r
3951 recordCompat = AccessibilityRecordCompat.obtain();
\r
3953 recordCompat.setScrollable(canScroll());
\r
3954 if (event.getEventType() == AccessibilityEventCompat.TYPE_VIEW_SCROLLED
\r
3955 && mAdapter != null) {
\r
3956 recordCompat.setItemCount(mAdapter.getCount());
\r
3957 recordCompat.setFromIndex(mCurItem);
\r
3958 recordCompat.setToIndex(mCurItem);
\r
3963 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
\r
3964 super.onInitializeAccessibilityNodeInfo(host, info);
\r
3965 info.setClassName(DirectionalViewpager.class.getName());
\r
3966 info.setScrollable(canScroll());
\r
3967 if (isHorizontal()) {
\r
3968 if (canScrollHorizontally(1)) {
\r
3969 info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);
\r
3971 if (canScrollHorizontally(-1)) {
\r
3972 info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);
\r
3975 if (internalCanScrollVertically(1)) {
\r
3976 info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);
\r
3978 if (internalCanScrollVertically(-1)) {
\r
3979 info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);
\r
3985 public boolean performAccessibilityAction(View host, int action, Bundle args) {
\r
3986 if (super.performAccessibilityAction(host, action, args)) {
\r
3990 if (isHorizontal()) {
\r
3992 case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD: {
\r
3993 if (canScrollHorizontally(1)) {
\r
3994 setCurrentItem(mCurItem + 1);
\r
3999 case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD: {
\r
4000 if (canScrollHorizontally(-1)) {
\r
4001 setCurrentItem(mCurItem - 1);
\r
4009 case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD: {
\r
4010 if (internalCanScrollVertically(1)) {
\r
4011 setCurrentItem(mCurItem + 1);
\r
4016 case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD: {
\r
4017 if (internalCanScrollVertically(-1)) {
\r
4018 setCurrentItem(mCurItem - 1);
\r
4028 private boolean canScroll() {
\r
4029 return (mAdapter != null) && (mAdapter.getCount() > 1);
\r
4033 private class PagerObserver extends DataSetObserver {
\r
4035 public void onChanged() {
\r
4040 public void onInvalidated() {
\r
4046 * Layout parameters that should be supplied for views added to a
\r
4049 public static class LayoutParams extends ViewGroup.LayoutParams {
\r
4051 * true if this view is a decoration on the pager itself and not
\r
4052 * a view supplied by the adapter.
\r
4054 public boolean isDecor;
\r
4057 * Gravity setting for use on decor views only:
\r
4058 * Where to position the view page within the overall ViewPager
\r
4059 * container; constants are defined in {@link android.view.Gravity}.
\r
4061 public int gravity;
\r
4064 * Width as a 0-1 multiplier of the measured pager width
\r
4066 float widthFactor = 0.f;
\r
4068 float heightFactor = 0.f;
\r
4071 * true if this view was added during layout and needs to be measured
\r
4072 * before being positioned.
\r
4074 boolean needsMeasure;
\r
4077 * Adapter position this view is for if !isDecor
\r
4082 * Current child index within the ViewPager that this view occupies
\r
4086 public LayoutParams() {
\r
4087 super(FILL_PARENT, FILL_PARENT);
\r
4090 public LayoutParams(Context context, AttributeSet attrs) {
\r
4091 super(context, attrs);
\r
4093 final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
\r
4094 gravity = a.getInteger(0, Gravity.TOP);
\r
4099 static class ViewPositionComparator implements Comparator<View> {
\r
4101 public int compare(View lhs, View rhs) {
\r
4102 final LayoutParams llp = (LayoutParams) lhs.getLayoutParams();
\r
4103 final LayoutParams rlp = (LayoutParams) rhs.getLayoutParams();
\r
4104 if (llp.isDecor != rlp.isDecor) {
\r
4105 return llp.isDecor ? 1 : -1;
\r
4107 return llp.position - rlp.position;
\r
4111 public void setDirection(Direction direction) {
\r
4112 mDirection = direction.name();
\r
4116 private String logDestroyItem(int pos, View object) {
\r
4117 return "populate() - destroyItem() with pos: " + pos + " view: " + object;
\r