Vous êtes sur la page 1sur 56

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
4a/BsuG0obdhugHAIcQBy6HTgdfB2QHbweJh5AHmoe6B8sH+QgJiBYIHogoCC4IMwg4iD2IQwhKiHuIj
4iwCMWI34j1iQ6JHIkwiVYJbQmHCZAJl4mfCaaJqgm+id6J8ooFCieKLQoyikOKTApWinWKjIqcCqqKt
YrIityK8osJCxkLKws7i5ELoguni6sLrouyC7qLzwvgi+8MCYwejCoMOIxKDF6McAx9jIUMygzpDQCNC
Y04DVcNew2YjbSNyI3hjfKOEw4ajjQOgQ6sjryO1Q7/DxiPOI95j7eQDQAAAABAAAAqAC9ABAAAAAAAA
IAAQACABYAAAEAAXYAAAAAeNqFkr1OAkEUhb8RNCEaY2VhNbWJ64L4BxWx02iMf9QsIhiBNbASfQELC9
/DN7DwAbTxCXwYz86OBtHETObOmXvO/dk7C8zyQA6TLwAd7QwbFnTL8BTz3HqcY4N7j/NYXjyeluLD4x
msOfG4wJJJPJ5j2Tx6/Mqiefb4jdC8s0PMNXcMuKSt2omyP2mXCCnKWiKxlho34mJ6NHQmUjcJnL+rZc
fih+7W0tnSOZI9l/JAsVlUTF/ckfxt5ewq34A1KUKtMlVO2aMuffVXzMpE1CRvJ/gz18FQipS3P6occi
xPehv3dlzGLN9IfNFxAZuyVfftV8qZai7k7SpzpBkFrLu9pVwltv/te1f+luuoJrYn/9BNq+/ml+g9Kq
xqNf6cedN56tJH6iKtkHx3WnfTtuy7Ota/YKj+K8Jl2ZL+pK/XLX8CxEdgJnjabdL5101lGMbx832ToZ
k004DSIL37ee5n762EfZzeFJWi0ISK0oBEJZI0SAnNIyll9df0L5Wc7+sne62zrh+ec3/2fq5190Z6Z5
9//+ml3vmev8/86I0wwgVM4EImMonJTOEiLuYSLuUyLucKpjKNK5nOVVzNNVzLdVzPDcxgJjdyEzdzC7
OYzRxu5Tbmcjt3cCd3MY+7mc89jFKRyASFmoaWBdzLfSzkfhaxmCV09FnKgAcY40GW8RAPs5wVPMKjPM
ZKHucJVrGaJ3mKNaxlHU/zDM/yHM+zng1s5AVe5CU2sZmXeYUtvMprvM4bbGUb23mTHbzFTnbxNu/wLr
t5jz3s5X328QH7+ZADfMTHfMKnHOQzDvE5X3CYLznCUY7xFV/zDd/yHd/zAz/yEz/zC79ynBP8xkl+5w

9O8Sd/cXrSrq1bUhlb+n+OVaOjZmUmM5thFrM2G7M1F5zNpJf0kl7SS3pJL+klvaSXxr1Kr9Kr9Cq9Sq
/Sq/QqvUqv0kt6SS/pJb2kl4Ze9j7Z+2Tvk71P9j55dPz/tdmYw/eHTuiETuiETuiETujEOWd4j7CXsJ
ewl7CXsJewl7CXsJewl7CXsJewl7CXsJewl7CXSHpJL+klvayX9bJe1st6WS/rZb2sl/VCL/RCL/RCL/
RCL/RCL/SKXtErekWv6BW9olf0il7Rq/VqvVqv1qv1ar1ar9ar9Wq9Rq/Ra/QavUav0WuGXnHPintW3L
PinrWet563nrfnzsMsZm025vA7W/evdf9a9691/1r3r3X/Wvevdf/azvnO+c75zvnO+c75zvlufN7v6I
Z9tX29vl5fr6/X1+vr9fUGzg2cGzg3yP8BqrOmDgAAALgB/4WwAY0AS7AIUFixAQGOWbFGBitYIbAQWU
uwFFJYIbCAWR2wBitcWFmwFCsAAAABUyBBkgAA) format('woff'),
url(/i/noticons/noticons-regular-webfont.ttf) format('truetype'
),
url(/i/noticons/noticons-regular-webfont.svg#NoticonsRegular) f
ormat('svg');
font-weight: normal;
font-style: normal;
}
/**
* All noticons
*/
/*

Update, Sep 2nd:


Can't use [class*="noticon"] as that's too scattershot.
Can't use [class*="noticon"]:before either.
Icons inserted using the div will show up correctly,
but the div itself will still behave like a div. */
.noticon {
display: inline-block;
width: 16px;
height: 16px;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-size: 16px;
line-height: 1;
font-family: 'Noticons';
text-decoration: inherit;
font-weight: normal;
font-style: normal;
vertical-align: top;
-moz-transition: color .1s ease-in 0;
-webkit-transition: color .1s ease-in 0;
text-align: center;
}
/**
* Individual icons
*/
/* Post formats */
.noticon-standard:before {
.noticon-aside:before {
.noticon-image:before {
.noticon-gallery:before {
.noticon-video:before {
.noticon-status:before {
.noticon-quote:before {
.noticon-link:before {
.noticon-chat:before {
.noticon-audio:before {

content:
content:
content:
content:
content:
content:
content:
content:
content:
content:

'\f100';
'\f101';
'\f102';
'\f103';
'\f104';
'\f105';
'\f106';
'\f107';
'\f108';
'\f109';

}
}
}
}
}
}
}
}
}
}

/* Social icons */
.noticon-github:before {
.noticon-dribbble:before {
.noticon-twitter:before {
.noticon-facebook:before {
.noticon-facebook-alt:before {
.noticon-wordpress:before {
.noticon-googleplus:before {
.noticon-linkedin:before {
.noticon-linkedin-alt:before {
.noticon-pinterest:before {
.noticon-pinterest-alt:before {
.noticon-flickr:before {
.noticon-vimeo:before {
.noticon-youtube:before {
.noticon-tumblr:before {
.noticon-instagram:before {
.noticon-codepen:before {
.noticon-polldaddy:before {
.noticon-googleplus-alt:before {
.noticon-path:before {
.noticon-skype:before {
.noticon-digg:before {
.noticon-reddit:before {
.noticon-stumbleupon:before {
.noticon-pocket:before {
.noticon-dropbox:before {

content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:

'\f200';
'\f201';
'\f202';
'\f203';
'\f204';
'\f205';
'\f206';
'\f207';
'\f208';
'\f209';
'\f210';
'\f211';
'\f212';
'\f213';
'\f214';
'\f215';
'\f216';
'\f217';
'\f218';
'\f219';
'\f220';
'\f221';
'\f222';
'\f223';
'\f224';
'\f225';

}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}

/* Meta icons */
.noticon-comment:before {
.noticon-category:before {
.noticon-tag:before {
.noticon-time:before {
.noticon-user:before {
.noticon-day:before {
.noticon-week:before {
.noticon-month:before {
.noticon-pinned:before {

content:
content:
content:
content:
content:
content:
content:
content:
content:

'\f300';
'\f301';
'\f302';
'\f303';
'\f304';
'\f305';
'\f306';
'\f307';
'\f308';

}
}
}
}
}
}
}
}
}

/* Other icons */
.noticon-search:before {
.noticon-unzoom:before {
.noticon-zoom:before {
.noticon-show:before {
.noticon-hide:before {
.noticon-close:before {
.noticon-close-alt:before {
.noticon-trash:before {
.noticon-star:before {
.noticon-home:before {
.noticon-mail:before {
.noticon-edit:before {
.noticon-reply:before {
.noticon-feed:before {
.noticon-warning:before {
.noticon-share:before {
.noticon-attachment:before {
.noticon-location:before {
.noticon-checkmark:before {

content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:

'\f400';
'\f401';
'\f402';
'\f403';
'\f404';
'\f405';
'\f406';
'\f407';
'\f408';
'\f409';
'\f410';
'\f411';
'\f412';
'\f413';
'\f414';
'\f415';
'\f416';
'\f417';
'\f418';

}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}

.noticon-menu:before {
.noticon-refresh:before {
.noticon-minimize:before {
.noticon-maximize:before {
.noticon-404:before {
.noticon-spam:before {
.noticon-summary:before {
.noticon-cloud:before {
.noticon-key:before {
.noticon-dot:before {
.noticon-next:before {
.noticon-previous:before {
.noticon-expand:before {
.noticon-collapse:before {
.noticon-dropdown:before {
.noticon-dropdown-left:before {
.noticon-top:before {
.noticon-draggable:before {
.noticon-phone:before {
.noticon-send-to-phone:before {
.noticon-plugin:before {
.noticon-cloud-download:before {
.noticon-cloud-upload:before {
.noticon-external:before {
.noticon-document:before {
.noticon-book:before {
.noticon-cog:before {
.noticon-unapprove:before {
.noticon-cart:before {
.noticon-pause:before {
.noticon-stop:before {
.noticon-skip-back:before {
.noticon-skip-ahead:before {
.noticon-play:before {
.noticon-tablet:before {
.noticon-send-to-tablet:before {
.noticon-info:before {
.noticon-notice:before {
.noticon-help:before {
.noticon-fastforward:before {
.noticon-rewind:before {
.noticon-portfolio:before {
.noticon-heart:before {
.noticon-code:before {
.noticon-subscribe:before {
.noticon-unsubscribe:before {
.noticon-subscribed:before {
.noticon-reply-alt:before {
.noticon-reply-single:before {
.noticon-flag:before {
.noticon-print:before {
.noticon-lock:before {
.noticon-bold:before {
.noticon-italic:before {
.noticon-picture:before {
.noticon-fullscreen:before {
.noticon-website:before {
.noticon-ellipsis:before {
/* Generic shapes */

content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:

'\f419';
'\f420';
'\f421';
'\f422';
'\f423';
'\f424';
'\f425';
'\f426';
'\f427';
'\f428';
'\f429';
'\f430';
'\f431';
'\f432';
'\f433';
'\f434';
'\f435';
'\f436';
'\f437';
'\f438';
'\f439';
'\f440';
'\f441';
'\f442';
'\f443';
'\f444';
'\f445';
'\f446';
'\f447';
'\f448';
'\f449';
'\f450';
'\f451';
'\f452';
'\f453';
'\f454';
'\f455';
'\f456';
'\f457';
'\f458';
'\f459';
'\f460';
'\f461';
'\f462';
'\f463';
'\f464';
'\f465';
'\f466';
'\f467';
'\f468';
'\f469';
'\f470';
'\f471';
'\f472';
'\f473';
'\f474';
'\f475';
'\f476';

}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}

.noticon-uparrow:before {
.noticon-rightarrow:before {
.noticon-downarrow:before {
.noticon-leftarrow:before {

content:
content:
content:
content:

'\f500';
'\f501';
'\f502';
'\f503';

}
}
}
}

/* WPCOM specific */
.noticon-notification:before {
.noticon-follow:before {
.noticon-unfollow:before {
.noticon-following:before {
.noticon-trophy:before {
.noticon-reblog:before {
.noticon-milestone:before {
.noticon-compact:before {
.noticon-gridview:before {

content:
content:
content:
content:
content:
content:
content:
content:
content:

'\f800';
'\f801';
'\f802';
'\f803';
'\f804';
'\f805';
'\f806';
'\f807';
'\f808';

}
}
}
}
}
}
}
}
}

.noticon-trapper:before {
.noticon-spike:before {
.noticon-promoted:before {
.noticon-wordads:before {
.noticon-atsign:before {
.noticon-automattic:before {
.noticon-automattic-ring:before {
.noticon-automattic-blip:before {
.noticon-bullseye:before {
.noticon-lightbulb:before {
.noticon-reader:before {
.noticon-reader-alt:before {
.noticon-gift:before {
.noticon-bullhorn:before {
.noticon-eventbrite:before {
.noticon-colors:before {
.noticon-features:before {
.noticon-layouts:before {
.noticon-price:before {
.noticon-types:before {
.noticon-localization:before {
.noticon-add:before {
.noticon-art:before {
.noticon-fonts:before {
.noticon-title:before {
.noticon-gravatar:before {
.noticon-vaultpress:before {
.noticon-akismet:before {
.noticon-jetpack:before {

content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:
content:

'\f810';
'\f811';
'\f812';
'\f813';
'\f814';
'\f815';
'\f816';
'\f817';
'\f8a0';
'\f8a1';
'\f8a2';
'\f8a3';
'\f8a4';
'\f8a5';
'\f8a6';
'\f8a7';
'\f8a8';
'\f8a9';
'\f8b0';
'\f8b1';
'\f8b2';
'\f8b3';
'\f8b4';
'\f8b5';
'\f8b6';
'\f8d0';
'\f8d1';
'\f8d2';
'\f8d3';

}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}

/**
* Deprecated noticon names
*/
.noticon-text:before {
.noticon-like:before {
.noticon-stats:before {
.noticon-wp:before {
.noticon-wpcom:before {
.noticon-alert:before {
.noticon-up:before {
.noticon-right:before {
.noticon-down:before {
.noticon-left:before {

content:
content:
content:
content:
content:
content:
content:
content:
content:
content:

'\f100';
'\f408';
'\f806';
'\f205';
'\f205';
'\f414';
'\f500';
'\f501';
'\f502';
'\f503';

}
}
}
}
}
}
}
}
}
}

.noticon-close-small:before {
.noticon-arrow-right:before {
.noticon-arrow-left:before {
.noticon-camera:before {
.noticon-gplus:before {
.noticon-eye:before {
.noticon-eye-cross:before {


content:
content:
content:
content:
content:
content:
content:

'\f405';
'\f429';
'\f430';
'\f102';
'\f206';
'\f403';
'\f404';

}
}
}
}
}
}
}

m ](c Nh-bW8MX T |Uck<t"xd '|`j} < DGVc]f "2^a#xx@


Z] :WKI Gh a & T 'MrMW e  5 (,nc
gK I4}.=J| 2> vj
>=Rkm%2^&S
AE<{U%lh1y;
9u=Z
Q wF }
a4Q
W ujs
 sO[
# E,
21 [4 [XvF
W #Z\h
c  ZJt:n
? Uuj67
k~MI
U
:F
Zi
tu
3
{b<5y=g
[ h xE 0kh:
HZnToY'
*5f,HN

"?,XD:jvr\hE
~ V%c m=N
9R*Y
U]?Si
* 2O
W4>Vk8[
\SsJ9gs[KFT
?M{
,t)e
;fLd
Tr
+ ]@m3s X!qsc'+r[cGgg&ukK /*
* 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.
*/
package android.support.v4.view;
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import

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

import java.lang.reflect.Method;
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 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>
*
* <p>ViewPager is most often used in conjunction with {@link android.app.Fragme
nt},
* which is a convenient way to supply and manage the lifecycle of each page.
* There are standard adapters implemented for using fragments with the ViewPage
r,
* which cover the most common use cases. These are
* {@link android.support.v4.app.FragmentPagerAdapter} and
* {@link android.support.v4.app.FragmentStatePagerAdapter}; each of these
* classes have simple code showing how to build a full user interface
* with them.
*
* <p>Here is a more complicated example of ViewPager, using it in conjuction
* with {@link android.app.ActionBar} tabs. You can find other examples of usin
g
* ViewPager in the API 4+ Support Demos and API 13+ Support Demos sample code.
*
* {@sample development/samples/Support13Demos/src/com/example/android/supportv1
3/app/ActionBarTabsPager.java
*
complete}
*/
public class ViewPager extends ViewGroup {
private static final String TAG = "ViewPager";
private static final boolean DEBUG = false;
private static final boolean USE_CACHE = false;
private static final int DEFAULT_OFFSCREEN_PAGES = 1;
private static final int MAX_SETTLE_DURATION = 600; // ms
private static final int MIN_DISTANCE_FOR_FLING = 25; // dips
private static final int DEFAULT_GUTTER_SIZE = 16; // dips
private static final int MIN_FLING_VELOCITY = 400; // dips
private static final int[] LAYOUT_ATTRS = new int[] {
android.R.attr.layout_gravity
};
/**
* Used to track what the expected number of items in the adapter should be.
* If the app changes this when we don't expect it, we'll throw a big obnoxi
ous exception.
*/
private int mExpectedAdapterCount;
static class ItemInfo {
Object object;
int position;

boolean scrolling;
float widthFactor;
float offset;

private static final Comparator<ItemInfo> COMPARATOR = new Comparator<ItemIn


fo>(){
@Override
public int compare(ItemInfo lhs, ItemInfo rhs) {
return lhs.position - rhs.position;
}
};
private static final Interpolator sInterpolator = new Interpolator() {
public float getInterpolation(float t) {
t -= 1.0f;
return t * t * t * t * t + 1.0f;
}
};
private final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>();
private final ItemInfo mTempItem = new ItemInfo();
private final Rect mTempRect = new Rect();
private
private
private
private
private
private
private

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

private
private
private
private

int mPageMargin;
Drawable mMarginDrawable;
int mTopPageBounds;
int mBottomPageBounds;

// Offsets of the first and last items, if known.


// Set during population, used to determine if we are at the beginning
// or end of the pager data set during touch scrolling.
private float mFirstOffset = -Float.MAX_VALUE;
private float mLastOffset = Float.MAX_VALUE;
private int mChildWidthMeasureSpec;
private int mChildHeightMeasureSpec;
private boolean mInLayout;
private boolean mScrollingCacheEnabled;
private boolean mPopulatePending;
private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES;
private
private
private
private
private
private
/**

boolean mIsBeingDragged;
boolean mIsUnableToDrag;
boolean mIgnoreGutter;
int mDefaultGutterSize;
int mGutterSize;
int mTouchSlop;

* Position of the last motion event.


*/
private float mLastMotionX;
private float mLastMotionY;
private float mInitialMotionX;
private float mInitialMotionY;
/**
* ID of the active pointer. This is used to retain consistency during
* drags/flings if multiple pointers are used.
*/
private int mActivePointerId = INVALID_POINTER;
/**
* Sentinel value for no current active pointer.
* Used by {@link #mActivePointerId}.
*/
private static final int INVALID_POINTER = -1;
/**
* Determines speed during touch scrolling
*/
private VelocityTracker mVelocityTracker;
private int mMinimumVelocity;
private int mMaximumVelocity;
private int mFlingDistance;
private int mCloseEnough;
// If the pager is at least this close to its final position, complete the s
croll
// on touch down and let the user interact with the content inside instead o
f
// "catching" the flinging pager.
private static final int CLOSE_ENOUGH = 2; // dp
private boolean mFakeDragging;
private long mFakeDragBeginTime;
private EdgeEffectCompat mLeftEdge;
private EdgeEffectCompat mRightEdge;
private
private
private
private

boolean mFirstLayout = true;


boolean mNeedCalculatePageOffsets = false;
boolean mCalledSuper;
int mDecorChildCount;

private
private
private
private
private

OnPageChangeListener mOnPageChangeListener;
OnPageChangeListener mInternalPageChangeListener;
OnAdapterChangeListener mAdapterChangeListener;
PageTransformer mPageTransformer;
Method mSetChildrenDrawingOrderEnabled;

private static final int DRAW_ORDER_DEFAULT = 0;


private static final int DRAW_ORDER_FORWARD = 1;
private static final int DRAW_ORDER_REVERSE = 2;
private int mDrawingOrder;
private ArrayList<View> mDrawingOrderedChildren;
private static final ViewPositionComparator sPositionComparator = new ViewPo
sitionComparator();
/**
* 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;

n.

/**
* Indicates that the pager is in the process of settling to a final positio
*/
public static final int SCROLL_STATE_SETTLING = 2;
private final Runnable mEndScrollRunnable = new Runnable() {
public void run() {
setScrollState(SCROLL_STATE_IDLE);
populate();
}
};
private int mScrollState = SCROLL_STATE_IDLE;
/**
* 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 tou


ch scroll.
*
* @param position Position index of the first page currently being disp
layed.
*
Page position+1 will be visible if positionOffset is
nonzero.
* @param positionOffset Value from [0, 1) indicating the offset from th
e page at position.
* @param positionOffsetPixels Value in pixels indicating the offset fro
m position.
*/
public void onPageScrolled(int position, float positionOffset, int posit
ionOffsetPixels);
/**
* This method will be invoked when a new page becomes selected. Animati
on is not
* necessarily complete.
*
* @param position Position index of the new selected page.
*/
public void onPageSelected(int position);

user

/**
* Called when the scroll state changes. Useful for discovering when the
* begins dragging, when the pager is automatically settling to the curr

ent page,

* or when it is fully stopped/idle.


*
* @param state The new scroll state.
* @see ViewPager#SCROLL_STATE_IDLE
* @see ViewPager#SCROLL_STATE_DRAGGING
* @see ViewPager#SCROLL_STATE_SETTLING
*/
public void onPageScrollStateChanged(int state);

/**
* Simple implementation of the {@link OnPageChangeListener} interface with

stub

* implementations of each method. Extend this if you do not intend to overr

ide

* every method of {@link OnPageChangeListener}.


*/
public static class SimpleOnPageChangeListener implements OnPageChangeListen

er {

@Override
public void onPageScrolled(int position, float positionOffset, int posit
ionOffsetPixels) {
// This space for rent
}
@Override
public void onPageSelected(int position) {
// This space for rent
}

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

/**
* A PageTransformer is invoked whenever a visible/attached page is scrolled

* This offers an opportunity for the application to apply a custom transfor


mation
* to the page views using animation properties.
*
* <p>As property animation is only supported as of Android 3.0 and forward,
* setting a PageTransformer on a ViewPager on earlier platform versions wil
l
* be ignored.</p>
*/
public interface PageTransformer {
/**
* Apply a property transformation to the given page.
*
* @param page Apply the transformation to this page
* @param position Position of page relative to the current front-and-ce
nter
*
position of the pager. 0 is front and center. 1 is on
e full
*
page position to the right, and -1 is one page positi
on to the left.

*/
public void transformPage(View page, float position);

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

as

/**
* Used internally to tag special types of child views that should be added
* pager decorations by default.
*/
interface Decor {}
public ViewPager(Context context) {
super(context);
initViewPager();
}
public ViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
initViewPager();
}

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

ion);

mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configurat
mMinimumVelocity = (int) (MIN_FLING_VELOCITY * density);
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
mLeftEdge = new EdgeEffectCompat(context);
mRightEdge = new EdgeEffectCompat(context);
mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density);
mCloseEnough = (int) (CLOSE_ENOUGH * density);
mDefaultGutterSize = (int) (DEFAULT_GUTTER_SIZE * density);
ViewCompat.setAccessibilityDelegate(this, new MyAccessibilityDelegate())

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

@Override
protected void onDetachedFromWindow() {
removeCallbacks(mEndScrollRunnable);
super.onDetachedFromWindow();
}
private void setScrollState(int newState) {
if (mScrollState == newState) {
return;
}
mScrollState = newState;
if (mPageTransformer != null) {
// PageTransformers can do complex things that benefit from hardware

layers.

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

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

r);

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

mRestoredAdapterState = null;
mRestoredClassLoader = null;
} else if (!wasFirstLayout) {
populate();
} else {
requestLayout();
}

if (mAdapterChangeListener != null && oldAdapter != adapter) {


mAdapterChangeListener.onAdapterChanged(oldAdapter, adapter);
}

private void removeNonDecorViews() {


for (int i = 0; i < getChildCount(); i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (!lp.isDecor) {
removeViewAt(i);
i--;
}
}
}
/**
* Retrieve the current adapter supplying pages.
*
* @return The currently registered PagerAdapter
*/
public PagerAdapter getAdapter() {
return mAdapter;
}
void setOnAdapterChangeListener(OnAdapterChangeListener listener) {
mAdapterChangeListener = listener;
}
private int getClientWidth() {
return getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
}
/**
* Set the currently selected page. If the ViewPager has already been throug
h its first
* layout with its current adapter there will be a smooth animated transitio
n 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 tra
nsition immediately
*/
public void setCurrentItem(int item, boolean smoothScroll) {
mPopulatePending = false;
setCurrentItemInternal(item, smoothScroll, false);
}
public int getCurrentItem() {
return mCurItem;
}
{

void setCurrentItemInternal(int item, boolean smoothScroll, boolean always)


}

setCurrentItemInternal(item, smoothScroll, always, 0);

void setCurrentItemInternal(int item, boolean smoothScroll, boolean always,


int velocity) {
if (mAdapter == null || mAdapter.getCount() <= 0) {
setScrollingCacheEnabled(false);
return;
}
if (!always && mCurItem == item && mItems.size() != 0) {
setScrollingCacheEnabled(false);
return;
}
if (item < 0) {
item = 0;
} else if (item >= mAdapter.getCount()) {
item = mAdapter.getCount() - 1;
}
final int pageLimit = mOffscreenPageLimit;
if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) {
// We are doing a jump by more than one page. To avoid
// glitches, we want to keep all current pages in the view
// until the scroll ends.
for (int i=0; i<mItems.size(); i++) {
mItems.get(i).scrolling = true;
}
}
final boolean dispatchSelected = mCurItem != item;
if (mFirstLayout) {
// We don't have any idea how big we are yet and shouldn't have any
pages either.
// Just set things up and let the pending layout handle things.
mCurItem = item;
if (dispatchSelected && mOnPageChangeListener != null) {
mOnPageChangeListener.onPageSelected(item);
}
if (dispatchSelected && mInternalPageChangeListener != null) {
mInternalPageChangeListener.onPageSelected(item);
}
requestLayout();
} else {
populate(item);
scrollToItem(item, smoothScroll, velocity, dispatchSelected);
}

}
private void scrollToItem(int item, boolean smoothScroll, int velocity,
boolean dispatchSelected) {
final ItemInfo curInfo = infoForPosition(item);
int destX = 0;
if (curInfo != null) {
final int width = getClientWidth();
destX = (int) (width * Math.max(mFirstOffset,
Math.min(curInfo.offset, mLastOffset)));
}
if (smoothScroll) {
smoothScrollTo(destX, 0, velocity);
if (dispatchSelected && mOnPageChangeListener != null) {
mOnPageChangeListener.onPageSelected(item);
}
if (dispatchSelected && mInternalPageChangeListener != null) {
mInternalPageChangeListener.onPageSelected(item);
}
} else {
if (dispatchSelected && mOnPageChangeListener != null) {
mOnPageChangeListener.onPageSelected(item);
}
if (dispatchSelected && mInternalPageChangeListener != null) {
mInternalPageChangeListener.onPageSelected(item);
}
completeScroll(false);
scrollTo(destX, 0);
pageScrolled(destX);
}
}
/**
* Set a listener that will be invoked whenever the page changes or is incre
mentally
* scrolled. See {@link OnPageChangeListener}.
*
* @param listener Listener to set
*/
public void setOnPageChangeListener(OnPageChangeListener listener) {
mOnPageChangeListener = listener;
}
/**
* Set a {@link PageTransformer} that will be called for each attached page
whenever
* the scroll position is changed. This allows the application to apply cust
om property
* transformations to each page, overriding the default sliding look and fee
l.
*
* <p><em>Note:</em> Prior to Android 3.0 the property animation APIs did no
t exist.
* As a result, setting a PageTransformer prior to Android 3.0 (API 11) will
have no effect.</p>
*
* @param reverseDrawingOrder true if the supplied PageTransformer requires
page views
*
to be drawn from last to first instead of firs
t to last.

* @param transformer PageTransformer that will modify each page's animation


properties
*/
public void setPageTransformer(boolean reverseDrawingOrder, PageTransformer
transformer) {
if (Build.VERSION.SDK_INT >= 11) {
final boolean hasTransformer = transformer != null;
final boolean needsPopulate = hasTransformer != (mPageTransformer !=
null);
mPageTransformer = transformer;
setChildrenDrawingOrderEnabledCompat(hasTransformer);
if (hasTransformer) {
mDrawingOrder = reverseDrawingOrder ? DRAW_ORDER_REVERSE : DRAW_
ORDER_FORWARD;
} else {
mDrawingOrder = DRAW_ORDER_DEFAULT;
}
if (needsPopulate) populate();
}
}
void setChildrenDrawingOrderEnabledCompat(boolean enable) {
if (Build.VERSION.SDK_INT >= 7) {
if (mSetChildrenDrawingOrderEnabled == null) {
try {
mSetChildrenDrawingOrderEnabled = ViewGroup.class.getDeclare
dMethod(
"setChildrenDrawingOrderEnabled", new Class[] { Bool
ean.TYPE });
} catch (NoSuchMethodException e) {
Log.e(TAG, "Can't find setChildrenDrawingOrderEnabled", e);
}
}
try {
mSetChildrenDrawingOrderEnabled.invoke(this, enable);
} catch (Exception e) {
Log.e(TAG, "Error changing children drawing order", e);
}
}
}
@Override
protected int getChildDrawingOrder(int childCount, int i) {
final int index = mDrawingOrder == DRAW_ORDER_REVERSE ? childCount - 1 i : i;
final int result = ((LayoutParams) mDrawingOrderedChildren.get(index).ge
tLayoutParams()).childIndex;
return result;
}

ry.

/**
* Set a separate OnPageChangeListener for internal use by the support libra

*
* @param listener Listener to set
* @return The old listener that was set, if any.
*/
OnPageChangeListener setInternalPageChangeListener(OnPageChangeListener list
ener) {
OnPageChangeListener oldListener = mInternalPageChangeListener;

mInternalPageChangeListener = listener;
return oldListener;

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

/**
* Set the number of pages that should be retained to either side of
* current page in the view hierarchy in an idle state. Pages beyond
* limit will be recreated from the adapter when needed.
*
* <p>This is offered as an optimization. If you know in advance the
* of pages you will need to support or have lazy-loading mechanisms

the
this
number
in plac

* on your pages, tweaking this setting can have benefits in perceived smoot

hness

* 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 f
or
* 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(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 d
istance that
// the screen has to travel, however, we don't want this duration to be effe
cted in a
// purely linear fashion. Instead, we use this method to moderate the effect
that the distance
// of travel has on the overall snap duration.
float distanceInfluenceForSnapDuration(float f) {

f -= 0.5f; // center the values about 0.


f *= 0.3f * Math.PI / 2.0f;
return (float) Math.sin(f);

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

gin);

int width = getClientWidth();


int halfWidth = width / 2;
float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / width);
float distance = halfWidth + halfWidth *
distanceInfluenceForSnapDuration(distanceRatio);

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

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


ViewCompat.postInvalidateOnAnimation(this);

ItemInfo addNewItem(int position, int index) {


ItemInfo ii = new ItemInfo();
ii.position = position;
ii.object = mAdapter.instantiateItem(this, position);
ii.widthFactor = mAdapter.getPageWidth(position);
if (index < 0 || index >= mItems.size()) {
mItems.add(ii);
} else {
mItems.add(index, ii);
}
return ii;
}
void dataSetChanged() {
// This method only gets called if our observer is attached, so mAdapter
is non-null.
final int adapterCount = mAdapter.getCount();
mExpectedAdapterCount = adapterCount;
boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1 &&
mItems.size() < adapterCount;
int newCurrItem = mCurItem;
boolean isUpdating = false;
for (int i = 0; i < mItems.size(); i++) {
final ItemInfo ii = mItems.get(i);
final int newPos = mAdapter.getItemPosition(ii.object);
if (newPos == PagerAdapter.POSITION_UNCHANGED) {
continue;
}
if (newPos == PagerAdapter.POSITION_NONE) {
mItems.remove(i);
i--;
if (!isUpdating) {
mAdapter.startUpdate(this);
isUpdating = true;
}
mAdapter.destroyItem(this, ii.position, ii.object);
needPopulate = true;
if (mCurItem == ii.position) {
// Keep the current item in the valid range
newCurrItem = Math.max(0, Math.min(mCurItem, adapterCount -

1));

needPopulate = true;
}
continue;

if (ii.position != newPos) {
if (ii.position == mCurItem) {

// Our current item changed position. Follow it.


newCurrItem = newPos;

ii.position = newPos;
needPopulate = true;

if (isUpdating) {
mAdapter.finishUpdate(this);
}
Collections.sort(mItems, COMPARATOR);
if (needPopulate) {
// Reset our known page widths; populate will recompute them.
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (!lp.isDecor) {
lp.widthFactor = 0.f;
}
}

setCurrentItemInternal(newCurrItem, false, true);


requestLayout();

void populate() {
populate(mCurItem);
}
void populate(int newCurrentItem) {
ItemInfo oldCurInfo = null;
int focusDirection = View.FOCUS_FORWARD;
if (mCurItem != newCurrentItem) {
focusDirection = mCurItem < newCurrentItem ? View.FOCUS_RIGHT : View
.FOCUS_LEFT;
oldCurInfo = infoForPosition(mCurItem);
mCurItem = newCurrentItem;
}
if (mAdapter == null) {
sortChildDrawingOrder();
return;
}
//
//
//
//
if

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.
(mPopulatePending) {
if (DEBUG) Log.i(TAG, "populate is pending, skipping for now...");
sortChildDrawingOrder();
return;

//
//
//
if
}

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.
(getWindowToken() == null) {
return;

mAdapter.startUpdate(this);
final
final
final
final

int
int
int
int

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

if (N != mExpectedAdapterCount) {
String resName;
try {
resName = getResources().getResourceName(getId());
} catch (Resources.NotFoundException e) {
resName = Integer.toHexString(getId());
}
throw new IllegalStateException("The application's PagerAdapter chan
ged the adapter's" +
" contents without calling PagerAdapter#notifyDataSetChanged
!" +
" Expected adapter item count: " + mExpectedAdapterCount + "
, found: " + N +
" Pager id: " + resName +
" Pager class: " + getClass() +
" Problematic adapter: " + mAdapter.getClass());
}
// Locate the currently focused item or add it if needed.
int curIndex = -1;
ItemInfo curItem = null;
for (curIndex = 0; curIndex < mItems.size(); curIndex++) {
final ItemInfo ii = mItems.get(curIndex);
if (ii.position >= mCurItem) {
if (ii.position == mCurItem) curItem = ii;
break;
}
}
if (curItem == null && N > 0) {
curItem = addNewItem(mCurItem, curIndex);
}
//
//
//
if

Fill 3x the available width or up to the number of offscreen


pages requested to either side, whichever is larger.
If we have no current item we have no work to do.
(curItem != null) {
float extraWidthLeft = 0.f;
int itemIndex = curIndex - 1;
ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
final int clientWidth = getClientWidth();
final float leftWidthNeeded = clientWidth <= 0 ? 0 :
2.f - curItem.widthFactor + (float) getPaddingLeft() / (floa
t) clientWidth;
for (int pos = mCurItem - 1; pos >= 0; pos--) {
if (extraWidthLeft >= leftWidthNeeded && pos < startPos) {

if (ii == null) {
break;
}
if (pos == ii.position && !ii.scrolling) {
mItems.remove(itemIndex);
mAdapter.destroyItem(this, pos, ii.object);
if (DEBUG) {
Log.i(TAG, "populate() - destroyItem() with pos: " +

pos +

" view: " + ((View) ii.object));


}
itemIndex--;
curIndex--;
ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;

" + pos +

x) : null;

null;

null;

}
} else if (ii != null && pos == ii.position) {
extraWidthLeft += ii.widthFactor;
itemIndex--;
ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
} else {
ii = addNewItem(pos, itemIndex + 1);
extraWidthLeft += ii.widthFactor;
curIndex++;
ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
}

float extraWidthRight = curItem.widthFactor;


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

}
}

calculatePageOffsets(curItem, curIndex, oldCurInfo);

if (DEBUG) {
Log.i(TAG, "Current page list:");
for (int i=0; i<mItems.size(); i++) {
Log.i(TAG, "#" + i + ": page " + mItems.get(i).position);
}
}
mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object
: null);
mAdapter.finishUpdate(this);

width.

// Check width measurement of current pages and drawing sort order.


// Update LayoutParams as needed.
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
lp.childIndex = i;
if (!lp.isDecor && lp.widthFactor == 0.f) {
// 0 means requery the adapter for this, it doesn't have a valid
final ItemInfo ii = infoForChild(child);
if (ii != null) {
lp.widthFactor = ii.widthFactor;
lp.position = ii.position;
}

}
}
sortChildDrawingOrder();

if (hasFocus()) {
View currentFocused = findFocus();
ItemInfo ii = currentFocused != null ? infoForAnyChild(currentFocuse
d) : null;
if (ii == null || ii.position != mCurItem) {
for (int i=0; i<getChildCount(); i++) {
View child = getChildAt(i);
ii = infoForChild(child);
if (ii != null && ii.position == mCurItem) {
if (child.requestFocus(focusDirection)) {
break;
}
}
}
}
}
}
private void sortChildDrawingOrder() {
if (mDrawingOrder != DRAW_ORDER_DEFAULT) {
if (mDrawingOrderedChildren == null) {
mDrawingOrderedChildren = new ArrayList<View>();

} else {
mDrawingOrderedChildren.clear();
}
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
mDrawingOrderedChildren.add(child);
}
Collections.sort(mDrawingOrderedChildren, sPositionComparator);

private void calculatePageOffsets(ItemInfo curItem, int curIndex, ItemInfo o


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

offset -= ii.widthFactor + marginOffset;


ii.offset = offset;

// Base all offsets off of curItem.


final int itemCount = mItems.size();
float offset = curItem.offset;
int pos = curItem.position - 1;
mFirstOffset = curItem.position == 0 ? curItem.offset : -Float.MAX_VALUE

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

mNeedCalculatePageOffsets = false;

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

out.writeInt(position);
out.writeParcelable(adapterState, flags);

@Override
public String toString() {
return "FragmentPager.SavedState{"
+ Integer.toHexString(System.identityHashCode(this))
+ " position=" + position + "}";
}
public static final Parcelable.Creator<SavedState> CREATOR
= ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbac
ks<SavedState>() {
@Override
public SavedState createFromParcel(Parcel in, ClassLoader lo
ader) {
return new SavedState(in, loader);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
});

SavedState(Parcel in, ClassLoader loader) {


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

@Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.position = mCurItem;
if (mAdapter != null) {
ss.adapterState = mAdapter.saveState();
}
return ss;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
if (!(state instanceof SavedState)) {
super.onRestoreInstanceState(state);
return;
}
SavedState ss = (SavedState)state;
super.onRestoreInstanceState(ss.getSuperState());
if (mAdapter != null) {
mAdapter.restoreState(ss.adapterState, ss.loader);
setCurrentItemInternal(ss.position, false, true);

} else {
mRestoredCurItem = ss.position;
mRestoredAdapterState = ss.adapterState;
mRestoredClassLoader = ss.loader;
}

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

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

@Override
public void removeView(View view) {
if (mInLayout) {
removeViewInLayout(view);
} else {
super.removeView(view);
}
}
ItemInfo infoForChild(View child) {
for (int i=0; i<mItems.size(); i++) {
ItemInfo ii = mItems.get(i);
if (mAdapter.isViewFromObject(child, ii.object)) {
return ii;
}
}
return null;
}
ItemInfo infoForAnyChild(View child) {
ViewParent parent;
while ((parent=child.getParent()) != this) {
if (parent == null || !(parent instanceof View)) {
return null;
}
child = (View)parent;

}
return infoForChild(child);

ItemInfo infoForPosition(int position) {


for (int i = 0; i < mItems.size(); i++) {
ItemInfo ii = mItems.get(i);
if (ii.position == position) {
return ii;
}
}
return null;
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mFirstLayout = true;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// For simple implementation, our internal size is always 0.
// We depend on the container to specify the layout size of
// our view. We can't really know what it is since we will be
// adding and removing different arbitrary views and do not
// want the layout to change as this happens.
setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
getDefaultSize(0, heightMeasureSpec));
final int measuredWidth = getMeasuredWidth();
final int maxGutterSize = measuredWidth / 10;
mGutterSize = Math.min(maxGutterSize, mDefaultGutterSize);

);

// Children are just made to fill our space.


int childWidthSize = measuredWidth - getPaddingLeft() - getPaddingRight(

int childHeightSize = getMeasuredHeight() - getPaddingTop() - getPadding


Bottom();

SK;

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

ravity.BOTTOM;

final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK


int widthMode = MeasureSpec.AT_MOST;
int heightMode = MeasureSpec.AT_MOST;
boolean consumeVertical = vgrav == Gravity.TOP || vgrav == G

boolean consumeHorizontal = hgrav == Gravity.LEFT || hgrav =

= Gravity.RIGHT;

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

widthMode);

final int heightSpec = MeasureSpec.makeMeasureSpec(heightSiz

e, heightMode);

child.measure(widthSpec, heightSpec);

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

mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthSize, Mea


sureSpec.EXACTLY);
mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeightSize, M
easureSpec.EXACTLY);
// Make sure we have created all fragments that we need to have shown.
mInLayout = true;
populate();
mInLayout = false;
// Page views next.
size = getChildCount();
for (int i = 0; i < size; ++i) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
if (DEBUG) Log.v(TAG, "Measuring #" + i + " " + child
+ ": " + mChildWidthMeasureSpec);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp == null || !lp.isDecor) {
final int widthSpec = MeasureSpec.makeMeasureSpec(
(int) (childWidthSize * lp.widthFactor), MeasureSpec

.EXACTLY);

child.measure(widthSpec, 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, in


t oldMargin) {
if (oldWidth > 0 && !mItems.isEmpty()) {
final int widthWithMargin = width - getPaddingLeft() - getPaddingRig
ht() + margin;
final int oldWidthWithMargin = oldWidth - getPaddingLeft() - getPadd
ingRight()
+ oldMargin;
final int xpos = getScrollX();
final float pageOffset = (float) xpos / oldWidthWithMargin;
final int newOffsetPixels = (int) (pageOffset * widthWithMargin);

progress.
Passed();

scrollTo(newOffsetPixels, getScrollY());
if (!mScroller.isFinished()) {
// We now return to your regularly scheduled scroll, already in
final int newDuration = mScroller.getDuration() - mScroller.time
ItemInfo targetInfo = infoForPosition(mCurItem);
mScroller.startScroll(newOffsetPixels, 0,
(int) (targetInfo.offset * width), 0, newDuration);

}
} else {
final ItemInfo ii = infoForPosition(mCurItem);
final float scrollOffset = ii != null ? Math.min(ii.offset, mLastOff
set) : 0;
final int scrollPos = (int) (scrollOffset *
(width - getPaddingLeft() - getPaddingR
ight()));
if (scrollPos != getScrollX()) {
completeScroll(false);
scrollTo(scrollPos, getScrollY());
}
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int count = getChildCount();
int width = r - l;
int height = b - t;
int paddingLeft = getPaddingLeft();
int paddingTop = getPaddingTop();

int paddingRight = getPaddingRight();


int paddingBottom = getPaddingBottom();
final int scrollX = getScrollX();
int decorCount = 0;

SK;

// First pass - decor views. We need to do this in two passes so that


// we have the proper offsets for non-decor views later.
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
int childLeft = 0;
int childTop = 0;
if (lp.isDecor) {
final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MA

()) / 2,

Width();

t()) / 2,

dHeight();

final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK


switch (hgrav) {
default:
childLeft = paddingLeft;
break;
case Gravity.LEFT:
childLeft = paddingLeft;
paddingLeft += child.getMeasuredWidth();
break;
case Gravity.CENTER_HORIZONTAL:
childLeft = Math.max((width - child.getMeasuredWidth
paddingLeft);
break;
case Gravity.RIGHT:
childLeft = width - paddingRight - child.getMeasured
paddingRight += child.getMeasuredWidth();
break;

}
switch (vgrav) {
default:
childTop = paddingTop;
break;
case Gravity.TOP:
childTop = paddingTop;
paddingTop += child.getMeasuredHeight();
break;
case Gravity.CENTER_VERTICAL:
childTop = Math.max((height - child.getMeasuredHeigh
paddingTop);
break;
case Gravity.BOTTOM:
childTop = height - paddingBottom - child.getMeasure
paddingBottom += child.getMeasuredHeight();
break;

}
childLeft += scrollX;
child.layout(childLeft, childTop,
childLeft + child.getMeasuredWidth(),

childTop + child.getMeasuredHeight());
decorCount++;

final int childWidth = width - paddingLeft - paddingRight;


// Page views. Do this once we have the right padding offsets from above

for (int i = 0; i < count; i++) {


final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
ItemInfo ii;
if (!lp.isDecor && (ii = infoForChild(child)) != null) {
int loff = (int) (childWidth * ii.offset);
int childLeft = paddingLeft + loff;
int childTop = paddingTop;
if (lp.needsMeasure) {
// This was added during layout and needs measurement.
// Do it now that we know what we're working with.
lp.needsMeasure = false;
final int widthSpec = MeasureSpec.makeMeasureSpec(
(int) (childWidth * lp.widthFactor),
MeasureSpec.EXACTLY);
final int heightSpec = MeasureSpec.makeMeasureSpec(
(int) (height - paddingTop - paddingBottom),
MeasureSpec.EXACTLY);
child.measure(widthSpec, heightSpec);
}
if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + "
f=" + ii.object
+ ":" + childLeft + "," + childTop + " " + child.get
MeasuredWidth()
+ "x" + child.getMeasuredHeight());
child.layout(childLeft, childTop,
childLeft + child.getMeasuredWidth(),
childTop + child.getMeasuredHeight());
}
}
}
mTopPageBounds = paddingTop;
mBottomPageBounds = height - paddingBottom;
mDecorChildCount = decorCount;

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

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

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

// Keep on drawing until the animation has finished.


ViewCompat.postInvalidateOnAnimation(this);
return;

// Done with scroll, clean up state.


completeScroll(true);

private boolean pageScrolled(int xpos) {


if (mItems.size() == 0) {
mCalledSuper = false;
onPageScrolled(0, 0, 0);
if (!mCalledSuper) {
throw new IllegalStateException(
"onPageScrolled did not call superclass implementation")
}
return false;

}
final
final
final
final
final
final

ItemInfo ii = infoForCurrentScrollPosition();
int width = getClientWidth();
int widthWithMargin = width + mPageMargin;
float marginOffset = (float) mPageMargin / width;
int currentPage = ii.position;
float pageOffset = (((float) xpos / width) - ii.offset) /
(ii.widthFactor + marginOffset);
final int offsetPixels = (int) (pageOffset * widthWithMargin);

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

/**
* This method will be invoked when the current page is scrolled, either as

part

* of a programmatically initiated smooth scroll or a user initiated touch s


croll.
* If you override this method you must call through to the superclass imple
mentation
* (e.g. super.onPageScrolled(position, offset, offsetPixels)) before onPage
Scrolled
* returns.
*
* @param position Position index of the first page currently being displaye
d.
*
Page position+1 will be visible if positionOffset is nonz
ero.

* @param offset Value from [0, 1) indicating the offset from the page at po
sition.
* @param offsetPixels Value in pixels indicating the offset from position.
*/
protected void onPageScrolled(int position, float offset, int offsetPixels)
{
// Offset any decor views if needed - keep them on-screen at all times.
if (mDecorChildCount > 0) {
final int scrollX = getScrollX();
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
final int width = getWidth();
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (!lp.isDecor) continue;
final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
int childLeft = 0;
switch (hgrav) {
default:
childLeft = paddingLeft;
break;
case Gravity.LEFT:
childLeft = paddingLeft;
paddingLeft += child.getWidth();
break;
case Gravity.CENTER_HORIZONTAL:
childLeft = Math.max((width - child.getMeasuredWidth())

/ 2,

paddingLeft);
break;
case Gravity.RIGHT:
childLeft = width - paddingRight - child.getMeasuredWidt

h();

paddingRight += child.getMeasuredWidth();
break;

}
childLeft += scrollX;

ixels);

final int childOffset = childLeft - child.getLeft();


if (childOffset != 0) {
child.offsetLeftAndRight(childOffset);
}

if (mOnPageChangeListener != null) {
mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels)
}
if (mInternalPageChangeListener != null) {
mInternalPageChangeListener.onPageScrolled(position, offset, offsetP
}
if (mPageTransformer != null) {
final int scrollX = getScrollX();
final int childCount = getChildCount();

for (int i = 0; i < childCount; i++) {


final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp.isDecor) continue;
final float transformPos = (float) (child.getLeft() - scrollX) /
getClientWidth();
mPageTransformer.transformPage(child, transformPos);
}
}
}

mCalledSuper = true;

private void completeScroll(boolean postEvents) {


boolean needPopulate = mScrollState == SCROLL_STATE_SETTLING;
if (needPopulate) {
// Done with scroll, no longer want to cache view drawing.
setScrollingCacheEnabled(false);
mScroller.abortAnimation();
int oldX = getScrollX();
int oldY = getScrollY();
int x = mScroller.getCurrX();
int y = mScroller.getCurrY();
if (oldX != x || oldY != y) {
scrollTo(x, y);
}
}
mPopulatePending = false;
for (int i=0; i<mItems.size(); i++) {
ItemInfo ii = mItems.get(i);
if (ii.scrolling) {
needPopulate = true;
ii.scrolling = false;
}
}
if (needPopulate) {
if (postEvents) {
ViewCompat.postOnAnimation(this, mEndScrollRunnable);
} else {
mEndScrollRunnable.run();
}
}
}
private boolean isGutterDrag(float x, float dx) {
return (x < mGutterSize && dx > 0) || (x > getWidth() - mGutterSize && d
x < 0);
}
private void enableLayers(boolean enable) {
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final int layerType = enable ?
ViewCompat.LAYER_TYPE_HARDWARE : ViewCompat.LAYER_TYPE_NONE;
ViewCompat.setLayerType(getChildAt(i), layerType, null);
}
}

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

UP) {

// Always take care of the touch gesture being complete.


if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_

// Release the drag.


if (DEBUG) Log.v(TAG, "Intercept done!");
mIsBeingDragged = false;
mIsUnableToDrag = false;
mActivePointerId = INVALID_POINTER;
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
return false;

// Nothing more to do here if we have decided whether or not we


// are dragging.
if (action != MotionEvent.ACTION_DOWN) {
if (mIsBeingDragged) {
if (DEBUG) Log.v(TAG, "Intercept returning true!");
return true;
}
if (mIsUnableToDrag) {
if (DEBUG) Log.v(TAG, "Intercept returning false!");
return false;
}
}
switch (action) {
case MotionEvent.ACTION_MOVE: {
/*
* mIsBeingDragged == false, otherwise the shortcut would have c
aught it. Check
* whether the user has moved far enough from his original down
touch.
*/

tent.

/*
* 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 con
}

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 - mInitialMotionY);
if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + x
Diff + "," + yDiff);
if (dx != 0 && !isGutterDrag(mLastMotionX, dx) &&
canScroll(this, false, (int) dx, (int) x, (int) y)) {
// Nested view has scrollable area under this point. Let it
be handled there.
mLastMotionX = x;
mLastMotionY = y;
mIsUnableToDrag = true;
return false;
}
if (xDiff > mTouchSlop && xDiff * 0.5f > yDiff) {
if (DEBUG) Log.v(TAG, "Starting drag!");
mIsBeingDragged = true;
setScrollState(SCROLL_STATE_DRAGGING);
mLastMotionX = dx > 0 ? mInitialMotionX + mTouchSlop :
mInitialMotionX - mTouchSlop;
mLastMotionY = y;
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;
}
if (mIsBeingDragged) {
// Scroll to follow the motion event
if (performDrag(x)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
break;
}
case MotionEvent.ACTION_DOWN: {
/*
* Remember location of down touch.
* ACTION_DOWN always refers to pointer index 0.
*/
mLastMotionX = mInitialMotionX = ev.getX();
mLastMotionY = mInitialMotionY = ev.getY();
mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
mIsUnableToDrag = false;

mCloseEnough) {

mScroller.computeScrollOffset();
if (mScrollState == SCROLL_STATE_SETTLING &&
Math.abs(mScroller.getFinalX() - mScroller.getCurrX()) >
// Let the user 'catch' the pager as it animates.
mScroller.abortAnimation();
mPopulatePending = false;
populate();

mIsBeingDragged = true;
setScrollState(SCROLL_STATE_DRAGGING);
} else {
completeScroll(false);
mIsBeingDragged = false;
}
if (DEBUG) Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMot

ionY

break;

+ " mIsBeingDragged=" + mIsBeingDragged


+ "mIsUnableToDrag=" + mIsUnableToDrag);

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

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

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

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

if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0)

// Don't handle edge touches immediately -- they may actually belong


to one of our
// descendants.
return false;
}
if (mAdapter == null || mAdapter.getCount() == 0) {
// Nothing to present or scroll; nothing to touch.
return false;
}
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);
final int action = ev.getAction();
boolean needsInvalidate = false;

switch (action & MotionEventCompat.ACTION_MASK) {


case MotionEvent.ACTION_DOWN: {
mScroller.abortAnimation();
mPopulatePending = false;
populate();
mIsBeingDragged = true;
setScrollState(SCROLL_STATE_DRAGGING);
// Remember where the motion event started
mLastMotionX = mInitialMotionX = ev.getX();
mLastMotionY = mInitialMotionY = ev.getY();
mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
break;

}
case MotionEvent.ACTION_MOVE:
if (!mIsBeingDragged) {
final int pointerIndex = MotionEventCompat.findPointerIndex(
ev, mActivePointerId);
final float 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 - mInitialMotionX > 0 ? mInitialMotionX
+ mTouchSlop :
mInitialMotionX - mTouchSlop;
mLastMotionY = y;
setScrollState(SCROLL_STATE_DRAGGING);
setScrollingCacheEnabled(true);
}
}
// Not else! Note that mIsBeingDragged can be set above.
if (mIsBeingDragged) {
// Scroll to follow the motion event
final int activePointerIndex = MotionEventCompat.findPointer
Index(
ev, mActivePointerId);
final float x = MotionEventCompat.getX(ev, activePointerInde
x);
needsInvalidate |= performDrag(x);
}
break;
case MotionEvent.ACTION_UP:
if (mIsBeingDragged) {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocit
y);
int initialVelocity = (int) VelocityTrackerCompat.getXVeloci
ty(
velocityTracker, mActivePointerId);
mPopulatePending = true;
final int width = getClientWidth();
final int scrollX = getScrollX();
final ItemInfo ii = infoForCurrentScrollPosition();
final int currentPage = ii.position;

final float pageOffset = (((float) scrollX / width) - ii.off


set) / ii.widthFactor;
final int activePointerIndex =
MotionEventCompat.findPointerIndex(ev, mActivePointe
rId);
final float x = MotionEventCompat.getX(ev, activePointerInde
x);
final int totalDelta = (int) (x - mInitialMotionX);
int nextPage = determineTargetPage(currentPage, pageOffset,
initialVelocity,
totalDelta);
setCurrentItemInternal(nextPage, true, true, initialVelocity
);

se();

se();

);

mActivePointerId = INVALID_POINTER;
endDrag();
needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelea
}
break;
case MotionEvent.ACTION_CANCEL:
if (mIsBeingDragged) {
scrollToItem(mCurItem, true, 0, false);
mActivePointerId = INVALID_POINTER;
endDrag();
needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelea
}
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) {
ViewCompat.postInvalidateOnAnimation(this);
}
return true;

private boolean performDrag(float x) {


boolean needsInvalidate = false;
final float deltaX = mLastMotionX - x;
mLastMotionX = x;
float oldScrollX = getScrollX();
float scrollX = oldScrollX + deltaX;
final int width = getClientWidth();
float leftBound = width * mFirstOffset;

float rightBound = width * mLastOffset;


boolean leftAbsolute = true;
boolean rightAbsolute = true;
final ItemInfo firstItem = mItems.get(0);
final ItemInfo lastItem = mItems.get(mItems.size() - 1);
if (firstItem.position != 0) {
leftAbsolute = false;
leftBound = firstItem.offset * width;
}
if (lastItem.position != mAdapter.getCount() - 1) {
rightAbsolute = false;
rightBound = lastItem.offset * width;
}
if (scrollX < leftBound) {
if (leftAbsolute) {
float over = leftBound - scrollX;
needsInvalidate = mLeftEdge.onPull(Math.abs(over) / width);
}
scrollX = leftBound;
} else if (scrollX > rightBound) {
if (rightAbsolute) {
float over = scrollX - rightBound;
needsInvalidate = mRightEdge.onPull(Math.abs(over) / width);
}
scrollX = rightBound;
}
// Don't lose the rounded component
mLastMotionX += scrollX - (int) scrollX;
scrollTo((int) scrollX, getScrollY());
pageScrolled((int) scrollX);
}

return needsInvalidate;

/**
* @return Info about the page at the current scroll position.
*
This can be synthetic for a missing middle page; the 'object' fie
ld can be null.
*/
private ItemInfo infoForCurrentScrollPosition() {
final int width = getClientWidth();
final float scrollOffset = width > 0 ? (float) getScrollX() / width : 0;
final float marginOffset = width > 0 ? (float) mPageMargin / width : 0;
int lastPos = -1;
float lastOffset = 0.f;
float lastWidth = 0.f;
boolean first = true;
ItemInfo lastItem = null;
for (int i = 0; i < mItems.size(); i++) {
ItemInfo ii = mItems.get(i);
float offset;
if (!first && ii.position != lastPos + 1) {
// Create a synthetic item for a missing page.
ii = mTempItem;
ii.offset = lastOffset + lastWidth + marginOffset;
ii.position = lastPos + 1;
ii.widthFactor = mAdapter.getPageWidth(ii.position);

i--;
}
offset = ii.offset;

}
}

final float leftBound = offset;


final float rightBound = offset + ii.widthFactor + marginOffset;
if (first || scrollOffset >= leftBound) {
if (scrollOffset < rightBound || i == mItems.size() - 1) {
return ii;
}
} else {
return lastItem;
}
first = false;
lastPos = ii.position;
lastOffset = offset;
lastWidth = ii.widthFactor;
lastItem = ii;

return lastItem;

private int determineTargetPage(int currentPage, float pageOffset, int veloc


ity, int deltaX) {
int targetPage;
if (Math.abs(deltaX) > mFlingDistance && Math.abs(velocity) > mMinimumVe
locity) {
targetPage = velocity > 0 ? currentPage : currentPage + 1;
} else {
final float truncator = currentPage >= mCurItem ? 0.4f : 0.6f;
targetPage = (int) (currentPage + pageOffset + truncator);
}
if (mItems.size() > 0) {
final ItemInfo firstItem = mItems.get(0);
final ItemInfo lastItem = mItems.get(mItems.size() - 1);
// Only let the user target pages we have items for
targetPage = Math.max(firstItem.position, Math.min(targetPage, lastI
tem.position));
}
}

return targetPage;

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

tom();

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() - getPaddingBot
final int width = getWidth();

canvas.rotate(270);
canvas.translate(-height + getPaddingTop(), mFirstOffset * width

);

mLeftEdge.setSize(height, width);
needsInvalidate |= mLeftEdge.draw(canvas);
canvas.restoreToCount(restoreCount);

tom();

}
if (!mRightEdge.isFinished()) {
final int restoreCount = canvas.save();
final int width = getWidth();
final int height = getHeight() - getPaddingTop() - getPaddingBot
canvas.rotate(90);
canvas.translate(-getPaddingTop(), -(mLastOffset + 1) * width);
mRightEdge.setSize(height, width);
needsInvalidate |= mRightEdge.draw(canvas);
canvas.restoreToCount(restoreCount);

}
} else {
mLeftEdge.finish();
mRightEdge.finish();
}

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

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Draw the margin drawable between pages if needed.
if (mPageMargin > 0 && mMarginDrawable != null && mItems.size() > 0 && m
Adapter != null) {
final int scrollX = getScrollX();
final int width = getWidth();
final float marginOffset = (float) mPageMargin / width;
int itemIndex = 0;
ItemInfo ii = mItems.get(0);
float offset = ii.offset;
final int itemCount = mItems.size();
final int firstPos = ii.position;
final int lastPos = mItems.get(itemCount - 1).position;
for (int pos = firstPos; pos < lastPos; pos++) {
while (pos > ii.position && itemIndex < itemCount) {
ii = mItems.get(++itemIndex);
}
float drawAt;
if (pos == ii.position) {
drawAt = (ii.offset + ii.widthFactor) * width;
offset = ii.offset + ii.widthFactor + marginOffset;
} else {
float widthFactor = mAdapter.getPageWidth(pos);
drawAt = (offset + widthFactor) * width;

offset += widthFactor + marginOffset;

if (drawAt + mPageMargin > scrollX) {


mMarginDrawable.setBounds((int) drawAt, mTopPageBounds,
(int) (drawAt + mPageMargin + 0.5f), mBottomPageBoun

ds);

mMarginDrawable.draw(canvas);

if (drawAt > scrollX + width) {


break; // No more visible, no sense in continuing
}

/**
* 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 ViewPag
er
* 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 re
al drag
* is already in progress, this method will return false.
*
* @return true if the fake drag began successfully, false if it could not b
e 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 begi
nFakeDrag first.");
}
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(
velocityTracker, mActivePointerId);
mPopulatePending = true;
final int width = getClientWidth();
final int scrollX = getScrollX();
final ItemInfo ii = infoForCurrentScrollPosition();
final int currentPage = ii.position;
final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.wi
dthFactor;
final int totalDelta = (int) (mLastMotionX - mInitialMotionX);
int nextPage = determineTargetPage(currentPage, pageOffset, initialVeloc
ity,
totalDelta);
setCurrentItemInternal(nextPage, true, true, initialVelocity);
endDrag();
}

mFakeDragging = false;

/**
* Fake drag by an offset in pixels. You must have called {@link #beginFakeD
rag()} 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 begi
nFakeDrag first.");
}
mLastMotionX += xOffset;
float oldScrollX = getScrollX();
float scrollX = oldScrollX - xOffset;
final int width = getClientWidth();
float leftBound = width * mFirstOffset;
float rightBound = width * mLastOffset;
final ItemInfo firstItem = mItems.get(0);
final ItemInfo lastItem = mItems.get(mItems.size() - 1);
if (firstItem.position != 0) {
leftBound = firstItem.offset * width;

}
if (lastItem.position != mAdapter.getCount() - 1) {
rightBound = lastItem.offset * width;
}
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());
pageScrolled((int) scrollX);
// Synthesize an event for the VelocityTracker.
final long time = SystemClock.uptimeMillis();
final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, Moti
onEvent.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;
}

x);

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, newPointerInde

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

private void endDrag() {


mIsBeingDragged = false;
mIsUnableToDrag = false;
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;

private void setScrollingCacheEnabled(boolean enabled) {


if (mScrollingCacheEnabled != enabled) {
mScrollingCacheEnabled = enabled;
if (USE_CACHE) {
final int size = getChildCount();
for (int i = 0; i < size; ++i) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
child.setDrawingCacheEnabled(enabled);
}
}
}
}
}
public boolean canScrollHorizontally(int direction) {
if (mAdapter == null) {
return false;
}

final int width = getClientWidth();


final int scrollX = getScrollX();
if (direction < 0) {
return (scrollX > (int) (width * mFirstOffset));
} else if (direction > 0) {
return (scrollX < (int) (width * mLastOffset));
} else {
return false;
}

/**
* Tests scrollability within child views of v given a delta of dx.
*
* @param v View to test for horizontal scrollability
* @param checkV Whether the view v passed should itself be checked for scro
llability (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.getRig
ht() &&

y + scrollY >= child.getTop() && y + scrollY < child.get

Bottom() &&

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 (Build.VERSION.SDK_INT >= 11) {
// The focus finder had a bug handling FOCUS_FORWARD and
FOCUS_BACKWARD
// before Android 3.0. Ignore the tab key on those devic
es.
if (KeyEventCompat.hasNoModifiers(event)) {
handled = arrowScroll(FOCUS_FORWARD);
} else if (KeyEventCompat.hasModifiers(event, KeyEvent.M
ETA_SHIFT_ON)) {
handled = arrowScroll(FOCUS_BACKWARD);
}
}
break;
}
}
return handled;
}
public boolean arrowScroll(int direction) {
View currentFocused = findFocus();
if (currentFocused == this) {
currentFocused = null;

} else if (currentFocused != null) {


boolean isChild = false;
for (ViewParent parent = currentFocused.getParent(); parent instance
of ViewGroup;
parent = parent.getParent()) {
if (parent == this) {
isChild = true;
break;
}
}
if (!isChild) {
// This would cause the focus search down below to fail in fun w
ays.
final StringBuilder sb = new StringBuilder();
sb.append(currentFocused.getClass().getSimpleName());
for (ViewParent parent = currentFocused.getParent(); parent inst
anceof ViewGroup;
parent = parent.getParent()) {
sb.append(" => ").append(parent.getClass().getSimpleName());
}
Log.e(TAG, "arrowScroll tried to find focus based on non-child "
+
"current focused view " + sb.toString());
currentFocused = null;
}
}
boolean handled = false;
View nextFocused = FocusFinder.getInstance().findNextFocus(this, current

Focused,

direction);
if (nextFocused != null && nextFocused != currentFocused) {
if (direction == View.FOCUS_LEFT) {
// If there is nothing to the left, or this

Vous aimerez peut-être aussi