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

2020-10-22 11:25:34 浏览数 (1)

我最近上班又遇到一个小难题了,就是如题所述: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就能用了。

代码语言:javascript复制
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 人点赞