我最近上班又遇到一个小难题了,就是如题所述: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 ex
01-29 18:46
查看更多