前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >完美解决关于禁止ViewPager预加载的相关问题

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

作者头像
砸漏
发布2020-10-22 11:25:34
1.7K0
发布2020-10-22 11:25:34
举报
文章被收录于专栏:恩蓝脚本恩蓝脚本

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

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-09-11 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档