--- /dev/null
+package com.folioreader.view;\r
+\r
+/**\r
+ * Created by mobisys on 10/10/2016.\r
+ */\r
+\r
+\r
+import android.content.Context;\r
+import android.content.res.Resources;\r
+import android.content.res.TypedArray;\r
+import android.database.DataSetObserver;\r
+import android.graphics.Canvas;\r
+import android.graphics.Rect;\r
+import android.graphics.drawable.Drawable;\r
+import android.os.Build;\r
+import android.os.Bundle;\r
+import android.os.Parcel;\r
+import android.os.Parcelable;\r
+import android.os.SystemClock;\r
+import android.support.annotation.CallSuper;\r
+import android.support.annotation.DrawableRes;\r
+import android.support.v4.os.ParcelableCompat;\r
+import android.support.v4.os.ParcelableCompatCreatorCallbacks;\r
+import android.support.v4.view.AccessibilityDelegateCompat;\r
+import android.support.v4.view.MotionEventCompat;\r
+import android.support.v4.view.PagerAdapter;\r
+import android.support.v4.view.VelocityTrackerCompat;\r
+import android.support.v4.view.ViewCompat;\r
+import android.support.v4.view.ViewConfigurationCompat;\r
+import android.support.v4.view.WindowInsetsCompat;\r
+import android.support.v4.view.accessibility.AccessibilityEventCompat;\r
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;\r
+import android.support.v4.view.accessibility.AccessibilityRecordCompat;\r
+import android.support.v4.widget.EdgeEffectCompat;\r
+import android.util.AttributeSet;\r
+import android.util.Log;\r
+import android.view.FocusFinder;\r
+import android.view.Gravity;\r
+import android.view.KeyEvent;\r
+import android.view.MotionEvent;\r
+import android.view.SoundEffectConstants;\r
+import android.view.VelocityTracker;\r
+import android.view.View;\r
+import android.view.ViewConfiguration;\r
+import android.view.ViewGroup;\r
+import android.view.ViewParent;\r
+import android.view.accessibility.AccessibilityEvent;\r
+import android.view.animation.Interpolator;\r
+import android.widget.Scroller;\r
+\r
+import com.folioreader.R;\r
+\r
+import java.lang.reflect.Method;\r
+import java.util.ArrayList;\r
+import java.util.Collections;\r
+import java.util.Comparator;\r
+import java.util.List;\r
+\r
+public class DirectionalViewpager extends ViewGroup {\r
+ private static final String TAG = "ViewPager";\r
+ private static final boolean DEBUG = false;\r
+\r
+ private static final boolean USE_CACHE = false;\r
+\r
+ private static final int DEFAULT_OFFSCREEN_PAGES = 1;\r
+ private static final int MAX_SETTLE_DURATION = 600; // ms\r
+ private static final int MIN_DISTANCE_FOR_FLING = 25; // dips\r
+\r
+ private static final int DEFAULT_GUTTER_SIZE = 16; // dips\r
+\r
+ private static final int MIN_FLING_VELOCITY = 400; // dips\r
+\r
+ private static final int[] LAYOUT_ATTRS = new int[]{\r
+ android.R.attr.layout_gravity\r
+ };\r
+\r
+ public static enum Direction {\r
+ HORIZONTAL,\r
+ VERTICAL,\r
+ }\r
+\r
+ /**\r
+ * Used to track what the expected number of items in the adapter should be.\r
+ * If the app changes this when we don't expect it, we'll throw a big obnoxious exception.\r
+ */\r
+ private int mExpectedAdapterCount;\r
+ public String mDirection = Direction.VERTICAL.name();\r
+\r
+ static class ItemInfo {\r
+ Object object;\r
+ int position;\r
+ boolean scrolling;\r
+ float widthFactor;\r
+ float heightFactor;\r
+ float offset;\r
+ }\r
+\r
+ private static final Comparator<ItemInfo> COMPARATOR = new Comparator<ItemInfo>() {\r
+ @Override\r
+ public int compare(ItemInfo lhs, ItemInfo rhs) {\r
+ return lhs.position - rhs.position;\r
+ }\r
+ };\r
+\r
+ private static final Interpolator sInterpolator = new Interpolator() {\r
+ public float getInterpolation(float t) {\r
+ t -= 1.0f;\r
+ return t * t * t * t * t + 1.0f;\r
+ }\r
+ };\r
+\r
+ private final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>();\r
+ private final ItemInfo mTempItem = new ItemInfo();\r
+\r
+ private final Rect mTempRect = new Rect();\r
+\r
+ private PagerAdapter mAdapter;\r
+ private int mCurItem; // Index of currently displayed page.\r
+ private int mRestoredCurItem = -1;\r
+ private Parcelable mRestoredAdapterState = null;\r
+ private ClassLoader mRestoredClassLoader = null;\r
+\r
+ private Scroller mScroller;\r
+ private boolean mIsScrollStarted;\r
+\r
+ private PagerObserver mObserver;\r
+\r
+ private int mPageMargin;\r
+ private Drawable mMarginDrawable;\r
+ private int mTopPageBounds;\r
+ private int mBottomPageBounds;\r
+ private int mLeftPageBounds;\r
+ private int mRightPageBounds;\r
+\r
+ // Offsets of the first and last items, if known.\r
+ // Set during population, used to determine if we are at the beginning\r
+ // or end of the pager data set during touch scrolling.\r
+ private float mFirstOffset = -Float.MAX_VALUE;\r
+ private float mLastOffset = Float.MAX_VALUE;\r
+\r
+ private int mChildWidthMeasureSpec;\r
+ private int mChildHeightMeasureSpec;\r
+ private boolean mInLayout;\r
+\r
+ private boolean mScrollingCacheEnabled;\r
+\r
+ private boolean mPopulatePending;\r
+ private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES;\r
+\r
+ private boolean mIsBeingDragged;\r
+ private boolean mIsUnableToDrag;\r
+ private boolean mIgnoreGutter;\r
+ private int mDefaultGutterSize;\r
+ private int mGutterSize;\r
+ private int mTouchSlop;\r
+ /**\r
+ * Position of the last motion event.\r
+ */\r
+ private float mLastMotionX;\r
+ private float mLastMotionY;\r
+ private float mInitialMotionX;\r
+ private float mInitialMotionY;\r
+ /**\r
+ * ID of the active pointer. This is used to retain consistency during\r
+ * drags/flings if multiple pointers are used.\r
+ */\r
+ private int mActivePointerId = INVALID_POINTER;\r
+ /**\r
+ * Sentinel value for no current active pointer.\r
+ * Used by {@link #mActivePointerId}.\r
+ */\r
+ private static final int INVALID_POINTER = -1;\r
+\r
+ /**\r
+ * Determines speed during touch scrolling\r
+ */\r
+ private VelocityTracker mVelocityTracker;\r
+ private int mMinimumVelocity;\r
+ private int mMaximumVelocity;\r
+ private int mFlingDistance;\r
+ private int mCloseEnough;\r
+\r
+ // If the pager is at least this close to its final position, complete the scroll\r
+ // on touch down and let the user interact with the content inside instead of\r
+ // "catching" the flinging pager.\r
+ private static final int CLOSE_ENOUGH = 2; // dp\r
+\r
+ private boolean mFakeDragging;\r
+ private long mFakeDragBeginTime;\r
+\r
+ private EdgeEffectCompat mLeftEdge;\r
+ private EdgeEffectCompat mRightEdge;\r
+ private EdgeEffectCompat mTopEdge;\r
+ private EdgeEffectCompat mBottomEdge;\r
+\r
+ private boolean mFirstLayout = true;\r
+ private boolean mNeedCalculatePageOffsets = false;\r
+ private boolean mCalledSuper;\r
+ private int mDecorChildCount;\r
+\r
+ private List<OnPageChangeListener> mOnPageChangeListeners;\r
+ private OnPageChangeListener mOnPageChangeListener;\r
+ private OnPageChangeListener mInternalPageChangeListener;\r
+ private OnAdapterChangeListener mAdapterChangeListener;\r
+ private PageTransformer mPageTransformer;\r
+ private Method mSetChildrenDrawingOrderEnabled;\r
+\r
+ private static final int DRAW_ORDER_DEFAULT = 0;\r
+ private static final int DRAW_ORDER_FORWARD = 1;\r
+ private static final int DRAW_ORDER_REVERSE = 2;\r
+ private int mDrawingOrder;\r
+ private ArrayList<View> mDrawingOrderedChildren;\r
+ private static final ViewPositionComparator sPositionComparator = new ViewPositionComparator();\r
+\r
+ /**\r
+ * Indicates that the pager is in an idle, settled state. The current page\r
+ * is fully in view and no animation is in progress.\r
+ */\r
+ public static final int SCROLL_STATE_IDLE = 0;\r
+\r
+ /**\r
+ * Indicates that the pager is currently being dragged by the user.\r
+ */\r
+ public static final int SCROLL_STATE_DRAGGING = 1;\r
+\r
+ /**\r
+ * Indicates that the pager is in the process of settling to a final position.\r
+ */\r
+ public static final int SCROLL_STATE_SETTLING = 2;\r
+\r
+ private final Runnable mEndScrollRunnable = new Runnable() {\r
+ public void run() {\r
+ setScrollState(SCROLL_STATE_IDLE);\r
+ populate();\r
+ }\r
+ };\r
+\r
+ private int mScrollState = SCROLL_STATE_IDLE;\r
+\r
+ /**\r
+ * Callback interface for responding to changing state of the selected page.\r
+ */\r
+ public interface OnPageChangeListener {\r
+\r
+ /**\r
+ * This method will be invoked when the current\r
+ * page is scrolled, either as part\r
+ * of a programmatically initiated\r
+ * smooth scroll or a user initiated touch scroll.\r
+ *\r
+ * @param position Position index of the first page currently being displayed.\r
+ * <p>\r
+ * Page position+1 will be visible if positionOffset is nonzero.\r
+ * @param positionOffset\r
+ * Value from [0, 1) indicating the offset from the page at position.\r
+ * @param positionOffsetPixels\r
+ * Value in pixels indicating the offset from position.\r
+ */\r
+ public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels);\r
+\r
+ /**\r
+ * This method will be invoked when a new page becomes selected. Animation is not\r
+ * necessarily complete.\r
+ *\r
+ * @param position Position index of the new selected page.\r
+ */\r
+ public void onPageSelected(int position);\r
+\r
+ /**\r
+ * Called when the scroll state changes. Useful for discovering when the user\r
+ * begins dragging, when the pager is automatically settling to the current page,\r
+ * or when it is fully stopped/idle.\r
+ *\r
+ * @param state The new scroll state.\r
+ * @see ViewPager#SCROLL_STATE_IDLE\r
+ * @see ViewPager#SCROLL_STATE_DRAGGING\r
+ * @see ViewPager#SCROLL_STATE_SETTLING\r
+ */\r
+ public void onPageScrollStateChanged(int state);\r
+ }\r
+\r
+ /**\r
+ * Simple implementation of the {@link OnPageChangeListener}\r
+ * interface with stub\r
+ * implementations of each method.\r
+ * Extend this if you do not intend to override\r
+ * every method of {@link OnPageChangeListener}.\r
+ */\r
+ public static class SimpleOnPageChangeListener implements OnPageChangeListener {\r
+ @Override\r
+ public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {\r
+ // This space for rent\r
+ }\r
+\r
+ @Override\r
+ public void onPageSelected(int position) {\r
+ // This space for rent\r
+ }\r
+\r
+ @Override\r
+ public void onPageScrollStateChanged(int state) {\r
+ // This space for rent\r
+ }\r
+ }\r
+\r
+ /**\r
+ * A PageTransformer is invoked whenever a visible/attached page is scrolled.\r
+ * This offers an opportunity for the application to apply a custom transformation\r
+ * to the page views using animation properties.\r
+ * <p>\r
+ * <p>As property animation is only supported as of Android 3.0 and forward,\r
+ * setting a PageTransformer on a ViewPager on earlier platform versions will\r
+ * be ignored.</p>\r
+ */\r
+ public interface PageTransformer {\r
+ /**\r
+ * Apply a property transformation to the given page.\r
+ *\r
+ * @param page Apply the transformation to this page\r
+ * @param position Position of page relative to the current front-and-center\r
+ * position of the pager. 0 is front and center. 1 is one full\r
+ * page position to the right, and -1 is one page position to the left.\r
+ */\r
+ public void transformPage(View page, float position);\r
+ }\r
+\r
+ /**\r
+ * Used internally to monitor when adapters are switched.\r
+ */\r
+ interface OnAdapterChangeListener {\r
+ public void onAdapterChanged(PagerAdapter oldAdapter, PagerAdapter newAdapter);\r
+ }\r
+\r
+ /**\r
+ * Used internally to tag special types of child views that should be added as\r
+ * pager decorations by default.\r
+ */\r
+ interface Decor {\r
+ }\r
+\r
+ public DirectionalViewpager(Context context) {\r
+ super(context);\r
+ initViewPager();\r
+ }\r
+\r
+ public DirectionalViewpager(Context context, AttributeSet attrs) {\r
+ super(context, attrs);\r
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DirectionalViewpager);\r
+ if (a.getString(R.styleable.DirectionalViewpager_direction) != null) {\r
+ mDirection = a.getString(R.styleable.DirectionalViewpager_direction);\r
+ }\r
+ initViewPager();\r
+ }\r
+\r
+ void initViewPager() {\r
+ setWillNotDraw(false);\r
+ setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);\r
+ setFocusable(true);\r
+ final Context context = getContext();\r
+ mScroller = new Scroller(context, sInterpolator);\r
+ final ViewConfiguration configuration = ViewConfiguration.get(context);\r
+ final float density = context.getResources().getDisplayMetrics().density;\r
+\r
+ mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);\r
+ mMinimumVelocity = (int) (MIN_FLING_VELOCITY * density);\r
+ mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();\r
+ mLeftEdge = new EdgeEffectCompat(context);\r
+ mRightEdge = new EdgeEffectCompat(context);\r
+ mTopEdge = new EdgeEffectCompat(context);\r
+ mBottomEdge = new EdgeEffectCompat(context);\r
+\r
+ mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density);\r
+ mCloseEnough = (int) (CLOSE_ENOUGH * density);\r
+ mDefaultGutterSize = (int) (DEFAULT_GUTTER_SIZE * density);\r
+\r
+ ViewCompat.setAccessibilityDelegate(this, new MyAccessibilityDelegate());\r
+\r
+ if (ViewCompat.getImportantForAccessibility(this)\r
+ == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {\r
+ ViewCompat.setImportantForAccessibility(this,\r
+ ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);\r
+ }\r
+\r
+ ViewCompat.setOnApplyWindowInsetsListener(this,\r
+ new android.support\r
+ .v4.view.OnApplyWindowInsetsListener() {\r
+ private final Rect mTempRect = new Rect();\r
+\r
+ @Override\r
+ public WindowInsetsCompat\r
+ onApplyWindowInsets(final View v,\r
+ final WindowInsetsCompat originalInsets) {\r
+ // First let the ViewPager itself try and consume them...\r
+ final WindowInsetsCompat applied =\r
+ ViewCompat.onApplyWindowInsets(v, originalInsets);\r
+ if (applied.isConsumed()) {\r
+ // If the ViewPager consumed all insets, return now\r
+ return applied;\r
+ }\r
+\r
+ // Now we'll manually dispatch the insets to our children. Since ViewPager\r
+ // children are always full-height, we do not want to use the standard\r
+ // ViewGroup dispatchApplyWindowInsets since if child 0 consumes them,\r
+ // the rest of the children will not receive any insets. To workaround this\r
+ // we manually dispatch the applied insets, not allowing children to\r
+ // consume them from each other. We do however keep track of any insets\r
+ // which are consumed, returning the union of our children's consumption\r
+ final Rect res = mTempRect;\r
+ res.left = applied.getSystemWindowInsetLeft();\r
+ res.top = applied.getSystemWindowInsetTop();\r
+ res.right = applied.getSystemWindowInsetRight();\r
+ res.bottom = applied.getSystemWindowInsetBottom();\r
+\r
+ for (int i = 0, count = getChildCount(); i < count; i++) {\r
+ final WindowInsetsCompat childInsets = ViewCompat\r
+ .dispatchApplyWindowInsets(getChildAt(i), applied);\r
+ // Now keep track of any consumed by tracking each dimension's min\r
+ // value\r
+ res.left\r
+ = Math.min(childInsets.getSystemWindowInsetLeft(),\r
+ res.left);\r
+ res.top = Math.min(childInsets.getSystemWindowInsetTop(),\r
+ res.top);\r
+ res.right = Math.min(childInsets.getSystemWindowInsetRight(),\r
+ res.right);\r
+ res.bottom = Math.min(childInsets.getSystemWindowInsetBottom(),\r
+ res.bottom);\r
+ }\r
+\r
+ // Now return a new WindowInsets, using the consumed window insets\r
+ return applied.replaceSystemWindowInsets(\r
+ res.left, res.top, res.right, res.bottom);\r
+ }\r
+ });\r
+ }\r
+\r
+ @Override\r
+ protected void onDetachedFromWindow() {\r
+ removeCallbacks(mEndScrollRunnable);\r
+ // To be on the safe side, abort the scroller\r
+ if ((mScroller != null) && !mScroller.isFinished()) {\r
+ mScroller.abortAnimation();\r
+ }\r
+ super.onDetachedFromWindow();\r
+ }\r
+\r
+ private void setScrollState(int newState) {\r
+ if (mScrollState == newState) {\r
+ return;\r
+ }\r
+\r
+ mScrollState = newState;\r
+ if (mPageTransformer != null) {\r
+ // PageTransformers can do complex things that benefit from hardware layers.\r
+ enableLayers(newState != SCROLL_STATE_IDLE);\r
+ }\r
+ dispatchOnScrollStateChanged(newState);\r
+ }\r
+\r
+ /**\r
+ * Set a PagerAdapter that will supply views for this pager as needed.\r
+ *\r
+ * @param adapter Adapter to use\r
+ */\r
+ public void setAdapter(PagerAdapter adapter) {\r
+ if (mAdapter != null) {\r
+ mAdapter.unregisterDataSetObserver(mObserver);\r
+ mAdapter.startUpdate(this);\r
+ for (int i = 0; i < mItems.size(); i++) {\r
+ final ItemInfo ii = mItems.get(i);\r
+ mAdapter.destroyItem(this, ii.position, ii.object);\r
+ }\r
+ mAdapter.finishUpdate(this);\r
+ mItems.clear();\r
+ removeNonDecorViews();\r
+ mCurItem = 0;\r
+ scrollTo(0, 0);\r
+ }\r
+\r
+ final PagerAdapter oldAdapter = mAdapter;\r
+ mAdapter = adapter;\r
+ mExpectedAdapterCount = 0;\r
+\r
+ if (mAdapter != null) {\r
+ if (mObserver == null) {\r
+ mObserver = new PagerObserver();\r
+ }\r
+ mAdapter.registerDataSetObserver(mObserver);\r
+ mPopulatePending = false;\r
+ final boolean wasFirstLayout = mFirstLayout;\r
+ mFirstLayout = true;\r
+ mExpectedAdapterCount = mAdapter.getCount();\r
+ if (mRestoredCurItem >= 0) {\r
+ mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader);\r
+ setCurrentItemInternal(mRestoredCurItem, false, true);\r
+ mRestoredCurItem = -1;\r
+ mRestoredAdapterState = null;\r
+ mRestoredClassLoader = null;\r
+ } else if (!wasFirstLayout) {\r
+ populate();\r
+ } else {\r
+ requestLayout();\r
+ }\r
+ }\r
+\r
+ if (mAdapterChangeListener != null && oldAdapter != adapter) {\r
+ mAdapterChangeListener.onAdapterChanged(oldAdapter, adapter);\r
+ }\r
+ }\r
+\r
+ private void removeNonDecorViews() {\r
+ for (int i = 0; i < getChildCount(); i++) {\r
+ final View child = getChildAt(i);\r
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();\r
+ if (!lp.isDecor) {\r
+ removeViewAt(i);\r
+ i--;\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Retrieve the current adapter supplying pages.\r
+ *\r
+ * @return The currently registered PagerAdapter\r
+ */\r
+ public PagerAdapter getAdapter() {\r
+ return mAdapter;\r
+ }\r
+\r
+ void setOnAdapterChangeListener(OnAdapterChangeListener listener) {\r
+ mAdapterChangeListener = listener;\r
+ }\r
+\r
+ private int getClientWidth() {\r
+ return getMeasuredWidth() - getPaddingLeft() - getPaddingRight();\r
+ }\r
+\r
+ private int getClientHeight() {\r
+ return getMeasuredHeight() - getPaddingTop() - getPaddingBottom();\r
+ }\r
+\r
+ /**\r
+ * Set the currently selected page. If the ViewPager has already been through its first\r
+ * layout with its current adapter there will be a smooth animated transition between\r
+ * the current item and the specified item.\r
+ *\r
+ * @param item Item index to select\r
+ */\r
+ public void setCurrentItem(int item) {\r
+ mPopulatePending = false;\r
+ setCurrentItemInternal(item, !mFirstLayout, false);\r
+ }\r
+\r
+ /**\r
+ * Set the currently selected page.\r
+ *\r
+ * @param item Item index to select\r
+ * @param smoothScroll True to smoothly scroll to the new item, false to transition immediately\r
+ */\r
+ public void setCurrentItem(int item, boolean smoothScroll) {\r
+ mPopulatePending = false;\r
+ setCurrentItemInternal(item, smoothScroll, false);\r
+ }\r
+\r
+ public int getCurrentItem() {\r
+ return mCurItem;\r
+ }\r
+\r
+ public int getExpectedAdapterCount() {\r
+ return mExpectedAdapterCount;\r
+ }\r
+\r
+ void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) {\r
+ setCurrentItemInternal(item, smoothScroll, always, 0);\r
+ }\r
+\r
+ void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) {\r
+ if (mAdapter == null || mAdapter.getCount() <= 0) {\r
+ setScrollingCacheEnabled(false);\r
+ return;\r
+ }\r
+ if (!always && mCurItem == item && mItems.size() != 0) {\r
+ setScrollingCacheEnabled(false);\r
+ return;\r
+ }\r
+\r
+ if (item < 0) {\r
+ item = 0;\r
+ } else if (item >= mAdapter.getCount()) {\r
+ item = mAdapter.getCount() - 1;\r
+ }\r
+ final int pageLimit = mOffscreenPageLimit;\r
+ if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) {\r
+ // We are doing a jump by more than one page. To avoid\r
+ // glitches, we want to keep all current pages in the view\r
+ // until the scroll ends.\r
+ for (int i = 0; i < mItems.size(); i++) {\r
+ mItems.get(i).scrolling = true;\r
+ }\r
+ }\r
+ final boolean dispatchSelected = mCurItem != item;\r
+\r
+ if (mFirstLayout) {\r
+ // We don't have any idea how big we are yet and shouldn't have any pages either.\r
+ // Just set things up and let the pending layout handle things.\r
+ mCurItem = item;\r
+ if (dispatchSelected) {\r
+ dispatchOnPageSelected(item);\r
+ }\r
+ requestLayout();\r
+ } else {\r
+ populate(item);\r
+ scrollToItem(item, smoothScroll, velocity, dispatchSelected);\r
+ }\r
+ }\r
+\r
+ private void scrollToItem(int item, boolean smoothScroll, int velocity,\r
+ boolean dispatchSelected) {\r
+ final ItemInfo curInfo = infoForPosition(item);\r
+ int destX = 0;\r
+ int destY = 0;\r
+ if (isHorizontal()) {\r
+ if (curInfo != null) {\r
+ final int width = getClientWidth();\r
+ destX = (int) (width * Math.max(mFirstOffset,\r
+ Math.min(curInfo.offset, mLastOffset)));\r
+ }\r
+ if (smoothScroll) {\r
+ smoothScrollTo(destX, 0, velocity);\r
+ if (dispatchSelected) {\r
+ dispatchOnPageSelected(item);\r
+ }\r
+ } else {\r
+ if (dispatchSelected) {\r
+ dispatchOnPageSelected(item);\r
+ }\r
+ completeScroll(false);\r
+ scrollTo(destX, 0);\r
+ pageScrolled(destX, 0);\r
+ }\r
+ } else {\r
+ if (curInfo != null) {\r
+ final int height = getClientHeight();\r
+ destY = (int) (height * Math.max(mFirstOffset,\r
+ Math.min(curInfo.offset, mLastOffset)));\r
+ }\r
+ if (smoothScroll) {\r
+ smoothScrollTo(0, destY, velocity);\r
+ if (dispatchSelected && mOnPageChangeListener != null) {\r
+ mOnPageChangeListener.onPageSelected(item);\r
+ }\r
+ if (dispatchSelected && mInternalPageChangeListener != null) {\r
+ mInternalPageChangeListener.onPageSelected(item);\r
+ }\r
+ } else {\r
+ if (dispatchSelected && mOnPageChangeListener != null) {\r
+ mOnPageChangeListener.onPageSelected(item);\r
+ }\r
+ if (dispatchSelected && mInternalPageChangeListener != null) {\r
+ mInternalPageChangeListener.onPageSelected(item);\r
+ }\r
+ completeScroll(false);\r
+ scrollTo(0, destY);\r
+ pageScrolled(0, destY);\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Set a listener that will be invoked whenever the page changes or is incrementally\r
+ * scrolled. See {@link OnPageChangeListener}.\r
+ *\r
+ * @param listener Listener to set\r
+ * @deprecated Use {@link #addOnPageChangeListener(OnPageChangeListener)}\r
+ * and {@link #removeOnPageChangeListener(OnPageChangeListener)} instead.\r
+ */\r
+ @Deprecated\r
+ public void setOnPageChangeListener(OnPageChangeListener listener) {\r
+ mOnPageChangeListener = listener;\r
+ }\r
+\r
+ /**\r
+ * Add a listener that will be invoked whenever the page changes or is incrementally\r
+ * scrolled. See {@link OnPageChangeListener}.\r
+ * <p>\r
+ * <p>Components that add a listener should take care to remove it when finished.\r
+ * Other components that take ownership of a view may call {@link #clearOnPageChangeListeners()}\r
+ * to remove all attached listeners.</p>\r
+ *\r
+ * @param listener listener to add\r
+ */\r
+ public void addOnPageChangeListener(OnPageChangeListener listener) {\r
+ if (mOnPageChangeListeners == null) {\r
+ mOnPageChangeListeners = new ArrayList<>();\r
+ }\r
+ mOnPageChangeListeners.add(listener);\r
+ }\r
+\r
+ /**\r
+ * Remove a listener that was previously added via\r
+ * {@link #addOnPageChangeListener(OnPageChangeListener)}.\r
+ *\r
+ * @param listener listener to remove\r
+ */\r
+ public void removeOnPageChangeListener(OnPageChangeListener listener) {\r
+ if (mOnPageChangeListeners != null) {\r
+ mOnPageChangeListeners.remove(listener);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Remove all listeners that are notified of any changes in scroll state or position.\r
+ */\r
+ public void clearOnPageChangeListeners() {\r
+ if (mOnPageChangeListeners != null) {\r
+ mOnPageChangeListeners.clear();\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Set a {@link PageTransformer} that will be called for each attached page whenever\r
+ * the scroll position is changed. This allows the application to apply custom property\r
+ * transformations to each page, overriding the default sliding look and feel.\r
+ * <p>\r
+ * <p><em>Note:</em> Prior to Android 3.0 the property animation APIs did not exist.\r
+ * As a result, setting a PageTransformer prior to Android 3.0 (API 11) will have no effect.</p>\r
+ *\r
+ * @param reverseDrawingOrder true if the supplied PageTransformer requires page views\r
+ * to be drawn from last to first instead of first to last.\r
+ * @param transformer PageTransformer that will modify each page's animation properties\r
+ */\r
+ public void setPageTransformer(boolean reverseDrawingOrder, PageTransformer transformer) {\r
+ if (Build.VERSION.SDK_INT >= 11) {\r
+ final boolean hasTransformer = transformer != null;\r
+ final boolean needsPopulate = hasTransformer != (mPageTransformer != null);\r
+ mPageTransformer = transformer;\r
+ setChildrenDrawingOrderEnabledCompat(hasTransformer);\r
+ if (hasTransformer) {\r
+ mDrawingOrder = reverseDrawingOrder ? DRAW_ORDER_REVERSE : DRAW_ORDER_FORWARD;\r
+ } else {\r
+ mDrawingOrder = DRAW_ORDER_DEFAULT;\r
+ }\r
+ if (needsPopulate) populate();\r
+ }\r
+ }\r
+\r
+ void setChildrenDrawingOrderEnabledCompat(boolean enable) {\r
+ if (Build.VERSION.SDK_INT >= 7) {\r
+ if (mSetChildrenDrawingOrderEnabled == null) {\r
+ try {\r
+ mSetChildrenDrawingOrderEnabled = ViewGroup.class.getDeclaredMethod(\r
+ "setChildrenDrawingOrderEnabled", new Class[]{Boolean.TYPE});\r
+ } catch (NoSuchMethodException e) {\r
+ Log.e(TAG, "Can't find setChildrenDrawingOrderEnabled", e);\r
+ }\r
+ }\r
+ try {\r
+ mSetChildrenDrawingOrderEnabled\r
+ .invoke(this, enable);\r
+ } catch (Exception e) {\r
+ Log.e(TAG, "Error changing children drawing order", e);\r
+ }\r
+ }\r
+ }\r
+\r
+ @Override\r
+ protected int getChildDrawingOrder(int childCount, int i) {\r
+ final int index = mDrawingOrder\r
+ == DRAW_ORDER_REVERSE ? childCount - 1 - i : i;\r
+ final int result\r
+ = ((LayoutParams) mDrawingOrderedChildren.get(index).getLayoutParams()).childIndex;\r
+ return result;\r
+ }\r
+\r
+ /**\r
+ * Set a separate OnPageChangeListener for internal use by the support library.\r
+ *\r
+ * @param listener Listener to set\r
+ * @return The old listener that was set, if any.\r
+ */\r
+ OnPageChangeListener setInternalPageChangeListener(OnPageChangeListener listener) {\r
+ OnPageChangeListener oldListener = mInternalPageChangeListener;\r
+ mInternalPageChangeListener = listener;\r
+ return oldListener;\r
+ }\r
+\r
+ /**\r
+ * Returns the number of pages that will be retained to either side of the\r
+ * current page in the view hierarchy in an idle state. Defaults to 1.\r
+ *\r
+ * @return How many pages will be kept offscreen on either side\r
+ * @see #setOffscreenPageLimit(int)\r
+ */\r
+ public int getOffscreenPageLimit() {\r
+ return mOffscreenPageLimit;\r
+ }\r
+\r
+ /**\r
+ * Set the number of pages that should be\r
+ * retained to either side of the\r
+ * current page in the view hierarchy\r
+ * in an idle state. Pages beyond this\r
+ * limit will be recreated from the adapter when needed.\r
+ * <p>\r
+ * <p>This is offered as an optimization.\r
+ * If you know in advance the number\r
+ * of pages you will need to support or\r
+ * have lazy-loading mechanisms in place\r
+ * on your pages, tweaking this setting\r
+ * can have benefits in perceived smoothness\r
+ * of paging animations and interaction.\r
+ * If you have a small number of pages (3-4)\r
+ * that you can keep active all at once,\r
+ * less time will be spent in layout for\r
+ * newly created view subtrees as the\r
+ * user pages back and forth.</p>\r
+ * <p>\r
+ * <p>You should keep this limit low,\r
+ * especially if your pages have complex layouts.\r
+ * This setting defaults to 1.</p>\r
+ *\r
+ * @param limit How many pages will be kept offscreen in an idle state.\r
+ */\r
+ public void setOffscreenPageLimit(int limit) {\r
+ if (limit < DEFAULT_OFFSCREEN_PAGES) {\r
+ Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " +\r
+ DEFAULT_OFFSCREEN_PAGES);\r
+ limit = DEFAULT_OFFSCREEN_PAGES;\r
+ }\r
+ if (limit != mOffscreenPageLimit) {\r
+ mOffscreenPageLimit = limit;\r
+ populate();\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Set the margin between pages.\r
+ *\r
+ * @param marginPixels Distance between adjacent pages in pixels\r
+ * @see #getPageMargin()\r
+ * @see #setPageMarginDrawable(Drawable)\r
+ * @see #setPageMarginDrawable(int)\r
+ */\r
+ public void setPageMargin(int marginPixels) {\r
+ final int oldMargin = mPageMargin;\r
+ mPageMargin = marginPixels;\r
+\r
+ if (isHorizontal()) {\r
+ int width = getWidth();\r
+ recomputeScrollPosition(width, width, marginPixels, oldMargin, 0, 0);\r
+ } else {\r
+ int height = getHeight();\r
+ recomputeScrollPosition(0, 0, marginPixels, oldMargin, height, height);\r
+ }\r
+\r
+ requestLayout();\r
+ }\r
+\r
+ /**\r
+ * Return the margin between pages.\r
+ *\r
+ * @return The size of the margin in pixels\r
+ */\r
+ public int getPageMargin() {\r
+ return mPageMargin;\r
+ }\r
+\r
+ /**\r
+ * Set a drawable that will be used to fill the margin between pages.\r
+ *\r
+ * @param d Drawable to display between pages\r
+ */\r
+ public void setPageMarginDrawable(Drawable d) {\r
+ mMarginDrawable = d;\r
+ if (d != null) refreshDrawableState();\r
+ setWillNotDraw(d == null);\r
+ invalidate();\r
+ }\r
+\r
+ /**\r
+ * Set a drawable that will be used to fill the margin between pages.\r
+ *\r
+ * @param resId Resource ID of a drawable to display between pages\r
+ */\r
+ public void setPageMarginDrawable(@DrawableRes int resId) {\r
+ setPageMarginDrawable(getContext().getResources().getDrawable(resId));\r
+ }\r
+\r
+ @Override\r
+ protected boolean verifyDrawable(Drawable who) {\r
+ return super.verifyDrawable(who) || who == mMarginDrawable;\r
+ }\r
+\r
+ @Override\r
+ protected void drawableStateChanged() {\r
+ super.drawableStateChanged();\r
+ final Drawable d = mMarginDrawable;\r
+ if (d != null && d.isStateful()) {\r
+ d.setState(getDrawableState());\r
+ }\r
+ }\r
+\r
+ // We want the duration of the page snap animation to be influenced by the distance that\r
+ // the screen has to travel, however, we don't want this duration to be effected in a\r
+ // purely linear fashion. Instead, we use this method to moderate the effect that the distance\r
+ // of travel has on the overall snap duration.\r
+ float distanceInfluenceForSnapDuration(float f) {\r
+ f -= 0.5f; // center the values about 0.\r
+ f *= 0.3f * Math.PI / 2.0f;\r
+ return (float) Math.sin(f);\r
+ }\r
+\r
+ /**\r
+ * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.\r
+ *\r
+ * @param x the number of pixels to scroll by on the X axis\r
+ * @param y the number of pixels to scroll by on the Y axis\r
+ */\r
+ void smoothScrollTo(int x, int y) {\r
+ smoothScrollTo(x, y, 0);\r
+ }\r
+\r
+ /**\r
+ * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.\r
+ *\r
+ * @param x the number of pixels to scroll by on the X axis\r
+ * @param y the number of pixels to scroll by on the Y axis\r
+ * @param velocity the velocity associated with a fling, if applicable. (0 otherwise)\r
+ */\r
+ void smoothScrollTo(int x, int y, int velocity) {\r
+ if (getChildCount() == 0) {\r
+ // Nothing to do.\r
+ setScrollingCacheEnabled(false);\r
+ return;\r
+ }\r
+\r
+ int sx;\r
+ if (isHorizontal()) {\r
+ boolean wasScrolling = (mScroller != null) && !mScroller.isFinished();\r
+ if (wasScrolling) {\r
+ // We're in the middle of a previously initiated scrolling. Check to see\r
+ // whether that scrolling has actually started (if we always call getStartX\r
+ // we can get a stale value from the scroller if it hadn't yet had its first\r
+ // computeScrollOffset call) to decide what is the current scrolling position.\r
+ sx = mIsScrollStarted ? mScroller.getCurrX() : mScroller.getStartX();\r
+ // And abort the current scrolling.\r
+ mScroller.abortAnimation();\r
+ setScrollingCacheEnabled(false);\r
+ } else {\r
+ sx = getScrollX();\r
+ }\r
+ } else {\r
+ sx = getScrollX();\r
+ }\r
+ int sy = getScrollY();\r
+ int dx = x - sx;\r
+ int dy = y - sy;\r
+ if (dx == 0 && dy == 0) {\r
+ completeScroll(false);\r
+ populate();\r
+ setScrollState(SCROLL_STATE_IDLE);\r
+ return;\r
+ }\r
+\r
+ setScrollingCacheEnabled(true);\r
+ setScrollState(SCROLL_STATE_SETTLING);\r
+ int duration = 0;\r
+ if (isHorizontal()) {\r
+ final int width = getClientWidth();\r
+ final int halfWidth = width / 2;\r
+ final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / width);\r
+ final float distance = halfWidth + halfWidth *\r
+ distanceInfluenceForSnapDuration(distanceRatio);\r
+ velocity = Math.abs(velocity);\r
+ if (velocity > 0) {\r
+ duration = 4 * Math.round(1000 * Math.abs(distance / velocity));\r
+ } else {\r
+ final float pageWidth = width * mAdapter.getPageWidth(mCurItem);\r
+ final float pageDelta = (float) Math.abs(dx) / (pageWidth + mPageMargin);\r
+ duration = (int) ((pageDelta + 1) * 100);\r
+ }\r
+ } else {\r
+ final int height = getClientHeight();\r
+ final int halfHeight = height / 2;\r
+ final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / height);\r
+ final float distance = halfHeight + halfHeight *\r
+ distanceInfluenceForSnapDuration(distanceRatio);\r
+\r
+ duration = 0;\r
+ velocity = Math.abs(velocity);\r
+ if (velocity > 0) {\r
+ duration = 4 * Math.round(1000 * Math.abs(distance / velocity));\r
+ } else {\r
+ final float pageHeight = height * mAdapter.getPageWidth(mCurItem);\r
+ final float pageDelta = (float) Math.abs(dx) / (pageHeight + mPageMargin);\r
+ duration = (int) ((pageDelta + 1) * 100);\r
+ }\r
+ }\r
+ duration = Math.min(duration, MAX_SETTLE_DURATION);\r
+\r
+ // Reset the "scroll started" flag. It will be flipped to true in all places\r
+ // where we call computeScrollOffset().\r
+ if (isHorizontal()) {\r
+ mIsScrollStarted = false;\r
+ }\r
+ mScroller.startScroll(sx, sy, dx, dy, duration);\r
+ ViewCompat.postInvalidateOnAnimation(this);\r
+ }\r
+\r
+ private boolean isHorizontal() {\r
+ return mDirection.equalsIgnoreCase(Direction.HORIZONTAL.name());\r
+ }\r
+\r
+ ItemInfo addNewItem(int position, int index) {\r
+ ItemInfo ii = new ItemInfo();\r
+ ii.position = position;\r
+ ii.object = mAdapter.instantiateItem(this, position);\r
+ if (isHorizontal()) {\r
+ ii.widthFactor = mAdapter.getPageWidth(position);\r
+ } else {\r
+ ii.heightFactor = mAdapter.getPageWidth(position);\r
+ }\r
+ if (index < 0 || index >= mItems.size()) {\r
+ mItems.add(ii);\r
+ } else {\r
+ mItems.add(index, ii);\r
+ }\r
+ return ii;\r
+ }\r
+\r
+ void dataSetChanged() {\r
+ // This method only gets called if our observer is attached, so mAdapter is non-null.\r
+\r
+ final int adapterCount = mAdapter.getCount();\r
+ mExpectedAdapterCount = adapterCount;\r
+ boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1 &&\r
+ mItems.size() < adapterCount;\r
+ int newCurrItem = mCurItem;\r
+\r
+ boolean isUpdating = false;\r
+ for (int i = 0; i < mItems.size(); i++) {\r
+ final ItemInfo ii = mItems.get(i);\r
+ final int newPos = mAdapter.getItemPosition(ii.object);\r
+\r
+ if (newPos == PagerAdapter.POSITION_UNCHANGED) {\r
+ continue;\r
+ }\r
+\r
+ if (newPos == PagerAdapter.POSITION_NONE) {\r
+ mItems.remove(i);\r
+ i--;\r
+\r
+ if (!isUpdating) {\r
+ mAdapter.startUpdate(this);\r
+ isUpdating = true;\r
+ }\r
+\r
+ mAdapter.destroyItem(this, ii.position, ii.object);\r
+ needPopulate = true;\r
+\r
+ if (mCurItem == ii.position) {\r
+ // Keep the current item in the valid range\r
+ newCurrItem = Math.max(0, Math.min(mCurItem, adapterCount - 1));\r
+ needPopulate = true;\r
+ }\r
+ continue;\r
+ }\r
+\r
+ if (ii.position != newPos) {\r
+ if (ii.position == mCurItem) {\r
+ // Our current item changed position. Follow it.\r
+ newCurrItem = newPos;\r
+ }\r
+\r
+ ii.position = newPos;\r
+ needPopulate = true;\r
+ }\r
+ }\r
+\r
+ if (isUpdating) {\r
+ mAdapter.finishUpdate(this);\r
+ }\r
+\r
+ Collections.sort(mItems, COMPARATOR);\r
+\r
+ if (needPopulate) {\r
+ // Reset our known page widths; populate will recompute them.\r
+ final int childCount = getChildCount();\r
+ for (int i = 0; i < childCount; i++) {\r
+ final View child = getChildAt(i);\r
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();\r
+ if (!lp.isDecor) {\r
+ if (isHorizontal()) {\r
+ lp.widthFactor = 0.f;\r
+ } else {\r
+ lp.heightFactor = 0.f;\r
+ }\r
+ }\r
+ }\r
+\r
+ setCurrentItemInternal(newCurrItem, false, true);\r
+ requestLayout();\r
+ }\r
+ }\r
+\r
+ void populate() {\r
+ populate(mCurItem);\r
+ }\r
+\r
+ void populate(int newCurrentItem) {\r
+ ItemInfo oldCurInfo = null;\r
+ int focusDirection = View.FOCUS_FORWARD;\r
+ if (mCurItem != newCurrentItem) {\r
+ focusDirection = mCurItem < newCurrentItem ? View.FOCUS_DOWN : View.FOCUS_UP;\r
+ oldCurInfo = infoForPosition(mCurItem);\r
+ mCurItem = newCurrentItem;\r
+ }\r
+\r
+ if (mAdapter == null) {\r
+ sortChildDrawingOrder();\r
+ return;\r
+ }\r
+\r
+ // Bail now if we are waiting to populate. This is to hold off\r
+ // on creating views from the time the user releases their finger to\r
+ // fling to a new position until we have finished the scroll to\r
+ // that position, avoiding glitches from happening at that point.\r
+ if (mPopulatePending) {\r
+ if (DEBUG) Log.i(TAG, "populate is pending, skipping for now...");\r
+ sortChildDrawingOrder();\r
+ return;\r
+ }\r
+\r
+ // Also, don't populate until we are attached to a window. This is to\r
+ // avoid trying to populate before we have restored our view hierarchy\r
+ // state and conflicting with what is restored.\r
+ if (getWindowToken() == null) {\r
+ return;\r
+ }\r
+\r
+ mAdapter.startUpdate(this);\r
+\r
+ final int pageLimit = mOffscreenPageLimit;\r
+ final int startPos = Math.max(0, mCurItem - pageLimit);\r
+ final int N = mAdapter.getCount();\r
+ final int endPos = Math.min(N - 1, mCurItem + pageLimit);\r
+\r
+ if (N != mExpectedAdapterCount) {\r
+ String resName;\r
+ try {\r
+ resName = getResources().getResourceName(getId());\r
+ } catch (Resources.NotFoundException e) {\r
+ resName = Integer.toHexString(getId());\r
+ }\r
+ throw new IllegalStateException("The application's PagerAdapter changed the adapter's" +\r
+ " contents without calling PagerAdapter#notifyDataSetChanged!" +\r
+ " Expected adapter item count: " + mExpectedAdapterCount + ", found: " + N +\r
+ " Pager id: " + resName +\r
+ " Pager class: " + getClass() +\r
+ " Problematic adapter: " + mAdapter.getClass());\r
+ }\r
+\r
+ // Locate the currently focused item or add it if needed.\r
+ int curIndex = -1;\r
+ ItemInfo curItem = null;\r
+ for (curIndex = 0; curIndex < mItems.size(); curIndex++) {\r
+ final ItemInfo ii = mItems.get(curIndex);\r
+ if (ii.position >= mCurItem) {\r
+ if (ii.position == mCurItem) curItem = ii;\r
+ break;\r
+ }\r
+ }\r
+\r
+ if (curItem == null && N > 0) {\r
+ curItem = addNewItem(mCurItem, curIndex);\r
+ }\r
+\r
+ // Fill 3x the available width or up to the number of offscreen\r
+ // pages requested to either side, whichever is larger.\r
+ // If we have no current item we have no work to do.\r
+ if (curItem != null) {\r
+ if (isHorizontal()) {\r
+ float extraWidthLeft = 0.f;\r
+ int itemIndex = curIndex - 1;\r
+ ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;\r
+ final int clientWidth = getClientWidth();\r
+ final float leftWidthNeeded = clientWidth <= 0 ? 0 :\r
+ 2.f - curItem.widthFactor + (float) getPaddingLeft() / (float) clientWidth;\r
+ for (int pos = mCurItem - 1; pos >= 0; pos--) {\r
+ if (extraWidthLeft >= leftWidthNeeded && pos < startPos) {\r
+ if (ii == null) {\r
+ break;\r
+ }\r
+ if (pos == ii.position && !ii.scrolling) {\r
+ mItems.remove(itemIndex);\r
+ mAdapter.destroyItem(this, pos, ii.object);\r
+ if (DEBUG) {\r
+ Log.i(TAG, logDestroyItem(pos, ((View) ii.object)));\r
+ }\r
+ itemIndex--;\r
+ curIndex--;\r
+ ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;\r
+ }\r
+ } else if (ii != null && pos == ii.position) {\r
+ extraWidthLeft += ii.widthFactor;\r
+ itemIndex--;\r
+ ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;\r
+ } else {\r
+ ii = addNewItem(pos, itemIndex + 1);\r
+ extraWidthLeft += ii.widthFactor;\r
+ curIndex++;\r
+ ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;\r
+ }\r
+ }\r
+\r
+ float extraWidthRight = curItem.widthFactor;\r
+ itemIndex = curIndex + 1;\r
+ if (extraWidthRight < 2.f) {\r
+ ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;\r
+ final float rightWidthNeeded = clientWidth <= 0 ? 0 :\r
+ (float) getPaddingRight() / (float) clientWidth + 2.f;\r
+ for (int pos = mCurItem + 1; pos < N; pos++) {\r
+ if (extraWidthRight >= rightWidthNeeded && pos > endPos) {\r
+ if (ii == null) {\r
+ break;\r
+ }\r
+ if (pos == ii.position && !ii.scrolling) {\r
+ mItems.remove(itemIndex);\r
+ mAdapter.destroyItem(this, pos, ii.object);\r
+ if (DEBUG) {\r
+ Log.i(TAG, logDestroyItem(pos, ((View) ii.object)));\r
+ }\r
+ ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;\r
+ }\r
+ } else if (ii != null && pos == ii.position) {\r
+ extraWidthRight += ii.widthFactor;\r
+ itemIndex++;\r
+ ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;\r
+ } else {\r
+ ii = addNewItem(pos, itemIndex);\r
+ itemIndex++;\r
+ extraWidthRight += ii.widthFactor;\r
+ ii = itemIndex < mItems.size()\r
+ ? mItems.get(itemIndex) : null;\r
+ }\r
+ }\r
+ }\r
+ } else {\r
+ float extraHeightTop = 0.f;\r
+ int itemIndex = curIndex - 1;\r
+ ItemInfo ii\r
+ = itemIndex >= 0 ? mItems.get(itemIndex) : null;\r
+ final int clientHeight = getClientHeight();\r
+ final float topHeightNeeded = clientHeight <= 0 ? 0 :\r
+ 2.f - curItem.heightFactor\r
+ + (float) getPaddingLeft() / (float) clientHeight;\r
+ for (int pos = mCurItem - 1; pos >= 0; pos--) {\r
+ if (extraHeightTop >= topHeightNeeded && pos < startPos) {\r
+ if (ii == null) {\r
+ break;\r
+ }\r
+ if (pos == ii.position && !ii.scrolling) {\r
+ mItems.remove(itemIndex);\r
+ mAdapter.destroyItem(this, pos, ii.object);\r
+ if (DEBUG) {\r
+ Log.i(TAG, logDestroyItem(pos, ((View) ii.object)));\r
+ }\r
+ itemIndex--;\r
+ curIndex--;\r
+ ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;\r
+ }\r
+ } else if (ii != null && pos == ii.position) {\r
+ extraHeightTop += ii.heightFactor;\r
+ itemIndex--;\r
+ ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;\r
+ } else {\r
+ ii = addNewItem(pos, itemIndex + 1);\r
+ extraHeightTop += ii.heightFactor;\r
+ curIndex++;\r
+ ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;\r
+ }\r
+ }\r
+\r
+ float extraHeightBottom = curItem.heightFactor;\r
+ itemIndex = curIndex + 1;\r
+ if (extraHeightBottom < 2.f) {\r
+ ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;\r
+ final float bottomHeightNeeded = clientHeight <= 0 ? 0 :\r
+ (float) getPaddingRight() / (float) clientHeight + 2.f;\r
+ for (int pos = mCurItem + 1; pos < N; pos++) {\r
+ if (extraHeightBottom >= bottomHeightNeeded && pos > endPos) {\r
+ if (ii == null) {\r
+ break;\r
+ }\r
+ if (pos == ii.position && !ii.scrolling) {\r
+ mItems.remove(itemIndex);\r
+ mAdapter.destroyItem(this, pos, ii.object);\r
+ if (DEBUG) {\r
+ Log.i(TAG, logDestroyItem(pos, ((View) ii.object)));\r
+ }\r
+ ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;\r
+ }\r
+ } else if (ii != null && pos == ii.position) {\r
+ extraHeightBottom += ii.heightFactor;\r
+ itemIndex++;\r
+ ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;\r
+ } else {\r
+ ii = addNewItem(pos, itemIndex);\r
+ itemIndex++;\r
+ extraHeightBottom += ii.heightFactor;\r
+ ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ calculatePageOffsets(curItem, curIndex, oldCurInfo);\r
+ }\r
+\r
+ if (DEBUG) {\r
+ Log.i(TAG, "Current page list:");\r
+ for (int i = 0; i < mItems.size(); i++) {\r
+ Log.i(TAG, "#" + i + ": page " + mItems.get(i).position);\r
+ }\r
+ }\r
+\r
+ mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null);\r
+\r
+ mAdapter.finishUpdate(this);\r
+\r
+ // Check width measurement of current pages and drawing sort order.\r
+ // Update LayoutParams as needed.\r
+ final int childCount = getChildCount();\r
+ if (isHorizontal()) {\r
+ for (int i = 0; i < childCount; i++) {\r
+ final View child = getChildAt(i);\r
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();\r
+ lp.childIndex = i;\r
+ if (!lp.isDecor && lp.widthFactor == 0.f) {\r
+ // 0 means requery the adapter for this, it doesn't have a valid width.\r
+ final ItemInfo ii = infoForChild(child);\r
+ if (ii != null) {\r
+ lp.widthFactor = ii.widthFactor;\r
+ lp.position = ii.position;\r
+ }\r
+ }\r
+ }\r
+ sortChildDrawingOrder();\r
+\r
+ if (hasFocus()) {\r
+ View currentFocused = findFocus();\r
+ ItemInfo ii\r
+ = currentFocused != null ? infoForAnyChild(currentFocused) : null;\r
+ if (ii == null || ii.position != mCurItem) {\r
+ for (int i = 0; i < getChildCount(); i++) {\r
+ View child = getChildAt(i);\r
+ ii = infoForChild(child);\r
+ if (ii != null\r
+ && ii.position == mCurItem &&\r
+ child.requestFocus(View.FOCUS_FORWARD)) {\r
+ break;\r
+ }\r
+ }\r
+ }\r
+ }\r
+ } else {\r
+ for (int i = 0; i < childCount; i++) {\r
+ final View child = getChildAt(i);\r
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();\r
+ lp.childIndex = i;\r
+ if (!lp.isDecor && lp.heightFactor == 0.f) {\r
+ final ItemInfo ii = infoForChild(child);\r
+ if (ii != null) {\r
+ lp.heightFactor = ii.heightFactor;\r
+ lp.position = ii.position;\r
+ }\r
+ }\r
+ }\r
+ sortChildDrawingOrder();\r
+\r
+ if (hasFocus()) {\r
+ View currentFocused = findFocus();\r
+ ItemInfo ii = currentFocused != null ? infoForAnyChild(currentFocused) : null;\r
+ if (ii == null || ii.position != mCurItem) {\r
+ for (int i = 0; i < getChildCount(); i++) {\r
+ View child = getChildAt(i);\r
+ ii = infoForChild(child);\r
+ if (ii != null && ii.position == mCurItem\r
+ && child.requestFocus(focusDirection)) {\r
+// if (child.requestFocus(focusDirection)) {\r
+ break;\r
+ // }\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ private void sortChildDrawingOrder() {\r
+ if (mDrawingOrder != DRAW_ORDER_DEFAULT) {\r
+ if (mDrawingOrderedChildren == null) {\r
+ mDrawingOrderedChildren = new ArrayList<View>();\r
+ } else {\r
+ mDrawingOrderedChildren.clear();\r
+ }\r
+ final int childCount = getChildCount();\r
+ for (int i = 0; i < childCount; i++) {\r
+ final View child = getChildAt(i);\r
+ mDrawingOrderedChildren.add(child);\r
+ }\r
+ Collections.sort(mDrawingOrderedChildren, sPositionComparator);\r
+ }\r
+ }\r
+\r
+ private void calculatePageOffsets(ItemInfo curItem, int curIndex, ItemInfo oldCurInfo) {\r
+ final int N = mAdapter.getCount();\r
+ if (isHorizontal()) {\r
+ final int width = getClientWidth();\r
+ final float marginOffset = width > 0 ? (float) mPageMargin / width : 0;\r
+ // Fix up offsets for later layout.\r
+ if (oldCurInfo != null) {\r
+ final int oldCurPosition = oldCurInfo.position;\r
+ // Base offsets off of oldCurInfo.\r
+ if (oldCurPosition < curItem.position) {\r
+ int itemIndex = 0;\r
+ ItemInfo ii = null;\r
+ float offset = oldCurInfo.offset + oldCurInfo.widthFactor + marginOffset;\r
+ for (int pos = oldCurPosition + 1;\r
+ pos <= curItem.position && itemIndex < mItems.size(); pos++) {\r
+ ii = mItems.get(itemIndex);\r
+ while (pos > ii.position && itemIndex < mItems.size() - 1) {\r
+ itemIndex++;\r
+ ii = mItems.get(itemIndex);\r
+ }\r
+ while (pos < ii.position) {\r
+ // We don't have an item populated for this,\r
+ // ask the adapter for an offset.\r
+ offset += mAdapter.getPageWidth(pos) + marginOffset;\r
+ pos++;\r
+ }\r
+ ii.offset = offset;\r
+ offset += ii.widthFactor + marginOffset;\r
+ }\r
+ } else if (oldCurPosition > curItem.position) {\r
+ int itemIndex = mItems.size() - 1;\r
+ ItemInfo ii = null;\r
+ float offset = oldCurInfo.offset;\r
+ for (int pos = oldCurPosition - 1;\r
+ pos >= curItem.position && itemIndex >= 0; pos--) {\r
+ ii = mItems.get(itemIndex);\r
+ while (pos < ii.position && itemIndex > 0) {\r
+ itemIndex--;\r
+ ii = mItems.get(itemIndex);\r
+ }\r
+ while (pos > ii.position) {\r
+ // We don't have an item populated for this,\r
+ // ask the adapter for an offset.\r
+ offset -= mAdapter.getPageWidth(pos) + marginOffset;\r
+ pos--;\r
+ }\r
+ offset -= ii.widthFactor + marginOffset;\r
+ ii.offset = offset;\r
+ }\r
+ }\r
+ }\r
+\r
+ // Base all offsets off of curItem.\r
+ final int itemCount = mItems.size();\r
+ float offset = curItem.offset;\r
+ int pos = curItem.position - 1;\r
+ mFirstOffset = curItem.position == 0 ? curItem.offset : -Float.MAX_VALUE;\r
+ mLastOffset = curItem.position == N - 1 ?\r
+ curItem.offset + curItem.widthFactor - 1 : Float.MAX_VALUE;\r
+ // Previous pages\r
+ for (int i = curIndex - 1; i >= 0; i--, pos--) {\r
+ final ItemInfo ii = mItems.get(i);\r
+ while (pos > ii.position) {\r
+ offset -= mAdapter.getPageWidth(pos--) + marginOffset;\r
+ }\r
+ offset -= ii.widthFactor + marginOffset;\r
+ ii.offset = offset;\r
+ if (ii.position == 0) mFirstOffset = offset;\r
+ }\r
+ offset = curItem.offset + curItem.widthFactor + marginOffset;\r
+ pos = curItem.position + 1;\r
+ // Next pages\r
+ for (int i = curIndex + 1; i < itemCount; i++, pos++) {\r
+ final ItemInfo ii = mItems.get(i);\r
+ while (pos < ii.position) {\r
+ offset += mAdapter.getPageWidth(pos++) + marginOffset;\r
+ }\r
+ if (ii.position == N - 1) {\r
+ mLastOffset = offset + ii.widthFactor - 1;\r
+ }\r
+ ii.offset = offset;\r
+ offset += ii.widthFactor + marginOffset;\r
+ }\r
+ } else {\r
+ final int height = getClientHeight();\r
+ final float marginOffset = height > 0 ? (float) mPageMargin / height : 0;\r
+ // Fix up offsets for later layout.\r
+ if (oldCurInfo != null) {\r
+ final int oldCurPosition = oldCurInfo.position;\r
+ // Base offsets off of oldCurInfo.\r
+ if (oldCurPosition < curItem.position) {\r
+ int itemIndex = 0;\r
+ ItemInfo ii = null;\r
+ float offset = oldCurInfo.offset + oldCurInfo.heightFactor + marginOffset;\r
+ for (int pos = oldCurPosition + 1;\r
+ pos <= curItem.position && itemIndex < mItems.size(); pos++) {\r
+ ii = mItems.get(itemIndex);\r
+ while (pos > ii.position && itemIndex < mItems.size() - 1) {\r
+ itemIndex++;\r
+ ii = mItems.get(itemIndex);\r
+ }\r
+ while (pos < ii.position) {\r
+ // We don't have an item populated for this,\r
+ // ask the adapter for an offset.\r
+ offset += mAdapter.getPageWidth(pos) + marginOffset;\r
+ pos++;\r
+ }\r
+ ii.offset = offset;\r
+ offset += ii.heightFactor + marginOffset;\r
+ }\r
+ } else if (oldCurPosition > curItem.position) {\r
+ int itemIndex = mItems.size() - 1;\r
+ ItemInfo ii = null;\r
+ float offset = oldCurInfo.offset;\r
+ for (int pos = oldCurPosition - 1;\r
+ pos >= curItem.position && itemIndex >= 0;\r
+ pos--) {\r
+ ii = mItems.get(itemIndex);\r
+ while (pos < ii.position && itemIndex > 0) {\r
+ itemIndex--;\r
+ ii = mItems.get(itemIndex);\r
+ }\r
+ while (pos > ii.position) {\r
+ // We don't have an item populated for this,\r
+ // ask the adapter for an offset.\r
+ offset -= mAdapter.getPageWidth(pos) + marginOffset;\r
+ pos--;\r
+ }\r
+ offset -= ii.heightFactor + marginOffset;\r
+ ii.offset = offset;\r
+ }\r
+ }\r
+ }\r
+\r
+ // Base all offsets off of curItem.\r
+ final int itemCount = mItems.size();\r
+ float offset = curItem.offset;\r
+ int pos = curItem.position - 1;\r
+ mFirstOffset = curItem.position == 0 ? curItem.offset : -Float.MAX_VALUE;\r
+ mLastOffset = curItem.position == N - 1 ?\r
+ curItem.offset + curItem.heightFactor - 1 : Float.MAX_VALUE;\r
+ // Previous pages\r
+ for (int i = curIndex - 1; i >= 0; i--, pos--) {\r
+ final ItemInfo ii = mItems.get(i);\r
+ while (pos > ii.position) {\r
+ offset -= mAdapter.getPageWidth(pos--) + marginOffset;\r
+ }\r
+ offset -= ii.heightFactor + marginOffset;\r
+ ii.offset = offset;\r
+ if (ii.position == 0) mFirstOffset = offset;\r
+ }\r
+ offset = curItem.offset + curItem.heightFactor + marginOffset;\r
+ pos = curItem.position + 1;\r
+ // Next pages\r
+ for (int i = curIndex + 1; i < itemCount; i++, pos++) {\r
+ final ItemInfo ii = mItems.get(i);\r
+ while (pos < ii.position) {\r
+ offset += mAdapter.getPageWidth(pos++) + marginOffset;\r
+ }\r
+ if (ii.position == N - 1) {\r
+ mLastOffset = offset + ii.heightFactor - 1;\r
+ }\r
+ ii.offset = offset;\r
+ offset += ii.heightFactor + marginOffset;\r
+ }\r
+ }\r
+\r
+ mNeedCalculatePageOffsets = false;\r
+ }\r
+\r
+ /**\r
+ * This is the persistent state that is saved by ViewPager. Only needed\r
+ * if you are creating a sublass of ViewPager that must save its own\r
+ * state, in which case it should implement a subclass of this which\r
+ * contains that state.\r
+ */\r
+ public static class SavedState extends BaseSavedState {\r
+ int position;\r
+ Parcelable adapterState;\r
+ ClassLoader loader;\r
+\r
+ public SavedState(Parcelable superState) {\r
+ super(superState);\r
+ }\r
+\r
+ @Override\r
+ public void writeToParcel(Parcel out, int flags) {\r
+ super.writeToParcel(out, flags);\r
+ out.writeInt(position);\r
+ out.writeParcelable(adapterState, flags);\r
+ }\r
+\r
+ @Override\r
+ public String toString() {\r
+ return "FragmentPager.SavedState{"\r
+ + Integer.toHexString(System.identityHashCode(this))\r
+ + " position=" + position + "}";\r
+ }\r
+\r
+ public static final Parcelable.Creator<SavedState> CREATOR\r
+ = ParcelableCompat\r
+ .newCreator(new ParcelableCompatCreatorCallbacks<SavedState>() {\r
+ @Override\r
+ public SavedState\r
+ createFromParcel(Parcel in, ClassLoader loader) {\r
+ return new SavedState(in, loader);\r
+ }\r
+\r
+ @Override\r
+ public SavedState[] newArray(int size) {\r
+ return new SavedState[size];\r
+ }\r
+ });\r
+\r
+ SavedState(Parcel in, ClassLoader loader) {\r
+ super(in);\r
+ if (loader == null) {\r
+ loader = getClass().getClassLoader();\r
+ }\r
+ position = in.readInt();\r
+ adapterState = in.readParcelable(loader);\r
+ this.loader = loader;\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public Parcelable onSaveInstanceState() {\r
+ Parcelable superState = super.onSaveInstanceState();\r
+ SavedState ss = new SavedState(superState);\r
+ ss.position = mCurItem;\r
+ if (mAdapter != null) {\r
+ ss.adapterState = mAdapter.saveState();\r
+ }\r
+ return ss;\r
+ }\r
+\r
+ @Override\r
+ public void onRestoreInstanceState(Parcelable state) {\r
+ if (!(state instanceof SavedState)) {\r
+ super.onRestoreInstanceState(state);\r
+ return;\r
+ }\r
+\r
+ SavedState ss = (SavedState) state;\r
+ super.onRestoreInstanceState(ss.getSuperState());\r
+\r
+ if (mAdapter != null) {\r
+ mAdapter.restoreState(ss.adapterState, ss.loader);\r
+ setCurrentItemInternal(ss.position, false, true);\r
+ } else {\r
+ mRestoredCurItem = ss.position;\r
+ mRestoredAdapterState = ss.adapterState;\r
+ mRestoredClassLoader = ss.loader;\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public void addView(View child, int index, ViewGroup.LayoutParams params) {\r
+ if (!checkLayoutParams(params)) {\r
+ params = generateLayoutParams(params);\r
+ }\r
+ final LayoutParams lp = (LayoutParams) params;\r
+ lp.isDecor |= child instanceof Decor;\r
+ if (mInLayout) {\r
+ if (lp != null && lp.isDecor) {\r
+ throw new IllegalStateException("Cannot add pager decor view during layout");\r
+ }\r
+ lp.needsMeasure = true;\r
+ addViewInLayout(child, index, params);\r
+ } else {\r
+ super.addView(child, index, params);\r
+ }\r
+\r
+ if (USE_CACHE) {\r
+ if (child.getVisibility() != GONE) {\r
+ child.setDrawingCacheEnabled(mScrollingCacheEnabled);\r
+ } else {\r
+ child.setDrawingCacheEnabled(false);\r
+ }\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public void removeView(View view) {\r
+ if (mInLayout) {\r
+ removeViewInLayout(view);\r
+ } else {\r
+ super.removeView(view);\r
+ }\r
+ }\r
+\r
+ ItemInfo infoForChild(View child) {\r
+ for (int i = 0; i < mItems.size(); i++) {\r
+ ItemInfo ii = mItems.get(i);\r
+ if (mAdapter.isViewFromObject(child, ii.object)) {\r
+ return ii;\r
+ }\r
+ }\r
+ return null;\r
+ }\r
+\r
+ ItemInfo infoForAnyChild(View child) {\r
+ ViewParent parent;\r
+ while ((parent = child.getParent()) != this) {\r
+ if (parent == null || !(parent instanceof View)) {\r
+ return null;\r
+ }\r
+ child = (View) parent;\r
+ }\r
+ return infoForChild(child);\r
+ }\r
+\r
+ ItemInfo infoForPosition(int position) {\r
+ for (int i = 0; i < mItems.size(); i++) {\r
+ ItemInfo ii = mItems.get(i);\r
+ if (ii.position == position) {\r
+ return ii;\r
+ }\r
+ }\r
+ return null;\r
+ }\r
+\r
+ @Override\r
+ protected void onAttachedToWindow() {\r
+ super.onAttachedToWindow();\r
+ mFirstLayout = true;\r
+ }\r
+\r
+ @Override\r
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\r
+ // For simple implementation, our internal size is always 0.\r
+ // We depend on the container to specify the layout size of\r
+ // our view. We can't really know what it is since we will be\r
+ // adding and removing different arbitrary views and do not\r
+ // want the layout to change as this happens.\r
+ setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),\r
+ getDefaultSize(0, heightMeasureSpec));\r
+\r
+ int childWidthSize = 0;\r
+ int childHeightSize = 0;\r
+ if (isHorizontal()) {\r
+ int measuredWidth = getMeasuredWidth();\r
+ int maxGutterSize = measuredWidth / 10;\r
+ mGutterSize = Math.min(maxGutterSize, mDefaultGutterSize);\r
+\r
+ // Children are just made to fill our space.\r
+ childWidthSize = measuredWidth - getPaddingLeft() - getPaddingRight();\r
+ childHeightSize = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();\r
+ } else {\r
+ int measuredHeight = getMeasuredHeight();\r
+ int maxGutterSize = measuredHeight / 10;\r
+ mGutterSize = Math.min(maxGutterSize, mDefaultGutterSize);\r
+\r
+ // Children are just made to fill our space.\r
+ childWidthSize = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();\r
+ childHeightSize = measuredHeight - getPaddingTop() - getPaddingBottom();\r
+ }\r
+\r
+ /*\r
+ * Make sure all children have been properly measured. Decor views first.\r
+ * Right now we cheat and make this less complicated by assuming decor\r
+ * views won't intersect. We will pin to edges based on gravity.\r
+ */\r
+ int size = getChildCount();\r
+ for (int i = 0; i < size; ++i) {\r
+ final View child = getChildAt(i);\r
+ if (child.getVisibility() != GONE) {\r
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();\r
+ if (lp != null && lp.isDecor) {\r
+ final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;\r
+ final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;\r
+ int widthMode = MeasureSpec.AT_MOST;\r
+ int heightMode = MeasureSpec.AT_MOST;\r
+ boolean consumeVertical = vgrav == Gravity.TOP || vgrav == Gravity.BOTTOM;\r
+ boolean consumeHorizontal = hgrav == Gravity.LEFT || hgrav == Gravity.RIGHT;\r
+\r
+ if (consumeVertical) {\r
+ widthMode = MeasureSpec.EXACTLY;\r
+ } else if (consumeHorizontal) {\r
+ heightMode = MeasureSpec.EXACTLY;\r
+ }\r
+\r
+ int widthSize = childWidthSize;\r
+ int heightSize = childHeightSize;\r
+ if (lp.width != LayoutParams.WRAP_CONTENT) {\r
+ widthMode = MeasureSpec.EXACTLY;\r
+ if (lp.width != LayoutParams.FILL_PARENT) {\r
+ widthSize = lp.width;\r
+ }\r
+ }\r
+ if (lp.height != LayoutParams.WRAP_CONTENT) {\r
+ heightMode = MeasureSpec.EXACTLY;\r
+ if (lp.height != LayoutParams.FILL_PARENT) {\r
+ heightSize = lp.height;\r
+ }\r
+ }\r
+ final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode);\r
+ final int heightSpec = MeasureSpec.makeMeasureSpec(heightSize, heightMode);\r
+ child.measure(widthSpec, heightSpec);\r
+\r
+ if (consumeVertical) {\r
+ childHeightSize -= child.getMeasuredHeight();\r
+ } else if (consumeHorizontal) {\r
+ childWidthSize -= child.getMeasuredWidth();\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthSize, MeasureSpec.EXACTLY);\r
+ mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeightSize, MeasureSpec.EXACTLY);\r
+\r
+ // Make sure we have created all fragments that we need to have shown.\r
+ mInLayout = true;\r
+ populate();\r
+ mInLayout = false;\r
+\r
+ // Page views next.\r
+ size = getChildCount();\r
+ for (int i = 0; i < size; ++i) {\r
+ final View child = getChildAt(i);\r
+ if (child.getVisibility() != GONE) {\r
+ if (DEBUG) Log.v(TAG, "Measuring #" + i + " " + child\r
+ + ": " + mChildWidthMeasureSpec);\r
+\r
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();\r
+ if (lp == null || !lp.isDecor) {\r
+ if (isHorizontal()) {\r
+ int widthSpec = MeasureSpec.makeMeasureSpec(\r
+ (int) (childWidthSize * lp.widthFactor), MeasureSpec.EXACTLY);\r
+ child.measure(widthSpec, mChildHeightMeasureSpec);\r
+ } else {\r
+ int heightSpec = MeasureSpec.makeMeasureSpec(\r
+ (int) (childHeightSize * lp.heightFactor), MeasureSpec.EXACTLY);\r
+ child.measure(mChildWidthMeasureSpec, heightSpec);\r
+ }\r
+ }\r
+\r
+ }\r
+ }\r
+ }\r
+\r
+ @Override\r
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {\r
+ super.onSizeChanged(w, h, oldw, oldh);\r
+\r
+ // Make sure scroll position is set correctly.\r
+\r
+ if (isHorizontal()) {\r
+ if (w != oldw) {\r
+ recomputeScrollPosition(w, oldw, mPageMargin, mPageMargin, 0, 0);\r
+ }\r
+ } else {\r
+ if (h != oldh) {\r
+ recomputeScrollPosition(0, 0, mPageMargin, mPageMargin, h, oldh);\r
+ }\r
+ }\r
+ }\r
+\r
+\r
+ private void recomputeScrollPosition(int width, int oldWidth, int margin,\r
+ int oldMargin, int height, int oldHeight) {\r
+ if (isHorizontal()) {\r
+ if (oldWidth > 0 && !mItems.isEmpty()) {\r
+ if (!mScroller.isFinished()) {\r
+ mScroller.setFinalX(\r
+ getCurrentItem() * getClientWidth());\r
+ } else {\r
+ final int widthWithMargin\r
+ = width - getPaddingLeft() - getPaddingRight() + margin;\r
+ final int oldWidthWithMargin\r
+ = oldWidth - getPaddingLeft() - getPaddingRight()\r
+ + oldMargin;\r
+ final int xpos = getScrollX();\r
+ final float pageOffset = (float) xpos / oldWidthWithMargin;\r
+ final int newOffsetPixels = (int) (pageOffset * widthWithMargin);\r
+\r
+ scrollTo(newOffsetPixels, getScrollY());\r
+ }\r
+ } else {\r
+ final ItemInfo ii = infoForPosition(mCurItem);\r
+ final float scrollOffset = ii != null ? Math.min(ii.offset, mLastOffset) : 0;\r
+ final int scrollPos = (int) (scrollOffset *\r
+ (width - getPaddingLeft() - getPaddingRight()));\r
+ if (scrollPos != getScrollX()) {\r
+ completeScroll(false);\r
+ scrollTo(scrollPos, getScrollY());\r
+ }\r
+ }\r
+ } else {\r
+ final int heightWithMargin = height - getPaddingTop() - getPaddingBottom() + margin;\r
+ final int oldHeightWithMargin = oldHeight - getPaddingTop() - getPaddingBottom()\r
+ + oldMargin;\r
+ final int ypos = getScrollY();\r
+ final float pageOffset = (float) ypos / oldHeightWithMargin;\r
+ final int newOffsetPixels = (int) (pageOffset * heightWithMargin);\r
+\r
+ scrollTo(getScrollX(), newOffsetPixels);\r
+ if (!mScroller.isFinished()) {\r
+ // We now return to your regularly scheduled scroll, already in progress.\r
+ final int newDuration = mScroller.getDuration() - mScroller.timePassed();\r
+ ItemInfo targetInfo = infoForPosition(mCurItem);\r
+ mScroller.startScroll(0, newOffsetPixels,\r
+ 0, (int) (targetInfo.offset * height), newDuration);\r
+ } else {\r
+ final ItemInfo ii = infoForPosition(mCurItem);\r
+ final float scrollOffset = ii != null ? Math.min(ii.offset, mLastOffset) : 0;\r
+ final int scrollPos = (int) (scrollOffset *\r
+ (height - getPaddingTop() - getPaddingBottom()));\r
+ if (scrollPos != getScrollY()) {\r
+ completeScroll(false);\r
+ scrollTo(getScrollX(), scrollPos);\r
+ }\r
+ }\r
+ }\r
+\r
+ }\r
+\r
+ @Override\r
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {\r
+ final int count = getChildCount();\r
+ int width = r - l;\r
+ int height = b - t;\r
+ int paddingLeft = getPaddingLeft();\r
+ int paddingTop = getPaddingTop();\r
+ int paddingRight = getPaddingRight();\r
+ int paddingBottom = getPaddingBottom();\r
+ final int scrollX = getScrollX();\r
+ final int scrollY = getScrollY();\r
+\r
+ int decorCount = 0;\r
+\r
+ // First pass - decor views. We need to do this in two passes so that\r
+ // we have the proper offsets for non-decor views later.\r
+ for (int i = 0; i < count; i++) {\r
+ final View child = getChildAt(i);\r
+ if (child.getVisibility() != GONE) {\r
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();\r
+ int childLeft = 0;\r
+ int childTop = 0;\r
+ if (lp.isDecor) {\r
+ final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;\r
+ final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;\r
+ switch (hgrav) {\r
+ default:\r
+ childLeft = paddingLeft;\r
+ break;\r
+ case Gravity.LEFT:\r
+ childLeft = paddingLeft;\r
+ paddingLeft += child.getMeasuredWidth();\r
+ break;\r
+ case Gravity.CENTER_HORIZONTAL:\r
+ childLeft = Math.max((width - child.getMeasuredWidth()) / 2,\r
+ paddingLeft);\r
+ break;\r
+ case Gravity.RIGHT:\r
+ childLeft = width - paddingRight - child.getMeasuredWidth();\r
+ paddingRight += child.getMeasuredWidth();\r
+ break;\r
+ }\r
+ switch (vgrav) {\r
+ default:\r
+ childTop = paddingTop;\r
+ break;\r
+ case Gravity.TOP:\r
+ childTop = paddingTop;\r
+ paddingTop += child.getMeasuredHeight();\r
+ break;\r
+ case Gravity.CENTER_VERTICAL:\r
+ childTop = Math.max((height - child.getMeasuredHeight()) / 2,\r
+ paddingTop);\r
+ break;\r
+ case Gravity.BOTTOM:\r
+ childTop = height - paddingBottom - child.getMeasuredHeight();\r
+ paddingBottom += child.getMeasuredHeight();\r
+ break;\r
+ }\r
+ if (isHorizontal()) {\r
+ childLeft += scrollX;\r
+ } else {\r
+ childTop += scrollY;\r
+ }\r
+ child.layout(childLeft, childTop,\r
+ childLeft + child.getMeasuredWidth(),\r
+ childTop + child.getMeasuredHeight());\r
+ decorCount++;\r
+ }\r
+ }\r
+ }\r
+\r
+ if (isHorizontal()) {\r
+ final int childWidth = width - paddingLeft - paddingRight;\r
+ // Page views. Do this once we have the right padding offsets from above.\r
+ for (int i = 0; i < count; i++) {\r
+ final View child = getChildAt(i);\r
+ if (child.getVisibility() != GONE) {\r
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();\r
+ ItemInfo ii;\r
+ if (!lp.isDecor && (ii = infoForChild(child)) != null) {\r
+ int loff = (int) (childWidth * ii.offset);\r
+ int childLeft = paddingLeft + loff;\r
+ int childTop = paddingTop;\r
+ if (lp.needsMeasure) {\r
+ // This was added during layout and needs measurement.\r
+ // Do it now that we know what we're working with.\r
+ lp.needsMeasure = false;\r
+ final int widthSpec = MeasureSpec.makeMeasureSpec(\r
+ (int) (childWidth * lp.widthFactor),\r
+ MeasureSpec.EXACTLY);\r
+ final int heightSpec = MeasureSpec.makeMeasureSpec(\r
+ (int) (height - paddingTop - paddingBottom),\r
+ MeasureSpec.EXACTLY);\r
+ child.measure(widthSpec, heightSpec);\r
+ }\r
+ if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object\r
+ + ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth()\r
+ + "x" + child.getMeasuredHeight());\r
+ child.layout(childLeft, childTop,\r
+ childLeft + child.getMeasuredWidth(),\r
+ childTop + child.getMeasuredHeight());\r
+ }\r
+ }\r
+ }\r
+ mTopPageBounds = paddingTop;\r
+ mBottomPageBounds = height - paddingBottom;\r
+ } else {\r
+ final int childHeight = height - paddingTop - paddingBottom;\r
+ // Page views. Do this once we have the right padding offsets from above.\r
+ for (int i = 0; i < count; i++) {\r
+ final View child = getChildAt(i);\r
+ if (child.getVisibility() != GONE) {\r
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();\r
+ ItemInfo ii;\r
+ if (!lp.isDecor && (ii = infoForChild(child)) != null) {\r
+ int toff = (int) (childHeight * ii.offset);\r
+ int childLeft = paddingLeft;\r
+ int childTop = paddingTop + toff;\r
+ if (lp.needsMeasure) {\r
+ // This was added during layout and needs measurement.\r
+ // Do it now that we know what we're working with.\r
+ lp.needsMeasure = false;\r
+ final int widthSpec = MeasureSpec.makeMeasureSpec(\r
+ (int) (width - paddingLeft - paddingRight),\r
+ MeasureSpec.EXACTLY);\r
+ final int heightSpec = MeasureSpec.makeMeasureSpec(\r
+ (int) (childHeight * lp.heightFactor),\r
+ MeasureSpec.EXACTLY);\r
+ child.measure(widthSpec, heightSpec);\r
+ }\r
+ if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object\r
+ + ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth()\r
+ + "x" + child.getMeasuredHeight());\r
+ child.layout(childLeft, childTop,\r
+ childLeft + child.getMeasuredWidth(),\r
+ childTop + child.getMeasuredHeight());\r
+ }\r
+ }\r
+ }\r
+ mLeftPageBounds = paddingLeft;\r
+ mRightPageBounds = width - paddingRight;\r
+ }\r
+ mDecorChildCount = decorCount;\r
+\r
+ if (mFirstLayout) {\r
+ scrollToItem(mCurItem, false, 0, false);\r
+ }\r
+ mFirstLayout = false;\r
+ }\r
+\r
+ @Override\r
+ public void computeScroll() {\r
+ mIsScrollStarted = true;\r
+ if (!mScroller.isFinished() && mScroller.computeScrollOffset()) {\r
+ int oldX = getScrollX();\r
+ int oldY = getScrollY();\r
+ int x = mScroller.getCurrX();\r
+ int y = mScroller.getCurrY();\r
+\r
+ if (oldX != x || oldY != y) {\r
+ scrollTo(x, y);\r
+ if (isHorizontal()) {\r
+ if (!pageScrolled(x, 0)) {\r
+ mScroller.abortAnimation();\r
+ scrollTo(0, y);\r
+ }\r
+ } else {\r
+ if (!pageScrolled(0, y)) {\r
+ mScroller.abortAnimation();\r
+ scrollTo(x, 0);\r
+ }\r
+ }\r
+ }\r
+\r
+ // Keep on drawing until the animation has finished.\r
+ ViewCompat.postInvalidateOnAnimation(this);\r
+ return;\r
+ }\r
+\r
+ // Done with scroll, clean up state.\r
+ completeScroll(true);\r
+ }\r
+\r
+ private boolean pageScrolled(int xpos, int ypos) {\r
+ if (mItems.size() == 0) {\r
+ if (mFirstLayout) {\r
+ // If we haven't been laid out yet, we probably just haven't been populated yet.\r
+ // Let's skip this call since it doesn't make sense in this state\r
+ return false;\r
+ }\r
+ mCalledSuper = false;\r
+ onPageScrolled(0, 0, 0);\r
+ if (!mCalledSuper) {\r
+ throw new IllegalStateException(\r
+ "onPageScrolled did not call superclass implementation");\r
+ }\r
+ return false;\r
+ }\r
+ final ItemInfo ii = infoForCurrentScrollPosition();\r
+ int currentPage = 0;\r
+ float pageOffset = 0;\r
+ int offsetPixels = 0;\r
+ if (isHorizontal()) {\r
+ int width = getClientWidth();\r
+ int widthWithMargin = width + mPageMargin;\r
+ float marginOffset = (float) mPageMargin / width;\r
+ currentPage = ii.position;\r
+ pageOffset = (((float) xpos / width) - ii.offset) /\r
+ (ii.widthFactor + marginOffset);\r
+ offsetPixels = (int) (pageOffset * widthWithMargin);\r
+ } else {\r
+ int height = getClientHeight();\r
+ int heightWithMargin = height + mPageMargin;\r
+ float marginOffset = (float) mPageMargin / height;\r
+ currentPage = ii.position;\r
+ pageOffset = (((float) ypos / height) - ii.offset) /\r
+ (ii.heightFactor + marginOffset);\r
+ offsetPixels = (int) (pageOffset * heightWithMargin);\r
+ }\r
+\r
+ mCalledSuper = false;\r
+ onPageScrolled(currentPage, pageOffset, offsetPixels);\r
+ if (!mCalledSuper) {\r
+ throw new IllegalStateException(\r
+ "onPageScrolled did not call superclass implementation");\r
+ }\r
+ return true;\r
+ }\r
+\r
+ /**\r
+ * This method will be invoked when the current page is scrolled, either as part\r
+ * of a programmatically initiated smooth scroll or a user initiated touch scroll.\r
+ * If you override this method you must call through to the superclass implementation\r
+ * (e.g. super.onPageScrolled(position, offset, offsetPixels)) before onPageScrolled\r
+ * returns.\r
+ *\r
+ * @param position Position index of the first page currently being displayed.\r
+ * Page position+1 will be visible if positionOffset is nonzero.\r
+ * @param offset Value from [0, 1) indicating the offset from the page at position.\r
+ * @param offsetPixels Value in pixels indicating the offset from position.\r
+ */\r
+ @CallSuper\r
+ protected void onPageScrolled(int position, float offset, int offsetPixels) {\r
+ // Offset any decor views if needed - keep them on-screen at all times.\r
+ if (isHorizontal()) {\r
+ if (mDecorChildCount > 0) {\r
+ final int scrollX = getScrollX();\r
+ int paddingLeft = getPaddingLeft();\r
+ int paddingRight = getPaddingRight();\r
+ final int width = getWidth();\r
+ final int childCount = getChildCount();\r
+ for (int i = 0; i < childCount; i++) {\r
+ final View child = getChildAt(i);\r
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();\r
+ if (!lp.isDecor) continue;\r
+\r
+ final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;\r
+ int childLeft = 0;\r
+ switch (hgrav) {\r
+ default:\r
+ childLeft = paddingLeft;\r
+ break;\r
+ case Gravity.LEFT:\r
+ childLeft = paddingLeft;\r
+ paddingLeft += child.getWidth();\r
+ break;\r
+ case Gravity.CENTER_HORIZONTAL:\r
+ childLeft = Math.max((width - child.getMeasuredWidth()) / 2,\r
+ paddingLeft);\r
+ break;\r
+ case Gravity.RIGHT:\r
+ childLeft = width - paddingRight - child.getMeasuredWidth();\r
+ paddingRight += child.getMeasuredWidth();\r
+ break;\r
+ }\r
+ childLeft += scrollX;\r
+\r
+ final int childOffset = childLeft - child.getLeft();\r
+ if (childOffset != 0) {\r
+ child.offsetLeftAndRight(childOffset);\r
+ }\r
+ }\r
+ }\r
+\r
+ dispatchOnPageScrolled(position, offset, offsetPixels);\r
+\r
+ if (mPageTransformer != null) {\r
+ final int scrollX = getScrollX();\r
+ final int childCount = getChildCount();\r
+ for (int i = 0; i < childCount; i++) {\r
+ final View child = getChildAt(i);\r
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();\r
+\r
+ if (lp.isDecor) continue;\r
+ final float transformPos\r
+ = (float) (child.getLeft() - scrollX) / getClientWidth();\r
+ mPageTransformer.transformPage(child, transformPos);\r
+ }\r
+ }\r
+ } else {\r
+ if (mDecorChildCount > 0) {\r
+ final int scrollY = getScrollY();\r
+ int paddingTop = getPaddingTop();\r
+ int paddingBottom = getPaddingBottom();\r
+ final int height = getHeight();\r
+ final int childCount = getChildCount();\r
+ for (int i = 0; i < childCount; i++) {\r
+ final View child = getChildAt(i);\r
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();\r
+ if (!lp.isDecor) continue;\r
+\r
+ final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;\r
+ int childTop = 0;\r
+ switch (vgrav) {\r
+ default:\r
+ childTop = paddingTop;\r
+ break;\r
+ case Gravity.TOP:\r
+ childTop = paddingTop;\r
+ paddingTop += child.getHeight();\r
+ break;\r
+ case Gravity.CENTER_VERTICAL:\r
+ childTop = Math.max((height - child.getMeasuredHeight()) / 2,\r
+ paddingTop);\r
+ break;\r
+ case Gravity.BOTTOM:\r
+ childTop = height - paddingBottom - child.getMeasuredHeight();\r
+ paddingBottom += child.getMeasuredHeight();\r
+ break;\r
+ }\r
+ childTop += scrollY;\r
+\r
+ final int childOffset = childTop - child.getTop();\r
+ if (childOffset != 0) {\r
+ child.offsetTopAndBottom(childOffset);\r
+ }\r
+ }\r
+ }\r
+\r
+ if (mOnPageChangeListener != null) {\r
+ mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels);\r
+ }\r
+ if (mInternalPageChangeListener != null) {\r
+ mInternalPageChangeListener.onPageScrolled(position, offset, offsetPixels);\r
+ }\r
+\r
+ if (mPageTransformer != null) {\r
+ final int scrollY = getScrollY();\r
+ final int childCount = getChildCount();\r
+ for (int i = 0; i < childCount; i++) {\r
+ final View child = getChildAt(i);\r
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();\r
+\r
+ if (lp.isDecor) continue;\r
+\r
+ final float transformPos\r
+ = (float) (child.getTop() - scrollY) / getClientHeight();\r
+ mPageTransformer.transformPage(child, transformPos);\r
+ }\r
+ }\r
+ }\r
+\r
+ mCalledSuper = true;\r
+ }\r
+\r
+ private void dispatchOnPageScrolled(int position, float offset, int offsetPixels) {\r
+ if (mOnPageChangeListener != null) {\r
+ mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels);\r
+ }\r
+ if (mOnPageChangeListeners != null) {\r
+ for (int i = 0, z = mOnPageChangeListeners.size(); i < z; i++) {\r
+ OnPageChangeListener listener = mOnPageChangeListeners.get(i);\r
+ if (listener != null) {\r
+ listener.onPageScrolled(position, offset, offsetPixels);\r
+ }\r
+ }\r
+ }\r
+ if (mInternalPageChangeListener != null) {\r
+ mInternalPageChangeListener.onPageScrolled(position, offset, offsetPixels);\r
+ }\r
+ }\r
+\r
+ private void dispatchOnPageSelected(int position) {\r
+ if (mOnPageChangeListener != null) {\r
+ mOnPageChangeListener.onPageSelected(position);\r
+ }\r
+ if (mOnPageChangeListeners != null) {\r
+ for (int i = 0, z = mOnPageChangeListeners.size(); i < z; i++) {\r
+ OnPageChangeListener listener = mOnPageChangeListeners.get(i);\r
+ if (listener != null) {\r
+ listener.onPageSelected(position);\r
+ }\r
+ }\r
+ }\r
+ if (mInternalPageChangeListener != null) {\r
+ mInternalPageChangeListener.onPageSelected(position);\r
+ }\r
+ }\r
+\r
+ private void dispatchOnScrollStateChanged(int state) {\r
+ if (mOnPageChangeListener != null) {\r
+ mOnPageChangeListener.onPageScrollStateChanged(state);\r
+ }\r
+ if (mOnPageChangeListeners != null) {\r
+ for (int i = 0, z = mOnPageChangeListeners.size(); i < z; i++) {\r
+ OnPageChangeListener listener = mOnPageChangeListeners.get(i);\r
+ if (listener != null) {\r
+ listener.onPageScrollStateChanged(state);\r
+ }\r
+ }\r
+ }\r
+ if (mInternalPageChangeListener != null) {\r
+ mInternalPageChangeListener.onPageScrollStateChanged(state);\r
+ }\r
+ }\r
+\r
+ private void completeScroll(boolean postEvents) {\r
+ boolean needPopulate = mScrollState == SCROLL_STATE_SETTLING;\r
+ if (needPopulate) {\r
+ // Done with scroll, no longer want to cache view drawing.\r
+ setScrollingCacheEnabled(false);\r
+ boolean wasScrolling = !mScroller.isFinished();\r
+ if (wasScrolling) {\r
+ mScroller.abortAnimation();\r
+ int oldX = getScrollX();\r
+ int oldY = getScrollY();\r
+ int x = mScroller.getCurrX();\r
+ int y = mScroller.getCurrY();\r
+ if (oldX != x || oldY != y) {\r
+ scrollTo(x, y);\r
+ if (isHorizontal() && x != oldX) {\r
+ pageScrolled(x, 0);\r
+ }\r
+ }\r
+ }\r
+ }\r
+ mPopulatePending = false;\r
+ for (int i = 0; i < mItems.size(); i++) {\r
+ ItemInfo ii = mItems.get(i);\r
+ if (ii.scrolling) {\r
+ needPopulate = true;\r
+ ii.scrolling = false;\r
+ }\r
+ }\r
+ if (needPopulate) {\r
+ if (postEvents) {\r
+ ViewCompat.postOnAnimation(this, mEndScrollRunnable);\r
+ } else {\r
+ mEndScrollRunnable.run();\r
+ }\r
+ }\r
+ }\r
+\r
+ private boolean isGutterDrag(float x, float dx, float y, float dy) {\r
+ if (isHorizontal()) {\r
+ return (x < mGutterSize && dx > 0) || (x > getWidth() - mGutterSize && dx < 0);\r
+ } else {\r
+ return (y < mGutterSize && dy > 0) || (y > getHeight() - mGutterSize && dy < 0);\r
+ }\r
+ }\r
+\r
+ private void enableLayers(boolean enable) {\r
+ final int childCount = getChildCount();\r
+ for (int i = 0; i < childCount; i++) {\r
+ final int layerType = enable ?\r
+ ViewCompat.LAYER_TYPE_HARDWARE : ViewCompat.LAYER_TYPE_NONE;\r
+ ViewCompat.setLayerType(getChildAt(i), layerType, null);\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public boolean onInterceptTouchEvent(MotionEvent ev) {\r
+ /*\r
+ * This method JUST determines whether we want to intercept the motion.\r
+ * If we return true, onMotionEvent will be called and we do the actual\r
+ * scrolling there.\r
+ */\r
+\r
+ final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;\r
+\r
+ // Always take care of the touch gesture being complete.\r
+ if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {\r
+ // Release the drag.\r
+ if (DEBUG) Log.v(TAG, "Intercept done!");\r
+ if (isHorizontal()) {\r
+ resetTouch();\r
+ } else {\r
+ mIsBeingDragged = false;\r
+ mIsUnableToDrag = false;\r
+ mActivePointerId = INVALID_POINTER;\r
+ if (mVelocityTracker != null) {\r
+ mVelocityTracker.recycle();\r
+ mVelocityTracker = null;\r
+ }\r
+ }\r
+ return false;\r
+ }\r
+\r
+ // Nothing more to do here if we have decided whether or not we\r
+ // are dragging.\r
+ if (action != MotionEvent.ACTION_DOWN) {\r
+ if (mIsBeingDragged) {\r
+ if (DEBUG) Log.v(TAG, "Intercept returning true!");\r
+ return true;\r
+ }\r
+ if (mIsUnableToDrag) {\r
+ if (DEBUG) Log.v(TAG, "Intercept returning false!");\r
+ return false;\r
+ }\r
+ }\r
+\r
+ if (isHorizontal()) {\r
+\r
+ switch (action) {\r
+ case MotionEvent.ACTION_MOVE: {\r
+ /*\r
+ * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check\r
+ * whether the user has moved far enough from his original down touch.\r
+ */\r
+\r
+ /*\r
+ * Locally do absolute value. mLastMotionY is set to the y value\r
+ * of the down event.\r
+ */\r
+ final int activePointerId = mActivePointerId;\r
+ if (activePointerId == INVALID_POINTER) {\r
+ break;\r
+ }\r
+\r
+ final int pointerIndex\r
+ = MotionEventCompat.findPointerIndex(ev, activePointerId);\r
+ final float x = MotionEventCompat.getX(ev, pointerIndex);\r
+ final float dx = x - mLastMotionX;\r
+ final float xDiff = Math.abs(dx);\r
+ final float y = MotionEventCompat.getY(ev, pointerIndex);\r
+ final float yDiff = Math.abs(y - mInitialMotionY);\r
+\r
+ if (dx != 0 && !isGutterDrag(mLastMotionX, dx, 0, 0) &&\r
+ canScroll(this, false, (int) dx, 0, (int) x, (int) y)) {\r
+ // Nested view has scrollable\r
+ // area under this point. Let it be handled there.\r
+ mLastMotionX = x;\r
+ mLastMotionY = y;\r
+ mIsUnableToDrag = true;\r
+ return false;\r
+ }\r
+ if (xDiff > mTouchSlop && xDiff * 0.5f > yDiff) {\r
+ if (DEBUG) Log.v(TAG, getContext().getString(R.string.debug_start_drag));\r
+ mIsBeingDragged = true;\r
+ requestParentDisallowInterceptTouchEvent(true);\r
+ setScrollState(SCROLL_STATE_DRAGGING);\r
+ mLastMotionX = dx > 0 ? mInitialMotionX + mTouchSlop :\r
+ mInitialMotionX - mTouchSlop;\r
+ mLastMotionY = y;\r
+ setScrollingCacheEnabled(true);\r
+ } else if (yDiff > mTouchSlop) {\r
+ // The finger has moved enough in the vertical\r
+ // direction to be counted as a drag... abort\r
+ // any attempt to drag horizontally, to work correctly\r
+ // with children that have scrolling containers.\r
+ if (DEBUG)\r
+ Log.v(TAG, getContext().getString(R.string.debug_start_unable_drag));\r
+ mIsUnableToDrag = true;\r
+ }\r
+ if (mIsBeingDragged && performDrag(x, 0)) {\r
+ // Scroll to follow the motion event\r
+ ViewCompat.postInvalidateOnAnimation(this);\r
+ }\r
+ break;\r
+ }\r
+\r
+ case MotionEvent.ACTION_DOWN: {\r
+ /*\r
+ * Remember location of down touch.\r
+ * ACTION_DOWN always refers to pointer index 0.\r
+ */\r
+ mLastMotionX = mInitialMotionX = ev.getX();\r
+ mLastMotionY = mInitialMotionY = ev.getY();\r
+ mActivePointerId = MotionEventCompat.getPointerId(ev, 0);\r
+ mIsUnableToDrag = false;\r
+\r
+ mIsScrollStarted = true;\r
+ mScroller.computeScrollOffset();\r
+ if (mScrollState == SCROLL_STATE_SETTLING &&\r
+ Math.abs(mScroller.getFinalX() - mScroller.getCurrX()) > mCloseEnough) {\r
+ // Let the user 'catch' the pager as it animates.\r
+ mScroller.abortAnimation();\r
+ mPopulatePending = false;\r
+ populate();\r
+ mIsBeingDragged = true;\r
+ requestParentDisallowInterceptTouchEvent(true);\r
+ setScrollState(SCROLL_STATE_DRAGGING);\r
+ } else {\r
+ completeScroll(false);\r
+ mIsBeingDragged = false;\r
+ }\r
+\r
+ if (DEBUG) Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY\r
+ + " mIsBeingDragged=" + mIsBeingDragged\r
+ + "mIsUnableToDrag=" + mIsUnableToDrag);\r
+ break;\r
+ }\r
+\r
+ case MotionEventCompat.ACTION_POINTER_UP:\r
+ onSecondaryPointerUp(ev);\r
+ break;\r
+ }\r
+\r
+ /* if (mVelocityTracker == null) {\r
+ mVelocityTracker = VelocityTracker.obtain();\r
+ }\r
+ mVelocityTracker.addMovement(ev);\r
+*/\r
+ /*\r
+ * The only time we want to intercept motion events is if we are in the\r
+ * drag mode.\r
+ */\r
+ } else {\r
+ switch (action) {\r
+ case MotionEvent.ACTION_MOVE: {\r
+ /*\r
+ * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check\r
+ * whether the user has moved far enough from his original down touch.\r
+ */\r
+\r
+ /*\r
+ * Locally do absolute value. mLastMotionY is set to the y value\r
+ * of the down event.\r
+ */\r
+ final int activePointerId = mActivePointerId;\r
+ if (activePointerId == INVALID_POINTER) {\r
+ // If we don't have a valid id, the touch down wasn't on content.\r
+ break;\r
+ }\r
+\r
+ final int pointerIndex\r
+ = MotionEventCompat.findPointerIndex(ev, activePointerId);\r
+ final float y = MotionEventCompat.getY(ev, pointerIndex);\r
+ final float dy = y - mLastMotionY;\r
+ final float yDiff = Math.abs(dy);\r
+ final float x\r
+ = MotionEventCompat.getX(ev, pointerIndex);\r
+ final float xDiff = Math.abs(x - mInitialMotionX);\r
+\r
+ if (dy != 0 && !isGutterDrag(0, 0, mLastMotionY, dy) &&\r
+ canScroll(this, false, 0,\r
+ (int) dy, (int) x, (int) y)) {\r
+ // Nested view has scrollable\r
+ // area under this point.\r
+ // Let it be handled there.\r
+ mLastMotionX = x;\r
+ mLastMotionY = y;\r
+ mIsUnableToDrag = true;\r
+ return false;\r
+ }\r
+ if (yDiff > mTouchSlop && yDiff * 0.5f > xDiff) {\r
+ if (DEBUG) Log.v(TAG, getContext().getString(R.string.debug_start_drag));\r
+ mIsBeingDragged = true;\r
+ requestParentDisallowInterceptTouchEvent(true);\r
+ setScrollState(SCROLL_STATE_DRAGGING);\r
+ mLastMotionY = dy > 0 ? mInitialMotionY + mTouchSlop :\r
+ mInitialMotionY - mTouchSlop;\r
+ mLastMotionX = x;\r
+ setScrollingCacheEnabled(true);\r
+ } else if (xDiff > mTouchSlop) {\r
+ // The finger has moved enough in the vertical\r
+ // direction to be counted as a drag... abort\r
+ // any attempt to drag horizontally, to work correctly\r
+ // with children that have scrolling containers.\r
+ if (DEBUG)\r
+ Log.v(TAG, getContext().getString(R.string.debug_start_unable_drag));\r
+ mIsUnableToDrag = true;\r
+ }\r
+ if (mIsBeingDragged && performDrag(0, y)) {\r
+ // Scroll to follow the motion event\r
+ ViewCompat.postInvalidateOnAnimation(this);\r
+ }\r
+ break;\r
+ }\r
+\r
+ case MotionEvent.ACTION_DOWN: {\r
+ /*\r
+ * Remember location of down touch.\r
+ * ACTION_DOWN always refers to pointer index 0.\r
+ */\r
+ mLastMotionX = mInitialMotionX = ev.getX();\r
+ mLastMotionY = mInitialMotionY = ev.getY();\r
+ mActivePointerId = MotionEventCompat.getPointerId(ev, 0);\r
+ mIsUnableToDrag = false;\r
+\r
+ mScroller.computeScrollOffset();\r
+ if (mScrollState == SCROLL_STATE_SETTLING &&\r
+ Math.abs(mScroller.getFinalY() - mScroller.getCurrY()) > mCloseEnough) {\r
+ // Let the user 'catch' the pager as it animates.\r
+ mScroller.abortAnimation();\r
+ mPopulatePending = false;\r
+ populate();\r
+ mIsBeingDragged = true;\r
+ requestParentDisallowInterceptTouchEvent(true);\r
+ setScrollState(SCROLL_STATE_DRAGGING);\r
+ } else {\r
+ completeScroll(false);\r
+ mIsBeingDragged = false;\r
+ }\r
+\r
+ if (DEBUG) Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY\r
+ + " mIsBeingDragged=" + mIsBeingDragged\r
+ + "mIsUnableToDrag=" + mIsUnableToDrag);\r
+ break;\r
+ }\r
+\r
+ case MotionEventCompat.ACTION_POINTER_UP:\r
+ onSecondaryPointerUp(ev);\r
+ break;\r
+ }\r
+\r
+ }\r
+ if (mVelocityTracker == null) {\r
+ mVelocityTracker = VelocityTracker.obtain();\r
+ }\r
+ mVelocityTracker.addMovement(ev);\r
+ return mIsBeingDragged;\r
+ }\r
+\r
+ @Override\r
+ public boolean onTouchEvent(MotionEvent ev) {\r
+ if (mFakeDragging) {\r
+ // A fake drag is in progress already, ignore this real one\r
+ // but still eat the touch events.\r
+ // (It is likely that the user is multi-touching the screen.)\r
+ return true;\r
+ }\r
+\r
+ if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {\r
+ // Don't handle edge touches immediately -- they may actually belong to one of our\r
+ // descendants.\r
+ return false;\r
+ }\r
+\r
+ if (mAdapter == null || mAdapter.getCount() == 0) {\r
+ // Nothing to present or scroll; nothing to touch.\r
+ return false;\r
+ }\r
+\r
+ if (mVelocityTracker == null) {\r
+ mVelocityTracker = VelocityTracker.obtain();\r
+ }\r
+ mVelocityTracker.addMovement(ev);\r
+\r
+ final int action = ev.getAction();\r
+ boolean needsInvalidate = false;\r
+\r
+ if (isHorizontal()) {\r
+ switch (action & MotionEventCompat.ACTION_MASK) {\r
+ case MotionEvent.ACTION_DOWN: {\r
+ mScroller.abortAnimation();\r
+ mPopulatePending = false;\r
+ populate();\r
+\r
+ // Remember where the motion event started\r
+ mLastMotionX = mInitialMotionX = ev.getX();\r
+ mLastMotionY = mInitialMotionY = ev.getY();\r
+ mActivePointerId = MotionEventCompat.getPointerId(ev, 0);\r
+ break;\r
+ }\r
+ case MotionEvent.ACTION_MOVE:\r
+ if (!mIsBeingDragged) {\r
+ final int pointerIndex\r
+ = MotionEventCompat.findPointerIndex(ev,\r
+ mActivePointerId);\r
+ if (pointerIndex == -1) {\r
+ // A child has consumed some\r
+ // touch events and put us into an inconsistent state.\r
+ needsInvalidate = resetTouch();\r
+ break;\r
+ }\r
+ final float x = MotionEventCompat.getX(ev, pointerIndex);\r
+ final float xDiff = Math.abs(x - mLastMotionX);\r
+ final float y\r
+ = MotionEventCompat.getY(ev,\r
+ pointerIndex);\r
+ final float yDiff = Math.abs(y - mLastMotionY);\r
+ if (xDiff > mTouchSlop && xDiff > yDiff) {\r
+ if (DEBUG)\r
+ Log.v(TAG, getContext().getString(R.string.debug_start_drag));\r
+ mIsBeingDragged = true;\r
+ requestParentDisallowInterceptTouchEvent(true);\r
+ mLastMotionX = x - mInitialMotionX > 0 ? mInitialMotionX + mTouchSlop :\r
+ mInitialMotionX - mTouchSlop;\r
+ mLastMotionY = y;\r
+ setScrollState(SCROLL_STATE_DRAGGING);\r
+ setScrollingCacheEnabled(true);\r
+\r
+ // Disallow Parent Intercept, just in case\r
+ ViewParent parent = getParent();\r
+ if (parent != null) {\r
+ parent.requestDisallowInterceptTouchEvent(true);\r
+ }\r
+ }\r
+ }\r
+ // Not else! Note that mIsBeingDragged can be set above.\r
+ if (mIsBeingDragged) {\r
+ // Scroll to follow the motion event\r
+ final int activePointerIndex = MotionEventCompat.findPointerIndex(\r
+ ev, mActivePointerId);\r
+ final float x = MotionEventCompat.getX(ev, activePointerIndex);\r
+ needsInvalidate |= performDrag(x, 0);\r
+ }\r
+ break;\r
+ case MotionEvent.ACTION_UP:\r
+ if (mIsBeingDragged) {\r
+ final VelocityTracker velocityTracker = mVelocityTracker;\r
+ velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);\r
+ int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(\r
+ velocityTracker, mActivePointerId);\r
+ mPopulatePending = true;\r
+ final int width = getClientWidth();\r
+ final int scrollX = getScrollX();\r
+ final ItemInfo ii = infoForCurrentScrollPosition();\r
+ final float marginOffset = (float) mPageMargin / width;\r
+ final int currentPage = ii.position;\r
+ final float pageOffset = (((float) scrollX / width) - ii.offset)\r
+ / (ii.widthFactor + marginOffset);\r
+ final int activePointerIndex =\r
+ MotionEventCompat.findPointerIndex(ev, mActivePointerId);\r
+ final float x = MotionEventCompat.getX(ev, activePointerIndex);\r
+ final int totalDelta = (int) (x - mInitialMotionX);\r
+ int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,\r
+ totalDelta, 0);\r
+ setCurrentItemInternal(nextPage, true, true, initialVelocity);\r
+\r
+ needsInvalidate = resetTouch();\r
+ }\r
+ break;\r
+ case MotionEvent.ACTION_CANCEL:\r
+ if (mIsBeingDragged) {\r
+ scrollToItem(mCurItem, true, 0, false);\r
+ needsInvalidate = resetTouch();\r
+ }\r
+ break;\r
+ case MotionEventCompat.ACTION_POINTER_DOWN: {\r
+ final int index = MotionEventCompat.getActionIndex(ev);\r
+ final float x = MotionEventCompat.getX(ev, index);\r
+ mLastMotionX = x;\r
+ mActivePointerId = MotionEventCompat.getPointerId(ev, index);\r
+ break;\r
+ }\r
+ case MotionEventCompat.ACTION_POINTER_UP:\r
+ onSecondaryPointerUp(ev);\r
+ mLastMotionX = MotionEventCompat.getX(ev,\r
+ MotionEventCompat.findPointerIndex(ev, mActivePointerId));\r
+ break;\r
+ }\r
+ } else {\r
+ switch (action & MotionEventCompat.ACTION_MASK) {\r
+ case MotionEvent.ACTION_DOWN: {\r
+ mScroller.abortAnimation();\r
+ mPopulatePending = false;\r
+ populate();\r
+\r
+ // Remember where the motion event started\r
+ mLastMotionX = mInitialMotionX = ev.getX();\r
+ mLastMotionY = mInitialMotionY = ev.getY();\r
+ mActivePointerId = MotionEventCompat.getPointerId(ev, 0);\r
+ break;\r
+ }\r
+ case MotionEvent.ACTION_MOVE:\r
+ if (!mIsBeingDragged) {\r
+ final int pointerIndex =\r
+ MotionEventCompat.findPointerIndex(ev, mActivePointerId);\r
+ final float y = MotionEventCompat.getY(ev, pointerIndex);\r
+ final float yDiff\r
+ = Math.abs(y - mLastMotionY);\r
+ final float x\r
+ = MotionEventCompat.getX(ev, pointerIndex);\r
+ final float xDiff = Math.abs(x - mLastMotionX);\r
+\r
+ if (yDiff > mTouchSlop && yDiff > xDiff) {\r
+ if (DEBUG)\r
+ Log.v(TAG, getContext().getString(R.string.debug_start_drag));\r
+ mIsBeingDragged = true;\r
+ requestParentDisallowInterceptTouchEvent(true);\r
+ mLastMotionY = y - mInitialMotionY > 0 ? mInitialMotionY + mTouchSlop :\r
+ mInitialMotionY - mTouchSlop;\r
+ mLastMotionX = x;\r
+ setScrollState(SCROLL_STATE_DRAGGING);\r
+ setScrollingCacheEnabled(true);\r
+\r
+ // Disallow Parent Intercept, just in case\r
+ ViewParent parent = getParent();\r
+ if (parent != null) {\r
+ parent.requestDisallowInterceptTouchEvent(true);\r
+ }\r
+ }\r
+ }\r
+ // Not else! Note that mIsBeingDragged can be set above.\r
+ if (mIsBeingDragged) {\r
+ // Scroll to follow the motion event\r
+ final int activePointerIndex = MotionEventCompat.findPointerIndex(\r
+ ev, mActivePointerId);\r
+ final float y = MotionEventCompat.getY(ev, activePointerIndex);\r
+ needsInvalidate |= performDrag(0, y);\r
+ }\r
+ break;\r
+ case MotionEvent.ACTION_UP:\r
+ if (mIsBeingDragged) {\r
+ final VelocityTracker velocityTracker = mVelocityTracker;\r
+ velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);\r
+ int initialVelocity = (int) VelocityTrackerCompat.getYVelocity(\r
+ velocityTracker, mActivePointerId);\r
+ mPopulatePending = true;\r
+ final int height = getClientHeight();\r
+ final int scrollY = getScrollY();\r
+ final ItemInfo ii = infoForCurrentScrollPosition();\r
+ final int currentPage = ii.position;\r
+ final float pageOffset =\r
+ (((float) scrollY / height) - ii.offset) / ii.heightFactor;\r
+ final int activePointerIndex =\r
+ MotionEventCompat.findPointerIndex(ev, mActivePointerId);\r
+ final float y = MotionEventCompat.getY(ev, activePointerIndex);\r
+ final int totalDelta = (int) (y - mInitialMotionY);\r
+ int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,\r
+ 0, totalDelta);\r
+ setCurrentItemInternal(nextPage, true, true, initialVelocity);\r
+\r
+ mActivePointerId = INVALID_POINTER;\r
+ endDrag();\r
+ needsInvalidate = mTopEdge.onRelease() | mBottomEdge.onRelease();\r
+ }\r
+ break;\r
+ case MotionEvent.ACTION_CANCEL:\r
+ if (mIsBeingDragged) {\r
+ scrollToItem(mCurItem, true, 0, false);\r
+ mActivePointerId = INVALID_POINTER;\r
+ endDrag();\r
+ needsInvalidate = mTopEdge.onRelease() | mBottomEdge.onRelease();\r
+ }\r
+ break;\r
+ case MotionEventCompat.ACTION_POINTER_DOWN: {\r
+ final int index = MotionEventCompat.getActionIndex(ev);\r
+ final float y = MotionEventCompat.getY(ev, index);\r
+ mLastMotionY = y;\r
+ mActivePointerId = MotionEventCompat.getPointerId(ev, index);\r
+ break;\r
+ }\r
+ case MotionEventCompat.ACTION_POINTER_UP:\r
+ onSecondaryPointerUp(ev);\r
+ mLastMotionY = MotionEventCompat.getY(ev,\r
+ MotionEventCompat.findPointerIndex(ev, mActivePointerId));\r
+ break;\r
+ }\r
+ }\r
+ if (needsInvalidate) {\r
+ ViewCompat.postInvalidateOnAnimation(this);\r
+ }\r
+ return true;\r
+ }\r
+\r
+ private boolean resetTouch() {\r
+ boolean needsInvalidate;\r
+ mActivePointerId = INVALID_POINTER;\r
+ endDrag();\r
+ needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease();\r
+ return needsInvalidate;\r
+ }\r
+\r
+ private void requestParentDisallowInterceptTouchEvent(boolean disallowIntercept) {\r
+ final ViewParent parent = getParent();\r
+ if (parent != null) {\r
+ parent.requestDisallowInterceptTouchEvent(disallowIntercept);\r
+ }\r
+ }\r
+\r
+ private boolean performDrag(float x, float y) {\r
+ boolean needsInvalidate = false;\r
+ if (isHorizontal()) {\r
+ final float deltaX = mLastMotionX - x;\r
+ mLastMotionX = x;\r
+\r
+ float oldScrollX = getScrollX();\r
+ float scrollX = oldScrollX + deltaX;\r
+ final int width = getClientWidth();\r
+\r
+ float leftBound = width * mFirstOffset;\r
+ float rightBound = width * mLastOffset;\r
+ boolean leftAbsolute = true;\r
+ boolean rightAbsolute = true;\r
+\r
+ final ItemInfo firstItem = mItems.get(0);\r
+ final ItemInfo lastItem = mItems.get(mItems.size() - 1);\r
+ if (firstItem.position != 0) {\r
+ leftAbsolute = false;\r
+ leftBound = firstItem.offset * width;\r
+ }\r
+ if (lastItem.position != mAdapter.getCount() - 1) {\r
+ rightAbsolute = false;\r
+ rightBound = lastItem.offset * width;\r
+ }\r
+\r
+ if (scrollX < leftBound) {\r
+ if (leftAbsolute) {\r
+ float over = leftBound - scrollX;\r
+ needsInvalidate = mLeftEdge.onPull(Math.abs(over) / width);\r
+ }\r
+ scrollX = leftBound;\r
+ } else if (scrollX > rightBound) {\r
+ if (rightAbsolute) {\r
+ float over = scrollX - rightBound;\r
+ needsInvalidate = mRightEdge.onPull(Math.abs(over) / width);\r
+ }\r
+ scrollX = rightBound;\r
+ }\r
+ // Don't lose the rounded component\r
+ mLastMotionX += scrollX - (int) scrollX;\r
+ scrollTo((int) scrollX, getScrollY());\r
+ pageScrolled((int) scrollX, 0);\r
+ } else {\r
+\r
+ final float deltaY = mLastMotionY - y;\r
+ mLastMotionY = y;\r
+\r
+ float oldScrollY = getScrollY();\r
+ float scrollY = oldScrollY + deltaY;\r
+ final int height = getClientHeight();\r
+\r
+ float topBound = height * mFirstOffset;\r
+ float bottomBound = height * mLastOffset;\r
+ boolean topAbsolute = true;\r
+ boolean bottomAbsolute = true;\r
+\r
+ final ItemInfo firstItem = mItems.get(0);\r
+ final ItemInfo lastItem = mItems.get(mItems.size() - 1);\r
+ if (firstItem.position != 0) {\r
+ topAbsolute = false;\r
+ topBound = firstItem.offset * height;\r
+ }\r
+ if (lastItem.position != mAdapter.getCount() - 1) {\r
+ bottomAbsolute = false;\r
+ bottomBound = lastItem.offset * height;\r
+ }\r
+\r
+ if (scrollY < topBound) {\r
+ if (topAbsolute) {\r
+ float over = topBound - scrollY;\r
+ needsInvalidate = mTopEdge.onPull(Math.abs(over) / height);\r
+ }\r
+ scrollY = topBound;\r
+ } else if (scrollY > bottomBound) {\r
+ if (bottomAbsolute) {\r
+ float over = scrollY - bottomBound;\r
+ needsInvalidate = mBottomEdge.onPull(Math.abs(over) / height);\r
+ }\r
+ scrollY = bottomBound;\r
+ }\r
+ // Don't lose the rounded component\r
+ mLastMotionX += scrollY - (int) scrollY;\r
+ scrollTo(getScrollX(), (int) scrollY);\r
+ pageScrolled(0, (int) scrollY);\r
+ }\r
+\r
+ return needsInvalidate;\r
+ }\r
+\r
+ /**\r
+ * @return Info about the page at the current scroll position.\r
+ * This can be synthetic for a missing middle page; the 'object' field can be null.\r
+ */\r
+ private ItemInfo infoForCurrentScrollPosition() {\r
+ ItemInfo lastItem = null;\r
+ if (isHorizontal()) {\r
+ final int width = getClientWidth();\r
+ final float scrollOffset = width > 0 ? (float) getScrollX() / width : 0;\r
+ final float marginOffset = width > 0 ? (float) mPageMargin / width : 0;\r
+ int lastPos = -1;\r
+ float lastOffset = 0.f;\r
+ float lastWidth = 0.f;\r
+ boolean first = true;\r
+\r
+ for (int i = 0; i < mItems.size(); i++) {\r
+ ItemInfo ii = mItems.get(i);\r
+ float offset;\r
+ if (!first && ii.position != lastPos + 1) {\r
+ // Create a synthetic item for a missing page.\r
+ ii = mTempItem;\r
+ ii.offset = lastOffset + lastWidth + marginOffset;\r
+ ii.position = lastPos + 1;\r
+ ii.widthFactor = mAdapter.getPageWidth(ii.position);\r
+ i--;\r
+ }\r
+ offset = ii.offset;\r
+\r
+ final float leftBound = offset;\r
+ final float rightBound = offset + ii.widthFactor + marginOffset;\r
+ if (first || scrollOffset >= leftBound) {\r
+ if (scrollOffset < rightBound || i == mItems.size() - 1) {\r
+ return ii;\r
+ }\r
+ } else {\r
+ return lastItem;\r
+ }\r
+ first = false;\r
+ lastPos = ii.position;\r
+ lastOffset = offset;\r
+ lastWidth = ii.widthFactor;\r
+ lastItem = ii;\r
+ }\r
+ } else {\r
+ final int height = getClientHeight();\r
+ final float scrollOffset = height > 0 ? (float) getScrollY() / height : 0;\r
+ final float marginOffset = height > 0 ? (float) mPageMargin / height : 0;\r
+ int lastPos = -1;\r
+ float lastOffset = 0.f;\r
+ float lastHeight = 0.f;\r
+ boolean first = true;\r
+\r
+ for (int i = 0; i < mItems.size(); i++) {\r
+ ItemInfo ii = mItems.get(i);\r
+ float offset;\r
+ if (!first && ii.position != lastPos + 1) {\r
+ // Create a synthetic item for a missing page.\r
+ ii = mTempItem;\r
+ ii.offset = lastOffset + lastHeight + marginOffset;\r
+ ii.position = lastPos + 1;\r
+ ii.heightFactor = mAdapter.getPageWidth(ii.position);\r
+ i--;\r
+ }\r
+ offset = ii.offset;\r
+\r
+ final float topBound = offset;\r
+ final float bottomBound = offset + ii.heightFactor + marginOffset;\r
+ if (first || scrollOffset >= topBound) {\r
+ if (scrollOffset < bottomBound || i == mItems.size() - 1) {\r
+ return ii;\r
+ }\r
+ } else {\r
+ return lastItem;\r
+ }\r
+ first = false;\r
+ lastPos = ii.position;\r
+ lastOffset = offset;\r
+ lastHeight = ii.heightFactor;\r
+ lastItem = ii;\r
+ }\r
+ }\r
+\r
+ return lastItem;\r
+ }\r
+\r
+ private int determineTargetPage(int currentPage, float pageOffset,\r
+ int velocity, int deltaX, int deltaY) {\r
+ int targetPage;\r
+ if (isHorizontal()) {\r
+ if (Math.abs(deltaX) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) {\r
+ targetPage = velocity > 0 ? currentPage : currentPage + 1;\r
+ } else {\r
+ final float truncator = currentPage >= mCurItem ? 0.4f : 0.6f;\r
+ targetPage = (int) (currentPage + pageOffset + truncator);\r
+ }\r
+ } else {\r
+ if (Math.abs(deltaY) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) {\r
+ targetPage = velocity > 0 ? currentPage : currentPage + 1;\r
+ } else {\r
+ final float truncator = currentPage >= mCurItem ? 0.4f : 0.6f;\r
+ targetPage = (int) (currentPage + pageOffset + truncator);\r
+ }\r
+ }\r
+\r
+ if (mItems.size() > 0) {\r
+ final ItemInfo firstItem = mItems.get(0);\r
+ final ItemInfo lastItem = mItems.get(mItems.size() - 1);\r
+\r
+ // Only let the user target pages we have items for\r
+ targetPage = Math.max(firstItem.position, Math.min(targetPage, lastItem.position));\r
+ }\r
+\r
+ return targetPage;\r
+ }\r
+\r
+ @Override\r
+ public void draw(Canvas canvas) {\r
+ super.draw(canvas);\r
+ boolean needsInvalidate = false;\r
+\r
+ final int overScrollMode = ViewCompat.getOverScrollMode(this);\r
+ if (overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS ||\r
+ (overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS &&\r
+ mAdapter != null && mAdapter.getCount() > 1)) {\r
+ if (isHorizontal()) {\r
+ if (!mLeftEdge.isFinished()) {\r
+ final int restoreCount = canvas.save();\r
+ final int height = getHeight() - getPaddingTop() - getPaddingBottom();\r
+ final int width = getWidth();\r
+\r
+ canvas.rotate(270);\r
+ canvas.translate(-height + getPaddingTop(), mFirstOffset * width);\r
+ mLeftEdge.setSize(height, width);\r
+ needsInvalidate |= mLeftEdge.draw(canvas);\r
+ canvas.restoreToCount(restoreCount);\r
+ }\r
+ if (!mRightEdge.isFinished()) {\r
+ final int restoreCount = canvas.save();\r
+ final int width = getWidth();\r
+ final int height = getHeight() - getPaddingTop() - getPaddingBottom();\r
+\r
+ canvas.rotate(90);\r
+ canvas.translate(-getPaddingTop(), -(mLastOffset + 1) * width);\r
+ mRightEdge.setSize(height, width);\r
+ needsInvalidate |= mRightEdge.draw(canvas);\r
+ canvas.restoreToCount(restoreCount);\r
+ } else {\r
+ mLeftEdge.finish();\r
+ mRightEdge.finish();\r
+ }\r
+ } else {\r
+ if (!mTopEdge.isFinished()) {\r
+ final int restoreCount = canvas.save();\r
+ final int height = getHeight();\r
+ final int width = getWidth() - getPaddingLeft() - getPaddingRight();\r
+\r
+ canvas.translate(getPaddingLeft(), mFirstOffset * height);\r
+ mTopEdge.setSize(width, height);\r
+ needsInvalidate |= mTopEdge.draw(canvas);\r
+ canvas.restoreToCount(restoreCount);\r
+ }\r
+ if (!mBottomEdge.isFinished()) {\r
+ final int restoreCount = canvas.save();\r
+ final int height = getHeight();\r
+ final int width = getWidth() - getPaddingLeft() - getPaddingRight();\r
+\r
+ canvas.rotate(180);\r
+ canvas.translate(-width - getPaddingLeft(), -(mLastOffset + 1) * height);\r
+ mBottomEdge.setSize(width, height);\r
+ needsInvalidate |= mBottomEdge.draw(canvas);\r
+ canvas.restoreToCount(restoreCount);\r
+ } else {\r
+ mTopEdge.finish();\r
+ mBottomEdge.finish();\r
+ }\r
+ }\r
+ }\r
+\r
+\r
+ if (needsInvalidate) {\r
+ // Keep animating\r
+ ViewCompat.postInvalidateOnAnimation(this);\r
+ }\r
+ }\r
+\r
+ @Override\r
+ protected void onDraw(Canvas canvas) {\r
+ super.onDraw(canvas);\r
+\r
+ // Draw the margin drawable between pages if needed.\r
+ if (mPageMargin > 0 && mMarginDrawable != null && mItems.size() > 0 && mAdapter != null) {\r
+ if (isHorizontal()) {\r
+ final int scrollX = getScrollX();\r
+ final int width = getWidth();\r
+\r
+ final float marginOffset = (float) mPageMargin / width;\r
+ int itemIndex = 0;\r
+ ItemInfo ii = mItems.get(0);\r
+ float offset = ii.offset;\r
+ final int itemCount = mItems.size();\r
+ final int firstPos = ii.position;\r
+ final int lastPos = mItems.get(itemCount - 1).position;\r
+ for (int pos = firstPos; pos < lastPos; pos++) {\r
+ while (pos > ii.position && itemIndex < itemCount) {\r
+ ii = mItems.get(++itemIndex);\r
+ }\r
+\r
+ float drawAt;\r
+ if (pos == ii.position) {\r
+ drawAt = (ii.offset + ii.widthFactor) * width;\r
+ offset = ii.offset + ii.widthFactor + marginOffset;\r
+ } else {\r
+ float widthFactor = mAdapter.getPageWidth(pos);\r
+ drawAt = (offset + widthFactor) * width;\r
+ offset += widthFactor + marginOffset;\r
+ }\r
+\r
+ if (drawAt + mPageMargin > scrollX) {\r
+ mMarginDrawable.setBounds(Math.round(drawAt), mTopPageBounds,\r
+ Math.round(drawAt + mPageMargin), mBottomPageBounds);\r
+ mMarginDrawable.draw(canvas);\r
+ }\r
+\r
+ if (drawAt > scrollX + width) {\r
+ break; // No more visible, no sense in continuing\r
+ }\r
+ }\r
+ } else {\r
+ final int scrollY = getScrollY();\r
+ final int height = getHeight();\r
+\r
+ final float marginOffset = (float) mPageMargin / height;\r
+ int itemIndex = 0;\r
+ ItemInfo ii = mItems.get(0);\r
+ float offset = ii.offset;\r
+ final int itemCount = mItems.size();\r
+ final int firstPos = ii.position;\r
+ final int lastPos = mItems.get(itemCount - 1).position;\r
+ for (int pos = firstPos; pos < lastPos; pos++) {\r
+ while (pos > ii.position && itemIndex < itemCount) {\r
+ ii = mItems.get(++itemIndex);\r
+ }\r
+\r
+ float drawAt;\r
+ if (pos == ii.position) {\r
+ drawAt = (ii.offset + ii.heightFactor) * height;\r
+ offset = ii.offset + ii.heightFactor + marginOffset;\r
+ } else {\r
+ float heightFactor = mAdapter.getPageWidth(pos);\r
+ drawAt = (offset + heightFactor) * height;\r
+ offset += heightFactor + marginOffset;\r
+ }\r
+\r
+ if (drawAt + mPageMargin > scrollY) {\r
+ mMarginDrawable.setBounds(mLeftPageBounds, (int) drawAt,\r
+ mRightPageBounds, (int) (drawAt + mPageMargin + 0.5f));\r
+ mMarginDrawable.draw(canvas);\r
+ }\r
+\r
+ if (drawAt > scrollY + height) {\r
+ break; // No more visible, no sense in continuing\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Start a fake drag of the pager.\r
+ * <p>\r
+ * <p>A fake drag can be useful if you want to synchronize the motion of the ViewPager\r
+ * with the touch scrolling of another view, while still letting the ViewPager\r
+ * control the snapping motion and fling behavior. (e.g. parallax-scrolling tabs.)\r
+ * Call {@link #fakeDragBy(float)} to simulate the actual drag motion. Call\r
+ * {@link #endFakeDrag()} to complete the fake drag and fling as necessary.\r
+ * <p>\r
+ * <p>During a fake drag the ViewPager will ignore all touch events. If a real drag\r
+ * is already in progress, this method will return false.\r
+ *\r
+ * @return true if the fake drag began successfully, false if it could not be started.\r
+ * @see #fakeDragBy(float)\r
+ * @see #endFakeDrag()\r
+ */\r
+ public boolean beginFakeDrag() {\r
+ if (mIsBeingDragged) {\r
+ return false;\r
+ }\r
+ mFakeDragging = true;\r
+ setScrollState(SCROLL_STATE_DRAGGING);\r
+ if (isHorizontal()) {\r
+ mInitialMotionX = mLastMotionX = 0;\r
+ } else {\r
+ mInitialMotionY = mLastMotionY = 0;\r
+ }\r
+ if (mVelocityTracker == null) {\r
+ mVelocityTracker = VelocityTracker.obtain();\r
+ } else {\r
+ mVelocityTracker.clear();\r
+ }\r
+ final long time = SystemClock.uptimeMillis();\r
+ final MotionEvent ev = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 0, 0, 0);\r
+ mVelocityTracker.addMovement(ev);\r
+ ev.recycle();\r
+ mFakeDragBeginTime = time;\r
+ return true;\r
+ }\r
+\r
+ /**\r
+ * End a fake drag of the pager.\r
+ *\r
+ * @see #beginFakeDrag()\r
+ * @see #endFakeDrag()\r
+ */\r
+ public void endFakeDrag() {\r
+ if (!mFakeDragging) {\r
+ throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");\r
+ }\r
+\r
+ if (mAdapter != null) {\r
+ if (isHorizontal()) {\r
+ final VelocityTracker velocityTracker = mVelocityTracker;\r
+ velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);\r
+ int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(\r
+ velocityTracker, mActivePointerId);\r
+ mPopulatePending = true;\r
+ final int width = getClientWidth();\r
+ final int scrollX = getScrollX();\r
+ final ItemInfo ii = infoForCurrentScrollPosition();\r
+ final int currentPage = ii.position;\r
+ final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor;\r
+ final int totalDelta = (int) (mLastMotionX - mInitialMotionX);\r
+ int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,\r
+ totalDelta, 0);\r
+ setCurrentItemInternal(nextPage, true, true, initialVelocity);\r
+ } else {\r
+ final VelocityTracker velocityTracker = mVelocityTracker;\r
+ velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);\r
+ int initialVelocity = (int) VelocityTrackerCompat.getYVelocity(\r
+ velocityTracker, mActivePointerId);\r
+ mPopulatePending = true;\r
+ final int height = getClientHeight();\r
+ final int scrollY = getScrollY();\r
+ final ItemInfo ii = infoForCurrentScrollPosition();\r
+ final int currentPage = ii.position;\r
+ final float pageOffset = (((float) scrollY / height) - ii.offset) / ii.heightFactor;\r
+ final int totalDelta = (int) (mLastMotionY - mInitialMotionY);\r
+ int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,\r
+ 0, totalDelta);\r
+ setCurrentItemInternal(nextPage, true, true, initialVelocity);\r
+ }\r
+ }\r
+ endDrag();\r
+\r
+ mFakeDragging = false;\r
+ }\r
+\r
+ /**\r
+ * Fake drag by an offset in pixels. You must have called {@link #beginFakeDrag()} first.\r
+ *\r
+ * @param xOffset Offset in pixels to drag by.\r
+ * @see #beginFakeDrag()\r
+ * @see #endFakeDrag()\r
+ */\r
+ public void fakeDragBy(float xOffset, float yOffset) {\r
+ MotionEvent ev = null;\r
+ if (!mFakeDragging) {\r
+ throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");\r
+ }\r
+\r
+ if (mAdapter == null) {\r
+ return;\r
+ }\r
+\r
+ if (isHorizontal()) {\r
+ mLastMotionX += xOffset;\r
+\r
+ float oldScrollX = getScrollX();\r
+ float scrollX = oldScrollX - xOffset;\r
+ final int width = getClientWidth();\r
+\r
+ float leftBound = width * mFirstOffset;\r
+ float rightBound = width * mLastOffset;\r
+\r
+ final ItemInfo firstItem = mItems.get(0);\r
+ final ItemInfo lastItem = mItems.get(mItems.size() - 1);\r
+ if (firstItem.position != 0) {\r
+ leftBound = firstItem.offset * width;\r
+ }\r
+ if (lastItem.position != mAdapter.getCount() - 1) {\r
+ rightBound = lastItem.offset * width;\r
+ }\r
+\r
+ if (scrollX < leftBound) {\r
+ scrollX = leftBound;\r
+ } else if (scrollX > rightBound) {\r
+ scrollX = rightBound;\r
+ }\r
+ // Don't lose the rounded component\r
+ mLastMotionX += scrollX - (int) scrollX;\r
+ scrollTo((int) scrollX, getScrollY());\r
+ pageScrolled((int) scrollX, 0);\r
+\r
+ // Synthesize an event for the VelocityTracker.\r
+ final long time = SystemClock.uptimeMillis();\r
+ ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE,\r
+ mLastMotionX, 0, 0);\r
+ } else {\r
+ mLastMotionY += yOffset;\r
+\r
+ float oldScrollY = getScrollY();\r
+ float scrollY = oldScrollY - yOffset;\r
+ final int height = getClientHeight();\r
+\r
+ float topBound = height * mFirstOffset;\r
+ float bottomBound = height * mLastOffset;\r
+\r
+ final ItemInfo firstItem = mItems.get(0);\r
+ final ItemInfo lastItem = mItems.get(mItems.size() - 1);\r
+ if (firstItem.position != 0) {\r
+ topBound = firstItem.offset * height;\r
+ }\r
+ if (lastItem.position != mAdapter.getCount() - 1) {\r
+ bottomBound = lastItem.offset * height;\r
+ }\r
+\r
+ if (scrollY < topBound) {\r
+ scrollY = topBound;\r
+ } else if (scrollY > bottomBound) {\r
+ scrollY = bottomBound;\r
+ }\r
+ // Don't lose the rounded component\r
+ mLastMotionY += scrollY - (int) scrollY;\r
+ scrollTo(getScrollX(), (int) scrollY);\r
+ pageScrolled(0, (int) scrollY);\r
+\r
+ // Synthesize an event for the VelocityTracker.\r
+ final long time = SystemClock.uptimeMillis();\r
+ ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE,\r
+ 0, mLastMotionY, 0);\r
+ }\r
+ mVelocityTracker.addMovement(ev);\r
+ ev.recycle();\r
+ }\r
+\r
+ /**\r
+ * Returns true if a fake drag is in progress.\r
+ *\r
+ * @return true if currently in a fake drag, false otherwise.\r
+ * @see #beginFakeDrag()\r
+ * @see #fakeDragBy(float)\r
+ * @see #endFakeDrag()\r
+ */\r
+ public boolean isFakeDragging() {\r
+ return mFakeDragging;\r
+ }\r
+\r
+ private void onSecondaryPointerUp(MotionEvent ev) {\r
+ final int pointerIndex = MotionEventCompat.getActionIndex(ev);\r
+ final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);\r
+ if (pointerId == mActivePointerId) {\r
+ // This was our active pointer going up. Choose a new\r
+ // active pointer and adjust accordingly.\r
+ final int newPointerIndex = pointerIndex == 0 ? 1 : 0;\r
+ if (isHorizontal()) {\r
+ mLastMotionX = MotionEventCompat.getX(ev, newPointerIndex);\r
+ } else {\r
+ mLastMotionY = MotionEventCompat.getY(ev, newPointerIndex);\r
+ }\r
+ mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);\r
+ if (mVelocityTracker != null) {\r
+ mVelocityTracker.clear();\r
+ }\r
+ }\r
+ }\r
+\r
+ private void endDrag() {\r
+ mIsBeingDragged = false;\r
+ mIsUnableToDrag = false;\r
+\r
+ if (mVelocityTracker != null) {\r
+ mVelocityTracker.recycle();\r
+ mVelocityTracker = null;\r
+ }\r
+ }\r
+\r
+ private void setScrollingCacheEnabled(boolean enabled) {\r
+ if (mScrollingCacheEnabled != enabled) {\r
+ mScrollingCacheEnabled = enabled;\r
+ if (USE_CACHE) {\r
+ final int size = getChildCount();\r
+ for (int i = 0; i < size; ++i) {\r
+ final View child = getChildAt(i);\r
+ if (child.getVisibility() != GONE) {\r
+ child.setDrawingCacheEnabled(enabled);\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ public boolean canScrollHorizontally(int direction) {\r
+ if (mAdapter == null) {\r
+ return false;\r
+ }\r
+\r
+ final int width = getClientWidth();\r
+ final int scrollX = getScrollX();\r
+ if (direction < 0) {\r
+ return (scrollX > (int) (width * mFirstOffset));\r
+ } else if (direction > 0) {\r
+ return (scrollX < (int) (width * mLastOffset));\r
+ } else {\r
+ return false;\r
+ }\r
+ }\r
+\r
+ public boolean internalCanScrollVertically(int direction) {\r
+ if (mAdapter == null) {\r
+ return false;\r
+ }\r
+\r
+ final int height = getClientHeight();\r
+ final int scrollY = getScrollY();\r
+ if (direction < 0) {\r
+ return (scrollY > (int) (height * mFirstOffset));\r
+ } else if (direction > 0) {\r
+ return (scrollY < (int) (height * mLastOffset));\r
+ } else {\r
+ return false;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Tests scrollability within child views of v given a delta of dx.\r
+ *\r
+ * @param v View to test for horizontal scrollability\r
+ * @param checkV Whether the view v passed should itself be checked for scrollability (true),\r
+ * or just its children (false).\r
+ * @param dx Delta scrolled in pixels\r
+ * @param x X coordinate of the active touch point\r
+ * @param y Y coordinate of the active touch point\r
+ * @return true if child views of v can be scrolled by delta of dx.\r
+ */\r
+ protected boolean canScroll(View v, boolean checkV, int dx, int dy, int x, int y) {\r
+ if (v instanceof ViewGroup) {\r
+ if (isHorizontal()) {\r
+ final ViewGroup group = (ViewGroup) v;\r
+ final int scrollX = v.getScrollX();\r
+ final int scrollY = v.getScrollY();\r
+ final int count = group.getChildCount();\r
+ // Count backwards - let topmost views consume scroll distance first.\r
+ for (int i = count - 1; i >= 0; i--) {\r
+ // TODO: Add versioned support here for transformed views.\r
+ // This will not work for transformed views in Honeycomb+\r
+ final View child = group.getChildAt(i);\r
+ if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&\r
+ y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&\r
+ canScroll(child, true, dx, 0, x + scrollX - child.getLeft(),\r
+ y + scrollY - child.getTop())) {\r
+ return true;\r
+ }\r
+ }\r
+ return checkV && ViewCompat.canScrollHorizontally(v, -dx);\r
+ } else {\r
+ final ViewGroup group = (ViewGroup) v;\r
+ final int scrollX = v.getScrollX();\r
+ final int scrollY = v.getScrollY();\r
+ final int count = group.getChildCount();\r
+ // Count backwards - let topmost views consume scroll distance first.\r
+ for (int i = count - 1; i >= 0; i--) {\r
+ // TODO: Add versioned support here for transformed views.\r
+ // This will not work for transformed views in Honeycomb+\r
+ final View child = group.getChildAt(i);\r
+ if (y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&\r
+ x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&\r
+ canScroll(child, true, 0, dy, x + scrollX - child.getLeft(),\r
+ y + scrollY - child.getTop())) {\r
+ return true;\r
+ }\r
+ }\r
+\r
+ return checkV && ViewCompat.canScrollVertically(v, -dy);\r
+\r
+ }\r
+ }\r
+ return false;\r
+ }\r
+\r
+ @Override\r
+ public boolean dispatchKeyEvent(KeyEvent event) {\r
+ // Let the focused view and/or our descendants get the key first\r
+ return super.dispatchKeyEvent(event) || executeKeyEvent(event);\r
+ }\r
+\r
+ /**\r
+ * You can call this function yourself to have the scroll view perform\r
+ * scrolling from a key event, just as if the event had been dispatched to\r
+ * it by the view hierarchy.\r
+ *\r
+ * @param event The key event to execute.\r
+ * @return Return true if the event was handled, else false.\r
+ */\r
+ public boolean executeKeyEvent(KeyEvent event) {\r
+ boolean handled = false;\r
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {\r
+ switch (event.getKeyCode()) {\r
+ case KeyEvent.KEYCODE_DPAD_LEFT:\r
+ handled = arrowScroll(FOCUS_LEFT);\r
+ break;\r
+ case KeyEvent.KEYCODE_DPAD_RIGHT:\r
+ handled = arrowScroll(FOCUS_RIGHT);\r
+ break;\r
+ case KeyEvent.KEYCODE_TAB:\r
+ if (Build.VERSION.SDK_INT >= 11) {\r
+ // The focus finder had a bug handling FOCUS_FORWARD and FOCUS_BACKWARD\r
+ // before Android 3.0. Ignore the tab key on those devices.\r
+ if (KeyEvent.metaStateHasNoModifiers(event.getMetaState())) {\r
+ handled = arrowScroll(FOCUS_FORWARD);\r
+ } else if (KeyEvent.metaStateHasNoModifiers(event.getMetaState())) {\r
+ handled = arrowScroll(FOCUS_BACKWARD);\r
+ }\r
+ }\r
+ break;\r
+ }\r
+ }\r
+ return handled;\r
+ }\r
+\r
+ public boolean arrowScroll(int direction) {\r
+ View currentFocused = findFocus();\r
+ if (currentFocused == this) {\r
+ currentFocused = null;\r
+ } else if (currentFocused != null) {\r
+ boolean isChild = false;\r
+ for (ViewParent parent\r
+ = currentFocused.getParent();\r
+ parent instanceof ViewGroup;\r
+ parent = parent.getParent()) {\r
+ if (parent == this) {\r
+ isChild = true;\r
+ break;\r
+ }\r
+ }\r
+ if (!isChild) {\r
+ // This would cause the focus search down below to fail in fun ways.\r
+ final StringBuilder sb = new StringBuilder();\r
+ sb.append(currentFocused.getClass().getSimpleName());\r
+ for (ViewParent parent\r
+ = currentFocused.getParent();\r
+ parent instanceof ViewGroup;\r
+ parent = parent.getParent()) {\r
+ sb.append(" => ").append(parent.getClass().getSimpleName());\r
+ }\r
+ Log.e(TAG, "arrowScroll tried to find focus based on non-child " +\r
+ "current focused view " + sb.toString());\r
+ currentFocused = null;\r
+ }\r
+ }\r
+\r
+ boolean handled = false;\r
+\r
+ View nextFocused\r
+ = FocusFinder.getInstance().findNextFocus(this, currentFocused,\r
+ direction);\r
+ if (nextFocused != null && nextFocused != currentFocused) {\r
+ if (isHorizontal()) {\r
+ if (direction == View.FOCUS_LEFT) {\r
+ // If there is nothing\r
+ // to the left, or this is causing us to\r
+ // jump to the right,\r
+ // then what we really want to do is page left.\r
+ final int nextLeft\r
+ = getChildRectInPagerCoordinates(mTempRect, nextFocused).left;\r
+ final int currLeft\r
+ = getChildRectInPagerCoordinates(mTempRect, currentFocused).left;\r
+ if (currentFocused != null && nextLeft >= currLeft) {\r
+ handled = pageLeft();\r
+ } else {\r
+ handled = nextFocused.requestFocus();\r
+ }\r
+ } else if (direction == View.FOCUS_RIGHT) {\r
+ // If there is nothing to the right,\r
+ // or this is causing us to\r
+ // jump to the left,\r
+ // then what we really\r
+ // want to do is page right.\r
+ final int nextLeft\r
+ = getChildRectInPagerCoordinates(mTempRect, nextFocused).left;\r
+ final int currLeft\r
+ = getChildRectInPagerCoordinates(mTempRect, currentFocused).left;\r
+ if (currentFocused != null && nextLeft <= currLeft) {\r
+ handled = pageRight();\r
+ } else {\r
+ handled = nextFocused.requestFocus();\r
+ }\r
+ } else if (direction == FOCUS_LEFT || direction == FOCUS_BACKWARD) {\r
+ // Trying to move left and nothing there; try to page.\r
+ handled = pageLeft();\r
+ } else if (direction == FOCUS_RIGHT || direction == FOCUS_FORWARD) {\r
+ // Trying to move right and nothing there; try to page.\r
+ handled = pageRight();\r
+ }\r
+ } else {\r
+ if (direction == View.FOCUS_UP) {\r
+ // If there is nothing to the left,\r
+ // or this is causing us to\r
+ // jump to the right,\r
+ // then what we really want to do is page left.\r
+ final int nextTop\r
+ = getChildRectInPagerCoordinates(mTempRect, nextFocused).top;\r
+ final int currTop\r
+ = getChildRectInPagerCoordinates(mTempRect, currentFocused).top;\r
+ if (currentFocused != null && nextTop >= currTop) {\r
+ handled = pageUp();\r
+ } else {\r
+ handled = nextFocused.requestFocus();\r
+ }\r
+ } else if (direction == View.FOCUS_DOWN) {\r
+ final int nextDown =\r
+ getChildRectInPagerCoordinates(mTempRect, nextFocused).bottom;\r
+ final int currDown =\r
+ getChildRectInPagerCoordinates(mTempRect, currentFocused).bottom;\r
+ if (currentFocused != null && nextDown <= currDown) {\r
+ handled = pageDown();\r
+ } else {\r
+ handled = nextFocused.requestFocus();\r
+ }\r
+ } else if (direction == FOCUS_UP || direction == FOCUS_BACKWARD) {\r
+ // Trying to move left and nothing there; try to page.\r
+ handled = pageUp();\r
+ } else if (direction == FOCUS_DOWN || direction == FOCUS_FORWARD) {\r
+ // Trying to move right and nothing there; try to page.\r
+ handled = pageDown();\r
+\r
+ }\r
+ }\r
+ if (handled) {\r
+ playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));\r
+ }\r
+ return handled;\r
+ }\r
+ return handled;\r
+ }\r
+\r
+\r
+ private Rect getChildRectInPagerCoordinates(Rect outRect, View child) {\r
+ if (outRect == null) {\r
+ outRect = new Rect();\r
+ }\r
+ if (child == null) {\r
+ outRect.set(0, 0, 0, 0);\r
+ return outRect;\r
+ }\r
+ outRect.left = child.getLeft();\r
+ outRect.right = child.getRight();\r
+ outRect.top = child.getTop();\r
+ outRect.bottom = child.getBottom();\r
+\r
+ ViewParent parent = child.getParent();\r
+ while (parent instanceof ViewGroup && parent != this) {\r
+ final ViewGroup group = (ViewGroup) parent;\r
+ outRect.left += group.getLeft();\r
+ outRect.right += group.getRight();\r
+ outRect.top += group.getTop();\r
+ outRect.bottom += group.getBottom();\r
+\r
+ parent = group.getParent();\r
+ }\r
+ return outRect;\r
+ }\r
+\r
+ boolean pageLeft() {\r
+ if (mCurItem > 0) {\r
+ setCurrentItem(mCurItem - 1, true);\r
+ return true;\r
+ }\r
+ return false;\r
+ }\r
+\r
+ boolean pageRight() {\r
+ if (mAdapter != null && mCurItem < (mAdapter.getCount() - 1)) {\r
+ setCurrentItem(mCurItem + 1, true);\r
+ return true;\r
+ }\r
+ return false;\r
+ }\r
+\r
+ boolean pageUp() {\r
+ if (mCurItem > 0) {\r
+ setCurrentItem(mCurItem - 1, true);\r
+ return true;\r
+ }\r
+ return false;\r
+ }\r
+\r
+ boolean pageDown() {\r
+ if (mAdapter != null && mCurItem < (mAdapter.getCount() - 1)) {\r
+ setCurrentItem(mCurItem + 1, true);\r
+ return true;\r
+ }\r
+ return false;\r
+ }\r
+\r
+ /**\r
+ * We only want the current page that is being shown to be focusable.\r
+ */\r
+ @Override\r
+ public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {\r
+ final int focusableCount = views.size();\r
+\r
+ final int descendantFocusability = getDescendantFocusability();\r
+\r
+ if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {\r
+ for (int i = 0; i < getChildCount(); i++) {\r
+ final View child = getChildAt(i);\r
+ if (child.getVisibility() == VISIBLE) {\r
+ ItemInfo ii = infoForChild(child);\r
+ if (ii != null && ii.position == mCurItem) {\r
+ child.addFocusables(views, direction, focusableMode);\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ // we add ourselves (if focusable) in all cases except for when we are\r
+ // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable. this is\r
+ // to avoid the focus search finding layouts when a more precise search\r
+ // among the focusable children would be more interesting.\r
+ if (\r
+ descendantFocusability != FOCUS_AFTER_DESCENDANTS ||\r
+ // No focusable descendants\r
+ (focusableCount == views.size())) {\r
+ // Note that we can't call the superclass here, because it will\r
+ // add all views in. So we need to do the same thing View does.\r
+ if (!isFocusable()) {\r
+ return;\r
+ }\r
+ if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE &&\r
+ isInTouchMode() && !isFocusableInTouchMode()) {\r
+ return;\r
+ }\r
+ if (views != null) {\r
+ views.add(this);\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * We only want the current page that is being shown to be touchable.\r
+ */\r
+ @Override\r
+ public void addTouchables(ArrayList<View> views) {\r
+ // Note that we don't call super.addTouchables(), which means that\r
+ // we don't call View.addTouchables(). This is okay because a ViewPager\r
+ // is itself not touchable.\r
+ for (int i = 0; i < getChildCount(); i++) {\r
+ final View child = getChildAt(i);\r
+ if (child.getVisibility() == VISIBLE) {\r
+ ItemInfo ii = infoForChild(child);\r
+ if (ii != null && ii.position == mCurItem) {\r
+ child.addTouchables(views);\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * We only want the current page that is being shown to be focusable.\r
+ */\r
+ @Override\r
+ protected boolean onRequestFocusInDescendants(int direction,\r
+ Rect previouslyFocusedRect) {\r
+ int index;\r
+ int increment;\r
+ int end;\r
+ int count = getChildCount();\r
+ if ((direction & FOCUS_FORWARD) != 0) {\r
+ index = 0;\r
+ increment = 1;\r
+ end = count;\r
+ } else {\r
+ index = count - 1;\r
+ increment = -1;\r
+ end = -1;\r
+ }\r
+ for (int i = index; i != end; i += increment) {\r
+ View child = getChildAt(i);\r
+ if (child.getVisibility() == VISIBLE) {\r
+ ItemInfo ii = infoForChild(child);\r
+ if (ii != null && ii.position ==\r
+ mCurItem && child.requestFocus(direction, previouslyFocusedRect)) {\r
+ return true;\r
+ }\r
+ }\r
+ }\r
+ return false;\r
+ }\r
+\r
+ @Override\r
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {\r
+ // Dispatch scroll events from this ViewPager.\r
+ if (event.getEventType() == AccessibilityEventCompat.TYPE_VIEW_SCROLLED) {\r
+ return super.dispatchPopulateAccessibilityEvent(event);\r
+ }\r
+\r
+ // Dispatch all other accessibility events from the current page.\r
+ final int childCount = getChildCount();\r
+ for (int i = 0; i < childCount; i++) {\r
+ final View child = getChildAt(i);\r
+ if (child.getVisibility() == VISIBLE) {\r
+ final ItemInfo ii = infoForChild(child);\r
+ if (ii != null && ii.position == mCurItem &&\r
+ child.dispatchPopulateAccessibilityEvent(event)) {\r
+ return true;\r
+ }\r
+ }\r
+ }\r
+\r
+ return false;\r
+ }\r
+\r
+ @Override\r
+ protected ViewGroup.LayoutParams generateDefaultLayoutParams() {\r
+ return new LayoutParams();\r
+ }\r
+\r
+ @Override\r
+ protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {\r
+ return generateDefaultLayoutParams();\r
+ }\r
+\r
+ @Override\r
+ protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {\r
+ return p instanceof LayoutParams && super.checkLayoutParams(p);\r
+ }\r
+\r
+ @Override\r
+ public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {\r
+ return new LayoutParams(getContext(), attrs);\r
+ }\r
+\r
+ class MyAccessibilityDelegate extends AccessibilityDelegateCompat {\r
+\r
+ @Override\r
+ public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {\r
+ super.onInitializeAccessibilityEvent(host, event);\r
+ event.setClassName(DirectionalViewpager.class.getName());\r
+ AccessibilityRecordCompat recordCompat = null;\r
+ if (isHorizontal()) {\r
+ recordCompat =\r
+ AccessibilityEventCompat.asRecord(event);\r
+ } else {\r
+ recordCompat = AccessibilityRecordCompat.obtain();\r
+ }\r
+ recordCompat.setScrollable(canScroll());\r
+ if (event.getEventType() == AccessibilityEventCompat.TYPE_VIEW_SCROLLED\r
+ && mAdapter != null) {\r
+ recordCompat.setItemCount(mAdapter.getCount());\r
+ recordCompat.setFromIndex(mCurItem);\r
+ recordCompat.setToIndex(mCurItem);\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {\r
+ super.onInitializeAccessibilityNodeInfo(host, info);\r
+ info.setClassName(DirectionalViewpager.class.getName());\r
+ info.setScrollable(canScroll());\r
+ if (isHorizontal()) {\r
+ if (canScrollHorizontally(1)) {\r
+ info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);\r
+ }\r
+ if (canScrollHorizontally(-1)) {\r
+ info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);\r
+ }\r
+ } else {\r
+ if (internalCanScrollVertically(1)) {\r
+ info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);\r
+ }\r
+ if (internalCanScrollVertically(-1)) {\r
+ info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);\r
+ }\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public boolean performAccessibilityAction(View host, int action, Bundle args) {\r
+ if (super.performAccessibilityAction(host, action, args)) {\r
+ return true;\r
+ }\r
+\r
+ if (isHorizontal()) {\r
+ switch (action) {\r
+ case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD: {\r
+ if (canScrollHorizontally(1)) {\r
+ setCurrentItem(mCurItem + 1);\r
+ return true;\r
+ }\r
+ }\r
+ return false;\r
+ case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD: {\r
+ if (canScrollHorizontally(-1)) {\r
+ setCurrentItem(mCurItem - 1);\r
+ return true;\r
+ }\r
+ }\r
+ return false;\r
+ }\r
+ } else {\r
+ switch (action) {\r
+ case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD: {\r
+ if (internalCanScrollVertically(1)) {\r
+ setCurrentItem(mCurItem + 1);\r
+ return true;\r
+ }\r
+ }\r
+ return false;\r
+ case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD: {\r
+ if (internalCanScrollVertically(-1)) {\r
+ setCurrentItem(mCurItem - 1);\r
+ return true;\r
+ }\r
+ }\r
+ return false;\r
+ }\r
+ }\r
+ return false;\r
+ }\r
+\r
+ private boolean canScroll() {\r
+ return (mAdapter != null) && (mAdapter.getCount() > 1);\r
+ }\r
+ }\r
+\r
+ private class PagerObserver extends DataSetObserver {\r
+ @Override\r
+ public void onChanged() {\r
+ dataSetChanged();\r
+ }\r
+\r
+ @Override\r
+ public void onInvalidated() {\r
+ dataSetChanged();\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Layout parameters that should be supplied for views added to a\r
+ * ViewPager.\r
+ */\r
+ public static class LayoutParams extends ViewGroup.LayoutParams {\r
+ /**\r
+ * true if this view is a decoration on the pager itself and not\r
+ * a view supplied by the adapter.\r
+ */\r
+ public boolean isDecor;\r
+\r
+ /**\r
+ * Gravity setting for use on decor views only:\r
+ * Where to position the view page within the overall ViewPager\r
+ * container; constants are defined in {@link android.view.Gravity}.\r
+ */\r
+ public int gravity;\r
+\r
+ /**\r
+ * Width as a 0-1 multiplier of the measured pager width\r
+ */\r
+ float widthFactor = 0.f;\r
+\r
+ float heightFactor = 0.f;\r
+\r
+ /**\r
+ * true if this view was added during layout and needs to be measured\r
+ * before being positioned.\r
+ */\r
+ boolean needsMeasure;\r
+\r
+ /**\r
+ * Adapter position this view is for if !isDecor\r
+ */\r
+ int position;\r
+\r
+ /**\r
+ * Current child index within the ViewPager that this view occupies\r
+ */\r
+ int childIndex;\r
+\r
+ public LayoutParams() {\r
+ super(FILL_PARENT, FILL_PARENT);\r
+ }\r
+\r
+ public LayoutParams(Context context, AttributeSet attrs) {\r
+ super(context, attrs);\r
+\r
+ final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS);\r
+ gravity = a.getInteger(0, Gravity.TOP);\r
+ a.recycle();\r
+ }\r
+ }\r
+\r
+ static class ViewPositionComparator implements Comparator<View> {\r
+ @Override\r
+ public int compare(View lhs, View rhs) {\r
+ final LayoutParams llp = (LayoutParams) lhs.getLayoutParams();\r
+ final LayoutParams rlp = (LayoutParams) rhs.getLayoutParams();\r
+ if (llp.isDecor != rlp.isDecor) {\r
+ return llp.isDecor ? 1 : -1;\r
+ }\r
+ return llp.position - rlp.position;\r
+ }\r
+ }\r
+\r
+ public void setDirection(Direction direction) {\r
+ mDirection = direction.name();\r
+ initViewPager();\r
+ }\r
+\r
+ private String logDestroyItem(int pos, View object) {\r
+ return "populate() - destroyItem() with pos: " + pos + " view: " + object;\r
+ }\r
+}\r