├── .gitignore ├── .idea ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── libraries │ ├── ComAndroidSupportAppcompatV71800_aar.xml │ ├── ViewPagerSample_build_exploded_bundles_ComAndroidSupportAppcompatV71800_aar_classes_jar_ComAndroidSupportAppcompatV71800_aar.xml │ ├── ViewPager_build_exploded_bundles_ComAndroidSupportAppcompatV71800_aar_classes_jar_ComAndroidSupportAppcompatV71800_aar.xml │ └── support_v4_18_0_0.xml ├── misc.xml ├── modules.xml ├── scopes │ └── scope_settings.xml └── vcs.xml ├── LICENSE.md ├── README.md ├── ViewPager ├── .gitignore ├── ViewPager-ViewPager.iml ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── ryanharter │ │ └── viewpager │ │ ├── PagerAdapter.java │ │ └── ViewPager.java │ └── res │ ├── drawable-hdpi │ └── ic_launcher.png │ ├── drawable-mdpi │ └── ic_launcher.png │ ├── drawable-xhdpi │ └── ic_launcher.png │ ├── values-v11 │ └── styles.xml │ ├── values-v14 │ └── styles.xml │ └── values │ ├── attrs.xml │ ├── strings.xml │ └── styles.xml ├── ViewPagerSample ├── .gitignore ├── ViewPagerSample.iml ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── ryanharter │ │ └── viewpager │ │ └── sample │ │ ├── HorizontalPagingFragment.java │ │ ├── MainActivity.java │ │ └── VerticalPagingFragment.java │ └── res │ ├── drawable-hdpi │ └── ic_launcher.png │ ├── drawable-mdpi │ └── ic_launcher.png │ ├── drawable-xhdpi │ └── ic_launcher.png │ ├── drawable-xxhdpi │ └── ic_launcher.png │ ├── layout │ ├── activity_main.xml │ ├── fragment_horizontal.xml │ ├── fragment_main.xml │ ├── fragment_vertical.xml │ └── page.xml │ ├── menu │ └── main.xml │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── arrays.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea/workspace.xml 4 | .DS_Store 5 | *.iml 6 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | ViewPagerSample -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 23 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/ComAndroidSupportAppcompatV71800_aar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/libraries/ViewPagerSample_build_exploded_bundles_ComAndroidSupportAppcompatV71800_aar_classes_jar_ComAndroidSupportAppcompatV71800_aar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/libraries/ViewPager_build_exploded_bundles_ComAndroidSupportAppcompatV71800_aar_classes_jar_ComAndroidSupportAppcompatV71800_aar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/libraries/support_v4_18_0_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | Java language level migration aids 18 | 19 | 20 | 21 | 22 | Abstraction issues 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 48 | 49 | 50 | 51 | 52 | 1.6 53 | 54 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/scopes/scope_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) [year] [fullname] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ViewPager-Android 2 | 3 | Based on the Android support library's ViewPager class, this ViewPager supports both horizontal and vertical paging views. 4 | 5 | # Usage 6 | 7 | ## ViewPager 8 | 9 | This ViewPager is a drop in replacement for the support library version. Simply reference the library and replace your ViewPager imports with this version. 10 | 11 | Reference in your XML like this. 12 | 13 | ```xml 14 | 20 | ``` 21 | 22 | Notice that you can use `app:orientation="vertical"` to easily set the orientation of the ViewPager to vertical. Orientation defaults to horizontal. 23 | 24 | You can also set orientation in code using `mViewPager.setOrientation(ViewPager.ORIENTATION_VERTICAL)`. 25 | 26 | ## PagerAdapter 27 | 28 | Again, the PagerAdapter included here is a drop in replacement. Simply change you import from `android.support.v4.view.PagerAdapter` to `com.ryanharter.viewpager.PagerAdapter` and you're good to go. 29 | 30 | I still need to add `FragmentPagerAdapter` and FragmentStatePagerAdapter` subclasses to easily replace those, but if you need them now it shouldn't be difficult. 31 | -------------------------------------------------------------------------------- /ViewPager/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /ViewPager/ViewPager-ViewPager.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 18 | 19 | 20 | 21 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /ViewPager/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'android-library' 2 | 3 | repositories { 4 | mavenCentral() 5 | } 6 | 7 | android { 8 | compileSdkVersion 19 9 | buildToolsVersion "19.0.0" 10 | 11 | defaultConfig { 12 | minSdkVersion 7 13 | targetSdkVersion 19 14 | } 15 | 16 | compileOptions { 17 | sourceCompatibility JavaVersion.VERSION_1_7 18 | targetCompatibility JavaVersion.VERSION_1_7 19 | } 20 | } 21 | 22 | dependencies { 23 | compile 'com.android.support:appcompat-v7:18.0.0' 24 | } 25 | -------------------------------------------------------------------------------- /ViewPager/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /ViewPager/src/main/java/com/ryanharter/viewpager/PagerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.ryanharter.viewpager; 2 | 3 | /** 4 | * {@inheritDoc} 5 | */ 6 | public abstract class PagerAdapter extends android.support.v4.view.PagerAdapter { 7 | 8 | /** 9 | * {@inheritDoc} 10 | * @deprecated Use {@link #getPageSize(int)} instead. 11 | */ 12 | @Override 13 | public float getPageWidth(int position) { 14 | return super.getPageWidth(position); 15 | } 16 | 17 | /** 18 | * Returns the proportional size (width or height depending on orientation) 19 | * of a given page as a percentage of the ViewPager's measured size from (0.f-1.f). 20 | * 21 | * @param position The position of the page requested 22 | * @return Proportional size for the given page position 23 | */ 24 | public float getPageSize(int position) { 25 | return getPageWidth(position); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ViewPager/src/main/java/com/ryanharter/viewpager/ViewPager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ryanharter.viewpager; 18 | 19 | import android.content.Context; 20 | import android.content.res.Resources; 21 | import android.content.res.TypedArray; 22 | import android.database.DataSetObserver; 23 | import android.graphics.Canvas; 24 | import android.graphics.Rect; 25 | import android.graphics.drawable.Drawable; 26 | import android.os.Build; 27 | import android.os.Bundle; 28 | import android.os.Parcel; 29 | import android.os.Parcelable; 30 | import android.os.SystemClock; 31 | import android.support.v4.os.ParcelableCompat; 32 | import android.support.v4.os.ParcelableCompatCreatorCallbacks; 33 | import android.support.v4.view.AccessibilityDelegateCompat; 34 | import android.support.v4.view.KeyEventCompat; 35 | import android.support.v4.view.MotionEventCompat; 36 | import android.support.v4.view.VelocityTrackerCompat; 37 | import android.support.v4.view.ViewCompat; 38 | import android.support.v4.view.ViewConfigurationCompat; 39 | import android.support.v4.view.accessibility.AccessibilityEventCompat; 40 | import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; 41 | import android.support.v4.view.accessibility.AccessibilityRecordCompat; 42 | import android.support.v4.widget.EdgeEffectCompat; 43 | import android.util.AttributeSet; 44 | import android.util.Log; 45 | import android.view.FocusFinder; 46 | import android.view.Gravity; 47 | import android.view.KeyEvent; 48 | import android.view.MotionEvent; 49 | import android.view.SoundEffectConstants; 50 | import android.view.VelocityTracker; 51 | import android.view.View; 52 | import android.view.ViewConfiguration; 53 | import android.view.ViewGroup; 54 | import android.view.ViewParent; 55 | import android.view.accessibility.AccessibilityEvent; 56 | import android.view.animation.Interpolator; 57 | import android.widget.Scroller; 58 | 59 | import java.lang.reflect.Method; 60 | import java.util.ArrayList; 61 | import java.util.Collections; 62 | import java.util.Comparator; 63 | 64 | /** 65 | * Layout manager that allows the user to flip left and right 66 | * through pages of data. You supply an implementation of a 67 | * {@link PagerAdapter} to generate the pages that the view shows. 68 | * 69 | *

Note this class is currently under early design and 70 | * development. The API will likely change in later updates of 71 | * the compatibility library, requiring changes to the source code 72 | * of apps when they are compiled against the newer version.

73 | * 74 | *

ViewPager is most often used in conjunction with {@link android.app.Fragment}, 75 | * which is a convenient way to supply and manage the lifecycle of each page. 76 | * There are standard adapters implemented for using fragments with the ViewPager, 77 | * which cover the most common use cases. These are 78 | * {@link android.support.v4.app.FragmentPagerAdapter} and 79 | * {@link android.support.v4.app.FragmentStatePagerAdapter}; each of these 80 | * classes have simple code showing how to build a full user interface 81 | * with them. 82 | * 83 | *

Here is a more complicated example of ViewPager, using it in conjuction 84 | * with {@link android.app.ActionBar} tabs. You can find other examples of using 85 | * ViewPager in the API 4+ Support Demos and API 13+ Support Demos sample code. 86 | * 87 | * {@sample development/samples/Support13Demos/src/com/example/android/supportv13/app/ActionBarTabsPager.java 88 | * complete} 89 | */ 90 | public class ViewPager extends ViewGroup { 91 | private static final String TAG = "ViewPager"; 92 | private static final boolean DEBUG = false; 93 | 94 | private static final boolean USE_CACHE = false; 95 | 96 | private static final int DEFAULT_OFFSCREEN_PAGES = 1; 97 | private static final int MAX_SETTLE_DURATION = 600; // ms 98 | private static final int MIN_DISTANCE_FOR_FLING = 25; // dips 99 | 100 | private static final int DEFAULT_GUTTER_SIZE = 16; // dips 101 | 102 | private static final int MIN_FLING_VELOCITY = 400; // dips 103 | 104 | private static final int[] LAYOUT_ATTRS = new int[] { 105 | android.R.attr.layout_gravity 106 | }; 107 | 108 | private static final int ORIENTATION_HORIZONTAL = 0; 109 | private static final int ORIENTATION_VERTICAL = 1; 110 | 111 | /** 112 | * Used to track what the expected number of items in the adapter should be. 113 | * If the app changes this when we don't expect it, we'll throw a big obnoxious exception. 114 | */ 115 | private int mExpectedAdapterCount; 116 | 117 | static class ItemInfo { 118 | Object object; 119 | int position; 120 | boolean scrolling; 121 | float sizeFactor; 122 | float offset; 123 | } 124 | 125 | private static final Comparator COMPARATOR = new Comparator(){ 126 | @Override 127 | public int compare(ItemInfo lhs, ItemInfo rhs) { 128 | return lhs.position - rhs.position; 129 | } 130 | }; 131 | 132 | private static final Interpolator sInterpolator = new Interpolator() { 133 | public float getInterpolation(float t) { 134 | t -= 1.0f; 135 | return t * t * t * t * t + 1.0f; 136 | } 137 | }; 138 | 139 | private final ArrayList mItems = new ArrayList(); 140 | private final ItemInfo mTempItem = new ItemInfo(); 141 | 142 | private final Rect mTempRect = new Rect(); 143 | 144 | private PagerAdapter mAdapter; 145 | private int mCurItem; // Index of currently displayed page. 146 | private int mRestoredCurItem = -1; 147 | private Parcelable mRestoredAdapterState = null; 148 | private ClassLoader mRestoredClassLoader = null; 149 | private Scroller mScroller; 150 | private PagerObserver mObserver; 151 | 152 | private int mOrientation = ORIENTATION_HORIZONTAL; 153 | private int mPageMargin; 154 | private Drawable mMarginDrawable; 155 | private int mLeftPageBounds; 156 | private int mTopPageBounds; 157 | private int mRightPageBounds; 158 | private int mBottomPageBounds; 159 | 160 | // Offsets of the first and last items, if known. 161 | // Set during population, used to determine if we are at the beginning 162 | // or end of the pager data set during touch scrolling. 163 | private float mFirstOffset = -Float.MAX_VALUE; 164 | private float mLastOffset = Float.MAX_VALUE; 165 | 166 | private int mChildWidthMeasureSpec; 167 | private int mChildHeightMeasureSpec; 168 | private boolean mInLayout; 169 | 170 | private boolean mScrollingCacheEnabled; 171 | 172 | private boolean mPopulatePending; 173 | private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES; 174 | 175 | private boolean mIsBeingDragged; 176 | private boolean mIsUnableToDrag; 177 | private boolean mIgnoreGutter; 178 | private int mDefaultGutterSize; 179 | private int mGutterSize; 180 | private int mTouchSlop; 181 | /** 182 | * Position of the last motion event. 183 | */ 184 | private float mLastMotionX; 185 | private float mLastMotionY; 186 | private float mInitialMotionX; 187 | private float mInitialMotionY; 188 | /** 189 | * ID of the active pointer. This is used to retain consistency during 190 | * drags/flings if multiple pointers are used. 191 | */ 192 | private int mActivePointerId = INVALID_POINTER; 193 | /** 194 | * Sentinel value for no current active pointer. 195 | * Used by {@link #mActivePointerId}. 196 | */ 197 | private static final int INVALID_POINTER = -1; 198 | 199 | /** 200 | * Determines speed during touch scrolling 201 | */ 202 | private VelocityTracker mVelocityTracker; 203 | private int mMinimumVelocity; 204 | private int mMaximumVelocity; 205 | private int mFlingDistance; 206 | private int mCloseEnough; 207 | 208 | // If the pager is at least this close to its final position, complete the scroll 209 | // on touch down and let the user interact with the content inside instead of 210 | // "catching" the flinging pager. 211 | private static final int CLOSE_ENOUGH = 2; // dp 212 | 213 | private boolean mFakeDragging; 214 | private long mFakeDragBeginTime; 215 | 216 | // TODO Do I need separate edge effects, or can I just reuse these with better names like mStartEdge, mEndEdge? 217 | private EdgeEffectCompat mLeftEdge; 218 | private EdgeEffectCompat mRightEdge; 219 | 220 | private boolean mFirstLayout = true; 221 | private boolean mNeedCalculatePageOffsets = false; 222 | private boolean mCalledSuper; 223 | private int mDecorChildCount; 224 | 225 | private OnPageChangeListener mOnPageChangeListener; 226 | private OnPageChangeListener mInternalPageChangeListener; 227 | private OnAdapterChangeListener mAdapterChangeListener; 228 | private PageTransformer mPageTransformer; 229 | private Method mSetChildrenDrawingOrderEnabled; 230 | 231 | private static final int DRAW_ORDER_DEFAULT = 0; 232 | private static final int DRAW_ORDER_FORWARD = 1; 233 | private static final int DRAW_ORDER_REVERSE = 2; 234 | private int mDrawingOrder; 235 | private ArrayList mDrawingOrderedChildren; 236 | private static final ViewPositionComparator sPositionComparator = new ViewPositionComparator(); 237 | 238 | /** 239 | * Indicates that the pager is in an idle, settled state. The current page 240 | * is fully in view and no animation is in progress. 241 | */ 242 | public static final int SCROLL_STATE_IDLE = 0; 243 | 244 | /** 245 | * Indicates that the pager is currently being dragged by the user. 246 | */ 247 | public static final int SCROLL_STATE_DRAGGING = 1; 248 | 249 | /** 250 | * Indicates that the pager is in the process of settling to a final position. 251 | */ 252 | public static final int SCROLL_STATE_SETTLING = 2; 253 | 254 | private final Runnable mEndScrollRunnable = new Runnable() { 255 | public void run() { 256 | setScrollState(SCROLL_STATE_IDLE); 257 | populate(); 258 | } 259 | }; 260 | 261 | private int mScrollState = SCROLL_STATE_IDLE; 262 | 263 | /** 264 | * Callback interface for responding to changing state of the selected page. 265 | */ 266 | public interface OnPageChangeListener { 267 | 268 | /** 269 | * This method will be invoked when the current page is scrolled, either as part 270 | * of a programmatically initiated smooth scroll or a user initiated touch scroll. 271 | * 272 | * @param position Position index of the first page currently being displayed. 273 | * Page position+1 will be visible if positionOffset is nonzero. 274 | * @param positionOffset Value from [0, 1) indicating the offset from the page at position. 275 | * @param positionOffsetPixels Value in pixels indicating the offset from position. 276 | */ 277 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels); 278 | 279 | /** 280 | * This method will be invoked when a new page becomes selected. Animation is not 281 | * necessarily complete. 282 | * 283 | * @param position Position index of the new selected page. 284 | */ 285 | public void onPageSelected(int position); 286 | 287 | /** 288 | * Called when the scroll state changes. Useful for discovering when the user 289 | * begins dragging, when the pager is automatically settling to the current page, 290 | * or when it is fully stopped/idle. 291 | * 292 | * @param state The new scroll state. 293 | * @see ViewPager#SCROLL_STATE_IDLE 294 | * @see ViewPager#SCROLL_STATE_DRAGGING 295 | * @see ViewPager#SCROLL_STATE_SETTLING 296 | */ 297 | public void onPageScrollStateChanged(int state); 298 | } 299 | 300 | /** 301 | * Simple implementation of the {@link OnPageChangeListener} interface with stub 302 | * implementations of each method. Extend this if you do not intend to override 303 | * every method of {@link OnPageChangeListener}. 304 | */ 305 | public static class SimpleOnPageChangeListener implements OnPageChangeListener { 306 | @Override 307 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 308 | // This space for rent 309 | } 310 | 311 | @Override 312 | public void onPageSelected(int position) { 313 | // This space for rent 314 | } 315 | 316 | @Override 317 | public void onPageScrollStateChanged(int state) { 318 | // This space for rent 319 | } 320 | } 321 | 322 | /** 323 | * A PageTransformer is invoked whenever a visible/attached page is scrolled. 324 | * This offers an opportunity for the application to apply a custom transformation 325 | * to the page views using animation properties. 326 | * 327 | *

As property animation is only supported as of Android 3.0 and forward, 328 | * setting a PageTransformer on a ViewPager on earlier platform versions will 329 | * be ignored.

330 | */ 331 | public interface PageTransformer { 332 | /** 333 | * Apply a property transformation to the given page. 334 | * 335 | * @param page Apply the transformation to this page 336 | * @param position Position of page relative to the current front-and-center 337 | * position of the pager. 0 is front and center. 1 is one full 338 | * page position to the right, and -1 is one page position to the left. 339 | */ 340 | public void transformPage(View page, float position); 341 | } 342 | 343 | /** 344 | * Used internally to monitor when adapters are switched. 345 | */ 346 | interface OnAdapterChangeListener { 347 | public void onAdapterChanged(PagerAdapter oldAdapter, PagerAdapter newAdapter); 348 | } 349 | 350 | /** 351 | * Used internally to tag special types of child views that should be added as 352 | * pager decorations by default. 353 | */ 354 | interface Decor {} 355 | 356 | public ViewPager(Context context) { 357 | this(context, null); 358 | } 359 | 360 | public ViewPager(Context context, AttributeSet attrs) { 361 | this(context, attrs, 0); 362 | } 363 | 364 | public ViewPager(Context context, AttributeSet attrs, int defStyle) { 365 | super(context, attrs, defStyle); 366 | 367 | TypedArray a = context.obtainStyledAttributes(attrs, 368 | R.styleable.ViewPager, defStyle, 0); 369 | mOrientation = a.getInt(R.styleable.ViewPager_orientation, ORIENTATION_HORIZONTAL); 370 | a.recycle(); 371 | 372 | initViewPager(); 373 | } 374 | 375 | void initViewPager() { 376 | setWillNotDraw(false); 377 | setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); 378 | setFocusable(true); 379 | final Context context = getContext(); 380 | mScroller = new Scroller(context, sInterpolator); 381 | 382 | final ViewConfiguration configuration = ViewConfiguration.get(context); 383 | final float density = context.getResources().getDisplayMetrics().density; 384 | 385 | mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration); 386 | mMinimumVelocity = (int) (MIN_FLING_VELOCITY * density); 387 | mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); 388 | mLeftEdge = new EdgeEffectCompat(context); 389 | mRightEdge = new EdgeEffectCompat(context); 390 | 391 | mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density); 392 | mCloseEnough = (int) (CLOSE_ENOUGH * density); 393 | mDefaultGutterSize = (int) (DEFAULT_GUTTER_SIZE * density); 394 | 395 | ViewCompat.setAccessibilityDelegate(this, new MyAccessibilityDelegate()); 396 | 397 | if (ViewCompat.getImportantForAccessibility(this) 398 | == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 399 | ViewCompat.setImportantForAccessibility(this, 400 | ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES); 401 | } 402 | } 403 | 404 | @Override 405 | protected void onDetachedFromWindow() { 406 | removeCallbacks(mEndScrollRunnable); 407 | super.onDetachedFromWindow(); 408 | } 409 | 410 | private void setScrollState(int newState) { 411 | if (mScrollState == newState) { 412 | return; 413 | } 414 | 415 | mScrollState = newState; 416 | if (mPageTransformer != null) { 417 | // PageTransformers can do complex things that benefit from hardware layers. 418 | enableLayers(newState != SCROLL_STATE_IDLE); 419 | } 420 | if (mOnPageChangeListener != null) { 421 | mOnPageChangeListener.onPageScrollStateChanged(newState); 422 | } 423 | } 424 | 425 | /** 426 | * Set a PagerAdapter that will supply views for this pager as needed. 427 | * 428 | * @param adapter Adapter to use 429 | */ 430 | public void setAdapter(PagerAdapter adapter) { 431 | if (mAdapter != null) { 432 | mAdapter.unregisterDataSetObserver(mObserver); 433 | mAdapter.startUpdate(this); 434 | for (int i = 0; i < mItems.size(); i++) { 435 | final ItemInfo ii = mItems.get(i); 436 | mAdapter.destroyItem(this, ii.position, ii.object); 437 | } 438 | mAdapter.finishUpdate(this); 439 | mItems.clear(); 440 | removeNonDecorViews(); 441 | mCurItem = 0; 442 | scrollTo(0, 0); 443 | } 444 | 445 | final PagerAdapter oldAdapter = mAdapter; 446 | mAdapter = adapter; 447 | mExpectedAdapterCount = 0; 448 | 449 | if (mAdapter != null) { 450 | if (mObserver == null) { 451 | mObserver = new PagerObserver(); 452 | } 453 | mAdapter.registerDataSetObserver(mObserver); 454 | mPopulatePending = false; 455 | final boolean wasFirstLayout = mFirstLayout; 456 | mFirstLayout = true; 457 | mExpectedAdapterCount = mAdapter.getCount(); 458 | if (mRestoredCurItem >= 0) { 459 | mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader); 460 | setCurrentItemInternal(mRestoredCurItem, false, true); 461 | mRestoredCurItem = -1; 462 | mRestoredAdapterState = null; 463 | mRestoredClassLoader = null; 464 | } else if (!wasFirstLayout) { 465 | populate(); 466 | } else { 467 | requestLayout(); 468 | } 469 | } 470 | 471 | if (mAdapterChangeListener != null && oldAdapter != adapter) { 472 | mAdapterChangeListener.onAdapterChanged(oldAdapter, adapter); 473 | } 474 | } 475 | 476 | private void removeNonDecorViews() { 477 | for (int i = 0; i < getChildCount(); i++) { 478 | final View child = getChildAt(i); 479 | final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 480 | if (!lp.isDecor) { 481 | removeViewAt(i); 482 | i--; 483 | } 484 | } 485 | } 486 | 487 | /** 488 | * Retrieve the current adapter supplying pages. 489 | * 490 | * @return The currently registered PagerAdapter 491 | */ 492 | public PagerAdapter getAdapter() { 493 | return mAdapter; 494 | } 495 | 496 | void setOnAdapterChangeListener(OnAdapterChangeListener listener) { 497 | mAdapterChangeListener = listener; 498 | } 499 | 500 | public int getOrientation() { 501 | return mOrientation; 502 | } 503 | 504 | public boolean isOrientationHorizontal() { 505 | return mOrientation == ORIENTATION_HORIZONTAL; 506 | } 507 | 508 | private int getClientWidth() { 509 | return getMeasuredWidth() - getPaddingLeft() - getPaddingRight(); 510 | } 511 | 512 | private int getClientHeight() { 513 | return getMeasuredHeight() - getPaddingTop() - getPaddingBottom(); 514 | } 515 | 516 | /** 517 | * Set the currently selected page. If the ViewPager has already been through its first 518 | * layout with its current adapter there will be a smooth animated transition between 519 | * the current item and the specified item. 520 | * 521 | * @param item Item index to select 522 | */ 523 | public void setCurrentItem(int item) { 524 | mPopulatePending = false; 525 | setCurrentItemInternal(item, !mFirstLayout, false); 526 | } 527 | 528 | /** 529 | * Set the currently selected page. 530 | * 531 | * @param item Item index to select 532 | * @param smoothScroll True to smoothly scroll to the new item, false to transition immediately 533 | */ 534 | public void setCurrentItem(int item, boolean smoothScroll) { 535 | mPopulatePending = false; 536 | setCurrentItemInternal(item, smoothScroll, false); 537 | } 538 | 539 | public int getCurrentItem() { 540 | return mCurItem; 541 | } 542 | 543 | void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) { 544 | setCurrentItemInternal(item, smoothScroll, always, 0); 545 | } 546 | 547 | void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) { 548 | if (mAdapter == null || mAdapter.getCount() <= 0) { 549 | setScrollingCacheEnabled(false); 550 | return; 551 | } 552 | if (!always && mCurItem == item && mItems.size() != 0) { 553 | setScrollingCacheEnabled(false); 554 | return; 555 | } 556 | 557 | if (item < 0) { 558 | item = 0; 559 | } else if (item >= mAdapter.getCount()) { 560 | item = mAdapter.getCount() - 1; 561 | } 562 | final int pageLimit = mOffscreenPageLimit; 563 | if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) { 564 | // We are doing a jump by more than one page. To avoid 565 | // glitches, we want to keep all current pages in the view 566 | // until the scroll ends. 567 | for (int i=0; iNote: Prior to Android 3.0 the property animation APIs did not exist. 642 | * As a result, setting a PageTransformer prior to Android 3.0 (API 11) will have no effect.

643 | * 644 | * @param reverseDrawingOrder true if the supplied PageTransformer requires page views 645 | * to be drawn from last to first instead of first to last. 646 | * @param transformer PageTransformer that will modify each page's animation properties 647 | */ 648 | public void setPageTransformer(boolean reverseDrawingOrder, PageTransformer transformer) { 649 | if (Build.VERSION.SDK_INT >= 11) { 650 | final boolean hasTransformer = transformer != null; 651 | final boolean needsPopulate = hasTransformer != (mPageTransformer != null); 652 | mPageTransformer = transformer; 653 | setChildrenDrawingOrderEnabledCompat(hasTransformer); 654 | if (hasTransformer) { 655 | mDrawingOrder = reverseDrawingOrder ? DRAW_ORDER_REVERSE : DRAW_ORDER_FORWARD; 656 | } else { 657 | mDrawingOrder = DRAW_ORDER_DEFAULT; 658 | } 659 | if (needsPopulate) populate(); 660 | } 661 | } 662 | 663 | void setChildrenDrawingOrderEnabledCompat(boolean enable) { 664 | if (Build.VERSION.SDK_INT >= 7) { 665 | if (mSetChildrenDrawingOrderEnabled == null) { 666 | try { 667 | mSetChildrenDrawingOrderEnabled = ViewGroup.class.getDeclaredMethod( 668 | "setChildrenDrawingOrderEnabled", new Class[] { Boolean.TYPE }); 669 | } catch (NoSuchMethodException e) { 670 | Log.e(TAG, "Can't find setChildrenDrawingOrderEnabled", e); 671 | } 672 | } 673 | try { 674 | mSetChildrenDrawingOrderEnabled.invoke(this, enable); 675 | } catch (Exception e) { 676 | Log.e(TAG, "Error changing children drawing order", e); 677 | } 678 | } 679 | } 680 | 681 | @Override 682 | protected int getChildDrawingOrder(int childCount, int i) { 683 | final int index = mDrawingOrder == DRAW_ORDER_REVERSE ? childCount - 1 - i : i; 684 | final int result = ((LayoutParams) mDrawingOrderedChildren.get(index).getLayoutParams()).childIndex; 685 | return result; 686 | } 687 | 688 | /** 689 | * Set a separate OnPageChangeListener for internal use by the support library. 690 | * 691 | * @param listener Listener to set 692 | * @return The old listener that was set, if any. 693 | */ 694 | OnPageChangeListener setInternalPageChangeListener(OnPageChangeListener listener) { 695 | OnPageChangeListener oldListener = mInternalPageChangeListener; 696 | mInternalPageChangeListener = listener; 697 | return oldListener; 698 | } 699 | 700 | /** 701 | * Returns the number of pages that will be retained to either side of the 702 | * current page in the view hierarchy in an idle state. Defaults to 1. 703 | * 704 | * @return How many pages will be kept offscreen on either side 705 | * @see #setOffscreenPageLimit(int) 706 | */ 707 | public int getOffscreenPageLimit() { 708 | return mOffscreenPageLimit; 709 | } 710 | 711 | /** 712 | * Set the number of pages that should be retained to either side of the 713 | * current page in the view hierarchy in an idle state. Pages beyond this 714 | * limit will be recreated from the adapter when needed. 715 | * 716 | *

This is offered as an optimization. If you know in advance the number 717 | * of pages you will need to support or have lazy-loading mechanisms in place 718 | * on your pages, tweaking this setting can have benefits in perceived smoothness 719 | * of paging animations and interaction. If you have a small number of pages (3-4) 720 | * that you can keep active all at once, less time will be spent in layout for 721 | * newly created view subtrees as the user pages back and forth.

722 | * 723 | *

You should keep this limit low, especially if your pages have complex layouts. 724 | * This setting defaults to 1.

725 | * 726 | * @param limit How many pages will be kept offscreen in an idle state. 727 | */ 728 | public void setOffscreenPageLimit(int limit) { 729 | if (limit < DEFAULT_OFFSCREEN_PAGES) { 730 | Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " + 731 | DEFAULT_OFFSCREEN_PAGES); 732 | limit = DEFAULT_OFFSCREEN_PAGES; 733 | } 734 | if (limit != mOffscreenPageLimit) { 735 | mOffscreenPageLimit = limit; 736 | populate(); 737 | } 738 | } 739 | 740 | /** 741 | * Set the margin between pages. 742 | * 743 | * @param marginPixels Distance between adjacent pages in pixels 744 | * @see #getPageMargin() 745 | * @see #setPageMarginDrawable(Drawable) 746 | * @see #setPageMarginDrawable(int) 747 | */ 748 | public void setPageMargin(int marginPixels) { 749 | final int oldMargin = mPageMargin; 750 | mPageMargin = marginPixels; 751 | 752 | int spacing = 0; 753 | if (isOrientationHorizontal()) { 754 | spacing = getWidth(); 755 | } else { 756 | spacing = getHeight(); 757 | } 758 | recomputeScrollPosition(spacing, spacing, spacing, spacing, marginPixels, oldMargin); 759 | 760 | requestLayout(); 761 | } 762 | 763 | /** 764 | * Return the margin between pages. 765 | * 766 | * @return The size of the margin in pixels 767 | */ 768 | public int getPageMargin() { 769 | return mPageMargin; 770 | } 771 | 772 | /** 773 | * Set a drawable that will be used to fill the margin between pages. 774 | * 775 | * @param d Drawable to display between pages 776 | */ 777 | public void setPageMarginDrawable(Drawable d) { 778 | mMarginDrawable = d; 779 | if (d != null) refreshDrawableState(); 780 | setWillNotDraw(d == null); 781 | invalidate(); 782 | } 783 | 784 | /** 785 | * Set a drawable that will be used to fill the margin between pages. 786 | * 787 | * @param resId Resource ID of a drawable to display between pages 788 | */ 789 | public void setPageMarginDrawable(int resId) { 790 | setPageMarginDrawable(getContext().getResources().getDrawable(resId)); 791 | } 792 | 793 | @Override 794 | protected boolean verifyDrawable(Drawable who) { 795 | return super.verifyDrawable(who) || who == mMarginDrawable; 796 | } 797 | 798 | @Override 799 | protected void drawableStateChanged() { 800 | super.drawableStateChanged(); 801 | final Drawable d = mMarginDrawable; 802 | if (d != null && d.isStateful()) { 803 | d.setState(getDrawableState()); 804 | } 805 | } 806 | 807 | // We want the duration of the page snap animation to be influenced by the distance that 808 | // the screen has to travel, however, we don't want this duration to be effected in a 809 | // purely linear fashion. Instead, we use this method to moderate the effect that the distance 810 | // of travel has on the overall snap duration. 811 | float distanceInfluenceForSnapDuration(float f) { 812 | f -= 0.5f; // center the values about 0. 813 | f *= 0.3f * Math.PI / 2.0f; 814 | return (float) Math.sin(f); 815 | } 816 | 817 | /** 818 | * Like {@link View#scrollBy}, but scroll smoothly instead of immediately. 819 | * 820 | * @param x the number of pixels to scroll by on the X axis 821 | * @param y the number of pixels to scroll by on the Y axis 822 | */ 823 | void smoothScrollTo(int x, int y) { 824 | smoothScrollTo(x, y, 0); 825 | } 826 | 827 | /** 828 | * Like {@link View#scrollBy}, but scroll smoothly instead of immediately. 829 | * 830 | * @param x the number of pixels to scroll by on the X axis 831 | * @param y the number of pixels to scroll by on the Y axis 832 | * @param velocity the velocity associated with a fling, if applicable. (0 otherwise) 833 | */ 834 | void smoothScrollTo(int x, int y, int velocity) { 835 | if (getChildCount() == 0) { 836 | // Nothing to do. 837 | setScrollingCacheEnabled(false); 838 | return; 839 | } 840 | int sx = getScrollX(); 841 | int sy = getScrollY(); 842 | int dx = x - sx; 843 | int dy = y - sy; 844 | if (dx == 0 && dy == 0) { 845 | completeScroll(false); 846 | populate(); 847 | setScrollState(SCROLL_STATE_IDLE); 848 | return; 849 | } 850 | 851 | setScrollingCacheEnabled(true); 852 | setScrollState(SCROLL_STATE_SETTLING); 853 | 854 | final int size = isOrientationHorizontal() ? getClientWidth() : getClientHeight(); 855 | final int halfSize = size / 2; 856 | final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / size); 857 | final float distance = halfSize + halfSize * 858 | distanceInfluenceForSnapDuration(distanceRatio); 859 | 860 | int duration = 0; 861 | velocity = Math.abs(velocity); 862 | if (velocity > 0) { 863 | duration = 4 * Math.round(1000 * Math.abs(distance / velocity)); 864 | } else { 865 | final float pageSize = size * mAdapter.getPageSize(mCurItem); 866 | final float pageDelta = (float) Math.abs(dx) / (pageSize + mPageMargin); 867 | duration = (int) ((pageDelta + 1) * 100); 868 | } 869 | duration = Math.min(duration, MAX_SETTLE_DURATION); 870 | 871 | mScroller.startScroll(sx, sy, dx, dy, duration); 872 | ViewCompat.postInvalidateOnAnimation(this); 873 | } 874 | 875 | ItemInfo addNewItem(int position, int index) { 876 | ItemInfo ii = new ItemInfo(); 877 | ii.position = position; 878 | ii.object = mAdapter.instantiateItem(this, position); 879 | ii.sizeFactor = mAdapter.getPageSize(position); 880 | if (index < 0 || index >= mItems.size()) { 881 | mItems.add(ii); 882 | } else { 883 | mItems.add(index, ii); 884 | } 885 | return ii; 886 | } 887 | 888 | void dataSetChanged() { 889 | // This method only gets called if our observer is attached, so mAdapter is non-null. 890 | 891 | final int adapterCount = mAdapter.getCount(); 892 | mExpectedAdapterCount = adapterCount; 893 | boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1 && 894 | mItems.size() < adapterCount; 895 | int newCurrItem = mCurItem; 896 | 897 | boolean isUpdating = false; 898 | for (int i = 0; i < mItems.size(); i++) { 899 | final ItemInfo ii = mItems.get(i); 900 | final int newPos = mAdapter.getItemPosition(ii.object); 901 | 902 | if (newPos == PagerAdapter.POSITION_UNCHANGED) { 903 | continue; 904 | } 905 | 906 | if (newPos == PagerAdapter.POSITION_NONE) { 907 | mItems.remove(i); 908 | i--; 909 | 910 | if (!isUpdating) { 911 | mAdapter.startUpdate(this); 912 | isUpdating = true; 913 | } 914 | 915 | mAdapter.destroyItem(this, ii.position, ii.object); 916 | needPopulate = true; 917 | 918 | if (mCurItem == ii.position) { 919 | // Keep the current item in the valid range 920 | newCurrItem = Math.max(0, Math.min(mCurItem, adapterCount - 1)); 921 | needPopulate = true; 922 | } 923 | continue; 924 | } 925 | 926 | if (ii.position != newPos) { 927 | if (ii.position == mCurItem) { 928 | // Our current item changed position. Follow it. 929 | newCurrItem = newPos; 930 | } 931 | 932 | ii.position = newPos; 933 | needPopulate = true; 934 | } 935 | } 936 | 937 | if (isUpdating) { 938 | mAdapter.finishUpdate(this); 939 | } 940 | 941 | Collections.sort(mItems, COMPARATOR); 942 | 943 | if (needPopulate) { 944 | // Reset our known page widths; populate will recompute them. 945 | final int childCount = getChildCount(); 946 | for (int i = 0; i < childCount; i++) { 947 | final View child = getChildAt(i); 948 | final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 949 | if (!lp.isDecor) { 950 | lp.sizeFactor = 0.f; 951 | } 952 | } 953 | 954 | setCurrentItemInternal(newCurrItem, false, true); 955 | requestLayout(); 956 | } 957 | } 958 | 959 | void populate() { 960 | populate(mCurItem); 961 | } 962 | 963 | void populate(int newCurrentItem) { 964 | ItemInfo oldCurInfo = null; 965 | int focusDirection = View.FOCUS_FORWARD; 966 | if (mCurItem != newCurrentItem) { 967 | if (isOrientationHorizontal()) { 968 | focusDirection = mCurItem < newCurrentItem ? View.FOCUS_RIGHT : View.FOCUS_LEFT; 969 | } else { 970 | focusDirection = mCurItem < newCurrentItem ? View.FOCUS_DOWN : View.FOCUS_UP; 971 | } 972 | oldCurInfo = infoForPosition(mCurItem); 973 | mCurItem = newCurrentItem; 974 | } 975 | 976 | if (mAdapter == null) { 977 | sortChildDrawingOrder(); 978 | return; 979 | } 980 | 981 | // Bail now if we are waiting to populate. This is to hold off 982 | // on creating views from the time the user releases their finger to 983 | // fling to a new position until we have finished the scroll to 984 | // that position, avoiding glitches from happening at that point. 985 | if (mPopulatePending) { 986 | if (DEBUG) Log.i(TAG, "populate is pending, skipping for now..."); 987 | sortChildDrawingOrder(); 988 | return; 989 | } 990 | 991 | // Also, don't populate until we are attached to a window. This is to 992 | // avoid trying to populate before we have restored our view hierarchy 993 | // state and conflicting with what is restored. 994 | if (getWindowToken() == null) { 995 | return; 996 | } 997 | 998 | mAdapter.startUpdate(this); 999 | 1000 | final int pageLimit = mOffscreenPageLimit; 1001 | final int startPos = Math.max(0, mCurItem - pageLimit); 1002 | final int N = mAdapter.getCount(); 1003 | final int endPos = Math.min(N-1, mCurItem + pageLimit); 1004 | 1005 | if (N != mExpectedAdapterCount) { 1006 | String resName; 1007 | try { 1008 | resName = getResources().getResourceName(getId()); 1009 | } catch (Resources.NotFoundException e) { 1010 | resName = Integer.toHexString(getId()); 1011 | } 1012 | throw new IllegalStateException("The application's PagerAdapter changed the adapter's" + 1013 | " contents without calling PagerAdapter#notifyDataSetChanged!" + 1014 | " Expected adapter item count: " + mExpectedAdapterCount + ", found: " + N + 1015 | " Pager id: " + resName + 1016 | " Pager class: " + getClass() + 1017 | " Problematic adapter: " + mAdapter.getClass()); 1018 | } 1019 | 1020 | // Locate the currently focused item or add it if needed. 1021 | int curIndex = -1; 1022 | ItemInfo curItem = null; 1023 | for (curIndex = 0; curIndex < mItems.size(); curIndex++) { 1024 | final ItemInfo ii = mItems.get(curIndex); 1025 | if (ii.position >= mCurItem) { 1026 | if (ii.position == mCurItem) curItem = ii; 1027 | break; 1028 | } 1029 | } 1030 | 1031 | if (curItem == null && N > 0) { 1032 | curItem = addNewItem(mCurItem, curIndex); 1033 | } 1034 | 1035 | // Fill 3x the available width or up to the number of offscreen 1036 | // pages requested to either side, whichever is larger. 1037 | // If we have no current item we have no work to do. 1038 | if (curItem != null) { 1039 | float extraSizeStart = 0.f; 1040 | int itemIndex = curIndex - 1; 1041 | ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null; 1042 | final int paddingStart = isOrientationHorizontal() ? getPaddingLeft() : getPaddingTop(); 1043 | final int clientSize = isOrientationHorizontal() ? getClientWidth() : getClientHeight(); 1044 | final float startSizeNeeded = clientSize <= 0 ? 0 : 1045 | 2.f - curItem.sizeFactor + (float) paddingStart / (float) clientSize; 1046 | for (int pos = mCurItem - 1; pos >= 0; pos--) { 1047 | if (extraSizeStart >= startSizeNeeded && pos < startPos) { 1048 | if (ii == null) { 1049 | break; 1050 | } 1051 | if (pos == ii.position && !ii.scrolling) { 1052 | mItems.remove(itemIndex); 1053 | mAdapter.destroyItem(this, pos, ii.object); 1054 | if (DEBUG) { 1055 | Log.i(TAG, "populate() - destroyItem() with pos: " + pos + 1056 | " view: " + ((View) ii.object)); 1057 | } 1058 | itemIndex--; 1059 | curIndex--; 1060 | ii = itemIndex >= 0 ? mItems.get(itemIndex) : null; 1061 | } 1062 | } else if (ii != null && pos == ii.position) { 1063 | extraSizeStart += ii.sizeFactor; 1064 | itemIndex--; 1065 | ii = itemIndex >= 0 ? mItems.get(itemIndex) : null; 1066 | } else { 1067 | ii = addNewItem(pos, itemIndex + 1); 1068 | extraSizeStart += ii.sizeFactor; 1069 | curIndex++; 1070 | ii = itemIndex >= 0 ? mItems.get(itemIndex) : null; 1071 | } 1072 | } 1073 | 1074 | float extraSizeEnd = curItem.sizeFactor; 1075 | itemIndex = curIndex + 1; 1076 | if (extraSizeEnd < 2.f) { 1077 | ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null; 1078 | final int paddingEnd = isOrientationHorizontal() ? getPaddingRight() : getPaddingBottom(); 1079 | final float endSizeNeeded = clientSize <= 0 ? 0 : 1080 | (float) paddingEnd / (float) clientSize + 2.f; 1081 | for (int pos = mCurItem + 1; pos < N; pos++) { 1082 | if (extraSizeEnd >= endSizeNeeded && pos > endPos) { 1083 | if (ii == null) { 1084 | break; 1085 | } 1086 | if (pos == ii.position && !ii.scrolling) { 1087 | mItems.remove(itemIndex); 1088 | mAdapter.destroyItem(this, pos, ii.object); 1089 | if (DEBUG) { 1090 | Log.i(TAG, "populate() - destroyItem() with pos: " + pos + 1091 | " view: " + ((View) ii.object)); 1092 | } 1093 | ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null; 1094 | } 1095 | } else if (ii != null && pos == ii.position) { 1096 | extraSizeEnd += ii.sizeFactor; 1097 | itemIndex++; 1098 | ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null; 1099 | } else { 1100 | ii = addNewItem(pos, itemIndex); 1101 | itemIndex++; 1102 | extraSizeEnd += ii.sizeFactor; 1103 | ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null; 1104 | } 1105 | } 1106 | } 1107 | 1108 | calculatePageOffsets(curItem, curIndex, oldCurInfo); 1109 | } 1110 | 1111 | if (DEBUG) { 1112 | Log.i(TAG, "Current page list:"); 1113 | for (int i=0; i(); 1161 | } else { 1162 | mDrawingOrderedChildren.clear(); 1163 | } 1164 | final int childCount = getChildCount(); 1165 | for (int i = 0; i < childCount; i++) { 1166 | final View child = getChildAt(i); 1167 | mDrawingOrderedChildren.add(child); 1168 | } 1169 | Collections.sort(mDrawingOrderedChildren, sPositionComparator); 1170 | } 1171 | } 1172 | 1173 | private void calculatePageOffsets(ItemInfo curItem, int curIndex, ItemInfo oldCurInfo) { 1174 | final int N = mAdapter.getCount(); 1175 | final int size = isOrientationHorizontal() ? getClientWidth() : getClientHeight(); 1176 | final float marginOffset = size > 0 ? (float) mPageMargin / size : 0; 1177 | // Fix up offsets for later layout. 1178 | if (oldCurInfo != null) { 1179 | final int oldCurPosition = oldCurInfo.position; 1180 | // Base offsets off of oldCurInfo. 1181 | if (oldCurPosition < curItem.position) { 1182 | int itemIndex = 0; 1183 | ItemInfo ii = null; 1184 | float offset = oldCurInfo.offset + oldCurInfo.sizeFactor + marginOffset; 1185 | for (int pos = oldCurPosition + 1; 1186 | pos <= curItem.position && itemIndex < mItems.size(); pos++) { 1187 | ii = mItems.get(itemIndex); 1188 | while (pos > ii.position && itemIndex < mItems.size() - 1) { 1189 | itemIndex++; 1190 | ii = mItems.get(itemIndex); 1191 | } 1192 | while (pos < ii.position) { 1193 | // We don't have an item populated for this, 1194 | // ask the adapter for an offset. 1195 | offset += mAdapter.getPageSize(pos) + marginOffset; 1196 | pos++; 1197 | } 1198 | ii.offset = offset; 1199 | offset += ii.sizeFactor + marginOffset; 1200 | } 1201 | } else if (oldCurPosition > curItem.position) { 1202 | int itemIndex = mItems.size() - 1; 1203 | ItemInfo ii = null; 1204 | float offset = oldCurInfo.offset; 1205 | for (int pos = oldCurPosition - 1; 1206 | pos >= curItem.position && itemIndex >= 0; pos--) { 1207 | ii = mItems.get(itemIndex); 1208 | while (pos < ii.position && itemIndex > 0) { 1209 | itemIndex--; 1210 | ii = mItems.get(itemIndex); 1211 | } 1212 | while (pos > ii.position) { 1213 | // We don't have an item populated for this, 1214 | // ask the adapter for an offset. 1215 | offset -= mAdapter.getPageSize(pos) + marginOffset; 1216 | pos--; 1217 | } 1218 | offset -= ii.sizeFactor + marginOffset; 1219 | ii.offset = offset; 1220 | } 1221 | } 1222 | } 1223 | 1224 | // Base all offsets off of curItem. 1225 | final int itemCount = mItems.size(); 1226 | float offset = curItem.offset; 1227 | int pos = curItem.position - 1; 1228 | mFirstOffset = curItem.position == 0 ? curItem.offset : -Float.MAX_VALUE; 1229 | mLastOffset = curItem.position == N - 1 ? 1230 | curItem.offset + curItem.sizeFactor - 1 : Float.MAX_VALUE; 1231 | // Previous pages 1232 | for (int i = curIndex - 1; i >= 0; i--, pos--) { 1233 | final ItemInfo ii = mItems.get(i); 1234 | while (pos > ii.position) { 1235 | offset -= mAdapter.getPageSize(pos--) + marginOffset; 1236 | } 1237 | offset -= ii.sizeFactor + marginOffset; 1238 | ii.offset = offset; 1239 | if (ii.position == 0) mFirstOffset = offset; 1240 | } 1241 | offset = curItem.offset + curItem.sizeFactor + marginOffset; 1242 | pos = curItem.position + 1; 1243 | // Next pages 1244 | for (int i = curIndex + 1; i < itemCount; i++, pos++) { 1245 | final ItemInfo ii = mItems.get(i); 1246 | while (pos < ii.position) { 1247 | offset += mAdapter.getPageSize(pos++) + marginOffset; 1248 | } 1249 | if (ii.position == N - 1) { 1250 | mLastOffset = offset + ii.sizeFactor - 1; 1251 | } 1252 | ii.offset = offset; 1253 | offset += ii.sizeFactor + marginOffset; 1254 | } 1255 | 1256 | mNeedCalculatePageOffsets = false; 1257 | } 1258 | 1259 | /** 1260 | * This is the persistent state that is saved by ViewPager. Only needed 1261 | * if you are creating a sublass of ViewPager that must save its own 1262 | * state, in which case it should implement a subclass of this which 1263 | * contains that state. 1264 | */ 1265 | public static class SavedState extends BaseSavedState { 1266 | int position; 1267 | Parcelable adapterState; 1268 | ClassLoader loader; 1269 | 1270 | public SavedState(Parcelable superState) { 1271 | super(superState); 1272 | } 1273 | 1274 | @Override 1275 | public void writeToParcel(Parcel out, int flags) { 1276 | super.writeToParcel(out, flags); 1277 | out.writeInt(position); 1278 | out.writeParcelable(adapterState, flags); 1279 | } 1280 | 1281 | @Override 1282 | public String toString() { 1283 | return "FragmentPager.SavedState{" 1284 | + Integer.toHexString(System.identityHashCode(this)) 1285 | + " position=" + position + "}"; 1286 | } 1287 | 1288 | public static final Parcelable.Creator CREATOR 1289 | = ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks() { 1290 | @Override 1291 | public SavedState createFromParcel(Parcel in, ClassLoader loader) { 1292 | return new SavedState(in, loader); 1293 | } 1294 | @Override 1295 | public SavedState[] newArray(int size) { 1296 | return new SavedState[size]; 1297 | } 1298 | }); 1299 | 1300 | SavedState(Parcel in, ClassLoader loader) { 1301 | super(in); 1302 | if (loader == null) { 1303 | loader = getClass().getClassLoader(); 1304 | } 1305 | position = in.readInt(); 1306 | adapterState = in.readParcelable(loader); 1307 | this.loader = loader; 1308 | } 1309 | } 1310 | 1311 | @Override 1312 | public Parcelable onSaveInstanceState() { 1313 | Parcelable superState = super.onSaveInstanceState(); 1314 | SavedState ss = new SavedState(superState); 1315 | ss.position = mCurItem; 1316 | if (mAdapter != null) { 1317 | ss.adapterState = mAdapter.saveState(); 1318 | } 1319 | return ss; 1320 | } 1321 | 1322 | @Override 1323 | public void onRestoreInstanceState(Parcelable state) { 1324 | if (!(state instanceof SavedState)) { 1325 | super.onRestoreInstanceState(state); 1326 | return; 1327 | } 1328 | 1329 | SavedState ss = (SavedState)state; 1330 | super.onRestoreInstanceState(ss.getSuperState()); 1331 | 1332 | if (mAdapter != null) { 1333 | mAdapter.restoreState(ss.adapterState, ss.loader); 1334 | setCurrentItemInternal(ss.position, false, true); 1335 | } else { 1336 | mRestoredCurItem = ss.position; 1337 | mRestoredAdapterState = ss.adapterState; 1338 | mRestoredClassLoader = ss.loader; 1339 | } 1340 | } 1341 | 1342 | @Override 1343 | public void addView(View child, int index, ViewGroup.LayoutParams params) { 1344 | if (!checkLayoutParams(params)) { 1345 | params = generateLayoutParams(params); 1346 | } 1347 | final LayoutParams lp = (LayoutParams) params; 1348 | lp.isDecor |= child instanceof Decor; 1349 | if (mInLayout) { 1350 | if (lp != null && lp.isDecor) { 1351 | throw new IllegalStateException("Cannot add pager decor view during layout"); 1352 | } 1353 | lp.needsMeasure = true; 1354 | addViewInLayout(child, index, params); 1355 | } else { 1356 | super.addView(child, index, params); 1357 | } 1358 | 1359 | if (USE_CACHE) { 1360 | if (child.getVisibility() != GONE) { 1361 | child.setDrawingCacheEnabled(mScrollingCacheEnabled); 1362 | } else { 1363 | child.setDrawingCacheEnabled(false); 1364 | } 1365 | } 1366 | } 1367 | 1368 | @Override 1369 | public void removeView(View view) { 1370 | if (mInLayout) { 1371 | removeViewInLayout(view); 1372 | } else { 1373 | super.removeView(view); 1374 | } 1375 | } 1376 | 1377 | ItemInfo infoForChild(View child) { 1378 | for (int i=0; i 0 && !mItems.isEmpty()) { 1532 | final int paddingStart = isOrientationHorizontal() ? getPaddingLeft() : getPaddingTop(); 1533 | final int paddingEnd = isOrientationHorizontal() ? getPaddingRight() 1534 | : getPaddingBottom(); 1535 | final int sizeWithMargin = size - paddingStart - paddingEnd + margin; 1536 | final int oldSizeWithMargin = oldSize - paddingStart - paddingEnd + oldMargin; 1537 | final int xPos = getScrollX(); 1538 | final int yPos = getScrollY(); 1539 | 1540 | final float pageOffset = (float) (isOrientationHorizontal() ? xPos : yPos) / oldSizeWithMargin; 1541 | final int newXOffsetPixels = isOrientationHorizontal() ? (int) (pageOffset * sizeWithMargin) : xPos; 1542 | final int newYOffsetPixels = isOrientationHorizontal() ? yPos : (int) (pageOffset * sizeWithMargin); 1543 | 1544 | scrollTo(newXOffsetPixels, newYOffsetPixels); 1545 | if (!mScroller.isFinished()) { 1546 | // We now return to your regularly schedules scroll, already in progress. 1547 | final int newDuration = mScroller.getDuration() - mScroller.timePassed(); 1548 | ItemInfo targetInfo = infoForPosition(mCurItem); 1549 | if (isOrientationHorizontal()) { 1550 | mScroller.startScroll(newXOffsetPixels, 0, 1551 | (int) (targetInfo.offset * size), 0, newDuration); 1552 | } else { 1553 | mScroller.startScroll(0, newYOffsetPixels, 1554 | (int) (targetInfo.offset * size), 0, newDuration); 1555 | } 1556 | } 1557 | } 1558 | } 1559 | 1560 | @Override 1561 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 1562 | final int count = getChildCount(); 1563 | int width = r - l; 1564 | int height = b - t; 1565 | int paddingLeft = getPaddingLeft(); 1566 | int paddingTop = getPaddingTop(); 1567 | int paddingRight = getPaddingRight(); 1568 | int paddingBottom = getPaddingBottom(); 1569 | final int scrollX = getScrollX(); 1570 | final int scrollY = getScrollY(); 1571 | 1572 | int decorCount = 0; 1573 | 1574 | // First pass - decor views. We need to do this in two passes so that 1575 | // we have the proper offsets for non-decor views later. 1576 | for (int i = 0; i < count; i++) { 1577 | final View child = getChildAt(i); 1578 | if (child.getVisibility() != GONE) { 1579 | final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 1580 | int childLeft = 0; 1581 | int childTop = 0; 1582 | if (lp.isDecor) { 1583 | final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK; 1584 | final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK; 1585 | switch (hgrav) { 1586 | default: 1587 | childLeft = paddingLeft; 1588 | break; 1589 | case Gravity.LEFT: 1590 | childLeft = paddingLeft; 1591 | paddingLeft += child.getMeasuredWidth(); 1592 | break; 1593 | case Gravity.CENTER_HORIZONTAL: 1594 | childLeft = Math.max((width - child.getMeasuredWidth()) / 2, 1595 | paddingLeft); 1596 | break; 1597 | case Gravity.RIGHT: 1598 | childLeft = width - paddingRight - child.getMeasuredWidth(); 1599 | paddingRight += child.getMeasuredWidth(); 1600 | break; 1601 | } 1602 | switch (vgrav) { 1603 | default: 1604 | childTop = paddingTop; 1605 | break; 1606 | case Gravity.TOP: 1607 | childTop = paddingTop; 1608 | paddingTop += child.getMeasuredHeight(); 1609 | break; 1610 | case Gravity.CENTER_VERTICAL: 1611 | childTop = Math.max((height - child.getMeasuredHeight()) / 2, 1612 | paddingTop); 1613 | break; 1614 | case Gravity.BOTTOM: 1615 | childTop = height - paddingBottom - child.getMeasuredHeight(); 1616 | paddingBottom += child.getMeasuredHeight(); 1617 | break; 1618 | } 1619 | if (isOrientationHorizontal()) { 1620 | childLeft += scrollX; 1621 | } else { 1622 | childTop += scrollY; 1623 | } 1624 | child.layout(childLeft, childTop, 1625 | childLeft + child.getMeasuredWidth(), 1626 | childTop + child.getMeasuredHeight()); 1627 | decorCount++; 1628 | } 1629 | } 1630 | } 1631 | 1632 | int childSize = 0; 1633 | if (isOrientationHorizontal()) { 1634 | childSize = width - paddingLeft - paddingRight; 1635 | } else { 1636 | childSize = height - paddingTop - paddingBottom; 1637 | } 1638 | // Page views. Do this once we have the right padding offsets from above. 1639 | for (int i = 0; i < count; i++) { 1640 | final View child = getChildAt(i); 1641 | if (child.getVisibility() != GONE) { 1642 | final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 1643 | ItemInfo ii; 1644 | if (!lp.isDecor && (ii = infoForChild(child)) != null) { 1645 | int loff = (int) (childSize * ii.offset); 1646 | int childLeft = paddingLeft + (isOrientationHorizontal() ? loff : 0); 1647 | int childTop = paddingTop + (isOrientationHorizontal() ? 0 : loff); 1648 | if (lp.needsMeasure) { 1649 | // This was added during layout and needs measurement. 1650 | // Do it now that we know what we're working with. 1651 | lp.needsMeasure = false; 1652 | int widthSpec = 0, heightSpec = 0; 1653 | if (isOrientationHorizontal()) { 1654 | widthSpec = MeasureSpec.makeMeasureSpec( 1655 | (int) (childSize * lp.sizeFactor), 1656 | MeasureSpec.EXACTLY); 1657 | heightSpec = MeasureSpec.makeMeasureSpec( 1658 | (int) (height - paddingTop - paddingBottom), 1659 | MeasureSpec.EXACTLY); 1660 | } else { 1661 | widthSpec = MeasureSpec.makeMeasureSpec( 1662 | (int) (width - paddingLeft - paddingRight), 1663 | MeasureSpec.EXACTLY); 1664 | heightSpec = MeasureSpec.makeMeasureSpec( 1665 | (int) (childSize * lp.sizeFactor), 1666 | MeasureSpec.EXACTLY); 1667 | } 1668 | child.measure(widthSpec, heightSpec); 1669 | } 1670 | if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object 1671 | + ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth() 1672 | + "x" + child.getMeasuredHeight()); 1673 | child.layout(childLeft, childTop, 1674 | childLeft + child.getMeasuredWidth(), 1675 | childTop + child.getMeasuredHeight()); 1676 | } 1677 | } 1678 | } 1679 | mLeftPageBounds = paddingLeft; 1680 | mTopPageBounds = paddingTop; 1681 | mRightPageBounds = width - paddingRight; 1682 | mBottomPageBounds = height - paddingBottom; 1683 | mDecorChildCount = decorCount; 1684 | 1685 | if (mFirstLayout) { 1686 | scrollToItem(mCurItem, false, 0, false); 1687 | } 1688 | mFirstLayout = false; 1689 | } 1690 | 1691 | @Override 1692 | public void computeScroll() { 1693 | if (!mScroller.isFinished() && mScroller.computeScrollOffset()) { 1694 | int oldX = getScrollX(); 1695 | int oldY = getScrollY(); 1696 | int x = mScroller.getCurrX(); 1697 | int y = mScroller.getCurrY(); 1698 | 1699 | if (oldX != x || oldY != y) { 1700 | scrollTo(x, y); 1701 | if (!pageScrolled(isOrientationHorizontal() ? x : y)) { 1702 | mScroller.abortAnimation(); 1703 | if (isOrientationHorizontal()) { 1704 | scrollTo(0, y); 1705 | } else { 1706 | scrollTo(x, 0); 1707 | } 1708 | } 1709 | } 1710 | 1711 | // Keep on drawing until the animation has finished. 1712 | ViewCompat.postInvalidateOnAnimation(this); 1713 | return; 1714 | } 1715 | 1716 | // Done with scroll, clean up state. 1717 | completeScroll(true); 1718 | } 1719 | 1720 | private boolean pageScrolled(int pos) { 1721 | if (mItems.size() == 0) { 1722 | mCalledSuper = false; 1723 | onPageScrolled(0, 0, 0); 1724 | if (!mCalledSuper) { 1725 | throw new IllegalStateException( 1726 | "onPageScrolled did not call superclass implementation"); 1727 | } 1728 | return false; 1729 | } 1730 | final ItemInfo ii = infoForCurrentScrollPosition(); 1731 | final int size = isOrientationHorizontal() ? getClientWidth() : getClientHeight(); 1732 | final int sizeWithMargin = size + mPageMargin; 1733 | final float marginOffset = (float) mPageMargin / size; 1734 | final int currentPage = ii.position; 1735 | final float pageOffset = (((float) pos / size) - ii.offset) / 1736 | (ii.sizeFactor + marginOffset); 1737 | final int offsetPixels = (int) (pageOffset * sizeWithMargin); 1738 | 1739 | mCalledSuper = false; 1740 | onPageScrolled(currentPage, pageOffset, offsetPixels); 1741 | if (!mCalledSuper) { 1742 | throw new IllegalStateException( 1743 | "onPageScrolled did not call superclass implementation"); 1744 | } 1745 | return true; 1746 | } 1747 | 1748 | /** 1749 | * This method will be invoked when the current page is scrolled, either as part 1750 | * of a programmatically initiated smooth scroll or a user initiated touch scroll. 1751 | * If you override this method you must call through to the superclass implementation 1752 | * (e.g. super.onPageScrolled(position, offset, offsetPixels)) before onPageScrolled 1753 | * returns. 1754 | * 1755 | * @param position Position index of the first page currently being displayed. 1756 | * Page position+1 will be visible if positionOffset is nonzero. 1757 | * @param offset Value from [0, 1) indicating the offset from the page at position. 1758 | * @param offsetPixels Value in pixels indicating the offset from position. 1759 | */ 1760 | protected void onPageScrolled(int position, float offset, int offsetPixels) { 1761 | // Offset any decor views if needed - keep them on-screen at all times. 1762 | if (mDecorChildCount > 0) { 1763 | // TODO This is where I start getting tired. Refactor this better later. 1764 | if (isOrientationHorizontal()) { 1765 | final int scrollX = getScrollX(); 1766 | int paddingLeft = getPaddingLeft(); 1767 | int paddingRight = getPaddingRight(); 1768 | final int width = getWidth(); 1769 | final int childCount = getChildCount(); 1770 | for (int i = 0; i < childCount; i++) { 1771 | final View child = getChildAt(i); 1772 | final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 1773 | if (!lp.isDecor) continue; 1774 | 1775 | final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK; 1776 | int childLeft = 0; 1777 | switch (hgrav) { 1778 | default: 1779 | childLeft = paddingLeft; 1780 | break; 1781 | case Gravity.LEFT: 1782 | childLeft = paddingLeft; 1783 | paddingLeft += child.getWidth(); 1784 | break; 1785 | case Gravity.CENTER_HORIZONTAL: 1786 | childLeft = Math.max((width - child.getMeasuredWidth()) / 2, 1787 | paddingLeft); 1788 | break; 1789 | case Gravity.RIGHT: 1790 | childLeft = width - paddingRight - child.getMeasuredWidth(); 1791 | paddingRight += child.getMeasuredWidth(); 1792 | break; 1793 | } 1794 | childLeft += scrollX; 1795 | 1796 | final int childOffset = childLeft - child.getLeft(); 1797 | if (childOffset != 0) { 1798 | child.offsetLeftAndRight(childOffset); 1799 | } 1800 | } 1801 | } else { 1802 | final int scrollY = getScrollY(); 1803 | int paddingTop = getPaddingTop(); 1804 | int paddingBottom = getPaddingBottom(); 1805 | final int height = getHeight(); 1806 | final int childCount = getChildCount(); 1807 | for (int i = 0; i < childCount; i++) { 1808 | final View child = getChildAt(i); 1809 | final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 1810 | if (!lp.isDecor) continue; 1811 | 1812 | final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK; 1813 | int childTop = 0; 1814 | switch (vgrav) { 1815 | default: 1816 | childTop = paddingTop; 1817 | break; 1818 | case Gravity.TOP: 1819 | childTop = paddingTop; 1820 | paddingTop += child.getHeight(); 1821 | break; 1822 | case Gravity.CENTER_VERTICAL: 1823 | childTop = Math.max((height - child.getMeasuredHeight()) / 2, 1824 | paddingTop); 1825 | break; 1826 | case Gravity.BOTTOM: 1827 | childTop = height - paddingBottom - child.getMeasuredHeight(); 1828 | paddingBottom += child.getMeasuredHeight(); 1829 | break; 1830 | } 1831 | childTop += scrollY; 1832 | 1833 | final int childOffset = childTop - child.getTop(); 1834 | if (childOffset != 0) { 1835 | child.offsetTopAndBottom(childOffset); 1836 | } 1837 | } 1838 | } 1839 | } 1840 | 1841 | if (mOnPageChangeListener != null) { 1842 | mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels); 1843 | } 1844 | if (mInternalPageChangeListener != null) { 1845 | mInternalPageChangeListener.onPageScrolled(position, offset, offsetPixels); 1846 | } 1847 | 1848 | if (mPageTransformer != null) { 1849 | final boolean horizontal = isOrientationHorizontal(); 1850 | final int scroll = horizontal ? getScrollX() : getScrollY(); 1851 | final int childCount = getChildCount(); 1852 | for (int i = 0; i < childCount; i++) { 1853 | final View child = getChildAt(i); 1854 | final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 1855 | 1856 | if (lp.isDecor) continue; 1857 | 1858 | float transformPos; 1859 | if (horizontal) { 1860 | transformPos = (float) (child.getLeft() - scroll) / getClientWidth(); 1861 | } else { 1862 | transformPos = (float) (child.getTop() - scroll) / getClientHeight(); 1863 | } 1864 | mPageTransformer.transformPage(child, transformPos); 1865 | } 1866 | } 1867 | 1868 | mCalledSuper = true; 1869 | } 1870 | 1871 | private void completeScroll(boolean postEvents) { 1872 | boolean needPopulate = mScrollState == SCROLL_STATE_SETTLING; 1873 | if (needPopulate) { 1874 | // Done with scroll, no longer want to cache view drawing. 1875 | setScrollingCacheEnabled(false); 1876 | mScroller.abortAnimation(); 1877 | int oldX = getScrollX(); 1878 | int oldY = getScrollY(); 1879 | int x = mScroller.getCurrX(); 1880 | int y = mScroller.getCurrY(); 1881 | if (oldX != x || oldY != y) { 1882 | scrollTo(x, y); 1883 | } 1884 | } 1885 | mPopulatePending = false; 1886 | for (int i=0; i 0) || (x > getWidth() - mGutterSize && dx < 0); 1905 | } else { 1906 | return (x < mGutterSize && dx > 0) || (x > getHeight() - mGutterSize && dx < 0); 1907 | } 1908 | } 1909 | 1910 | private void enableLayers(boolean enable) { 1911 | final int childCount = getChildCount(); 1912 | for (int i = 0; i < childCount; i++) { 1913 | final int layerType = enable ? 1914 | ViewCompat.LAYER_TYPE_HARDWARE : ViewCompat.LAYER_TYPE_NONE; 1915 | ViewCompat.setLayerType(getChildAt(i), layerType, null); 1916 | } 1917 | } 1918 | 1919 | @Override 1920 | public boolean onInterceptTouchEvent(MotionEvent ev) { 1921 | /* 1922 | * This method JUST determines whether we want to intercept the motion. 1923 | * If we return true, onMotionEvent will be called and we do the actual 1924 | * scrolling there. 1925 | */ 1926 | 1927 | final int action = ev.getAction() & MotionEventCompat.ACTION_MASK; 1928 | 1929 | // Always take care of the touch gesture being complete. 1930 | if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { 1931 | // Release the drag. 1932 | if (DEBUG) Log.v(TAG, "Intercept done!"); 1933 | mIsBeingDragged = false; 1934 | mIsUnableToDrag = false; 1935 | mActivePointerId = INVALID_POINTER; 1936 | if (mVelocityTracker != null) { 1937 | mVelocityTracker.recycle(); 1938 | mVelocityTracker = null; 1939 | } 1940 | return false; 1941 | } 1942 | 1943 | // Nothing more to do here if we have decided whether or not we 1944 | // are dragging. 1945 | if (action != MotionEvent.ACTION_DOWN) { 1946 | if (mIsBeingDragged) { 1947 | if (DEBUG) Log.v(TAG, "Intercept returning true!"); 1948 | return true; 1949 | } 1950 | if (mIsUnableToDrag) { 1951 | if (DEBUG) Log.v(TAG, "Intercept returning false!"); 1952 | return false; 1953 | } 1954 | } 1955 | 1956 | switch (action) { 1957 | case MotionEvent.ACTION_MOVE: { 1958 | /* 1959 | * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check 1960 | * whether the user has moved far enough from his original down touch. 1961 | */ 1962 | 1963 | /* 1964 | * Locally do absolute value. mLastMotionY is set to the y value 1965 | * of the down event. 1966 | */ 1967 | final int activePointerId = mActivePointerId; 1968 | if (activePointerId == INVALID_POINTER) { 1969 | // If we don't have a valid id, the touch down wasn't on content. 1970 | break; 1971 | } 1972 | 1973 | final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId); 1974 | final float x = MotionEventCompat.getX(ev, pointerIndex); 1975 | final float dx = x - mLastMotionX; 1976 | final float xDiff = Math.abs(dx); 1977 | final float y = MotionEventCompat.getY(ev, pointerIndex); 1978 | final float dy = y - mLastMotionY; 1979 | final float yDiff = Math.abs(y - mInitialMotionY); 1980 | if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff); 1981 | 1982 | if (isOrientationHorizontal()) { 1983 | if (dx != 0 && !isGutterDrag(mLastMotionX, dx) && 1984 | canScroll(this, false, (int) dx, (int) x, (int) y)) { 1985 | // Nested view has scrollable area under this point. Let it be handled there. 1986 | mLastMotionX = x; 1987 | mLastMotionY = y; 1988 | mIsUnableToDrag = true; 1989 | return false; 1990 | } 1991 | if (xDiff > mTouchSlop && xDiff * 0.5f > yDiff) { 1992 | if (DEBUG) Log.v(TAG, "Starting drag!"); 1993 | mIsBeingDragged = true; 1994 | setScrollState(SCROLL_STATE_DRAGGING); 1995 | mLastMotionX = dx > 0 ? mInitialMotionX + mTouchSlop : 1996 | mInitialMotionX - mTouchSlop; 1997 | mLastMotionY = y; 1998 | setScrollingCacheEnabled(true); 1999 | } else if (yDiff > mTouchSlop) { 2000 | // The finger has moved enough in the vertical 2001 | // direction to be counted as a drag... abort 2002 | // any attempt to drag horizontally, to work correctly 2003 | // with children that have scrolling containers. 2004 | if (DEBUG) Log.v(TAG, "Starting unable to drag!"); 2005 | mIsUnableToDrag = true; 2006 | } 2007 | if (mIsBeingDragged) { 2008 | // Scroll to follow the motion event 2009 | if (performDrag(x)) { 2010 | ViewCompat.postInvalidateOnAnimation(this); 2011 | } 2012 | } 2013 | } else { 2014 | if (dy != 0 && !isGutterDrag(mLastMotionY, dy) && 2015 | canScroll(this, false, (int) dx, (int) x, (int) y)) { 2016 | // Nested view has scrollable area under this point. Let it be handled there. 2017 | mLastMotionX = x; 2018 | mLastMotionY = y; 2019 | mIsUnableToDrag = true; 2020 | return false; 2021 | } 2022 | if (yDiff > mTouchSlop && yDiff * 0.5f > xDiff) { 2023 | if (DEBUG) Log.v(TAG, "Starting drag!"); 2024 | mIsBeingDragged = true; 2025 | setScrollState(SCROLL_STATE_DRAGGING); 2026 | mLastMotionY = dy > 0 ? mInitialMotionY + mTouchSlop : 2027 | mInitialMotionY - mTouchSlop; 2028 | mLastMotionX = x; 2029 | setScrollingCacheEnabled(true); 2030 | } else if (xDiff > mTouchSlop) { 2031 | // The finger has moved enough in the vertical 2032 | // direction to be counted as a drag... abort 2033 | // any attempt to drag horizontally, to work correctly 2034 | // with children that have scrolling containers. 2035 | if (DEBUG) Log.v(TAG, "Starting unable to drag!"); 2036 | mIsUnableToDrag = true; 2037 | } 2038 | if (mIsBeingDragged) { 2039 | // Scroll to follow the motion event 2040 | if (performDrag(y)) { 2041 | ViewCompat.postInvalidateOnAnimation(this); 2042 | } 2043 | } 2044 | } 2045 | break; 2046 | } 2047 | 2048 | case MotionEvent.ACTION_DOWN: { 2049 | /* 2050 | * Remember location of down touch. 2051 | * ACTION_DOWN always refers to pointer index 0. 2052 | */ 2053 | mLastMotionX = mInitialMotionX = ev.getX(); 2054 | mLastMotionY = mInitialMotionY = ev.getY(); 2055 | mActivePointerId = MotionEventCompat.getPointerId(ev, 0); 2056 | mIsUnableToDrag = false; 2057 | 2058 | mScroller.computeScrollOffset(); 2059 | int distance = 0; 2060 | if (isOrientationHorizontal()) { 2061 | distance = Math.abs(mScroller.getFinalX() - mScroller.getCurrX()); 2062 | } else { 2063 | distance = Math.abs(mScroller.getFinalY() - mScroller.getCurrY()); 2064 | } 2065 | if (mScrollState == SCROLL_STATE_SETTLING && distance > mCloseEnough) { 2066 | // Let the user 'catch' the pager as it animates. 2067 | mScroller.abortAnimation(); 2068 | mPopulatePending = false; 2069 | populate(); 2070 | mIsBeingDragged = true; 2071 | setScrollState(SCROLL_STATE_DRAGGING); 2072 | } else { 2073 | completeScroll(false); 2074 | mIsBeingDragged = false; 2075 | } 2076 | 2077 | if (DEBUG) Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY 2078 | + " mIsBeingDragged=" + mIsBeingDragged 2079 | + "mIsUnableToDrag=" + mIsUnableToDrag); 2080 | break; 2081 | } 2082 | 2083 | case MotionEventCompat.ACTION_POINTER_UP: 2084 | onSecondaryPointerUp(ev); 2085 | break; 2086 | } 2087 | 2088 | if (mVelocityTracker == null) { 2089 | mVelocityTracker = VelocityTracker.obtain(); 2090 | } 2091 | mVelocityTracker.addMovement(ev); 2092 | 2093 | /* 2094 | * The only time we want to intercept motion events is if we are in the 2095 | * drag mode. 2096 | */ 2097 | return mIsBeingDragged; 2098 | } 2099 | 2100 | @Override 2101 | public boolean onTouchEvent(MotionEvent ev) { 2102 | if (mFakeDragging) { 2103 | // A fake drag is in progress already, ignore this real one 2104 | // but still eat the touch events. 2105 | // (It is likely that the user is multi-touching the screen.) 2106 | return true; 2107 | } 2108 | 2109 | if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) { 2110 | // Don't handle edge touches immediately -- they may actually belong to one of our 2111 | // descendants. 2112 | return false; 2113 | } 2114 | 2115 | if (mAdapter == null || mAdapter.getCount() == 0) { 2116 | // Nothing to present or scroll; nothing to touch. 2117 | return false; 2118 | } 2119 | 2120 | if (mVelocityTracker == null) { 2121 | mVelocityTracker = VelocityTracker.obtain(); 2122 | } 2123 | mVelocityTracker.addMovement(ev); 2124 | 2125 | final int action = ev.getAction(); 2126 | boolean needsInvalidate = false; 2127 | 2128 | switch (action & MotionEventCompat.ACTION_MASK) { 2129 | case MotionEvent.ACTION_DOWN: { 2130 | mScroller.abortAnimation(); 2131 | mPopulatePending = false; 2132 | populate(); 2133 | mIsBeingDragged = true; 2134 | setScrollState(SCROLL_STATE_DRAGGING); 2135 | 2136 | // Remember where the motion event started 2137 | mLastMotionX = mInitialMotionX = ev.getX(); 2138 | mLastMotionY = mInitialMotionY = ev.getY(); 2139 | mActivePointerId = MotionEventCompat.getPointerId(ev, 0); 2140 | break; 2141 | } 2142 | case MotionEvent.ACTION_MOVE: 2143 | if (!mIsBeingDragged) { 2144 | final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); 2145 | final float x = MotionEventCompat.getX(ev, pointerIndex); 2146 | final float xDiff = Math.abs(x - mLastMotionX); 2147 | final float y = MotionEventCompat.getY(ev, pointerIndex); 2148 | final float yDiff = Math.abs(y - mLastMotionY); 2149 | if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff); 2150 | if (isOrientationHorizontal()) { 2151 | if (xDiff > mTouchSlop && xDiff > yDiff) { 2152 | if (DEBUG) Log.v(TAG, "Starting drag!"); 2153 | mIsBeingDragged = true; 2154 | mLastMotionX = x - mInitialMotionX > 0 ? mInitialMotionX + mTouchSlop : 2155 | mInitialMotionX - mTouchSlop; 2156 | mLastMotionY = y; 2157 | setScrollState(SCROLL_STATE_DRAGGING); 2158 | setScrollingCacheEnabled(true); 2159 | } 2160 | } else { 2161 | if (yDiff > mTouchSlop && yDiff > xDiff) { 2162 | if (DEBUG) Log.v(TAG, "Starting drag!"); 2163 | mIsBeingDragged = true; 2164 | mLastMotionY = y - mInitialMotionY > 0 ? mInitialMotionY + mTouchSlop : 2165 | mInitialMotionY - mTouchSlop; 2166 | mLastMotionX = x; 2167 | setScrollState(SCROLL_STATE_DRAGGING); 2168 | setScrollingCacheEnabled(true); 2169 | } 2170 | } 2171 | } 2172 | // Not else! Note that mIsBeingDragged can be set above. 2173 | if (mIsBeingDragged) { 2174 | // Scroll to follow the motion event 2175 | final int activePointerIndex = MotionEventCompat.findPointerIndex( 2176 | ev, mActivePointerId); 2177 | float x = 0; 2178 | if (isOrientationHorizontal()) { 2179 | x = MotionEventCompat.getX(ev, activePointerIndex); 2180 | } else { 2181 | x = MotionEventCompat.getY(ev, activePointerIndex); 2182 | } 2183 | needsInvalidate |= performDrag(x); 2184 | } 2185 | break; 2186 | case MotionEvent.ACTION_UP: 2187 | if (mIsBeingDragged) { 2188 | final VelocityTracker velocityTracker = mVelocityTracker; 2189 | velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 2190 | mPopulatePending = true; 2191 | final ItemInfo ii = infoForCurrentScrollPosition(); 2192 | final int currentPage = ii.position; 2193 | 2194 | int initialVelocity, totalDelta; 2195 | float pageOffset; 2196 | if (isOrientationHorizontal()) { 2197 | initialVelocity = (int) VelocityTrackerCompat.getXVelocity( 2198 | velocityTracker, mActivePointerId); 2199 | final int width = getClientWidth(); 2200 | final int scrollX = getScrollX(); 2201 | pageOffset = (((float) scrollX / width) - ii.offset) / ii.sizeFactor; 2202 | final int activePointerIndex = 2203 | MotionEventCompat.findPointerIndex(ev, mActivePointerId); 2204 | final float x = MotionEventCompat.getX(ev, activePointerIndex); 2205 | totalDelta = (int) (x - mInitialMotionX); 2206 | } else { 2207 | initialVelocity = (int) VelocityTrackerCompat.getYVelocity( 2208 | velocityTracker, mActivePointerId); 2209 | final int height = getClientHeight(); 2210 | final int scrollY = getScrollY(); 2211 | pageOffset = (((float) scrollY / height) - ii.offset) / ii.sizeFactor; 2212 | final int activePointerIndex = 2213 | MotionEventCompat.findPointerIndex(ev, mActivePointerId); 2214 | final float y = MotionEventCompat.getY(ev, activePointerIndex); 2215 | totalDelta = (int) (y - mInitialMotionY); 2216 | } 2217 | 2218 | int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity, 2219 | totalDelta); 2220 | setCurrentItemInternal(nextPage, true, true, initialVelocity); 2221 | 2222 | mActivePointerId = INVALID_POINTER; 2223 | endDrag(); 2224 | needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease(); 2225 | } 2226 | break; 2227 | case MotionEvent.ACTION_CANCEL: 2228 | if (mIsBeingDragged) { 2229 | scrollToItem(mCurItem, true, 0, false); 2230 | mActivePointerId = INVALID_POINTER; 2231 | endDrag(); 2232 | needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease(); 2233 | } 2234 | break; 2235 | case MotionEventCompat.ACTION_POINTER_DOWN: { 2236 | final int index = MotionEventCompat.getActionIndex(ev); 2237 | final float x = MotionEventCompat.getX(ev, index); 2238 | final float y = MotionEventCompat.getY(ev, index); 2239 | mLastMotionX = x; 2240 | mLastMotionY = y; 2241 | mActivePointerId = MotionEventCompat.getPointerId(ev, index); 2242 | break; 2243 | } 2244 | case MotionEventCompat.ACTION_POINTER_UP: 2245 | onSecondaryPointerUp(ev); 2246 | mLastMotionX = MotionEventCompat.getX(ev, 2247 | MotionEventCompat.findPointerIndex(ev, mActivePointerId)); 2248 | mLastMotionY = MotionEventCompat.getY(ev, 2249 | MotionEventCompat.findPointerIndex(ev, mActivePointerId)); 2250 | break; 2251 | } 2252 | if (needsInvalidate) { 2253 | ViewCompat.postInvalidateOnAnimation(this); 2254 | } 2255 | return true; 2256 | } 2257 | 2258 | private boolean performDrag(float pos) { 2259 | boolean needsInvalidate = false; 2260 | 2261 | if (isOrientationHorizontal()) { 2262 | final float deltaX = mLastMotionX - pos; 2263 | mLastMotionX = pos; 2264 | 2265 | float oldScrollX = getScrollX(); 2266 | float scrollX = oldScrollX + deltaX; 2267 | final int width = getClientWidth(); 2268 | 2269 | float leftBound = width * mFirstOffset; 2270 | float rightBound = width * mLastOffset; 2271 | boolean leftAbsolute = true; 2272 | boolean rightAbsolute = true; 2273 | 2274 | final ItemInfo firstItem = mItems.get(0); 2275 | final ItemInfo lastItem = mItems.get(mItems.size() - 1); 2276 | if (firstItem.position != 0) { 2277 | leftAbsolute = false; 2278 | leftBound = firstItem.offset * width; 2279 | } 2280 | if (lastItem.position != mAdapter.getCount() - 1) { 2281 | rightAbsolute = false; 2282 | rightBound = lastItem.offset * width; 2283 | } 2284 | 2285 | if (scrollX < leftBound) { 2286 | if (leftAbsolute) { 2287 | float over = leftBound - scrollX; 2288 | needsInvalidate = mLeftEdge.onPull(Math.abs(over) / width); 2289 | } 2290 | scrollX = leftBound; 2291 | } else if (scrollX > rightBound) { 2292 | if (rightAbsolute) { 2293 | float over = scrollX - rightBound; 2294 | needsInvalidate = mRightEdge.onPull(Math.abs(over) / width); 2295 | } 2296 | scrollX = rightBound; 2297 | } 2298 | // Don't lose the rounded component 2299 | mLastMotionX += scrollX - (int) scrollX; 2300 | scrollTo((int) scrollX, getScrollY()); 2301 | pageScrolled((int) scrollX); 2302 | } else { 2303 | final float deltaY = mLastMotionY - pos; 2304 | mLastMotionY = pos; 2305 | 2306 | float oldScrollY = getScrollY(); 2307 | float scrollY = oldScrollY + deltaY; 2308 | final int height = getClientHeight(); 2309 | 2310 | float topBound = height * mFirstOffset; 2311 | float bottomBound = height * mLastOffset; 2312 | boolean topAbsolute = true; 2313 | boolean bottomAbsolute = true; 2314 | 2315 | final ItemInfo firstItem = mItems.get(0); 2316 | final ItemInfo lastItem = mItems.get(mItems.size() - 1); 2317 | if (firstItem.position != 0) { 2318 | topAbsolute = false; 2319 | topBound = firstItem.offset * height; 2320 | } 2321 | if (lastItem.position != mAdapter.getCount() - 1) { 2322 | bottomAbsolute = false; 2323 | bottomBound = lastItem.offset * height; 2324 | } 2325 | 2326 | if (scrollY < topBound) { 2327 | if (topAbsolute) { 2328 | float over = topBound - scrollY; 2329 | needsInvalidate = mLeftEdge.onPull(Math.abs(over) / height); 2330 | } 2331 | scrollY = topBound; 2332 | } else if (scrollY > bottomBound) { 2333 | if (bottomAbsolute) { 2334 | float over = scrollY - bottomBound; 2335 | needsInvalidate = mRightEdge.onPull(Math.abs(over) / height); 2336 | } 2337 | scrollY = bottomBound; 2338 | } 2339 | // Don't lose the rounded component 2340 | mLastMotionX += scrollY - (int) scrollY; 2341 | scrollTo(getScrollX(), (int) scrollY); 2342 | pageScrolled((int) scrollY); 2343 | } 2344 | 2345 | return needsInvalidate; 2346 | } 2347 | 2348 | /** 2349 | * @return Info about the page at the current scroll position. 2350 | * This can be synthetic for a missing middle page; the 'object' field can be null. 2351 | */ 2352 | private ItemInfo infoForCurrentScrollPosition() { 2353 | final int scroll = isOrientationHorizontal() ? getScrollX() : getScrollY(); 2354 | final int size = isOrientationHorizontal() ? getClientWidth() : getClientHeight(); 2355 | final float scrollOffset = size > 0 ? (float) scroll / size : 0; 2356 | final float marginOffset = size > 0 ? (float) mPageMargin / size : 0; 2357 | int lastPos = -1; 2358 | float lastOffset = 0.f; 2359 | float lastSize = 0.f; 2360 | boolean first = true; 2361 | 2362 | ItemInfo lastItem = null; 2363 | for (int i = 0; i < mItems.size(); i++) { 2364 | ItemInfo ii = mItems.get(i); 2365 | float offset; 2366 | if (!first && ii.position != lastPos + 1) { 2367 | // Create a synthetic item for a missing page. 2368 | ii = mTempItem; 2369 | ii.offset = lastOffset + lastSize + marginOffset; 2370 | ii.position = lastPos + 1; 2371 | ii.sizeFactor = mAdapter.getPageSize(ii.position); 2372 | i--; 2373 | } 2374 | offset = ii.offset; 2375 | 2376 | final float startBound = offset; 2377 | final float endBound = offset + ii.sizeFactor + marginOffset; 2378 | if (first || scrollOffset >= startBound) { 2379 | if (scrollOffset < endBound || i == mItems.size() - 1) { 2380 | return ii; 2381 | } 2382 | } else { 2383 | return lastItem; 2384 | } 2385 | first = false; 2386 | lastPos = ii.position; 2387 | lastOffset = offset; 2388 | lastSize = ii.sizeFactor; 2389 | lastItem = ii; 2390 | } 2391 | 2392 | return lastItem; 2393 | } 2394 | 2395 | private int determineTargetPage(int currentPage, float pageOffset, int velocity, int delta) { 2396 | int targetPage; 2397 | if (Math.abs(delta) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) { 2398 | targetPage = velocity > 0 ? currentPage : currentPage + 1; 2399 | } else { 2400 | final float truncator = currentPage >= mCurItem ? 0.4f : 0.6f; 2401 | targetPage = (int) (currentPage + pageOffset + truncator); 2402 | } 2403 | 2404 | if (mItems.size() > 0) { 2405 | final ItemInfo firstItem = mItems.get(0); 2406 | final ItemInfo lastItem = mItems.get(mItems.size() - 1); 2407 | 2408 | // Only let the user target pages we have items for 2409 | targetPage = Math.max(firstItem.position, Math.min(targetPage, lastItem.position)); 2410 | } 2411 | 2412 | return targetPage; 2413 | } 2414 | 2415 | @Override 2416 | public void draw(Canvas canvas) { 2417 | super.draw(canvas); 2418 | boolean needsInvalidate = false; 2419 | 2420 | final int overScrollMode = ViewCompat.getOverScrollMode(this); 2421 | if (overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS || 2422 | (overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS && 2423 | mAdapter != null && mAdapter.getCount() > 1)) { 2424 | if (!mLeftEdge.isFinished()) { 2425 | final int restoreCount = canvas.save(); 2426 | final int width = isOrientationHorizontal() 2427 | ? getHeight() - getPaddingTop() - getPaddingBottom() 2428 | : getWidth() - getPaddingLeft() - getPaddingRight(); 2429 | final int height = isOrientationHorizontal() 2430 | ? getWidth() - getPaddingLeft() - getPaddingRight() 2431 | : getHeight() - getPaddingTop() - getPaddingBottom(); 2432 | 2433 | if (isOrientationHorizontal()) { 2434 | canvas.rotate(270); 2435 | canvas.translate(-width + getPaddingTop(), mFirstOffset * height); 2436 | } 2437 | mLeftEdge.setSize(width, height); 2438 | needsInvalidate |= mLeftEdge.draw(canvas); 2439 | canvas.restoreToCount(restoreCount); 2440 | } 2441 | if (!mRightEdge.isFinished()) { 2442 | final int restoreCount = canvas.save(); 2443 | final int width = isOrientationHorizontal() 2444 | ? getHeight() - getPaddingTop() - getPaddingBottom() 2445 | : getWidth() - getPaddingLeft() - getPaddingRight(); 2446 | final int height = isOrientationHorizontal() 2447 | ? getWidth() - getPaddingLeft() - getPaddingRight() 2448 | : getHeight() - getPaddingTop() - getPaddingBottom(); 2449 | 2450 | if (isOrientationHorizontal()) { 2451 | canvas.rotate(90); 2452 | canvas.translate(-getPaddingTop(), -(mLastOffset + 1) * height); 2453 | } else { 2454 | canvas.rotate(180); 2455 | canvas.translate(-width, -(mLastOffset + 1) * height); 2456 | } 2457 | mRightEdge.setSize(width, height); 2458 | needsInvalidate |= mRightEdge.draw(canvas); 2459 | canvas.restoreToCount(restoreCount); 2460 | } 2461 | } else { 2462 | mLeftEdge.finish(); 2463 | mRightEdge.finish(); 2464 | } 2465 | 2466 | if (needsInvalidate) { 2467 | // Keep animating 2468 | ViewCompat.postInvalidateOnAnimation(this); 2469 | } 2470 | } 2471 | 2472 | @Override 2473 | protected void onDraw(Canvas canvas) { 2474 | super.onDraw(canvas); 2475 | 2476 | // Draw the margin drawable between pages if needed. 2477 | if (mPageMargin > 0 && mMarginDrawable != null && mItems.size() > 0 && mAdapter != null) { 2478 | if (isOrientationHorizontal()) { 2479 | final int scrollX = getScrollX(); 2480 | final int width = getWidth(); 2481 | 2482 | final float marginOffset = (float) mPageMargin / width; 2483 | int itemIndex = 0; 2484 | ItemInfo ii = mItems.get(0); 2485 | float offset = ii.offset; 2486 | final int itemCount = mItems.size(); 2487 | final int firstPos = ii.position; 2488 | final int lastPos = mItems.get(itemCount - 1).position; 2489 | for (int pos = firstPos; pos < lastPos; pos++) { 2490 | while (pos > ii.position && itemIndex < itemCount) { 2491 | ii = mItems.get(++itemIndex); 2492 | } 2493 | 2494 | float drawAt; 2495 | if (pos == ii.position) { 2496 | drawAt = (ii.offset + ii.sizeFactor) * width; 2497 | offset = ii.offset + ii.sizeFactor + marginOffset; 2498 | } else { 2499 | float widthFactor = mAdapter.getPageWidth(pos); 2500 | drawAt = (offset + widthFactor) * width; 2501 | offset += widthFactor + marginOffset; 2502 | } 2503 | 2504 | if (drawAt + mPageMargin > scrollX) { 2505 | mMarginDrawable.setBounds((int) drawAt, mTopPageBounds, 2506 | (int) (drawAt + mPageMargin + 0.5f), mBottomPageBounds); 2507 | mMarginDrawable.draw(canvas); 2508 | } 2509 | 2510 | if (drawAt > scrollX + width) { 2511 | break; // No more visible, no sense in continuing 2512 | } 2513 | } 2514 | } else { 2515 | final int scrollY = getScrollY(); 2516 | final int height = getHeight(); 2517 | 2518 | final float marginOffset = (float) mPageMargin / height; 2519 | int itemIndex = 0; 2520 | ItemInfo ii = mItems.get(0); 2521 | float offset = ii.offset; 2522 | final int itemCount = mItems.size(); 2523 | final int firstPos = ii.position; 2524 | final int lastPos = mItems.get(itemCount - 1).position; 2525 | for (int pos = firstPos; pos < lastPos; pos++) { 2526 | while (pos > ii.position && itemIndex < itemCount) { 2527 | ii = mItems.get(++itemIndex); 2528 | } 2529 | 2530 | float drawAt; 2531 | if (pos == ii.position) { 2532 | drawAt = (ii.offset + ii.sizeFactor) * height; 2533 | offset = ii.offset + ii.sizeFactor + marginOffset; 2534 | } else { 2535 | float sizeFactor = mAdapter.getPageSize(pos); 2536 | drawAt = (offset + sizeFactor) * height; 2537 | offset += sizeFactor + marginOffset; 2538 | } 2539 | 2540 | if (drawAt + mPageMargin > scrollY) { 2541 | mMarginDrawable.setBounds(mLeftPageBounds, (int) drawAt, 2542 | mRightPageBounds, (int) (drawAt + mPageMargin + 0.5f)); 2543 | mMarginDrawable.draw(canvas); 2544 | } 2545 | 2546 | if (drawAt > scrollY + height) { 2547 | break; // No more visible, no sense in continuing 2548 | } 2549 | } 2550 | } 2551 | } 2552 | } 2553 | 2554 | /** 2555 | * Start a fake drag of the pager. 2556 | * 2557 | *

A fake drag can be useful if you want to synchronize the motion of the ViewPager 2558 | * with the touch scrolling of another view, while still letting the ViewPager 2559 | * control the snapping motion and fling behavior. (e.g. parallax-scrolling tabs.) 2560 | * Call {@link #fakeDragBy(float)} to simulate the actual drag motion. Call 2561 | * {@link #endFakeDrag()} to complete the fake drag and fling as necessary. 2562 | * 2563 | *

During a fake drag the ViewPager will ignore all touch events. If a real drag 2564 | * is already in progress, this method will return false. 2565 | * 2566 | * @return true if the fake drag began successfully, false if it could not be started. 2567 | * 2568 | * @see #fakeDragBy(float) 2569 | * @see #endFakeDrag() 2570 | */ 2571 | public boolean beginFakeDrag() { 2572 | if (mIsBeingDragged) { 2573 | return false; 2574 | } 2575 | mFakeDragging = true; 2576 | setScrollState(SCROLL_STATE_DRAGGING); 2577 | if (isOrientationHorizontal()) { 2578 | mInitialMotionX = mLastMotionX = 0; 2579 | } else { 2580 | mInitialMotionY = mLastMotionY = 0; 2581 | } 2582 | if (mVelocityTracker == null) { 2583 | mVelocityTracker = VelocityTracker.obtain(); 2584 | } else { 2585 | mVelocityTracker.clear(); 2586 | } 2587 | final long time = SystemClock.uptimeMillis(); 2588 | final MotionEvent ev = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 0, 0, 0); 2589 | mVelocityTracker.addMovement(ev); 2590 | ev.recycle(); 2591 | mFakeDragBeginTime = time; 2592 | return true; 2593 | } 2594 | 2595 | /** 2596 | * End a fake drag of the pager. 2597 | * 2598 | * @see #beginFakeDrag() 2599 | * @see #fakeDragBy(float) 2600 | */ 2601 | public void endFakeDrag() { 2602 | if (!mFakeDragging) { 2603 | throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first."); 2604 | } 2605 | 2606 | final VelocityTracker velocityTracker = mVelocityTracker; 2607 | velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 2608 | 2609 | final ItemInfo ii = infoForCurrentScrollPosition(); 2610 | final int currentPage = ii.position; 2611 | 2612 | int initialVelocity, totalDelta; 2613 | float pageOffset; 2614 | if (isOrientationHorizontal()) { 2615 | initialVelocity = (int) VelocityTrackerCompat.getXVelocity( 2616 | velocityTracker, mActivePointerId); 2617 | mPopulatePending = true; 2618 | final int width = getClientWidth(); 2619 | final int scrollX = getScrollX(); 2620 | pageOffset = (((float) scrollX / width) - ii.offset) / ii.sizeFactor; 2621 | totalDelta = (int) (mLastMotionX - mInitialMotionX); 2622 | } else { 2623 | initialVelocity = (int) VelocityTrackerCompat.getYVelocity( 2624 | velocityTracker, mActivePointerId); 2625 | mPopulatePending = true; 2626 | final int height = getClientHeight(); 2627 | final int scrollY = getScrollY(); 2628 | pageOffset = (((float) scrollY / height) - ii.offset) / ii.sizeFactor; 2629 | totalDelta = (int) (mLastMotionY - mInitialMotionY); 2630 | } 2631 | 2632 | int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity, 2633 | totalDelta); 2634 | setCurrentItemInternal(nextPage, true, true, initialVelocity); 2635 | endDrag(); 2636 | 2637 | mFakeDragging = false; 2638 | } 2639 | 2640 | /** 2641 | * Fake drag by an offset in pixels. You must have called {@link #beginFakeDrag()} first. 2642 | * 2643 | * @param offset Offset in pixels to drag by. 2644 | * @see #beginFakeDrag() 2645 | * @see #endFakeDrag() 2646 | */ 2647 | public void fakeDragBy(float offset) { 2648 | if (!mFakeDragging) { 2649 | throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first."); 2650 | } 2651 | 2652 | if (isOrientationHorizontal()) { 2653 | mLastMotionX += offset; 2654 | 2655 | float oldScrollX = getScrollX(); 2656 | float scrollX = oldScrollX - offset; 2657 | final int width = getClientWidth(); 2658 | 2659 | float leftBound = width * mFirstOffset; 2660 | float rightBound = width * mLastOffset; 2661 | 2662 | final ItemInfo firstItem = mItems.get(0); 2663 | final ItemInfo lastItem = mItems.get(mItems.size() - 1); 2664 | if (firstItem.position != 0) { 2665 | leftBound = firstItem.offset * width; 2666 | } 2667 | if (lastItem.position != mAdapter.getCount() - 1) { 2668 | rightBound = lastItem.offset * width; 2669 | } 2670 | 2671 | if (scrollX < leftBound) { 2672 | scrollX = leftBound; 2673 | } else if (scrollX > rightBound) { 2674 | scrollX = rightBound; 2675 | } 2676 | // Don't lose the rounded component 2677 | mLastMotionX += scrollX - (int) scrollX; 2678 | scrollTo((int) scrollX, getScrollY()); 2679 | pageScrolled((int) scrollX); 2680 | 2681 | // Synthesize an event for the VelocityTracker. 2682 | final long time = SystemClock.uptimeMillis(); 2683 | final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE, 2684 | mLastMotionX, 0, 0); 2685 | mVelocityTracker.addMovement(ev); 2686 | ev.recycle(); 2687 | } else { 2688 | mLastMotionY += offset; 2689 | 2690 | float oldScrollY = getScrollY(); 2691 | float scrollY = oldScrollY - offset; 2692 | final int height = getClientHeight(); 2693 | 2694 | float topBound = height * mFirstOffset; 2695 | float bottomBound = height * mLastOffset; 2696 | 2697 | final ItemInfo firstItem = mItems.get(0); 2698 | final ItemInfo lastItem = mItems.get(mItems.size() - 1); 2699 | if (firstItem.position != 0) { 2700 | topBound = firstItem.offset * height; 2701 | } 2702 | if (lastItem.position != mAdapter.getCount() - 1) { 2703 | bottomBound = lastItem.offset * height; 2704 | } 2705 | 2706 | if (scrollY < topBound) { 2707 | scrollY = topBound; 2708 | } else if (scrollY > bottomBound) { 2709 | scrollY = bottomBound; 2710 | } 2711 | // Don't lose the rounded component 2712 | mLastMotionY += scrollY - (int) scrollY; 2713 | scrollTo(getScrollX(), (int) scrollY); 2714 | pageScrolled((int) scrollY); 2715 | 2716 | // Synthesize an event for the VelocityTracker. 2717 | final long time = SystemClock.uptimeMillis(); 2718 | final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE, 2719 | 0, mLastMotionY, 0); 2720 | mVelocityTracker.addMovement(ev); 2721 | ev.recycle(); 2722 | } 2723 | } 2724 | 2725 | /** 2726 | * Returns true if a fake drag is in progress. 2727 | * 2728 | * @return true if currently in a fake drag, false otherwise. 2729 | * 2730 | * @see #beginFakeDrag() 2731 | * @see #fakeDragBy(float) 2732 | * @see #endFakeDrag() 2733 | */ 2734 | public boolean isFakeDragging() { 2735 | return mFakeDragging; 2736 | } 2737 | 2738 | private void onSecondaryPointerUp(MotionEvent ev) { 2739 | final int pointerIndex = MotionEventCompat.getActionIndex(ev); 2740 | final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex); 2741 | if (pointerId == mActivePointerId) { 2742 | // This was our active pointer going up. Choose a new 2743 | // active pointer and adjust accordingly. 2744 | final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 2745 | mLastMotionX = MotionEventCompat.getX(ev, newPointerIndex); 2746 | mLastMotionY = MotionEventCompat.getY(ev, newPointerIndex); 2747 | mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex); 2748 | if (mVelocityTracker != null) { 2749 | mVelocityTracker.clear(); 2750 | } 2751 | } 2752 | } 2753 | 2754 | private void endDrag() { 2755 | mIsBeingDragged = false; 2756 | mIsUnableToDrag = false; 2757 | 2758 | if (mVelocityTracker != null) { 2759 | mVelocityTracker.recycle(); 2760 | mVelocityTracker = null; 2761 | } 2762 | } 2763 | 2764 | private void setScrollingCacheEnabled(boolean enabled) { 2765 | if (mScrollingCacheEnabled != enabled) { 2766 | mScrollingCacheEnabled = enabled; 2767 | if (USE_CACHE) { 2768 | final int size = getChildCount(); 2769 | for (int i = 0; i < size; ++i) { 2770 | final View child = getChildAt(i); 2771 | if (child.getVisibility() != GONE) { 2772 | child.setDrawingCacheEnabled(enabled); 2773 | } 2774 | } 2775 | } 2776 | } 2777 | } 2778 | 2779 | public boolean canScrollHorizontally(int direction) { 2780 | if (mAdapter == null) { 2781 | return false; 2782 | } 2783 | 2784 | final int width = getClientWidth(); 2785 | final int scrollX = getScrollX(); 2786 | if (direction < 0) { 2787 | return (scrollX > (int) (width * mFirstOffset)); 2788 | } else if (direction > 0) { 2789 | return (scrollX < (int) (width * mLastOffset)); 2790 | } else { 2791 | return false; 2792 | } 2793 | } 2794 | 2795 | public boolean canScrollVertically(int direction) { 2796 | if (mAdapter == null) { 2797 | return false; 2798 | } 2799 | 2800 | final int height = getClientHeight(); 2801 | final int scrollY = getScrollY(); 2802 | if (direction < 0) { 2803 | return (scrollY > (int) (height * mFirstOffset)); 2804 | } else if (direction > 0) { 2805 | return (scrollY < (int) (height * mLastOffset)); 2806 | } else { 2807 | return false; 2808 | } 2809 | } 2810 | 2811 | /** 2812 | * Tests scrollability within child views of v given a delta of dx. 2813 | * 2814 | * @param v View to test for horizontal scrollability 2815 | * @param checkV Whether the view v passed should itself be checked for scrollability (true), 2816 | * or just its children (false). 2817 | * @param dx Delta scrolled in pixels 2818 | * @param x X coordinate of the active touch point 2819 | * @param y Y coordinate of the active touch point 2820 | * @return true if child views of v can be scrolled by delta of dx. 2821 | */ 2822 | protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) { 2823 | if (v instanceof ViewGroup) { 2824 | final ViewGroup group = (ViewGroup) v; 2825 | final int scrollX = v.getScrollX(); 2826 | final int scrollY = v.getScrollY(); 2827 | final int count = group.getChildCount(); 2828 | // Count backwards - let topmost views consume scroll distance first. 2829 | for (int i = count - 1; i >= 0; i--) { 2830 | // TODO: Add versioned support here for transformed views. 2831 | // This will not work for transformed views in Honeycomb+ 2832 | final View child = group.getChildAt(i); 2833 | if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() && 2834 | y + scrollY >= child.getTop() && y + scrollY < child.getBottom() && 2835 | canScroll(child, true, dx, x + scrollX - child.getLeft(), 2836 | y + scrollY - child.getTop())) { 2837 | return true; 2838 | } 2839 | } 2840 | } 2841 | 2842 | boolean canScroll = false; 2843 | if (isOrientationHorizontal()) { 2844 | canScroll = ViewCompat.canScrollHorizontally(v, -dx); 2845 | } else { 2846 | canScroll = ViewCompat.canScrollVertically(v, -dx); 2847 | } 2848 | return checkV && canScroll; 2849 | } 2850 | 2851 | @Override 2852 | public boolean dispatchKeyEvent(KeyEvent event) { 2853 | // Let the focused view and/or our descendants get the key first 2854 | return super.dispatchKeyEvent(event) || executeKeyEvent(event); 2855 | } 2856 | 2857 | /** 2858 | * You can call this function yourself to have the scroll view perform 2859 | * scrolling from a key event, just as if the event had been dispatched to 2860 | * it by the view hierarchy. 2861 | * 2862 | * @param event The key event to execute. 2863 | * @return Return true if the event was handled, else false. 2864 | */ 2865 | public boolean executeKeyEvent(KeyEvent event) { 2866 | boolean handled = false; 2867 | if (event.getAction() == KeyEvent.ACTION_DOWN) { 2868 | switch (event.getKeyCode()) { 2869 | case KeyEvent.KEYCODE_DPAD_LEFT: 2870 | if (isOrientationHorizontal()) 2871 | handled = arrowScroll(FOCUS_LEFT); 2872 | break; 2873 | case KeyEvent.KEYCODE_DPAD_RIGHT: 2874 | if (isOrientationHorizontal()) 2875 | handled = arrowScroll(FOCUS_RIGHT); 2876 | break; 2877 | case KeyEvent.KEYCODE_DPAD_UP: 2878 | if (!isOrientationHorizontal()) 2879 | handled = arrowScroll(FOCUS_UP); 2880 | break; 2881 | case KeyEvent.KEYCODE_DPAD_DOWN: 2882 | if (!isOrientationHorizontal()) 2883 | handled = arrowScroll(FOCUS_DOWN); 2884 | break; 2885 | case KeyEvent.KEYCODE_TAB: 2886 | if (Build.VERSION.SDK_INT >= 11) { 2887 | // The focus finder had a bug handling FOCUS_FORWARD and FOCUS_BACKWARD 2888 | // before Android 3.0. Ignore the tab key on those devices. 2889 | if (KeyEventCompat.hasNoModifiers(event)) { 2890 | handled = arrowScroll(FOCUS_FORWARD); 2891 | } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_SHIFT_ON)) { 2892 | handled = arrowScroll(FOCUS_BACKWARD); 2893 | } 2894 | } 2895 | break; 2896 | } 2897 | } 2898 | return handled; 2899 | } 2900 | 2901 | public boolean arrowScroll(int direction) { 2902 | View currentFocused = findFocus(); 2903 | if (currentFocused == this) { 2904 | currentFocused = null; 2905 | } else if (currentFocused != null) { 2906 | boolean isChild = false; 2907 | for (ViewParent parent = currentFocused.getParent(); parent instanceof ViewGroup; 2908 | parent = parent.getParent()) { 2909 | if (parent == this) { 2910 | isChild = true; 2911 | break; 2912 | } 2913 | } 2914 | if (!isChild) { 2915 | // This would cause the focus search down below to fail in fun ways. 2916 | final StringBuilder sb = new StringBuilder(); 2917 | sb.append(currentFocused.getClass().getSimpleName()); 2918 | for (ViewParent parent = currentFocused.getParent(); parent instanceof ViewGroup; 2919 | parent = parent.getParent()) { 2920 | sb.append(" => ").append(parent.getClass().getSimpleName()); 2921 | } 2922 | Log.e(TAG, "arrowScroll tried to find focus based on non-child " + 2923 | "current focused view " + sb.toString()); 2924 | currentFocused = null; 2925 | } 2926 | } 2927 | 2928 | boolean handled = false; 2929 | 2930 | View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, 2931 | direction); 2932 | if (nextFocused != null && nextFocused != currentFocused) { 2933 | if (direction == View.FOCUS_LEFT) { 2934 | // If there is nothing to the left, or this is causing us to 2935 | // jump to the right, then what we really want to do is page left. 2936 | final int nextLeft = getChildRectInPagerCoordinates(mTempRect, nextFocused).left; 2937 | final int currLeft = getChildRectInPagerCoordinates(mTempRect, currentFocused).left; 2938 | if (currentFocused != null && nextLeft >= currLeft) { 2939 | handled = pageLeft(); 2940 | } else { 2941 | handled = nextFocused.requestFocus(); 2942 | } 2943 | } else if (direction == View.FOCUS_RIGHT) { 2944 | // If there is nothing to the right, or this is causing us to 2945 | // jump to the left, then what we really want to do is page right. 2946 | final int nextLeft = getChildRectInPagerCoordinates(mTempRect, nextFocused).left; 2947 | final int currLeft = getChildRectInPagerCoordinates(mTempRect, currentFocused).left; 2948 | if (currentFocused != null && nextLeft <= currLeft) { 2949 | handled = pageRight(); 2950 | } else { 2951 | handled = nextFocused.requestFocus(); 2952 | } 2953 | } else if (direction == View.FOCUS_UP) { 2954 | final int nextUp = getChildRectInPagerCoordinates(mTempRect, nextFocused).top; 2955 | final int currUp = getChildRectInPagerCoordinates(mTempRect, currentFocused).top; 2956 | if (currentFocused != null && nextUp >= currUp) { 2957 | handled = pageLeft(); 2958 | } else { 2959 | handled = nextFocused.requestFocus(); 2960 | } 2961 | } else if (direction == View.FOCUS_DOWN) { 2962 | final int nextUp = getChildRectInPagerCoordinates(mTempRect, nextFocused).top; 2963 | final int currUp = getChildRectInPagerCoordinates(mTempRect, currentFocused).top; 2964 | if (currentFocused != null && nextUp <= currUp) { 2965 | handled = pageRight(); 2966 | } else { 2967 | handled = nextFocused.requestFocus(); 2968 | } 2969 | } 2970 | } else if (direction == FOCUS_LEFT || direction == FOCUS_UP || direction == FOCUS_BACKWARD) { 2971 | // Trying to move left and nothing there; try to page. 2972 | handled = pageLeft(); 2973 | } else if (direction == FOCUS_RIGHT || direction == FOCUS_DOWN || direction == FOCUS_FORWARD) { 2974 | // Trying to move right and nothing there; try to page. 2975 | handled = pageRight(); 2976 | } 2977 | if (handled) { 2978 | playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction)); 2979 | } 2980 | return handled; 2981 | } 2982 | 2983 | private Rect getChildRectInPagerCoordinates(Rect outRect, View child) { 2984 | if (outRect == null) { 2985 | outRect = new Rect(); 2986 | } 2987 | if (child == null) { 2988 | outRect.set(0, 0, 0, 0); 2989 | return outRect; 2990 | } 2991 | outRect.left = child.getLeft(); 2992 | outRect.right = child.getRight(); 2993 | outRect.top = child.getTop(); 2994 | outRect.bottom = child.getBottom(); 2995 | 2996 | ViewParent parent = child.getParent(); 2997 | while (parent instanceof ViewGroup && parent != this) { 2998 | final ViewGroup group = (ViewGroup) parent; 2999 | outRect.left += group.getLeft(); 3000 | outRect.right += group.getRight(); 3001 | outRect.top += group.getTop(); 3002 | outRect.bottom += group.getBottom(); 3003 | 3004 | parent = group.getParent(); 3005 | } 3006 | return outRect; 3007 | } 3008 | 3009 | boolean pageLeft() { 3010 | if (mCurItem > 0) { 3011 | setCurrentItem(mCurItem-1, true); 3012 | return true; 3013 | } 3014 | return false; 3015 | } 3016 | 3017 | boolean pageRight() { 3018 | if (mAdapter != null && mCurItem < (mAdapter.getCount()-1)) { 3019 | setCurrentItem(mCurItem+1, true); 3020 | return true; 3021 | } 3022 | return false; 3023 | } 3024 | 3025 | /** 3026 | * We only want the current page that is being shown to be focusable. 3027 | */ 3028 | @Override 3029 | public void addFocusables(ArrayList views, int direction, int focusableMode) { 3030 | final int focusableCount = views.size(); 3031 | 3032 | final int descendantFocusability = getDescendantFocusability(); 3033 | 3034 | if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) { 3035 | for (int i = 0; i < getChildCount(); i++) { 3036 | final View child = getChildAt(i); 3037 | if (child.getVisibility() == VISIBLE) { 3038 | ItemInfo ii = infoForChild(child); 3039 | if (ii != null && ii.position == mCurItem) { 3040 | child.addFocusables(views, direction, focusableMode); 3041 | } 3042 | } 3043 | } 3044 | } 3045 | 3046 | // we add ourselves (if focusable) in all cases except for when we are 3047 | // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable. this is 3048 | // to avoid the focus search finding layouts when a more precise search 3049 | // among the focusable children would be more interesting. 3050 | if ( 3051 | descendantFocusability != FOCUS_AFTER_DESCENDANTS || 3052 | // No focusable descendants 3053 | (focusableCount == views.size())) { 3054 | // Note that we can't call the superclass here, because it will 3055 | // add all views in. So we need to do the same thing View does. 3056 | if (!isFocusable()) { 3057 | return; 3058 | } 3059 | if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE && 3060 | isInTouchMode() && !isFocusableInTouchMode()) { 3061 | return; 3062 | } 3063 | if (views != null) { 3064 | views.add(this); 3065 | } 3066 | } 3067 | } 3068 | 3069 | /** 3070 | * We only want the current page that is being shown to be touchable. 3071 | */ 3072 | @Override 3073 | public void addTouchables(ArrayList views) { 3074 | // Note that we don't call super.addTouchables(), which means that 3075 | // we don't call View.addTouchables(). This is okay because a ViewPager 3076 | // is itself not touchable. 3077 | for (int i = 0; i < getChildCount(); i++) { 3078 | final View child = getChildAt(i); 3079 | if (child.getVisibility() == VISIBLE) { 3080 | ItemInfo ii = infoForChild(child); 3081 | if (ii != null && ii.position == mCurItem) { 3082 | child.addTouchables(views); 3083 | } 3084 | } 3085 | } 3086 | } 3087 | 3088 | /** 3089 | * We only want the current page that is being shown to be focusable. 3090 | */ 3091 | @Override 3092 | protected boolean onRequestFocusInDescendants(int direction, 3093 | Rect previouslyFocusedRect) { 3094 | int index; 3095 | int increment; 3096 | int end; 3097 | int count = getChildCount(); 3098 | if ((direction & FOCUS_FORWARD) != 0) { 3099 | index = 0; 3100 | increment = 1; 3101 | end = count; 3102 | } else { 3103 | index = count - 1; 3104 | increment = -1; 3105 | end = -1; 3106 | } 3107 | for (int i = index; i != end; i += increment) { 3108 | View child = getChildAt(i); 3109 | if (child.getVisibility() == VISIBLE) { 3110 | ItemInfo ii = infoForChild(child); 3111 | if (ii != null && ii.position == mCurItem) { 3112 | if (child.requestFocus(direction, previouslyFocusedRect)) { 3113 | return true; 3114 | } 3115 | } 3116 | } 3117 | } 3118 | return false; 3119 | } 3120 | 3121 | @Override 3122 | public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { 3123 | // Dispatch scroll events from this ViewPager. 3124 | if (event.getEventType() == AccessibilityEventCompat.TYPE_VIEW_SCROLLED) { 3125 | return super.dispatchPopulateAccessibilityEvent(event); 3126 | } 3127 | 3128 | // Dispatch all other accessibility events from the current page. 3129 | final int childCount = getChildCount(); 3130 | for (int i = 0; i < childCount; i++) { 3131 | final View child = getChildAt(i); 3132 | if (child.getVisibility() == VISIBLE) { 3133 | final ItemInfo ii = infoForChild(child); 3134 | if (ii != null && ii.position == mCurItem && 3135 | child.dispatchPopulateAccessibilityEvent(event)) { 3136 | return true; 3137 | } 3138 | } 3139 | } 3140 | 3141 | return false; 3142 | } 3143 | 3144 | @Override 3145 | protected ViewGroup.LayoutParams generateDefaultLayoutParams() { 3146 | return new LayoutParams(); 3147 | } 3148 | 3149 | @Override 3150 | protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 3151 | return generateDefaultLayoutParams(); 3152 | } 3153 | 3154 | @Override 3155 | protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 3156 | return p instanceof LayoutParams && super.checkLayoutParams(p); 3157 | } 3158 | 3159 | @Override 3160 | public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { 3161 | return new LayoutParams(getContext(), attrs); 3162 | } 3163 | 3164 | class MyAccessibilityDelegate extends AccessibilityDelegateCompat { 3165 | 3166 | @Override 3167 | public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) { 3168 | super.onInitializeAccessibilityEvent(host, event); 3169 | event.setClassName(ViewPager.class.getName()); 3170 | final AccessibilityRecordCompat recordCompat = AccessibilityRecordCompat.obtain(); 3171 | recordCompat.setScrollable(canScroll()); 3172 | if (event.getEventType() == AccessibilityEventCompat.TYPE_VIEW_SCROLLED 3173 | && mAdapter != null) { 3174 | recordCompat.setItemCount(mAdapter.getCount()); 3175 | recordCompat.setFromIndex(mCurItem); 3176 | recordCompat.setToIndex(mCurItem); 3177 | } 3178 | } 3179 | 3180 | @Override 3181 | public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) { 3182 | super.onInitializeAccessibilityNodeInfo(host, info); 3183 | info.setClassName(ViewPager.class.getName()); 3184 | info.setScrollable(canScroll()); 3185 | if (canScrollHorizontally(1)) { 3186 | info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD); 3187 | } 3188 | if (canScrollHorizontally(-1)) { 3189 | info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD); 3190 | } 3191 | } 3192 | 3193 | @Override 3194 | public boolean performAccessibilityAction(View host, int action, Bundle args) { 3195 | if (super.performAccessibilityAction(host, action, args)) { 3196 | return true; 3197 | } 3198 | switch (action) { 3199 | case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD: { 3200 | if (canScrollHorizontally(1)) { 3201 | setCurrentItem(mCurItem + 1); 3202 | return true; 3203 | } 3204 | } return false; 3205 | case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD: { 3206 | if (canScrollHorizontally(-1)) { 3207 | setCurrentItem(mCurItem - 1); 3208 | return true; 3209 | } 3210 | } return false; 3211 | } 3212 | return false; 3213 | } 3214 | 3215 | private boolean canScroll() { 3216 | return (mAdapter != null) && (mAdapter.getCount() > 1); 3217 | } 3218 | } 3219 | 3220 | private class PagerObserver extends DataSetObserver { 3221 | @Override 3222 | public void onChanged() { 3223 | dataSetChanged(); 3224 | } 3225 | @Override 3226 | public void onInvalidated() { 3227 | dataSetChanged(); 3228 | } 3229 | } 3230 | 3231 | /** 3232 | * Layout parameters that should be supplied for views added to a 3233 | * ViewPager. 3234 | */ 3235 | public static class LayoutParams extends ViewGroup.LayoutParams { 3236 | /** 3237 | * true if this view is a decoration on the pager itself and not 3238 | * a view supplied by the adapter. 3239 | */ 3240 | public boolean isDecor; 3241 | 3242 | /** 3243 | * Gravity setting for use on decor views only: 3244 | * Where to position the view page within the overall ViewPager 3245 | * container; constants are defined in {@link android.view.Gravity}. 3246 | */ 3247 | public int gravity; 3248 | 3249 | /** 3250 | * Width as a 0-1 multiplier of the measured pager width 3251 | */ 3252 | float sizeFactor = 0.f; 3253 | 3254 | /** 3255 | * true if this view was added during layout and needs to be measured 3256 | * before being positioned. 3257 | */ 3258 | boolean needsMeasure; 3259 | 3260 | /** 3261 | * Adapter position this view is for if !isDecor 3262 | */ 3263 | int position; 3264 | 3265 | /** 3266 | * Current child index within the ViewPager that this view occupies 3267 | */ 3268 | int childIndex; 3269 | 3270 | public LayoutParams() { 3271 | super(MATCH_PARENT, MATCH_PARENT); 3272 | } 3273 | 3274 | public LayoutParams(Context context, AttributeSet attrs) { 3275 | super(context, attrs); 3276 | 3277 | final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS); 3278 | gravity = a.getInteger(0, Gravity.TOP); 3279 | a.recycle(); 3280 | } 3281 | } 3282 | 3283 | static class ViewPositionComparator implements Comparator { 3284 | @Override 3285 | public int compare(View lhs, View rhs) { 3286 | final LayoutParams llp = (LayoutParams) lhs.getLayoutParams(); 3287 | final LayoutParams rlp = (LayoutParams) rhs.getLayoutParams(); 3288 | if (llp.isDecor != rlp.isDecor) { 3289 | return llp.isDecor ? 1 : -1; 3290 | } 3291 | return llp.position - rlp.position; 3292 | } 3293 | } 3294 | } -------------------------------------------------------------------------------- /ViewPager/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rharter/ViewPager-Android/d914029c02cb13fe08cb7739e0a76982e8ebc264/ViewPager/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /ViewPager/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rharter/ViewPager-Android/d914029c02cb13fe08cb7739e0a76982e8ebc264/ViewPager/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /ViewPager/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rharter/ViewPager-Android/d914029c02cb13fe08cb7739e0a76982e8ebc264/ViewPager/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /ViewPager/src/main/res/values-v11/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /ViewPager/src/main/res/values-v14/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /ViewPager/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ViewPager/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ViewPager 3 | 4 | -------------------------------------------------------------------------------- /ViewPager/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 14 | 15 | 16 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /ViewPagerSample/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /ViewPagerSample/ViewPagerSample.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 17 | 18 | 19 | 20 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /ViewPagerSample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'android' 2 | 3 | repositories { 4 | mavenCentral() 5 | } 6 | 7 | android { 8 | compileSdkVersion 19 9 | buildToolsVersion "19.0.0" 10 | 11 | defaultConfig { 12 | minSdkVersion 7 13 | targetSdkVersion 19 14 | } 15 | 16 | compileOptions { 17 | sourceCompatibility JavaVersion.VERSION_1_7 18 | targetCompatibility JavaVersion.VERSION_1_7 19 | } 20 | } 21 | 22 | dependencies { 23 | compile 'com.android.support:appcompat-v7:18.0.0' 24 | compile project(':ViewPager') 25 | } 26 | -------------------------------------------------------------------------------- /ViewPagerSample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /ViewPagerSample/src/main/java/com/ryanharter/viewpager/sample/HorizontalPagingFragment.java: -------------------------------------------------------------------------------- 1 | package com.ryanharter.viewpager.sample; 2 | 3 | import com.ryanharter.viewpager.PagerAdapter; 4 | import com.ryanharter.viewpager.ViewPager; 5 | 6 | import android.content.Context; 7 | import android.graphics.Color; 8 | import android.os.Bundle; 9 | import android.support.v4.app.Fragment; 10 | import android.view.LayoutInflater; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | import android.widget.TextView; 14 | 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | 18 | /** 19 | * Created by rharter on 11/11/13. 20 | */ 21 | public class HorizontalPagingFragment extends Fragment { 22 | 23 | ViewPager mPager; 24 | HorizontalPagerAdapter mAdapter; 25 | 26 | @Override 27 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 28 | Bundle savedInstanceState) { 29 | View v = inflater.inflate(R.layout.fragment_horizontal, container, false); 30 | 31 | mPager = (ViewPager) v.findViewById(R.id.pager); 32 | 33 | return v; 34 | } 35 | 36 | @Override 37 | public void onActivityCreated(Bundle savedInstanceState) { 38 | super.onActivityCreated(savedInstanceState); 39 | 40 | mAdapter = new HorizontalPagerAdapter(getActivity()); 41 | mPager.setAdapter(mAdapter); 42 | } 43 | 44 | public static class HorizontalPagerAdapter extends PagerAdapter { 45 | 46 | static final List PAGES = new ArrayList(); 47 | static { 48 | PAGES.add(new PageInfo(Color.BLUE, "Nothing Unusual")); 49 | PAGES.add(new PageInfo(Color.RED, "Use a normal PagerAdapter, but subclass mine")); 50 | PAGES.add(new PageInfo(Color.GREEN, "Nothin Unusual")); 51 | PAGES.add(new PageInfo(Color.MAGENTA, "Nothin Unusual")); 52 | } 53 | 54 | Context mContext; 55 | LayoutInflater mLayoutInflater; 56 | 57 | public HorizontalPagerAdapter(Context context) { 58 | super(); 59 | mContext = context; 60 | mLayoutInflater = LayoutInflater.from(mContext); 61 | } 62 | 63 | @Override 64 | public int getCount() { 65 | return PAGES.size(); 66 | } 67 | 68 | @Override 69 | public boolean isViewFromObject(View view, Object o) { 70 | return view == o; 71 | } 72 | 73 | @Override 74 | public Object instantiateItem(ViewGroup container, int position) { 75 | View v = mLayoutInflater.inflate(R.layout.page, container, false); 76 | 77 | final PageInfo info = PAGES.get(position); 78 | 79 | View page = v.findViewById(R.id.container); 80 | page.setBackgroundColor(info.background); 81 | 82 | TextView content = (TextView) v.findViewById(R.id.text); 83 | content.setText(info.text); 84 | 85 | container.addView(v); 86 | return v; 87 | } 88 | 89 | @Override 90 | public void destroyItem(ViewGroup container, int position, Object object) { 91 | container.removeView((View) object); 92 | } 93 | 94 | private static class PageInfo { 95 | int background; 96 | String text; 97 | 98 | PageInfo(int background, String text) { 99 | this.background = background; 100 | this.text = text; 101 | } 102 | } 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /ViewPagerSample/src/main/java/com/ryanharter/viewpager/sample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.ryanharter.viewpager.sample; 2 | 3 | import android.support.v4.app.Fragment; 4 | import android.support.v4.app.FragmentTransaction; 5 | import android.support.v7.app.ActionBar; 6 | import android.support.v7.app.ActionBarActivity; 7 | import android.os.Bundle; 8 | import android.view.Menu; 9 | import android.view.MenuItem; 10 | import android.widget.ArrayAdapter; 11 | import android.widget.SpinnerAdapter; 12 | 13 | public class MainActivity extends ActionBarActivity { 14 | 15 | private SpinnerAdapter mSpinnerAdapter; 16 | 17 | @Override 18 | protected void onCreate(Bundle savedInstanceState) { 19 | super.onCreate(savedInstanceState); 20 | setContentView(R.layout.activity_main); 21 | 22 | mSpinnerAdapter = ArrayAdapter.createFromResource(this, R.array.fragment_list, 23 | android.R.layout.simple_spinner_dropdown_item); 24 | 25 | getSupportActionBar().setDisplayShowTitleEnabled(false); 26 | getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_LIST); 27 | getSupportActionBar().setListNavigationCallbacks(mSpinnerAdapter, 28 | new ActionBar.OnNavigationListener() { 29 | 30 | String[] strings = getResources().getStringArray(R.array.fragment_list); 31 | 32 | @Override 33 | public boolean onNavigationItemSelected(int i, long l) { 34 | Fragment f = null; 35 | switch(strings[i]) { 36 | case "Horizontal": 37 | f = new HorizontalPagingFragment(); 38 | break; 39 | case "Vertical": 40 | default: 41 | f = new VerticalPagingFragment(); 42 | break; 43 | } 44 | 45 | FragmentTransaction ft = getSupportFragmentManager() 46 | .beginTransaction(); 47 | 48 | ft.replace(R.id.container, f, strings[i]); 49 | ft.commit(); 50 | return true; 51 | } 52 | } 53 | ); 54 | 55 | if (savedInstanceState == null) { 56 | getSupportFragmentManager().beginTransaction() 57 | .add(R.id.container, new VerticalPagingFragment()) 58 | .commit(); 59 | } 60 | } 61 | 62 | 63 | @Override 64 | public boolean onCreateOptionsMenu(Menu menu) { 65 | 66 | // Inflate the menu; this adds items to the action bar if it is present. 67 | getMenuInflater().inflate(R.menu.main, menu); 68 | return true; 69 | } 70 | 71 | @Override 72 | public boolean onOptionsItemSelected(MenuItem item) { 73 | // Handle action bar item clicks here. The action bar will 74 | // automatically handle clicks on the Home/Up button, so long 75 | // as you specify a parent activity in AndroidManifest.xml. 76 | int id = item.getItemId(); 77 | if (id == R.id.action_settings) { 78 | return true; 79 | } 80 | return super.onOptionsItemSelected(item); 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /ViewPagerSample/src/main/java/com/ryanharter/viewpager/sample/VerticalPagingFragment.java: -------------------------------------------------------------------------------- 1 | package com.ryanharter.viewpager.sample; 2 | 3 | import com.ryanharter.viewpager.PagerAdapter; 4 | import com.ryanharter.viewpager.ViewPager; 5 | 6 | import android.content.Context; 7 | import android.graphics.Color; 8 | import android.os.Bundle; 9 | import android.support.v4.app.Fragment; 10 | import android.view.LayoutInflater; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | import android.widget.TextView; 14 | 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | 18 | /** 19 | * Created by rharter on 11/11/13. 20 | */ 21 | public class VerticalPagingFragment extends Fragment { 22 | 23 | ViewPager mPager; 24 | HorizontalPagerAdapter mAdapter; 25 | 26 | @Override 27 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 28 | Bundle savedInstanceState) { 29 | View v = inflater.inflate(R.layout.fragment_vertical, container, false); 30 | 31 | mPager = (ViewPager) v.findViewById(R.id.pager); 32 | 33 | return v; 34 | } 35 | 36 | @Override 37 | public void onActivityCreated(Bundle savedInstanceState) { 38 | super.onActivityCreated(savedInstanceState); 39 | 40 | mAdapter = new HorizontalPagerAdapter(getActivity()); 41 | mPager.setAdapter(mAdapter); 42 | } 43 | 44 | public static class HorizontalPagerAdapter extends PagerAdapter { 45 | 46 | static final List PAGES = new ArrayList(); 47 | static { 48 | PAGES.add(new PageInfo(Color.BLUE, "This one swipes vertically")); 49 | PAGES.add(new PageInfo(Color.RED, "Simply set the orientation flag")); 50 | PAGES.add(new PageInfo(Color.GREEN, "Use the same PagerAdapter")); 51 | PAGES.add(new PageInfo(Color.MAGENTA, "Nothing Unusual")); 52 | } 53 | 54 | Context mContext; 55 | LayoutInflater mLayoutInflater; 56 | 57 | public HorizontalPagerAdapter(Context context) { 58 | super(); 59 | mContext = context; 60 | mLayoutInflater = LayoutInflater.from(mContext); 61 | } 62 | 63 | @Override 64 | public int getCount() { 65 | return PAGES.size(); 66 | } 67 | 68 | @Override 69 | public boolean isViewFromObject(View view, Object o) { 70 | return view == o; 71 | } 72 | 73 | @Override 74 | public Object instantiateItem(ViewGroup container, int position) { 75 | View v = mLayoutInflater.inflate(R.layout.page, container, false); 76 | 77 | final PageInfo info = PAGES.get(position); 78 | 79 | View page = v.findViewById(R.id.container); 80 | page.setBackgroundColor(info.background); 81 | 82 | TextView content = (TextView) v.findViewById(R.id.text); 83 | content.setText(info.text); 84 | 85 | container.addView(v); 86 | return v; 87 | } 88 | 89 | @Override 90 | public void destroyItem(ViewGroup container, int position, Object object) { 91 | container.removeView((View) object); 92 | } 93 | 94 | private static class PageInfo { 95 | int background; 96 | String text; 97 | 98 | PageInfo(int background, String text) { 99 | this.background = background; 100 | this.text = text; 101 | } 102 | } 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /ViewPagerSample/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rharter/ViewPager-Android/d914029c02cb13fe08cb7739e0a76982e8ebc264/ViewPagerSample/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /ViewPagerSample/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rharter/ViewPager-Android/d914029c02cb13fe08cb7739e0a76982e8ebc264/ViewPagerSample/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /ViewPagerSample/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rharter/ViewPager-Android/d914029c02cb13fe08cb7739e0a76982e8ebc264/ViewPagerSample/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /ViewPagerSample/src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rharter/ViewPager-Android/d914029c02cb13fe08cb7739e0a76982e8ebc264/ViewPagerSample/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /ViewPagerSample/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /ViewPagerSample/src/main/res/layout/fragment_horizontal.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /ViewPagerSample/src/main/res/layout/fragment_main.xml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /ViewPagerSample/src/main/res/layout/fragment_vertical.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 14 | 15 | -------------------------------------------------------------------------------- /ViewPagerSample/src/main/res/layout/page.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 12 | 20 | 21 | -------------------------------------------------------------------------------- /ViewPagerSample/src/main/res/menu/main.xml: -------------------------------------------------------------------------------- 1 |

5 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /ViewPagerSample/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /ViewPagerSample/src/main/res/values/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Horizontal 5 | Vertical 6 | 7 | -------------------------------------------------------------------------------- /ViewPagerSample/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | 7 | -------------------------------------------------------------------------------- /ViewPagerSample/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ViewPager Sample 5 | Hello world! 6 | Settings 7 | Horizontal Pager 8 | Vertical Pager 9 | 10 | 11 | -------------------------------------------------------------------------------- /ViewPagerSample/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | buildscript { 3 | repositories { 4 | mavenCentral() 5 | } 6 | dependencies { 7 | classpath 'com.android.tools.build:gradle:0.6.+' 8 | } 9 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rharter/ViewPager-Android/d914029c02cb13fe08cb7739e0a76982e8ebc264/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Nov 12 08:34:20 CST 2013 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=http\://services.gradle.org/distributions/gradle-1.8-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':ViewPagerSample', ':ViewPager' 2 | --------------------------------------------------------------------------------