完美解决关于禁止ViewPager预加载的相关问题

我最近上班又遇到一个小难题了,就是如题所述:ViewPager预加载的问题。相信用过ViewPager的人大抵都有遇到过这种情况,网上的解决办法也就那么几个,终于在我自己不断试验之下,完美解决了(禁止了)ViewPager的预加载。

好了,首先来说明一下,什么是ViewPager的预加载:ViewPager有一个 “预加载”的机制,默认会把ViewPager当前位置的左右相邻页面预先初始化(俗称的预加载),它的默认值是 1,这样做的好处就是ViewPager左右滑动会更加流畅。

可是我的情况很特殊,因为我 5 个Fragment里有一个Fragment是有SurfaceView的,这样造成的问题就是,我ViewPager滑动到其相邻页面时,含有SurfaceView的页面就会被预先初始化,然后SurfaceView就开始预览了,只是我们看不到而已。同样的,当我们从含有SurfaceView的页面滑倒其相邻的页面时,SurfaceView并不会回调其surfaceDestory方法。于是这给我造成了极大的困扰。

ok,下面言归正传,到底该怎么禁止ViewPager的这个预加载问题呢?

方案1:网上大多数说法是 懒加载,即让ViewPager预加载初始化UI,而具体一些数据,网络访问请求等延迟加载。这是靠Fragment里有一个setUserVisibleHint(boolean isVisibleToUser)的方法,我们可以在这个方法里做判断,当其True可见时(即切换到某一个具体Fragment)时才去加载数据,这样可以省流量。但这里并不满足我的需求,因为某一个Fragment并不会在ViewPager滑动到其相邻的Fragment时销毁。这个只可以解决部分人问题。

首先我们来深入了解下ViewPager的预加载机制:

上文提到过,ViewPager默认预加载的数量是1,这一点我们可以在ViewPager源码里看到。

DEFAULT_OFFSCREEN_PAGES 这里就定义了默认值是1, 所以网上 有种解决方案 说调用ViewPager的setOffscreenPageLimit(int limit),来设置ViewPager预加载的数量,但是这里很明确的告诉你,这种方案是不可行的,如下图ViewPager源码:

我们可以看到,如果你调用该方法传进来的值小于1是无效的,会被强行的拽回1。而且DEFAULT_OFFSCREEN_PAGES 这个值是private的,子类继承ViewPager也是不可见的。

方案2:然后网上有第二种说法,自定义一个ViewPager,把原生ViewPager全拷过来,修改这个DEFAULT_OFFSCREEN_PAGES 值为0。对,就是这种解决方案!!但是!!

但是!!!但是什么呢?但是我试过,没用。为什么呢?接下来就是本文的重点了。

因为现在Android都6.0了,版本都老高了,其实android虽然每个版本都有v4包,但是这些v4包是有差异的。这就造成高版本v4包里的ViewPager,即使你Copy它,将其DEFAULT_OFFSCREEN_PAGES的值改为0,还是不起作用的,其中的逻辑变了。具体哪里变了导致无效我也没有继续研究了,毕竟公司不会花时间让我研究这些啊。偷笑

完美解决方案:ok,所以关于禁止ViewPager预加载的完美解决方案就是,使用低版本v4包里的ViewPager,完全copy一份,将其中的DEFAULT_OFFSCREEN_PAGES值改为0即可。博主亲测 API 14 即 Android 4.0的v4包里ViewPager 有效。

当然,谷歌既然有这么一种ViewPager的机制肯定有它的道理,所以一般还是预加载的好。

最后,因为低版本的源码越来越少的人会去下载,这里直接把这个禁止了预加载的ViewPager贴上来,需要的人就拿去吧。copy就能用了。

package com.plumcot.usb.view; 

/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */ 

import android.content.Context;
import android.database.DataSetObserver;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
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.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.widget.EdgeEffectCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.FocusFinder;
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.util.ArrayList;
import java.util.Collections;
import java.util.Comparator; 

/**
 * Layout manager that allows the user to flip left and right
 * through pages of data. You supply an implementation of a
 * {@link android.support.v4.view.PagerAdapter} to generate the pages that the view shows.
 *
 * <p>Note this class is currently under early design and
 * development. The API will likely change in later updates of
 * the compatibility library, requiring changes to the source code
 * of apps when they are compiled against the newer version.</p>
 */
public class NoPreloadViewPager extends ViewGroup {
  private static final String TAG = "<span style="font-family:Arial, Helvetica, sans-serif;">NoPreLoadViewPager</span>";
  private static final boolean DEBUG = false; 

  private static final boolean USE_CACHE = false; 

  private static final int DEFAULT_OFFSCREEN_PAGES = 0;//默认是1
  private static final int MAX_SETTLE_DURATION = 600; // ms 

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

  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) {
      // _o(t) = t * t * ((tension + 1) * t + tension)
      // o(t) = _o(t - 1) + 1
      t -= 1.0f;
      return t * t * t + 1.0f;
    }
  }; 

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

  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 mChildWidthMeasureSpec;
  private int mChildHeightMeasureSpec;
  private boolean mInLayout; 

  private boolean mScrollingCacheEnabled; 

  private boolean mPopulatePending;
  private boolean mScrolling;
  private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES; 

  private boolean mIsBeingDragged;
  private boolean mIsUnableToDrag;
  private int mTouchSlop;
  private float mInitialMotionX;
  /**
   * Position of the last motion event.
   */
  private float mLastMotionX;
  private float mLastMotionY;
  /**
   * 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 float mBaseLineFlingVelocity;
  private float mFlingVelocityInfluence; 

  private boolean mFakeDragging;
  private long mFakeDragBeginTime; 

  private EdgeEffectCompat mLeftEdge;
  private EdgeEffectCompat mRightEdge; 

  private boolean mFirstLayout = true; 

  private OnPageChangeListener mOnPageChangeListener; 

  /**
   * 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 int mScrollState = SCROLL_STATE_IDLE; 

  /**
   * Callback interface for responding to changing state of the selected page.
   */
  public interface OnPageChangeListener { 

    /**
     * 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.
     *
     * @param position Position index of the first page currently being displayed.
     *         Page position+1 will be visible if positionOffset is nonzero.
     * @param positionOffset Value from [0, 1) indicating the offset from the page at position.
     * @param positionOffsetPixels Value in pixels indicating the offset from position.
     */
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels); 

    /**
     * This method will be invoked when a new page becomes selected. Animation is not
     * necessarily complete.
     *
     * @param position Position index of the new selected page.
     */
    public void onPageSelected(int position); 

    /**
     * Called when the scroll state changes. Useful for discovering when the user
     * begins dragging, when the pager is automatically settling to the current page,
     * or when it is fully stopped/idle.
     *
     * @param state The new scroll state.
     * @see android.support.v4.view.ViewPager#SCROLL_STATE_IDLE
     * @see android.support.v4.view.ViewPager#SCROLL_STATE_DRAGGING
     * @see android.support.v4.view.ViewPager#SCROLL_STATE_SETTLING
     */
    public void onPageScrollStateChanged(int state);
  } 

  /**
   * Simple implementation of the {@link android.support.v4.view.LazyViewPager.OnPageChangeListener} interface with stub
   * implementations of each method. Extend this if you do not intend to override
   * every method of {@link android.support.v4.view.LazyViewPager.OnPageChangeListener}.
   */
  public static class SimpleOnPageChangeListener implements OnPageChangeListener {
    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
      // This space for rent
    } 

    @Override
    public void onPageSelected(int position) {
      // This space for rent
    } 

    @Override
    public void onPageScrollStateChanged(int state) {
      // This space for rent
    }
  } 

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

  public NoPreloadViewPager(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);
    mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
    mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
    mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
    mLeftEdge = new EdgeEffectCompat(context);
    mRightEdge = new EdgeEffectCompat(context); 

    float density = context.getResources().getDisplayMetrics().density;
    mBaseLineFlingVelocity = 2500.0f * density;
    mFlingVelocityInfluence = 0.4f;
  } 

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

    mScrollState = newState;
    if (mOnPageChangeListener != null) {
      mOnPageChangeListener.onPageScrollStateChanged(newState);
    }
  } 

  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();
      removeAllViews();
      mCurItem = 0;
      scrollTo(0, 0);
    } 

    mAdapter = adapter; 

    if (mAdapter != null) {
      if (mObserver == null) {
        mObserver = new PagerObserver();
      }
//      mAdapter.registerDataSetObserver(mObserver);
      mPopulatePending = false;
      if (mRestoredCurItem >= 0) {
        mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader);
        setCurrentItemInternal(mRestoredCurItem, false, true);
        mRestoredCurItem = -1;
        mRestoredAdapterState = null;
        mRestoredClassLoader = null;
      } else {
        populate();
      }
    }
  } 

  public PagerAdapter getAdapter() {
    return mAdapter;
  } 

  /**
   * Set the currently selected page. If the ViewPager has already been through its first
   * layout 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;
    mCurItem = item;
    populate();
    final int destX = (getWidth() + mPageMargin) * item;
    if (smoothScroll) {
      smoothScrollTo(destX, 0, velocity);
      if (dispatchSelected && mOnPageChangeListener != null) {
        mOnPageChangeListener.onPageSelected(item);
      }
    } else {
      if (dispatchSelected && mOnPageChangeListener != null) {
        mOnPageChangeListener.onPageSelected(item);
      }
      completeScroll();
      scrollTo(destX, 0);
    }
  } 

  public void setOnPageChangeListener(OnPageChangeListener listener) {
    mOnPageChangeListener = listener;
  } 

  /**
   * 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>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>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(android.graphics.drawable.Drawable)
   * @see #setPageMarginDrawable(int)
   */
  public void setPageMargin(int marginPixels) {
    final int oldMargin = mPageMargin;
    mPageMargin = marginPixels; 

    final int width = getWidth();
    recomputeScrollPosition(width, width, 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 android.view.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 android.view.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();
      setScrollState(SCROLL_STATE_IDLE);
      return;
    } 

    setScrollingCacheEnabled(true);
    mScrolling = true;
    setScrollState(SCROLL_STATE_SETTLING); 

    final float pageDelta = (float) Math.abs(dx) / (getWidth() + mPageMargin);
    int duration = (int) (pageDelta * 100); 

    velocity = Math.abs(velocity);
    if (velocity > 0) {
      duration += (duration / (velocity / mBaseLineFlingVelocity)) * mFlingVelocityInfluence;
    } else {
      duration += 100;
    }
    duration = Math.min(duration, MAX_SETTLE_DURATION); 

    mScroller.startScroll(sx, sy, dx, dy, duration);
    invalidate();
  } 

  void addNewItem(int position, int index) {
    ItemInfo ii = new ItemInfo();
    ii.position = position;
    ii.object = mAdapter.instantiateItem(this, position);
    if (index < 0) {
      mItems.add(ii);
    } else {
      mItems.add(index, ii);
    }
  } 

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

    boolean needPopulate = mItems.size() < 3 && mItems.size() < mAdapter.getCount();
    int newCurrItem = -1; 

    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--;
        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, mAdapter.getCount() - 1));
        }
        continue;
      } 

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

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

    Collections.sort(mItems, COMPARATOR); 

    if (newCurrItem >= 0) {
      // TODO This currently causes a jump.
      setCurrentItemInternal(newCurrItem, false, true);
      needPopulate = true;
    }
    if (needPopulate) {
      populate();
      requestLayout();
    }
  } 

  void populate() {
    if (mAdapter == null) {
      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...");
      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 (DEBUG) Log.v(TAG, "populating: startPos=" + startPos + " endPos=" + endPos); 

    // Add and remove pages in the existing list.
    int lastPos = -1;
    for (int i=0; i<mItems.size(); i++) {
      ItemInfo ii = mItems.get(i);
      if ((ii.position < startPos || ii.position > endPos) && !ii.scrolling) {
        if (DEBUG) Log.i(TAG, "removing: " + ii.position + " @ " + i);
        mItems.remove(i);
        i--;
        mAdapter.destroyItem(this, ii.position, ii.object);
      } else if (lastPos < endPos && ii.position > startPos) {
        // The next item is outside of our range, but we have a gap
        // between it and the last item where we want to have a page
        // shown. Fill in the gap.
        lastPos++;
        if (lastPos < startPos) {
          lastPos = startPos;
        }
        while (lastPos <= endPos && lastPos < ii.position) {
          if (DEBUG) Log.i(TAG, "inserting: " + lastPos + " @ " + i);
          addNewItem(lastPos, i);
          lastPos++;
          i++;
        }
      }
      lastPos = ii.position;
    } 

    // Add any new pages we need at the end.
    lastPos = mItems.size() > 0 ? mItems.get(mItems.size()-1).position : -1;
    if (lastPos < endPos) {
      lastPos++;
      lastPos = lastPos > startPos ? lastPos : startPos;
      while (lastPos <= endPos) {
        if (DEBUG) Log.i(TAG, "appending: " + lastPos);
        addNewItem(lastPos, -1);
        lastPos++;
      }
    } 

    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);
      }
    } 

    ItemInfo curItem = null;
    for (int i=0; i<mItems.size(); i++) {
      if (mItems.get(i).position == mCurItem) {
        curItem = mItems.get(i);
        break;
      }
    }
    mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null); 

    mAdapter.finishUpdate(this); 

    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(FOCUS_FORWARD)) {
              break;
            }
          }
        }
      }
    }
  } 

  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, LayoutParams params) {
    if (mInLayout) {
      addViewInLayout(child, index, params);
      child.measure(mChildWidthMeasureSpec, mChildHeightMeasureSpec);
    } else {
      super.addView(child, index, params);
    } 

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

  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);
  } 

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

  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // For simple implementation, or 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)); 

    // Children are just made to fill our space.
    mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() -
        getPaddingLeft() - getPaddingRight(), MeasureSpec.EXACTLY);
    mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight() -
        getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY); 

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

    // Make sure all children have been properly measured.
    final int 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);
        child.measure(mChildWidthMeasureSpec, mChildHeightMeasureSpec);
      }
    }
  } 

  @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 (w != oldw) {
      recomputeScrollPosition(w, oldw, mPageMargin, mPageMargin);
    }
  } 

  private void recomputeScrollPosition(int width, int oldWidth, int margin, int oldMargin) {
    final int widthWithMargin = width + margin;
    if (oldWidth > 0) {
      final int oldScrollPos = getScrollX();
      final int oldwwm = oldWidth + oldMargin;
      final int oldScrollItem = oldScrollPos / oldwwm;
      final float scrollOffset = (float) (oldScrollPos % oldwwm) / oldwwm;
      final int scrollPos = (int) ((oldScrollItem + scrollOffset) * widthWithMargin);
      scrollTo(scrollPos, getScrollY());
      if (!mScroller.isFinished()) {
        // We now return to your regularly scheduled scroll, already in progress.
        final int newDuration = mScroller.getDuration() - mScroller.timePassed();
        mScroller.startScroll(scrollPos, 0, mCurItem * widthWithMargin, 0, newDuration);
      }
    } else {
      int scrollPos = mCurItem * widthWithMargin;
      if (scrollPos != getScrollX()) {
        completeScroll();
        scrollTo(scrollPos, getScrollY());
      }
    }
  } 

  @Override
  protected void onLayout(boolean changed, int l, int t, int r, int b) {
    mInLayout = true;
    populate();
    mInLayout = false; 

    final int count = getChildCount();
    final int width = r-l; 

    for (int i = 0; i < count; i++) {
      View child = getChildAt(i);
      ItemInfo ii;
      if (child.getVisibility() != GONE && (ii=infoForChild(child)) != null) {
        int loff = (width + mPageMargin) * ii.position;
        int childLeft = getPaddingLeft() + loff;
        int childTop = getPaddingTop();
        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());
      }
    }
    mFirstLayout = false;
  } 

  @Override
  public void computeScroll() {
    if (DEBUG) Log.i(TAG, "computeScroll: finished=" + mScroller.isFinished());
    if (!mScroller.isFinished()) {
      if (mScroller.computeScrollOffset()) {
        if (DEBUG) Log.i(TAG, "computeScroll: still scrolling");
        int oldX = getScrollX();
        int oldY = getScrollY();
        int x = mScroller.getCurrX();
        int y = mScroller.getCurrY(); 

        if (oldX != x || oldY != y) {
          scrollTo(x, y);
        } 

        if (mOnPageChangeListener != null) {
          final int widthWithMargin = getWidth() + mPageMargin;
          final int position = x / widthWithMargin;
          final int offsetPixels = x % widthWithMargin;
          final float offset = (float) offsetPixels / widthWithMargin;
          mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels);
        } 

        // Keep on drawing until the animation has finished.
        invalidate();
        return;
      }
    } 

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

  private void completeScroll() {
    boolean needPopulate = mScrolling;
    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);
      }
      setScrollState(SCROLL_STATE_IDLE);
    }
    mPopulatePending = false;
    mScrolling = false;
    for (int i=0; i<mItems.size(); i++) {
      ItemInfo ii = mItems.get(i);
      if (ii.scrolling) {
        needPopulate = true;
        ii.scrolling = false;
      }
    }
    if (needPopulate) {
      populate();
    }
  } 

  @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;
      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 x = MotionEventCompat.getX(ev, pointerIndex);
        final float dx = x - mLastMotionX;
        final float xDiff = Math.abs(dx);
        final float y = MotionEventCompat.getY(ev, pointerIndex);
        final float yDiff = Math.abs(y - mLastMotionY);
        final int scrollX = getScrollX();
        final boolean atEdge = (dx > 0 && scrollX == 0) || (dx < 0 && mAdapter != null &&
            scrollX >= (mAdapter.getCount() - 1) * getWidth() - 1);
        if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff); 

        if (canScroll(this, false, (int) dx, (int) x, (int) y)) {
          // Nested view has scrollable area under this point. Let it be handled there.
          mInitialMotionX = mLastMotionX = x;
          mLastMotionY = y;
          return false;
        }
        if (xDiff > mTouchSlop && xDiff > yDiff) {
          if (DEBUG) Log.v(TAG, "Starting drag!");
          mIsBeingDragged = true;
          setScrollState(SCROLL_STATE_DRAGGING);
          mLastMotionX = x;
          setScrollingCacheEnabled(true);
        } else {
          if (yDiff > 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;
          }
        }
        break;
      } 

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

        if (mScrollState == SCROLL_STATE_SETTLING) {
          // Let the user 'catch' the pager as it animates.
          mIsBeingDragged = true;
          mIsUnableToDrag = false;
          setScrollState(SCROLL_STATE_DRAGGING);
        } else {
          completeScroll();
          mIsBeingDragged = false;
          mIsUnableToDrag = false;
        } 

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

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

    /*
    * 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: {
        /*
         * If being flinged and user touches, stop the fling. isFinished
         * will be false if being flinged.
         */
        completeScroll(); 

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

          final int lastItemIndex = mAdapter.getCount() - 1;
          final float leftBound = Math.max(0, (mCurItem - 1) * widthWithMargin);
          final float rightBound =
              Math.min(mCurItem + 1, lastItemIndex) * widthWithMargin;
          if (scrollX < leftBound) {
            if (leftBound == 0) {
              float over = -scrollX;
              needsInvalidate = mLeftEdge.onPull(over / width);
            }
            scrollX = leftBound;
          } else if (scrollX > rightBound) {
            if (rightBound == lastItemIndex * widthWithMargin) {
              float over = scrollX - rightBound;
              needsInvalidate = mRightEdge.onPull(over / width);
            }
            scrollX = rightBound;
          }
          // Don't lose the rounded component
          mLastMotionX += scrollX - (int) scrollX;
          scrollTo((int) scrollX, getScrollY());
          if (mOnPageChangeListener != null) {
            final int position = (int) scrollX / widthWithMargin;
            final int positionOffsetPixels = (int) scrollX % widthWithMargin;
            final float positionOffset = (float) positionOffsetPixels / widthWithMargin;
            mOnPageChangeListener.onPageScrolled(position, positionOffset,
                positionOffsetPixels);
          }
        }
        break;
      case MotionEvent.ACTION_UP:
        if (mIsBeingDragged) {
          final VelocityTracker velocityTracker = mVelocityTracker;
          velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
          int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(
              velocityTracker, mActivePointerId);
          mPopulatePending = true;
          final int widthWithMargin = getWidth() + mPageMargin;
          final int scrollX = getScrollX();
          final int currentPage = scrollX / widthWithMargin;
          int nextPage = initialVelocity > 0 ? currentPage : currentPage + 1;
          setCurrentItemInternal(nextPage, true, true, initialVelocity); 

          mActivePointerId = INVALID_POINTER;
          endDrag();
          needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease();
        }
        break;
      case MotionEvent.ACTION_CANCEL:
        if (mIsBeingDragged) {
          setCurrentItemInternal(mCurItem, true, true);
          mActivePointerId = INVALID_POINTER;
          endDrag();
          needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease();
        }
        break;
      case MotionEventCompat.ACTION_POINTER_DOWN: {
        final int index = MotionEventCompat.getActionIndex(ev);
        final float x = MotionEventCompat.getX(ev, index);
        mLastMotionX = x;
        mActivePointerId = MotionEventCompat.getPointerId(ev, index);
        break;
      }
      case MotionEventCompat.ACTION_POINTER_UP:
        onSecondaryPointerUp(ev);
        mLastMotionX = MotionEventCompat.getX(ev,
            MotionEventCompat.findPointerIndex(ev, mActivePointerId));
        break;
    }
    if (needsInvalidate) {
      invalidate();
    }
    return true;
  } 

  @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 (!mLeftEdge.isFinished()) {
        final int restoreCount = canvas.save();
        final int height = getHeight() - getPaddingTop() - getPaddingBottom(); 

        canvas.rotate(270);
        canvas.translate(-height + getPaddingTop(), 0);
        mLeftEdge.setSize(height, getWidth());
        needsInvalidate |= mLeftEdge.draw(canvas);
        canvas.restoreToCount(restoreCount);
      }
      if (!mRightEdge.isFinished()) {
        final int restoreCount = canvas.save();
        final int width = getWidth();
        final int height = getHeight() - getPaddingTop() - getPaddingBottom();
        final int itemCount = mAdapter != null ? mAdapter.getCount() : 1; 

        canvas.rotate(90);
        canvas.translate(-getPaddingTop(),
            -itemCount * (width + mPageMargin) + mPageMargin);
        mRightEdge.setSize(height, width);
        needsInvalidate |= mRightEdge.draw(canvas);
        canvas.restoreToCount(restoreCount);
      }
    } else {
      mLeftEdge.finish();
      mRightEdge.finish();
    } 

    if (needsInvalidate) {
      // Keep animating
      invalidate();
    }
  } 

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

    // Draw the margin drawable if needed.
    if (mPageMargin > 0 && mMarginDrawable != null) {
      final int scrollX = getScrollX();
      final int width = getWidth();
      final int offset = scrollX % (width + mPageMargin);
      if (offset != 0) {
        // Pages fit completely when settled; we only need to draw when in between
        final int left = scrollX - offset + width;
        mMarginDrawable.setBounds(left, 0, left + mPageMargin, getHeight());
        mMarginDrawable.draw(canvas);
      }
    }
  } 

  /**
   * Start a fake drag of the pager.
   *
   * <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>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);
    mInitialMotionX = mLastMotionX = 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;
    if ((Math.abs(initialVelocity) > mMinimumVelocity)
        || Math.abs(mInitialMotionX-mLastMotionX) >= (getWidth()/3)) {
      if (mLastMotionX > mInitialMotionX) {
        setCurrentItemInternal(mCurItem-1, true, true);
      } else {
        setCurrentItemInternal(mCurItem+1, true, true);
      }
    } else {
      setCurrentItemInternal(mCurItem, true, true);
    }
    endDrag(); 

    mFakeDragging = false;
  } 

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

    mLastMotionX += xOffset;
    float scrollX = getScrollX() - xOffset;
    final int width = getWidth();
    final int widthWithMargin = width + mPageMargin; 

    final float leftBound = Math.max(0, (mCurItem - 1) * widthWithMargin);
    final float rightBound =
        Math.min(mCurItem + 1, mAdapter.getCount() - 1) * widthWithMargin;
    if (scrollX < leftBound) {
      scrollX = leftBound;
    } else if (scrollX > rightBound) {
      scrollX = rightBound;
    }
    // Don't lose the rounded component
    mLastMotionX += scrollX - (int) scrollX;
    scrollTo((int) scrollX, getScrollY());
    if (mOnPageChangeListener != null) {
      final int position = (int) scrollX / widthWithMargin;
      final int positionOffsetPixels = (int) scrollX % widthWithMargin;
      final float positionOffset = (float) positionOffsetPixels / widthWithMargin;
      mOnPageChangeListener.onPageScrolled(position, positionOffset,
          positionOffsetPixels);
    } 

    // Synthesize an event for the VelocityTracker.
    final long time = SystemClock.uptimeMillis();
    final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE,
        mLastMotionX, 0, 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;
      mLastMotionX = MotionEventCompat.getX(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);
          }
        }
      }
    }
  } 

  /**
   * 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 dx 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 dx, 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 (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
            y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
            canScroll(child, true, dx, x + scrollX - child.getLeft(),
                y + scrollY - child.getTop())) {
          return true;
        }
      }
    } 

    return checkV && ViewCompat.canScrollHorizontally(v, -dx);
  } 

  @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 (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; 

    boolean handled = false; 

    View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused,
        direction);
    if (nextFocused != null && nextFocused != currentFocused) {
      if (direction == View.FOCUS_LEFT) {
        // 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.
        if (currentFocused != null && nextFocused.getLeft() >= currentFocused.getLeft()) {
          handled = pageLeft();
        } else {
          handled = nextFocused.requestFocus();
        }
      } else if (direction == View.FOCUS_RIGHT) {
        // 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.
        if (currentFocused != null && nextFocused.getLeft() <= currentFocused.getLeft()) {
          handled = pageRight();
        } else {
          handled = nextFocused.requestFocus();
        }
      }
    } else if (direction == FOCUS_LEFT || direction == FOCUS_BACKWARD) {
      // Trying to move left and nothing there; try to page.
      handled = pageLeft();
    } else if (direction == FOCUS_RIGHT || direction == FOCUS_FORWARD) {
      // Trying to move right and nothing there; try to page.
      handled = pageRight();
    }
    if (handled) {
      playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
    }
    return handled;
  } 

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

  boolean pageRight() {
    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) {
    // ViewPagers should only report accessibility info for the current page,
    // otherwise things get very confusing. 

    // TODO: Should this note something about the paging container? 

    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;
  } 

  private class PagerObserver extends DataSetObserver { 

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

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android仿微信Viewpager-Fragment惰性加载(lazy-loading)

    前言 今天起床,拿起手机开机第一时间当然是打开微信了,左右滑动Viewpager,发现它使用了一种叫惰性加载,或者说懒加载(lazy-loading)的方式加载Viewpager中的Fragment.效果如图: 什么是lazy-loading呢?顾名思义就是在必要的时候才加载,否则不进行View的绘制和数据的加载.原因是Viewpager一次只会显示一个页卡,那么刚开始的时候,只需加载第一张Fragment页卡,其他的不加载,当用户向右滑动切换再进行加载.因为其他Fragment对于用户来说是不

  • android实现ViewPager懒加载的三种方法

    在项目中ViewPager和Fragment接口框架已经是处处可见,但是在使用中,我们肯定不希望用户在当前页面时就在前后页面的数据,加入数据量很大,而用户又不愿意左右滑动浏览,那么这时候ViewPager中本来充满善意的预加载就有点令人不爽了.我们能做的就是屏蔽掉ViewPager的预加载机制.虽然ViewPager中提供的有setOffscreenPageLimit()来控制其预加载的数目,但是当设置为0后我们发现其根本没效果,这个的最小值就是1,也就是你只能最少前后各预加载一页.那么,这时候

  • 详解Android_性能优化之ViewPager加载成百上千高清大图oom解决方案

    一.背景 最近做项目需要用到选择图片上传,类似于微信.微博那样的图片选择器,ContentResolver读取本地图片资源并用RecyclerView+Glide加载图片显示就搞定列表的显示,这个没什么大问题,重点是,点击图片进入大图浏览,比如你相册有几百张图片,也就意味着在ViewPager中需要加载几百个view,况且手机拍出来的图片都是1-2千万左右像素的高清大图(笔者手机2千万像素 也就是拍照出来的照片3888*5152),大小也有5-7个兆,ViewPager滑动不了十几张就oom了,

  • Android ViewPager制作新手导航页(动态加载)

    我们来讲个老生常谈的话题,估计大家都用过的->ViewPager,用它来做新手导航页面,虽然这次也是讲这个,但是和以往的用法可能有些不同,大家都看到标题进来的,应该知道的是:动态加载指示器. 什么叫动态加载呢,是不是感觉很高大上呢,其实呢就是动态的去加载指示器的数量的,而不是在布局文件中写死.希望看了这篇文章大家对ViewPager有新的认识. 看到这个效果大家应该都很不屑吧,今天讲这个就是为了让大家有新的认识.好了,好好听,开始了. 这个动态加载就是为了动态的加载下面的灰色圆点指示器和红色圆点

  • Android ViewPager加载图片效果

    目前项目中需要用到ViewPager加载图片,现在在此记录一下. 首先先看布局文件:activity_main.xml <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.c

  • Android使用ViewPager加载图片和轮播视频

    作为Android基础组件之一,大家对viewpager已经很熟悉了,网上也有很多使用viewpager来加载图片的案例.但是像微信那样点击图片,可以轮播显示图片和视频的例子却没找到.正巧项目中有需求,可以就花时间写了下,现在给一下核心代码,希望对有此需求的同学们起一个抛砖引玉的作用.话不多说了,上代码: 以下是initData的代码 public void initData() { //把聊天界面的图片和视频找出来,并加到数组中,并在 //并根据传进来的position来找到视频或图片在数组中

  • Android之Viewpager+Fragment实现懒加载示例

    我们在做应用开发的时候,一个Activity里面可能会以viewpager(或其他容器)与多个Fragment来组合使用.而ViewPager默认会缓存三页数据,即:Viewpager每加载一个Fragment,都会预先加载此Fragment左侧或右侧的Fragment.而如果每个fragment都需要去加载数据,或从本地加载,或从网络加载,那么在这个activity刚创建的时候就变成需要初始化大量资源,浪费用户流量不止,还造成卡顿,这样的结果,我们当然不会满意.那么,能不能做到当切换到这个fr

  • android 解决ViewPager加载大量图片内存溢出问题

    1.大家都知道为ViewPager构建适配器继承PagerAdapter,怎么构建就不说了.Viewpager会默认加载当前页和当前页的左右两页.一开始当前页是下标0,所以一开始默认加载第0页(指下标,下同)和第1页.当你向右滑动,当前页为第1页时,ViewPager会加载第2页,这时一共有3页存在(第0,1,2页).再向右滑动,当前页为第2页时,会移除第0页,加载第3页,同理向左滑动当前页为第1页时,会移除第3页.这么说应该懂了吧. 知道了上面的原理,就可以让ViewPager始终只加载3页的

  • 详解Android 在 ViewPager 中使用 Fragment 的懒加载

    我们先看一下效果: 首先,我们要知道什么是懒加载: 懒加载,就是先初始化控件,在用户可见的时候再加载数据. 为什么要懒加载? 懒加载多被使用在新闻资讯类客户端中,试想那么多的分类如果一下子都加载出来,真的是极大地消耗了系统资源.可能有人会说 ViewPager 有 viewPager.setOffscreenPageLimit() 的方法,我们传个 0 进去不就好了吗?看过源码的应该知道,即便你传了 0 进去,系统也会默认为 1 的,也就是 ViewPager 依然会加载当前页面的前后各一个 F

  • Android开发中如何解决Fragment +Viewpager滑动页面重复加载的问题

    前言 之前在做一个Viewpager上面加载多个Fragment时总会实例化已经创建好的Fragmnet对象类似 viewPager.setAdapter(new FragmentPagerAdapter(getSupportFragmentManager()) { @Override public Fragment getItem(int position) { switch(position){ case 0: fragments=new Fragmnet01(); break; case

  • Android ViewPager动态加载问题

    今天做项目时,纠结了很久,动态添加view,刚开始按照其他的adapter处理,但是不会刷新view,来回翻几页,还会view覆盖,最后手动调用adapter的destroyItem和instantiateItem方法,还是不行,最后重写notifyDataSetChanged中removeAllViews和instantiateItem,有点效果,可是还是不理想.最后查询资料要重写PagerAdapter的方法 如下: public int getItemPosition(Object obj

随机推荐