├── .gitignore ├── DoubleViewPager ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── emoiluj │ │ └── doubleviewpager │ │ ├── DoubleViewPager.java │ │ ├── DoubleViewPagerAdapter.java │ │ ├── HorizontalViewPager.java │ │ └── VerticalViewPager.java │ └── res │ └── values │ └── strings.xml ├── DoubleViewPagerSample ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ ├── OpenSans-Light.ttf │ └── colors.json │ ├── java │ └── com │ │ └── emoiluj │ │ └── doubleviewpagersample │ │ ├── MainActivity.java │ │ ├── SplashActivity.java │ │ └── VerticalPagerAdapter.java │ └── res │ ├── drawable │ ├── logo.png │ ├── my_edit_text.xml │ ├── my_textfield_activated_holo_dark.9.png │ ├── my_textfield_default_holo_dark.9.png │ ├── my_textfield_disabled_focused_holo_dark.9.png │ ├── my_textfield_disabled_holo_dark.9.png │ └── my_textfield_focused_holo_dark.9.png │ ├── layout │ ├── activity_main.xml │ └── activity_splash.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── LICENSE ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images ├── desc.png ├── description.gif └── google-play-badge.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea/workspace.xml 4 | /.idea/libraries 5 | .DS_Store 6 | /build 7 | /captures 8 | *.iml 9 | -------------------------------------------------------------------------------- /DoubleViewPager/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /DoubleViewPager/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | //TO UPLOAD TO BINTRAY 3 | //apply plugin: 'com.jfrog.bintray' 4 | //apply plugin: 'com.github.dcendents.android-maven' 5 | 6 | // TO UPLOAD TO BINTRAY 7 | //group = "com.github.juliome10" 8 | //version = "1.0.1" 9 | 10 | android { 11 | compileSdkVersion 22 12 | buildToolsVersion "22.0.1" 13 | resourcePrefix "doubleviewpager__" //TO GENERATE AAR STEP1 14 | 15 | defaultConfig { 16 | minSdkVersion 8 17 | targetSdkVersion 22 18 | versionCode 1 19 | versionName "1.0" 20 | } 21 | buildTypes { 22 | release { 23 | minifyEnabled false 24 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 25 | } 26 | } 27 | } 28 | 29 | //TO UPLOAD TO BINTRAY 30 | //def siteUrl = 'https://github.com/juliome10/DoubleViewPager' // Homepage URL of the library 31 | //def issueUrl = 'https://github.com/juliome10/DoubleViewPager/issues' // URL for issues 32 | //def gitUrl = 'https://github.com/juliome10/DoubleViewPager.git' // Git repository URL 33 | // 34 | // 35 | //install { 36 | // repositories.mavenInstaller { 37 | // // This generates POM.xml with proper parameters 38 | // pom { 39 | // project { 40 | // packaging 'aar' 41 | // 42 | // // Add your description here 43 | // name 'DoubleViewPager' 44 | // url siteUrl 45 | // 46 | // // Set your license 47 | // licenses { 48 | // license { 49 | // name 'The Apache Software License, Version 2.0' 50 | // url 'http://www.apache.org/licenses/LICENSE-2.0.txt' 51 | // } 52 | // } 53 | // developers { 54 | // developer { 55 | // id 'juliome10' 56 | // name 'Julio Gómez' 57 | // email 'juliogomezreis@gmail.com' 58 | // } 59 | // } 60 | // scm { 61 | // connection gitUrl 62 | // developerConnection gitUrl 63 | // url siteUrl 64 | // 65 | // } 66 | // } 67 | // } 68 | // } 69 | //} 70 | // 71 | //Properties properties = new Properties() 72 | //properties.load(project.rootProject.file('local.properties').newDataInputStream()) 73 | // 74 | //bintray { 75 | // user = properties.getProperty("bintray.user") 76 | // key = properties.getProperty("bintray.apikey") 77 | // 78 | // configurations = ['archives'] 79 | // pkg { 80 | // repo = "maven" 81 | // name = "DoubleViewPager" 82 | // websiteUrl = siteUrl 83 | // issueTrackerUrl = issueUrl 84 | // vcsUrl = gitUrl 85 | // licenses = ["Apache-2.0"] 86 | // publish = true 87 | // labels = [] 88 | // publicDownloadNumbers = true 89 | // } 90 | //} 91 | 92 | dependencies { 93 | compile 'com.android.support:support-v4:22.+' 94 | compile fileTree(include: '*.jar', dir: 'libs') 95 | } 96 | 97 | //TO UPLOAD TO BINTRAY 98 | //task sourcesJar(type: Jar) { 99 | // from android.sourceSets.main.java.srcDirs 100 | // classifier = 'sources' 101 | //} 102 | // 103 | //task javadoc(type: Javadoc) { 104 | // classpath = configurations.compile 105 | //// source = android.sourceSets.main.java.srcDirs 106 | //// classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 107 | //} 108 | // 109 | //task javadocJar(type: Jar, dependsOn: javadoc) { 110 | // classifier = 'javadoc' 111 | // from javadoc.destinationDir 112 | //} 113 | //artifacts { 114 | // archives javadocJar 115 | // archives sourcesJar 116 | //} 117 | // 118 | //task findConventions << { 119 | // println project.getConvention() 120 | //} -------------------------------------------------------------------------------- /DoubleViewPager/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in C:\Users\0011361\Desktop\Orange\Software\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /DoubleViewPager/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /DoubleViewPager/src/main/java/com/emoiluj/doubleviewpager/DoubleViewPager.java: -------------------------------------------------------------------------------- 1 | package com.emoiluj.doubleviewpager; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | 6 | public class DoubleViewPager extends HorizontalViewPager{ 7 | 8 | public DoubleViewPager(Context context, AttributeSet attrs) { 9 | super(context, attrs); 10 | } 11 | 12 | public DoubleViewPager(Context context) { 13 | super(context); 14 | } 15 | } -------------------------------------------------------------------------------- /DoubleViewPager/src/main/java/com/emoiluj/doubleviewpager/DoubleViewPagerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.emoiluj.doubleviewpager; 2 | 3 | import android.content.Context; 4 | import android.support.v4.view.PagerAdapter; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.view.ViewGroup.LayoutParams; 8 | 9 | import java.util.ArrayList; 10 | 11 | public class DoubleViewPagerAdapter extends PagerAdapter{ 12 | 13 | private Context mContext; 14 | private ArrayList mAdapters; 15 | 16 | public DoubleViewPagerAdapter(Context context, ArrayList verticalAdapters){ 17 | mContext = context; 18 | mAdapters = verticalAdapters; 19 | } 20 | 21 | 22 | @Override 23 | public int getCount() { 24 | return mAdapters.size(); 25 | } 26 | 27 | @Override 28 | public boolean isViewFromObject(View view, Object object) { 29 | return view == object; 30 | } 31 | 32 | @Override 33 | public void destroyItem(ViewGroup container, int position, Object view) { 34 | container.removeView((View) view); 35 | } 36 | 37 | @Override 38 | public Object instantiateItem(final ViewGroup container, int position) { 39 | VerticalViewPager childVP = new VerticalViewPager(mContext); 40 | childVP.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); 41 | childVP.setAdapter(mAdapters.get(position)); 42 | container.addView(childVP); 43 | return childVP; 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /DoubleViewPager/src/main/java/com/emoiluj/doubleviewpager/HorizontalViewPager.java: -------------------------------------------------------------------------------- 1 | package com.emoiluj.doubleviewpager; 2 | 3 | /* 4 | * Copyright (C) 2011 The Android Open Source Project 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import java.util.ArrayList; 20 | import java.util.Collections; 21 | import java.util.Comparator; 22 | 23 | import android.content.Context; 24 | import android.content.res.TypedArray; 25 | import android.database.DataSetObserver; 26 | import android.graphics.Canvas; 27 | import android.graphics.Rect; 28 | import android.graphics.drawable.Drawable; 29 | import android.os.Build; 30 | import android.os.Bundle; 31 | import android.os.Parcel; 32 | import android.os.Parcelable; 33 | import android.os.SystemClock; 34 | import android.support.v4.os.ParcelableCompat; 35 | import android.support.v4.os.ParcelableCompatCreatorCallbacks; 36 | import android.support.v4.view.AccessibilityDelegateCompat; 37 | import android.support.v4.view.KeyEventCompat; 38 | import android.support.v4.view.MotionEventCompat; 39 | import android.support.v4.view.PagerAdapter; 40 | import android.support.v4.view.VelocityTrackerCompat; 41 | import android.support.v4.view.ViewCompat; 42 | import android.support.v4.view.ViewConfigurationCompat; 43 | import android.support.v4.view.ViewPager; 44 | import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; 45 | import android.support.v4.widget.EdgeEffectCompat; 46 | import android.util.AttributeSet; 47 | import android.util.Log; 48 | import android.view.FocusFinder; 49 | import android.view.Gravity; 50 | import android.view.KeyEvent; 51 | import android.view.MotionEvent; 52 | import android.view.SoundEffectConstants; 53 | import android.view.VelocityTracker; 54 | import android.view.View; 55 | import android.view.ViewConfiguration; 56 | import android.view.ViewGroup; 57 | import android.view.ViewParent; 58 | import android.view.accessibility.AccessibilityEvent; 59 | import android.view.animation.Interpolator; 60 | import android.widget.Scroller; 61 | 62 | /** 63 | * Layout manager that allows the user to flip left and right 64 | * through pages of data. You supply an implementation of a 65 | * {@link PagerAdapter} to generate the pages that the view shows. 66 | * 67 | *

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

71 | * 72 | *

ViewPager is most often used in conjunction with {@link android.app.Fragment}, 73 | * which is a convenient way to supply and manage the lifecycle of each page. 74 | * There are standard adapters implemented for using fragments with the ViewPager, 75 | * which cover the most common use cases. These are 76 | * {@link android.support.v4.app.FragmentPagerAdapter}, 77 | * {@link android.support.v4.app.FragmentStatePagerAdapter}, 78 | * {@link android.support.v13.app.FragmentPagerAdapter}, and 79 | * {@link android.support.v13.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 HorizontalViewPager extends ViewGroup { 91 | 92 | private static final String TAG = "HHH"; 93 | private static final boolean DEBUG = false; 94 | 95 | private static final boolean USE_CACHE = false; 96 | 97 | private static final int DEFAULT_OFFSCREEN_PAGES = 1; 98 | private static final int MAX_SETTLE_DURATION = 600; // ms 99 | private static final int MIN_DISTANCE_FOR_FLING = 25; // dips 100 | 101 | private static final int DEFAULT_GUTTER_SIZE = 16; // dips 102 | 103 | private static final int[] LAYOUT_ATTRS = new int[] { 104 | android.R.attr.layout_gravity 105 | }; 106 | 107 | static class ItemInfo { 108 | Object object; 109 | int position; 110 | boolean scrolling; 111 | float widthFactor; 112 | float offset; 113 | } 114 | 115 | private static final Comparator COMPARATOR = new Comparator(){ 116 | @Override 117 | public int compare(ItemInfo lhs, ItemInfo rhs) { 118 | return lhs.position - rhs.position; 119 | } 120 | }; 121 | 122 | private static final Interpolator sInterpolator = new Interpolator() { 123 | public float getInterpolation(float t) { 124 | t -= 1.0f; 125 | return t * t * t * t * t + 1.0f; 126 | } 127 | }; 128 | 129 | private final ArrayList mItems = new ArrayList(); 130 | private final ItemInfo mTempItem = new ItemInfo(); 131 | 132 | private final Rect mTempRect = new Rect(); 133 | 134 | private PagerAdapter mAdapter; 135 | private int mCurItem; // Index of currently displayed page. 136 | private int mRestoredCurItem = -1; 137 | private Parcelable mRestoredAdapterState = null; 138 | private ClassLoader mRestoredClassLoader = null; 139 | private Scroller mScroller; 140 | private PagerObserver mObserver; 141 | 142 | private int mPageMargin; 143 | private Drawable mMarginDrawable; 144 | private int mTopPageBounds; 145 | private int mBottomPageBounds; 146 | 147 | // Offsets of the first and last items, if known. 148 | // Set during population, used to determine if we are at the beginning 149 | // or end of the pager data set during touch scrolling. 150 | private float mFirstOffset = -Float.MAX_VALUE; 151 | private float mLastOffset = Float.MAX_VALUE; 152 | 153 | private int mChildWidthMeasureSpec; 154 | private int mChildHeightMeasureSpec; 155 | private boolean mInLayout; 156 | 157 | private boolean mScrollingCacheEnabled; 158 | 159 | private boolean mPopulatePending; 160 | private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES; 161 | 162 | private boolean mIsBeingDragged; 163 | private boolean mIsUnableToDrag; 164 | private boolean mIgnoreGutter; 165 | private int mDefaultGutterSize; 166 | private int mGutterSize; 167 | private int mTouchSlop; 168 | private float mInitialMotionX; 169 | /** 170 | * Position of the last motion event. 171 | */ 172 | private float mLastMotionX; 173 | private float mLastMotionY; 174 | /** 175 | * ID of the active pointer. This is used to retain consistency during 176 | * drags/flings if multiple pointers are used. 177 | */ 178 | private int mActivePointerId = INVALID_POINTER; 179 | /** 180 | * Sentinel value for no current active pointer. 181 | * Used by {@link #mActivePointerId}. 182 | */ 183 | private static final int INVALID_POINTER = -1; 184 | 185 | /** 186 | * Determines speed during touch scrolling 187 | */ 188 | private VelocityTracker mVelocityTracker; 189 | private int mMinimumVelocity; 190 | private int mMaximumVelocity; 191 | private int mFlingDistance; 192 | private int mCloseEnough; 193 | 194 | // If the pager is at least this close to its final position, complete the scroll 195 | // on touch down and let the user interact with the content inside instead of 196 | // "catching" the flinging pager. 197 | private static final int CLOSE_ENOUGH = 2; // dp 198 | 199 | private boolean mFakeDragging; 200 | private long mFakeDragBeginTime; 201 | 202 | private EdgeEffectCompat mLeftEdge; 203 | private EdgeEffectCompat mRightEdge; 204 | 205 | private boolean mFirstLayout = true; 206 | private boolean mNeedCalculatePageOffsets = false; 207 | private boolean mCalledSuper; 208 | private int mDecorChildCount; 209 | 210 | private OnPageChangeListener mOnPageChangeListener; 211 | private OnPageChangeListener mInternalPageChangeListener; 212 | private OnAdapterChangeListener mAdapterChangeListener; 213 | 214 | /** 215 | * Indicates that the pager is in an idle, settled state. The current page 216 | * is fully in view and no animation is in progress. 217 | */ 218 | public static final int SCROLL_STATE_IDLE = 0; 219 | 220 | /** 221 | * Indicates that the pager is currently being dragged by the user. 222 | */ 223 | public static final int SCROLL_STATE_DRAGGING = 1; 224 | 225 | /** 226 | * Indicates that the pager is in the process of settling to a final position. 227 | */ 228 | public static final int SCROLL_STATE_SETTLING = 2; 229 | 230 | private int mScrollState = SCROLL_STATE_IDLE; 231 | 232 | /** 233 | * Callback interface for responding to changing state of the selected page. 234 | */ 235 | public interface OnPageChangeListener { 236 | 237 | /** 238 | * This method will be invoked when the current page is scrolled, either as part 239 | * of a programmatically initiated smooth scroll or a user initiated touch scroll. 240 | * 241 | * @param position Position index of the first page currently being displayed. 242 | * Page position+1 will be visible if positionOffset is nonzero. 243 | * @param positionOffset Value from [0, 1) indicating the offset from the page at position. 244 | * @param positionOffsetPixels Value in pixels indicating the offset from position. 245 | */ 246 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels); 247 | 248 | /** 249 | * This method will be invoked when a new page becomes selected. Animation is not 250 | * necessarily complete. 251 | * 252 | * @param position Position index of the new selected page. 253 | */ 254 | public void onPageSelected(int position); 255 | 256 | /** 257 | * Called when the scroll state changes. Useful for discovering when the user 258 | * begins dragging, when the pager is automatically settling to the current page, 259 | * or when it is fully stopped/idle. 260 | * 261 | * @param state The new scroll state. 262 | * @see ViewPager#SCROLL_STATE_IDLE 263 | * @see ViewPager#SCROLL_STATE_DRAGGING 264 | * @see ViewPager#SCROLL_STATE_SETTLING 265 | */ 266 | public void onPageScrollStateChanged(int state); 267 | } 268 | 269 | /** 270 | * Simple implementation of the {@link OnPageChangeListener} interface with stub 271 | * implementations of each method. Extend this if you do not intend to override 272 | * every method of {@link OnPageChangeListener}. 273 | */ 274 | public static class SimpleOnPageChangeListener implements OnPageChangeListener { 275 | @Override 276 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 277 | // This space for rent 278 | } 279 | 280 | @Override 281 | public void onPageSelected(int position) { 282 | // This space for rent 283 | } 284 | 285 | @Override 286 | public void onPageScrollStateChanged(int state) { 287 | // This space for rent 288 | } 289 | } 290 | 291 | /** 292 | * Used internally to monitor when adapters are switched. 293 | */ 294 | interface OnAdapterChangeListener { 295 | public void onAdapterChanged(PagerAdapter oldAdapter, PagerAdapter newAdapter); 296 | } 297 | 298 | /** 299 | * Used internally to tag special types of child views that should be added as 300 | * pager decorations by default. 301 | */ 302 | interface Decor {} 303 | 304 | public HorizontalViewPager(Context context) { 305 | super(context); 306 | initViewPager(); 307 | } 308 | 309 | public HorizontalViewPager(Context context, AttributeSet attrs) { 310 | super(context, attrs); 311 | initViewPager(); 312 | } 313 | 314 | void initViewPager() { 315 | setWillNotDraw(false); 316 | setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); 317 | setFocusable(true); 318 | final Context context = getContext(); 319 | mScroller = new Scroller(context, sInterpolator); 320 | final ViewConfiguration configuration = ViewConfiguration.get(context); 321 | mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration); 322 | mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); 323 | mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); 324 | mLeftEdge = new EdgeEffectCompat(context); 325 | mRightEdge = new EdgeEffectCompat(context); 326 | 327 | final float density = context.getResources().getDisplayMetrics().density; 328 | mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density); 329 | mCloseEnough = (int) (CLOSE_ENOUGH * density); 330 | mDefaultGutterSize = (int) (DEFAULT_GUTTER_SIZE * density); 331 | 332 | ViewCompat.setAccessibilityDelegate(this, new MyAccessibilityDelegate()); 333 | 334 | if (ViewCompat.getImportantForAccessibility(this) 335 | == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 336 | ViewCompat.setImportantForAccessibility(this, 337 | ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES); 338 | } 339 | } 340 | 341 | private void setScrollState(int newState) { 342 | if (mScrollState == newState) { 343 | return; 344 | } 345 | 346 | mScrollState = newState; 347 | if (mOnPageChangeListener != null) { 348 | mOnPageChangeListener.onPageScrollStateChanged(newState); 349 | } 350 | } 351 | 352 | /** 353 | * Set a PagerAdapter that will supply views for this pager as needed. 354 | * 355 | * @param adapter Adapter to use 356 | */ 357 | public void setAdapter(PagerAdapter adapter) { 358 | if (mAdapter != null) { 359 | mAdapter.unregisterDataSetObserver(mObserver); 360 | mAdapter.startUpdate(this); 361 | for (int i = 0; i < mItems.size(); i++) { 362 | final ItemInfo ii = mItems.get(i); 363 | mAdapter.destroyItem(this, ii.position, ii.object); 364 | } 365 | mAdapter.finishUpdate(this); 366 | mItems.clear(); 367 | removeNonDecorViews(); 368 | mCurItem = 0; 369 | scrollTo(0, 0); 370 | } 371 | 372 | final PagerAdapter oldAdapter = mAdapter; 373 | mAdapter = adapter; 374 | 375 | if (mAdapter != null) { 376 | if (mObserver == null) { 377 | mObserver = new PagerObserver(); 378 | } 379 | mAdapter.registerDataSetObserver(mObserver); 380 | mPopulatePending = false; 381 | mFirstLayout = true; 382 | if (mRestoredCurItem >= 0) { 383 | mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader); 384 | setCurrentItemInternal(mRestoredCurItem, false, true); 385 | mRestoredCurItem = -1; 386 | mRestoredAdapterState = null; 387 | mRestoredClassLoader = null; 388 | } else { 389 | populate(); 390 | } 391 | } 392 | 393 | if (mAdapterChangeListener != null && oldAdapter != adapter) { 394 | mAdapterChangeListener.onAdapterChanged(oldAdapter, adapter); 395 | } 396 | } 397 | 398 | private void removeNonDecorViews() { 399 | for (int i = 0; i < getChildCount(); i++) { 400 | final View child = getChildAt(i); 401 | final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 402 | if (!lp.isDecor) { 403 | removeViewAt(i); 404 | i--; 405 | } 406 | } 407 | } 408 | 409 | /** 410 | * Retrieve the current adapter supplying pages. 411 | * 412 | * @return The currently registered PagerAdapter 413 | */ 414 | public PagerAdapter getAdapter() { 415 | return mAdapter; 416 | } 417 | 418 | void setOnAdapterChangeListener(OnAdapterChangeListener listener) { 419 | mAdapterChangeListener = listener; 420 | } 421 | 422 | /** 423 | * Set the currently selected page. If the ViewPager has already been through its first 424 | * layout with its current adapter there will be a smooth animated transition between 425 | * the current item and the specified item. 426 | * 427 | * @param item Item index to select 428 | */ 429 | public void setCurrentItem(int item) { 430 | mPopulatePending = false; 431 | setCurrentItemInternal(item, !mFirstLayout, false); 432 | } 433 | 434 | /** 435 | * Set the currently selected page. 436 | * 437 | * @param item Item index to select 438 | * @param smoothScroll True to smoothly scroll to the new item, false to transition immediately 439 | */ 440 | public void setCurrentItem(int item, boolean smoothScroll) { 441 | mPopulatePending = false; 442 | setCurrentItemInternal(item, smoothScroll, false); 443 | } 444 | 445 | public int getCurrentItem() { 446 | return mCurItem; 447 | } 448 | 449 | void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) { 450 | setCurrentItemInternal(item, smoothScroll, always, 0); 451 | } 452 | 453 | void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) { 454 | if (mAdapter == null || mAdapter.getCount() <= 0) { 455 | setScrollingCacheEnabled(false); 456 | return; 457 | } 458 | if (!always && mCurItem == item && mItems.size() != 0) { 459 | setScrollingCacheEnabled(false); 460 | return; 461 | } 462 | 463 | if (item < 0) { 464 | item = 0; 465 | } else if (item >= mAdapter.getCount()) { 466 | item = mAdapter.getCount() - 1; 467 | } 468 | final int pageLimit = mOffscreenPageLimit; 469 | if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) { 470 | // We are doing a jump by more than one page. To avoid 471 | // glitches, we want to keep all current pages in the view 472 | // until the scroll ends. 473 | for (int i=0; iThis is offered as an optimization. If you know in advance the number 545 | * of pages you will need to support or have lazy-loading mechanisms in place 546 | * on your pages, tweaking this setting can have benefits in perceived smoothness 547 | * of paging animations and interaction. If you have a small number of pages (3-4) 548 | * that you can keep active all at once, less time will be spent in layout for 549 | * newly created view subtrees as the user pages back and forth.

550 | * 551 | *

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

553 | * 554 | * @param limit How many pages will be kept offscreen in an idle state. 555 | */ 556 | public void setOffscreenPageLimit(int limit) { 557 | if (limit < DEFAULT_OFFSCREEN_PAGES) { 558 | Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " + 559 | DEFAULT_OFFSCREEN_PAGES); 560 | limit = DEFAULT_OFFSCREEN_PAGES; 561 | } 562 | if (limit != mOffscreenPageLimit) { 563 | mOffscreenPageLimit = limit; 564 | populate(); 565 | } 566 | } 567 | 568 | /** 569 | * Set the margin between pages. 570 | * 571 | * @param marginPixels Distance between adjacent pages in pixels 572 | * @see #getPageMargin() 573 | * @see #setPageMarginDrawable(Drawable) 574 | * @see #setPageMarginDrawable(int) 575 | */ 576 | public void setPageMargin(int marginPixels) { 577 | final int oldMargin = mPageMargin; 578 | mPageMargin = marginPixels; 579 | 580 | final int width = getWidth(); 581 | recomputeScrollPosition(width, width, marginPixels, oldMargin); 582 | 583 | requestLayout(); 584 | } 585 | 586 | /** 587 | * Return the margin between pages. 588 | * 589 | * @return The size of the margin in pixels 590 | */ 591 | public int getPageMargin() { 592 | return mPageMargin; 593 | } 594 | 595 | /** 596 | * Set a drawable that will be used to fill the margin between pages. 597 | * 598 | * @param d Drawable to display between pages 599 | */ 600 | public void setPageMarginDrawable(Drawable d) { 601 | mMarginDrawable = d; 602 | if (d != null) refreshDrawableState(); 603 | setWillNotDraw(d == null); 604 | invalidate(); 605 | } 606 | 607 | /** 608 | * Set a drawable that will be used to fill the margin between pages. 609 | * 610 | * @param resId Resource ID of a drawable to display between pages 611 | */ 612 | public void setPageMarginDrawable(int resId) { 613 | setPageMarginDrawable(getContext().getResources().getDrawable(resId)); 614 | } 615 | 616 | @Override 617 | protected boolean verifyDrawable(Drawable who) { 618 | return super.verifyDrawable(who) || who == mMarginDrawable; 619 | } 620 | 621 | @Override 622 | protected void drawableStateChanged() { 623 | super.drawableStateChanged(); 624 | final Drawable d = mMarginDrawable; 625 | if (d != null && d.isStateful()) { 626 | d.setState(getDrawableState()); 627 | } 628 | } 629 | 630 | // We want the duration of the page snap animation to be influenced by the distance that 631 | // the screen has to travel, however, we don't want this duration to be effected in a 632 | // purely linear fashion. Instead, we use this method to moderate the effect that the distance 633 | // of travel has on the overall snap duration. 634 | float distanceInfluenceForSnapDuration(float f) { 635 | f -= 0.5f; // center the values about 0. 636 | f *= 0.3f * Math.PI / 2.0f; 637 | return (float) Math.sin(f); 638 | } 639 | 640 | /** 641 | * Like {@link View#scrollBy}, but scroll smoothly instead of immediately. 642 | * 643 | * @param x the number of pixels to scroll by on the X axis 644 | * @param y the number of pixels to scroll by on the Y axis 645 | */ 646 | void smoothScrollTo(int x, int y) { 647 | smoothScrollTo(x, y, 0); 648 | } 649 | 650 | /** 651 | * Like {@link View#scrollBy}, but scroll smoothly instead of immediately. 652 | * 653 | * @param x the number of pixels to scroll by on the X axis 654 | * @param y the number of pixels to scroll by on the Y axis 655 | * @param velocity the velocity associated with a fling, if applicable. (0 otherwise) 656 | */ 657 | void smoothScrollTo(int x, int y, int velocity) { 658 | if (getChildCount() == 0) { 659 | // Nothing to do. 660 | setScrollingCacheEnabled(false); 661 | return; 662 | } 663 | int sx = getScrollX(); 664 | int sy = getScrollY(); 665 | int dx = x - sx; 666 | int dy = y - sy; 667 | if (dx == 0 && dy == 0) { 668 | completeScroll(); 669 | populate(); 670 | setScrollState(SCROLL_STATE_IDLE); 671 | return; 672 | } 673 | 674 | setScrollingCacheEnabled(true); 675 | setScrollState(SCROLL_STATE_SETTLING); 676 | 677 | final int width = getWidth(); 678 | final int halfWidth = width / 2; 679 | final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / width); 680 | final float distance = halfWidth + halfWidth * 681 | distanceInfluenceForSnapDuration(distanceRatio); 682 | 683 | int duration = 0; 684 | velocity = Math.abs(velocity); 685 | if (velocity > 0) { 686 | duration = 4 * Math.round(1000 * Math.abs(distance / velocity)); 687 | } else { 688 | final float pageWidth = width * mAdapter.getPageWidth(mCurItem); 689 | final float pageDelta = (float) Math.abs(dx) / (pageWidth + mPageMargin); 690 | duration = (int) ((pageDelta + 1) * 100); 691 | } 692 | duration = Math.min(duration, MAX_SETTLE_DURATION); 693 | 694 | mScroller.startScroll(sx, sy, dx, dy, duration); 695 | ViewCompat.postInvalidateOnAnimation(this); 696 | } 697 | 698 | ItemInfo addNewItem(int position, int index) { 699 | ItemInfo ii = new ItemInfo(); 700 | ii.position = position; 701 | ii.object = mAdapter.instantiateItem(this, position); 702 | ii.widthFactor = mAdapter.getPageWidth(position); 703 | if (index < 0 || index >= mItems.size()) { 704 | mItems.add(ii); 705 | } else { 706 | mItems.add(index, ii); 707 | } 708 | return ii; 709 | } 710 | 711 | void dataSetChanged() { 712 | // This method only gets called if our observer is attached, so mAdapter is non-null. 713 | 714 | boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1 && 715 | mItems.size() < mAdapter.getCount(); 716 | int newCurrItem = mCurItem; 717 | 718 | boolean isUpdating = false; 719 | for (int i = 0; i < mItems.size(); i++) { 720 | final ItemInfo ii = mItems.get(i); 721 | final int newPos = mAdapter.getItemPosition(ii.object); 722 | 723 | if (newPos == PagerAdapter.POSITION_UNCHANGED) { 724 | continue; 725 | } 726 | 727 | if (newPos == PagerAdapter.POSITION_NONE) { 728 | mItems.remove(i); 729 | i--; 730 | 731 | if (!isUpdating) { 732 | mAdapter.startUpdate(this); 733 | isUpdating = true; 734 | } 735 | 736 | mAdapter.destroyItem(this, ii.position, ii.object); 737 | needPopulate = true; 738 | 739 | if (mCurItem == ii.position) { 740 | // Keep the current item in the valid range 741 | newCurrItem = Math.max(0, Math.min(mCurItem, mAdapter.getCount() - 1)); 742 | needPopulate = true; 743 | } 744 | continue; 745 | } 746 | 747 | if (ii.position != newPos) { 748 | if (ii.position == mCurItem) { 749 | // Our current item changed position. Follow it. 750 | newCurrItem = newPos; 751 | } 752 | 753 | ii.position = newPos; 754 | needPopulate = true; 755 | } 756 | } 757 | 758 | if (isUpdating) { 759 | mAdapter.finishUpdate(this); 760 | } 761 | 762 | Collections.sort(mItems, COMPARATOR); 763 | 764 | if (needPopulate) { 765 | // Reset our known page widths; populate will recompute them. 766 | final int childCount = getChildCount(); 767 | for (int i = 0; i < childCount; i++) { 768 | final View child = getChildAt(i); 769 | final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 770 | if (!lp.isDecor) { 771 | lp.widthFactor = 0.f; 772 | } 773 | } 774 | 775 | setCurrentItemInternal(newCurrItem, false, true); 776 | requestLayout(); 777 | } 778 | } 779 | 780 | void populate() { 781 | populate(mCurItem); 782 | } 783 | 784 | void populate(int newCurrentItem) { 785 | ItemInfo oldCurInfo = null; 786 | if (mCurItem != newCurrentItem) { 787 | oldCurInfo = infoForPosition(mCurItem); 788 | mCurItem = newCurrentItem; 789 | } 790 | 791 | if (mAdapter == null) { 792 | return; 793 | } 794 | 795 | // Bail now if we are waiting to populate. This is to hold off 796 | // on creating views from the time the user releases their finger to 797 | // fling to a new position until we have finished the scroll to 798 | // that position, avoiding glitches from happening at that point. 799 | if (mPopulatePending) { 800 | if (DEBUG) Log.i(TAG, "populate is pending, skipping for now..."); 801 | return; 802 | } 803 | 804 | // Also, don't populate until we are attached to a window. This is to 805 | // avoid trying to populate before we have restored our view hierarchy 806 | // state and conflicting with what is restored. 807 | if (getWindowToken() == null) { 808 | return; 809 | } 810 | 811 | mAdapter.startUpdate(this); 812 | 813 | final int pageLimit = mOffscreenPageLimit; 814 | final int startPos = Math.max(0, mCurItem - pageLimit); 815 | final int N = mAdapter.getCount(); 816 | final int endPos = Math.min(N-1, mCurItem + pageLimit); 817 | 818 | // Locate the currently focused item or add it if needed. 819 | int curIndex = -1; 820 | ItemInfo curItem = null; 821 | for (curIndex = 0; curIndex < mItems.size(); curIndex++) { 822 | final ItemInfo ii = mItems.get(curIndex); 823 | if (ii.position >= mCurItem) { 824 | if (ii.position == mCurItem) curItem = ii; 825 | break; 826 | } 827 | } 828 | 829 | if (curItem == null && N > 0) { 830 | curItem = addNewItem(mCurItem, curIndex); 831 | } 832 | 833 | // Fill 3x the available width or up to the number of offscreen 834 | // pages requested to either side, whichever is larger. 835 | // If we have no current item we have no work to do. 836 | if (curItem != null) { 837 | float extraWidthLeft = 0.f; 838 | int itemIndex = curIndex - 1; 839 | ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null; 840 | final float leftWidthNeeded = 2.f - curItem.widthFactor; 841 | for (int pos = mCurItem - 1; pos >= 0; pos--) { 842 | if (extraWidthLeft >= leftWidthNeeded && pos < startPos) { 843 | if (ii == null) { 844 | break; 845 | } 846 | if (pos == ii.position && !ii.scrolling) { 847 | mItems.remove(itemIndex); 848 | mAdapter.destroyItem(this, pos, ii.object); 849 | itemIndex--; 850 | curIndex--; 851 | ii = itemIndex >= 0 ? mItems.get(itemIndex) : null; 852 | } 853 | } else if (ii != null && pos == ii.position) { 854 | extraWidthLeft += ii.widthFactor; 855 | itemIndex--; 856 | ii = itemIndex >= 0 ? mItems.get(itemIndex) : null; 857 | } else { 858 | ii = addNewItem(pos, itemIndex + 1); 859 | extraWidthLeft += ii.widthFactor; 860 | curIndex++; 861 | ii = itemIndex >= 0 ? mItems.get(itemIndex) : null; 862 | } 863 | } 864 | 865 | float extraWidthRight = curItem.widthFactor; 866 | itemIndex = curIndex + 1; 867 | if (extraWidthRight < 2.f) { 868 | ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null; 869 | for (int pos = mCurItem + 1; pos < N; pos++) { 870 | if (extraWidthRight >= 2.f && pos > endPos) { 871 | if (ii == null) { 872 | break; 873 | } 874 | if (pos == ii.position && !ii.scrolling) { 875 | mItems.remove(itemIndex); 876 | mAdapter.destroyItem(this, pos, ii.object); 877 | ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null; 878 | } 879 | } else if (ii != null && pos == ii.position) { 880 | extraWidthRight += ii.widthFactor; 881 | itemIndex++; 882 | ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null; 883 | } else { 884 | ii = addNewItem(pos, itemIndex); 885 | itemIndex++; 886 | extraWidthRight += ii.widthFactor; 887 | ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null; 888 | } 889 | } 890 | } 891 | 892 | calculatePageOffsets(curItem, curIndex, oldCurInfo); 893 | } 894 | 895 | if (DEBUG) { 896 | Log.i(TAG, "Current page list:"); 897 | for (int i=0; i 0 ? (float) mPageMargin / width : 0; 941 | // Fix up offsets for later layout. 942 | if (oldCurInfo != null) { 943 | final int oldCurPosition = oldCurInfo.position; 944 | // Base offsets off of oldCurInfo. 945 | if (oldCurPosition < curItem.position) { 946 | int itemIndex = 0; 947 | ItemInfo ii = null; 948 | float offset = oldCurInfo.offset + oldCurInfo.widthFactor + marginOffset; 949 | for (int pos = oldCurPosition + 1; 950 | pos <= curItem.position && itemIndex < mItems.size(); pos++) { 951 | ii = mItems.get(itemIndex); 952 | while (pos > ii.position && itemIndex < mItems.size() - 1) { 953 | itemIndex++; 954 | ii = mItems.get(itemIndex); 955 | } 956 | while (pos < ii.position) { 957 | // We don't have an item populated for this, 958 | // ask the adapter for an offset. 959 | offset += mAdapter.getPageWidth(pos) + marginOffset; 960 | pos++; 961 | } 962 | ii.offset = offset; 963 | offset += ii.widthFactor + marginOffset; 964 | } 965 | } else if (oldCurPosition > curItem.position) { 966 | int itemIndex = mItems.size() - 1; 967 | ItemInfo ii = null; 968 | float offset = oldCurInfo.offset; 969 | for (int pos = oldCurPosition - 1; 970 | pos >= curItem.position && itemIndex >= 0; pos--) { 971 | ii = mItems.get(itemIndex); 972 | while (pos < ii.position && itemIndex > 0) { 973 | itemIndex--; 974 | ii = mItems.get(itemIndex); 975 | } 976 | while (pos > ii.position) { 977 | // We don't have an item populated for this, 978 | // ask the adapter for an offset. 979 | offset -= mAdapter.getPageWidth(pos) + marginOffset; 980 | pos--; 981 | } 982 | offset -= ii.widthFactor + marginOffset; 983 | ii.offset = offset; 984 | } 985 | } 986 | } 987 | 988 | // Base all offsets off of curItem. 989 | final int itemCount = mItems.size(); 990 | float offset = curItem.offset; 991 | int pos = curItem.position - 1; 992 | mFirstOffset = curItem.position == 0 ? curItem.offset : -Float.MAX_VALUE; 993 | mLastOffset = curItem.position == N - 1 ? 994 | curItem.offset + curItem.widthFactor - 1 : Float.MAX_VALUE; 995 | // Previous pages 996 | for (int i = curIndex - 1; i >= 0; i--, pos--) { 997 | final ItemInfo ii = mItems.get(i); 998 | while (pos > ii.position) { 999 | offset -= mAdapter.getPageWidth(pos--) + marginOffset; 1000 | } 1001 | offset -= ii.widthFactor + marginOffset; 1002 | ii.offset = offset; 1003 | if (ii.position == 0) mFirstOffset = offset; 1004 | } 1005 | offset = curItem.offset + curItem.widthFactor + marginOffset; 1006 | pos = curItem.position + 1; 1007 | // Next pages 1008 | for (int i = curIndex + 1; i < itemCount; i++, pos++) { 1009 | final ItemInfo ii = mItems.get(i); 1010 | while (pos < ii.position) { 1011 | offset += mAdapter.getPageWidth(pos++) + marginOffset; 1012 | } 1013 | if (ii.position == N - 1) { 1014 | mLastOffset = offset + ii.widthFactor - 1; 1015 | } 1016 | ii.offset = offset; 1017 | offset += ii.widthFactor + marginOffset; 1018 | } 1019 | 1020 | mNeedCalculatePageOffsets = false; 1021 | } 1022 | 1023 | /** 1024 | * This is the persistent state that is saved by ViewPager. Only needed 1025 | * if you are creating a sublass of ViewPager that must save its own 1026 | * state, in which case it should implement a subclass of this which 1027 | * contains that state. 1028 | */ 1029 | public static class SavedState extends BaseSavedState { 1030 | int position; 1031 | Parcelable adapterState; 1032 | ClassLoader loader; 1033 | 1034 | public SavedState(Parcelable superState) { 1035 | super(superState); 1036 | } 1037 | 1038 | @Override 1039 | public void writeToParcel(Parcel out, int flags) { 1040 | super.writeToParcel(out, flags); 1041 | out.writeInt(position); 1042 | out.writeParcelable(adapterState, flags); 1043 | } 1044 | 1045 | @Override 1046 | public String toString() { 1047 | return "FragmentPager.SavedState{" 1048 | + Integer.toHexString(System.identityHashCode(this)) 1049 | + " position=" + position + "}"; 1050 | } 1051 | 1052 | public static final Parcelable.Creator CREATOR 1053 | = ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks() { 1054 | @Override 1055 | public SavedState createFromParcel(Parcel in, ClassLoader loader) { 1056 | return new SavedState(in, loader); 1057 | } 1058 | @Override 1059 | public SavedState[] newArray(int size) { 1060 | return new SavedState[size]; 1061 | } 1062 | }); 1063 | 1064 | SavedState(Parcel in, ClassLoader loader) { 1065 | super(in); 1066 | if (loader == null) { 1067 | loader = getClass().getClassLoader(); 1068 | } 1069 | position = in.readInt(); 1070 | adapterState = in.readParcelable(loader); 1071 | this.loader = loader; 1072 | } 1073 | } 1074 | 1075 | @Override 1076 | public Parcelable onSaveInstanceState() { 1077 | Parcelable superState = super.onSaveInstanceState(); 1078 | SavedState ss = new SavedState(superState); 1079 | ss.position = mCurItem; 1080 | if (mAdapter != null) { 1081 | ss.adapterState = mAdapter.saveState(); 1082 | } 1083 | return ss; 1084 | } 1085 | 1086 | @Override 1087 | public void onRestoreInstanceState(Parcelable state) { 1088 | if (!(state instanceof SavedState)) { 1089 | super.onRestoreInstanceState(state); 1090 | return; 1091 | } 1092 | 1093 | SavedState ss = (SavedState)state; 1094 | super.onRestoreInstanceState(ss.getSuperState()); 1095 | 1096 | if (mAdapter != null) { 1097 | mAdapter.restoreState(ss.adapterState, ss.loader); 1098 | setCurrentItemInternal(ss.position, false, true); 1099 | } else { 1100 | mRestoredCurItem = ss.position; 1101 | mRestoredAdapterState = ss.adapterState; 1102 | mRestoredClassLoader = ss.loader; 1103 | } 1104 | } 1105 | 1106 | @Override 1107 | public void addView(View child, int index, ViewGroup.LayoutParams params) { 1108 | if (!checkLayoutParams(params)) { 1109 | params = generateLayoutParams(params); 1110 | } 1111 | final LayoutParams lp = (LayoutParams) params; 1112 | lp.isDecor |= child instanceof Decor; 1113 | if (mInLayout) { 1114 | if (lp != null && lp.isDecor) { 1115 | throw new IllegalStateException("Cannot add pager decor view during layout"); 1116 | } 1117 | lp.needsMeasure = true; 1118 | addViewInLayout(child, index, params); 1119 | } else { 1120 | super.addView(child, index, params); 1121 | } 1122 | 1123 | if (USE_CACHE) { 1124 | if (child.getVisibility() != GONE) { 1125 | child.setDrawingCacheEnabled(mScrollingCacheEnabled); 1126 | } else { 1127 | child.setDrawingCacheEnabled(false); 1128 | } 1129 | } 1130 | } 1131 | 1132 | ItemInfo infoForChild(View child) { 1133 | for (int i=0; i 0 && !mItems.isEmpty()) { 1276 | final int widthWithMargin = width + margin; 1277 | final int oldWidthWithMargin = oldWidth + oldMargin; 1278 | final int xpos = getScrollX(); 1279 | final float pageOffset = (float) xpos / oldWidthWithMargin; 1280 | final int newOffsetPixels = (int) (pageOffset * widthWithMargin); 1281 | 1282 | scrollTo(newOffsetPixels, getScrollY()); 1283 | if (!mScroller.isFinished()) { 1284 | // We now return to your regularly scheduled scroll, already in progress. 1285 | final int newDuration = mScroller.getDuration() - mScroller.timePassed(); 1286 | ItemInfo targetInfo = infoForPosition(mCurItem); 1287 | mScroller.startScroll(newOffsetPixels, 0, 1288 | (int) (targetInfo.offset * width), 0, newDuration); 1289 | } 1290 | } else { 1291 | final ItemInfo ii = infoForPosition(mCurItem); 1292 | final float scrollOffset = ii != null ? Math.min(ii.offset, mLastOffset) : 0; 1293 | final int scrollPos = (int) (scrollOffset * width); 1294 | if (scrollPos != getScrollX()) { 1295 | completeScroll(); 1296 | scrollTo(scrollPos, getScrollY()); 1297 | } 1298 | } 1299 | } 1300 | 1301 | @Override 1302 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 1303 | mInLayout = true; 1304 | populate(); 1305 | mInLayout = false; 1306 | 1307 | final int count = getChildCount(); 1308 | int width = r - l; 1309 | int height = b - t; 1310 | int paddingLeft = getPaddingLeft(); 1311 | int paddingTop = getPaddingTop(); 1312 | int paddingRight = getPaddingRight(); 1313 | int paddingBottom = getPaddingBottom(); 1314 | final int scrollX = getScrollX(); 1315 | 1316 | int decorCount = 0; 1317 | 1318 | // First pass - decor views. We need to do this in two passes so that 1319 | // we have the proper offsets for non-decor views later. 1320 | for (int i = 0; i < count; i++) { 1321 | final View child = getChildAt(i); 1322 | if (child.getVisibility() != GONE) { 1323 | final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 1324 | int childLeft = 0; 1325 | int childTop = 0; 1326 | if (lp.isDecor) { 1327 | final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK; 1328 | final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK; 1329 | switch (hgrav) { 1330 | default: 1331 | childLeft = paddingLeft; 1332 | break; 1333 | case Gravity.LEFT: 1334 | childLeft = paddingLeft; 1335 | paddingLeft += child.getMeasuredWidth(); 1336 | break; 1337 | case Gravity.CENTER_HORIZONTAL: 1338 | childLeft = Math.max((width - child.getMeasuredWidth()) / 2, 1339 | paddingLeft); 1340 | break; 1341 | case Gravity.RIGHT: 1342 | childLeft = width - paddingRight - child.getMeasuredWidth(); 1343 | paddingRight += child.getMeasuredWidth(); 1344 | break; 1345 | } 1346 | switch (vgrav) { 1347 | default: 1348 | childTop = paddingTop; 1349 | break; 1350 | case Gravity.TOP: 1351 | childTop = paddingTop; 1352 | paddingTop += child.getMeasuredHeight(); 1353 | break; 1354 | case Gravity.CENTER_VERTICAL: 1355 | childTop = Math.max((height - child.getMeasuredHeight()) / 2, 1356 | paddingTop); 1357 | break; 1358 | case Gravity.BOTTOM: 1359 | childTop = height - paddingBottom - child.getMeasuredHeight(); 1360 | paddingBottom += child.getMeasuredHeight(); 1361 | break; 1362 | } 1363 | childLeft += scrollX; 1364 | child.layout(childLeft, childTop, 1365 | childLeft + child.getMeasuredWidth(), 1366 | childTop + child.getMeasuredHeight()); 1367 | decorCount++; 1368 | } 1369 | } 1370 | } 1371 | 1372 | // Page views. Do this once we have the right padding offsets from above. 1373 | for (int i = 0; i < count; i++) { 1374 | final View child = getChildAt(i); 1375 | if (child.getVisibility() != GONE) { 1376 | final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 1377 | ItemInfo ii; 1378 | if (!lp.isDecor && (ii = infoForChild(child)) != null) { 1379 | int loff = (int) (width * ii.offset); 1380 | int childLeft = paddingLeft + loff; 1381 | int childTop = paddingTop; 1382 | if (lp.needsMeasure) { 1383 | // This was added during layout and needs measurement. 1384 | // Do it now that we know what we're working with. 1385 | lp.needsMeasure = false; 1386 | final int widthSpec = MeasureSpec.makeMeasureSpec( 1387 | (int) ((width - paddingLeft - paddingRight) * lp.widthFactor), 1388 | MeasureSpec.EXACTLY); 1389 | final int heightSpec = MeasureSpec.makeMeasureSpec( 1390 | (int) (height - paddingTop - paddingBottom), 1391 | MeasureSpec.EXACTLY); 1392 | child.measure(widthSpec, heightSpec); 1393 | } 1394 | if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object 1395 | + ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth() 1396 | + "x" + child.getMeasuredHeight()); 1397 | child.layout(childLeft, childTop, 1398 | childLeft + child.getMeasuredWidth(), 1399 | childTop + child.getMeasuredHeight()); 1400 | } 1401 | } 1402 | } 1403 | mTopPageBounds = paddingTop; 1404 | mBottomPageBounds = height - paddingBottom; 1405 | mDecorChildCount = decorCount; 1406 | mFirstLayout = false; 1407 | } 1408 | 1409 | @Override 1410 | public void computeScroll() { 1411 | if (!mScroller.isFinished() && mScroller.computeScrollOffset()) { 1412 | int oldX = getScrollX(); 1413 | int oldY = getScrollY(); 1414 | int x = mScroller.getCurrX(); 1415 | int y = mScroller.getCurrY(); 1416 | 1417 | if (oldX != x || oldY != y) { 1418 | scrollTo(x, y); 1419 | if (!pageScrolled(x)) { 1420 | mScroller.abortAnimation(); 1421 | scrollTo(0, y); 1422 | } 1423 | } 1424 | 1425 | // Keep on drawing until the animation has finished. 1426 | ViewCompat.postInvalidateOnAnimation(this); 1427 | return; 1428 | } 1429 | 1430 | // Done with scroll, clean up state. 1431 | completeScroll(); 1432 | } 1433 | 1434 | private boolean pageScrolled(int xpos) { 1435 | if (mItems.size() == 0) { 1436 | mCalledSuper = false; 1437 | onPageScrolled(0, 0, 0); 1438 | if (!mCalledSuper) { 1439 | throw new IllegalStateException( 1440 | "onPageScrolled did not call superclass implementation"); 1441 | } 1442 | return false; 1443 | } 1444 | final ItemInfo ii = infoForCurrentScrollPosition(); 1445 | final int width = getWidth(); 1446 | final int widthWithMargin = width + mPageMargin; 1447 | final float marginOffset = (float) mPageMargin / width; 1448 | final int currentPage = ii.position; 1449 | final float pageOffset = (((float) xpos / width) - ii.offset) / 1450 | (ii.widthFactor + marginOffset); 1451 | final int offsetPixels = (int) (pageOffset * widthWithMargin); 1452 | 1453 | mCalledSuper = false; 1454 | onPageScrolled(currentPage, pageOffset, offsetPixels); 1455 | if (!mCalledSuper) { 1456 | throw new IllegalStateException( 1457 | "onPageScrolled did not call superclass implementation"); 1458 | } 1459 | return true; 1460 | } 1461 | 1462 | /** 1463 | * This method will be invoked when the current page is scrolled, either as part 1464 | * of a programmatically initiated smooth scroll or a user initiated touch scroll. 1465 | * If you override this method you must call through to the superclass implementation 1466 | * (e.g. super.onPageScrolled(position, offset, offsetPixels)) before onPageScrolled 1467 | * returns. 1468 | * 1469 | * @param position Position index of the first page currently being displayed. 1470 | * Page position+1 will be visible if positionOffset is nonzero. 1471 | * @param offset Value from [0, 1) indicating the offset from the page at position. 1472 | * @param offsetPixels Value in pixels indicating the offset from position. 1473 | */ 1474 | protected void onPageScrolled(int position, float offset, int offsetPixels) { 1475 | // Offset any decor views if needed - keep them on-screen at all times. 1476 | if (mDecorChildCount > 0) { 1477 | final int scrollX = getScrollX(); 1478 | int paddingLeft = getPaddingLeft(); 1479 | int paddingRight = getPaddingRight(); 1480 | final int width = getWidth(); 1481 | final int childCount = getChildCount(); 1482 | for (int i = 0; i < childCount; i++) { 1483 | final View child = getChildAt(i); 1484 | final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 1485 | if (!lp.isDecor) continue; 1486 | 1487 | final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK; 1488 | int childLeft = 0; 1489 | switch (hgrav) { 1490 | default: 1491 | childLeft = paddingLeft; 1492 | break; 1493 | case Gravity.LEFT: 1494 | childLeft = paddingLeft; 1495 | paddingLeft += child.getWidth(); 1496 | break; 1497 | case Gravity.CENTER_HORIZONTAL: 1498 | childLeft = Math.max((width - child.getMeasuredWidth()) / 2, 1499 | paddingLeft); 1500 | break; 1501 | case Gravity.RIGHT: 1502 | childLeft = width - paddingRight - child.getMeasuredWidth(); 1503 | paddingRight += child.getMeasuredWidth(); 1504 | break; 1505 | } 1506 | childLeft += scrollX; 1507 | 1508 | final int childOffset = childLeft - child.getLeft(); 1509 | if (childOffset != 0) { 1510 | child.offsetLeftAndRight(childOffset); 1511 | } 1512 | } 1513 | } 1514 | 1515 | if (mOnPageChangeListener != null) { 1516 | mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels); 1517 | } 1518 | if (mInternalPageChangeListener != null) { 1519 | mInternalPageChangeListener.onPageScrolled(position, offset, offsetPixels); 1520 | } 1521 | mCalledSuper = true; 1522 | } 1523 | 1524 | private void completeScroll() { 1525 | boolean needPopulate = mScrollState == SCROLL_STATE_SETTLING; 1526 | if (needPopulate) { 1527 | // Done with scroll, no longer want to cache view drawing. 1528 | setScrollingCacheEnabled(false); 1529 | mScroller.abortAnimation(); 1530 | int oldX = getScrollX(); 1531 | int oldY = getScrollY(); 1532 | int x = mScroller.getCurrX(); 1533 | int y = mScroller.getCurrY(); 1534 | if (oldX != x || oldY != y) { 1535 | scrollTo(x, y); 1536 | } 1537 | setScrollState(SCROLL_STATE_IDLE); 1538 | } 1539 | mPopulatePending = false; 1540 | for (int i=0; i 0) || (x > getWidth() - mGutterSize && dx < 0); 1554 | } 1555 | 1556 | @Override 1557 | public boolean onInterceptTouchEvent(MotionEvent ev) { 1558 | 1559 | if (DEBUG) Log.i(TAG, "I - Recibo ev"); 1560 | 1561 | 1562 | /* 1563 | * This method JUST determines whether we want to intercept the motion. 1564 | * If we return true, onMotionEvent will be called and we do the actual 1565 | * scrolling there. 1566 | */ 1567 | 1568 | final int action = ev.getAction() & MotionEventCompat.ACTION_MASK; 1569 | 1570 | // Always take care of the touch gesture being complete. 1571 | if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { 1572 | if (DEBUG) Log.i(TAG, "I - CANCEL - UP"); 1573 | // Release the drag. 1574 | if (DEBUG) Log.v(TAG, "Intercept done!"); 1575 | mIsBeingDragged = false; 1576 | mIsUnableToDrag = false; 1577 | mActivePointerId = INVALID_POINTER; 1578 | if (mVelocityTracker != null) { 1579 | mVelocityTracker.recycle(); 1580 | mVelocityTracker = null; 1581 | } 1582 | return false; 1583 | } 1584 | 1585 | // Nothing more to do here if we have decided whether or not we 1586 | // are dragging. 1587 | if (action != MotionEvent.ACTION_DOWN) { 1588 | if (DEBUG) Log.i(TAG, "I - DOWN"); 1589 | if (mIsBeingDragged) { 1590 | if (DEBUG) Log.v(TAG, "Intercept returning true!"); 1591 | return true; 1592 | } 1593 | if (mIsUnableToDrag) { 1594 | if (DEBUG) Log.v(TAG, "Intercept returning false!"); 1595 | return false; 1596 | } 1597 | } 1598 | 1599 | switch (action) { 1600 | case MotionEvent.ACTION_MOVE: { 1601 | if (DEBUG) Log.i(TAG, "I - MOVE"); 1602 | /* 1603 | * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check 1604 | * whether the user has moved far enough from his original down touch. 1605 | */ 1606 | 1607 | /* 1608 | * Locally do absolute value. mLastMotionY is set to the y value 1609 | * of the down event. 1610 | */ 1611 | final int activePointerId = mActivePointerId; 1612 | if (activePointerId == INVALID_POINTER) { 1613 | // If we don't have a valid id, the touch down wasn't on content. 1614 | break; 1615 | } 1616 | 1617 | final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId); 1618 | final float x = MotionEventCompat.getX(ev, pointerIndex); 1619 | final float dx = x - mLastMotionX; 1620 | final float xDiff = Math.abs(dx); 1621 | final float y = MotionEventCompat.getY(ev, pointerIndex); 1622 | final float yDiff = Math.abs(y - mLastMotionY); 1623 | if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff); 1624 | 1625 | if (dx != 0 && !isGutterDrag(mLastMotionX, dx) && 1626 | canScroll(this, false, (int) dx, (int) x, (int) y)) { 1627 | // Nested view has scrollable area under this point. Let it be handled there. 1628 | mInitialMotionX = mLastMotionX = x; 1629 | mLastMotionY = y; 1630 | mIsUnableToDrag = true; 1631 | return false; 1632 | } 1633 | if (xDiff > mTouchSlop && xDiff > yDiff) { 1634 | if (DEBUG) Log.v(TAG, "Starting drag!"); 1635 | mIsBeingDragged = true; 1636 | setScrollState(SCROLL_STATE_DRAGGING); 1637 | mLastMotionX = dx > 0 ? mInitialMotionX + mTouchSlop : 1638 | mInitialMotionX - mTouchSlop; 1639 | setScrollingCacheEnabled(true); 1640 | } else { 1641 | if (yDiff > mTouchSlop) { 1642 | // The finger has moved enough in the vertical 1643 | // direction to be counted as a drag... abort 1644 | // any attempt to drag horizontally, to work correctly 1645 | // with children that have scrolling containers. 1646 | if (DEBUG) Log.v(TAG, "Starting unable to drag!"); 1647 | mIsUnableToDrag = true; 1648 | } 1649 | } 1650 | if (mIsBeingDragged) { 1651 | // Scroll to follow the motion event 1652 | if (performDrag(x)) { 1653 | ViewCompat.postInvalidateOnAnimation(this); 1654 | } 1655 | } 1656 | break; 1657 | } 1658 | 1659 | case MotionEvent.ACTION_DOWN: { 1660 | if (DEBUG) Log.i(TAG, "I - DOWN2"); 1661 | 1662 | /* 1663 | * Remember location of down touch. 1664 | * ACTION_DOWN always refers to pointer index 0. 1665 | */ 1666 | mLastMotionX = mInitialMotionX = ev.getX(); 1667 | mLastMotionY = ev.getY(); 1668 | mActivePointerId = MotionEventCompat.getPointerId(ev, 0); 1669 | mIsUnableToDrag = false; 1670 | 1671 | mScroller.computeScrollOffset(); 1672 | if (mScrollState == SCROLL_STATE_SETTLING && 1673 | Math.abs(mScroller.getFinalX() - mScroller.getCurrX()) > mCloseEnough) { 1674 | // Let the user 'catch' the pager as it animates. 1675 | mScroller.abortAnimation(); 1676 | mPopulatePending = false; 1677 | populate(); 1678 | mIsBeingDragged = true; 1679 | setScrollState(SCROLL_STATE_DRAGGING); 1680 | } else { 1681 | completeScroll(); 1682 | mIsBeingDragged = false; 1683 | } 1684 | 1685 | if (DEBUG) Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY 1686 | + " mIsBeingDragged=" + mIsBeingDragged 1687 | + "mIsUnableToDrag=" + mIsUnableToDrag); 1688 | break; 1689 | } 1690 | 1691 | case MotionEventCompat.ACTION_POINTER_UP: 1692 | if (DEBUG) Log.i(TAG, "I - ACTION POINTER UP"); 1693 | onSecondaryPointerUp(ev); 1694 | break; 1695 | } 1696 | 1697 | if (mVelocityTracker == null) { 1698 | mVelocityTracker = VelocityTracker.obtain(); 1699 | } 1700 | mVelocityTracker.addMovement(ev); 1701 | 1702 | /* 1703 | * The only time we want to intercept motion events is if we are in the 1704 | * drag mode. 1705 | */ 1706 | return mIsBeingDragged; 1707 | } 1708 | 1709 | public void setmActivePointerId(int index){ 1710 | mActivePointerId = index; 1711 | } 1712 | 1713 | @Override 1714 | public boolean onTouchEvent(MotionEvent ev) { 1715 | 1716 | if (DEBUG) Log.i(TAG, "Recibo ev"); 1717 | 1718 | if (mFakeDragging) { 1719 | // A fake drag is in progress already, ignore this real one 1720 | // but still eat the touch events. 1721 | // (It is likely that the user is multi-touching the screen.) 1722 | return true; 1723 | } 1724 | 1725 | if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) { 1726 | // Don't handle edge touches immediately -- they may actually belong to one of our 1727 | // descendants. 1728 | return false; 1729 | } 1730 | 1731 | if (mAdapter == null || mAdapter.getCount() == 0) { 1732 | // Nothing to present or scroll; nothing to touch. 1733 | return false; 1734 | } 1735 | 1736 | if (mVelocityTracker == null) { 1737 | mVelocityTracker = VelocityTracker.obtain(); 1738 | } 1739 | mVelocityTracker.addMovement(ev); 1740 | 1741 | final int action = ev.getAction(); 1742 | boolean needsInvalidate = false; 1743 | 1744 | switch (action & MotionEventCompat.ACTION_MASK) { 1745 | 1746 | case MotionEvent.ACTION_DOWN: { 1747 | if (DEBUG) Log.i(TAG, "T - DOWN"); 1748 | mScroller.abortAnimation(); 1749 | mPopulatePending = false; 1750 | populate(); 1751 | mIsBeingDragged = true; 1752 | setScrollState(SCROLL_STATE_DRAGGING); 1753 | 1754 | // Remember where the motion event started 1755 | mLastMotionX = mInitialMotionX = ev.getX(); 1756 | mActivePointerId = MotionEventCompat.getPointerId(ev, 0); 1757 | break; 1758 | } 1759 | 1760 | case MotionEvent.ACTION_MOVE: 1761 | if (DEBUG) Log.i(TAG, "T - MOVE"); 1762 | if (!mIsBeingDragged) { 1763 | 1764 | if (DEBUG) Log.i(TAG, "mActivePointerId: " + mActivePointerId); 1765 | 1766 | final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); 1767 | if (DEBUG) Log.i(TAG, "pointerIndex: " + pointerIndex); 1768 | 1769 | 1770 | final float x = MotionEventCompat.getX(ev, pointerIndex); 1771 | 1772 | if (DEBUG) Log.i(TAG, "X: " + x); 1773 | if (DEBUG) Log.i(TAG, "mLastMotionX: " + mLastMotionX); 1774 | 1775 | final float xDiff = Math.abs(x - mLastMotionX); 1776 | final float y = MotionEventCompat.getY(ev, pointerIndex); 1777 | final float yDiff = Math.abs(y - mLastMotionY); 1778 | if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff); 1779 | if (xDiff > mTouchSlop && xDiff > yDiff) { 1780 | if (DEBUG) Log.v(TAG, "Starting drag!"); 1781 | mIsBeingDragged = true; 1782 | mLastMotionX = x - mInitialMotionX > 0 ? mInitialMotionX + mTouchSlop : 1783 | mInitialMotionX - mTouchSlop; 1784 | setScrollState(SCROLL_STATE_DRAGGING); 1785 | setScrollingCacheEnabled(true); 1786 | } 1787 | } 1788 | // Not else! Note that mIsBeingDragged can be set above. 1789 | if (mIsBeingDragged) { 1790 | // Scroll to follow the motion event 1791 | final int activePointerIndex = MotionEventCompat.findPointerIndex( 1792 | ev, mActivePointerId); 1793 | final float x = MotionEventCompat.getX(ev, activePointerIndex); 1794 | needsInvalidate |= performDrag(x); 1795 | } 1796 | break; 1797 | 1798 | case MotionEvent.ACTION_UP: 1799 | if (DEBUG) Log.i(TAG, "T - ACTION UP"); 1800 | if (mIsBeingDragged) { 1801 | final VelocityTracker velocityTracker = mVelocityTracker; 1802 | velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 1803 | int initialVelocity = (int) VelocityTrackerCompat.getXVelocity( 1804 | velocityTracker, mActivePointerId); 1805 | mPopulatePending = true; 1806 | final int width = getWidth(); 1807 | final int scrollX = getScrollX(); 1808 | final ItemInfo ii = infoForCurrentScrollPosition(); 1809 | final int currentPage = ii.position; 1810 | final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor; 1811 | final int activePointerIndex = 1812 | MotionEventCompat.findPointerIndex(ev, mActivePointerId); 1813 | final float x = MotionEventCompat.getX(ev, activePointerIndex); 1814 | final int totalDelta = (int) (x - mInitialMotionX); 1815 | int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity, 1816 | totalDelta); 1817 | setCurrentItemInternal(nextPage, true, true, initialVelocity); 1818 | 1819 | mActivePointerId = INVALID_POINTER; 1820 | endDrag(); 1821 | needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease(); 1822 | } 1823 | break; 1824 | 1825 | case MotionEvent.ACTION_CANCEL: 1826 | if (DEBUG) Log.i(TAG, "T - CANCEL"); 1827 | if (mIsBeingDragged) { 1828 | setCurrentItemInternal(mCurItem, true, true); 1829 | mActivePointerId = INVALID_POINTER; 1830 | endDrag(); 1831 | needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease(); 1832 | } 1833 | break; 1834 | 1835 | case MotionEventCompat.ACTION_POINTER_DOWN: { 1836 | if (DEBUG) Log.i(TAG, "T - ACTION POINTER DOWN"); 1837 | final int index = MotionEventCompat.getActionIndex(ev); 1838 | final float x = MotionEventCompat.getX(ev, index); 1839 | mLastMotionX = x; 1840 | mActivePointerId = MotionEventCompat.getPointerId(ev, index); 1841 | break; 1842 | } 1843 | 1844 | case MotionEventCompat.ACTION_POINTER_UP: 1845 | if (DEBUG) Log.i(TAG, "T - ACTION POINTER UP"); 1846 | onSecondaryPointerUp(ev); 1847 | mLastMotionX = MotionEventCompat.getX(ev, 1848 | MotionEventCompat.findPointerIndex(ev, mActivePointerId)); 1849 | break; 1850 | } 1851 | if (needsInvalidate) { 1852 | ViewCompat.postInvalidateOnAnimation(this); 1853 | } 1854 | return true; 1855 | } 1856 | 1857 | private boolean performDrag(float x) { 1858 | boolean needsInvalidate = false; 1859 | 1860 | final float deltaX = mLastMotionX - x; 1861 | mLastMotionX = x; 1862 | 1863 | float oldScrollX = getScrollX(); 1864 | float scrollX = oldScrollX + deltaX; 1865 | final int width = getWidth(); 1866 | 1867 | float leftBound = width * mFirstOffset; 1868 | float rightBound = width * mLastOffset; 1869 | boolean leftAbsolute = true; 1870 | boolean rightAbsolute = true; 1871 | 1872 | final ItemInfo firstItem = mItems.get(0); 1873 | final ItemInfo lastItem = mItems.get(mItems.size() - 1); 1874 | if (firstItem.position != 0) { 1875 | leftAbsolute = false; 1876 | leftBound = firstItem.offset * width; 1877 | } 1878 | if (lastItem.position != mAdapter.getCount() - 1) { 1879 | rightAbsolute = false; 1880 | rightBound = lastItem.offset * width; 1881 | } 1882 | 1883 | if (scrollX < leftBound) { 1884 | if (leftAbsolute) { 1885 | float over = leftBound - scrollX; 1886 | needsInvalidate = mLeftEdge.onPull(Math.abs(over) / width); 1887 | } 1888 | scrollX = leftBound; 1889 | } else if (scrollX > rightBound) { 1890 | if (rightAbsolute) { 1891 | float over = scrollX - rightBound; 1892 | needsInvalidate = mRightEdge.onPull(Math.abs(over) / width); 1893 | } 1894 | scrollX = rightBound; 1895 | } 1896 | // Don't lose the rounded component 1897 | mLastMotionX += scrollX - (int) scrollX; 1898 | scrollTo((int) scrollX, getScrollY()); 1899 | pageScrolled((int) scrollX); 1900 | 1901 | return needsInvalidate; 1902 | } 1903 | 1904 | /** 1905 | * @return Info about the page at the current scroll position. 1906 | * This can be synthetic for a missing middle page; the 'object' field can be null. 1907 | */ 1908 | private ItemInfo infoForCurrentScrollPosition() { 1909 | final int width = getWidth(); 1910 | final float scrollOffset = width > 0 ? (float) getScrollX() / width : 0; 1911 | final float marginOffset = width > 0 ? (float) mPageMargin / width : 0; 1912 | int lastPos = -1; 1913 | float lastOffset = 0.f; 1914 | float lastWidth = 0.f; 1915 | boolean first = true; 1916 | 1917 | ItemInfo lastItem = null; 1918 | for (int i = 0; i < mItems.size(); i++) { 1919 | ItemInfo ii = mItems.get(i); 1920 | float offset; 1921 | if (!first && ii.position != lastPos + 1) { 1922 | // Create a synthetic item for a missing page. 1923 | ii = mTempItem; 1924 | ii.offset = lastOffset + lastWidth + marginOffset; 1925 | ii.position = lastPos + 1; 1926 | ii.widthFactor = mAdapter.getPageWidth(ii.position); 1927 | i--; 1928 | } 1929 | offset = ii.offset; 1930 | 1931 | final float leftBound = offset; 1932 | final float rightBound = offset + ii.widthFactor + marginOffset; 1933 | if (first || scrollOffset >= leftBound) { 1934 | if (scrollOffset < rightBound || i == mItems.size() - 1) { 1935 | return ii; 1936 | } 1937 | } else { 1938 | return lastItem; 1939 | } 1940 | first = false; 1941 | lastPos = ii.position; 1942 | lastOffset = offset; 1943 | lastWidth = ii.widthFactor; 1944 | lastItem = ii; 1945 | } 1946 | 1947 | return lastItem; 1948 | } 1949 | 1950 | private int determineTargetPage(int currentPage, float pageOffset, int velocity, int deltaX) { 1951 | int targetPage; 1952 | if (Math.abs(deltaX) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) { 1953 | targetPage = velocity > 0 ? currentPage : currentPage + 1; 1954 | } else { 1955 | targetPage = (int) (currentPage + pageOffset + 0.5f); 1956 | } 1957 | 1958 | if (mItems.size() > 0) { 1959 | final ItemInfo firstItem = mItems.get(0); 1960 | final ItemInfo lastItem = mItems.get(mItems.size() - 1); 1961 | 1962 | // Only let the user target pages we have items for 1963 | targetPage = Math.max(firstItem.position, Math.min(targetPage, lastItem.position)); 1964 | } 1965 | 1966 | return targetPage; 1967 | } 1968 | 1969 | @Override 1970 | public void draw(Canvas canvas) { 1971 | super.draw(canvas); 1972 | boolean needsInvalidate = false; 1973 | 1974 | final int overScrollMode = ViewCompat.getOverScrollMode(this); 1975 | if (overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS || 1976 | (overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS && 1977 | mAdapter != null && mAdapter.getCount() > 1)) { 1978 | if (!mLeftEdge.isFinished()) { 1979 | final int restoreCount = canvas.save(); 1980 | final int height = getHeight() - getPaddingTop() - getPaddingBottom(); 1981 | final int width = getWidth(); 1982 | 1983 | canvas.rotate(270); 1984 | canvas.translate(-height + getPaddingTop(), mFirstOffset * width); 1985 | mLeftEdge.setSize(height, width); 1986 | needsInvalidate |= mLeftEdge.draw(canvas); 1987 | canvas.restoreToCount(restoreCount); 1988 | } 1989 | if (!mRightEdge.isFinished()) { 1990 | final int restoreCount = canvas.save(); 1991 | final int width = getWidth(); 1992 | final int height = getHeight() - getPaddingTop() - getPaddingBottom(); 1993 | 1994 | canvas.rotate(90); 1995 | canvas.translate(-getPaddingTop(), -(mLastOffset + 1) * width); 1996 | mRightEdge.setSize(height, width); 1997 | needsInvalidate |= mRightEdge.draw(canvas); 1998 | canvas.restoreToCount(restoreCount); 1999 | } 2000 | } else { 2001 | mLeftEdge.finish(); 2002 | mRightEdge.finish(); 2003 | } 2004 | 2005 | if (needsInvalidate) { 2006 | // Keep animating 2007 | ViewCompat.postInvalidateOnAnimation(this); 2008 | } 2009 | } 2010 | 2011 | @Override 2012 | protected void onDraw(Canvas canvas) { 2013 | super.onDraw(canvas); 2014 | 2015 | // Draw the margin drawable between pages if needed. 2016 | if (mPageMargin > 0 && mMarginDrawable != null && mItems.size() > 0 && mAdapter != null) { 2017 | final int scrollX = getScrollX(); 2018 | final int width = getWidth(); 2019 | 2020 | final float marginOffset = (float) mPageMargin / width; 2021 | int itemIndex = 0; 2022 | ItemInfo ii = mItems.get(0); 2023 | float offset = ii.offset; 2024 | final int itemCount = mItems.size(); 2025 | final int firstPos = ii.position; 2026 | final int lastPos = mItems.get(itemCount - 1).position; 2027 | for (int pos = firstPos; pos < lastPos; pos++) { 2028 | while (pos > ii.position && itemIndex < itemCount) { 2029 | ii = mItems.get(++itemIndex); 2030 | } 2031 | 2032 | float drawAt; 2033 | if (pos == ii.position) { 2034 | drawAt = (ii.offset + ii.widthFactor) * width; 2035 | offset = ii.offset + ii.widthFactor + marginOffset; 2036 | } else { 2037 | float widthFactor = mAdapter.getPageWidth(pos); 2038 | drawAt = (offset + widthFactor) * width; 2039 | offset += widthFactor + marginOffset; 2040 | } 2041 | 2042 | if (drawAt + mPageMargin > scrollX) { 2043 | mMarginDrawable.setBounds((int) drawAt, mTopPageBounds, 2044 | (int) (drawAt + mPageMargin + 0.5f), mBottomPageBounds); 2045 | mMarginDrawable.draw(canvas); 2046 | } 2047 | 2048 | if (drawAt > scrollX + width) { 2049 | break; // No more visible, no sense in continuing 2050 | } 2051 | } 2052 | } 2053 | } 2054 | 2055 | /** 2056 | * Start a fake drag of the pager. 2057 | * 2058 | *

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

During a fake drag the ViewPager will ignore all touch events. If a real drag 2065 | * is already in progress, this method will return false. 2066 | * 2067 | * @return true if the fake drag began successfully, false if it could not be started. 2068 | * 2069 | * @see #fakeDragBy(float) 2070 | * @see #endFakeDrag() 2071 | */ 2072 | public boolean beginFakeDrag() { 2073 | if (mIsBeingDragged) { 2074 | return false; 2075 | } 2076 | mFakeDragging = true; 2077 | setScrollState(SCROLL_STATE_DRAGGING); 2078 | mInitialMotionX = mLastMotionX = 0; 2079 | if (mVelocityTracker == null) { 2080 | mVelocityTracker = VelocityTracker.obtain(); 2081 | } else { 2082 | mVelocityTracker.clear(); 2083 | } 2084 | final long time = SystemClock.uptimeMillis(); 2085 | final MotionEvent ev = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 0, 0, 0); 2086 | mVelocityTracker.addMovement(ev); 2087 | ev.recycle(); 2088 | mFakeDragBeginTime = time; 2089 | return true; 2090 | } 2091 | 2092 | /** 2093 | * End a fake drag of the pager. 2094 | * 2095 | * @see #beginFakeDrag() 2096 | * @see #fakeDragBy(float) 2097 | */ 2098 | public void endFakeDrag() { 2099 | if (!mFakeDragging) { 2100 | throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first."); 2101 | } 2102 | 2103 | final VelocityTracker velocityTracker = mVelocityTracker; 2104 | velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 2105 | int initialVelocity = (int) VelocityTrackerCompat.getXVelocity( 2106 | velocityTracker, mActivePointerId); 2107 | mPopulatePending = true; 2108 | final int width = getWidth(); 2109 | final int scrollX = getScrollX(); 2110 | final ItemInfo ii = infoForCurrentScrollPosition(); 2111 | final int currentPage = ii.position; 2112 | final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor; 2113 | final int totalDelta = (int) (mLastMotionX - mInitialMotionX); 2114 | int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity, 2115 | totalDelta); 2116 | setCurrentItemInternal(nextPage, true, true, initialVelocity); 2117 | endDrag(); 2118 | 2119 | mFakeDragging = false; 2120 | } 2121 | 2122 | /** 2123 | * Fake drag by an offset in pixels. You must have called {@link #beginFakeDrag()} first. 2124 | * 2125 | * @param xOffset Offset in pixels to drag by. 2126 | * @see #beginFakeDrag() 2127 | * @see #endFakeDrag() 2128 | */ 2129 | public void fakeDragBy(float xOffset) { 2130 | if (!mFakeDragging) { 2131 | throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first."); 2132 | } 2133 | 2134 | mLastMotionX += xOffset; 2135 | 2136 | float oldScrollX = getScrollX(); 2137 | float scrollX = oldScrollX - xOffset; 2138 | final int width = getWidth(); 2139 | 2140 | float leftBound = width * mFirstOffset; 2141 | float rightBound = width * mLastOffset; 2142 | 2143 | final ItemInfo firstItem = mItems.get(0); 2144 | final ItemInfo lastItem = mItems.get(mItems.size() - 1); 2145 | if (firstItem.position != 0) { 2146 | leftBound = firstItem.offset * width; 2147 | } 2148 | if (lastItem.position != mAdapter.getCount() - 1) { 2149 | rightBound = lastItem.offset * width; 2150 | } 2151 | 2152 | if (scrollX < leftBound) { 2153 | scrollX = leftBound; 2154 | } else if (scrollX > rightBound) { 2155 | scrollX = rightBound; 2156 | } 2157 | // Don't lose the rounded component 2158 | mLastMotionX += scrollX - (int) scrollX; 2159 | scrollTo((int) scrollX, getScrollY()); 2160 | pageScrolled((int) scrollX); 2161 | 2162 | // Synthesize an event for the VelocityTracker. 2163 | final long time = SystemClock.uptimeMillis(); 2164 | final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE, 2165 | mLastMotionX, 0, 0); 2166 | mVelocityTracker.addMovement(ev); 2167 | ev.recycle(); 2168 | } 2169 | 2170 | /** 2171 | * Returns true if a fake drag is in progress. 2172 | * 2173 | * @return true if currently in a fake drag, false otherwise. 2174 | * 2175 | * @see #beginFakeDrag() 2176 | * @see #fakeDragBy(float) 2177 | * @see #endFakeDrag() 2178 | */ 2179 | public boolean isFakeDragging() { 2180 | return mFakeDragging; 2181 | } 2182 | 2183 | private void onSecondaryPointerUp(MotionEvent ev) { 2184 | final int pointerIndex = MotionEventCompat.getActionIndex(ev); 2185 | final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex); 2186 | if (pointerId == mActivePointerId) { 2187 | // This was our active pointer going up. Choose a new 2188 | // active pointer and adjust accordingly. 2189 | final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 2190 | mLastMotionX = MotionEventCompat.getX(ev, newPointerIndex); 2191 | mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex); 2192 | if (mVelocityTracker != null) { 2193 | mVelocityTracker.clear(); 2194 | } 2195 | } 2196 | } 2197 | 2198 | private void endDrag() { 2199 | mIsBeingDragged = false; 2200 | mIsUnableToDrag = false; 2201 | 2202 | if (mVelocityTracker != null) { 2203 | mVelocityTracker.recycle(); 2204 | mVelocityTracker = null; 2205 | } 2206 | } 2207 | 2208 | private void setScrollingCacheEnabled(boolean enabled) { 2209 | if (mScrollingCacheEnabled != enabled) { 2210 | mScrollingCacheEnabled = enabled; 2211 | if (USE_CACHE) { 2212 | final int size = getChildCount(); 2213 | for (int i = 0; i < size; ++i) { 2214 | final View child = getChildAt(i); 2215 | if (child.getVisibility() != GONE) { 2216 | child.setDrawingCacheEnabled(enabled); 2217 | } 2218 | } 2219 | } 2220 | } 2221 | } 2222 | 2223 | /** 2224 | * Tests scrollability within child views of v given a delta of dx. 2225 | * 2226 | * @param v View to test for horizontal scrollability 2227 | * @param checkV Whether the view v passed should itself be checked for scrollability (true), 2228 | * or just its children (false). 2229 | * @param dx Delta scrolled in pixels 2230 | * @param x X coordinate of the active touch point 2231 | * @param y Y coordinate of the active touch point 2232 | * @return true if child views of v can be scrolled by delta of dx. 2233 | */ 2234 | protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) { 2235 | if (v instanceof ViewGroup) { 2236 | final ViewGroup group = (ViewGroup) v; 2237 | final int scrollX = v.getScrollX(); 2238 | final int scrollY = v.getScrollY(); 2239 | final int count = group.getChildCount(); 2240 | // Count backwards - let topmost views consume scroll distance first. 2241 | for (int i = count - 1; i >= 0; i--) { 2242 | // TODO: Add versioned support here for transformed views. 2243 | // This will not work for transformed views in Honeycomb+ 2244 | final View child = group.getChildAt(i); 2245 | if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() && 2246 | y + scrollY >= child.getTop() && y + scrollY < child.getBottom() && 2247 | canScroll(child, true, dx, x + scrollX - child.getLeft(), 2248 | y + scrollY - child.getTop())) { 2249 | return true; 2250 | } 2251 | } 2252 | } 2253 | 2254 | return checkV && ViewCompat.canScrollHorizontally(v, -dx); 2255 | } 2256 | 2257 | @Override 2258 | public boolean dispatchKeyEvent(KeyEvent event) { 2259 | // Let the focused view and/or our descendants get the key first 2260 | return super.dispatchKeyEvent(event) || executeKeyEvent(event); 2261 | } 2262 | 2263 | /** 2264 | * You can call this function yourself to have the scroll view perform 2265 | * scrolling from a key event, just as if the event had been dispatched to 2266 | * it by the view hierarchy. 2267 | * 2268 | * @param event The key event to execute. 2269 | * @return Return true if the event was handled, else false. 2270 | */ 2271 | public boolean executeKeyEvent(KeyEvent event) { 2272 | boolean handled = false; 2273 | if (event.getAction() == KeyEvent.ACTION_DOWN) { 2274 | switch (event.getKeyCode()) { 2275 | case KeyEvent.KEYCODE_DPAD_LEFT: 2276 | handled = arrowScroll(FOCUS_LEFT); 2277 | break; 2278 | case KeyEvent.KEYCODE_DPAD_RIGHT: 2279 | handled = arrowScroll(FOCUS_RIGHT); 2280 | break; 2281 | case KeyEvent.KEYCODE_TAB: 2282 | if (Build.VERSION.SDK_INT >= 11) { 2283 | // The focus finder had a bug handling FOCUS_FORWARD and FOCUS_BACKWARD 2284 | // before Android 3.0. Ignore the tab key on those devices. 2285 | if (KeyEventCompat.hasNoModifiers(event)) { 2286 | handled = arrowScroll(FOCUS_FORWARD); 2287 | } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_SHIFT_ON)) { 2288 | handled = arrowScroll(FOCUS_BACKWARD); 2289 | } 2290 | } 2291 | break; 2292 | } 2293 | } 2294 | return handled; 2295 | } 2296 | 2297 | public boolean arrowScroll(int direction) { 2298 | View currentFocused = findFocus(); 2299 | if (currentFocused == this) currentFocused = null; 2300 | 2301 | boolean handled = false; 2302 | 2303 | View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, 2304 | direction); 2305 | if (nextFocused != null && nextFocused != currentFocused) { 2306 | if (direction == View.FOCUS_LEFT) { 2307 | // If there is nothing to the left, or this is causing us to 2308 | // jump to the right, then what we really want to do is page left. 2309 | final int nextLeft = getChildRectInPagerCoordinates(mTempRect, nextFocused).left; 2310 | final int currLeft = getChildRectInPagerCoordinates(mTempRect, currentFocused).left; 2311 | if (currentFocused != null && nextLeft >= currLeft) { 2312 | handled = pageLeft(); 2313 | } else { 2314 | handled = nextFocused.requestFocus(); 2315 | } 2316 | } else if (direction == View.FOCUS_RIGHT) { 2317 | // If there is nothing to the right, or this is causing us to 2318 | // jump to the left, then what we really want to do is page right. 2319 | final int nextLeft = getChildRectInPagerCoordinates(mTempRect, nextFocused).left; 2320 | final int currLeft = getChildRectInPagerCoordinates(mTempRect, currentFocused).left; 2321 | if (currentFocused != null && nextLeft <= currLeft) { 2322 | handled = pageRight(); 2323 | } else { 2324 | handled = nextFocused.requestFocus(); 2325 | } 2326 | } 2327 | } else if (direction == FOCUS_LEFT || direction == FOCUS_BACKWARD) { 2328 | // Trying to move left and nothing there; try to page. 2329 | handled = pageLeft(); 2330 | } else if (direction == FOCUS_RIGHT || direction == FOCUS_FORWARD) { 2331 | // Trying to move right and nothing there; try to page. 2332 | handled = pageRight(); 2333 | } 2334 | if (handled) { 2335 | playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction)); 2336 | } 2337 | return handled; 2338 | } 2339 | 2340 | private Rect getChildRectInPagerCoordinates(Rect outRect, View child) { 2341 | if (outRect == null) { 2342 | outRect = new Rect(); 2343 | } 2344 | if (child == null) { 2345 | outRect.set(0, 0, 0, 0); 2346 | return outRect; 2347 | } 2348 | outRect.left = child.getLeft(); 2349 | outRect.right = child.getRight(); 2350 | outRect.top = child.getTop(); 2351 | outRect.bottom = child.getBottom(); 2352 | 2353 | ViewParent parent = child.getParent(); 2354 | while (parent instanceof ViewGroup && parent != this) { 2355 | final ViewGroup group = (ViewGroup) parent; 2356 | outRect.left += group.getLeft(); 2357 | outRect.right += group.getRight(); 2358 | outRect.top += group.getTop(); 2359 | outRect.bottom += group.getBottom(); 2360 | 2361 | parent = group.getParent(); 2362 | } 2363 | return outRect; 2364 | } 2365 | 2366 | boolean pageLeft() { 2367 | if (mCurItem > 0) { 2368 | setCurrentItem(mCurItem-1, true); 2369 | return true; 2370 | } 2371 | return false; 2372 | } 2373 | 2374 | boolean pageRight() { 2375 | if (mAdapter != null && mCurItem < (mAdapter.getCount()-1)) { 2376 | setCurrentItem(mCurItem+1, true); 2377 | return true; 2378 | } 2379 | return false; 2380 | } 2381 | 2382 | /** 2383 | * We only want the current page that is being shown to be focusable. 2384 | */ 2385 | @Override 2386 | public void addFocusables(ArrayList views, int direction, int focusableMode) { 2387 | final int focusableCount = views.size(); 2388 | 2389 | final int descendantFocusability = getDescendantFocusability(); 2390 | 2391 | if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) { 2392 | for (int i = 0; i < getChildCount(); i++) { 2393 | final View child = getChildAt(i); 2394 | if (child.getVisibility() == VISIBLE) { 2395 | ItemInfo ii = infoForChild(child); 2396 | if (ii != null && ii.position == mCurItem) { 2397 | child.addFocusables(views, direction, focusableMode); 2398 | } 2399 | } 2400 | } 2401 | } 2402 | 2403 | // we add ourselves (if focusable) in all cases except for when we are 2404 | // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable. this is 2405 | // to avoid the focus search finding layouts when a more precise search 2406 | // among the focusable children would be more interesting. 2407 | if ( 2408 | descendantFocusability != FOCUS_AFTER_DESCENDANTS || 2409 | // No focusable descendants 2410 | (focusableCount == views.size())) { 2411 | // Note that we can't call the superclass here, because it will 2412 | // add all views in. So we need to do the same thing View does. 2413 | if (!isFocusable()) { 2414 | return; 2415 | } 2416 | if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE && 2417 | isInTouchMode() && !isFocusableInTouchMode()) { 2418 | return; 2419 | } 2420 | if (views != null) { 2421 | views.add(this); 2422 | } 2423 | } 2424 | } 2425 | 2426 | /** 2427 | * We only want the current page that is being shown to be touchable. 2428 | */ 2429 | @Override 2430 | public void addTouchables(ArrayList views) { 2431 | // Note that we don't call super.addTouchables(), which means that 2432 | // we don't call View.addTouchables(). This is okay because a ViewPager 2433 | // is itself not touchable. 2434 | for (int i = 0; i < getChildCount(); i++) { 2435 | final View child = getChildAt(i); 2436 | if (child.getVisibility() == VISIBLE) { 2437 | ItemInfo ii = infoForChild(child); 2438 | if (ii != null && ii.position == mCurItem) { 2439 | child.addTouchables(views); 2440 | } 2441 | } 2442 | } 2443 | } 2444 | 2445 | /** 2446 | * We only want the current page that is being shown to be focusable. 2447 | */ 2448 | @Override 2449 | protected boolean onRequestFocusInDescendants(int direction, 2450 | Rect previouslyFocusedRect) { 2451 | int index; 2452 | int increment; 2453 | int end; 2454 | int count = getChildCount(); 2455 | if ((direction & FOCUS_FORWARD) != 0) { 2456 | index = 0; 2457 | increment = 1; 2458 | end = count; 2459 | } else { 2460 | index = count - 1; 2461 | increment = -1; 2462 | end = -1; 2463 | } 2464 | for (int i = index; i != end; i += increment) { 2465 | View child = getChildAt(i); 2466 | if (child.getVisibility() == VISIBLE) { 2467 | ItemInfo ii = infoForChild(child); 2468 | if (ii != null && ii.position == mCurItem) { 2469 | if (child.requestFocus(direction, previouslyFocusedRect)) { 2470 | return true; 2471 | } 2472 | } 2473 | } 2474 | } 2475 | return false; 2476 | } 2477 | 2478 | @Override 2479 | public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { 2480 | // ViewPagers should only report accessibility info for the current page, 2481 | // otherwise things get very confusing. 2482 | 2483 | // TODO: Should this note something about the paging container? 2484 | 2485 | final int childCount = getChildCount(); 2486 | for (int i = 0; i < childCount; i++) { 2487 | final View child = getChildAt(i); 2488 | if (child.getVisibility() == VISIBLE) { 2489 | final ItemInfo ii = infoForChild(child); 2490 | if (ii != null && ii.position == mCurItem && 2491 | child.dispatchPopulateAccessibilityEvent(event)) { 2492 | return true; 2493 | } 2494 | } 2495 | } 2496 | 2497 | return false; 2498 | } 2499 | 2500 | @Override 2501 | protected ViewGroup.LayoutParams generateDefaultLayoutParams() { 2502 | return new LayoutParams(); 2503 | } 2504 | 2505 | @Override 2506 | protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 2507 | return generateDefaultLayoutParams(); 2508 | } 2509 | 2510 | @Override 2511 | protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 2512 | return p instanceof LayoutParams && super.checkLayoutParams(p); 2513 | } 2514 | 2515 | @Override 2516 | public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { 2517 | return new LayoutParams(getContext(), attrs); 2518 | } 2519 | 2520 | class MyAccessibilityDelegate extends AccessibilityDelegateCompat { 2521 | 2522 | @Override 2523 | public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) { 2524 | super.onInitializeAccessibilityEvent(host, event); 2525 | event.setClassName(ViewPager.class.getName()); 2526 | } 2527 | 2528 | @Override 2529 | public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) { 2530 | super.onInitializeAccessibilityNodeInfo(host, info); 2531 | info.setClassName(ViewPager.class.getName()); 2532 | info.setScrollable(mAdapter != null && mAdapter.getCount() > 1); 2533 | if (mAdapter != null && mCurItem >= 0 && mCurItem < mAdapter.getCount() - 1) { 2534 | info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD); 2535 | } 2536 | if (mAdapter != null && mCurItem > 0 && mCurItem < mAdapter.getCount()) { 2537 | info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD); 2538 | } 2539 | } 2540 | 2541 | @Override 2542 | public boolean performAccessibilityAction(View host, int action, Bundle args) { 2543 | if (super.performAccessibilityAction(host, action, args)) { 2544 | return true; 2545 | } 2546 | switch (action) { 2547 | case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD: { 2548 | if (mAdapter != null && mCurItem >= 0 && mCurItem < mAdapter.getCount() - 1) { 2549 | setCurrentItem(mCurItem + 1); 2550 | return true; 2551 | } 2552 | } return false; 2553 | case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD: { 2554 | if (mAdapter != null && mCurItem > 0 && mCurItem < mAdapter.getCount()) { 2555 | setCurrentItem(mCurItem - 1); 2556 | return true; 2557 | } 2558 | } return false; 2559 | } 2560 | return false; 2561 | } 2562 | } 2563 | 2564 | private class PagerObserver extends DataSetObserver { 2565 | @Override 2566 | public void onChanged() { 2567 | dataSetChanged(); 2568 | } 2569 | @Override 2570 | public void onInvalidated() { 2571 | dataSetChanged(); 2572 | } 2573 | } 2574 | 2575 | /** 2576 | * Layout parameters that should be supplied for views added to a 2577 | * ViewPager. 2578 | */ 2579 | public static class LayoutParams extends ViewGroup.LayoutParams { 2580 | /** 2581 | * true if this view is a decoration on the pager itself and not 2582 | * a view supplied by the adapter. 2583 | */ 2584 | public boolean isDecor; 2585 | 2586 | /** 2587 | * Gravity setting for use on decor views only: 2588 | * Where to position the view page within the overall ViewPager 2589 | * container; constants are defined in {@link android.view.Gravity}. 2590 | */ 2591 | public int gravity; 2592 | 2593 | /** 2594 | * Width as a 0-1 multiplier of the measured pager width 2595 | */ 2596 | public float widthFactor = 0.f; 2597 | 2598 | /** 2599 | * true if this view was added during layout and needs to be measured 2600 | * before being positioned. 2601 | */ 2602 | public boolean needsMeasure; 2603 | 2604 | public LayoutParams() { 2605 | super(FILL_PARENT, FILL_PARENT); 2606 | } 2607 | 2608 | public LayoutParams(Context context, AttributeSet attrs) { 2609 | super(context, attrs); 2610 | 2611 | final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS); 2612 | gravity = a.getInteger(0, Gravity.TOP); 2613 | a.recycle(); 2614 | } 2615 | } 2616 | } -------------------------------------------------------------------------------- /DoubleViewPager/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | DoubleViewPager 3 | 4 | -------------------------------------------------------------------------------- /DoubleViewPagerSample/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /DoubleViewPagerSample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 22 5 | buildToolsVersion "22.0.1" 6 | 7 | defaultConfig { 8 | applicationId "com.emoiluj.doubleviewpagersample" 9 | minSdkVersion 8 10 | targetSdkVersion 21 11 | versionCode 3 12 | versionName "1.2" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile project(':DoubleViewPager') 24 | } 25 | -------------------------------------------------------------------------------- /DoubleViewPagerSample/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in C:\Users\0011361\Desktop\Orange\Software\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /DoubleViewPagerSample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /DoubleViewPagerSample/src/main/assets/OpenSans-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliome10/DoubleViewPager/b4027dc0ce3c52ddb4f5069cfb6d5708068b0e59/DoubleViewPagerSample/src/main/assets/OpenSans-Light.ttf -------------------------------------------------------------------------------- /DoubleViewPagerSample/src/main/assets/colors.json: -------------------------------------------------------------------------------- 1 | [ 2 | [ 3 | "B71C1C", 4 | "C62828", 5 | "D32F2F", 6 | "E53935", 7 | "F44336", 8 | "EF5350", 9 | "E57373", 10 | "EF9A9A", 11 | "FFCDD2", 12 | "FFEBEE" 13 | ], 14 | [ 15 | "880E4F", 16 | "AD1457", 17 | "C2185B", 18 | "D81B60", 19 | "E91E63", 20 | "EC407A", 21 | "F06292", 22 | "F48FB1", 23 | "F8BBD0", 24 | "FCE4EC" 25 | ], 26 | [ 27 | "4A148C", 28 | "6A1B9A", 29 | "7B1FA2", 30 | "8E24AA", 31 | "9C27B0", 32 | "AB47BC", 33 | "BA68C8", 34 | "CE93D8", 35 | "E1BEE7", 36 | "F3E5F5" 37 | ], 38 | [ 39 | "311B92", 40 | "4527A0", 41 | "512DA8", 42 | "5E35B1", 43 | "673AB7", 44 | "7E57C2", 45 | "9575CD", 46 | "B39DDB", 47 | "D1C4E9", 48 | "EDE7F6" 49 | ], 50 | [ 51 | "1A237E", 52 | "283593", 53 | "303F9F", 54 | "3949AB", 55 | "3F51B5", 56 | "5C6BC0", 57 | "7986CB", 58 | "9FA8DA", 59 | "C5CAE9", 60 | "E8EAF6" 61 | ], 62 | [ 63 | "0D47A1", 64 | "1565C0", 65 | "1976D2", 66 | "1E88E5", 67 | "2196F3", 68 | "42A5F5", 69 | "64B5F6", 70 | "90CAF9", 71 | "BBDEFB", 72 | "E3F2FD" 73 | ], 74 | [ 75 | "01579B", 76 | "0277BD", 77 | "0288D1", 78 | "039BE5", 79 | "03A9F4", 80 | "29B6F6", 81 | "4FC3F7", 82 | "81D4FA", 83 | "B3E5FC", 84 | "E1F5FE" 85 | ], 86 | [ 87 | "006064", 88 | "00838F", 89 | "0097A7", 90 | "00ACC1", 91 | "00BCD4", 92 | "26C6DA", 93 | "4DD0E1", 94 | "80DEEA", 95 | "B2EBF2", 96 | "E0F7FA" 97 | ], 98 | [ 99 | "004D40", 100 | "00695C", 101 | "00796B", 102 | "00897B", 103 | "009688", 104 | "26A69A", 105 | "4DB6AC", 106 | "80CBC4", 107 | "B2DFDB", 108 | "E0F2F1" 109 | ], 110 | [ 111 | "1B5E20", 112 | "2E7D32", 113 | "388E3C", 114 | "43A047", 115 | "4CAF50", 116 | "66BB6A", 117 | "81C784", 118 | "A5D6A7", 119 | "C8E6C9", 120 | "E8F5E9" 121 | ] 122 | ] -------------------------------------------------------------------------------- /DoubleViewPagerSample/src/main/java/com/emoiluj/doubleviewpagersample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.emoiluj.doubleviewpagersample; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.support.v4.view.PagerAdapter; 6 | import android.view.Window; 7 | 8 | import com.emoiluj.doubleviewpager.DoubleViewPager; 9 | import com.emoiluj.doubleviewpager.DoubleViewPagerAdapter; 10 | 11 | import java.util.ArrayList; 12 | 13 | 14 | public class MainActivity extends Activity{ 15 | 16 | private DoubleViewPager viewpager; 17 | private int horizontalChilds; 18 | private int verticalChilds; 19 | 20 | 21 | @Override 22 | protected void onCreate(Bundle savedInstanceState) { 23 | super.onCreate(savedInstanceState); 24 | requestWindowFeature(Window.FEATURE_NO_TITLE); 25 | setContentView(R.layout.activity_main); 26 | 27 | loadDataFromSplash(); 28 | loadUI(); 29 | } 30 | 31 | private void loadDataFromSplash(){ 32 | horizontalChilds = getIntent().getExtras().getInt("HORIZONTAL"); 33 | verticalChilds = getIntent().getExtras().getInt("VERTICAL"); 34 | } 35 | 36 | private void loadUI() { 37 | 38 | ArrayList verticalAdapters = new ArrayList(); 39 | generateVerticalAdapters(verticalAdapters); 40 | 41 | viewpager = (DoubleViewPager) findViewById(R.id.pager); 42 | viewpager.setAdapter(new DoubleViewPagerAdapter(getApplicationContext(), verticalAdapters)); 43 | } 44 | 45 | private void generateVerticalAdapters(ArrayList verticalAdapters) { 46 | for (int i=0; i 0 && etVertical.getText().toString().length() > 0) { 88 | Intent intent = new Intent(getApplicationContext(), com.emoiluj.doubleviewpagersample.MainActivity.class); 89 | intent.putExtra("HORIZONTAL", Integer.valueOf(etHorizontal.getText().toString())); 90 | intent.putExtra("VERTICAL", Integer.valueOf(etVertical.getText().toString())); 91 | startActivity(intent); 92 | finish(); 93 | } else { 94 | Toast.makeText(getApplicationContext(), "Boxes can not be empty!", Toast.LENGTH_SHORT).show(); 95 | } 96 | } 97 | 98 | } -------------------------------------------------------------------------------- /DoubleViewPagerSample/src/main/java/com/emoiluj/doubleviewpagersample/VerticalPagerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.emoiluj.doubleviewpagersample; 2 | 3 | import android.content.Context; 4 | import android.graphics.Color; 5 | import android.support.v4.view.PagerAdapter; 6 | import android.util.Log; 7 | import android.view.Gravity; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.LinearLayout; 11 | import android.widget.RelativeLayout.LayoutParams; 12 | import android.widget.TextView; 13 | 14 | import org.json.JSONArray; 15 | import org.json.JSONException; 16 | 17 | import java.io.IOException; 18 | import java.io.InputStream; 19 | 20 | public class VerticalPagerAdapter extends PagerAdapter{ 21 | 22 | private Context mContext; 23 | private int mParent; 24 | private int mChilds; 25 | private JSONArray mColors; 26 | 27 | public VerticalPagerAdapter(Context c, int parent, int childs){ 28 | mContext = c; 29 | mParent = parent; 30 | mChilds = childs; 31 | loadJSONFromAsset(c); 32 | } 33 | 34 | public int getItemPosition(Object object) { 35 | return POSITION_NONE; 36 | } 37 | 38 | @Override 39 | public int getCount() { 40 | return mChilds; 41 | } 42 | 43 | @Override 44 | public boolean isViewFromObject(View view, Object object) { 45 | return view == object; 46 | } 47 | 48 | @Override 49 | public void destroyItem(ViewGroup container, int position, Object object) { 50 | container.removeView((View) object); 51 | } 52 | 53 | @Override 54 | public Object instantiateItem(ViewGroup container, int position) { 55 | 56 | LinearLayout linear = new LinearLayout(mContext); 57 | linear.setOrientation(LinearLayout.VERTICAL); 58 | linear.setGravity(Gravity.CENTER); 59 | LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 60 | linear.setLayoutParams(lp); 61 | 62 | TextView tvParent = new TextView(mContext); 63 | tvParent.setGravity(Gravity.CENTER_HORIZONTAL); 64 | tvParent.setText("Parent:" + mParent); 65 | tvParent.setTextColor(Color.BLACK); 66 | tvParent.setTextSize(70); 67 | linear.addView(tvParent); 68 | 69 | TextView tvChild = new TextView(mContext); 70 | tvChild.setGravity(Gravity.CENTER_HORIZONTAL); 71 | tvChild.setText("Child:" + position); 72 | tvChild.setTextColor(Color.BLACK); 73 | tvChild.setTextSize(70); 74 | linear.addView(tvChild); 75 | 76 | setColors(position, linear); 77 | container.addView(linear); 78 | return linear; 79 | } 80 | 81 | public void setColors(int position, View layout){ 82 | 83 | try { 84 | String colorString = "#" + mColors.getJSONArray(mParent%10).getString(position%10); 85 | layout.setBackgroundColor(Color.parseColor(colorString)); 86 | } catch (JSONException ex){ 87 | Log.e("XXX", "Fail to load color ["+mParent+"]["+position+"]"); 88 | } 89 | 90 | } 91 | 92 | public void loadJSONFromAsset(Context ctx) { 93 | try { 94 | InputStream is = ctx.getAssets().open("colors.json"); 95 | int size = is.available(); 96 | byte[] buffer = new byte[size]; 97 | is.read(buffer); 98 | is.close(); 99 | String stringJson = new String(buffer, "UTF-8"); 100 | mColors = new JSONArray(stringJson); 101 | } catch (IOException ex) { 102 | Log.e("XXX", "Fail to load color JSON file"); 103 | ex.printStackTrace(); 104 | } catch (JSONException ex) { 105 | Log.e("XXX", "Fail to parse colors JSON"); 106 | ex.printStackTrace(); 107 | } 108 | } 109 | } -------------------------------------------------------------------------------- /DoubleViewPagerSample/src/main/res/drawable/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliome10/DoubleViewPager/b4027dc0ce3c52ddb4f5069cfb6d5708068b0e59/DoubleViewPagerSample/src/main/res/drawable/logo.png -------------------------------------------------------------------------------- /DoubleViewPagerSample/src/main/res/drawable/my_edit_text.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /DoubleViewPagerSample/src/main/res/drawable/my_textfield_activated_holo_dark.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliome10/DoubleViewPager/b4027dc0ce3c52ddb4f5069cfb6d5708068b0e59/DoubleViewPagerSample/src/main/res/drawable/my_textfield_activated_holo_dark.9.png -------------------------------------------------------------------------------- /DoubleViewPagerSample/src/main/res/drawable/my_textfield_default_holo_dark.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliome10/DoubleViewPager/b4027dc0ce3c52ddb4f5069cfb6d5708068b0e59/DoubleViewPagerSample/src/main/res/drawable/my_textfield_default_holo_dark.9.png -------------------------------------------------------------------------------- /DoubleViewPagerSample/src/main/res/drawable/my_textfield_disabled_focused_holo_dark.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliome10/DoubleViewPager/b4027dc0ce3c52ddb4f5069cfb6d5708068b0e59/DoubleViewPagerSample/src/main/res/drawable/my_textfield_disabled_focused_holo_dark.9.png -------------------------------------------------------------------------------- /DoubleViewPagerSample/src/main/res/drawable/my_textfield_disabled_holo_dark.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliome10/DoubleViewPager/b4027dc0ce3c52ddb4f5069cfb6d5708068b0e59/DoubleViewPagerSample/src/main/res/drawable/my_textfield_disabled_holo_dark.9.png -------------------------------------------------------------------------------- /DoubleViewPagerSample/src/main/res/drawable/my_textfield_focused_holo_dark.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliome10/DoubleViewPager/b4027dc0ce3c52ddb4f5069cfb6d5708068b0e59/DoubleViewPagerSample/src/main/res/drawable/my_textfield_focused_holo_dark.9.png -------------------------------------------------------------------------------- /DoubleViewPagerSample/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /DoubleViewPagerSample/src/main/res/layout/activity_splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 19 | 20 | 29 | 30 | 36 | 37 | 44 | 45 | 48 | 49 | 58 | 59 | 68 | 69 | 70 | 73 | 74 | 83 | 84 | 93 | 94 | 95 | 96 |