package com.yuyh.sprintnba.widget;

import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
import android.support.v4.os.ParcelableCompat;
import android.support.v4.os.ParcelableCompatCreatorCallbacks;
import android.support.v4.view.AccessibilityDelegateCompat;
import android.support.v4.view.KeyEventCompat;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.VelocityTrackerCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewConfigurationCompat;
import android.support.v4.view.ViewPager;
import android.support.v4.view.accessibility.AccessibilityEventCompat;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.support.v4.view.accessibility.AccessibilityRecordCompat;
import android.support.v4.widget.EdgeEffectCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.FocusFinder;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SoundEffectConstants;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.Interpolator;
import android.widget.Scroller;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

/**
 * Created by sll on 2016/3/15.
 * 垂直方向的viewpager
 */
public class VerticalViewPager extends ViewGroup {

  private static final String TAG = "ViewPager";
  private static final boolean DEBUG = false;

  private static final boolean USE_CACHE = false;

  private static final int DEFAULT_OFFSCREEN_PAGES = 1;
  private static final int MAX_SETTLE_DURATION = 600; // ms
  private static final int MIN_DISTANCE_FOR_FLING = 25; // dips

  private static final int DEFAULT_GUTTER_SIZE = 16; // dips

  private static final int MIN_FLING_VELOCITY = 400; // dips

  private static final int[] LAYOUT_ATTRS = new int[] {
      android.R.attr.layout_gravity
  };

  /**
   * Used to track what the expected number of items in the adapter should be.
   * If the app changes this when we don't expect it, we'll throw a big obnoxious exception.
   */
  private int mExpectedAdapterCount;

  static class ItemInfo {
    Object object;
    int position;
    boolean scrolling;
    float heightFactor;
    float offset;
  }

  private static final Comparator<ItemInfo> COMPARATOR = new Comparator<ItemInfo>() {
    @Override public int compare(ItemInfo lhs, ItemInfo rhs) {
      return lhs.position - rhs.position;
    }
  };

  private static final Interpolator sInterpolator = new Interpolator() {
    public float getInterpolation(float t) {
      t -= 1.0f;
      return t * t * t * t * t + 1.0f;
    }
  };

  private final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>();
  private final ItemInfo mTempItem = new ItemInfo();

  private final Rect mTempRect = new Rect();

  private PagerAdapter mAdapter;
  private int mCurItem;   // Index of currently displayed page.
  private int mRestoredCurItem = -1;
  private Parcelable mRestoredAdapterState = null;
  private ClassLoader mRestoredClassLoader = null;
  private Scroller mScroller;
  private PagerObserver mObserver;

  private int mPageMargin;
  private Drawable mMarginDrawable;
  private int mLeftPageBounds;
  private int mRightPageBounds;

  // Offsets of the first and last items, if known.
  // Set during population, used to determine if we are at the beginning
  // or end of the pager data set during touch scrolling.
  private float mFirstOffset = -Float.MAX_VALUE;
  private float mLastOffset = Float.MAX_VALUE;

  private int mChildWidthMeasureSpec;
  private int mChildHeightMeasureSpec;
  private boolean mInLayout;

  private boolean mScrollingCacheEnabled;

  private boolean mPopulatePending;
  private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES;

  private boolean mIsBeingDragged;
  private boolean mIsUnableToDrag;
  private boolean mIgnoreGutter;
  private int mDefaultGutterSize;
  private int mGutterSize;
  private int mTouchSlop;
  /**
   * Position of the last motion event.
   */
  private float mLastMotionX;
  private float mLastMotionY;
  private float mInitialMotionX;
  private float mInitialMotionY;
  /**
   * ID of the active pointer. This is used to retain consistency during
   * drags/flings if multiple pointers are used.
   */
  private int mActivePointerId = INVALID_POINTER;
  /**
   * Sentinel value for no current active pointer.
   * Used by {@link #mActivePointerId}.
   */
  private static final int INVALID_POINTER = -1;

  /**
   * Determines speed during touch scrolling
   */
  private VelocityTracker mVelocityTracker;
  private int mMinimumVelocity;
  private int mMaximumVelocity;
  private int mFlingDistance;
  private int mCloseEnough;

  // If the pager is at least this close to its final position, complete the scroll
  // on touch down and let the user interact with the content inside instead of
  // "catching" the flinging pager.
  private static final int CLOSE_ENOUGH = 2; // dp

  private boolean mFakeDragging;
  private long mFakeDragBeginTime;

  private EdgeEffectCompat mTopEdge;
  private EdgeEffectCompat mBottomEdge;

  private boolean mFirstLayout = true;
  private boolean mNeedCalculatePageOffsets = false;
  private boolean mCalledSuper;
  private int mDecorChildCount;

  private ViewPager.OnPageChangeListener mOnPageChangeListener;
  private ViewPager.OnPageChangeListener mInternalPageChangeListener;
  private OnAdapterChangeListener mAdapterChangeListener;
  private ViewPager.PageTransformer mPageTransformer;
  private Method mSetChildrenDrawingOrderEnabled;

  private static final int DRAW_ORDER_DEFAULT = 0;
  private static final int DRAW_ORDER_FORWARD = 1;
  private static final int DRAW_ORDER_REVERSE = 2;
  private int mDrawingOrder;
  private ArrayList<View> mDrawingOrderedChildren;
  private static final ViewPositionComparator sPositionComparator = new ViewPositionComparator();

  /**
   * Indicates that the pager is in an idle, settled state. The current page
   * is fully in view and no animation is in progress.
   */
  public static final int SCROLL_STATE_IDLE = 0;

  /**
   * Indicates that the pager is currently being dragged by the user.
   */
  public static final int SCROLL_STATE_DRAGGING = 1;

  /**
   * Indicates that the pager is in the process of settling to a final position.
   */
  public static final int SCROLL_STATE_SETTLING = 2;

  private final Runnable mEndScrollRunnable = new Runnable() {
    public void run() {
      setScrollState(SCROLL_STATE_IDLE);
      populate();
    }
  };

  private int mScrollState = SCROLL_STATE_IDLE;

  /**
   * Used internally to monitor when adapters are switched.
   */
  interface OnAdapterChangeListener {
    public void onAdapterChanged(PagerAdapter oldAdapter, PagerAdapter newAdapter);
  }

  /**
   * Used internally to tag special types of child views that should be added as
   * pager decorations by default.
   */
  interface Decor {
  }

  public VerticalViewPager(Context context) {
    super(context);
    initViewPager();
  }

  public VerticalViewPager(Context context, AttributeSet attrs) {
    super(context, attrs);
    initViewPager();
  }

  void initViewPager() {
    setWillNotDraw(false);
    setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
    setFocusable(true);
    final Context context = getContext();
    mScroller = new Scroller(context, sInterpolator);
    final ViewConfiguration configuration = ViewConfiguration.get(context);
    final float density = context.getResources().getDisplayMetrics().density;

    mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
    mMinimumVelocity = (int) (MIN_FLING_VELOCITY * density);
    mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
    mTopEdge = new EdgeEffectCompat(context);
    mBottomEdge = new EdgeEffectCompat(context);

    mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density);
    mCloseEnough = (int) (CLOSE_ENOUGH * density);
    mDefaultGutterSize = (int) (DEFAULT_GUTTER_SIZE * density);

    ViewCompat.setAccessibilityDelegate(this, new MyAccessibilityDelegate());

    if (ViewCompat.getImportantForAccessibility(this)
        == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
      ViewCompat.setImportantForAccessibility(this, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
    }
  }

  @Override protected void onDetachedFromWindow() {
    removeCallbacks(mEndScrollRunnable);
    super.onDetachedFromWindow();
  }

  private void setScrollState(int newState) {
    if (mScrollState == newState) {
      return;
    }

    mScrollState = newState;
    if (mPageTransformer != null) {
      // PageTransformers can do complex things that benefit from hardware layers.
      enableLayers(newState != SCROLL_STATE_IDLE);
    }
    if (mOnPageChangeListener != null) {
      mOnPageChangeListener.onPageScrollStateChanged(newState);
    }
  }

  /**
   * Set a PagerAdapter that will supply views for this pager as needed.
   *
   * @param adapter Adapter to use
   */
  public void setAdapter(PagerAdapter adapter) {
    if (mAdapter != null) {
      mAdapter.unregisterDataSetObserver(mObserver);
      mAdapter.startUpdate(this);
      for (int i = 0; i < mItems.size(); i++) {
        final ItemInfo ii = mItems.get(i);
        mAdapter.destroyItem(this, ii.position, ii.object);
      }
      mAdapter.finishUpdate(this);
      mItems.clear();
      removeNonDecorViews();
      mCurItem = 0;
      scrollTo(0, 0);
    }

    final PagerAdapter oldAdapter = mAdapter;
    mAdapter = adapter;
    mExpectedAdapterCount = 0;

    if (mAdapter != null) {
      if (mObserver == null) {
        mObserver = new PagerObserver();
      }
      mAdapter.registerDataSetObserver(mObserver);
      mPopulatePending = false;
      final boolean wasFirstLayout = mFirstLayout;
      mFirstLayout = true;
      mExpectedAdapterCount = mAdapter.getCount();
      if (mRestoredCurItem >= 0) {
        mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader);
        setCurrentItemInternal(mRestoredCurItem, false, true);
        mRestoredCurItem = -1;
        mRestoredAdapterState = null;
        mRestoredClassLoader = null;
      } else if (!wasFirstLayout) {
        populate();
      } else {
        requestLayout();
      }
    }

    if (mAdapterChangeListener != null && oldAdapter != adapter) {
      mAdapterChangeListener.onAdapterChanged(oldAdapter, adapter);
    }
  }

  private void removeNonDecorViews() {
    for (int i = 0; i < getChildCount(); i++) {
      final View child = getChildAt(i);
      final LayoutParams lp = (LayoutParams) child.getLayoutParams();
      if (!lp.isDecor) {
        removeViewAt(i);
        i--;
      }
    }
  }

  /**
   * Retrieve the current adapter supplying pages.
   *
   * @return The currently registered PagerAdapter
   */
  public PagerAdapter getAdapter() {
    return mAdapter;
  }

  void setOnAdapterChangeListener(OnAdapterChangeListener listener) {
    mAdapterChangeListener = listener;
  }

  //    private int getClientWidth() {
  //        return getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
  //    }

  private int getClientHeight() {
    return getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
  }

  /**
   * Set the currently selected page. If the ViewPager has already been through its first
   * layout with its current adapter there will be a smooth animated transition between
   * the current item and the specified item.
   *
   * @param item Item index to select
   */
  public void setCurrentItem(int item) {
    mPopulatePending = false;
    setCurrentItemInternal(item, !mFirstLayout, false);
  }

  /**
   * Set the currently selected page.
   *
   * @param item Item index to select
   * @param smoothScroll True to smoothly scroll to the new item, false to transition immediately
   */
  public void setCurrentItem(int item, boolean smoothScroll) {
    mPopulatePending = false;
    setCurrentItemInternal(item, smoothScroll, false);
  }

  public int getCurrentItem() {
    return mCurItem;
  }

  void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) {
    setCurrentItemInternal(item, smoothScroll, always, 0);
  }

  void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) {
    if (mAdapter == null || mAdapter.getCount() <= 0) {
      setScrollingCacheEnabled(false);
      return;
    }
    if (!always && mCurItem == item && mItems.size() != 0) {
      setScrollingCacheEnabled(false);
      return;
    }

    if (item < 0) {
      item = 0;
    } else if (item >= mAdapter.getCount()) {
      item = mAdapter.getCount() - 1;
    }
    final int pageLimit = mOffscreenPageLimit;
    if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) {
      // We are doing a jump by more than one page.  To avoid
      // glitches, we want to keep all current pages in the view
      // until the scroll ends.
      for (int i = 0; i < mItems.size(); i++) {
        mItems.get(i).scrolling = true;
      }
    }
    final boolean dispatchSelected = mCurItem != item;

    if (mFirstLayout) {
      // We don't have any idea how big we are yet and shouldn't have any pages either.
      // Just set things up and let the pending layout handle things.
      mCurItem = item;
      if (dispatchSelected && mOnPageChangeListener != null) {
        mOnPageChangeListener.onPageSelected(item);
      }
      if (dispatchSelected && mInternalPageChangeListener != null) {
        mInternalPageChangeListener.onPageSelected(item);
      }
      requestLayout();
    } else {
      populate(item);
      scrollToItem(item, smoothScroll, velocity, dispatchSelected);
    }
  }

  private void scrollToItem(int item, boolean smoothScroll, int velocity,
      boolean dispatchSelected) {
    final ItemInfo curInfo = infoForPosition(item);
    int destY = 0;
    if (curInfo != null) {
      final int height = getClientHeight();
      destY = (int) (height * Math.max(mFirstOffset, Math.min(curInfo.offset, mLastOffset)));
    }
    if (smoothScroll) {
      smoothScrollTo(0, destY, velocity);
      if (dispatchSelected && mOnPageChangeListener != null) {
        mOnPageChangeListener.onPageSelected(item);
      }
      if (dispatchSelected && mInternalPageChangeListener != null) {
        mInternalPageChangeListener.onPageSelected(item);
      }
    } else {
      if (dispatchSelected && mOnPageChangeListener != null) {
        mOnPageChangeListener.onPageSelected(item);
      }
      if (dispatchSelected && mInternalPageChangeListener != null) {
        mInternalPageChangeListener.onPageSelected(item);
      }
      completeScroll(false);
      scrollTo(0, destY);
      pageScrolled(destY);
    }
  }

  /**
   * Set a listener that will be invoked whenever the page changes or is incrementally
   * scrolled. See {@link android.support.v4.view.ViewPager.OnPageChangeListener}.
   *
   * @param listener Listener to set
   */
  public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) {
    mOnPageChangeListener = listener;
  }

  /**
   * Set a {@link android.support.v4.view.ViewPager.PageTransformer} that will be called for each
   * attached page whenever
   * the scroll position is changed. This allows the application to apply custom property
   * transformations to each page, overriding the default sliding look and feel.
   * <p/>
   * <p><em>Note:</em> Prior to Android 3.0 the property animation APIs did not exist.
   * As a result, setting a PageTransformer prior to Android 3.0 (API 11) will have no effect.</p>
   *
   * @param reverseDrawingOrder true if the supplied PageTransformer requires page views
   * to be drawn from last to first instead of first to last.
   * @param transformer PageTransformer that will modify each page's animation properties
   */
  public void setPageTransformer(boolean reverseDrawingOrder,
      ViewPager.PageTransformer transformer) {
    if (Build.VERSION.SDK_INT >= 11) {
      final boolean hasTransformer = transformer != null;
      final boolean needsPopulate = hasTransformer != (mPageTransformer != null);
      mPageTransformer = transformer;
      setChildrenDrawingOrderEnabledCompat(hasTransformer);
      if (hasTransformer) {
        mDrawingOrder = reverseDrawingOrder ? DRAW_ORDER_REVERSE : DRAW_ORDER_FORWARD;
      } else {
        mDrawingOrder = DRAW_ORDER_DEFAULT;
      }
      if (needsPopulate) populate();
    }
  }

  void setChildrenDrawingOrderEnabledCompat(boolean enable) {
    if (Build.VERSION.SDK_INT >= 7) {
      if (mSetChildrenDrawingOrderEnabled == null) {
        try {
          mSetChildrenDrawingOrderEnabled =
              ViewGroup.class.getDeclaredMethod("setChildrenDrawingOrderEnabled",
                  new Class[] { Boolean.TYPE });
        } catch (NoSuchMethodException e) {
          Log.e(TAG, "Can't find setChildrenDrawingOrderEnabled", e);
        }
      }
      try {
        mSetChildrenDrawingOrderEnabled.invoke(this, enable);
      } catch (Exception e) {
        Log.e(TAG, "Error changing children drawing order", e);
      }
    }
  }

  @Override protected int getChildDrawingOrder(int childCount, int i) {
    final int index = mDrawingOrder == DRAW_ORDER_REVERSE ? childCount - 1 - i : i;
    final int result =
        ((LayoutParams) mDrawingOrderedChildren.get(index).getLayoutParams()).childIndex;
    return result;
  }

  /**
   * Set a separate OnPageChangeListener for internal use by the support library.
   *
   * @param listener Listener to set
   * @return The old listener that was set, if any.
   */
  ViewPager.OnPageChangeListener setInternalPageChangeListener(
      ViewPager.OnPageChangeListener listener) {
    ViewPager.OnPageChangeListener oldListener = mInternalPageChangeListener;
    mInternalPageChangeListener = listener;
    return oldListener;
  }

  /**
   * Returns the number of pages that will be retained to either side of the
   * current page in the view hierarchy in an idle state. Defaults to 1.
   *
   * @return How many pages will be kept offscreen on either side
   * @see #setOffscreenPageLimit(int)
   */
  public int getOffscreenPageLimit() {
    return mOffscreenPageLimit;
  }

  /**
   * Set the number of pages that should be retained to either side of the
   * current page in the view hierarchy in an idle state. Pages beyond this
   * limit will be recreated from the adapter when needed.
   * <p/>
   * <p>This is offered as an optimization. If you know in advance the number
   * of pages you will need to support or have lazy-loading mechanisms in place
   * on your pages, tweaking this setting can have benefits in perceived smoothness
   * of paging animations and interaction. If you have a small number of pages (3-4)
   * that you can keep active all at once, less time will be spent in layout for
   * newly created view subtrees as the user pages back and forth.</p>
   * <p/>
   * <p>You should keep this limit low, especially if your pages have complex layouts.
   * This setting defaults to 1.</p>
   *
   * @param limit How many pages will be kept offscreen in an idle state.
   */
  public void setOffscreenPageLimit(int limit) {
    if (limit < DEFAULT_OFFSCREEN_PAGES) {
      Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " +
          DEFAULT_OFFSCREEN_PAGES);
      limit = DEFAULT_OFFSCREEN_PAGES;
    }
    if (limit != mOffscreenPageLimit) {
      mOffscreenPageLimit = limit;
      populate();
    }
  }

  /**
   * Set the margin between pages.
   *
   * @param marginPixels Distance between adjacent pages in pixels
   * @see #getPageMargin()
   * @see #setPageMarginDrawable(Drawable)
   * @see #setPageMarginDrawable(int)
   */
  public void setPageMargin(int marginPixels) {
    final int oldMargin = mPageMargin;
    mPageMargin = marginPixels;

    final int height = getHeight();
    recomputeScrollPosition(height, height, marginPixels, oldMargin);

    requestLayout();
  }

  /**
   * Return the margin between pages.
   *
   * @return The size of the margin in pixels
   */
  public int getPageMargin() {
    return mPageMargin;
  }

  /**
   * Set a drawable that will be used to fill the margin between pages.
   *
   * @param d Drawable to display between pages
   */
  public void setPageMarginDrawable(Drawable d) {
    mMarginDrawable = d;
    if (d != null) refreshDrawableState();
    setWillNotDraw(d == null);
    invalidate();
  }

  /**
   * Set a drawable that will be used to fill the margin between pages.
   *
   * @param resId Resource ID of a drawable to display between pages
   */
  public void setPageMarginDrawable(int resId) {
    setPageMarginDrawable(getContext().getResources().getDrawable(resId));
  }

  @Override protected boolean verifyDrawable(Drawable who) {
    return super.verifyDrawable(who) || who == mMarginDrawable;
  }

  @Override protected void drawableStateChanged() {
    super.drawableStateChanged();
    final Drawable d = mMarginDrawable;
    if (d != null && d.isStateful()) {
      d.setState(getDrawableState());
    }
  }

  // We want the duration of the page snap animation to be influenced by the distance that
  // the screen has to travel, however, we don't want this duration to be effected in a
  // purely linear fashion. Instead, we use this method to moderate the effect that the distance
  // of travel has on the overall snap duration.
  float distanceInfluenceForSnapDuration(float f) {
    f -= 0.5f; // center the values about 0.
    f *= 0.3f * Math.PI / 2.0f;
    return (float) Math.sin(f);
  }

  /**
   * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
   *
   * @param x the number of pixels to scroll by on the X axis
   * @param y the number of pixels to scroll by on the Y axis
   */
  void smoothScrollTo(int x, int y) {
    smoothScrollTo(x, y, 0);
  }

  /**
   * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
   *
   * @param x the number of pixels to scroll by on the X axis
   * @param y the number of pixels to scroll by on the Y axis
   * @param velocity the velocity associated with a fling, if applicable. (0 otherwise)
   */
  void smoothScrollTo(int x, int y, int velocity) {
    if (getChildCount() == 0) {
      // Nothing to do.
      setScrollingCacheEnabled(false);
      return;
    }
    int sx = getScrollX();
    int sy = getScrollY();
    int dx = x - sx;
    int dy = y - sy;
    if (dx == 0 && dy == 0) {
      completeScroll(false);
      populate();
      setScrollState(SCROLL_STATE_IDLE);
      return;
    }

    setScrollingCacheEnabled(true);
    setScrollState(SCROLL_STATE_SETTLING);

    final int height = getClientHeight();
    final int halfHeight = height / 2;
    final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / height);
    final float distance =
        halfHeight + halfHeight * distanceInfluenceForSnapDuration(distanceRatio);

    int duration = 0;
    velocity = Math.abs(velocity);
    if (velocity > 0) {
      duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
    } else {
      final float pageHeight = height * mAdapter.getPageWidth(mCurItem);
      final float pageDelta = (float) Math.abs(dx) / (pageHeight + mPageMargin);
      duration = (int) ((pageDelta + 1) * 100);
    }
    duration = Math.min(duration, MAX_SETTLE_DURATION);

    mScroller.startScroll(sx, sy, dx, dy, duration);
    ViewCompat.postInvalidateOnAnimation(this);
  }

  ItemInfo addNewItem(int position, int index) {
    ItemInfo ii = new ItemInfo();
    ii.position = position;
    ii.object = mAdapter.instantiateItem(this, position);
    ii.heightFactor = mAdapter.getPageWidth(position);
    if (index < 0 || index >= mItems.size()) {
      mItems.add(ii);
    } else {
      mItems.add(index, ii);
    }
    return ii;
  }

  void dataSetChanged() {
    // This method only gets called if our observer is attached, so mAdapter is non-null.

    final int adapterCount = mAdapter.getCount();
    mExpectedAdapterCount = adapterCount;
    boolean needPopulate =
        mItems.size() < mOffscreenPageLimit * 2 + 1 && mItems.size() < adapterCount;
    int newCurrItem = mCurItem;

    boolean isUpdating = false;
    for (int i = 0; i < mItems.size(); i++) {
      final ItemInfo ii = mItems.get(i);
      final int newPos = mAdapter.getItemPosition(ii.object);

      if (newPos == PagerAdapter.POSITION_UNCHANGED) {
        continue;
      }

      if (newPos == PagerAdapter.POSITION_NONE) {
        mItems.remove(i);
        i--;

        if (!isUpdating) {
          mAdapter.startUpdate(this);
          isUpdating = true;
        }

        mAdapter.destroyItem(this, ii.position, ii.object);
        needPopulate = true;

        if (mCurItem == ii.position) {
          // Keep the current item in the valid range
          newCurrItem = Math.max(0, Math.min(mCurItem, adapterCount - 1));
          needPopulate = true;
        }
        continue;
      }

      if (ii.position != newPos) {
        if (ii.position == mCurItem) {
          // Our current item changed position. Follow it.
          newCurrItem = newPos;
        }

        ii.position = newPos;
        needPopulate = true;
      }
    }

    if (isUpdating) {
      mAdapter.finishUpdate(this);
    }

    Collections.sort(mItems, COMPARATOR);

    if (needPopulate) {
      // Reset our known page widths; populate will recompute them.
      final int childCount = getChildCount();
      for (int i = 0; i < childCount; i++) {
        final View child = getChildAt(i);
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        if (!lp.isDecor) {
          lp.heightFactor = 0.f;
        }
      }

      setCurrentItemInternal(newCurrItem, false, true);
      requestLayout();
    }
  }

  void populate() {
    populate(mCurItem);
  }

  void populate(int newCurrentItem) {
    ItemInfo oldCurInfo = null;
    int focusDirection = View.FOCUS_FORWARD;
    if (mCurItem != newCurrentItem) {
      focusDirection = mCurItem < newCurrentItem ? View.FOCUS_DOWN : View.FOCUS_UP;
      oldCurInfo = infoForPosition(mCurItem);
      mCurItem = newCurrentItem;
    }

    if (mAdapter == null) {
      sortChildDrawingOrder();
      return;
    }

    // Bail now if we are waiting to populate.  This is to hold off
    // on creating views from the time the user releases their finger to
    // fling to a new position until we have finished the scroll to
    // that position, avoiding glitches from happening at that point.
    if (mPopulatePending) {
      if (DEBUG) Log.i(TAG, "populate is pending, skipping for now...");
      sortChildDrawingOrder();
      return;
    }

    // Also, don't populate until we are attached to a window.  This is to
    // avoid trying to populate before we have restored our view hierarchy
    // state and conflicting with what is restored.
    if (getWindowToken() == null) {
      return;
    }

    mAdapter.startUpdate(this);

    final int pageLimit = mOffscreenPageLimit;
    final int startPos = Math.max(0, mCurItem - pageLimit);
    final int N = mAdapter.getCount();
    final int endPos = Math.min(N - 1, mCurItem + pageLimit);

    if (N != mExpectedAdapterCount) {
      String resName;
      try {
        resName = getResources().getResourceName(getId());
      } catch (Resources.NotFoundException e) {
        resName = Integer.toHexString(getId());
      }
      throw new IllegalStateException("The application's PagerAdapter changed the adapter's" +
          " contents without calling PagerAdapter#notifyDataSetChanged!" +
          " Expected adapter item count: " + mExpectedAdapterCount + ", found: " + N +
          " Pager id: " + resName +
          " Pager class: " + getClass() +
          " Problematic adapter: " + mAdapter.getClass());
    }

    // Locate the currently focused item or add it if needed.
    int curIndex = -1;
    ItemInfo curItem = null;
    for (curIndex = 0; curIndex < mItems.size(); curIndex++) {
      final ItemInfo ii = mItems.get(curIndex);
      if (ii.position >= mCurItem) {
        if (ii.position == mCurItem) curItem = ii;
        break;
      }
    }

    if (curItem == null && N > 0) {
      curItem = addNewItem(mCurItem, curIndex);
    }

    // Fill 3x the available width or up to the number of offscreen
    // pages requested to either side, whichever is larger.
    // If we have no current item we have no work to do.
    if (curItem != null) {
      float extraHeightTop = 0.f;
      int itemIndex = curIndex - 1;
      ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
      final int clientHeight = getClientHeight();
      final float topHeightNeeded = clientHeight <= 0 ? 0
          : 2.f - curItem.heightFactor + (float) getPaddingLeft() / (float) clientHeight;
      for (int pos = mCurItem - 1; pos >= 0; pos--) {
        if (extraHeightTop >= topHeightNeeded && pos < startPos) {
          if (ii == null) {
            break;
          }
          if (pos == ii.position && !ii.scrolling) {
            mItems.remove(itemIndex);
            mAdapter.destroyItem(this, pos, ii.object);
            if (DEBUG) {
              Log.i(TAG, "populate() - destroyItem() with pos: " + pos +
                  " view: " + ((View) ii.object));
            }
            itemIndex--;
            curIndex--;
            ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
          }
        } else if (ii != null && pos == ii.position) {
          extraHeightTop += ii.heightFactor;
          itemIndex--;
          ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
        } else {
          ii = addNewItem(pos, itemIndex + 1);
          extraHeightTop += ii.heightFactor;
          curIndex++;
          ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
        }
      }

      float extraHeightBottom = curItem.heightFactor;
      itemIndex = curIndex + 1;
      if (extraHeightBottom < 2.f) {
        ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
        final float bottomHeightNeeded =
            clientHeight <= 0 ? 0 : (float) getPaddingRight() / (float) clientHeight + 2.f;
        for (int pos = mCurItem + 1; pos < N; pos++) {
          if (extraHeightBottom >= bottomHeightNeeded && pos > endPos) {
            if (ii == null) {
              break;
            }
            if (pos == ii.position && !ii.scrolling) {
              mItems.remove(itemIndex);
              mAdapter.destroyItem(this, pos, ii.object);
              if (DEBUG) {
                Log.i(TAG, "populate() - destroyItem() with pos: " + pos +
                    " view: " + ((View) ii.object));
              }
              ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
            }
          } else if (ii != null && pos == ii.position) {
            extraHeightBottom += ii.heightFactor;
            itemIndex++;
            ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
          } else {
            ii = addNewItem(pos, itemIndex);
            itemIndex++;
            extraHeightBottom += ii.heightFactor;
            ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
          }
        }
      }

      calculatePageOffsets(curItem, curIndex, oldCurInfo);
    }

    if (DEBUG) {
      Log.i(TAG, "Current page list:");
      for (int i = 0; i < mItems.size(); i++) {
        Log.i(TAG, "#" + i + ": page " + mItems.get(i).position);
      }
    }

    mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null);

    mAdapter.finishUpdate(this);

    // Check width measurement of current pages and drawing sort order.
    // Update LayoutParams as needed.
    final int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
      final View child = getChildAt(i);
      final LayoutParams lp = (LayoutParams) child.getLayoutParams();
      lp.childIndex = i;
      if (!lp.isDecor && lp.heightFactor == 0.f) {
        // 0 means requery the adapter for this, it doesn't have a valid width.
        final ItemInfo ii = infoForChild(child);
        if (ii != null) {
          lp.heightFactor = ii.heightFactor;
          lp.position = ii.position;
        }
      }
    }
    sortChildDrawingOrder();

    if (hasFocus()) {
      View currentFocused = findFocus();
      ItemInfo ii = currentFocused != null ? infoForAnyChild(currentFocused) : null;
      if (ii == null || ii.position != mCurItem) {
        for (int i = 0; i < getChildCount(); i++) {
          View child = getChildAt(i);
          ii = infoForChild(child);
          if (ii != null && ii.position == mCurItem) {
            if (child.requestFocus(focusDirection)) {
              break;
            }
          }
        }
      }
    }
  }

  private void sortChildDrawingOrder() {
    if (mDrawingOrder != DRAW_ORDER_DEFAULT) {
      if (mDrawingOrderedChildren == null) {
        mDrawingOrderedChildren = new ArrayList<View>();
      } else {
        mDrawingOrderedChildren.clear();
      }
      final int childCount = getChildCount();
      for (int i = 0; i < childCount; i++) {
        final View child = getChildAt(i);
        mDrawingOrderedChildren.add(child);
      }
      Collections.sort(mDrawingOrderedChildren, sPositionComparator);
    }
  }

  private void calculatePageOffsets(ItemInfo curItem, int curIndex, ItemInfo oldCurInfo) {
    final int N = mAdapter.getCount();
    final int height = getClientHeight();
    final float marginOffset = height > 0 ? (float) mPageMargin / height : 0;
    // Fix up offsets for later layout.
    if (oldCurInfo != null) {
      final int oldCurPosition = oldCurInfo.position;
      // Base offsets off of oldCurInfo.
      if (oldCurPosition < curItem.position) {
        int itemIndex = 0;
        ItemInfo ii = null;
        float offset = oldCurInfo.offset + oldCurInfo.heightFactor + marginOffset;
        for (int pos = oldCurPosition + 1; pos <= curItem.position && itemIndex < mItems.size();
            pos++) {
          ii = mItems.get(itemIndex);
          while (pos > ii.position && itemIndex < mItems.size() - 1) {
            itemIndex++;
            ii = mItems.get(itemIndex);
          }
          while (pos < ii.position) {
            // We don't have an item populated for this,
            // ask the adapter for an offset.
            offset += mAdapter.getPageWidth(pos) + marginOffset;
            pos++;
          }
          ii.offset = offset;
          offset += ii.heightFactor + marginOffset;
        }
      } else if (oldCurPosition > curItem.position) {
        int itemIndex = mItems.size() - 1;
        ItemInfo ii = null;
        float offset = oldCurInfo.offset;
        for (int pos = oldCurPosition - 1; pos >= curItem.position && itemIndex >= 0; pos--) {
          ii = mItems.get(itemIndex);
          while (pos < ii.position && itemIndex > 0) {
            itemIndex--;
            ii = mItems.get(itemIndex);
          }
          while (pos > ii.position) {
            // We don't have an item populated for this,
            // ask the adapter for an offset.
            offset -= mAdapter.getPageWidth(pos) + marginOffset;
            pos--;
          }
          offset -= ii.heightFactor + marginOffset;
          ii.offset = offset;
        }
      }
    }

    // Base all offsets off of curItem.
    final int itemCount = mItems.size();
    float offset = curItem.offset;
    int pos = curItem.position - 1;
    mFirstOffset = curItem.position == 0 ? curItem.offset : -Float.MAX_VALUE;
    mLastOffset =
        curItem.position == N - 1 ? curItem.offset + curItem.heightFactor - 1 : Float.MAX_VALUE;
    // Previous pages
    for (int i = curIndex - 1; i >= 0; i--, pos--) {
      final ItemInfo ii = mItems.get(i);
      while (pos > ii.position) {
        offset -= mAdapter.getPageWidth(pos--) + marginOffset;
      }
      offset -= ii.heightFactor + marginOffset;
      ii.offset = offset;
      if (ii.position == 0) mFirstOffset = offset;
    }
    offset = curItem.offset + curItem.heightFactor + marginOffset;
    pos = curItem.position + 1;
    // Next pages
    for (int i = curIndex + 1; i < itemCount; i++, pos++) {
      final ItemInfo ii = mItems.get(i);
      while (pos < ii.position) {
        offset += mAdapter.getPageWidth(pos++) + marginOffset;
      }
      if (ii.position == N - 1) {
        mLastOffset = offset + ii.heightFactor - 1;
      }
      ii.offset = offset;
      offset += ii.heightFactor + marginOffset;
    }

    mNeedCalculatePageOffsets = false;
  }

  /**
   * This is the persistent state that is saved by ViewPager.  Only needed
   * if you are creating a sublass of ViewPager that must save its own
   * state, in which case it should implement a subclass of this which
   * contains that state.
   */
  public static class SavedState extends BaseSavedState {
    int position;
    Parcelable adapterState;
    ClassLoader loader;

    public SavedState(Parcelable superState) {
      super(superState);
    }

    @Override public void writeToParcel(Parcel out, int flags) {
      super.writeToParcel(out, flags);
      out.writeInt(position);
      out.writeParcelable(adapterState, flags);
    }

    @Override public String toString() {
      return "FragmentPager.SavedState{"
          + Integer.toHexString(System.identityHashCode(this))
          + " position="
          + position
          + "}";
    }

    public static final Creator<SavedState> CREATOR =
        ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks<SavedState>() {
          @Override
          public SavedState createFromParcel(Parcel in, ClassLoader loader) {
            return new SavedState(in, loader);
          }

          @Override
          public SavedState[] newArray(int size) {
            return new SavedState[size];
          }
        });

    SavedState(Parcel in, ClassLoader loader) {
      super(in);
      if (loader == null) {
        loader = getClass().getClassLoader();
      }
      position = in.readInt();
      adapterState = in.readParcelable(loader);
      this.loader = loader;
    }
  }

  @Override public Parcelable onSaveInstanceState() {
    Parcelable superState = super.onSaveInstanceState();
    SavedState ss = new SavedState(superState);
    ss.position = mCurItem;
    if (mAdapter != null) {
      ss.adapterState = mAdapter.saveState();
    }
    return ss;
  }

  @Override public void onRestoreInstanceState(Parcelable state) {
    if (!(state instanceof SavedState)) {
      super.onRestoreInstanceState(state);
      return;
    }

    SavedState ss = (SavedState) state;
    super.onRestoreInstanceState(ss.getSuperState());

    if (mAdapter != null) {
      mAdapter.restoreState(ss.adapterState, ss.loader);
      setCurrentItemInternal(ss.position, false, true);
    } else {
      mRestoredCurItem = ss.position;
      mRestoredAdapterState = ss.adapterState;
      mRestoredClassLoader = ss.loader;
    }
  }

  @Override public void addView(View child, int index, ViewGroup.LayoutParams params) {
    if (!checkLayoutParams(params)) {
      params = generateLayoutParams(params);
    }
    final LayoutParams lp = (LayoutParams) params;
    lp.isDecor |= child instanceof Decor;
    if (mInLayout) {
      if (lp != null && lp.isDecor) {
        throw new IllegalStateException("Cannot add pager decor view during layout");
      }
      lp.needsMeasure = true;
      addViewInLayout(child, index, params);
    } else {
      super.addView(child, index, params);
    }

    if (USE_CACHE) {
      if (child.getVisibility() != GONE) {
        child.setDrawingCacheEnabled(mScrollingCacheEnabled);
      } else {
        child.setDrawingCacheEnabled(false);
      }
    }
  }

  @Override public void removeView(View view) {
    if (mInLayout) {
      removeViewInLayout(view);
    } else {
      super.removeView(view);
    }
  }

  ItemInfo infoForChild(View child) {
    for (int i = 0; i < mItems.size(); i++) {
      ItemInfo ii = mItems.get(i);
      if (mAdapter.isViewFromObject(child, ii.object)) {
        return ii;
      }
    }
    return null;
  }

  ItemInfo infoForAnyChild(View child) {
    ViewParent parent;
    while ((parent = child.getParent()) != this) {
      if (parent == null || !(parent instanceof View)) {
        return null;
      }
      child = (View) parent;
    }
    return infoForChild(child);
  }

  ItemInfo infoForPosition(int position) {
    for (int i = 0; i < mItems.size(); i++) {
      ItemInfo ii = mItems.get(i);
      if (ii.position == position) {
        return ii;
      }
    }
    return null;
  }

  @Override protected void onAttachedToWindow() {
    super.onAttachedToWindow();
    mFirstLayout = true;
  }

  @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // For simple implementation, our internal size is always 0.
    // We depend on the container to specify the layout size of
    // our view.  We can't really know what it is since we will be
    // adding and removing different arbitrary views and do not
    // want the layout to change as this happens.
    setMeasuredDimension(getDefaultSize(0, widthMeasureSpec), getDefaultSize(0, heightMeasureSpec));

    final int measuredHeight = getMeasuredHeight();
    final int maxGutterSize = measuredHeight / 10;
    mGutterSize = Math.min(maxGutterSize, mDefaultGutterSize);

    // Children are just made to fill our space.
    int childWidthSize = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
    int childHeightSize = measuredHeight - getPaddingTop() - getPaddingBottom();

        /*
         * Make sure all children have been properly measured. Decor views first.
         * Right now we cheat and make this less complicated by assuming decor
         * views won't intersect. We will pin to edges based on gravity.
         */
    int size = getChildCount();
    for (int i = 0; i < size; ++i) {
      final View child = getChildAt(i);
      if (child.getVisibility() != GONE) {
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        if (lp != null && lp.isDecor) {
          final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
          final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
          int widthMode = MeasureSpec.AT_MOST;
          int heightMode = MeasureSpec.AT_MOST;
          boolean consumeVertical = vgrav == Gravity.TOP || vgrav == Gravity.BOTTOM;
          boolean consumeHorizontal = hgrav == Gravity.LEFT || hgrav == Gravity.RIGHT;

          if (consumeVertical) {
            widthMode = MeasureSpec.EXACTLY;
          } else if (consumeHorizontal) {
            heightMode = MeasureSpec.EXACTLY;
          }

          int widthSize = childWidthSize;
          int heightSize = childHeightSize;
          if (lp.width != LayoutParams.WRAP_CONTENT) {
            widthMode = MeasureSpec.EXACTLY;
            if (lp.width != LayoutParams.FILL_PARENT) {
              widthSize = lp.width;
            }
          }
          if (lp.height != LayoutParams.WRAP_CONTENT) {
            heightMode = MeasureSpec.EXACTLY;
            if (lp.height != LayoutParams.FILL_PARENT) {
              heightSize = lp.height;
            }
          }
          final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode);
          final int heightSpec = MeasureSpec.makeMeasureSpec(heightSize, heightMode);
          child.measure(widthSpec, heightSpec);

          if (consumeVertical) {
            childHeightSize -= child.getMeasuredHeight();
          } else if (consumeHorizontal) {
            childWidthSize -= child.getMeasuredWidth();
          }
        }
      }
    }

    mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthSize, MeasureSpec.EXACTLY);
    mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeightSize, MeasureSpec.EXACTLY);

    // Make sure we have created all fragments that we need to have shown.
    mInLayout = true;
    populate();
    mInLayout = false;

    // Page views next.
    size = getChildCount();
    for (int i = 0; i < size; ++i) {
      final View child = getChildAt(i);
      if (child.getVisibility() != GONE) {
        if (DEBUG) Log.v(TAG, "Measuring #" + i + " " + child + ": " + mChildWidthMeasureSpec);

        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        if (lp == null || !lp.isDecor) {
          final int heightSpec =
              MeasureSpec.makeMeasureSpec((int) (childHeightSize * lp.heightFactor),
                  MeasureSpec.EXACTLY);
          child.measure(mChildWidthMeasureSpec, heightSpec);
        }
      }
    }
  }

  @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);

    // Make sure scroll position is set correctly.
    if (h != oldh) {
      recomputeScrollPosition(h, oldh, mPageMargin, mPageMargin);
    }
  }

  private void recomputeScrollPosition(int height, int oldHeight, int margin, int oldMargin) {
    if (oldHeight > 0 && !mItems.isEmpty()) {
      final int heightWithMargin = height - getPaddingTop() - getPaddingBottom() + margin;
      final int oldHeightWithMargin = oldHeight - getPaddingTop() - getPaddingBottom() + oldMargin;
      final int ypos = getScrollY();
      final float pageOffset = (float) ypos / oldHeightWithMargin;
      final int newOffsetPixels = (int) (pageOffset * heightWithMargin);

      scrollTo(getScrollX(), newOffsetPixels);
      if (!mScroller.isFinished()) {
        // We now return to your regularly scheduled scroll, already in progress.
        final int newDuration = mScroller.getDuration() - mScroller.timePassed();
        ItemInfo targetInfo = infoForPosition(mCurItem);
        mScroller.startScroll(0, newOffsetPixels, 0, (int) (targetInfo.offset * height),
            newDuration);
      }
    } else {
      final ItemInfo ii = infoForPosition(mCurItem);
      final float scrollOffset = ii != null ? Math.min(ii.offset, mLastOffset) : 0;
      final int scrollPos = (int) (scrollOffset * (height - getPaddingTop() - getPaddingBottom()));
      if (scrollPos != getScrollY()) {
        completeScroll(false);
        scrollTo(getScrollX(), scrollPos);
      }
    }
  }

  @Override protected void onLayout(boolean changed, int l, int t, int r, int b) {
    final int count = getChildCount();
    int width = r - l;
    int height = b - t;
    int paddingLeft = getPaddingLeft();
    int paddingTop = getPaddingTop();
    int paddingRight = getPaddingRight();
    int paddingBottom = getPaddingBottom();
    final int scrollY = getScrollY();

    int decorCount = 0;

    // First pass - decor views. We need to do this in two passes so that
    // we have the proper offsets for non-decor views later.
    for (int i = 0; i < count; i++) {
      final View child = getChildAt(i);
      if (child.getVisibility() != GONE) {
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        int childLeft = 0;
        int childTop = 0;
        if (lp.isDecor) {
          final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
          final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
          switch (hgrav) {
            default:
              childLeft = paddingLeft;
              break;
            case Gravity.LEFT:
              childLeft = paddingLeft;
              paddingLeft += child.getMeasuredWidth();
              break;
            case Gravity.CENTER_HORIZONTAL:
              childLeft = Math.max((width - child.getMeasuredWidth()) / 2, paddingLeft);
              break;
            case Gravity.RIGHT:
              childLeft = width - paddingRight - child.getMeasuredWidth();
              paddingRight += child.getMeasuredWidth();
              break;
          }
          switch (vgrav) {
            default:
              childTop = paddingTop;
              break;
            case Gravity.TOP:
              childTop = paddingTop;
              paddingTop += child.getMeasuredHeight();
              break;
            case Gravity.CENTER_VERTICAL:
              childTop = Math.max((height - child.getMeasuredHeight()) / 2, paddingTop);
              break;
            case Gravity.BOTTOM:
              childTop = height - paddingBottom - child.getMeasuredHeight();
              paddingBottom += child.getMeasuredHeight();
              break;
          }
          childTop += scrollY;
          child.layout(childLeft, childTop, childLeft + child.getMeasuredWidth(),
              childTop + child.getMeasuredHeight());
          decorCount++;
        }
      }
    }

    final int childHeight = height - paddingTop - paddingBottom;
    // Page views. Do this once we have the right padding offsets from above.
    for (int i = 0; i < count; i++) {
      final View child = getChildAt(i);
      if (child.getVisibility() != GONE) {
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        ItemInfo ii;
        if (!lp.isDecor && (ii = infoForChild(child)) != null) {
          int toff = (int) (childHeight * ii.offset);
          int childLeft = paddingLeft;
          int childTop = paddingTop + toff;
          if (lp.needsMeasure) {
            // This was added during layout and needs measurement.
            // Do it now that we know what we're working with.
            lp.needsMeasure = false;
            final int widthSpec =
                MeasureSpec.makeMeasureSpec((int) (width - paddingLeft - paddingRight),
                    MeasureSpec.EXACTLY);
            final int heightSpec =
                MeasureSpec.makeMeasureSpec((int) (childHeight * lp.heightFactor),
                    MeasureSpec.EXACTLY);
            child.measure(widthSpec, heightSpec);
          }
          if (DEBUG) {
            Log.v(TAG, "Positioning #"
                + i
                + " "
                + child
                + " f="
                + ii.object
                + ":"
                + childLeft
                + ","
                + childTop
                + " "
                + child.getMeasuredWidth()
                + "x"
                + child.getMeasuredHeight());
          }
          child.layout(childLeft, childTop, childLeft + child.getMeasuredWidth(),
              childTop + child.getMeasuredHeight());
        }
      }
    }
    mLeftPageBounds = paddingLeft;
    mRightPageBounds = width - paddingRight;
    mDecorChildCount = decorCount;

    if (mFirstLayout) {
      scrollToItem(mCurItem, false, 0, false);
    }
    mFirstLayout = false;
  }

  @Override public void computeScroll() {
    if (!mScroller.isFinished() && mScroller.computeScrollOffset()) {
      int oldX = getScrollX();
      int oldY = getScrollY();
      int x = mScroller.getCurrX();
      int y = mScroller.getCurrY();

      if (oldX != x || oldY != y) {
        scrollTo(x, y);
        if (!pageScrolled(y)) {
          mScroller.abortAnimation();
          scrollTo(x, 0);
        }
      }

      // Keep on drawing until the animation has finished.
      ViewCompat.postInvalidateOnAnimation(this);
      return;
    }

    // Done with scroll, clean up state.
    completeScroll(true);
  }

  private boolean pageScrolled(int ypos) {
    if (mItems.size() == 0) {
      mCalledSuper = false;
      onPageScrolled(0, 0, 0);
      if (!mCalledSuper) {
        throw new IllegalStateException("onPageScrolled did not call superclass implementation");
      }
      return false;
    }
    final ItemInfo ii = infoForCurrentScrollPosition();
    final int height = getClientHeight();
    final int heightWithMargin = height + mPageMargin;
    final float marginOffset = (float) mPageMargin / height;
    final int currentPage = ii.position;
    final float pageOffset =
        (((float) ypos / height) - ii.offset) / (ii.heightFactor + marginOffset);
    final int offsetPixels = (int) (pageOffset * heightWithMargin);

    mCalledSuper = false;
    onPageScrolled(currentPage, pageOffset, offsetPixels);
    if (!mCalledSuper) {
      throw new IllegalStateException("onPageScrolled did not call superclass implementation");
    }
    return true;
  }

  /**
   * This method will be invoked when the current page is scrolled, either as part
   * of a programmatically initiated smooth scroll or a user initiated touch scroll.
   * If you override this method you must call through to the superclass implementation
   * (e.g. super.onPageScrolled(position, offset, offsetPixels)) before onPageScrolled
   * returns.
   *
   * @param position Position index of the first page currently being displayed.
   * Page position+1 will be visible if positionOffset is nonzero.
   * @param offset Value from [0, 1) indicating the offset from the page at position.
   * @param offsetPixels Value in pixels indicating the offset from position.
   */
  protected void onPageScrolled(int position, float offset, int offsetPixels) {
    // Offset any decor views if needed - keep them on-screen at all times.
    if (mDecorChildCount > 0) {
      final int scrollY = getScrollY();
      int paddingTop = getPaddingTop();
      int paddingBottom = getPaddingBottom();
      final int height = getHeight();
      final int childCount = getChildCount();
      for (int i = 0; i < childCount; i++) {
        final View child = getChildAt(i);
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        if (!lp.isDecor) continue;

        final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
        int childTop = 0;
        switch (vgrav) {
          default:
            childTop = paddingTop;
            break;
          case Gravity.TOP:
            childTop = paddingTop;
            paddingTop += child.getHeight();
            break;
          case Gravity.CENTER_VERTICAL:
            childTop = Math.max((height - child.getMeasuredHeight()) / 2, paddingTop);
            break;
          case Gravity.BOTTOM:
            childTop = height - paddingBottom - child.getMeasuredHeight();
            paddingBottom += child.getMeasuredHeight();
            break;
        }
        childTop += scrollY;

        final int childOffset = childTop - child.getTop();
        if (childOffset != 0) {
          child.offsetTopAndBottom(childOffset);
        }
      }
    }

    if (mOnPageChangeListener != null) {
      mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels);
    }
    if (mInternalPageChangeListener != null) {
      mInternalPageChangeListener.onPageScrolled(position, offset, offsetPixels);
    }

    if (mPageTransformer != null) {
      final int scrollY = getScrollY();
      final int childCount = getChildCount();
      for (int i = 0; i < childCount; i++) {
        final View child = getChildAt(i);
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();

        if (lp.isDecor) continue;

        final float transformPos = (float) (child.getTop() - scrollY) / getClientHeight();
        mPageTransformer.transformPage(child, transformPos);
      }
    }

    mCalledSuper = true;
  }

  private void completeScroll(boolean postEvents) {
    boolean needPopulate = mScrollState == SCROLL_STATE_SETTLING;
    if (needPopulate) {
      // Done with scroll, no longer want to cache view drawing.
      setScrollingCacheEnabled(false);
      mScroller.abortAnimation();
      int oldX = getScrollX();
      int oldY = getScrollY();
      int x = mScroller.getCurrX();
      int y = mScroller.getCurrY();
      if (oldX != x || oldY != y) {
        scrollTo(x, y);
      }
    }
    mPopulatePending = false;
    for (int i = 0; i < mItems.size(); i++) {
      ItemInfo ii = mItems.get(i);
      if (ii.scrolling) {
        needPopulate = true;
        ii.scrolling = false;
      }
    }
    if (needPopulate) {
      if (postEvents) {
        ViewCompat.postOnAnimation(this, mEndScrollRunnable);
      } else {
        mEndScrollRunnable.run();
      }
    }
  }

  private boolean isGutterDrag(float y, float dy) {
    return (y < mGutterSize && dy > 0) || (y > getHeight() - mGutterSize && dy < 0);
  }

  private void enableLayers(boolean enable) {
    final int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
      final int layerType = enable ? ViewCompat.LAYER_TYPE_HARDWARE : ViewCompat.LAYER_TYPE_NONE;
      ViewCompat.setLayerType(getChildAt(i), layerType, null);
    }
  }

  @Override public boolean onInterceptTouchEvent(MotionEvent ev) {
        /*
         * This method JUST determines whether we want to intercept the motion.
         * If we return true, onMotionEvent will be called and we do the actual
         * scrolling there.
         */

    final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;

    // Always take care of the touch gesture being complete.
    if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
      // Release the drag.
      if (DEBUG) Log.v(TAG, "Intercept done!");
      mIsBeingDragged = false;
      mIsUnableToDrag = false;
      mActivePointerId = INVALID_POINTER;
      if (mVelocityTracker != null) {
        mVelocityTracker.recycle();
        mVelocityTracker = null;
      }
      return false;
    }

    // Nothing more to do here if we have decided whether or not we
    // are dragging.
    if (action != MotionEvent.ACTION_DOWN) {
      if (mIsBeingDragged) {
        if (DEBUG) Log.v(TAG, "Intercept returning true!");
        return true;
      }
      if (mIsUnableToDrag) {
        if (DEBUG) Log.v(TAG, "Intercept returning false!");
        return false;
      }
    }

    switch (action) {
      case MotionEvent.ACTION_MOVE: {
                /*
                 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
                 * whether the user has moved far enough from his original down touch.
                 */

                /*
                * Locally do absolute value. mLastMotionY is set to the y value
                * of the down event.
                */
        final int activePointerId = mActivePointerId;
        if (activePointerId == INVALID_POINTER) {
          // If we don't have a valid id, the touch down wasn't on content.
          break;
        }

        final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId);
        final float y = MotionEventCompat.getY(ev, pointerIndex);
        final float dy = y - mLastMotionY;
        final float yDiff = Math.abs(dy);
        final float x = MotionEventCompat.getX(ev, pointerIndex);
        final float xDiff = Math.abs(x - mInitialMotionX);
        if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);

        if (dy != 0 && !isGutterDrag(mLastMotionY, dy) &&
            canScroll(this, false, (int) dy, (int) x, (int) y)) {
          // Nested view has scrollable area under this point. Let it be handled there.
          mLastMotionX = x;
          mLastMotionY = y;
          mIsUnableToDrag = true;
          return false;
        }
        if (yDiff > mTouchSlop && yDiff * 0.5f > xDiff) {
          if (DEBUG) Log.v(TAG, "Starting drag!");
          mIsBeingDragged = true;
          requestParentDisallowInterceptTouchEvent(true);
          setScrollState(SCROLL_STATE_DRAGGING);
          mLastMotionY = dy > 0 ? mInitialMotionY + mTouchSlop : mInitialMotionY - mTouchSlop;
          mLastMotionX = x;
          setScrollingCacheEnabled(true);
        } else if (xDiff > mTouchSlop) {
          // The finger has moved enough in the vertical
          // direction to be counted as a drag...  abort
          // any attempt to drag horizontally, to work correctly
          // with children that have scrolling containers.
          if (DEBUG) Log.v(TAG, "Starting unable to drag!");
          mIsUnableToDrag = true;
        }
        if (mIsBeingDragged) {
          // Scroll to follow the motion event
          if (performDrag(y)) {
            ViewCompat.postInvalidateOnAnimation(this);
          }
        }
        break;
      }

      case MotionEvent.ACTION_DOWN: {
                /*
                 * Remember location of down touch.
                 * ACTION_DOWN always refers to pointer index 0.
                 */
        mLastMotionX = mInitialMotionX = ev.getX();
        mLastMotionY = mInitialMotionY = ev.getY();
        mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
        mIsUnableToDrag = false;

        mScroller.computeScrollOffset();
        if (mScrollState == SCROLL_STATE_SETTLING
            && Math.abs(mScroller.getFinalY() - mScroller.getCurrY()) > mCloseEnough) {
          // Let the user 'catch' the pager as it animates.
          mScroller.abortAnimation();
          mPopulatePending = false;
          populate();
          mIsBeingDragged = true;
          requestParentDisallowInterceptTouchEvent(true);
          setScrollState(SCROLL_STATE_DRAGGING);
        } else {
          completeScroll(false);
          mIsBeingDragged = false;
        }

        if (DEBUG) {
          Log.v(TAG, "Down at "
              + mLastMotionX
              + ","
              + mLastMotionY
              + " mIsBeingDragged="
              + mIsBeingDragged
              + "mIsUnableToDrag="
              + mIsUnableToDrag);
        }
        break;
      }

      case MotionEventCompat.ACTION_POINTER_UP:
        onSecondaryPointerUp(ev);
        break;
    }

    if (mVelocityTracker == null) {
      mVelocityTracker = VelocityTracker.obtain();
    }
    mVelocityTracker.addMovement(ev);

        /*
         * The only time we want to intercept motion events is if we are in the
         * drag mode.
         */
    return mIsBeingDragged;
  }

  @Override public boolean onTouchEvent(MotionEvent ev) {
    if (mFakeDragging) {
      // A fake drag is in progress already, ignore this real one
      // but still eat the touch events.
      // (It is likely that the user is multi-touching the screen.)
      return true;
    }

    if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
      // Don't handle edge touches immediately -- they may actually belong to one of our
      // descendants.
      return false;
    }

    if (mAdapter == null || mAdapter.getCount() == 0) {
      // Nothing to present or scroll; nothing to touch.
      return false;
    }

    if (mVelocityTracker == null) {
      mVelocityTracker = VelocityTracker.obtain();
    }
    mVelocityTracker.addMovement(ev);

    final int action = ev.getAction();
    boolean needsInvalidate = false;

    switch (action & MotionEventCompat.ACTION_MASK) {
      case MotionEvent.ACTION_DOWN: {
        mScroller.abortAnimation();
        mPopulatePending = false;
        populate();

        // Remember where the motion event started
        mLastMotionX = mInitialMotionX = ev.getX();
        mLastMotionY = mInitialMotionY = ev.getY();
        mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
        break;
      }
      case MotionEvent.ACTION_MOVE:
        if (!mIsBeingDragged) {
          final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
          final float y = MotionEventCompat.getY(ev, pointerIndex);
          final float yDiff = Math.abs(y - mLastMotionY);
          final float x = MotionEventCompat.getX(ev, pointerIndex);
          final float xDiff = Math.abs(x - mLastMotionX);
          if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
          if (yDiff > mTouchSlop && yDiff > xDiff) {
            if (DEBUG) Log.v(TAG, "Starting drag!");
            mIsBeingDragged = true;
            requestParentDisallowInterceptTouchEvent(true);
            mLastMotionY = y - mInitialMotionY > 0 ? mInitialMotionY + mTouchSlop
                : mInitialMotionY - mTouchSlop;
            mLastMotionX = x;
            setScrollState(SCROLL_STATE_DRAGGING);
            setScrollingCacheEnabled(true);

            // Disallow Parent Intercept, just in case
            ViewParent parent = getParent();
            if (parent != null) {
              parent.requestDisallowInterceptTouchEvent(true);
            }
          }
        }
        // Not else! Note that mIsBeingDragged can be set above.
        if (mIsBeingDragged) {
          // Scroll to follow the motion event
          final int activePointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
          final float y = MotionEventCompat.getY(ev, activePointerIndex);
          needsInvalidate |= performDrag(y);
        }
        break;
      case MotionEvent.ACTION_UP:
        if (mIsBeingDragged) {
          final VelocityTracker velocityTracker = mVelocityTracker;
          velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
          int initialVelocity =
              (int) VelocityTrackerCompat.getYVelocity(velocityTracker, mActivePointerId);
          mPopulatePending = true;
          final int height = getClientHeight();
          final int scrollY = getScrollY();
          final ItemInfo ii = infoForCurrentScrollPosition();
          final int currentPage = ii.position;
          final float pageOffset = (((float) scrollY / height) - ii.offset) / ii.heightFactor;
          final int activePointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
          final float y = MotionEventCompat.getY(ev, activePointerIndex);
          final int totalDelta = (int) (y - mInitialMotionY);
          int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity, totalDelta);
          setCurrentItemInternal(nextPage, true, true, initialVelocity);

          mActivePointerId = INVALID_POINTER;
          endDrag();
          needsInvalidate = mTopEdge.onRelease() | mBottomEdge.onRelease();
        }
        break;
      case MotionEvent.ACTION_CANCEL:
        if (mIsBeingDragged) {
          scrollToItem(mCurItem, true, 0, false);
          mActivePointerId = INVALID_POINTER;
          endDrag();
          needsInvalidate = mTopEdge.onRelease() | mBottomEdge.onRelease();
        }
        break;
      case MotionEventCompat.ACTION_POINTER_DOWN: {
        final int index = MotionEventCompat.getActionIndex(ev);
        final float y = MotionEventCompat.getY(ev, index);
        mLastMotionY = y;
        mActivePointerId = MotionEventCompat.getPointerId(ev, index);
        break;
      }
      case MotionEventCompat.ACTION_POINTER_UP:
        onSecondaryPointerUp(ev);
        mLastMotionY =
            MotionEventCompat.getY(ev, MotionEventCompat.findPointerIndex(ev, mActivePointerId));
        break;
    }
    if (needsInvalidate) {
      ViewCompat.postInvalidateOnAnimation(this);
    }
    return true;
  }

  private void requestParentDisallowInterceptTouchEvent(boolean disallowIntercept) {
    final ViewParent parent = getParent();
    if (parent != null) {
      parent.requestDisallowInterceptTouchEvent(disallowIntercept);
    }
  }

  private boolean performDrag(float y) {
    boolean needsInvalidate = false;

    final float deltaY = mLastMotionY - y;
    mLastMotionY = y;

    float oldScrollY = getScrollY();
    float scrollY = oldScrollY + deltaY;
    final int height = getClientHeight();

    float topBound = height * mFirstOffset;
    float bottomBound = height * mLastOffset;
    boolean topAbsolute = true;
    boolean bottomAbsolute = true;

    final ItemInfo firstItem = mItems.get(0);
    final ItemInfo lastItem = mItems.get(mItems.size() - 1);
    if (firstItem.position != 0) {
      topAbsolute = false;
      topBound = firstItem.offset * height;
    }
    if (lastItem.position != mAdapter.getCount() - 1) {
      bottomAbsolute = false;
      bottomBound = lastItem.offset * height;
    }

    if (scrollY < topBound) {
      if (topAbsolute) {
        float over = topBound - scrollY;
        needsInvalidate = mTopEdge.onPull(Math.abs(over) / height);
      }
      scrollY = topBound;
    } else if (scrollY > bottomBound) {
      if (bottomAbsolute) {
        float over = scrollY - bottomBound;
        needsInvalidate = mBottomEdge.onPull(Math.abs(over) / height);
      }
      scrollY = bottomBound;
    }
    // Don't lose the rounded component
    mLastMotionX += scrollY - (int) scrollY;
    scrollTo(getScrollX(), (int) scrollY);
    pageScrolled((int) scrollY);

    return needsInvalidate;
  }

  /**
   * @return Info about the page at the current scroll position.
   * This can be synthetic for a missing middle page; the 'object' field can be null.
   */
  private ItemInfo infoForCurrentScrollPosition() {
    final int height = getClientHeight();
    final float scrollOffset = height > 0 ? (float) getScrollY() / height : 0;
    final float marginOffset = height > 0 ? (float) mPageMargin / height : 0;
    int lastPos = -1;
    float lastOffset = 0.f;
    float lastHeight = 0.f;
    boolean first = true;

    ItemInfo lastItem = null;
    for (int i = 0; i < mItems.size(); i++) {
      ItemInfo ii = mItems.get(i);
      float offset;
      if (!first && ii.position != lastPos + 1) {
        // Create a synthetic item for a missing page.
        ii = mTempItem;
        ii.offset = lastOffset + lastHeight + marginOffset;
        ii.position = lastPos + 1;
        ii.heightFactor = mAdapter.getPageWidth(ii.position);
        i--;
      }
      offset = ii.offset;

      final float topBound = offset;
      final float bottomBound = offset + ii.heightFactor + marginOffset;
      if (first || scrollOffset >= topBound) {
        if (scrollOffset < bottomBound || i == mItems.size() - 1) {
          return ii;
        }
      } else {
        return lastItem;
      }
      first = false;
      lastPos = ii.position;
      lastOffset = offset;
      lastHeight = ii.heightFactor;
      lastItem = ii;
    }

    return lastItem;
  }

  private int determineTargetPage(int currentPage, float pageOffset, int velocity, int deltaY) {
    int targetPage;
    if (Math.abs(deltaY) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) {
      targetPage = velocity > 0 ? currentPage : currentPage + 1;
    } else {
      final float truncator = currentPage >= mCurItem ? 0.4f : 0.6f;
      targetPage = (int) (currentPage + pageOffset + truncator);
    }

    if (mItems.size() > 0) {
      final ItemInfo firstItem = mItems.get(0);
      final ItemInfo lastItem = mItems.get(mItems.size() - 1);

      // Only let the user target pages we have items for
      targetPage = Math.max(firstItem.position, Math.min(targetPage, lastItem.position));
    }

    return targetPage;
  }

  @Override public void draw(Canvas canvas) {
    super.draw(canvas);
    boolean needsInvalidate = false;

    final int overScrollMode = ViewCompat.getOverScrollMode(this);
    if (overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS || (overScrollMode
        == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS &&
        mAdapter != null && mAdapter.getCount() > 1)) {
      if (!mTopEdge.isFinished()) {
        final int restoreCount = canvas.save();
        final int height = getHeight();
        final int width = getWidth() - getPaddingLeft() - getPaddingRight();

        canvas.translate(getPaddingLeft(), mFirstOffset * height);
        mTopEdge.setSize(width, height);
        needsInvalidate |= mTopEdge.draw(canvas);
        canvas.restoreToCount(restoreCount);
      }
      if (!mBottomEdge.isFinished()) {
        final int restoreCount = canvas.save();
        final int height = getHeight();
        final int width = getWidth() - getPaddingLeft() - getPaddingRight();

        canvas.rotate(180);
        canvas.translate(-width - getPaddingLeft(), -(mLastOffset + 1) * height);
        mBottomEdge.setSize(width, height);
        needsInvalidate |= mBottomEdge.draw(canvas);
        canvas.restoreToCount(restoreCount);
      }
    } else {
      mTopEdge.finish();
      mBottomEdge.finish();
    }

    if (needsInvalidate) {
      // Keep animating
      ViewCompat.postInvalidateOnAnimation(this);
    }
  }

  @Override protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    // Draw the margin drawable between pages if needed.
    if (mPageMargin > 0 && mMarginDrawable != null && mItems.size() > 0 && mAdapter != null) {
      final int scrollY = getScrollY();
      final int height = getHeight();

      final float marginOffset = (float) mPageMargin / height;
      int itemIndex = 0;
      ItemInfo ii = mItems.get(0);
      float offset = ii.offset;
      final int itemCount = mItems.size();
      final int firstPos = ii.position;
      final int lastPos = mItems.get(itemCount - 1).position;
      for (int pos = firstPos; pos < lastPos; pos++) {
        while (pos > ii.position && itemIndex < itemCount) {
          ii = mItems.get(++itemIndex);
        }

        float drawAt;
        if (pos == ii.position) {
          drawAt = (ii.offset + ii.heightFactor) * height;
          offset = ii.offset + ii.heightFactor + marginOffset;
        } else {
          float heightFactor = mAdapter.getPageWidth(pos);
          drawAt = (offset + heightFactor) * height;
          offset += heightFactor + marginOffset;
        }

        if (drawAt + mPageMargin > scrollY) {
          mMarginDrawable.setBounds(mLeftPageBounds, (int) drawAt, mRightPageBounds,
              (int) (drawAt + mPageMargin + 0.5f));
          mMarginDrawable.draw(canvas);
        }

        if (drawAt > scrollY + height) {
          break; // No more visible, no sense in continuing
        }
      }
    }
  }

  /**
   * Start a fake drag of the pager.
   * <p/>
   * <p>A fake drag can be useful if you want to synchronize the motion of the ViewPager
   * with the touch scrolling of another view, while still letting the ViewPager
   * control the snapping motion and fling behavior. (e.g. parallax-scrolling tabs.)
   * Call {@link #fakeDragBy(float)} to simulate the actual drag motion. Call
   * {@link #endFakeDrag()} to complete the fake drag and fling as necessary.
   * <p/>
   * <p>During a fake drag the ViewPager will ignore all touch events. If a real drag
   * is already in progress, this method will return false.
   *
   * @return true if the fake drag began successfully, false if it could not be started.
   * @see #fakeDragBy(float)
   * @see #endFakeDrag()
   */
  public boolean beginFakeDrag() {
    if (mIsBeingDragged) {
      return false;
    }
    mFakeDragging = true;
    setScrollState(SCROLL_STATE_DRAGGING);
    mInitialMotionY = mLastMotionY = 0;
    if (mVelocityTracker == null) {
      mVelocityTracker = VelocityTracker.obtain();
    } else {
      mVelocityTracker.clear();
    }
    final long time = SystemClock.uptimeMillis();
    final MotionEvent ev = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 0, 0, 0);
    mVelocityTracker.addMovement(ev);
    ev.recycle();
    mFakeDragBeginTime = time;
    return true;
  }

  /**
   * End a fake drag of the pager.
   *
   * @see #beginFakeDrag()
   * @see #fakeDragBy(float)
   */
  public void endFakeDrag() {
    if (!mFakeDragging) {
      throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
    }

    final VelocityTracker velocityTracker = mVelocityTracker;
    velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
    int initialVelocity =
        (int) VelocityTrackerCompat.getYVelocity(velocityTracker, mActivePointerId);
    mPopulatePending = true;
    final int height = getClientHeight();
    final int scrollY = getScrollY();
    final ItemInfo ii = infoForCurrentScrollPosition();
    final int currentPage = ii.position;
    final float pageOffset = (((float) scrollY / height) - ii.offset) / ii.heightFactor;
    final int totalDelta = (int) (mLastMotionY - mInitialMotionY);
    int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity, totalDelta);
    setCurrentItemInternal(nextPage, true, true, initialVelocity);
    endDrag();

    mFakeDragging = false;
  }

  /**
   * Fake drag by an offset in pixels. You must have called {@link #beginFakeDrag()} first.
   *
   * @param yOffset Offset in pixels to drag by.
   * @see #beginFakeDrag()
   * @see #endFakeDrag()
   */
  public void fakeDragBy(float yOffset) {
    if (!mFakeDragging) {
      throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
    }

    mLastMotionY += yOffset;

    float oldScrollY = getScrollY();
    float scrollY = oldScrollY - yOffset;
    final int height = getClientHeight();

    float topBound = height * mFirstOffset;
    float bottomBound = height * mLastOffset;

    final ItemInfo firstItem = mItems.get(0);
    final ItemInfo lastItem = mItems.get(mItems.size() - 1);
    if (firstItem.position != 0) {
      topBound = firstItem.offset * height;
    }
    if (lastItem.position != mAdapter.getCount() - 1) {
      bottomBound = lastItem.offset * height;
    }

    if (scrollY < topBound) {
      scrollY = topBound;
    } else if (scrollY > bottomBound) {
      scrollY = bottomBound;
    }
    // Don't lose the rounded component
    mLastMotionY += scrollY - (int) scrollY;
    scrollTo(getScrollX(), (int) scrollY);
    pageScrolled((int) scrollY);

    // Synthesize an event for the VelocityTracker.
    final long time = SystemClock.uptimeMillis();
    final MotionEvent ev =
        MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE, 0, mLastMotionY, 0);
    mVelocityTracker.addMovement(ev);
    ev.recycle();
  }

  /**
   * Returns true if a fake drag is in progress.
   *
   * @return true if currently in a fake drag, false otherwise.
   * @see #beginFakeDrag()
   * @see #fakeDragBy(float)
   * @see #endFakeDrag()
   */
  public boolean isFakeDragging() {
    return mFakeDragging;
  }

  private void onSecondaryPointerUp(MotionEvent ev) {
    final int pointerIndex = MotionEventCompat.getActionIndex(ev);
    final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
    if (pointerId == mActivePointerId) {
      // This was our active pointer going up. Choose a new
      // active pointer and adjust accordingly.
      final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
      mLastMotionY = MotionEventCompat.getY(ev, newPointerIndex);
      mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
      if (mVelocityTracker != null) {
        mVelocityTracker.clear();
      }
    }
  }

  private void endDrag() {
    mIsBeingDragged = false;
    mIsUnableToDrag = false;

    if (mVelocityTracker != null) {
      mVelocityTracker.recycle();
      mVelocityTracker = null;
    }
  }

  private void setScrollingCacheEnabled(boolean enabled) {
    if (mScrollingCacheEnabled != enabled) {
      mScrollingCacheEnabled = enabled;
      if (USE_CACHE) {
        final int size = getChildCount();
        for (int i = 0; i < size; ++i) {
          final View child = getChildAt(i);
          if (child.getVisibility() != GONE) {
            child.setDrawingCacheEnabled(enabled);
          }
        }
      }
    }
  }

  public boolean internalCanScrollVertically(int direction) {
    if (mAdapter == null) {
      return false;
    }

    final int height = getClientHeight();
    final int scrollY = getScrollY();
    if (direction < 0) {
      return (scrollY > (int) (height * mFirstOffset));
    } else if (direction > 0) {
      return (scrollY < (int) (height * mLastOffset));
    } else {
      return false;
    }
  }

  /**
   * Tests scrollability within child views of v given a delta of dx.
   *
   * @param v View to test for horizontal scrollability
   * @param checkV Whether the view v passed should itself be checked for scrollability (true),
   * or just its children (false).
   * @param dy Delta scrolled in pixels
   * @param x X coordinate of the active touch point
   * @param y Y coordinate of the active touch point
   * @return true if child views of v can be scrolled by delta of dx.
   */
  protected boolean canScroll(View v, boolean checkV, int dy, int x, int y) {
    if (v instanceof ViewGroup) {
      final ViewGroup group = (ViewGroup) v;
      final int scrollX = v.getScrollX();
      final int scrollY = v.getScrollY();
      final int count = group.getChildCount();
      // Count backwards - let topmost views consume scroll distance first.
      for (int i = count - 1; i >= 0; i--) {
        // TODO: Add versioned support here for transformed views.
        // This will not work for transformed views in Honeycomb+
        final View child = group.getChildAt(i);
        if (y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
            x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
            canScroll(child, true, dy, x + scrollX - child.getLeft(),
                y + scrollY - child.getTop())) {
          return true;
        }
      }
    }

    return checkV && ViewCompat.canScrollVertically(v, -dy);
  }

  @Override public boolean dispatchKeyEvent(KeyEvent event) {
    // Let the focused view and/or our descendants get the key first
    return super.dispatchKeyEvent(event) || executeKeyEvent(event);
  }

  /**
   * You can call this function yourself to have the scroll view perform
   * scrolling from a key event, just as if the event had been dispatched to
   * it by the view hierarchy.
   *
   * @param event The key event to execute.
   * @return Return true if the event was handled, else false.
   */
  public boolean executeKeyEvent(KeyEvent event) {
    boolean handled = false;
    if (event.getAction() == KeyEvent.ACTION_DOWN) {
      switch (event.getKeyCode()) {
        case KeyEvent.KEYCODE_DPAD_LEFT:
          handled = arrowScroll(FOCUS_LEFT);
          break;
        case KeyEvent.KEYCODE_DPAD_RIGHT:
          handled = arrowScroll(FOCUS_RIGHT);
          break;
        case KeyEvent.KEYCODE_TAB:
          if (Build.VERSION.SDK_INT >= 11) {
            // The focus finder had a bug handling FOCUS_FORWARD and FOCUS_BACKWARD
            // before Android 3.0. Ignore the tab key on those devices.
            if (KeyEventCompat.hasNoModifiers(event)) {
              handled = arrowScroll(FOCUS_FORWARD);
            } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_SHIFT_ON)) {
              handled = arrowScroll(FOCUS_BACKWARD);
            }
          }
          break;
      }
    }
    return handled;
  }

  public boolean arrowScroll(int direction) {
    View currentFocused = findFocus();
    if (currentFocused == this) {
      currentFocused = null;
    } else if (currentFocused != null) {
      boolean isChild = false;
      for (ViewParent parent = currentFocused.getParent(); parent instanceof ViewGroup;
          parent = parent.getParent()) {
        if (parent == this) {
          isChild = true;
          break;
        }
      }
      if (!isChild) {
        // This would cause the focus search down below to fail in fun ways.
        final StringBuilder sb = new StringBuilder();
        sb.append(currentFocused.getClass().getSimpleName());
        for (ViewParent parent = currentFocused.getParent(); parent instanceof ViewGroup;
            parent = parent.getParent()) {
          sb.append(" => ").append(parent.getClass().getSimpleName());
        }
        Log.e(TAG, "arrowScroll tried to find focus based on non-child " +
            "current focused view " + sb.toString());
        currentFocused = null;
      }
    }

    boolean handled = false;

    View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, direction);
    if (nextFocused != null && nextFocused != currentFocused) {
      if (direction == View.FOCUS_UP) {
        // If there is nothing to the left, or this is causing us to
        // jump to the right, then what we really want to do is page left.
        final int nextTop = getChildRectInPagerCoordinates(mTempRect, nextFocused).top;
        final int currTop = getChildRectInPagerCoordinates(mTempRect, currentFocused).top;
        if (currentFocused != null && nextTop >= currTop) {
          handled = pageUp();
        } else {
          handled = nextFocused.requestFocus();
        }
      } else if (direction == View.FOCUS_DOWN) {
        // If there is nothing to the right, or this is causing us to
        // jump to the left, then what we really want to do is page right.
        final int nextDown = getChildRectInPagerCoordinates(mTempRect, nextFocused).bottom;
        final int currDown = getChildRectInPagerCoordinates(mTempRect, currentFocused).bottom;
        if (currentFocused != null && nextDown <= currDown) {
          handled = pageDown();
        } else {
          handled = nextFocused.requestFocus();
        }
      }
    } else if (direction == FOCUS_UP || direction == FOCUS_BACKWARD) {
      // Trying to move left and nothing there; try to page.
      handled = pageUp();
    } else if (direction == FOCUS_DOWN || direction == FOCUS_FORWARD) {
      // Trying to move right and nothing there; try to page.
      handled = pageDown();
    }
    if (handled) {
      playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
    }
    return handled;
  }

  private Rect getChildRectInPagerCoordinates(Rect outRect, View child) {
    if (outRect == null) {
      outRect = new Rect();
    }
    if (child == null) {
      outRect.set(0, 0, 0, 0);
      return outRect;
    }
    outRect.left = child.getLeft();
    outRect.right = child.getRight();
    outRect.top = child.getTop();
    outRect.bottom = child.getBottom();

    ViewParent parent = child.getParent();
    while (parent instanceof ViewGroup && parent != this) {
      final ViewGroup group = (ViewGroup) parent;
      outRect.left += group.getLeft();
      outRect.right += group.getRight();
      outRect.top += group.getTop();
      outRect.bottom += group.getBottom();

      parent = group.getParent();
    }
    return outRect;
  }

  boolean pageUp() {
    if (mCurItem > 0) {
      setCurrentItem(mCurItem - 1, true);
      return true;
    }
    return false;
  }

  boolean pageDown() {
    if (mAdapter != null && mCurItem < (mAdapter.getCount() - 1)) {
      setCurrentItem(mCurItem + 1, true);
      return true;
    }
    return false;
  }

  /**
   * We only want the current page that is being shown to be focusable.
   */
  @Override public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
    final int focusableCount = views.size();

    final int descendantFocusability = getDescendantFocusability();

    if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
      for (int i = 0; i < getChildCount(); i++) {
        final View child = getChildAt(i);
        if (child.getVisibility() == VISIBLE) {
          ItemInfo ii = infoForChild(child);
          if (ii != null && ii.position == mCurItem) {
            child.addFocusables(views, direction, focusableMode);
          }
        }
      }
    }

    // we add ourselves (if focusable) in all cases except for when we are
    // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable.  this is
    // to avoid the focus search finding layouts when a more precise search
    // among the focusable children would be more interesting.
    if (descendantFocusability != FOCUS_AFTER_DESCENDANTS ||
        // No focusable descendants
        (focusableCount == views.size())) {
      // Note that we can't call the superclass here, because it will
      // add all views in.  So we need to do the same thing View does.
      if (!isFocusable()) {
        return;
      }
      if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE &&
          isInTouchMode() && !isFocusableInTouchMode()) {
        return;
      }
      if (views != null) {
        views.add(this);
      }
    }
  }

  /**
   * We only want the current page that is being shown to be touchable.
   */
  @Override public void addTouchables(ArrayList<View> views) {
    // Note that we don't call super.addTouchables(), which means that
    // we don't call View.addTouchables().  This is okay because a ViewPager
    // is itself not touchable.
    for (int i = 0; i < getChildCount(); i++) {
      final View child = getChildAt(i);
      if (child.getVisibility() == VISIBLE) {
        ItemInfo ii = infoForChild(child);
        if (ii != null && ii.position == mCurItem) {
          child.addTouchables(views);
        }
      }
    }
  }

  /**
   * We only want the current page that is being shown to be focusable.
   */
  @Override protected boolean onRequestFocusInDescendants(int direction,
      Rect previouslyFocusedRect) {
    int index;
    int increment;
    int end;
    int count = getChildCount();
    if ((direction & FOCUS_FORWARD) != 0) {
      index = 0;
      increment = 1;
      end = count;
    } else {
      index = count - 1;
      increment = -1;
      end = -1;
    }
    for (int i = index; i != end; i += increment) {
      View child = getChildAt(i);
      if (child.getVisibility() == VISIBLE) {
        ItemInfo ii = infoForChild(child);
        if (ii != null && ii.position == mCurItem) {
          if (child.requestFocus(direction, previouslyFocusedRect)) {
            return true;
          }
        }
      }
    }
    return false;
  }

  @Override public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
    // Dispatch scroll events from this ViewPager.
    if (event.getEventType() == AccessibilityEventCompat.TYPE_VIEW_SCROLLED) {
      return super.dispatchPopulateAccessibilityEvent(event);
    }

    // Dispatch all other accessibility events from the current page.
    final int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
      final View child = getChildAt(i);
      if (child.getVisibility() == VISIBLE) {
        final ItemInfo ii = infoForChild(child);
        if (ii != null && ii.position == mCurItem &&
            child.dispatchPopulateAccessibilityEvent(event)) {
          return true;
        }
      }
    }

    return false;
  }

  @Override protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
    return new LayoutParams();
  }

  @Override protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
    return generateDefaultLayoutParams();
  }

  @Override protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
    return p instanceof LayoutParams && super.checkLayoutParams(p);
  }

  @Override public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
    return new LayoutParams(getContext(), attrs);
  }

  class MyAccessibilityDelegate extends AccessibilityDelegateCompat {

    @Override public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
      super.onInitializeAccessibilityEvent(host, event);
      event.setClassName(ViewPager.class.getName());
      final AccessibilityRecordCompat recordCompat = AccessibilityRecordCompat.obtain();
      recordCompat.setScrollable(canScroll());
      if (event.getEventType() == AccessibilityEventCompat.TYPE_VIEW_SCROLLED && mAdapter != null) {
        recordCompat.setItemCount(mAdapter.getCount());
        recordCompat.setFromIndex(mCurItem);
        recordCompat.setToIndex(mCurItem);
      }
    }

    @Override
    public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
      super.onInitializeAccessibilityNodeInfo(host, info);
      info.setClassName(ViewPager.class.getName());
      info.setScrollable(canScroll());
      if (internalCanScrollVertically(1)) {
        info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);
      }
      if (internalCanScrollVertically(-1)) {
        info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);
      }
    }

    @Override public boolean performAccessibilityAction(View host, int action, Bundle args) {
      if (super.performAccessibilityAction(host, action, args)) {
        return true;
      }
      switch (action) {
        case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD: {
          if (internalCanScrollVertically(1)) {
            setCurrentItem(mCurItem + 1);
            return true;
          }
        }
        return false;
        case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD: {
          if (internalCanScrollVertically(-1)) {
            setCurrentItem(mCurItem - 1);
            return true;
          }
        }
        return false;
      }
      return false;
    }

    private boolean canScroll() {
      return (mAdapter != null) && (mAdapter.getCount() > 1);
    }
  }

  private class PagerObserver extends DataSetObserver {
    @Override public void onChanged() {
      dataSetChanged();
    }

    @Override public void onInvalidated() {
      dataSetChanged();
    }
  }

  /**
   * Layout parameters that should be supplied for views added to a
   * ViewPager.
   */
  public static class LayoutParams extends ViewGroup.LayoutParams {
    /**
     * true if this view is a decoration on the pager itself and not
     * a view supplied by the adapter.
     */
    public boolean isDecor;

    /**
     * Gravity setting for use on decor views only:
     * Where to position the view page within the overall ViewPager
     * container; constants are defined in {@link Gravity}.
     */
    public int gravity;

    /**
     * Width as a 0-1 multiplier of the measured pager width
     */
    float heightFactor = 0.f;

    /**
     * true if this view was added during layout and needs to be measured
     * before being positioned.
     */
    boolean needsMeasure;

    /**
     * Adapter position this view is for if !isDecor
     */
    int position;

    /**
     * Current child index within the ViewPager that this view occupies
     */
    int childIndex;

    public LayoutParams() {
      super(FILL_PARENT, FILL_PARENT);
    }

    public LayoutParams(Context context, AttributeSet attrs) {
      super(context, attrs);

      final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
      gravity = a.getInteger(0, Gravity.TOP);
      a.recycle();
    }
  }

  static class ViewPositionComparator implements Comparator<View> {
    @Override public int compare(View lhs, View rhs) {
      final LayoutParams llp = (LayoutParams) lhs.getLayoutParams();
      final LayoutParams rlp = (LayoutParams) rhs.getLayoutParams();
      if (llp.isDecor != rlp.isDecor) {
        return llp.isDecor ? 1 : -1;
      }
      return llp.position - rlp.position;
    }
  }
}