+ * 常量
+ */
+public class AppConstants {
+
+ /**
+ * 长链 appId
+ */
+ public static final String SOCKET_APP_ID = "com.mogo.launcher";
+}
diff --git a/modules/mogo-module-main/src/main/java/com/mogo/module/main/MainActivity.java b/modules/mogo-module-main/src/main/java/com/mogo/module/main/MainActivity.java
index f03c2e4a12..fff3cbf7a7 100644
--- a/modules/mogo-module-main/src/main/java/com/mogo/module/main/MainActivity.java
+++ b/modules/mogo-module-main/src/main/java/com/mogo/module/main/MainActivity.java
@@ -1,7 +1,6 @@
package com.mogo.module.main;
import android.os.Bundle;
-import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -20,6 +19,7 @@ import com.mogo.module.main.cards.MogoModulesManager;
import com.mogo.module.main.cards.OrientedViewPager;
import com.mogo.module.main.cards.VerticalStackTransformer;
import com.mogo.service.MogoServicePaths;
+import com.mogo.service.connection.IMogoSocketManager;
import com.mogo.service.map.IMogoMapService;
import com.mogo.service.module.IMogoModuleProvider;
import com.mogo.utils.logger.Logger;
@@ -41,6 +41,8 @@ public class MainActivity extends MvpActivity< MainView, MainPresenter > impleme
private IMogoMapService mMogoMapService;
private MogoModulesHandler mMogoModuleHandler;
+ private IMogoSocketManager mMogoSocketManager;
+
private OrientedViewPager mCardsContainer;
private CardModulesAdapter mCardModulesAdapter;
@@ -49,7 +51,7 @@ public class MainActivity extends MvpActivity< MainView, MainPresenter > impleme
*/
private IMogoLocationClient mLocationClient;
- private int mCurrentPosition = 1;
+ private int mCurrentPosition = 0;
@Override
protected int getLayoutId() {
@@ -63,17 +65,9 @@ public class MainActivity extends MvpActivity< MainView, MainPresenter > impleme
mCardsContainer.setOnPageChangeListener( new ViewPager.OnPageChangeListener() {
- private int mLastPosition = -1;
-
@Override
public void onPageScrolled( int position, float positionOffset, int positionOffsetPixels ) {
Logger.i( TAG, "position = " + position );
- if ( mLastPosition != position ) {
- if ( mCardModulesAdapter != null ) {
- mCardModulesAdapter.render( position );
- }
- mLastPosition = position;
- }
}
@Override
@@ -89,31 +83,27 @@ public class MainActivity extends MvpActivity< MainView, MainPresenter > impleme
@Override
public void onPageScrollStateChanged( int state ) {
if ( state == ViewPager.SCROLL_STATE_IDLE ) {
- if ( mCurrentPosition == 0 ) {
- mCurrentPosition = mCardModulesAdapter.getCount() - 3;
- mCardsContainer.setCurrentItem( mCurrentPosition, false );
- } else if ( mCurrentPosition == mCardModulesAdapter.getCount() - 2 ) {
- mCurrentPosition = 1;
- mCardsContainer.setCurrentItem( mCurrentPosition, false );
- }
+// if ( mCurrentPosition == 0 ) {
+// mCurrentPosition = mCardModulesAdapter.getCount() - 3;
+// mCardsContainer.setCurrentItem( mCurrentPosition, false );
+// } else if ( mCurrentPosition == mCardModulesAdapter.getCount() - 2 ) {
+// mCurrentPosition = 1;
+// mCardsContainer.setCurrentItem( mCurrentPosition, false );
+// }
}
}
} );
}
- public int getCurrentPosition() {
- return mCurrentPosition;
- }
-
- public OrientedViewPager getCardsContainer() {
- return mCardsContainer;
- }
-
@Override
protected void onCreate( @Nullable Bundle savedInstanceState ) {
super.onCreate( savedInstanceState );
mMogoModuleHandler = new MogoModulesManager( this );
+
+ mMogoSocketManager = ( IMogoSocketManager ) ARouter.getInstance().build( MogoServicePaths.PATH_SOCKET_MANAGER ).navigation();
+ mMogoSocketManager.init( getApplicationContext(), AppConstants.SOCKET_APP_ID );
+
mMogoModuleHandler.onMapLoadedCallback( new Runnable() {
@Override
public void run() {
@@ -140,9 +130,9 @@ public class MainActivity extends MvpActivity< MainView, MainPresenter > impleme
private void loadModules() {
- List< IMogoModuleProvider > providers = mMogoModuleHandler.loadCards();
+ List< IMogoModuleProvider > providers = mMogoModuleHandler.loadCardsModule();
mCardModulesAdapter = new CardModulesAdapter( this, providers );
- mCardsContainer.setOffscreenPageLimit( providers.size() + 2 );
+ mCardsContainer.setOffscreenPageLimit( providers.size() );
mCardsContainer.setPageTransformer( true, new VerticalStackTransformer( this ) );
mCardsContainer.setAdapter( mCardModulesAdapter );
mCardsContainer.setCurrentItem( mCurrentPosition );
@@ -182,5 +172,6 @@ public class MainActivity extends MvpActivity< MainView, MainPresenter > impleme
mMogoModuleHandler.destroy();
mMogoModuleHandler = null;
}
+ mMogoSocketManager = null;
}
}
diff --git a/modules/mogo-module-main/src/main/java/com/mogo/module/main/cards/CardModulesAdapter.java b/modules/mogo-module-main/src/main/java/com/mogo/module/main/cards/CardModulesAdapter.java
index b572ee1ca1..806d216622 100644
--- a/modules/mogo-module-main/src/main/java/com/mogo/module/main/cards/CardModulesAdapter.java
+++ b/modules/mogo-module-main/src/main/java/com/mogo/module/main/cards/CardModulesAdapter.java
@@ -2,8 +2,6 @@ package com.mogo.module.main.cards;
import android.os.Bundle;
import android.text.TextUtils;
-import android.util.Log;
-import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
@@ -30,18 +28,10 @@ public class CardModulesAdapter extends FragmentStatePagerAdapter {
private final MainActivity mActivity;
private List< IMogoModuleProvider > mProviders;
- private PlaceholderFragmentProvider mLastPH;
- private PlaceholderFragmentProvider mFirstPH;
- private PlaceholderFragmentProvider mPH;
-
-
public CardModulesAdapter( @NonNull MainActivity fragmentActivity, List< IMogoModuleProvider > providers ) {
super( fragmentActivity.getSupportFragmentManager() );
mActivity = fragmentActivity;
this.mProviders = new ArrayList<>( providers );
- this.mProviders.add( 0, mLastPH = new PlaceholderFragmentProvider() );
- this.mProviders.add( mFirstPH = new PlaceholderFragmentProvider() );
- this.mProviders.add( mPH = new PlaceholderFragmentProvider() );
}
@NonNull
@@ -52,11 +42,6 @@ public class CardModulesAdapter extends FragmentStatePagerAdapter {
bundle.putInt( "position", factPosition );
Logger.d( TAG, "here" );
final Fragment f = mProviders.get( factPosition ).createFragment( mActivity, bundle );
- if ( position == 0 ) {
- mLastPH.setCopyTarget( f );
- } else if ( position == getCount() - 2 ) {
- mFirstPH.setCopyTarget( f );
- }
return f;
}
@@ -106,23 +91,8 @@ public class CardModulesAdapter extends FragmentStatePagerAdapter {
return currentPosition + offset;
}
- @Override
- public void destroyItem( @NonNull ViewGroup container, int position, @NonNull Object object ) {
-// super.destroyItem( container, position, object );
-// Logger.d( TAG, "destroy " + object );
- }
-
-
@Override
public void finishUpdate( @NonNull ViewGroup container ) {
super.finishUpdate( container );
}
-
- public void render( int position ) {
- if ( position == 1 ) {
- mLastPH.renderTargetUI();
- } else if ( position == getCount() - 2 ) {
- mFirstPH.renderTargetUI();
- }
- }
}
diff --git a/modules/mogo-module-main/src/main/java/com/mogo/module/main/cards/MogoModulesHandler.java b/modules/mogo-module-main/src/main/java/com/mogo/module/main/cards/MogoModulesHandler.java
index a36e9b2dc3..f3868cb6ac 100644
--- a/modules/mogo-module-main/src/main/java/com/mogo/module/main/cards/MogoModulesHandler.java
+++ b/modules/mogo-module-main/src/main/java/com/mogo/module/main/cards/MogoModulesHandler.java
@@ -29,7 +29,7 @@ public interface MogoModulesHandler extends IMogoMapListener,
*
* @return
*/
- List< IMogoModuleProvider > loadCards();
+ List< IMogoModuleProvider > loadCardsModule();
/**
* 加载小智语音
diff --git a/modules/mogo-module-main/src/main/java/com/mogo/module/main/cards/MogoModulesManager.java b/modules/mogo-module-main/src/main/java/com/mogo/module/main/cards/MogoModulesManager.java
index a576dfa58b..2bfb79620a 100644
--- a/modules/mogo-module-main/src/main/java/com/mogo/module/main/cards/MogoModulesManager.java
+++ b/modules/mogo-module-main/src/main/java/com/mogo/module/main/cards/MogoModulesManager.java
@@ -15,6 +15,7 @@ import com.mogo.map.model.MogoPoi;
import com.mogo.map.navi.IMogoNaviListener;
import com.mogo.map.navi.MogoNaviInfo;
import com.mogo.map.uicontroller.EnumMapUI;
+import com.mogo.module.common.MogoModule;
import com.mogo.module.common.MogoModulePaths;
import com.mogo.module.main.MainActivity;
import com.mogo.service.module.IMogoModuleLifecycle;
@@ -69,12 +70,12 @@ public class MogoModulesManager implements MogoModulesHandler,
}
@Override
- public List< IMogoModuleProvider > loadCards() {
- final List< String > modulePaths = MogoModulePaths.getModulesPath();
+ public List< IMogoModuleProvider > loadCardsModule() {
+ final List< MogoModule > modules = MogoModulePaths.getModules();
final ArrayList< IMogoModuleProvider > providers = new ArrayList<>();
- if ( modulePaths != null && !modulePaths.isEmpty() ) {
- for ( String modulePath : modulePaths ) {
- IMogoModuleProvider provider = load( modulePath );
+ if ( modules != null && !modules.isEmpty() ) {
+ for ( MogoModule module : modules ) {
+ IMogoModuleProvider provider = load( module.getPath() );
providers.add( provider );
mCardProviders.put( provider.getModuleName(), provider );
}
diff --git a/modules/mogo-module-main/src/main/java/com/mogo/module/main/cards/OrientedViewPager.java b/modules/mogo-module-main/src/main/java/com/mogo/module/main/cards/OrientedViewPager.java
index 6add107e86..5b0410d476 100644
--- a/modules/mogo-module-main/src/main/java/com/mogo/module/main/cards/OrientedViewPager.java
+++ b/modules/mogo-module-main/src/main/java/com/mogo/module/main/cards/OrientedViewPager.java
@@ -47,3338 +47,3338 @@ import java.util.Comparator;
public class OrientedViewPager extends ViewGroup {
- public enum Orientation {
- VERTICAL, HORIZONTAL
- }
-
- private static final String TAG = "ViewPager";
- private static final boolean DEBUG = false;
-
- private static final boolean USE_CACHE = false;
-
- private static final int DEFAULT_OFFSCREEN_PAGES = 1;
- private static final int MAX_SETTLE_DURATION = 600; // ms
- private static final int MIN_DISTANCE_FOR_FLING = 25; // dips
-
- private static final int DEFAULT_GUTTER_SIZE = 16; // dips
-
- private static final int MIN_FLING_VELOCITY = 400; // dips
-
- private static final int[] LAYOUT_ATTRS = new int[] {
- android.R.attr.layout_gravity
- };
-
- /**
- * Used to track what the expected number of items in the adapter should be.
- * If the app changes this when we don't expect it, we'll throw a big obnoxious exception.
- */
- private int mExpectedAdapterCount;
-
- private static class ItemInfo {
- Object object;
- int position;
- boolean scrolling;
- float sizeFactor;
- float offset;
- }
-
- private static final Comparator COMPARATOR = new Comparator() {
- @Override
- public int compare(ItemInfo lhs, ItemInfo rhs) {
- return lhs.position - rhs.position;
- }
- };
-
- private static final Interpolator sInterpolator = new Interpolator() {
- public float getInterpolation(float t) {
- t -= 1.0f;
- return t * t * t * t * t + 1.0f;
- }
- };
-
- private final ArrayList mItems = new ArrayList();
- private final ItemInfo mTempItem = new ItemInfo();
-
- private final Rect mTempRect = new Rect();
-
- private Orientation mOrientation = Orientation.HORIZONTAL;
-
- private PagerAdapter mAdapter;
- private int mCurItem; // Index of currently displayed page.
- private int mRestoredCurItem = -1;
- private Parcelable mRestoredAdapterState = null;
- private ClassLoader mRestoredClassLoader = null;
- private Scroller mScroller;
- private PagerObserver mObserver;
-
- private int mPageMargin;
- private Drawable mMarginDrawable;
- private int mTopLeftPageBounds;
- private int mBottomRightPageBounds;
-
- // Offsets of the first and last items, if known.
- // Set during population, used to determine if we are at the beginning
- // or end of the pager data set during touch scrolling.
- private float mFirstOffset = -Float.MAX_VALUE;
- private float mLastOffset = Float.MAX_VALUE;
-
- private int mChildWidthMeasureSpec;
- private int mChildHeightMeasureSpec;
- private boolean mInLayout;
-
- private boolean mScrollingCacheEnabled;
-
- private boolean mPopulatePending;
- private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES;
-
- private boolean mIsBeingDragged;
- private boolean mIsUnableToDrag;
- private boolean mIgnoreGutter;
- private int mDefaultGutterSize;
- private int mGutterSize;
- private int mTouchSlop;
- /**
- * Position of the last motion event.
- */
- private float mLastMotionX;
- private float mLastMotionY;
- private float mInitialMotionX;
- private float mInitialMotionY;
- /**
- * ID of the active pointer. This is used to retain consistency during
- * drags/flings if multiple pointers are used.
- */
- private int mActivePointerId = INVALID_POINTER;
- /**
- * Sentinel value for no current active pointer.
- * Used by {@link #mActivePointerId}.
- */
- private static final int INVALID_POINTER = -1;
-
- /**
- * Determines speed during touch scrolling
- */
- private VelocityTracker mVelocityTracker;
- private int mMinimumVelocity;
- private int mMaximumVelocity;
- private int mFlingDistance;
- private int mCloseEnough;
-
- // If the pager is at least this close to its final position, complete the scroll
- // on touch down and let the user interact with the content inside instead of
- // "catching" the flinging pager.
- private static final int CLOSE_ENOUGH = 2; // dp
-
- private boolean mFakeDragging;
- private long mFakeDragBeginTime;
-
- private EdgeEffectCompat mTopLeftEdge;
- private EdgeEffectCompat mRightBottomEdge;
-
- private boolean mFirstLayout = true;
- private boolean mNeedCalculatePageOffsets = false;
- private boolean mCalledSuper;
- private int mDecorChildCount;
-
- private ViewPager.OnPageChangeListener mOnPageChangeListener;
- private ViewPager.OnPageChangeListener mInternalPageChangeListener;
- private OnAdapterChangeListener mAdapterChangeListener;
- private ViewPager.PageTransformer mPageTransformer;
- private Method mSetChildrenDrawingOrderEnabled;
-
- private static final int DRAW_ORDER_DEFAULT = 0;
- private static final int DRAW_ORDER_FORWARD = 1;
- private static final int DRAW_ORDER_REVERSE = 2;
- private int mDrawingOrder;
- private ArrayList< View > mDrawingOrderedChildren;
- private static final ViewPositionComparator sPositionComparator = new ViewPositionComparator();
-
- /**
- * Indicates that the pager is in an idle, settled state. The current page
- * is fully in view and no animation is in progress.
- */
- public static final int SCROLL_STATE_IDLE = 0;
-
- /**
- * Indicates that the pager is currently being dragged by the user.
- */
- public static final int SCROLL_STATE_DRAGGING = 1;
-
- /**
- * Indicates that the pager is in the process of settling to a final position.
- */
- public static final int SCROLL_STATE_SETTLING = 2;
-
- private final Runnable mEndScrollRunnable = new Runnable() {
- public void run() {
- setScrollState(SCROLL_STATE_IDLE);
- populate();
- }
- };
-
- private int mScrollState = SCROLL_STATE_IDLE;
-
- /**
- * Used internally to monitor when adapters are switched.
- */
- interface OnAdapterChangeListener {
- public void onAdapterChanged(PagerAdapter oldAdapter, PagerAdapter newAdapter);
- }
-
- /**
- * Used internally to tag special types of child views that should be added as
- * pager decorations by default.
- */
- interface Decor {
- }
-
- public OrientedViewPager( Context context) {
- super(context);
- initViewPager();
- }
-
- public OrientedViewPager(Context context, AttributeSet attrs) {
- super(context, attrs);
- initViewPager();
- }
-
- void initViewPager() {
- setWillNotDraw(false);
- setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
- setFocusable(true);
- final Context context = getContext();
- mScroller = new Scroller(context, sInterpolator);
- final ViewConfiguration configuration = ViewConfiguration.get(context);
- final float density = context.getResources().getDisplayMetrics().density;
-
- mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
- mMinimumVelocity = (int) (MIN_FLING_VELOCITY * density);
- mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
- mTopLeftEdge = new EdgeEffectCompat(context);
- mRightBottomEdge = new EdgeEffectCompat(context);
-
- mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density);
- mCloseEnough = (int) (CLOSE_ENOUGH * density);
- mDefaultGutterSize = (int) (DEFAULT_GUTTER_SIZE * density);
-
- ViewCompat.setAccessibilityDelegate(this, new MyAccessibilityDelegate());
-
- if (ViewCompat.getImportantForAccessibility(this)
- == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
- ViewCompat.setImportantForAccessibility(this,
- ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
- }
- }
-
- public void setOrientation(Orientation orientation) {
- mOrientation = orientation;
- }
-
- @Override
- protected void onDetachedFromWindow() {
- removeCallbacks(mEndScrollRunnable);
- super.onDetachedFromWindow();
- }
-
- private void setScrollState(int newState) {
- if (mScrollState == newState) {
- return;
+ public enum Orientation {
+ VERTICAL, HORIZONTAL
}
- mScrollState = newState;
- if (mPageTransformer != null) {
- // PageTransformers can do complex things that benefit from hardware layers.
- enableLayers(newState != SCROLL_STATE_IDLE);
- }
- if (mOnPageChangeListener != null) {
- mOnPageChangeListener.onPageScrollStateChanged(newState);
- }
- }
+ private static final String TAG = "ViewPager";
+ private static final boolean DEBUG = false;
- /**
- * Set a PagerAdapter that will supply views for this pager as needed.
- *
- * @param adapter Adapter to use
- */
- public void setAdapter(PagerAdapter adapter) {
- if (mAdapter != null) {
- mAdapter.unregisterDataSetObserver(mObserver);
- mAdapter.startUpdate(this);
- for (int i = 0; i < mItems.size(); i++) {
- final ItemInfo ii = mItems.get(i);
- mAdapter.destroyItem(this, ii.position, ii.object);
- }
- mAdapter.finishUpdate(this);
- mItems.clear();
- removeNonDecorViews();
- mCurItem = 0;
- scrollTo(0, 0);
- }
+ private static final boolean USE_CACHE = false;
- final PagerAdapter oldAdapter = mAdapter;
- mAdapter = adapter;
- mExpectedAdapterCount = 0;
+ private static final int DEFAULT_OFFSCREEN_PAGES = 1;
+ private static final int MAX_SETTLE_DURATION = 600; // ms
+ private static final int MIN_DISTANCE_FOR_FLING = 25; // dips
- if (mAdapter != null) {
- if (mObserver == null) {
- mObserver = new PagerObserver();
- }
- mAdapter.registerDataSetObserver(mObserver);
- mPopulatePending = false;
- final boolean wasFirstLayout = mFirstLayout;
- mFirstLayout = true;
- mExpectedAdapterCount = mAdapter.getCount();
- if (mRestoredCurItem >= 0) {
- mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader);
- setCurrentItemInternal(mRestoredCurItem, false, true);
- mRestoredCurItem = -1;
- mRestoredAdapterState = null;
- mRestoredClassLoader = null;
- } else if (!wasFirstLayout) {
- populate();
- } else {
- requestLayout();
- }
- }
+ private static final int DEFAULT_GUTTER_SIZE = 16; // dips
- if (mAdapterChangeListener != null && oldAdapter != adapter) {
- mAdapterChangeListener.onAdapterChanged(oldAdapter, adapter);
- }
- }
+ private static final int MIN_FLING_VELOCITY = 400; // dips
- private void removeNonDecorViews() {
- for (int i = 0; i < getChildCount(); i++) {
- final View child = getChildAt(i);
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- if (!lp.isDecor) {
- removeViewAt(i);
- i--;
- }
- }
- }
+ private static final int[] LAYOUT_ATTRS = new int[]{
+ android.R.attr.layout_gravity
+ };
- /**
- * Retrieve the current adapter supplying pages.
- *
- * @return The currently registered PagerAdapter
- */
- public PagerAdapter getAdapter() {
- return mAdapter;
- }
-
- void setOnAdapterChangeListener(OnAdapterChangeListener listener) {
- mAdapterChangeListener = listener;
- }
-
- private int getClientSize() {
- return (mOrientation == Orientation.VERTICAL) ?
- getMeasuredHeight() - getPaddingTop() - getPaddingBottom() :
- getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
- }
-
- /**
- * Set the currently selected page. If the ViewPager has already been through its first
- * layout with its current adapter there will be a smooth animated transition between
- * the current item and the specified item.
- *
- * @param item Item index to select
- */
- public void setCurrentItem(int item) {
- mPopulatePending = false;
- setCurrentItemInternal(item, !mFirstLayout, false);
- }
-
- /**
- * Set the currently selected page.
- *
- * @param item Item index to select
- * @param smoothScroll True to smoothly scroll to the new item, false to transition immediately
- */
- public void setCurrentItem(int item, boolean smoothScroll) {
- mPopulatePending = false;
- setCurrentItemInternal(item, smoothScroll, false);
- }
-
- public int getCurrentItem() {
- return mCurItem;
- }
-
- void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) {
- setCurrentItemInternal(item, smoothScroll, always, 0);
- }
-
- void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) {
- if (mAdapter == null || mAdapter.getCount() <= 0) {
- setScrollingCacheEnabled(false);
- return;
- }
- if (!always && mCurItem == item && mItems.size() != 0) {
- setScrollingCacheEnabled(false);
- return;
- }
-
- if (item < 0) {
- item = 0;
- } else if (item >= mAdapter.getCount()) {
- item = mAdapter.getCount() - 1;
- }
- final int pageLimit = mOffscreenPageLimit;
- if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) {
- // We are doing a jump by more than one page. To avoid
- // glitches, we want to keep all current pages in the view
- // until the scroll ends.
- for (int i = 0; i < mItems.size(); i++) {
- mItems.get(i).scrolling = true;
- }
- }
- final boolean dispatchSelected = mCurItem != item;
-
- if (mFirstLayout) {
- // We don't have any idea how big we are yet and shouldn't have any pages either.
- // Just set things up and let the pending layout handle things.
- mCurItem = item;
- if (dispatchSelected && mOnPageChangeListener != null) {
- mOnPageChangeListener.onPageSelected(item);
- }
- if (dispatchSelected && mInternalPageChangeListener != null) {
- mInternalPageChangeListener.onPageSelected(item);
- }
- requestLayout();
- } else {
- populate(item);
- scrollToItem(item, smoothScroll, velocity, dispatchSelected);
- }
- }
-
- private void scrollToItem(int item, boolean smoothScroll, int velocity,
- boolean dispatchSelected) {
- final ItemInfo curInfo = infoForPosition(item);
- int dest = 0;
- if (curInfo != null) {
- final int size = getClientSize();
- dest = (int) (size * Math.max(mFirstOffset,
- Math.min(curInfo.offset, mLastOffset)));
- }
- if (smoothScroll) {
- if (mOrientation == Orientation.VERTICAL) {
- smoothScrollTo(0, dest, velocity);
- } else {
- smoothScrollTo(dest, 0, velocity);
- }
- if (dispatchSelected && mOnPageChangeListener != null) {
- mOnPageChangeListener.onPageSelected(item);
- }
- if (dispatchSelected && mInternalPageChangeListener != null) {
- mInternalPageChangeListener.onPageSelected(item);
- }
- } else {
- if (dispatchSelected && mOnPageChangeListener != null) {
- mOnPageChangeListener.onPageSelected(item);
- }
- if (dispatchSelected && mInternalPageChangeListener != null) {
- mInternalPageChangeListener.onPageSelected(item);
- }
- completeScroll(false);
- if (mOrientation == Orientation.VERTICAL) {
- scrollTo(0, dest);
- } else {
- scrollTo(dest, 0);
- }
- pageScrolled(dest);
- }
- }
-
- /**
- * Set a listener that will be invoked whenever the page changes or is incrementally
- * scrolled. See {@link androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback}.
- *
- * @param listener Listener to set
- */
- public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) {
- mOnPageChangeListener = listener;
- }
-
- /**
- * Set a {@link androidx.viewpager2.widget.ViewPager2.PageTransformer} that will be called for each
- * attached page whenever
- * the scroll position is changed. This allows the application to apply custom property
- * transformations to each page, overriding the default sliding look and feel.
- *
- *
Note: Prior to Android 3.0 the property animation APIs did not exist.
- * As a result, setting a PageTransformer prior to Android 3.0 (API 11) will have no effect.
- *
- * @param reverseDrawingOrder true if the supplied PageTransformer requires page views
- * to be drawn from last to first instead of first to last.
- * @param transformer PageTransformer that will modify each page's animation properties
- */
- public void setPageTransformer(boolean reverseDrawingOrder,
- ViewPager.PageTransformer transformer) {
- if ( Build.VERSION.SDK_INT >= 11) {
- final boolean hasTransformer = transformer != null;
- final boolean needsPopulate = hasTransformer != (mPageTransformer != null);
- mPageTransformer = transformer;
- setChildrenDrawingOrderEnabledCompat(hasTransformer);
- if (hasTransformer) {
- mDrawingOrder = reverseDrawingOrder ? DRAW_ORDER_REVERSE : DRAW_ORDER_FORWARD;
- } else {
- mDrawingOrder = DRAW_ORDER_DEFAULT;
- }
- if (needsPopulate) populate();
- }
- }
-
- void setChildrenDrawingOrderEnabledCompat(boolean enable) {
- if (Build.VERSION.SDK_INT >= 7) {
- if (mSetChildrenDrawingOrderEnabled == null) {
- try {
- mSetChildrenDrawingOrderEnabled = ViewGroup.class.getDeclaredMethod(
- "setChildrenDrawingOrderEnabled", new Class[] { Boolean.TYPE });
- } catch (NoSuchMethodException e) {
- Log.e(TAG, "Can't find setChildrenDrawingOrderEnabled", e);
- }
- }
- try {
- mSetChildrenDrawingOrderEnabled.invoke(this, enable);
- } catch (Exception e) {
- Log.e(TAG, "Error changing children drawing order", e);
- }
- }
- }
-
- @Override
- protected int getChildDrawingOrder(int childCount, int i) {
- final int index = mDrawingOrder == DRAW_ORDER_REVERSE ? childCount - 1 - i : i;
- final int result =
- ((LayoutParams) mDrawingOrderedChildren.get(index).getLayoutParams()).childIndex;
- return result;
- }
-
- /**
- * Set a separate OnPageChangeListener for internal use by the support library.
- *
- * @param listener Listener to set
- * @return The old listener that was set, if any.
- */
- ViewPager.OnPageChangeListener setInternalPageChangeListener(
- ViewPager.OnPageChangeListener listener) {
- ViewPager.OnPageChangeListener oldListener = mInternalPageChangeListener;
- mInternalPageChangeListener = listener;
- return oldListener;
- }
-
- /**
- * Returns the number of pages that will be retained to either side of the
- * current page in the view hierarchy in an idle state. Defaults to 1.
- *
- * @return How many pages will be kept offscreen on either side
- * @see #setOffscreenPageLimit(int)
- */
- public int getOffscreenPageLimit() {
- return mOffscreenPageLimit;
- }
-
- /**
- * Set the number of pages that should be retained to either side of the
- * current page in the view hierarchy in an idle state. Pages beyond this
- * limit will be recreated from the adapter when needed.
- *
- *
This is offered as an optimization. If you know in advance the number
- * of pages you will need to support or have lazy-loading mechanisms in place
- * on your pages, tweaking this setting can have benefits in perceived smoothness
- * of paging animations and interaction. If you have a small number of pages (3-4)
- * that you can keep active all at once, less time will be spent in layout for
- * newly created view subtrees as the user pages back and forth.
- *
- *
You should keep this limit low, especially if your pages have complex layouts.
- * This setting defaults to 1.
- *
- * @param limit How many pages will be kept offscreen in an idle state.
- */
- public void setOffscreenPageLimit(int limit) {
- if (limit < DEFAULT_OFFSCREEN_PAGES) {
- Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " +
- DEFAULT_OFFSCREEN_PAGES);
- limit = DEFAULT_OFFSCREEN_PAGES;
- }
- if (limit != mOffscreenPageLimit) {
- mOffscreenPageLimit = limit;
- populate();
- }
- }
-
- /**
- * Set the margin between pages.
- *
- * @param marginPixels Distance between adjacent pages in pixels
- * @see #getPageMargin()
- * @see #setPageMarginDrawable(Drawable)
- * @see #setPageMarginDrawable(int)
- */
- public void setPageMargin(int marginPixels) {
- final int oldMargin = mPageMargin;
- mPageMargin = marginPixels;
-
- final int size = (mOrientation == Orientation.VERTICAL) ? getHeight() : getWidth();
- recomputeScrollPosition(size, size, marginPixels, oldMargin);
-
- requestLayout();
- }
-
- /**
- * Return the margin between pages.
- *
- * @return The size of the margin in pixels
- */
- public int getPageMargin() {
- return mPageMargin;
- }
-
- /**
- * Set a drawable that will be used to fill the margin between pages.
- *
- * @param d Drawable to display between pages
- */
- public void setPageMarginDrawable(Drawable d) {
- mMarginDrawable = d;
- if (d != null) refreshDrawableState();
- setWillNotDraw(d == null);
- invalidate();
- }
-
- /**
- * Set a drawable that will be used to fill the margin between pages.
- *
- * @param resId Resource ID of a drawable to display between pages
- */
- public void setPageMarginDrawable(int resId) {
- setPageMarginDrawable(getContext().getResources().getDrawable(resId));
- }
-
- @Override
- protected boolean verifyDrawable(Drawable who) {
- return super.verifyDrawable(who) || who == mMarginDrawable;
- }
-
- @Override
- protected void drawableStateChanged() {
- super.drawableStateChanged();
- final Drawable d = mMarginDrawable;
- if (d != null && d.isStateful()) {
- d.setState(getDrawableState());
- }
- }
-
- // We want the duration of the page snap animation to be influenced by the distance that
- // the screen has to travel, however, we don't want this duration to be effected in a
- // purely linear fashion. Instead, we use this method to moderate the effect that the distance
- // of travel has on the overall snap duration.
- float distanceInfluenceForSnapDuration(float f) {
- f -= 0.5f; // center the values about 0.
- f *= 0.3f * Math.PI / 2.0f;
- return (float) Math.sin(f);
- }
-
- /**
- * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
- *
- * @param x the number of pixels to scroll by on the X axis
- * @param y the number of pixels to scroll by on the Y axis
- */
- void smoothScrollTo(int x, int y) {
- smoothScrollTo(x, y, 0);
- }
-
- /**
- * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
- *
- * @param x the number of pixels to scroll by on the X axis
- * @param y the number of pixels to scroll by on the Y axis
- * @param velocity the velocity associated with a fling, if applicable. (0 otherwise)
- */
- void smoothScrollTo(int x, int y, int velocity) {
- if (getChildCount() == 0) {
- // Nothing to do.
- setScrollingCacheEnabled(false);
- return;
- }
- int sx = getScrollX();
- int sy = getScrollY();
- int dx = x - sx;
- int dy = y - sy;
- if (dx == 0 && dy == 0) {
- completeScroll(false);
- populate();
- setScrollState(SCROLL_STATE_IDLE);
- return;
- }
-
- setScrollingCacheEnabled(true);
- setScrollState(SCROLL_STATE_SETTLING);
-
- final int size = getClientSize();
- final int halfSize = size / 2;
- final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / size);
- final float distance = halfSize + halfSize *
- distanceInfluenceForSnapDuration(distanceRatio);
-
- int duration = 0;
- velocity = Math.abs(velocity);
- if (velocity > 0) {
- duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
- } else {
- final float pageSize = size * mAdapter.getPageWidth(mCurItem);
- final float pageDelta = (float) Math.abs(dx) / (pageSize + mPageMargin);
- duration = (int) ((pageDelta + 1) * 100);
- }
- duration = Math.min(duration, MAX_SETTLE_DURATION);
-
- mScroller.startScroll(sx, sy, dx, dy, duration);
- ViewCompat.postInvalidateOnAnimation(this);
- }
-
- ItemInfo addNewItem(int position, int index) {
- ItemInfo ii = new ItemInfo();
- ii.position = position;
- ii.object = mAdapter.instantiateItem(this, position);
- ii.sizeFactor = mAdapter.getPageWidth(position);
- if (index < 0 || index >= mItems.size()) {
- mItems.add(ii);
- } else {
- mItems.add(index, ii);
- }
- return ii;
- }
-
- void dataSetChanged() {
- // This method only gets called if our observer is attached, so mAdapter is non-null.
-
- final int adapterCount = mAdapter.getCount();
- mExpectedAdapterCount = adapterCount;
- boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1 &&
- mItems.size() < adapterCount;
- int newCurrItem = mCurItem;
-
- boolean isUpdating = false;
- for (int i = 0; i < mItems.size(); i++) {
- final ItemInfo ii = mItems.get(i);
- final int newPos = mAdapter.getItemPosition(ii.object);
-
- if (newPos == PagerAdapter.POSITION_UNCHANGED) {
- continue;
- }
-
- if (newPos == PagerAdapter.POSITION_NONE) {
- mItems.remove(i);
- i--;
-
- if (!isUpdating) {
- mAdapter.startUpdate(this);
- isUpdating = true;
- }
-
- mAdapter.destroyItem(this, ii.position, ii.object);
- needPopulate = true;
-
- if (mCurItem == ii.position) {
- // Keep the current item in the valid range
- newCurrItem = Math.max(0, Math.min(mCurItem, adapterCount - 1));
- needPopulate = true;
- }
- continue;
- }
-
- if (ii.position != newPos) {
- if (ii.position == mCurItem) {
- // Our current item changed position. Follow it.
- newCurrItem = newPos;
- }
-
- ii.position = newPos;
- needPopulate = true;
- }
- }
-
- if (isUpdating) {
- mAdapter.finishUpdate(this);
- }
-
- Collections.sort(mItems, COMPARATOR);
-
- if (needPopulate) {
- // Reset our known page widths; populate will recompute them.
- final int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- final View child = getChildAt(i);
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- if (!lp.isDecor) {
- lp.heightFactor = 0.f;
- }
- }
-
- setCurrentItemInternal(newCurrItem, false, true);
- requestLayout();
- }
- }
-
- void populate() {
- populate(mCurItem);
- }
-
- void populate(int newCurrentItem) {
- ItemInfo oldCurInfo = null;
- int focusDirection = View.FOCUS_FORWARD;
- if (mCurItem != newCurrentItem) {
- focusDirection = mCurItem < newCurrentItem ? View.FOCUS_DOWN : View.FOCUS_UP;
- oldCurInfo = infoForPosition(mCurItem);
- mCurItem = newCurrentItem;
- }
-
- if (mAdapter == null) {
- sortChildDrawingOrder();
- return;
- }
-
- // Bail now if we are waiting to populate. This is to hold off
- // on creating views from the time the user releases their finger to
- // fling to a new position until we have finished the scroll to
- // that position, avoiding glitches from happening at that point.
- if (mPopulatePending) {
- if (DEBUG) Log.i(TAG, "populate is pending, skipping for now...");
- sortChildDrawingOrder();
- return;
- }
-
- // Also, don't populate until we are attached to a window. This is to
- // avoid trying to populate before we have restored our view hierarchy
- // state and conflicting with what is restored.
- if (getWindowToken() == null) {
- return;
- }
-
- mAdapter.startUpdate(this);
-
- final int pageLimit = mOffscreenPageLimit;
- final int startPos = Math.max(0, mCurItem - pageLimit);
- final int N = mAdapter.getCount();
- final int endPos = Math.min(N - 1, mCurItem + pageLimit);
-
- if (N != mExpectedAdapterCount) {
- String resName;
- try {
- resName = getResources().getResourceName(getId());
- } catch ( Resources.NotFoundException e) {
- resName = Integer.toHexString(getId());
- }
- throw new IllegalStateException("The application's PagerAdapter changed the adapter's" +
- " contents without calling PagerAdapter#notifyDataSetChanged!" +
- " Expected adapter item count: " + mExpectedAdapterCount + ", found: " + N +
- " Pager id: " + resName +
- " Pager class: " + getClass() +
- " Problematic adapter: " + mAdapter.getClass());
- }
-
- // Locate the currently focused item or add it if needed.
- int curIndex = -1;
- ItemInfo curItem = null;
- for (curIndex = 0; curIndex < mItems.size(); curIndex++) {
- final ItemInfo ii = mItems.get(curIndex);
- if (ii.position >= mCurItem) {
- if (ii.position == mCurItem) curItem = ii;
- break;
- }
- }
-
- if (curItem == null && N > 0) {
- curItem = addNewItem(mCurItem, curIndex);
- }
-
- // Fill 3x the available width or up to the number of offscreen
- // pages requested to either side, whichever is larger.
- // If we have no current item we have no work to do.
- if (curItem != null) {
- float extraSizeTopLeft = 0.f;
- int itemIndex = curIndex - 1;
- ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
- final int clientSize = getClientSize();
- final float topLeftSizeNeeded = clientSize <= 0 ? 0 :
- 2.f - curItem.sizeFactor + (float) getPaddingLeft() / (float) clientSize;
- for (int pos = mCurItem - 1; pos >= 0; pos--) {
- if (extraSizeTopLeft >= topLeftSizeNeeded && pos < startPos) {
- if (ii == null) {
- break;
- }
- if (pos == ii.position && !ii.scrolling) {
- mItems.remove(itemIndex);
- mAdapter.destroyItem(this, pos, ii.object);
- if (DEBUG) {
- Log.i(TAG, "populate() - destroyItem() with pos: " + pos +
- " view: " + ((View) ii.object));
- }
- itemIndex--;
- curIndex--;
- ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
- }
- } else if (ii != null && pos == ii.position) {
- extraSizeTopLeft += ii.sizeFactor;
- itemIndex--;
- ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
- } else {
- ii = addNewItem(pos, itemIndex + 1);
- extraSizeTopLeft += ii.sizeFactor;
- curIndex++;
- ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
- }
- }
-
- float extraSizeBottomRight = curItem.sizeFactor;
- itemIndex = curIndex + 1;
- if (extraSizeBottomRight < 2.f) {
- ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
- final float bottomRightSizeNeeded = clientSize <= 0 ? 0 :
- (float) getPaddingRight() / (float) clientSize + 2.f;
- for (int pos = mCurItem + 1; pos < N; pos++) {
- if (extraSizeBottomRight >= bottomRightSizeNeeded && pos > endPos) {
- if (ii == null) {
- break;
- }
- if (pos == ii.position && !ii.scrolling) {
- mItems.remove(itemIndex);
- mAdapter.destroyItem(this, pos, ii.object);
- if (DEBUG) {
- Log.i(TAG, "populate() - destroyItem() with pos: " + pos +
- " view: " + ((View) ii.object));
- }
- ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
- }
- } else if (ii != null && pos == ii.position) {
- extraSizeBottomRight += ii.sizeFactor;
- itemIndex++;
- ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
- } else {
- ii = addNewItem(pos, itemIndex);
- itemIndex++;
- extraSizeBottomRight += ii.sizeFactor;
- ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
- }
- }
- }
-
- calculatePageOffsets(curItem, curIndex, oldCurInfo);
- }
-
- if (DEBUG) {
- Log.i(TAG, "Current page list:");
- for (int i = 0; i < mItems.size(); i++) {
- Log.i(TAG, "#" + i + ": page " + mItems.get(i).position);
- }
- }
-
- mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null);
-
- mAdapter.finishUpdate(this);
-
- // Check width measurement of current pages and drawing sort order.
- // Update LayoutParams as needed.
- final int childCount = getChildCount();
- if (mOrientation == Orientation.VERTICAL) {
- for (int i = 0; i < childCount; i++) {
- final View child = getChildAt(i);
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- lp.childIndex = i;
- if (!lp.isDecor && lp.heightFactor == 0.f) {
- // 0 means requery the adapter for this, it doesn't have a valid width
- // .
- final ItemInfo ii = infoForChild(child);
- if (ii != null) {
- lp.heightFactor = ii.sizeFactor;
- lp.position = ii.position;
- }
- }
- }
- } else {
- for (int i = 0; i < childCount; i++) {
- final View child = getChildAt(i);
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- lp.childIndex = i;
- if (!lp.isDecor && lp.widthFactor == 0.f) {
- // 0 means requery the adapter for this, it doesn't have a valid width.
- final ItemInfo ii = infoForChild(child);
- if (ii != null) {
- lp.widthFactor = ii.sizeFactor;
- lp.position = ii.position;
- }
- }
- }
- }
- sortChildDrawingOrder();
-
- if (hasFocus()) {
- View currentFocused = findFocus();
- ItemInfo ii = currentFocused != null ? infoForAnyChild(currentFocused) : null;
- if (ii == null || ii.position != mCurItem) {
- for (int i = 0; i < getChildCount(); i++) {
- View child = getChildAt(i);
- ii = infoForChild(child);
- if (ii != null && ii.position == mCurItem) {
- if (child.requestFocus(focusDirection)) {
- break;
- }
- }
- }
- }
- }
- }
-
- private void sortChildDrawingOrder() {
- if (mDrawingOrder != DRAW_ORDER_DEFAULT) {
- if (mDrawingOrderedChildren == null) {
- mDrawingOrderedChildren = new ArrayList();
- } else {
- mDrawingOrderedChildren.clear();
- }
- final int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- final View child = getChildAt(i);
- mDrawingOrderedChildren.add(child);
- }
- Collections.sort(mDrawingOrderedChildren, sPositionComparator);
- }
- }
-
- private void calculatePageOffsets(ItemInfo curItem, int curIndex, ItemInfo oldCurInfo) {
- final int N = mAdapter.getCount();
- final int size = getClientSize();
- final float marginOffset = size > 0 ? (float) mPageMargin / size : 0;
- // Fix up offsets for later layout.
- if (oldCurInfo != null) {
- final int oldCurPosition = oldCurInfo.position;
- // Base offsets off of oldCurInfo.
- if (oldCurPosition < curItem.position) {
- int itemIndex = 0;
- ItemInfo ii = null;
- float offset = oldCurInfo.offset + oldCurInfo.sizeFactor + marginOffset;
- for (int pos = oldCurPosition + 1;
- pos <= curItem.position && itemIndex < mItems.size(); pos++) {
- ii = mItems.get(itemIndex);
- while (pos > ii.position && itemIndex < mItems.size() - 1) {
- itemIndex++;
- ii = mItems.get(itemIndex);
- }
- while (pos < ii.position) {
- // We don't have an item populated for this,
- // ask the adapter for an offset.
- offset += mAdapter.getPageWidth(pos) + marginOffset;
- pos++;
- }
- ii.offset = offset;
- offset += ii.sizeFactor + marginOffset;
- }
- } else if (oldCurPosition > curItem.position) {
- int itemIndex = mItems.size() - 1;
- ItemInfo ii = null;
- float offset = oldCurInfo.offset;
- for (int pos = oldCurPosition - 1;
- pos >= curItem.position && itemIndex >= 0; pos--) {
- ii = mItems.get(itemIndex);
- while (pos < ii.position && itemIndex > 0) {
- itemIndex--;
- ii = mItems.get(itemIndex);
- }
- while (pos > ii.position) {
- // We don't have an item populated for this,
- // ask the adapter for an offset.
- offset -= mAdapter.getPageWidth(pos) + marginOffset;
- pos--;
- }
- offset -= ii.sizeFactor + marginOffset;
- ii.offset = offset;
- }
- }
- }
-
- // Base all offsets off of curItem.
- final int itemCount = mItems.size();
- float offset = curItem.offset;
- int pos = curItem.position - 1;
- mFirstOffset = curItem.position == 0 ? curItem.offset : -Float.MAX_VALUE;
- mLastOffset = curItem.position == N - 1 ?
- curItem.offset + curItem.sizeFactor - 1 : Float.MAX_VALUE;
- // Previous pages
- for (int i = curIndex - 1; i >= 0; i--, pos--) {
- final ItemInfo ii = mItems.get(i);
- while (pos > ii.position) {
- offset -= mAdapter.getPageWidth(pos--) + marginOffset;
- }
- offset -= ii.sizeFactor + marginOffset;
- ii.offset = offset;
- if (ii.position == 0) mFirstOffset = offset;
- }
- offset = curItem.offset + curItem.sizeFactor + marginOffset;
- pos = curItem.position + 1;
- // Next pages
- for (int i = curIndex + 1; i < itemCount; i++, pos++) {
- final ItemInfo ii = mItems.get(i);
- while (pos < ii.position) {
- offset += mAdapter.getPageWidth(pos++) + marginOffset;
- }
- if (ii.position == N - 1) {
- mLastOffset = offset + ii.sizeFactor - 1;
- }
- ii.offset = offset;
- offset += ii.sizeFactor + marginOffset;
- }
-
- mNeedCalculatePageOffsets = false;
- }
-
- /**
- * This is the persistent state that is saved by ViewPager. Only needed
- * if you are creating a sublass of ViewPager that must save its own
- * state, in which case it should implement a subclass of this which
- * contains that state.
- */
- public static class ViewPagerSavedState extends BaseSavedState {
- int position;
- Parcelable adapterState;
- ClassLoader loader;
-
- public ViewPagerSavedState(Parcelable superState) {
- super(superState);
- }
-
- @Override
- public void writeToParcel( Parcel out, int flags) {
- super.writeToParcel(out, flags);
- out.writeInt(position);
- out.writeParcelable(adapterState, flags);
- }
-
- @Override
- public String toString() {
- return "FragmentPager.SavedState{"
- + Integer.toHexString(System.identityHashCode(this))
- + " position=" + position + "}";
- }
-
- public static final Parcelable.Creator CREATOR
- = ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks() {
- @Override
- public ViewPagerSavedState createFromParcel(Parcel in, ClassLoader loader) {
- return new ViewPagerSavedState(in, loader);
- }
-
- @Override
- public ViewPagerSavedState[] newArray(int size) {
- return new ViewPagerSavedState[size];
- }
- });
-
- ViewPagerSavedState(Parcel in, ClassLoader loader) {
- super(in);
- if (loader == null) {
- loader = getClass().getClassLoader();
- }
- position = in.readInt();
- adapterState = in.readParcelable(loader);
- this.loader = loader;
- }
- }
-
- @Override
- public Parcelable onSaveInstanceState() {
- Parcelable superState = super.onSaveInstanceState();
- ViewPagerSavedState ss = new ViewPagerSavedState(superState);
- ss.position = mCurItem;
- if (mAdapter != null) {
- ss.adapterState = mAdapter.saveState();
- }
- return ss;
- }
-
- @Override
- public void onRestoreInstanceState(Parcelable state) {
- if (!(state instanceof ViewPagerSavedState)) {
- super.onRestoreInstanceState(state);
- return;
- }
-
- ViewPagerSavedState ss = (ViewPagerSavedState) state;
- super.onRestoreInstanceState(ss.getSuperState());
-
- if (mAdapter != null) {
- mAdapter.restoreState(ss.adapterState, ss.loader);
- setCurrentItemInternal(ss.position, false, true);
- } else {
- mRestoredCurItem = ss.position;
- mRestoredAdapterState = ss.adapterState;
- mRestoredClassLoader = ss.loader;
- }
- }
-
- @Override
- public void addView(View child, int index, ViewGroup.LayoutParams params) {
- if (!checkLayoutParams(params)) {
- params = generateLayoutParams(params);
- }
- final LayoutParams lp = (LayoutParams) params;
- lp.isDecor |= child instanceof Decor;
- if (mInLayout) {
- if (lp != null && lp.isDecor) {
- throw new IllegalStateException("Cannot add pager decor view during layout");
- }
- lp.needsMeasure = true;
- addViewInLayout(child, index, params);
- } else {
- super.addView(child, index, params);
- }
-
- if (USE_CACHE) {
- if (child.getVisibility() != GONE) {
- child.setDrawingCacheEnabled(mScrollingCacheEnabled);
- } else {
- child.setDrawingCacheEnabled(false);
- }
- }
- }
-
- @Override
- public void removeView(View view) {
- if (mInLayout) {
- removeViewInLayout(view);
- } else {
- super.removeView(view);
- }
- }
-
- ItemInfo infoForChild(View child) {
- for (int i = 0; i < mItems.size(); i++) {
- ItemInfo ii = mItems.get(i);
- if (mAdapter.isViewFromObject(child, ii.object)) {
- return ii;
- }
- }
- return null;
- }
-
- ItemInfo infoForAnyChild(View child) {
- ViewParent parent;
- while ((parent = child.getParent()) != this) {
- if (parent == null || !(parent instanceof View)) {
- return null;
- }
- child = (View) parent;
- }
- return infoForChild(child);
- }
-
- ItemInfo infoForPosition(int position) {
- for (int i = 0; i < mItems.size(); i++) {
- ItemInfo ii = mItems.get(i);
- if (ii.position == position) {
- return ii;
- }
- }
- return null;
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- mFirstLayout = true;
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- // For simple implementation, our internal size is always 0.
- // We depend on the container to specify the layout size of
- // our view. We can't really know what it is since we will be
- // adding and removing different arbitrary views and do not
- // want the layout to change as this happens.
- setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
- getDefaultSize(0, heightMeasureSpec));
-
- final int measuredSize =
- (mOrientation == Orientation.VERTICAL) ? getMeasuredHeight() : getMeasuredWidth();
- final int maxGutterSize = measuredSize / 10;
- mGutterSize = Math.min(maxGutterSize, mDefaultGutterSize);
-
- // Children are just made to fill our space.
- int childWidthSize;
- int childHeightSize;
-
- if (mOrientation == Orientation.VERTICAL) {
- childWidthSize = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
- childHeightSize = measuredSize - getPaddingTop() - getPaddingBottom();
- } else {
- childWidthSize = measuredSize - getPaddingLeft() - getPaddingRight();
- childHeightSize = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
- }
-
- /*
- * Make sure all children have been properly measured. Decor views first.
- * Right now we cheat and make this less complicated by assuming decor
- * views won't intersect. We will pin to edges based on gravity.
+ /**
+ * Used to track what the expected number of items in the adapter should be.
+ * If the app changes this when we don't expect it, we'll throw a big obnoxious exception.
*/
- int size = getChildCount();
- for (int i = 0; i < size; ++i) {
- final View child = getChildAt(i);
- if (child.getVisibility() != GONE) {
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- if (lp != null && lp.isDecor) {
- final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
- final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
- int widthMode = MeasureSpec.AT_MOST;
- int heightMode = MeasureSpec.AT_MOST;
- boolean consumeVertical = vgrav == Gravity.TOP || vgrav == Gravity.BOTTOM;
- boolean consumeHorizontal = hgrav == Gravity.LEFT || hgrav == Gravity.RIGHT;
+ private int mExpectedAdapterCount;
- if (consumeVertical) {
- widthMode = MeasureSpec.EXACTLY;
- } else if (consumeHorizontal) {
- heightMode = MeasureSpec.EXACTLY;
- }
+ private static class ItemInfo {
+ Object object;
+ int position;
+ boolean scrolling;
+ float sizeFactor;
+ float offset;
+ }
- int widthSize = childWidthSize;
- int heightSize = childHeightSize;
- if (lp.width != LayoutParams.WRAP_CONTENT) {
- widthMode = MeasureSpec.EXACTLY;
- if (lp.width != LayoutParams.FILL_PARENT) {
- widthSize = lp.width;
- }
- }
- if (lp.height != LayoutParams.WRAP_CONTENT) {
- heightMode = MeasureSpec.EXACTLY;
- if (lp.height != LayoutParams.FILL_PARENT) {
- heightSize = lp.height;
- }
- }
- final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode);
- final int heightSpec = MeasureSpec.makeMeasureSpec(heightSize, heightMode);
- child.measure(widthSpec, heightSpec);
-
- if (consumeVertical) {
- childHeightSize -= child.getMeasuredHeight();
- } else if (consumeHorizontal) {
- childWidthSize -= child.getMeasuredWidth();
- }
+ private static final Comparator< ItemInfo > COMPARATOR = new Comparator< ItemInfo >() {
+ @Override
+ public int compare( ItemInfo lhs, ItemInfo rhs ) {
+ return lhs.position - rhs.position;
}
- }
- }
+ };
- mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthSize, MeasureSpec.EXACTLY);
- mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeightSize, MeasureSpec.EXACTLY);
-
- // Make sure we have created all fragments that we need to have shown.
- mInLayout = true;
- populate();
- mInLayout = false;
-
- // Page views next.
- size = getChildCount();
- for (int i = 0; i < size; ++i) {
- final View child = getChildAt(i);
- if (child.getVisibility() != GONE) {
- if (DEBUG) {
- Log.v(TAG, "Measuring #" + i + " " + child
- + ": " + mChildWidthMeasureSpec);
+ private static final Interpolator sInterpolator = new Interpolator() {
+ public float getInterpolation( float t ) {
+ t -= 1.0f;
+ return t * t * t * t * t + 1.0f;
}
+ };
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- if (lp == null || !lp.isDecor) {
- if (mOrientation == Orientation.VERTICAL) {
- final int heightSpec = MeasureSpec.makeMeasureSpec(
- (int) (childHeightSize * lp.heightFactor), MeasureSpec.EXACTLY);
- child.measure(mChildWidthMeasureSpec, heightSpec);
- } else {
+ private final ArrayList< ItemInfo > mItems = new ArrayList< ItemInfo >();
+ private final ItemInfo mTempItem = new ItemInfo();
- final int widthSpec = MeasureSpec.makeMeasureSpec(
- (int) (childWidthSize * lp.widthFactor), MeasureSpec.EXACTLY);
- child.measure(widthSpec, mChildHeightMeasureSpec);
- }
- }
- }
- }
- }
+ private final Rect mTempRect = new Rect();
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
+ private Orientation mOrientation = Orientation.HORIZONTAL;
- // Make sure scroll position is set correctly.
- if (mOrientation == Orientation.VERTICAL) {
- if (h != oldh) {
- recomputeScrollPosition(h, oldh, mPageMargin, mPageMargin);
- }
- } else {
- if (w != oldw) {
- recomputeScrollPosition(w, oldw, mPageMargin, mPageMargin);
- }
- }
- }
+ private PagerAdapter mAdapter;
+ private int mCurItem; // Index of currently displayed page.
+ private int mRestoredCurItem = -1;
+ private Parcelable mRestoredAdapterState = null;
+ private ClassLoader mRestoredClassLoader = null;
+ private Scroller mScroller;
+ private PagerObserver mObserver;
- private void recomputeScrollPosition(int size, int oldSize, int margin, int oldMargin) {
- if (mOrientation == Orientation.VERTICAL) {
- if (oldSize > 0 && !mItems.isEmpty()) {
- final int heightWithMargin = size - getPaddingTop() - getPaddingBottom() + margin;
- final int oldHeightWithMargin = oldSize - getPaddingTop() - getPaddingBottom()
- + oldMargin;
- final int ypos = getScrollY();
- final float pageOffset = (float) ypos / oldHeightWithMargin;
- final int newOffsetPixels = (int) (pageOffset * heightWithMargin);
+ private int mPageMargin;
+ private Drawable mMarginDrawable;
+ private int mTopLeftPageBounds;
+ private int mBottomRightPageBounds;
- scrollTo(getScrollX(), newOffsetPixels);
- if (!mScroller.isFinished()) {
- // We now return to your regularly scheduled scroll, already in progress.
- final int newDuration = mScroller.getDuration() - mScroller.timePassed();
- ItemInfo targetInfo = infoForPosition(mCurItem);
- mScroller.startScroll(0, newOffsetPixels,
- 0, (int) (targetInfo.offset * size), newDuration);
- }
- } else {
- final ItemInfo ii = infoForPosition(mCurItem);
- final float scrollOffset = ii != null ? Math.min(ii.offset, mLastOffset) : 0;
- final int scrollPos = (int) (scrollOffset *
- (size - getPaddingTop() - getPaddingBottom()));
- if (scrollPos != getScrollY()) {
- completeScroll(false);
- scrollTo(getScrollX(), scrollPos);
- }
- }
- } else {
- if (oldSize > 0 && !mItems.isEmpty()) {
- final int widthWithMargin = size - getPaddingLeft() - getPaddingRight() + margin;
- final int oldWidthWithMargin = oldSize - getPaddingLeft() - getPaddingRight()
- + oldMargin;
- final int xpos = getScrollX();
- final float pageOffset = (float) xpos / oldWidthWithMargin;
- final int newOffsetPixels = (int) (pageOffset * widthWithMargin);
+ // Offsets of the first and last items, if known.
+ // Set during population, used to determine if we are at the beginning
+ // or end of the pager data set during touch scrolling.
+ private float mFirstOffset = -Float.MAX_VALUE;
+ private float mLastOffset = Float.MAX_VALUE;
- scrollTo(newOffsetPixels, getScrollY());
- if (!mScroller.isFinished()) {
- // We now return to your regularly scheduled scroll, already in progress.
- final int newDuration = mScroller.getDuration() - mScroller.timePassed();
- ItemInfo targetInfo = infoForPosition(mCurItem);
- mScroller.startScroll(newOffsetPixels, 0,
- (int) (targetInfo.offset * size), 0, newDuration);
- }
- } else {
- final ItemInfo ii = infoForPosition(mCurItem);
- final float scrollOffset = ii != null ? Math.min(ii.offset, mLastOffset) : 0;
- final int scrollPos = (int) (scrollOffset *
- (size - getPaddingLeft() - getPaddingRight()));
- if (scrollPos != getScrollX()) {
- completeScroll(false);
- scrollTo(scrollPos, getScrollY());
- }
- }
- }
- }
+ private int mChildWidthMeasureSpec;
+ private int mChildHeightMeasureSpec;
+ private boolean mInLayout;
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- final int count = getChildCount();
- int width = r - l;
- int height = b - t;
- int paddingLeft = getPaddingLeft();
- int paddingTop = getPaddingTop();
- int paddingRight = getPaddingRight();
- int paddingBottom = getPaddingBottom();
- final int scroll = (mOrientation == Orientation.VERTICAL) ? getScrollY() : getScrollX();
+ private boolean mScrollingCacheEnabled;
- int decorCount = 0;
+ private boolean mPopulatePending;
+ private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES;
- // First pass - decor views. We need to do this in two passes so that
- // we have the proper offsets for non-decor views later.
- for (int i = 0; i < count; i++) {
- final View child = getChildAt(i);
- if (child.getVisibility() != GONE) {
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- int childLeft = 0;
- int childTop = 0;
- if (lp.isDecor) {
- final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
- final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
- switch (hgrav) {
- default:
- childLeft = paddingLeft;
- break;
- case Gravity.LEFT:
- childLeft = paddingLeft;
- paddingLeft += child.getMeasuredWidth();
- break;
- case Gravity.CENTER_HORIZONTAL:
- childLeft = Math.max((width - child.getMeasuredWidth()) / 2,
- paddingLeft);
- break;
- case Gravity.RIGHT:
- childLeft = width - paddingRight - child.getMeasuredWidth();
- paddingRight += child.getMeasuredWidth();
- break;
- }
- switch (vgrav) {
- default:
- childTop = paddingTop;
- break;
- case Gravity.TOP:
- childTop = paddingTop;
- paddingTop += child.getMeasuredHeight();
- break;
- case Gravity.CENTER_VERTICAL:
- childTop = Math.max((height - child.getMeasuredHeight()) / 2,
- paddingTop);
- break;
- case Gravity.BOTTOM:
- childTop = height - paddingBottom - child.getMeasuredHeight();
- paddingBottom += child.getMeasuredHeight();
- break;
- }
- if (mOrientation == Orientation.VERTICAL) {
- childTop += scroll;
- } else {
- childLeft += scroll;
- }
- child.layout(childLeft, childTop,
- childLeft + child.getMeasuredWidth(),
- childTop + child.getMeasuredHeight());
- decorCount++;
- }
- }
- }
-
- final int childSize =
- (mOrientation == Orientation.VERTICAL) ? height - paddingTop - paddingBottom
- : width - paddingLeft - paddingRight;
- // Page views. Do this once we have the right padding offsets from above.
- for (int i = 0; i < count; i++) {
- final View child = getChildAt(i);
- if (child.getVisibility() != GONE) {
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- ItemInfo ii;
- if (!lp.isDecor && (ii = infoForChild(child)) != null) {
- int topLeftoff = (int) (childSize * ii.offset);
- int childLeft;
- int childTop;
- if (mOrientation == Orientation.VERTICAL) {
- childLeft = paddingLeft;
- childTop = paddingTop + topLeftoff;
- if (lp.needsMeasure) {
- // This was added during layout and needs measurement.
- // Do it now that we know what we're working with.
- lp.needsMeasure = false;
- final int widthSpec = MeasureSpec.makeMeasureSpec(
- (int) (width - paddingLeft - paddingRight),
- MeasureSpec.EXACTLY);
- final int heightSpec = MeasureSpec.makeMeasureSpec(
- (int) (childSize * lp.heightFactor),
- MeasureSpec.EXACTLY);
- child.measure(widthSpec, heightSpec);
- }
- } else {
- childLeft = paddingLeft + topLeftoff;
- childTop = paddingTop;
- if (lp.needsMeasure) {
- // This was added during layout and needs measurement.
- // Do it now that we know what we're working with.
- lp.needsMeasure = false;
- final int widthSpec = MeasureSpec.makeMeasureSpec(
- (int) (childSize * lp.widthFactor),
- MeasureSpec.EXACTLY);
- final int heightSpec = MeasureSpec.makeMeasureSpec(
- (int) (height - paddingTop - paddingBottom),
- MeasureSpec.EXACTLY);
- child.measure(widthSpec, heightSpec);
- }
- }
- if (DEBUG) {
- Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object
- + ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth()
- + "x" + child.getMeasuredHeight());
- }
- child.layout(childLeft, childTop,
- childLeft + child.getMeasuredWidth(),
- childTop + child.getMeasuredHeight());
- }
- }
- }
- mTopLeftPageBounds = (mOrientation == Orientation.VERTICAL) ? paddingLeft : paddingTop;
- mBottomRightPageBounds =
- (mOrientation == Orientation.VERTICAL) ? width - paddingRight : height - paddingBottom;
- mDecorChildCount = decorCount;
-
- if (mFirstLayout) {
- scrollToItem(mCurItem, false, 0, false);
- }
- mFirstLayout = false;
- }
-
- @Override
- public void computeScroll() {
- if (!mScroller.isFinished() && mScroller.computeScrollOffset()) {
- int oldX = getScrollX();
- int oldY = getScrollY();
- int x = mScroller.getCurrX();
- int y = mScroller.getCurrY();
-
- if (oldX != x || oldY != y) {
- scrollTo(x, y);
- if (mOrientation == Orientation.VERTICAL) {
- if (!pageScrolled(y)) {
- mScroller.abortAnimation();
- scrollTo(x, 0);
- }
- } else {
- if (!pageScrolled(x)) {
- mScroller.abortAnimation();
- scrollTo(0, y);
- }
- }
- }
-
- // Keep on drawing until the animation has finished.
- ViewCompat.postInvalidateOnAnimation(this);
- return;
- }
-
- // Done with scroll, clean up state.
- completeScroll(true);
- }
-
- private boolean pageScrolled(int pos) {
- if (mItems.size() == 0) {
- mCalledSuper = false;
- onPageScrolled(0, 0, 0);
- if (!mCalledSuper) {
- throw new IllegalStateException(
- "onPageScrolled did not call superclass implementation");
- }
- return false;
- }
- final ItemInfo ii = infoForCurrentScrollPosition();
- final int size = getClientSize();
- final int sizeWithMargin = size + mPageMargin;
- final float marginOffset = (float) mPageMargin / size;
- final int currentPage = ii.position;
- final float pageOffset = (((float) pos / size) - ii.offset) / (ii.sizeFactor + marginOffset);
- final int offsetPixels = (int) (pageOffset * sizeWithMargin);
-
- mCalledSuper = false;
- onPageScrolled(currentPage, pageOffset, offsetPixels);
- if (!mCalledSuper) {
- throw new IllegalStateException(
- "onPageScrolled did not call superclass implementation");
- }
- return true;
- }
-
- /**
- * This method will be invoked when the current page is scrolled, either as part
- * of a programmatically initiated smooth scroll or a user initiated touch scroll.
- * If you override this method you must call through to the superclass implementation
- * (e.g. super.onPageScrolled(position, offset, offsetPixels)) before onPageScrolled
- * returns.
- *
- * @param position Position index of the first page currently being displayed.
- * Page position+1 will be visible if positionOffset is nonzero.
- * @param offset Value from [0, 1) indicating the offset from the page at position.
- * @param offsetPixels Value in pixels indicating the offset from position.
- */
- protected void onPageScrolled(int position, float offset, int offsetPixels) {
- // Offset any decor views if needed - keep them on-screen at all times.
- if (mDecorChildCount > 0) {
- if (mOrientation == Orientation.VERTICAL) {
- final int scrollY = getScrollY();
- int paddingTop = getPaddingTop();
- int paddingBottom = getPaddingBottom();
- final int height = getHeight();
- final int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- final View child = getChildAt(i);
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- if (!lp.isDecor) continue;
-
- final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
- int childTop = 0;
- switch (vgrav) {
- default:
- childTop = paddingTop;
- break;
- case Gravity.TOP:
- childTop = paddingTop;
- paddingTop += child.getHeight();
- break;
- case Gravity.CENTER_VERTICAL:
- childTop = Math.max((height - child.getMeasuredHeight()) / 2,
- paddingTop);
- break;
- case Gravity.BOTTOM:
- childTop = height - paddingBottom - child.getMeasuredHeight();
- paddingBottom += child.getMeasuredHeight();
- break;
- }
- childTop += scrollY;
-
- final int childOffset = childTop - child.getTop();
- if (childOffset != 0) {
- child.offsetTopAndBottom(childOffset);
- }
- }
- } else {
- final int scrollX = getScrollX();
- int paddingLeft = getPaddingLeft();
- int paddingRight = getPaddingRight();
- final int width = getWidth();
- final int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- final View child = getChildAt(i);
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- if (!lp.isDecor) continue;
-
- final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
- int childLeft = 0;
- switch (hgrav) {
- default:
- childLeft = paddingLeft;
- break;
- case Gravity.LEFT:
- childLeft = paddingLeft;
- paddingLeft += child.getWidth();
- break;
- case Gravity.CENTER_HORIZONTAL:
- childLeft = Math.max((width - child.getMeasuredWidth()) / 2,
- paddingLeft);
- break;
- case Gravity.RIGHT:
- childLeft = width - paddingRight - child.getMeasuredWidth();
- paddingRight += child.getMeasuredWidth();
- break;
- }
- childLeft += scrollX;
-
- final int childOffset = childLeft - child.getLeft();
- if (childOffset != 0) {
- child.offsetLeftAndRight(childOffset);
- }
- }
- }
- }
-
- if (mOnPageChangeListener != null) {
- mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels);
- }
- if (mInternalPageChangeListener != null) {
- mInternalPageChangeListener.onPageScrolled(position, offset, offsetPixels);
- }
-
- if (mPageTransformer != null) {
- final int scroll = (mOrientation == Orientation.VERTICAL) ? getScrollY() : getScrollX();
- final int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- final View child = getChildAt(i);
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-
- if (lp.isDecor) continue;
-
- final float transformPos =
- (float) (((mOrientation == Orientation.VERTICAL) ? child.getTop() : child.getLeft())
- - scroll) / getClientSize();
- mPageTransformer.transformPage(child, transformPos);
- }
- }
-
- mCalledSuper = true;
- }
-
- private void completeScroll(boolean postEvents) {
- boolean needPopulate = mScrollState == SCROLL_STATE_SETTLING;
- if (needPopulate) {
- // Done with scroll, no longer want to cache view drawing.
- setScrollingCacheEnabled(false);
- mScroller.abortAnimation();
- int oldX = getScrollX();
- int oldY = getScrollY();
- int x = mScroller.getCurrX();
- int y = mScroller.getCurrY();
- if (oldX != x || oldY != y) {
- scrollTo(x, y);
- }
- }
- mPopulatePending = false;
- for (int i = 0; i < mItems.size(); i++) {
- ItemInfo ii = mItems.get(i);
- if (ii.scrolling) {
- needPopulate = true;
- ii.scrolling = false;
- }
- }
- if (needPopulate) {
- if (postEvents) {
- ViewCompat.postOnAnimation(this, mEndScrollRunnable);
- } else {
- mEndScrollRunnable.run();
- }
- }
- }
-
- private boolean isGutterDrag(float axis, float dAxis) {
- return (axis < mGutterSize && dAxis > 0) || (axis
- > (mOrientation == Orientation.VERTICAL ? getHeight() : getWidth()) - mGutterSize
- && dAxis < 0);
- }
-
- private void enableLayers(boolean enable) {
- final int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- final int layerType = enable ?
- ViewCompat.LAYER_TYPE_HARDWARE : ViewCompat.LAYER_TYPE_NONE;
- ViewCompat.setLayerType(getChildAt(i), layerType, null);
- }
- }
-
- @Override
- public boolean onInterceptTouchEvent( MotionEvent ev) {
- /*
- * This method JUST determines whether we want to intercept the motion.
- * If we return true, onMotionEvent will be called and we do the actual
- * scrolling there.
+ private boolean mIsBeingDragged;
+ private boolean mIsUnableToDrag;
+ private boolean mIgnoreGutter;
+ private int mDefaultGutterSize;
+ private int mGutterSize;
+ private int mTouchSlop;
+ /**
+ * Position of the last motion event.
*/
+ private float mLastMotionX;
+ private float mLastMotionY;
+ private float mInitialMotionX;
+ private float mInitialMotionY;
+ /**
+ * ID of the active pointer. This is used to retain consistency during
+ * drags/flings if multiple pointers are used.
+ */
+ private int mActivePointerId = INVALID_POINTER;
+ /**
+ * Sentinel value for no current active pointer.
+ * Used by {@link #mActivePointerId}.
+ */
+ private static final int INVALID_POINTER = -1;
- final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
+ /**
+ * Determines speed during touch scrolling
+ */
+ private VelocityTracker mVelocityTracker;
+ private int mMinimumVelocity;
+ private int mMaximumVelocity;
+ private int mFlingDistance;
+ private int mCloseEnough;
- // Always take care of the touch gesture being complete.
- if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
- // Release the drag.
- if (DEBUG) Log.v(TAG, "Intercept done!");
- mIsBeingDragged = false;
- mIsUnableToDrag = false;
- mActivePointerId = INVALID_POINTER;
- if (mVelocityTracker != null) {
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- }
- return false;
- }
+ // If the pager is at least this close to its final position, complete the scroll
+ // on touch down and let the user interact with the content inside instead of
+ // "catching" the flinging pager.
+ private static final int CLOSE_ENOUGH = 2; // dp
- // Nothing more to do here if we have decided whether or not we
- // are dragging.
- if (action != MotionEvent.ACTION_DOWN) {
- if (mIsBeingDragged) {
- if (DEBUG) Log.v(TAG, "Intercept returning true!");
- return true;
- }
- if (mIsUnableToDrag) {
- if (DEBUG) Log.v(TAG, "Intercept returning false!");
- return false;
- }
- }
+ private boolean mFakeDragging;
+ private long mFakeDragBeginTime;
- switch (action) {
- case MotionEvent.ACTION_MOVE: {
- /*
- * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
- * whether the user has moved far enough from his original down touch.
- */
+ private EdgeEffectCompat mTopLeftEdge;
+ private EdgeEffectCompat mRightBottomEdge;
- /*
- * Locally do absolute value. mLastMotionY is set to the y value
- * of the down event.
- */
- final int activePointerId = mActivePointerId;
- if (activePointerId == INVALID_POINTER) {
- // If we don't have a valid id, the touch down wasn't on content.
- break;
- }
+ private boolean mFirstLayout = true;
+ private boolean mNeedCalculatePageOffsets = false;
+ private boolean mCalledSuper;
+ private int mDecorChildCount;
- final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId);
- if (mOrientation == Orientation.VERTICAL) {
- final float y = MotionEventCompat.getY(ev, pointerIndex);
- final float dy = y - mLastMotionY;
- final float yDiff = Math.abs(dy);
- final float x = MotionEventCompat.getX(ev, pointerIndex);
- final float xDiff = Math.abs(x - mInitialMotionX);
- if (DEBUG) {
- Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
- }
+ private ViewPager.OnPageChangeListener mOnPageChangeListener;
+ private ViewPager.OnPageChangeListener mInternalPageChangeListener;
+ private OnAdapterChangeListener mAdapterChangeListener;
+ private ViewPager.PageTransformer mPageTransformer;
+ private Method mSetChildrenDrawingOrderEnabled;
- if (dy != 0 && !isGutterDrag(mLastMotionY, dy) &&
- canScroll(this, false, (int) dy, (int) x, (int) y)) {
- // Nested view has scrollable area under this point. Let it be handled there.
- mLastMotionX = x;
- mLastMotionY = y;
- mIsUnableToDrag = true;
- return false;
- }
- if (yDiff > mTouchSlop && yDiff * 0.5f > xDiff) {
- if (DEBUG) Log.v(TAG, "Starting drag!");
- mIsBeingDragged = true;
- requestParentDisallowInterceptTouchEvent(true);
- setScrollState(SCROLL_STATE_DRAGGING);
- mLastMotionY = dy > 0 ? mInitialMotionY + mTouchSlop :
- mInitialMotionY - mTouchSlop;
- mLastMotionX = x;
- setScrollingCacheEnabled(true);
- } else if (xDiff > mTouchSlop) {
- // The finger has moved enough in the vertical
- // direction to be counted as a drag... abort
- // any attempt to drag horizontally, to work correctly
- // with children that have scrolling containers.
- if (DEBUG) Log.v(TAG, "Starting unable to drag!");
- mIsUnableToDrag = true;
- }
- if (mIsBeingDragged) {
- // Scroll to follow the motion event
- if (performDrag(y)) {
- ViewCompat.postInvalidateOnAnimation(this);
- }
- }
- } else {
- final float x = MotionEventCompat.getX(ev, pointerIndex);
- final float dx = x - mLastMotionX;
- final float xDiff = Math.abs(dx);
- final float y = MotionEventCompat.getY(ev, pointerIndex);
- final float yDiff = Math.abs(y - mInitialMotionY);
- if (DEBUG) {
- Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
- }
+ private static final int DRAW_ORDER_DEFAULT = 0;
+ private static final int DRAW_ORDER_FORWARD = 1;
+ private static final int DRAW_ORDER_REVERSE = 2;
+ private int mDrawingOrder;
+ private ArrayList< View > mDrawingOrderedChildren;
+ private static final ViewPositionComparator sPositionComparator = new ViewPositionComparator();
- if (dx != 0 && !isGutterDrag(mLastMotionX, dx) &&
- canScroll(this, false, (int) dx, (int) x, (int) y)) {
- // Nested view has scrollable area under this point. Let it be handled there.
- mLastMotionX = x;
- mLastMotionY = y;
- mIsUnableToDrag = true;
- return false;
- }
- if (xDiff > mTouchSlop && xDiff * 0.5f > yDiff) {
- if (DEBUG) Log.v(TAG, "Starting drag!");
- mIsBeingDragged = true;
- requestParentDisallowInterceptTouchEvent(true);
- setScrollState(SCROLL_STATE_DRAGGING);
- mLastMotionX = dx > 0 ? mInitialMotionX + mTouchSlop :
- mInitialMotionX - mTouchSlop;
- mLastMotionY = y;
- setScrollingCacheEnabled(true);
- } else if (yDiff > mTouchSlop) {
- // The finger has moved enough in the vertical
- // direction to be counted as a drag... abort
- // any attempt to drag horizontally, to work correctly
- // with children that have scrolling containers.
- if (DEBUG) Log.v(TAG, "Starting unable to drag!");
- mIsUnableToDrag = true;
- }
- if (mIsBeingDragged) {
- // Scroll to follow the motion event
- if (performDrag(x)) {
- ViewCompat.postInvalidateOnAnimation(this);
- }
- }
- }
- break;
- }
+ /**
+ * Indicates that the pager is in an idle, settled state. The current page
+ * is fully in view and no animation is in progress.
+ */
+ public static final int SCROLL_STATE_IDLE = 0;
- case MotionEvent.ACTION_DOWN: {
- /*
- * Remember location of down touch.
- * ACTION_DOWN always refers to pointer index 0.
- */
- mLastMotionX = mInitialMotionX = ev.getX();
- mLastMotionY = mInitialMotionY = ev.getY();
- mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
- mIsUnableToDrag = false;
+ /**
+ * Indicates that the pager is currently being dragged by the user.
+ */
+ public static final int SCROLL_STATE_DRAGGING = 1;
- mScroller.computeScrollOffset();
- if (mOrientation == Orientation.VERTICAL) {
- if (mScrollState == SCROLL_STATE_SETTLING &&
- Math.abs(mScroller.getFinalY() - mScroller.getCurrY()) > mCloseEnough) {
- // Let the user 'catch' the pager as it animates.
- mScroller.abortAnimation();
- mPopulatePending = false;
+ /**
+ * Indicates that the pager is in the process of settling to a final position.
+ */
+ public static final int SCROLL_STATE_SETTLING = 2;
+
+ private final Runnable mEndScrollRunnable = new Runnable() {
+ public void run() {
+ setScrollState( SCROLL_STATE_IDLE );
populate();
- mIsBeingDragged = true;
- requestParentDisallowInterceptTouchEvent(true);
- setScrollState(SCROLL_STATE_DRAGGING);
- } else {
- completeScroll(false);
- mIsBeingDragged = false;
- }
- } else {
- if (mScrollState == SCROLL_STATE_SETTLING &&
- Math.abs(mScroller.getFinalX() - mScroller.getCurrX()) > mCloseEnough) {
- // Let the user 'catch' the pager as it animates.
- mScroller.abortAnimation();
- mPopulatePending = false;
- populate();
- mIsBeingDragged = true;
- requestParentDisallowInterceptTouchEvent(true);
- setScrollState(SCROLL_STATE_DRAGGING);
- } else {
- completeScroll(false);
- mIsBeingDragged = false;
- }
}
+ };
- if (DEBUG) {
- Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY
- + " mIsBeingDragged=" + mIsBeingDragged
- + "mIsUnableToDrag=" + mIsUnableToDrag);
- }
- break;
- }
+ private int mScrollState = SCROLL_STATE_IDLE;
- case MotionEventCompat.ACTION_POINTER_UP:
- onSecondaryPointerUp(ev);
- break;
- }
-
- if (mVelocityTracker == null) {
- mVelocityTracker = VelocityTracker.obtain();
- }
- mVelocityTracker.addMovement(ev);
-
- /*
- * The only time we want to intercept motion events is if we are in the
- * drag mode.
+ /**
+ * Used internally to monitor when adapters are switched.
*/
- return mIsBeingDragged;
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- if (mFakeDragging) {
- // A fake drag is in progress already, ignore this real one
- // but still eat the touch events.
- // (It is likely that the user is multi-touching the screen.)
- return true;
+ interface OnAdapterChangeListener {
+ public void onAdapterChanged( PagerAdapter oldAdapter, PagerAdapter newAdapter );
}
- if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
- // Don't handle edge touches immediately -- they may actually belong to one of our
- // descendants.
- return false;
+ /**
+ * Used internally to tag special types of child views that should be added as
+ * pager decorations by default.
+ */
+ interface Decor {
}
- if (mAdapter == null || mAdapter.getCount() == 0) {
- // Nothing to present or scroll; nothing to touch.
- return false;
+ public OrientedViewPager( Context context ) {
+ super( context );
+ initViewPager();
}
- if (mVelocityTracker == null) {
- mVelocityTracker = VelocityTracker.obtain();
+ public OrientedViewPager( Context context, AttributeSet attrs ) {
+ super( context, attrs );
+ initViewPager();
}
- mVelocityTracker.addMovement(ev);
- final int action = ev.getAction();
- boolean needsInvalidate = false;
+ void initViewPager() {
+ setWillNotDraw( false );
+ setDescendantFocusability( FOCUS_AFTER_DESCENDANTS );
+ setFocusable( true );
+ final Context context = getContext();
+ mScroller = new Scroller( context, sInterpolator );
+ final ViewConfiguration configuration = ViewConfiguration.get( context );
+ final float density = context.getResources().getDisplayMetrics().density;
- switch (action & MotionEventCompat.ACTION_MASK) {
- case MotionEvent.ACTION_DOWN: {
- mScroller.abortAnimation();
+ mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop( configuration );
+ mMinimumVelocity = ( int ) ( MIN_FLING_VELOCITY * density );
+ mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
+ mTopLeftEdge = new EdgeEffectCompat( context );
+ mRightBottomEdge = new EdgeEffectCompat( context );
+
+ mFlingDistance = ( int ) ( MIN_DISTANCE_FOR_FLING * density );
+ mCloseEnough = ( int ) ( CLOSE_ENOUGH * density );
+ mDefaultGutterSize = ( int ) ( DEFAULT_GUTTER_SIZE * density );
+
+ ViewCompat.setAccessibilityDelegate( this, new MyAccessibilityDelegate() );
+
+ if ( ViewCompat.getImportantForAccessibility( this )
+ == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO ) {
+ ViewCompat.setImportantForAccessibility( this,
+ ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES );
+ }
+ }
+
+ public void setOrientation( Orientation orientation ) {
+ mOrientation = orientation;
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ removeCallbacks( mEndScrollRunnable );
+ super.onDetachedFromWindow();
+ }
+
+ private void setScrollState( int newState ) {
+ if ( mScrollState == newState ) {
+ return;
+ }
+
+ mScrollState = newState;
+ if ( mPageTransformer != null ) {
+ // PageTransformers can do complex things that benefit from hardware layers.
+ enableLayers( newState != SCROLL_STATE_IDLE );
+ }
+ if ( mOnPageChangeListener != null ) {
+ mOnPageChangeListener.onPageScrollStateChanged( newState );
+ }
+ }
+
+ /**
+ * Set a PagerAdapter that will supply views for this pager as needed.
+ *
+ * @param adapter Adapter to use
+ */
+ public void setAdapter( PagerAdapter adapter ) {
+ if ( mAdapter != null ) {
+ mAdapter.unregisterDataSetObserver( mObserver );
+ mAdapter.startUpdate( this );
+ for ( int i = 0; i < mItems.size(); i++ ) {
+ final ItemInfo ii = mItems.get( i );
+ mAdapter.destroyItem( this, ii.position, ii.object );
+ }
+ mAdapter.finishUpdate( this );
+ mItems.clear();
+ removeNonDecorViews();
+ mCurItem = 0;
+ scrollTo( 0, 0 );
+ }
+
+ final PagerAdapter oldAdapter = mAdapter;
+ mAdapter = adapter;
+ mExpectedAdapterCount = 0;
+
+ if ( mAdapter != null ) {
+ if ( mObserver == null ) {
+ mObserver = new PagerObserver();
+ }
+ mAdapter.registerDataSetObserver( mObserver );
+ mPopulatePending = false;
+ final boolean wasFirstLayout = mFirstLayout;
+ mFirstLayout = true;
+ mExpectedAdapterCount = mAdapter.getCount();
+ if ( mRestoredCurItem >= 0 ) {
+ mAdapter.restoreState( mRestoredAdapterState, mRestoredClassLoader );
+ setCurrentItemInternal( mRestoredCurItem, false, true );
+ mRestoredCurItem = -1;
+ mRestoredAdapterState = null;
+ mRestoredClassLoader = null;
+ } else if ( !wasFirstLayout ) {
+ populate();
+ } else {
+ requestLayout();
+ }
+ }
+
+ if ( mAdapterChangeListener != null && oldAdapter != adapter ) {
+ mAdapterChangeListener.onAdapterChanged( oldAdapter, adapter );
+ }
+ }
+
+ private void removeNonDecorViews() {
+ for ( int i = 0; i < getChildCount(); i++ ) {
+ final View child = getChildAt( i );
+ final LayoutParams lp = ( LayoutParams ) child.getLayoutParams();
+ if ( !lp.isDecor ) {
+ removeViewAt( i );
+ i--;
+ }
+ }
+ }
+
+ /**
+ * Retrieve the current adapter supplying pages.
+ *
+ * @return The currently registered PagerAdapter
+ */
+ public PagerAdapter getAdapter() {
+ return mAdapter;
+ }
+
+ void setOnAdapterChangeListener( OnAdapterChangeListener listener ) {
+ mAdapterChangeListener = listener;
+ }
+
+ private int getClientSize() {
+ return ( mOrientation == Orientation.VERTICAL ) ?
+ getMeasuredHeight() - getPaddingTop() - getPaddingBottom() :
+ getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
+ }
+
+ /**
+ * Set the currently selected page. If the ViewPager has already been through its first
+ * layout with its current adapter there will be a smooth animated transition between
+ * the current item and the specified item.
+ *
+ * @param item Item index to select
+ */
+ public void setCurrentItem( int item ) {
mPopulatePending = false;
+ setCurrentItemInternal( item, !mFirstLayout, false );
+ }
+
+ /**
+ * Set the currently selected page.
+ *
+ * @param item Item index to select
+ * @param smoothScroll True to smoothly scroll to the new item, false to transition immediately
+ */
+ public void setCurrentItem( int item, boolean smoothScroll ) {
+ mPopulatePending = false;
+ setCurrentItemInternal( item, smoothScroll, false );
+ }
+
+ public int getCurrentItem() {
+ return mCurItem;
+ }
+
+ void setCurrentItemInternal( int item, boolean smoothScroll, boolean always ) {
+ setCurrentItemInternal( item, smoothScroll, always, 0 );
+ }
+
+ void setCurrentItemInternal( int item, boolean smoothScroll, boolean always, int velocity ) {
+ if ( mAdapter == null || mAdapter.getCount() <= 0 ) {
+ setScrollingCacheEnabled( false );
+ return;
+ }
+ if ( !always && mCurItem == item && mItems.size() != 0 ) {
+ setScrollingCacheEnabled( false );
+ return;
+ }
+
+ if ( item < 0 ) {
+ item = 0;
+ } else if ( item >= mAdapter.getCount() ) {
+ item = mAdapter.getCount() - 1;
+ }
+ final int pageLimit = mOffscreenPageLimit;
+ if ( item > ( mCurItem + pageLimit ) || item < ( mCurItem - pageLimit ) ) {
+ // We are doing a jump by more than one page. To avoid
+ // glitches, we want to keep all current pages in the view
+ // until the scroll ends.
+ for ( int i = 0; i < mItems.size(); i++ ) {
+ mItems.get( i ).scrolling = true;
+ }
+ }
+ final boolean dispatchSelected = mCurItem != item;
+
+ if ( mFirstLayout ) {
+ // We don't have any idea how big we are yet and shouldn't have any pages either.
+ // Just set things up and let the pending layout handle things.
+ mCurItem = item;
+ if ( dispatchSelected && mOnPageChangeListener != null ) {
+ mOnPageChangeListener.onPageSelected( item );
+ }
+ if ( dispatchSelected && mInternalPageChangeListener != null ) {
+ mInternalPageChangeListener.onPageSelected( item );
+ }
+ requestLayout();
+ } else {
+ populate( item );
+ scrollToItem( item, smoothScroll, velocity, dispatchSelected );
+ }
+ }
+
+ private void scrollToItem( int item, boolean smoothScroll, int velocity,
+ boolean dispatchSelected ) {
+ final ItemInfo curInfo = infoForPosition( item );
+ int dest = 0;
+ if ( curInfo != null ) {
+ final int size = getClientSize();
+ dest = ( int ) ( size * Math.max( mFirstOffset,
+ Math.min( curInfo.offset, mLastOffset ) ) );
+ }
+ if ( smoothScroll ) {
+ if ( mOrientation == Orientation.VERTICAL ) {
+ smoothScrollTo( 0, dest, velocity );
+ } else {
+ smoothScrollTo( dest, 0, velocity );
+ }
+ if ( dispatchSelected && mOnPageChangeListener != null ) {
+ mOnPageChangeListener.onPageSelected( item );
+ }
+ if ( dispatchSelected && mInternalPageChangeListener != null ) {
+ mInternalPageChangeListener.onPageSelected( item );
+ }
+ } else {
+ if ( dispatchSelected && mOnPageChangeListener != null ) {
+ mOnPageChangeListener.onPageSelected( item );
+ }
+ if ( dispatchSelected && mInternalPageChangeListener != null ) {
+ mInternalPageChangeListener.onPageSelected( item );
+ }
+ completeScroll( false );
+ if ( mOrientation == Orientation.VERTICAL ) {
+ scrollTo( 0, dest );
+ } else {
+ scrollTo( dest, 0 );
+ }
+ pageScrolled( dest );
+ }
+ }
+
+ /**
+ * Set a listener that will be invoked whenever the page changes or is incrementally
+ * scrolled. See {@link androidx.viewpager.widget.ViewPager.OnPageChangeListener}.
+ *
+ * @param listener Listener to set
+ */
+ public void setOnPageChangeListener( ViewPager.OnPageChangeListener listener ) {
+ mOnPageChangeListener = listener;
+ }
+
+ /**
+ * Set a {@link androidx.viewpager.widget.ViewPager.PageTransformer} that will be called for each
+ * attached page whenever
+ * the scroll position is changed. This allows the application to apply custom property
+ * transformations to each page, overriding the default sliding look and feel.
+ *
+ *
Note: Prior to Android 3.0 the property animation APIs did not exist.
+ * As a result, setting a PageTransformer prior to Android 3.0 (API 11) will have no effect.
+ *
+ * @param reverseDrawingOrder true if the supplied PageTransformer requires page views
+ * to be drawn from last to first instead of first to last.
+ * @param transformer PageTransformer that will modify each page's animation properties
+ */
+ public void setPageTransformer( boolean reverseDrawingOrder,
+ ViewPager.PageTransformer transformer ) {
+ if ( Build.VERSION.SDK_INT >= 11 ) {
+ final boolean hasTransformer = transformer != null;
+ final boolean needsPopulate = hasTransformer != ( mPageTransformer != null );
+ mPageTransformer = transformer;
+ setChildrenDrawingOrderEnabledCompat( hasTransformer );
+ if ( hasTransformer ) {
+ mDrawingOrder = reverseDrawingOrder ? DRAW_ORDER_REVERSE : DRAW_ORDER_FORWARD;
+ } else {
+ mDrawingOrder = DRAW_ORDER_DEFAULT;
+ }
+ if ( needsPopulate ) populate();
+ }
+ }
+
+ void setChildrenDrawingOrderEnabledCompat( boolean enable ) {
+ if ( Build.VERSION.SDK_INT >= 7 ) {
+ if ( mSetChildrenDrawingOrderEnabled == null ) {
+ try {
+ mSetChildrenDrawingOrderEnabled = ViewGroup.class.getDeclaredMethod(
+ "setChildrenDrawingOrderEnabled", new Class[]{Boolean.TYPE} );
+ } catch ( NoSuchMethodException e ) {
+ Log.e( TAG, "Can't find setChildrenDrawingOrderEnabled", e );
+ }
+ }
+ try {
+ mSetChildrenDrawingOrderEnabled.invoke( this, enable );
+ } catch ( Exception e ) {
+ Log.e( TAG, "Error changing children drawing order", e );
+ }
+ }
+ }
+
+ @Override
+ protected int getChildDrawingOrder( int childCount, int i ) {
+ final int index = mDrawingOrder == DRAW_ORDER_REVERSE ? childCount - 1 - i : i;
+ final int result =
+ ( ( LayoutParams ) mDrawingOrderedChildren.get( index ).getLayoutParams() ).childIndex;
+ return result;
+ }
+
+ /**
+ * Set a separate OnPageChangeListener for internal use by the support library.
+ *
+ * @param listener Listener to set
+ * @return The old listener that was set, if any.
+ */
+ ViewPager.OnPageChangeListener setInternalPageChangeListener(
+ ViewPager.OnPageChangeListener listener ) {
+ ViewPager.OnPageChangeListener oldListener = mInternalPageChangeListener;
+ mInternalPageChangeListener = listener;
+ return oldListener;
+ }
+
+ /**
+ * Returns the number of pages that will be retained to either side of the
+ * current page in the view hierarchy in an idle state. Defaults to 1.
+ *
+ * @return How many pages will be kept offscreen on either side
+ * @see #setOffscreenPageLimit(int)
+ */
+ public int getOffscreenPageLimit() {
+ return mOffscreenPageLimit;
+ }
+
+ /**
+ * Set the number of pages that should be retained to either side of the
+ * current page in the view hierarchy in an idle state. Pages beyond this
+ * limit will be recreated from the adapter when needed.
+ *
+ *
This is offered as an optimization. If you know in advance the number
+ * of pages you will need to support or have lazy-loading mechanisms in place
+ * on your pages, tweaking this setting can have benefits in perceived smoothness
+ * of paging animations and interaction. If you have a small number of pages (3-4)
+ * that you can keep active all at once, less time will be spent in layout for
+ * newly created view subtrees as the user pages back and forth.
+ *
+ *
You should keep this limit low, especially if your pages have complex layouts.
+ * This setting defaults to 1.
+ *
+ * @param limit How many pages will be kept offscreen in an idle state.
+ */
+ public void setOffscreenPageLimit( int limit ) {
+ if ( limit < DEFAULT_OFFSCREEN_PAGES ) {
+ Log.w( TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " +
+ DEFAULT_OFFSCREEN_PAGES );
+ limit = DEFAULT_OFFSCREEN_PAGES;
+ }
+ if ( limit != mOffscreenPageLimit ) {
+ mOffscreenPageLimit = limit;
+ populate();
+ }
+ }
+
+ /**
+ * Set the margin between pages.
+ *
+ * @param marginPixels Distance between adjacent pages in pixels
+ * @see #getPageMargin()
+ * @see #setPageMarginDrawable(Drawable)
+ * @see #setPageMarginDrawable(int)
+ */
+ public void setPageMargin( int marginPixels ) {
+ final int oldMargin = mPageMargin;
+ mPageMargin = marginPixels;
+
+ final int size = ( mOrientation == Orientation.VERTICAL ) ? getHeight() : getWidth();
+ recomputeScrollPosition( size, size, marginPixels, oldMargin );
+
+ requestLayout();
+ }
+
+ /**
+ * Return the margin between pages.
+ *
+ * @return The size of the margin in pixels
+ */
+ public int getPageMargin() {
+ return mPageMargin;
+ }
+
+ /**
+ * Set a drawable that will be used to fill the margin between pages.
+ *
+ * @param d Drawable to display between pages
+ */
+ public void setPageMarginDrawable( Drawable d ) {
+ mMarginDrawable = d;
+ if ( d != null ) refreshDrawableState();
+ setWillNotDraw( d == null );
+ invalidate();
+ }
+
+ /**
+ * Set a drawable that will be used to fill the margin between pages.
+ *
+ * @param resId Resource ID of a drawable to display between pages
+ */
+ public void setPageMarginDrawable( int resId ) {
+ setPageMarginDrawable( getContext().getResources().getDrawable( resId ) );
+ }
+
+ @Override
+ protected boolean verifyDrawable( Drawable who ) {
+ return super.verifyDrawable( who ) || who == mMarginDrawable;
+ }
+
+ @Override
+ protected void drawableStateChanged() {
+ super.drawableStateChanged();
+ final Drawable d = mMarginDrawable;
+ if ( d != null && d.isStateful() ) {
+ d.setState( getDrawableState() );
+ }
+ }
+
+ // We want the duration of the page snap animation to be influenced by the distance that
+ // the screen has to travel, however, we don't want this duration to be effected in a
+ // purely linear fashion. Instead, we use this method to moderate the effect that the distance
+ // of travel has on the overall snap duration.
+ float distanceInfluenceForSnapDuration( float f ) {
+ f -= 0.5f; // center the values about 0.
+ f *= 0.3f * Math.PI / 2.0f;
+ return ( float ) Math.sin( f );
+ }
+
+ /**
+ * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
+ *
+ * @param x the number of pixels to scroll by on the X axis
+ * @param y the number of pixels to scroll by on the Y axis
+ */
+ void smoothScrollTo( int x, int y ) {
+ smoothScrollTo( x, y, 0 );
+ }
+
+ /**
+ * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
+ *
+ * @param x the number of pixels to scroll by on the X axis
+ * @param y the number of pixels to scroll by on the Y axis
+ * @param velocity the velocity associated with a fling, if applicable. (0 otherwise)
+ */
+ void smoothScrollTo( int x, int y, int velocity ) {
+ if ( getChildCount() == 0 ) {
+ // Nothing to do.
+ setScrollingCacheEnabled( false );
+ return;
+ }
+ int sx = getScrollX();
+ int sy = getScrollY();
+ int dx = x - sx;
+ int dy = y - sy;
+ if ( dx == 0 && dy == 0 ) {
+ completeScroll( false );
+ populate();
+ setScrollState( SCROLL_STATE_IDLE );
+ return;
+ }
+
+ setScrollingCacheEnabled( true );
+ setScrollState( SCROLL_STATE_SETTLING );
+
+ final int size = getClientSize();
+ final int halfSize = size / 2;
+ final float distanceRatio = Math.min( 1f, 1.0f * Math.abs( dx ) / size );
+ final float distance = halfSize + halfSize *
+ distanceInfluenceForSnapDuration( distanceRatio );
+
+ int duration = 0;
+ velocity = Math.abs( velocity );
+ if ( velocity > 0 ) {
+ duration = 4 * Math.round( 1000 * Math.abs( distance / velocity ) );
+ } else {
+ final float pageSize = size * mAdapter.getPageWidth( mCurItem );
+ final float pageDelta = ( float ) Math.abs( dx ) / ( pageSize + mPageMargin );
+ duration = ( int ) ( ( pageDelta + 1 ) * 100 );
+ }
+ duration = Math.min( duration, MAX_SETTLE_DURATION );
+
+ mScroller.startScroll( sx, sy, dx, dy, duration );
+ ViewCompat.postInvalidateOnAnimation( this );
+ }
+
+ ItemInfo addNewItem( int position, int index ) {
+ ItemInfo ii = new ItemInfo();
+ ii.position = position;
+ ii.object = mAdapter.instantiateItem( this, position );
+ ii.sizeFactor = mAdapter.getPageWidth( position );
+ if ( index < 0 || index >= mItems.size() ) {
+ mItems.add( ii );
+ } else {
+ mItems.add( index, ii );
+ }
+ return ii;
+ }
+
+ void dataSetChanged() {
+ // This method only gets called if our observer is attached, so mAdapter is non-null.
+
+ final int adapterCount = mAdapter.getCount();
+ mExpectedAdapterCount = adapterCount;
+ boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1 &&
+ mItems.size() < adapterCount;
+ int newCurrItem = mCurItem;
+
+ boolean isUpdating = false;
+ for ( int i = 0; i < mItems.size(); i++ ) {
+ final ItemInfo ii = mItems.get( i );
+ final int newPos = mAdapter.getItemPosition( ii.object );
+
+ if ( newPos == PagerAdapter.POSITION_UNCHANGED ) {
+ continue;
+ }
+
+ if ( newPos == PagerAdapter.POSITION_NONE ) {
+ mItems.remove( i );
+ i--;
+
+ if ( !isUpdating ) {
+ mAdapter.startUpdate( this );
+ isUpdating = true;
+ }
+
+ mAdapter.destroyItem( this, ii.position, ii.object );
+ needPopulate = true;
+
+ if ( mCurItem == ii.position ) {
+ // Keep the current item in the valid range
+ newCurrItem = Math.max( 0, Math.min( mCurItem, adapterCount - 1 ) );
+ needPopulate = true;
+ }
+ continue;
+ }
+
+ if ( ii.position != newPos ) {
+ if ( ii.position == mCurItem ) {
+ // Our current item changed position. Follow it.
+ newCurrItem = newPos;
+ }
+
+ ii.position = newPos;
+ needPopulate = true;
+ }
+ }
+
+ if ( isUpdating ) {
+ mAdapter.finishUpdate( this );
+ }
+
+ Collections.sort( mItems, COMPARATOR );
+
+ if ( needPopulate ) {
+ // Reset our known page widths; populate will recompute them.
+ final int childCount = getChildCount();
+ for ( int i = 0; i < childCount; i++ ) {
+ final View child = getChildAt( i );
+ final LayoutParams lp = ( LayoutParams ) child.getLayoutParams();
+ if ( !lp.isDecor ) {
+ lp.heightFactor = 0.f;
+ }
+ }
+
+ setCurrentItemInternal( newCurrItem, false, true );
+ requestLayout();
+ }
+ }
+
+ void populate() {
+ populate( mCurItem );
+ }
+
+ void populate( int newCurrentItem ) {
+ ItemInfo oldCurInfo = null;
+ int focusDirection = View.FOCUS_FORWARD;
+ if ( mCurItem != newCurrentItem ) {
+ focusDirection = mCurItem < newCurrentItem ? View.FOCUS_DOWN : View.FOCUS_UP;
+ oldCurInfo = infoForPosition( mCurItem );
+ mCurItem = newCurrentItem;
+ }
+
+ if ( mAdapter == null ) {
+ sortChildDrawingOrder();
+ return;
+ }
+
+ // Bail now if we are waiting to populate. This is to hold off
+ // on creating views from the time the user releases their finger to
+ // fling to a new position until we have finished the scroll to
+ // that position, avoiding glitches from happening at that point.
+ if ( mPopulatePending ) {
+ if ( DEBUG ) Log.i( TAG, "populate is pending, skipping for now..." );
+ sortChildDrawingOrder();
+ return;
+ }
+
+ // Also, don't populate until we are attached to a window. This is to
+ // avoid trying to populate before we have restored our view hierarchy
+ // state and conflicting with what is restored.
+ if ( getWindowToken() == null ) {
+ return;
+ }
+
+ mAdapter.startUpdate( this );
+
+ final int pageLimit = mOffscreenPageLimit;
+ final int startPos = Math.max( 0, mCurItem - pageLimit );
+ final int N = mAdapter.getCount();
+ final int endPos = Math.min( N - 1, mCurItem + pageLimit );
+
+ if ( N != mExpectedAdapterCount ) {
+ String resName;
+ try {
+ resName = getResources().getResourceName( getId() );
+ } catch ( Resources.NotFoundException e ) {
+ resName = Integer.toHexString( getId() );
+ }
+ throw new IllegalStateException( "The application's PagerAdapter changed the adapter's" +
+ " contents without calling PagerAdapter#notifyDataSetChanged!" +
+ " Expected adapter item count: " + mExpectedAdapterCount + ", found: " + N +
+ " Pager id: " + resName +
+ " Pager class: " + getClass() +
+ " Problematic adapter: " + mAdapter.getClass() );
+ }
+
+ // Locate the currently focused item or add it if needed.
+ int curIndex = -1;
+ ItemInfo curItem = null;
+ for ( curIndex = 0; curIndex < mItems.size(); curIndex++ ) {
+ final ItemInfo ii = mItems.get( curIndex );
+ if ( ii.position >= mCurItem ) {
+ if ( ii.position == mCurItem ) curItem = ii;
+ break;
+ }
+ }
+
+ if ( curItem == null && N > 0 ) {
+ curItem = addNewItem( mCurItem, curIndex );
+ }
+
+ // Fill 3x the available width or up to the number of offscreen
+ // pages requested to either side, whichever is larger.
+ // If we have no current item we have no work to do.
+ if ( curItem != null ) {
+ float extraSizeTopLeft = 0.f;
+ int itemIndex = curIndex - 1;
+ ItemInfo ii = itemIndex >= 0 ? mItems.get( itemIndex ) : null;
+ final int clientSize = getClientSize();
+ final float topLeftSizeNeeded = clientSize <= 0 ? 0 :
+ 2.f - curItem.sizeFactor + ( float ) getPaddingLeft() / ( float ) clientSize;
+ for ( int pos = mCurItem - 1; pos >= 0; pos-- ) {
+ if ( extraSizeTopLeft >= topLeftSizeNeeded && pos < startPos ) {
+ if ( ii == null ) {
+ break;
+ }
+ if ( pos == ii.position && !ii.scrolling ) {
+ mItems.remove( itemIndex );
+ mAdapter.destroyItem( this, pos, ii.object );
+ if ( DEBUG ) {
+ Log.i( TAG, "populate() - destroyItem() with pos: " + pos +
+ " view: " + ( ( View ) ii.object ) );
+ }
+ itemIndex--;
+ curIndex--;
+ ii = itemIndex >= 0 ? mItems.get( itemIndex ) : null;
+ }
+ } else if ( ii != null && pos == ii.position ) {
+ extraSizeTopLeft += ii.sizeFactor;
+ itemIndex--;
+ ii = itemIndex >= 0 ? mItems.get( itemIndex ) : null;
+ } else {
+ ii = addNewItem( pos, itemIndex + 1 );
+ extraSizeTopLeft += ii.sizeFactor;
+ curIndex++;
+ ii = itemIndex >= 0 ? mItems.get( itemIndex ) : null;
+ }
+ }
+
+ float extraSizeBottomRight = curItem.sizeFactor;
+ itemIndex = curIndex + 1;
+ if ( extraSizeBottomRight < 2.f ) {
+ ii = itemIndex < mItems.size() ? mItems.get( itemIndex ) : null;
+ final float bottomRightSizeNeeded = clientSize <= 0 ? 0 :
+ ( float ) getPaddingRight() / ( float ) clientSize + 2.f;
+ for ( int pos = mCurItem + 1; pos < N; pos++ ) {
+ if ( extraSizeBottomRight >= bottomRightSizeNeeded && pos > endPos ) {
+ if ( ii == null ) {
+ break;
+ }
+ if ( pos == ii.position && !ii.scrolling ) {
+ mItems.remove( itemIndex );
+ mAdapter.destroyItem( this, pos, ii.object );
+ if ( DEBUG ) {
+ Log.i( TAG, "populate() - destroyItem() with pos: " + pos +
+ " view: " + ( ( View ) ii.object ) );
+ }
+ ii = itemIndex < mItems.size() ? mItems.get( itemIndex ) : null;
+ }
+ } else if ( ii != null && pos == ii.position ) {
+ extraSizeBottomRight += ii.sizeFactor;
+ itemIndex++;
+ ii = itemIndex < mItems.size() ? mItems.get( itemIndex ) : null;
+ } else {
+ ii = addNewItem( pos, itemIndex );
+ itemIndex++;
+ extraSizeBottomRight += ii.sizeFactor;
+ ii = itemIndex < mItems.size() ? mItems.get( itemIndex ) : null;
+ }
+ }
+ }
+
+ calculatePageOffsets( curItem, curIndex, oldCurInfo );
+ }
+
+ if ( DEBUG ) {
+ Log.i( TAG, "Current page list:" );
+ for ( int i = 0; i < mItems.size(); i++ ) {
+ Log.i( TAG, "#" + i + ": page " + mItems.get( i ).position );
+ }
+ }
+
+ mAdapter.setPrimaryItem( this, mCurItem, curItem != null ? curItem.object : null );
+
+ mAdapter.finishUpdate( this );
+
+ // Check width measurement of current pages and drawing sort order.
+ // Update LayoutParams as needed.
+ final int childCount = getChildCount();
+ if ( mOrientation == Orientation.VERTICAL ) {
+ for ( int i = 0; i < childCount; i++ ) {
+ final View child = getChildAt( i );
+ final LayoutParams lp = ( LayoutParams ) child.getLayoutParams();
+ lp.childIndex = i;
+ if ( !lp.isDecor && lp.heightFactor == 0.f ) {
+ // 0 means requery the adapter for this, it doesn't have a valid width
+ // .
+ final ItemInfo ii = infoForChild( child );
+ if ( ii != null ) {
+ lp.heightFactor = ii.sizeFactor;
+ lp.position = ii.position;
+ }
+ }
+ }
+ } else {
+ for ( int i = 0; i < childCount; i++ ) {
+ final View child = getChildAt( i );
+ final LayoutParams lp = ( LayoutParams ) child.getLayoutParams();
+ lp.childIndex = i;
+ if ( !lp.isDecor && lp.widthFactor == 0.f ) {
+ // 0 means requery the adapter for this, it doesn't have a valid width.
+ final ItemInfo ii = infoForChild( child );
+ if ( ii != null ) {
+ lp.widthFactor = ii.sizeFactor;
+ lp.position = ii.position;
+ }
+ }
+ }
+ }
+ sortChildDrawingOrder();
+
+ if ( hasFocus() ) {
+ View currentFocused = findFocus();
+ ItemInfo ii = currentFocused != null ? infoForAnyChild( currentFocused ) : null;
+ if ( ii == null || ii.position != mCurItem ) {
+ for ( int i = 0; i < getChildCount(); i++ ) {
+ View child = getChildAt( i );
+ ii = infoForChild( child );
+ if ( ii != null && ii.position == mCurItem ) {
+ if ( child.requestFocus( focusDirection ) ) {
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void sortChildDrawingOrder() {
+ if ( mDrawingOrder != DRAW_ORDER_DEFAULT ) {
+ if ( mDrawingOrderedChildren == null ) {
+ mDrawingOrderedChildren = new ArrayList< View >();
+ } else {
+ mDrawingOrderedChildren.clear();
+ }
+ final int childCount = getChildCount();
+ for ( int i = 0; i < childCount; i++ ) {
+ final View child = getChildAt( i );
+ mDrawingOrderedChildren.add( child );
+ }
+ Collections.sort( mDrawingOrderedChildren, sPositionComparator );
+ }
+ }
+
+ private void calculatePageOffsets( ItemInfo curItem, int curIndex, ItemInfo oldCurInfo ) {
+ final int N = mAdapter.getCount();
+ final int size = getClientSize();
+ final float marginOffset = size > 0 ? ( float ) mPageMargin / size : 0;
+ // Fix up offsets for later layout.
+ if ( oldCurInfo != null ) {
+ final int oldCurPosition = oldCurInfo.position;
+ // Base offsets off of oldCurInfo.
+ if ( oldCurPosition < curItem.position ) {
+ int itemIndex = 0;
+ ItemInfo ii = null;
+ float offset = oldCurInfo.offset + oldCurInfo.sizeFactor + marginOffset;
+ for ( int pos = oldCurPosition + 1;
+ pos <= curItem.position && itemIndex < mItems.size(); pos++ ) {
+ ii = mItems.get( itemIndex );
+ while ( pos > ii.position && itemIndex < mItems.size() - 1 ) {
+ itemIndex++;
+ ii = mItems.get( itemIndex );
+ }
+ while ( pos < ii.position ) {
+ // We don't have an item populated for this,
+ // ask the adapter for an offset.
+ offset += mAdapter.getPageWidth( pos ) + marginOffset;
+ pos++;
+ }
+ ii.offset = offset;
+ offset += ii.sizeFactor + marginOffset;
+ }
+ } else if ( oldCurPosition > curItem.position ) {
+ int itemIndex = mItems.size() - 1;
+ ItemInfo ii = null;
+ float offset = oldCurInfo.offset;
+ for ( int pos = oldCurPosition - 1;
+ pos >= curItem.position && itemIndex >= 0; pos-- ) {
+ ii = mItems.get( itemIndex );
+ while ( pos < ii.position && itemIndex > 0 ) {
+ itemIndex--;
+ ii = mItems.get( itemIndex );
+ }
+ while ( pos > ii.position ) {
+ // We don't have an item populated for this,
+ // ask the adapter for an offset.
+ offset -= mAdapter.getPageWidth( pos ) + marginOffset;
+ pos--;
+ }
+ offset -= ii.sizeFactor + marginOffset;
+ ii.offset = offset;
+ }
+ }
+ }
+
+ // Base all offsets off of curItem.
+ final int itemCount = mItems.size();
+ float offset = curItem.offset;
+ int pos = curItem.position - 1;
+ mFirstOffset = curItem.position == 0 ? curItem.offset : -Float.MAX_VALUE;
+ mLastOffset = curItem.position == N - 1 ?
+ curItem.offset + curItem.sizeFactor - 1 : Float.MAX_VALUE;
+ // Previous pages
+ for ( int i = curIndex - 1; i >= 0; i--, pos-- ) {
+ final ItemInfo ii = mItems.get( i );
+ while ( pos > ii.position ) {
+ offset -= mAdapter.getPageWidth( pos-- ) + marginOffset;
+ }
+ offset -= ii.sizeFactor + marginOffset;
+ ii.offset = offset;
+ if ( ii.position == 0 ) mFirstOffset = offset;
+ }
+ offset = curItem.offset + curItem.sizeFactor + marginOffset;
+ pos = curItem.position + 1;
+ // Next pages
+ for ( int i = curIndex + 1; i < itemCount; i++, pos++ ) {
+ final ItemInfo ii = mItems.get( i );
+ while ( pos < ii.position ) {
+ offset += mAdapter.getPageWidth( pos++ ) + marginOffset;
+ }
+ if ( ii.position == N - 1 ) {
+ mLastOffset = offset + ii.sizeFactor - 1;
+ }
+ ii.offset = offset;
+ offset += ii.sizeFactor + marginOffset;
+ }
+
+ mNeedCalculatePageOffsets = false;
+ }
+
+ /**
+ * This is the persistent state that is saved by ViewPager. Only needed
+ * if you are creating a sublass of ViewPager that must save its own
+ * state, in which case it should implement a subclass of this which
+ * contains that state.
+ */
+ public static class ViewPagerSavedState extends BaseSavedState {
+ int position;
+ Parcelable adapterState;
+ ClassLoader loader;
+
+ public ViewPagerSavedState( Parcelable superState ) {
+ super( superState );
+ }
+
+ @Override
+ public void writeToParcel( Parcel out, int flags ) {
+ super.writeToParcel( out, flags );
+ out.writeInt( position );
+ out.writeParcelable( adapterState, flags );
+ }
+
+ @Override
+ public String toString() {
+ return "FragmentPager.SavedState{"
+ + Integer.toHexString( System.identityHashCode( this ) )
+ + " position=" + position + "}";
+ }
+
+ public static final Parcelable.Creator< ViewPagerSavedState > CREATOR
+ = ParcelableCompat.newCreator( new ParcelableCompatCreatorCallbacks< ViewPagerSavedState >() {
+ @Override
+ public ViewPagerSavedState createFromParcel( Parcel in, ClassLoader loader ) {
+ return new ViewPagerSavedState( in, loader );
+ }
+
+ @Override
+ public ViewPagerSavedState[] newArray( int size ) {
+ return new ViewPagerSavedState[size];
+ }
+ } );
+
+ ViewPagerSavedState( Parcel in, ClassLoader loader ) {
+ super( in );
+ if ( loader == null ) {
+ loader = getClass().getClassLoader();
+ }
+ position = in.readInt();
+ adapterState = in.readParcelable( loader );
+ this.loader = loader;
+ }
+ }
+
+ @Override
+ public Parcelable onSaveInstanceState() {
+ Parcelable superState = super.onSaveInstanceState();
+ ViewPagerSavedState ss = new ViewPagerSavedState( superState );
+ ss.position = mCurItem;
+ if ( mAdapter != null ) {
+ ss.adapterState = mAdapter.saveState();
+ }
+ return ss;
+ }
+
+ @Override
+ public void onRestoreInstanceState( Parcelable state ) {
+ if ( !( state instanceof ViewPagerSavedState ) ) {
+ super.onRestoreInstanceState( state );
+ return;
+ }
+
+ ViewPagerSavedState ss = ( ViewPagerSavedState ) state;
+ super.onRestoreInstanceState( ss.getSuperState() );
+
+ if ( mAdapter != null ) {
+ mAdapter.restoreState( ss.adapterState, ss.loader );
+ setCurrentItemInternal( ss.position, false, true );
+ } else {
+ mRestoredCurItem = ss.position;
+ mRestoredAdapterState = ss.adapterState;
+ mRestoredClassLoader = ss.loader;
+ }
+ }
+
+ @Override
+ public void addView( View child, int index, ViewGroup.LayoutParams params ) {
+ if ( !checkLayoutParams( params ) ) {
+ params = generateLayoutParams( params );
+ }
+ final LayoutParams lp = ( LayoutParams ) params;
+ lp.isDecor |= child instanceof Decor;
+ if ( mInLayout ) {
+ if ( lp != null && lp.isDecor ) {
+ throw new IllegalStateException( "Cannot add pager decor view during layout" );
+ }
+ lp.needsMeasure = true;
+ addViewInLayout( child, index, params );
+ } else {
+ super.addView( child, index, params );
+ }
+
+ if ( USE_CACHE ) {
+ if ( child.getVisibility() != GONE ) {
+ child.setDrawingCacheEnabled( mScrollingCacheEnabled );
+ } else {
+ child.setDrawingCacheEnabled( false );
+ }
+ }
+ }
+
+ @Override
+ public void removeView( View view ) {
+ if ( mInLayout ) {
+ removeViewInLayout( view );
+ } else {
+ super.removeView( view );
+ }
+ }
+
+ ItemInfo infoForChild( View child ) {
+ for ( int i = 0; i < mItems.size(); i++ ) {
+ ItemInfo ii = mItems.get( i );
+ if ( mAdapter.isViewFromObject( child, ii.object ) ) {
+ return ii;
+ }
+ }
+ return null;
+ }
+
+ ItemInfo infoForAnyChild( View child ) {
+ ViewParent parent;
+ while ( ( parent = child.getParent() ) != this ) {
+ if ( parent == null || !( parent instanceof View ) ) {
+ return null;
+ }
+ child = ( View ) parent;
+ }
+ return infoForChild( child );
+ }
+
+ ItemInfo infoForPosition( int position ) {
+ for ( int i = 0; i < mItems.size(); i++ ) {
+ ItemInfo ii = mItems.get( i );
+ if ( ii.position == position ) {
+ return ii;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mFirstLayout = true;
+ }
+
+ @Override
+ protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec ) {
+ // For simple implementation, our internal size is always 0.
+ // We depend on the container to specify the layout size of
+ // our view. We can't really know what it is since we will be
+ // adding and removing different arbitrary views and do not
+ // want the layout to change as this happens.
+ setMeasuredDimension( getDefaultSize( 0, widthMeasureSpec ),
+ getDefaultSize( 0, heightMeasureSpec ) );
+
+ final int measuredSize =
+ ( mOrientation == Orientation.VERTICAL ) ? getMeasuredHeight() : getMeasuredWidth();
+ final int maxGutterSize = measuredSize / 10;
+ mGutterSize = Math.min( maxGutterSize, mDefaultGutterSize );
+
+ // Children are just made to fill our space.
+ int childWidthSize;
+ int childHeightSize;
+
+ if ( mOrientation == Orientation.VERTICAL ) {
+ childWidthSize = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
+ childHeightSize = measuredSize - getPaddingTop() - getPaddingBottom();
+ } else {
+ childWidthSize = measuredSize - getPaddingLeft() - getPaddingRight();
+ childHeightSize = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
+ }
+
+ /*
+ * Make sure all children have been properly measured. Decor views first.
+ * Right now we cheat and make this less complicated by assuming decor
+ * views won't intersect. We will pin to edges based on gravity.
+ */
+ int size = getChildCount();
+ for ( int i = 0; i < size; ++i ) {
+ final View child = getChildAt( i );
+ if ( child.getVisibility() != GONE ) {
+ final LayoutParams lp = ( LayoutParams ) child.getLayoutParams();
+ if ( lp != null && lp.isDecor ) {
+ final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+ final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
+ int widthMode = MeasureSpec.AT_MOST;
+ int heightMode = MeasureSpec.AT_MOST;
+ boolean consumeVertical = vgrav == Gravity.TOP || vgrav == Gravity.BOTTOM;
+ boolean consumeHorizontal = hgrav == Gravity.LEFT || hgrav == Gravity.RIGHT;
+
+ if ( consumeVertical ) {
+ widthMode = MeasureSpec.EXACTLY;
+ } else if ( consumeHorizontal ) {
+ heightMode = MeasureSpec.EXACTLY;
+ }
+
+ int widthSize = childWidthSize;
+ int heightSize = childHeightSize;
+ if ( lp.width != LayoutParams.WRAP_CONTENT ) {
+ widthMode = MeasureSpec.EXACTLY;
+ if ( lp.width != LayoutParams.FILL_PARENT ) {
+ widthSize = lp.width;
+ }
+ }
+ if ( lp.height != LayoutParams.WRAP_CONTENT ) {
+ heightMode = MeasureSpec.EXACTLY;
+ if ( lp.height != LayoutParams.FILL_PARENT ) {
+ heightSize = lp.height;
+ }
+ }
+ final int widthSpec = MeasureSpec.makeMeasureSpec( widthSize, widthMode );
+ final int heightSpec = MeasureSpec.makeMeasureSpec( heightSize, heightMode );
+ child.measure( widthSpec, heightSpec );
+
+ if ( consumeVertical ) {
+ childHeightSize -= child.getMeasuredHeight();
+ } else if ( consumeHorizontal ) {
+ childWidthSize -= child.getMeasuredWidth();
+ }
+ }
+ }
+ }
+
+ mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec( childWidthSize, MeasureSpec.EXACTLY );
+ mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec( childHeightSize, MeasureSpec.EXACTLY );
+
+ // Make sure we have created all fragments that we need to have shown.
+ mInLayout = true;
populate();
+ mInLayout = false;
- // Remember where the motion event started
- mLastMotionX = mInitialMotionX = ev.getX();
- mLastMotionY = mInitialMotionY = ev.getY();
- mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
- break;
- }
- case MotionEvent.ACTION_MOVE:
- if (!mIsBeingDragged) {
- final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
- final float y = MotionEventCompat.getY(ev, pointerIndex);
- final float yDiff = Math.abs(y - mLastMotionY);
- final float x = MotionEventCompat.getX(ev, pointerIndex);
- final float xDiff = Math.abs(x - mLastMotionX);
- if (DEBUG) {
- Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
- }
- if (mOrientation == Orientation.VERTICAL) {
- if (yDiff > mTouchSlop && yDiff > xDiff) {
- if (DEBUG) Log.v(TAG, "Starting drag!");
- mIsBeingDragged = true;
- requestParentDisallowInterceptTouchEvent(true);
- mLastMotionY = y - mInitialMotionY > 0 ? mInitialMotionY + mTouchSlop :
- mInitialMotionY - mTouchSlop;
- mLastMotionX = x;
- setScrollState(SCROLL_STATE_DRAGGING);
- setScrollingCacheEnabled(true);
+ // Page views next.
+ size = getChildCount();
+ for ( int i = 0; i < size; ++i ) {
+ final View child = getChildAt( i );
+ if ( child.getVisibility() != GONE ) {
+ if ( DEBUG ) {
+ Log.v( TAG, "Measuring #" + i + " " + child
+ + ": " + mChildWidthMeasureSpec );
+ }
- // Disallow Parent Intercept, just in case
- ViewParent parent = getParent();
- if (parent != null) {
- parent.requestDisallowInterceptTouchEvent(true);
- }
+ final LayoutParams lp = ( LayoutParams ) child.getLayoutParams();
+ if ( lp == null || !lp.isDecor ) {
+ if ( mOrientation == Orientation.VERTICAL ) {
+ final int heightSpec = MeasureSpec.makeMeasureSpec(
+ ( int ) ( childHeightSize * lp.heightFactor ), MeasureSpec.EXACTLY );
+ child.measure( mChildWidthMeasureSpec, heightSpec );
+ } else {
+
+ final int widthSpec = MeasureSpec.makeMeasureSpec(
+ ( int ) ( childWidthSize * lp.widthFactor ), MeasureSpec.EXACTLY );
+ child.measure( widthSpec, mChildHeightMeasureSpec );
+ }
+ }
}
- } else {
- if (xDiff > mTouchSlop && xDiff > yDiff) {
- if (DEBUG) Log.v(TAG, "Starting drag!");
- mIsBeingDragged = true;
- requestParentDisallowInterceptTouchEvent(true);
- mLastMotionX = x - mInitialMotionX > 0 ? mInitialMotionX + mTouchSlop :
- mInitialMotionX - mTouchSlop;
- mLastMotionY = y;
- setScrollState(SCROLL_STATE_DRAGGING);
- setScrollingCacheEnabled(true);
+ }
+ }
- // Disallow Parent Intercept, just in case
- ViewParent parent = getParent();
- if (parent != null) {
- parent.requestDisallowInterceptTouchEvent(true);
- }
+ @Override
+ protected void onSizeChanged( int w, int h, int oldw, int oldh ) {
+ super.onSizeChanged( w, h, oldw, oldh );
+
+ // Make sure scroll position is set correctly.
+ if ( mOrientation == Orientation.VERTICAL ) {
+ if ( h != oldh ) {
+ recomputeScrollPosition( h, oldh, mPageMargin, mPageMargin );
+ }
+ } else {
+ if ( w != oldw ) {
+ recomputeScrollPosition( w, oldw, mPageMargin, mPageMargin );
}
- }
}
- // Not else! Note that mIsBeingDragged can be set above.
- if (mIsBeingDragged) {
- // Scroll to follow the motion event
- final int activePointerIndex = MotionEventCompat.findPointerIndex(
- ev, mActivePointerId);
- if (mOrientation == Orientation.VERTICAL) {
- final float y = MotionEventCompat.getY(ev, activePointerIndex);
- needsInvalidate |= performDrag(y);
- } else {
- final float x = MotionEventCompat.getX(ev, activePointerIndex);
- needsInvalidate |= performDrag(x);
- }
+ }
+
+ private void recomputeScrollPosition( int size, int oldSize, int margin, int oldMargin ) {
+ if ( mOrientation == Orientation.VERTICAL ) {
+ if ( oldSize > 0 && !mItems.isEmpty() ) {
+ final int heightWithMargin = size - getPaddingTop() - getPaddingBottom() + margin;
+ final int oldHeightWithMargin = oldSize - getPaddingTop() - getPaddingBottom()
+ + oldMargin;
+ final int ypos = getScrollY();
+ final float pageOffset = ( float ) ypos / oldHeightWithMargin;
+ final int newOffsetPixels = ( int ) ( pageOffset * heightWithMargin );
+
+ scrollTo( getScrollX(), newOffsetPixels );
+ if ( !mScroller.isFinished() ) {
+ // We now return to your regularly scheduled scroll, already in progress.
+ final int newDuration = mScroller.getDuration() - mScroller.timePassed();
+ ItemInfo targetInfo = infoForPosition( mCurItem );
+ mScroller.startScroll( 0, newOffsetPixels,
+ 0, ( int ) ( targetInfo.offset * size ), newDuration );
+ }
+ } else {
+ final ItemInfo ii = infoForPosition( mCurItem );
+ final float scrollOffset = ii != null ? Math.min( ii.offset, mLastOffset ) : 0;
+ final int scrollPos = ( int ) ( scrollOffset *
+ ( size - getPaddingTop() - getPaddingBottom() ) );
+ if ( scrollPos != getScrollY() ) {
+ completeScroll( false );
+ scrollTo( getScrollX(), scrollPos );
+ }
+ }
+ } else {
+ if ( oldSize > 0 && !mItems.isEmpty() ) {
+ final int widthWithMargin = size - getPaddingLeft() - getPaddingRight() + margin;
+ final int oldWidthWithMargin = oldSize - getPaddingLeft() - getPaddingRight()
+ + oldMargin;
+ final int xpos = getScrollX();
+ final float pageOffset = ( float ) xpos / oldWidthWithMargin;
+ final int newOffsetPixels = ( int ) ( pageOffset * widthWithMargin );
+
+ scrollTo( newOffsetPixels, getScrollY() );
+ if ( !mScroller.isFinished() ) {
+ // We now return to your regularly scheduled scroll, already in progress.
+ final int newDuration = mScroller.getDuration() - mScroller.timePassed();
+ ItemInfo targetInfo = infoForPosition( mCurItem );
+ mScroller.startScroll( newOffsetPixels, 0,
+ ( int ) ( targetInfo.offset * size ), 0, newDuration );
+ }
+ } else {
+ final ItemInfo ii = infoForPosition( mCurItem );
+ final float scrollOffset = ii != null ? Math.min( ii.offset, mLastOffset ) : 0;
+ final int scrollPos = ( int ) ( scrollOffset *
+ ( size - getPaddingLeft() - getPaddingRight() ) );
+ if ( scrollPos != getScrollX() ) {
+ completeScroll( false );
+ scrollTo( scrollPos, getScrollY() );
+ }
+ }
}
- break;
- case MotionEvent.ACTION_UP:
- if (mIsBeingDragged) {
- int currentPage;
- int initialVelocity;
- int totalDelta;
- float pageOffset;
- if (mOrientation == Orientation.VERTICAL) {
- final VelocityTracker velocityTracker = mVelocityTracker;
- velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
- initialVelocity = (int) VelocityTrackerCompat.getYVelocity(
- velocityTracker, mActivePointerId);
- mPopulatePending = true;
+ }
+
+ @Override
+ protected void onLayout( boolean changed, int l, int t, int r, int b ) {
+ final int count = getChildCount();
+ int width = r - l;
+ int height = b - t;
+ int paddingLeft = getPaddingLeft();
+ int paddingTop = getPaddingTop();
+ int paddingRight = getPaddingRight();
+ int paddingBottom = getPaddingBottom();
+ final int scroll = ( mOrientation == Orientation.VERTICAL ) ? getScrollY() : getScrollX();
+
+ int decorCount = 0;
+
+ // First pass - decor views. We need to do this in two passes so that
+ // we have the proper offsets for non-decor views later.
+ for ( int i = 0; i < count; i++ ) {
+ final View child = getChildAt( i );
+ if ( child.getVisibility() != GONE ) {
+ final LayoutParams lp = ( LayoutParams ) child.getLayoutParams();
+ int childLeft = 0;
+ int childTop = 0;
+ if ( lp.isDecor ) {
+ final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+ final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
+ switch ( hgrav ) {
+ default:
+ childLeft = paddingLeft;
+ break;
+ case Gravity.LEFT:
+ childLeft = paddingLeft;
+ paddingLeft += child.getMeasuredWidth();
+ break;
+ case Gravity.CENTER_HORIZONTAL:
+ childLeft = Math.max( ( width - child.getMeasuredWidth() ) / 2,
+ paddingLeft );
+ break;
+ case Gravity.RIGHT:
+ childLeft = width - paddingRight - child.getMeasuredWidth();
+ paddingRight += child.getMeasuredWidth();
+ break;
+ }
+ switch ( vgrav ) {
+ default:
+ childTop = paddingTop;
+ break;
+ case Gravity.TOP:
+ childTop = paddingTop;
+ paddingTop += child.getMeasuredHeight();
+ break;
+ case Gravity.CENTER_VERTICAL:
+ childTop = Math.max( ( height - child.getMeasuredHeight() ) / 2,
+ paddingTop );
+ break;
+ case Gravity.BOTTOM:
+ childTop = height - paddingBottom - child.getMeasuredHeight();
+ paddingBottom += child.getMeasuredHeight();
+ break;
+ }
+ if ( mOrientation == Orientation.VERTICAL ) {
+ childTop += scroll;
+ } else {
+ childLeft += scroll;
+ }
+ child.layout( childLeft, childTop,
+ childLeft + child.getMeasuredWidth(),
+ childTop + child.getMeasuredHeight() );
+ decorCount++;
+ }
+ }
+ }
+
+ final int childSize =
+ ( mOrientation == Orientation.VERTICAL ) ? height - paddingTop - paddingBottom
+ : width - paddingLeft - paddingRight;
+ // Page views. Do this once we have the right padding offsets from above.
+ for ( int i = 0; i < count; i++ ) {
+ final View child = getChildAt( i );
+ if ( child.getVisibility() != GONE ) {
+ final LayoutParams lp = ( LayoutParams ) child.getLayoutParams();
+ ItemInfo ii;
+ if ( !lp.isDecor && ( ii = infoForChild( child ) ) != null ) {
+ int topLeftoff = ( int ) ( childSize * ii.offset );
+ int childLeft;
+ int childTop;
+ if ( mOrientation == Orientation.VERTICAL ) {
+ childLeft = paddingLeft;
+ childTop = paddingTop + topLeftoff;
+ if ( lp.needsMeasure ) {
+ // This was added during layout and needs measurement.
+ // Do it now that we know what we're working with.
+ lp.needsMeasure = false;
+ final int widthSpec = MeasureSpec.makeMeasureSpec(
+ ( int ) ( width - paddingLeft - paddingRight ),
+ MeasureSpec.EXACTLY );
+ final int heightSpec = MeasureSpec.makeMeasureSpec(
+ ( int ) ( childSize * lp.heightFactor ),
+ MeasureSpec.EXACTLY );
+ child.measure( widthSpec, heightSpec );
+ }
+ } else {
+ childLeft = paddingLeft + topLeftoff;
+ childTop = paddingTop;
+ if ( lp.needsMeasure ) {
+ // This was added during layout and needs measurement.
+ // Do it now that we know what we're working with.
+ lp.needsMeasure = false;
+ final int widthSpec = MeasureSpec.makeMeasureSpec(
+ ( int ) ( childSize * lp.widthFactor ),
+ MeasureSpec.EXACTLY );
+ final int heightSpec = MeasureSpec.makeMeasureSpec(
+ ( int ) ( height - paddingTop - paddingBottom ),
+ MeasureSpec.EXACTLY );
+ child.measure( widthSpec, heightSpec );
+ }
+ }
+ if ( DEBUG ) {
+ Log.v( TAG, "Positioning #" + i + " " + child + " f=" + ii.object
+ + ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth()
+ + "x" + child.getMeasuredHeight() );
+ }
+ child.layout( childLeft, childTop,
+ childLeft + child.getMeasuredWidth(),
+ childTop + child.getMeasuredHeight() );
+ }
+ }
+ }
+ mTopLeftPageBounds = ( mOrientation == Orientation.VERTICAL ) ? paddingLeft : paddingTop;
+ mBottomRightPageBounds =
+ ( mOrientation == Orientation.VERTICAL ) ? width - paddingRight : height - paddingBottom;
+ mDecorChildCount = decorCount;
+
+ if ( mFirstLayout ) {
+ scrollToItem( mCurItem, false, 0, false );
+ }
+ mFirstLayout = false;
+ }
+
+ @Override
+ public void computeScroll() {
+ if ( !mScroller.isFinished() && mScroller.computeScrollOffset() ) {
+ int oldX = getScrollX();
+ int oldY = getScrollY();
+ int x = mScroller.getCurrX();
+ int y = mScroller.getCurrY();
+
+ if ( oldX != x || oldY != y ) {
+ scrollTo( x, y );
+ if ( mOrientation == Orientation.VERTICAL ) {
+ if ( !pageScrolled( y ) ) {
+ mScroller.abortAnimation();
+ scrollTo( x, 0 );
+ }
+ } else {
+ if ( !pageScrolled( x ) ) {
+ mScroller.abortAnimation();
+ scrollTo( 0, y );
+ }
+ }
+ }
+
+ // Keep on drawing until the animation has finished.
+ ViewCompat.postInvalidateOnAnimation( this );
+ return;
+ }
+
+ // Done with scroll, clean up state.
+ completeScroll( true );
+ }
+
+ private boolean pageScrolled( int pos ) {
+ if ( mItems.size() == 0 ) {
+ mCalledSuper = false;
+ onPageScrolled( 0, 0, 0 );
+ if ( !mCalledSuper ) {
+ throw new IllegalStateException(
+ "onPageScrolled did not call superclass implementation" );
+ }
+ return false;
+ }
+ final ItemInfo ii = infoForCurrentScrollPosition();
+ final int size = getClientSize();
+ final int sizeWithMargin = size + mPageMargin;
+ final float marginOffset = ( float ) mPageMargin / size;
+ final int currentPage = ii.position;
+ final float pageOffset = ( ( ( float ) pos / size ) - ii.offset ) / ( ii.sizeFactor + marginOffset );
+ final int offsetPixels = ( int ) ( pageOffset * sizeWithMargin );
+
+ mCalledSuper = false;
+ onPageScrolled( currentPage, pageOffset, offsetPixels );
+ if ( !mCalledSuper ) {
+ throw new IllegalStateException(
+ "onPageScrolled did not call superclass implementation" );
+ }
+ return true;
+ }
+
+ /**
+ * This method will be invoked when the current page is scrolled, either as part
+ * of a programmatically initiated smooth scroll or a user initiated touch scroll.
+ * If you override this method you must call through to the superclass implementation
+ * (e.g. super.onPageScrolled(position, offset, offsetPixels)) before onPageScrolled
+ * returns.
+ *
+ * @param position Position index of the first page currently being displayed.
+ * Page position+1 will be visible if positionOffset is nonzero.
+ * @param offset Value from [0, 1) indicating the offset from the page at position.
+ * @param offsetPixels Value in pixels indicating the offset from position.
+ */
+ protected void onPageScrolled( int position, float offset, int offsetPixels ) {
+ // Offset any decor views if needed - keep them on-screen at all times.
+ if ( mDecorChildCount > 0 ) {
+ if ( mOrientation == Orientation.VERTICAL ) {
+ final int scrollY = getScrollY();
+ int paddingTop = getPaddingTop();
+ int paddingBottom = getPaddingBottom();
+ final int height = getHeight();
+ final int childCount = getChildCount();
+ for ( int i = 0; i < childCount; i++ ) {
+ final View child = getChildAt( i );
+ final LayoutParams lp = ( LayoutParams ) child.getLayoutParams();
+ if ( !lp.isDecor ) continue;
+
+ final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
+ int childTop = 0;
+ switch ( vgrav ) {
+ default:
+ childTop = paddingTop;
+ break;
+ case Gravity.TOP:
+ childTop = paddingTop;
+ paddingTop += child.getHeight();
+ break;
+ case Gravity.CENTER_VERTICAL:
+ childTop = Math.max( ( height - child.getMeasuredHeight() ) / 2,
+ paddingTop );
+ break;
+ case Gravity.BOTTOM:
+ childTop = height - paddingBottom - child.getMeasuredHeight();
+ paddingBottom += child.getMeasuredHeight();
+ break;
+ }
+ childTop += scrollY;
+
+ final int childOffset = childTop - child.getTop();
+ if ( childOffset != 0 ) {
+ child.offsetTopAndBottom( childOffset );
+ }
+ }
+ } else {
+ final int scrollX = getScrollX();
+ int paddingLeft = getPaddingLeft();
+ int paddingRight = getPaddingRight();
+ final int width = getWidth();
+ final int childCount = getChildCount();
+ for ( int i = 0; i < childCount; i++ ) {
+ final View child = getChildAt( i );
+ final LayoutParams lp = ( LayoutParams ) child.getLayoutParams();
+ if ( !lp.isDecor ) continue;
+
+ final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+ int childLeft = 0;
+ switch ( hgrav ) {
+ default:
+ childLeft = paddingLeft;
+ break;
+ case Gravity.LEFT:
+ childLeft = paddingLeft;
+ paddingLeft += child.getWidth();
+ break;
+ case Gravity.CENTER_HORIZONTAL:
+ childLeft = Math.max( ( width - child.getMeasuredWidth() ) / 2,
+ paddingLeft );
+ break;
+ case Gravity.RIGHT:
+ childLeft = width - paddingRight - child.getMeasuredWidth();
+ paddingRight += child.getMeasuredWidth();
+ break;
+ }
+ childLeft += scrollX;
+
+ final int childOffset = childLeft - child.getLeft();
+ if ( childOffset != 0 ) {
+ child.offsetLeftAndRight( childOffset );
+ }
+ }
+ }
+ }
+
+ if ( mOnPageChangeListener != null ) {
+ mOnPageChangeListener.onPageScrolled( position, offset, offsetPixels );
+ }
+ if ( mInternalPageChangeListener != null ) {
+ mInternalPageChangeListener.onPageScrolled( position, offset, offsetPixels );
+ }
+
+ if ( mPageTransformer != null ) {
+ final int scroll = ( mOrientation == Orientation.VERTICAL ) ? getScrollY() : getScrollX();
+ final int childCount = getChildCount();
+ for ( int i = 0; i < childCount; i++ ) {
+ final View child = getChildAt( i );
+ final LayoutParams lp = ( LayoutParams ) child.getLayoutParams();
+
+ if ( lp.isDecor ) continue;
+
+ final float transformPos =
+ ( float ) ( ( ( mOrientation == Orientation.VERTICAL ) ? child.getTop() : child.getLeft() )
+ - scroll ) / getClientSize();
+ mPageTransformer.transformPage( child, transformPos );
+ }
+ }
+
+ mCalledSuper = true;
+ }
+
+ private void completeScroll( boolean postEvents ) {
+ boolean needPopulate = mScrollState == SCROLL_STATE_SETTLING;
+ if ( needPopulate ) {
+ // Done with scroll, no longer want to cache view drawing.
+ setScrollingCacheEnabled( false );
+ mScroller.abortAnimation();
+ int oldX = getScrollX();
+ int oldY = getScrollY();
+ int x = mScroller.getCurrX();
+ int y = mScroller.getCurrY();
+ if ( oldX != x || oldY != y ) {
+ scrollTo( x, y );
+ }
+ }
+ mPopulatePending = false;
+ for ( int i = 0; i < mItems.size(); i++ ) {
+ ItemInfo ii = mItems.get( i );
+ if ( ii.scrolling ) {
+ needPopulate = true;
+ ii.scrolling = false;
+ }
+ }
+ if ( needPopulate ) {
+ if ( postEvents ) {
+ ViewCompat.postOnAnimation( this, mEndScrollRunnable );
+ } else {
+ mEndScrollRunnable.run();
+ }
+ }
+ }
+
+ private boolean isGutterDrag( float axis, float dAxis ) {
+ return ( axis < mGutterSize && dAxis > 0 ) || ( axis
+ > ( mOrientation == Orientation.VERTICAL ? getHeight() : getWidth() ) - mGutterSize
+ && dAxis < 0 );
+ }
+
+ private void enableLayers( boolean enable ) {
+ final int childCount = getChildCount();
+ for ( int i = 0; i < childCount; i++ ) {
+ final int layerType = enable ?
+ ViewCompat.LAYER_TYPE_HARDWARE : ViewCompat.LAYER_TYPE_NONE;
+ ViewCompat.setLayerType( getChildAt( i ), layerType, null );
+ }
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent( MotionEvent ev ) {
+ /*
+ * This method JUST determines whether we want to intercept the motion.
+ * If we return true, onMotionEvent will be called and we do the actual
+ * scrolling there.
+ */
+
+ final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
+
+ // Always take care of the touch gesture being complete.
+ if ( action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP ) {
+ // Release the drag.
+ if ( DEBUG ) Log.v( TAG, "Intercept done!" );
+ mIsBeingDragged = false;
+ mIsUnableToDrag = false;
+ mActivePointerId = INVALID_POINTER;
+ if ( mVelocityTracker != null ) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ return false;
+ }
+
+ // Nothing more to do here if we have decided whether or not we
+ // are dragging.
+ if ( action != MotionEvent.ACTION_DOWN ) {
+ if ( mIsBeingDragged ) {
+ if ( DEBUG ) Log.v( TAG, "Intercept returning true!" );
+ return true;
+ }
+ if ( mIsUnableToDrag ) {
+ if ( DEBUG ) Log.v( TAG, "Intercept returning false!" );
+ return false;
+ }
+ }
+
+ switch ( action ) {
+ case MotionEvent.ACTION_MOVE: {
+ /*
+ * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
+ * whether the user has moved far enough from his original down touch.
+ */
+
+ /*
+ * Locally do absolute value. mLastMotionY is set to the y value
+ * of the down event.
+ */
+ final int activePointerId = mActivePointerId;
+ if ( activePointerId == INVALID_POINTER ) {
+ // If we don't have a valid id, the touch down wasn't on content.
+ break;
+ }
+
+ final int pointerIndex = MotionEventCompat.findPointerIndex( ev, activePointerId );
+ if ( mOrientation == Orientation.VERTICAL ) {
+ final float y = MotionEventCompat.getY( ev, pointerIndex );
+ final float dy = y - mLastMotionY;
+ final float yDiff = Math.abs( dy );
+ final float x = MotionEventCompat.getX( ev, pointerIndex );
+ final float xDiff = Math.abs( x - mInitialMotionX );
+ if ( DEBUG ) {
+ Log.v( TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff );
+ }
+
+ if ( dy != 0 && !isGutterDrag( mLastMotionY, dy ) &&
+ canScroll( this, false, ( int ) dy, ( int ) x, ( int ) y ) ) {
+ // Nested view has scrollable area under this point. Let it be handled there.
+ mLastMotionX = x;
+ mLastMotionY = y;
+ mIsUnableToDrag = true;
+ return false;
+ }
+ if ( yDiff > mTouchSlop && yDiff * 0.5f > xDiff ) {
+ if ( DEBUG ) Log.v( TAG, "Starting drag!" );
+ mIsBeingDragged = true;
+ requestParentDisallowInterceptTouchEvent( true );
+ setScrollState( SCROLL_STATE_DRAGGING );
+ mLastMotionY = dy > 0 ? mInitialMotionY + mTouchSlop :
+ mInitialMotionY - mTouchSlop;
+ mLastMotionX = x;
+ setScrollingCacheEnabled( true );
+ } else if ( xDiff > mTouchSlop ) {
+ // The finger has moved enough in the vertical
+ // direction to be counted as a drag... abort
+ // any attempt to drag horizontally, to work correctly
+ // with children that have scrolling containers.
+ if ( DEBUG ) Log.v( TAG, "Starting unable to drag!" );
+ mIsUnableToDrag = true;
+ }
+ if ( mIsBeingDragged ) {
+ // Scroll to follow the motion event
+ if ( performDrag( y ) ) {
+ ViewCompat.postInvalidateOnAnimation( this );
+ }
+ }
+ } else {
+ final float x = MotionEventCompat.getX( ev, pointerIndex );
+ final float dx = x - mLastMotionX;
+ final float xDiff = Math.abs( dx );
+ final float y = MotionEventCompat.getY( ev, pointerIndex );
+ final float yDiff = Math.abs( y - mInitialMotionY );
+ if ( DEBUG ) {
+ Log.v( TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff );
+ }
+
+ if ( dx != 0 && !isGutterDrag( mLastMotionX, dx ) &&
+ canScroll( this, false, ( int ) dx, ( int ) x, ( int ) y ) ) {
+ // Nested view has scrollable area under this point. Let it be handled there.
+ mLastMotionX = x;
+ mLastMotionY = y;
+ mIsUnableToDrag = true;
+ return false;
+ }
+ if ( xDiff > mTouchSlop && xDiff * 0.5f > yDiff ) {
+ if ( DEBUG ) Log.v( TAG, "Starting drag!" );
+ mIsBeingDragged = true;
+ requestParentDisallowInterceptTouchEvent( true );
+ setScrollState( SCROLL_STATE_DRAGGING );
+ mLastMotionX = dx > 0 ? mInitialMotionX + mTouchSlop :
+ mInitialMotionX - mTouchSlop;
+ mLastMotionY = y;
+ setScrollingCacheEnabled( true );
+ } else if ( yDiff > mTouchSlop ) {
+ // The finger has moved enough in the vertical
+ // direction to be counted as a drag... abort
+ // any attempt to drag horizontally, to work correctly
+ // with children that have scrolling containers.
+ if ( DEBUG ) Log.v( TAG, "Starting unable to drag!" );
+ mIsUnableToDrag = true;
+ }
+ if ( mIsBeingDragged ) {
+ // Scroll to follow the motion event
+ if ( performDrag( x ) ) {
+ ViewCompat.postInvalidateOnAnimation( this );
+ }
+ }
+ }
+ break;
+ }
+
+ case MotionEvent.ACTION_DOWN: {
+ /*
+ * Remember location of down touch.
+ * ACTION_DOWN always refers to pointer index 0.
+ */
+ mLastMotionX = mInitialMotionX = ev.getX();
+ mLastMotionY = mInitialMotionY = ev.getY();
+ mActivePointerId = MotionEventCompat.getPointerId( ev, 0 );
+ mIsUnableToDrag = false;
+
+ mScroller.computeScrollOffset();
+ if ( mOrientation == Orientation.VERTICAL ) {
+ if ( mScrollState == SCROLL_STATE_SETTLING &&
+ Math.abs( mScroller.getFinalY() - mScroller.getCurrY() ) > mCloseEnough ) {
+ // Let the user 'catch' the pager as it animates.
+ mScroller.abortAnimation();
+ mPopulatePending = false;
+ populate();
+ mIsBeingDragged = true;
+ requestParentDisallowInterceptTouchEvent( true );
+ setScrollState( SCROLL_STATE_DRAGGING );
+ } else {
+ completeScroll( false );
+ mIsBeingDragged = false;
+ }
+ } else {
+ if ( mScrollState == SCROLL_STATE_SETTLING &&
+ Math.abs( mScroller.getFinalX() - mScroller.getCurrX() ) > mCloseEnough ) {
+ // Let the user 'catch' the pager as it animates.
+ mScroller.abortAnimation();
+ mPopulatePending = false;
+ populate();
+ mIsBeingDragged = true;
+ requestParentDisallowInterceptTouchEvent( true );
+ setScrollState( SCROLL_STATE_DRAGGING );
+ } else {
+ completeScroll( false );
+ mIsBeingDragged = false;
+ }
+ }
+
+ if ( DEBUG ) {
+ Log.v( TAG, "Down at " + mLastMotionX + "," + mLastMotionY
+ + " mIsBeingDragged=" + mIsBeingDragged
+ + "mIsUnableToDrag=" + mIsUnableToDrag );
+ }
+ break;
+ }
+
+ case MotionEventCompat.ACTION_POINTER_UP:
+ onSecondaryPointerUp( ev );
+ break;
+ }
+
+ if ( mVelocityTracker == null ) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement( ev );
+
+ /*
+ * The only time we want to intercept motion events is if we are in the
+ * drag mode.
+ */
+ return mIsBeingDragged;
+ }
+
+ @Override
+ public boolean onTouchEvent( MotionEvent ev ) {
+ if ( mFakeDragging ) {
+ // A fake drag is in progress already, ignore this real one
+ // but still eat the touch events.
+ // (It is likely that the user is multi-touching the screen.)
+ return true;
+ }
+
+ if ( ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0 ) {
+ // Don't handle edge touches immediately -- they may actually belong to one of our
+ // descendants.
+ return false;
+ }
+
+ if ( mAdapter == null || mAdapter.getCount() == 0 ) {
+ // Nothing to present or scroll; nothing to touch.
+ return false;
+ }
+
+ if ( mVelocityTracker == null ) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement( ev );
+
+ final int action = ev.getAction();
+ boolean needsInvalidate = false;
+
+ switch ( action & MotionEventCompat.ACTION_MASK ) {
+ case MotionEvent.ACTION_DOWN: {
+ mScroller.abortAnimation();
+ mPopulatePending = false;
+ populate();
+
+ // Remember where the motion event started
+ mLastMotionX = mInitialMotionX = ev.getX();
+ mLastMotionY = mInitialMotionY = ev.getY();
+ mActivePointerId = MotionEventCompat.getPointerId( ev, 0 );
+ break;
+ }
+ case MotionEvent.ACTION_MOVE:
+ if ( !mIsBeingDragged ) {
+ final int pointerIndex = MotionEventCompat.findPointerIndex( ev, mActivePointerId );
+ final float y = MotionEventCompat.getY( ev, pointerIndex );
+ final float yDiff = Math.abs( y - mLastMotionY );
+ final float x = MotionEventCompat.getX( ev, pointerIndex );
+ final float xDiff = Math.abs( x - mLastMotionX );
+ if ( DEBUG ) {
+ Log.v( TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff );
+ }
+ if ( mOrientation == Orientation.VERTICAL ) {
+ if ( yDiff > mTouchSlop && yDiff > xDiff ) {
+ if ( DEBUG ) Log.v( TAG, "Starting drag!" );
+ mIsBeingDragged = true;
+ requestParentDisallowInterceptTouchEvent( true );
+ mLastMotionY = y - mInitialMotionY > 0 ? mInitialMotionY + mTouchSlop :
+ mInitialMotionY - mTouchSlop;
+ mLastMotionX = x;
+ setScrollState( SCROLL_STATE_DRAGGING );
+ setScrollingCacheEnabled( true );
+
+ // Disallow Parent Intercept, just in case
+ ViewParent parent = getParent();
+ if ( parent != null ) {
+ parent.requestDisallowInterceptTouchEvent( true );
+ }
+ }
+ } else {
+ if ( xDiff > mTouchSlop && xDiff > yDiff ) {
+ if ( DEBUG ) Log.v( TAG, "Starting drag!" );
+ mIsBeingDragged = true;
+ requestParentDisallowInterceptTouchEvent( true );
+ mLastMotionX = x - mInitialMotionX > 0 ? mInitialMotionX + mTouchSlop :
+ mInitialMotionX - mTouchSlop;
+ mLastMotionY = y;
+ setScrollState( SCROLL_STATE_DRAGGING );
+ setScrollingCacheEnabled( true );
+
+ // Disallow Parent Intercept, just in case
+ ViewParent parent = getParent();
+ if ( parent != null ) {
+ parent.requestDisallowInterceptTouchEvent( true );
+ }
+ }
+ }
+ }
+ // Not else! Note that mIsBeingDragged can be set above.
+ if ( mIsBeingDragged ) {
+ // Scroll to follow the motion event
+ final int activePointerIndex = MotionEventCompat.findPointerIndex(
+ ev, mActivePointerId );
+ if ( mOrientation == Orientation.VERTICAL ) {
+ final float y = MotionEventCompat.getY( ev, activePointerIndex );
+ needsInvalidate |= performDrag( y );
+ } else {
+ final float x = MotionEventCompat.getX( ev, activePointerIndex );
+ needsInvalidate |= performDrag( x );
+ }
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ if ( mIsBeingDragged ) {
+ int currentPage;
+ int initialVelocity;
+ int totalDelta;
+ float pageOffset;
+ if ( mOrientation == Orientation.VERTICAL ) {
+ final VelocityTracker velocityTracker = mVelocityTracker;
+ velocityTracker.computeCurrentVelocity( 1000, mMaximumVelocity );
+ initialVelocity = ( int ) VelocityTrackerCompat.getYVelocity(
+ velocityTracker, mActivePointerId );
+ mPopulatePending = true;
+ final int height = getClientSize();
+ final int scrollY = getScrollY();
+ final ItemInfo ii = infoForCurrentScrollPosition();
+ currentPage = ii.position;
+ pageOffset = ( ( ( float ) scrollY / height ) - ii.offset ) / ii.sizeFactor;
+ final int activePointerIndex =
+ MotionEventCompat.findPointerIndex( ev, mActivePointerId );
+ final float y = MotionEventCompat.getY( ev, activePointerIndex );
+ totalDelta = ( int ) ( y - mInitialMotionY );
+ } else {
+ final VelocityTracker velocityTracker = mVelocityTracker;
+ velocityTracker.computeCurrentVelocity( 1000, mMaximumVelocity );
+ initialVelocity = ( int ) VelocityTrackerCompat.getXVelocity(
+ velocityTracker, mActivePointerId );
+ mPopulatePending = true;
+ final int width = getClientSize();
+ final int scrollX = getScrollX();
+ final ItemInfo ii = infoForCurrentScrollPosition();
+ currentPage = ii.position;
+ pageOffset = ( ( ( float ) scrollX / width ) - ii.offset ) / ii.sizeFactor;
+ final int activePointerIndex =
+ MotionEventCompat.findPointerIndex( ev, mActivePointerId );
+ final float x = MotionEventCompat.getX( ev, activePointerIndex );
+ totalDelta = ( int ) ( x - mInitialMotionX );
+ }
+ int nextPage = determineTargetPage( currentPage, pageOffset, initialVelocity,
+ totalDelta );
+ setCurrentItemInternal( nextPage, true, true, initialVelocity );
+
+ mActivePointerId = INVALID_POINTER;
+ endDrag();
+ needsInvalidate = mTopLeftEdge.onRelease() | mRightBottomEdge.onRelease();
+ }
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ if ( mIsBeingDragged ) {
+ scrollToItem( mCurItem, true, 0, false );
+ mActivePointerId = INVALID_POINTER;
+ endDrag();
+ needsInvalidate = mTopLeftEdge.onRelease() | mRightBottomEdge.onRelease();
+ }
+ break;
+ case MotionEventCompat.ACTION_POINTER_DOWN: {
+ int index;
+ if ( mOrientation == Orientation.VERTICAL ) {
+ index = MotionEventCompat.getActionIndex( ev );
+ final float y = MotionEventCompat.getY( ev, index );
+ mLastMotionY = y;
+ } else {
+ index = MotionEventCompat.getActionIndex( ev );
+ final float x = MotionEventCompat.getX( ev, index );
+ mLastMotionX = x;
+ }
+ mActivePointerId = MotionEventCompat.getPointerId( ev, index );
+ break;
+ }
+ case MotionEventCompat.ACTION_POINTER_UP:
+ onSecondaryPointerUp( ev );
+ if ( mOrientation == Orientation.VERTICAL ) {
+ mLastMotionY = MotionEventCompat.getY( ev,
+ MotionEventCompat.findPointerIndex( ev, mActivePointerId ) );
+ } else {
+ mLastMotionX = MotionEventCompat.getX( ev,
+ MotionEventCompat.findPointerIndex( ev, mActivePointerId ) );
+ }
+ break;
+ }
+ if ( needsInvalidate ) {
+ ViewCompat.postInvalidateOnAnimation( this );
+ }
+ return true;
+ }
+
+ private void requestParentDisallowInterceptTouchEvent( boolean disallowIntercept ) {
+ final ViewParent parent = getParent();
+ if ( parent != null ) {
+ parent.requestDisallowInterceptTouchEvent( disallowIntercept );
+ }
+ }
+
+ private boolean performDrag( float dimen ) {
+ boolean needsInvalidate = false;
+
+ if ( mOrientation == Orientation.VERTICAL ) {
+ float y = dimen;
+ final float deltaY = mLastMotionY - y;
+ mLastMotionY = y;
+
+ float oldScrollY = getScrollY();
+ float scrollY = oldScrollY + deltaY;
final int height = getClientSize();
+
+ float topBound = height * mFirstOffset;
+ float bottomBound = height * mLastOffset;
+ boolean topAbsolute = true;
+ boolean bottomAbsolute = true;
+
+ final ItemInfo firstItem = mItems.get( 0 );
+ final ItemInfo lastItem = mItems.get( mItems.size() - 1 );
+ if ( firstItem.position != 0 ) {
+ topAbsolute = false;
+ topBound = firstItem.offset * height;
+ }
+ if ( lastItem.position != mAdapter.getCount() - 1 ) {
+ bottomAbsolute = false;
+ bottomBound = lastItem.offset * height;
+ }
+
+ if ( scrollY < topBound ) {
+ if ( topAbsolute ) {
+ float over = topBound - scrollY;
+ needsInvalidate = mTopLeftEdge.onPull( Math.abs( over ) / height );
+ }
+ scrollY = topBound;
+ } else if ( scrollY > bottomBound ) {
+ if ( bottomAbsolute ) {
+ float over = scrollY - bottomBound;
+ needsInvalidate = mRightBottomEdge.onPull( Math.abs( over ) / height );
+ }
+ scrollY = bottomBound;
+ }
+ // Don't lose the rounded component
+ mLastMotionX += scrollY - ( int ) scrollY;
+ scrollTo( getScrollX(), ( int ) scrollY );
+ pageScrolled( ( int ) scrollY );
+ } else {
+ float x = dimen;
+
+ final float deltaX = mLastMotionX - x;
+ mLastMotionX = x;
+
+ float oldScrollX = getScrollX();
+ float scrollX = oldScrollX + deltaX;
+ final int width = getClientSize();
+
+ float leftBound = width * mFirstOffset;
+ float rightBound = width * mLastOffset;
+ boolean leftAbsolute = true;
+ boolean rightAbsolute = true;
+
+ final ItemInfo firstItem = mItems.get( 0 );
+ final ItemInfo lastItem = mItems.get( mItems.size() - 1 );
+ if ( firstItem.position != 0 ) {
+ leftAbsolute = false;
+ leftBound = firstItem.offset * width;
+ }
+ if ( lastItem.position != mAdapter.getCount() - 1 ) {
+ rightAbsolute = false;
+ rightBound = lastItem.offset * width;
+ }
+
+ if ( scrollX < leftBound ) {
+ if ( leftAbsolute ) {
+ float over = leftBound - scrollX;
+ needsInvalidate = mTopLeftEdge.onPull( Math.abs( over ) / width );
+ }
+ scrollX = leftBound;
+ } else if ( scrollX > rightBound ) {
+ if ( rightAbsolute ) {
+ float over = scrollX - rightBound;
+ needsInvalidate = mRightBottomEdge.onPull( Math.abs( over ) / width );
+ }
+ scrollX = rightBound;
+ }
+ // Don't lose the rounded component
+ mLastMotionX += scrollX - ( int ) scrollX;
+ scrollTo( ( int ) scrollX, getScrollY() );
+ pageScrolled( ( int ) scrollX );
+ }
+
+ return needsInvalidate;
+ }
+
+ /**
+ * @return Info about the page at the current scroll position.
+ * This can be synthetic for a missing middle page; the 'object' field can be null.
+ */
+ private ItemInfo infoForCurrentScrollPosition() {
+ final int size = getClientSize();
+ final float scrollOffset =
+ size > 0 ? ( float ) ( ( mOrientation == Orientation.VERTICAL ) ? getScrollY() : getScrollX() )
+ / size : 0;
+ final float marginOffset = size > 0 ? ( float ) mPageMargin / size : 0;
+ int lastPos = -1;
+ float lastOffset = 0.f;
+ float lastSize = 0.f;
+ boolean first = true;
+
+ ItemInfo lastItem = null;
+ for ( int i = 0; i < mItems.size(); i++ ) {
+ ItemInfo ii = mItems.get( i );
+ float offset;
+ if ( !first && ii.position != lastPos + 1 ) {
+ // Create a synthetic item for a missing page.
+ ii = mTempItem;
+ ii.offset = lastOffset + lastSize + marginOffset;
+ ii.position = lastPos + 1;
+ ii.sizeFactor = mAdapter.getPageWidth( ii.position );
+ i--;
+ }
+ offset = ii.offset;
+
+ final float topLeftBound = offset;
+ final float bottomRightBound = offset + ii.sizeFactor + marginOffset;
+ if ( first || scrollOffset >= topLeftBound ) {
+ if ( scrollOffset < bottomRightBound || i == mItems.size() - 1 ) {
+ return ii;
+ }
+ } else {
+ return lastItem;
+ }
+ first = false;
+ lastPos = ii.position;
+ lastOffset = offset;
+ lastSize = ii.sizeFactor;
+ lastItem = ii;
+ }
+
+ return lastItem;
+ }
+
+ private int determineTargetPage( int currentPage, float pageOffset, int velocity, int deltaDimen ) {
+ int targetPage;
+ if ( Math.abs( deltaDimen ) > mFlingDistance && Math.abs( velocity ) > mMinimumVelocity ) {
+ targetPage = velocity > 0 ? currentPage : currentPage + 1;
+ } else {
+ final float truncator = currentPage >= mCurItem ? 0.4f : 0.6f;
+ targetPage = ( int ) ( currentPage + pageOffset + truncator );
+ }
+
+ if ( mItems.size() > 0 ) {
+ final ItemInfo firstItem = mItems.get( 0 );
+ final ItemInfo lastItem = mItems.get( mItems.size() - 1 );
+
+ // Only let the user target pages we have items for
+ targetPage = Math.max( firstItem.position, Math.min( targetPage, lastItem.position ) );
+ }
+
+ return targetPage;
+ }
+
+ @Override
+ public void draw( Canvas canvas ) {
+ super.draw( canvas );
+ boolean needsInvalidate = false;
+
+ final int overScrollMode = ViewCompat.getOverScrollMode( this );
+ if ( overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS ||
+ ( overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS &&
+ mAdapter != null && mAdapter.getCount() > 1 ) ) {
+ if ( mOrientation == Orientation.VERTICAL ) {
+ if ( !mTopLeftEdge.isFinished() ) {
+ final int restoreCount = canvas.save();
+ final int height = getHeight();
+ final int width = getWidth() - getPaddingLeft() - getPaddingRight();
+
+ canvas.translate( getPaddingLeft(), mFirstOffset * height );
+ mTopLeftEdge.setSize( width, height );
+ needsInvalidate |= mTopLeftEdge.draw( canvas );
+ canvas.restoreToCount( restoreCount );
+ }
+ if ( !mRightBottomEdge.isFinished() ) {
+ final int restoreCount = canvas.save();
+ final int height = getHeight();
+ final int width = getWidth() - getPaddingLeft() - getPaddingRight();
+
+ canvas.rotate( 180 );
+ canvas.translate( -width - getPaddingLeft(), -( mLastOffset + 1 ) * height );
+ mRightBottomEdge.setSize( width, height );
+ needsInvalidate |= mRightBottomEdge.draw( canvas );
+ canvas.restoreToCount( restoreCount );
+ }
+ } else {
+ if ( !mTopLeftEdge.isFinished() ) {
+ final int restoreCount = canvas.save();
+ final int height = getHeight() - getPaddingTop() - getPaddingBottom();
+ final int width = getWidth();
+
+ canvas.rotate( 270 );
+ canvas.translate( -height + getPaddingTop(), mFirstOffset * width );
+ mTopLeftEdge.setSize( height, width );
+ needsInvalidate |= mTopLeftEdge.draw( canvas );
+ canvas.restoreToCount( restoreCount );
+ }
+ if ( !mRightBottomEdge.isFinished() ) {
+ final int restoreCount = canvas.save();
+ final int width = getWidth();
+ final int height = getHeight() - getPaddingTop() - getPaddingBottom();
+
+ canvas.rotate( 90 );
+ canvas.translate( -getPaddingTop(), -( mLastOffset + 1 ) * width );
+ mRightBottomEdge.setSize( height, width );
+ needsInvalidate |= mRightBottomEdge.draw( canvas );
+ canvas.restoreToCount( restoreCount );
+ }
+ }
+ } else {
+ mTopLeftEdge.finish();
+ mRightBottomEdge.finish();
+ }
+
+ if ( needsInvalidate ) {
+ // Keep animating
+ ViewCompat.postInvalidateOnAnimation( this );
+ }
+ }
+
+ @Override
+ protected void onDraw( Canvas canvas ) {
+ super.onDraw( canvas );
+
+ // Draw the margin drawable between pages if needed.
+ if ( mPageMargin > 0 && mMarginDrawable != null && mItems.size() > 0 && mAdapter != null ) {
+ if ( mOrientation == Orientation.VERTICAL ) {
+ final int scrollY = getScrollY();
+ final int height = getHeight();
+
+ final float marginOffset = ( float ) mPageMargin / height;
+ int itemIndex = 0;
+ ItemInfo ii = mItems.get( 0 );
+ float offset = ii.offset;
+ final int itemCount = mItems.size();
+ final int firstPos = ii.position;
+ final int lastPos = mItems.get( itemCount - 1 ).position;
+ for ( int pos = firstPos; pos < lastPos; pos++ ) {
+ while ( pos > ii.position && itemIndex < itemCount ) {
+ ii = mItems.get( ++itemIndex );
+ }
+
+ float drawAt;
+ if ( pos == ii.position ) {
+ drawAt = ( ii.offset + ii.sizeFactor ) * height;
+ offset = ii.offset + ii.sizeFactor + marginOffset;
+ } else {
+ float heightFactor = mAdapter.getPageWidth( pos );
+ drawAt = ( offset + heightFactor ) * height;
+ offset += heightFactor + marginOffset;
+ }
+
+ if ( drawAt + mPageMargin > scrollY ) {
+ mMarginDrawable.setBounds( mTopLeftPageBounds, ( int ) drawAt,
+ mBottomRightPageBounds, ( int ) ( drawAt + mPageMargin + 0.5f ) );
+ mMarginDrawable.draw( canvas );
+ }
+
+ if ( drawAt > scrollY + height ) {
+ break; // No more visible, no sense in continuing
+ }
+ }
+ } else {
+ final int scrollX = getScrollX();
+ final int width = getWidth();
+
+ final float marginOffset = ( float ) mPageMargin / width;
+ int itemIndex = 0;
+ ItemInfo ii = mItems.get( 0 );
+ float offset = ii.offset;
+ final int itemCount = mItems.size();
+ final int firstPos = ii.position;
+ final int lastPos = mItems.get( itemCount - 1 ).position;
+ for ( int pos = firstPos; pos < lastPos; pos++ ) {
+ while ( pos > ii.position && itemIndex < itemCount ) {
+ ii = mItems.get( ++itemIndex );
+ }
+
+ float drawAt;
+ if ( pos == ii.position ) {
+ drawAt = ( ii.offset + ii.sizeFactor ) * width;
+ offset = ii.offset + ii.sizeFactor + marginOffset;
+ } else {
+ float widthFactor = mAdapter.getPageWidth( pos );
+ drawAt = ( offset + widthFactor ) * width;
+ offset += widthFactor + marginOffset;
+ }
+
+ if ( drawAt + mPageMargin > scrollX ) {
+ mMarginDrawable.setBounds( ( int ) drawAt, mTopLeftPageBounds,
+ ( int ) ( drawAt + mPageMargin + 0.5f ), mBottomRightPageBounds );
+ mMarginDrawable.draw( canvas );
+ }
+
+ if ( drawAt > scrollX + width ) {
+ break; // No more visible, no sense in continuing
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Start a fake drag of the pager.
+ *
+ *
A fake drag can be useful if you want to synchronize the motion of the ViewPager
+ * with the touch scrolling of another view, while still letting the ViewPager
+ * control the snapping motion and fling behavior. (e.g. parallax-scrolling tabs.)
+ * Call {@link #fakeDragBy(float)} to simulate the actual drag motion. Call
+ * {@link #endFakeDrag()} to complete the fake drag and fling as necessary.
+ *
+ *
During a fake drag the ViewPager will ignore all touch events. If a real drag
+ * is already in progress, this method will return false.
+ *
+ * @return true if the fake drag began successfully, false if it could not be started.
+ * @see #fakeDragBy(float)
+ * @see #endFakeDrag()
+ */
+ public boolean beginFakeDrag() {
+ if ( mIsBeingDragged ) {
+ return false;
+ }
+ mFakeDragging = true;
+ setScrollState( SCROLL_STATE_DRAGGING );
+ if ( mOrientation == Orientation.VERTICAL ) {
+ mInitialMotionY = mLastMotionY = 0;
+ } else {
+ mInitialMotionX = mLastMotionX = 0;
+ }
+ if ( mVelocityTracker == null ) {
+ mVelocityTracker = VelocityTracker.obtain();
+ } else {
+ mVelocityTracker.clear();
+ }
+ final long time = SystemClock.uptimeMillis();
+ final MotionEvent ev = MotionEvent.obtain( time, time, MotionEvent.ACTION_DOWN, 0, 0, 0 );
+ mVelocityTracker.addMovement( ev );
+ ev.recycle();
+ mFakeDragBeginTime = time;
+ return true;
+ }
+
+ /**
+ * End a fake drag of the pager.
+ *
+ * @see #beginFakeDrag()
+ * @see #fakeDragBy(float)
+ */
+ public void endFakeDrag() {
+ if ( !mFakeDragging ) {
+ throw new IllegalStateException( "No fake drag in progress. Call beginFakeDrag first." );
+ }
+
+ final VelocityTracker velocityTracker = mVelocityTracker;
+ velocityTracker.computeCurrentVelocity( 1000, mMaximumVelocity );
+ if ( mOrientation == Orientation.VERTICAL ) {
+ int initialVelocity = ( int ) VelocityTrackerCompat.getYVelocity(
+ velocityTracker, mActivePointerId );
+ mPopulatePending = true;
+ final int size = getClientSize();
final int scrollY = getScrollY();
final ItemInfo ii = infoForCurrentScrollPosition();
- currentPage = ii.position;
- pageOffset = (((float) scrollY / height) - ii.offset) / ii.sizeFactor;
- final int activePointerIndex =
- MotionEventCompat.findPointerIndex(ev, mActivePointerId);
- final float y = MotionEventCompat.getY(ev, activePointerIndex);
- totalDelta = (int) (y - mInitialMotionY);
- } else {
- final VelocityTracker velocityTracker = mVelocityTracker;
- velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
- initialVelocity = (int) VelocityTrackerCompat.getXVelocity(
- velocityTracker, mActivePointerId);
+ final int currentPage = ii.position;
+ final float pageOffset = ( ( ( float ) scrollY / size ) - ii.offset ) / ii.sizeFactor;
+ final int totalDelta = ( int ) ( mLastMotionY - mInitialMotionY );
+
+ int nextPage = determineTargetPage( currentPage, pageOffset, initialVelocity,
+ totalDelta );
+ setCurrentItemInternal( nextPage, true, true, initialVelocity );
+ } else {
+ int initialVelocity = ( int ) VelocityTrackerCompat.getXVelocity(
+ velocityTracker, mActivePointerId );
mPopulatePending = true;
- final int width = getClientSize();
+ final int size = getClientSize();
final int scrollX = getScrollX();
final ItemInfo ii = infoForCurrentScrollPosition();
- currentPage = ii.position;
- pageOffset = (((float) scrollX / width) - ii.offset) / ii.sizeFactor;
- final int activePointerIndex =
- MotionEventCompat.findPointerIndex(ev, mActivePointerId);
- final float x = MotionEventCompat.getX(ev, activePointerIndex);
- totalDelta = (int) (x - mInitialMotionX);
- }
- int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
- totalDelta);
- setCurrentItemInternal(nextPage, true, true, initialVelocity);
-
- mActivePointerId = INVALID_POINTER;
- endDrag();
- needsInvalidate = mTopLeftEdge.onRelease() | mRightBottomEdge.onRelease();
+ final int currentPage = ii.position;
+ final float pageOffset = ( ( ( float ) scrollX / size ) - ii.offset ) / ii.sizeFactor;
+ final int totalDelta = ( int ) ( mLastMotionX - mInitialMotionX );
+ int nextPage = determineTargetPage( currentPage, pageOffset, initialVelocity,
+ totalDelta );
+ setCurrentItemInternal( nextPage, true, true, initialVelocity );
}
- break;
- case MotionEvent.ACTION_CANCEL:
- if (mIsBeingDragged) {
- scrollToItem(mCurItem, true, 0, false);
- mActivePointerId = INVALID_POINTER;
- endDrag();
- needsInvalidate = mTopLeftEdge.onRelease() | mRightBottomEdge.onRelease();
+ endDrag();
+
+ mFakeDragging = false;
+ }
+
+ /**
+ * Fake drag by an offset in pixels. You must have called {@link #beginFakeDrag()} first.
+ *
+ * @param offset Offset in pixels to drag by.
+ * @see #beginFakeDrag()
+ * @see #endFakeDrag()
+ */
+ public void fakeDragBy( float offset ) {
+ if ( !mFakeDragging ) {
+ throw new IllegalStateException( "No fake drag in progress. Call beginFakeDrag first." );
}
- break;
- case MotionEventCompat.ACTION_POINTER_DOWN: {
- int index;
- if (mOrientation == Orientation.VERTICAL) {
- index = MotionEventCompat.getActionIndex(ev);
- final float y = MotionEventCompat.getY(ev, index);
- mLastMotionY = y;
- } else {
- index = MotionEventCompat.getActionIndex(ev);
- final float x = MotionEventCompat.getX(ev, index);
- mLastMotionX = x;
- }
- mActivePointerId = MotionEventCompat.getPointerId(ev, index);
- break;
- }
- case MotionEventCompat.ACTION_POINTER_UP:
- onSecondaryPointerUp(ev);
- if (mOrientation == Orientation.VERTICAL) {
- mLastMotionY = MotionEventCompat.getY(ev,
- MotionEventCompat.findPointerIndex(ev, mActivePointerId));
- } else {
- mLastMotionX = MotionEventCompat.getX(ev,
- MotionEventCompat.findPointerIndex(ev, mActivePointerId));
- }
- break;
- }
- if (needsInvalidate) {
- ViewCompat.postInvalidateOnAnimation(this);
- }
- return true;
- }
- private void requestParentDisallowInterceptTouchEvent(boolean disallowIntercept) {
- final ViewParent parent = getParent();
- if (parent != null) {
- parent.requestDisallowInterceptTouchEvent(disallowIntercept);
- }
- }
+ if ( mOrientation == Orientation.VERTICAL ) {
+ mLastMotionY += offset;
- private boolean performDrag(float dimen) {
- boolean needsInvalidate = false;
+ float oldScrollY = getScrollY();
+ float scrollY = oldScrollY - offset;
+ final int height = getClientSize();
- if (mOrientation == Orientation.VERTICAL) {
- float y = dimen;
- final float deltaY = mLastMotionY - y;
- mLastMotionY = y;
+ float topBound = height * mFirstOffset;
+ float bottomBound = height * mLastOffset;
- float oldScrollY = getScrollY();
- float scrollY = oldScrollY + deltaY;
- final int height = getClientSize();
-
- float topBound = height * mFirstOffset;
- float bottomBound = height * mLastOffset;
- boolean topAbsolute = true;
- boolean bottomAbsolute = true;
-
- final ItemInfo firstItem = mItems.get(0);
- final ItemInfo lastItem = mItems.get(mItems.size() - 1);
- if (firstItem.position != 0) {
- topAbsolute = false;
- topBound = firstItem.offset * height;
- }
- if (lastItem.position != mAdapter.getCount() - 1) {
- bottomAbsolute = false;
- bottomBound = lastItem.offset * height;
- }
-
- if (scrollY < topBound) {
- if (topAbsolute) {
- float over = topBound - scrollY;
- needsInvalidate = mTopLeftEdge.onPull(Math.abs(over) / height);
- }
- scrollY = topBound;
- } else if (scrollY > bottomBound) {
- if (bottomAbsolute) {
- float over = scrollY - bottomBound;
- needsInvalidate = mRightBottomEdge.onPull(Math.abs(over) / height);
- }
- scrollY = bottomBound;
- }
- // Don't lose the rounded component
- mLastMotionX += scrollY - (int) scrollY;
- scrollTo(getScrollX(), (int) scrollY);
- pageScrolled((int) scrollY);
- } else {
- float x = dimen;
-
- final float deltaX = mLastMotionX - x;
- mLastMotionX = x;
-
- float oldScrollX = getScrollX();
- float scrollX = oldScrollX + deltaX;
- final int width = getClientSize();
-
- float leftBound = width * mFirstOffset;
- float rightBound = width * mLastOffset;
- boolean leftAbsolute = true;
- boolean rightAbsolute = true;
-
- final ItemInfo firstItem = mItems.get(0);
- final ItemInfo lastItem = mItems.get(mItems.size() - 1);
- if (firstItem.position != 0) {
- leftAbsolute = false;
- leftBound = firstItem.offset * width;
- }
- if (lastItem.position != mAdapter.getCount() - 1) {
- rightAbsolute = false;
- rightBound = lastItem.offset * width;
- }
-
- if (scrollX < leftBound) {
- if (leftAbsolute) {
- float over = leftBound - scrollX;
- needsInvalidate = mTopLeftEdge.onPull(Math.abs(over) / width);
- }
- scrollX = leftBound;
- } else if (scrollX > rightBound) {
- if (rightAbsolute) {
- float over = scrollX - rightBound;
- needsInvalidate = mRightBottomEdge.onPull(Math.abs(over) / width);
- }
- scrollX = rightBound;
- }
- // Don't lose the rounded component
- mLastMotionX += scrollX - (int) scrollX;
- scrollTo((int) scrollX, getScrollY());
- pageScrolled((int) scrollX);
- }
-
- return needsInvalidate;
- }
-
- /**
- * @return Info about the page at the current scroll position.
- * This can be synthetic for a missing middle page; the 'object' field can be null.
- */
- private ItemInfo infoForCurrentScrollPosition() {
- final int size = getClientSize();
- final float scrollOffset =
- size > 0 ? (float) ((mOrientation == Orientation.VERTICAL) ? getScrollY() : getScrollX())
- / size : 0;
- final float marginOffset = size > 0 ? (float) mPageMargin / size : 0;
- int lastPos = -1;
- float lastOffset = 0.f;
- float lastSize = 0.f;
- boolean first = true;
-
- ItemInfo lastItem = null;
- for (int i = 0; i < mItems.size(); i++) {
- ItemInfo ii = mItems.get(i);
- float offset;
- if (!first && ii.position != lastPos + 1) {
- // Create a synthetic item for a missing page.
- ii = mTempItem;
- ii.offset = lastOffset + lastSize + marginOffset;
- ii.position = lastPos + 1;
- ii.sizeFactor = mAdapter.getPageWidth(ii.position);
- i--;
- }
- offset = ii.offset;
-
- final float topLeftBound = offset;
- final float bottomRightBound = offset + ii.sizeFactor + marginOffset;
- if (first || scrollOffset >= topLeftBound) {
- if (scrollOffset < bottomRightBound || i == mItems.size() - 1) {
- return ii;
- }
- } else {
- return lastItem;
- }
- first = false;
- lastPos = ii.position;
- lastOffset = offset;
- lastSize = ii.sizeFactor;
- lastItem = ii;
- }
-
- return lastItem;
- }
-
- private int determineTargetPage(int currentPage, float pageOffset, int velocity, int deltaDimen) {
- int targetPage;
- if (Math.abs(deltaDimen) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) {
- targetPage = velocity > 0 ? currentPage : currentPage + 1;
- } else {
- final float truncator = currentPage >= mCurItem ? 0.4f : 0.6f;
- targetPage = (int) (currentPage + pageOffset + truncator);
- }
-
- if (mItems.size() > 0) {
- final ItemInfo firstItem = mItems.get(0);
- final ItemInfo lastItem = mItems.get(mItems.size() - 1);
-
- // Only let the user target pages we have items for
- targetPage = Math.max(firstItem.position, Math.min(targetPage, lastItem.position));
- }
-
- return targetPage;
- }
-
- @Override
- public void draw( Canvas canvas) {
- super.draw(canvas);
- boolean needsInvalidate = false;
-
- final int overScrollMode = ViewCompat.getOverScrollMode(this);
- if (overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS ||
- (overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS &&
- mAdapter != null && mAdapter.getCount() > 1)) {
- if (mOrientation == Orientation.VERTICAL) {
- if (!mTopLeftEdge.isFinished()) {
- final int restoreCount = canvas.save();
- final int height = getHeight();
- final int width = getWidth() - getPaddingLeft() - getPaddingRight();
-
- canvas.translate(getPaddingLeft(), mFirstOffset * height);
- mTopLeftEdge.setSize(width, height);
- needsInvalidate |= mTopLeftEdge.draw(canvas);
- canvas.restoreToCount(restoreCount);
- }
- if (!mRightBottomEdge.isFinished()) {
- final int restoreCount = canvas.save();
- final int height = getHeight();
- final int width = getWidth() - getPaddingLeft() - getPaddingRight();
-
- canvas.rotate(180);
- canvas.translate(-width - getPaddingLeft(), -(mLastOffset + 1) * height);
- mRightBottomEdge.setSize(width, height);
- needsInvalidate |= mRightBottomEdge.draw(canvas);
- canvas.restoreToCount(restoreCount);
- }
- } else {
- if (!mTopLeftEdge.isFinished()) {
- final int restoreCount = canvas.save();
- final int height = getHeight() - getPaddingTop() - getPaddingBottom();
- final int width = getWidth();
-
- canvas.rotate(270);
- canvas.translate(-height + getPaddingTop(), mFirstOffset * width);
- mTopLeftEdge.setSize(height, width);
- needsInvalidate |= mTopLeftEdge.draw(canvas);
- canvas.restoreToCount(restoreCount);
- }
- if (!mRightBottomEdge.isFinished()) {
- final int restoreCount = canvas.save();
- final int width = getWidth();
- final int height = getHeight() - getPaddingTop() - getPaddingBottom();
-
- canvas.rotate(90);
- canvas.translate(-getPaddingTop(), -(mLastOffset + 1) * width);
- mRightBottomEdge.setSize(height, width);
- needsInvalidate |= mRightBottomEdge.draw(canvas);
- canvas.restoreToCount(restoreCount);
- }
- }
- } else {
- mTopLeftEdge.finish();
- mRightBottomEdge.finish();
- }
-
- if (needsInvalidate) {
- // Keep animating
- ViewCompat.postInvalidateOnAnimation(this);
- }
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
-
- // Draw the margin drawable between pages if needed.
- if (mPageMargin > 0 && mMarginDrawable != null && mItems.size() > 0 && mAdapter != null) {
- if (mOrientation == Orientation.VERTICAL) {
- final int scrollY = getScrollY();
- final int height = getHeight();
-
- final float marginOffset = (float) mPageMargin / height;
- int itemIndex = 0;
- ItemInfo ii = mItems.get(0);
- float offset = ii.offset;
- final int itemCount = mItems.size();
- final int firstPos = ii.position;
- final int lastPos = mItems.get(itemCount - 1).position;
- for (int pos = firstPos; pos < lastPos; pos++) {
- while (pos > ii.position && itemIndex < itemCount) {
- ii = mItems.get(++itemIndex);
- }
-
- float drawAt;
- if (pos == ii.position) {
- drawAt = (ii.offset + ii.sizeFactor) * height;
- offset = ii.offset + ii.sizeFactor + marginOffset;
- } else {
- float heightFactor = mAdapter.getPageWidth(pos);
- drawAt = (offset + heightFactor) * height;
- offset += heightFactor + marginOffset;
- }
-
- if (drawAt + mPageMargin > scrollY) {
- mMarginDrawable.setBounds(mTopLeftPageBounds, (int) drawAt,
- mBottomRightPageBounds, (int) (drawAt + mPageMargin + 0.5f));
- mMarginDrawable.draw(canvas);
- }
-
- if (drawAt > scrollY + height) {
- break; // No more visible, no sense in continuing
- }
- }
- } else {
- final int scrollX = getScrollX();
- final int width = getWidth();
-
- final float marginOffset = (float) mPageMargin / width;
- int itemIndex = 0;
- ItemInfo ii = mItems.get(0);
- float offset = ii.offset;
- final int itemCount = mItems.size();
- final int firstPos = ii.position;
- final int lastPos = mItems.get(itemCount - 1).position;
- for (int pos = firstPos; pos < lastPos; pos++) {
- while (pos > ii.position && itemIndex < itemCount) {
- ii = mItems.get(++itemIndex);
- }
-
- float drawAt;
- if (pos == ii.position) {
- drawAt = (ii.offset + ii.sizeFactor) * width;
- offset = ii.offset + ii.sizeFactor + marginOffset;
- } else {
- float widthFactor = mAdapter.getPageWidth(pos);
- drawAt = (offset + widthFactor) * width;
- offset += widthFactor + marginOffset;
- }
-
- if (drawAt + mPageMargin > scrollX) {
- mMarginDrawable.setBounds((int) drawAt, mTopLeftPageBounds,
- (int) (drawAt + mPageMargin + 0.5f), mBottomRightPageBounds);
- mMarginDrawable.draw(canvas);
- }
-
- if (drawAt > scrollX + width) {
- break; // No more visible, no sense in continuing
- }
- }
- }
- }
- }
-
- /**
- * Start a fake drag of the pager.
- *
- *
A fake drag can be useful if you want to synchronize the motion of the ViewPager
- * with the touch scrolling of another view, while still letting the ViewPager
- * control the snapping motion and fling behavior. (e.g. parallax-scrolling tabs.)
- * Call {@link #fakeDragBy(float)} to simulate the actual drag motion. Call
- * {@link #endFakeDrag()} to complete the fake drag and fling as necessary.
- *
- *
During a fake drag the ViewPager will ignore all touch events. If a real drag
- * is already in progress, this method will return false.
- *
- * @return true if the fake drag began successfully, false if it could not be started.
- * @see #fakeDragBy(float)
- * @see #endFakeDrag()
- */
- public boolean beginFakeDrag() {
- if (mIsBeingDragged) {
- return false;
- }
- mFakeDragging = true;
- setScrollState(SCROLL_STATE_DRAGGING);
- if (mOrientation == Orientation.VERTICAL) {
- mInitialMotionY = mLastMotionY = 0;
- } else {
- mInitialMotionX = mLastMotionX = 0;
- }
- if (mVelocityTracker == null) {
- mVelocityTracker = VelocityTracker.obtain();
- } else {
- mVelocityTracker.clear();
- }
- final long time = SystemClock.uptimeMillis();
- final MotionEvent ev = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 0, 0, 0);
- mVelocityTracker.addMovement(ev);
- ev.recycle();
- mFakeDragBeginTime = time;
- return true;
- }
-
- /**
- * End a fake drag of the pager.
- *
- * @see #beginFakeDrag()
- * @see #fakeDragBy(float)
- */
- public void endFakeDrag() {
- if (!mFakeDragging) {
- throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
- }
-
- final VelocityTracker velocityTracker = mVelocityTracker;
- velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
- if (mOrientation == Orientation.VERTICAL) {
- int initialVelocity = (int) VelocityTrackerCompat.getYVelocity(
- velocityTracker, mActivePointerId);
- mPopulatePending = true;
- final int size = getClientSize();
- final int scrollY = getScrollY();
- final ItemInfo ii = infoForCurrentScrollPosition();
- final int currentPage = ii.position;
- final float pageOffset = (((float) scrollY / size) - ii.offset) / ii.sizeFactor;
- final int totalDelta = (int) (mLastMotionY - mInitialMotionY);
-
- int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
- totalDelta);
- setCurrentItemInternal(nextPage, true, true, initialVelocity);
- } else {
- int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(
- velocityTracker, mActivePointerId);
- mPopulatePending = true;
- final int size = getClientSize();
- final int scrollX = getScrollX();
- final ItemInfo ii = infoForCurrentScrollPosition();
- final int currentPage = ii.position;
- final float pageOffset = (((float) scrollX / size) - ii.offset) / ii.sizeFactor;
- final int totalDelta = (int) (mLastMotionX - mInitialMotionX);
- int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
- totalDelta);
- setCurrentItemInternal(nextPage, true, true, initialVelocity);
- }
- endDrag();
-
- mFakeDragging = false;
- }
-
- /**
- * Fake drag by an offset in pixels. You must have called {@link #beginFakeDrag()} first.
- *
- * @param offset Offset in pixels to drag by.
- * @see #beginFakeDrag()
- * @see #endFakeDrag()
- */
- public void fakeDragBy(float offset) {
- if (!mFakeDragging) {
- throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
- }
-
- if (mOrientation == Orientation.VERTICAL) {
- mLastMotionY += offset;
-
- float oldScrollY = getScrollY();
- float scrollY = oldScrollY - offset;
- final int height = getClientSize();
-
- float topBound = height * mFirstOffset;
- float bottomBound = height * mLastOffset;
-
- final ItemInfo firstItem = mItems.get(0);
- final ItemInfo lastItem = mItems.get(mItems.size() - 1);
- if (firstItem.position != 0) {
- topBound = firstItem.offset * height;
- }
- if (lastItem.position != mAdapter.getCount() - 1) {
- bottomBound = lastItem.offset * height;
- }
-
- if (scrollY < topBound) {
- scrollY = topBound;
- } else if (scrollY > bottomBound) {
- scrollY = bottomBound;
- }
- // Don't lose the rounded component
- mLastMotionY += scrollY - (int) scrollY;
- scrollTo(getScrollX(), (int) scrollY);
- pageScrolled((int) scrollY);
-
- // Synthesize an event for the VelocityTracker.
- final long time = SystemClock.uptimeMillis();
- final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE,
- 0, mLastMotionY, 0);
- mVelocityTracker.addMovement(ev);
- ev.recycle();
- } else {
- mLastMotionX += offset;
-
- float oldScrollX = getScrollX();
- float scrollX = oldScrollX - offset;
- final int width = getClientSize();
-
- float leftBound = width * mFirstOffset;
- float rightBound = width * mLastOffset;
-
- final ItemInfo firstItem = mItems.get(0);
- final ItemInfo lastItem = mItems.get(mItems.size() - 1);
- if (firstItem.position != 0) {
- leftBound = firstItem.offset * width;
- }
- if (lastItem.position != mAdapter.getCount() - 1) {
- rightBound = lastItem.offset * width;
- }
-
- if (scrollX < leftBound) {
- scrollX = leftBound;
- } else if (scrollX > rightBound) {
- scrollX = rightBound;
- }
- // Don't lose the rounded component
- mLastMotionX += scrollX - (int) scrollX;
- scrollTo((int) scrollX, getScrollY());
- pageScrolled((int) scrollX);
-
- // Synthesize an event for the VelocityTracker.
- final long time = SystemClock.uptimeMillis();
- final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE,
- mLastMotionX, 0, 0);
- mVelocityTracker.addMovement(ev);
- ev.recycle();
- }
- }
-
- /**
- * Returns true if a fake drag is in progress.
- *
- * @return true if currently in a fake drag, false otherwise.
- * @see #beginFakeDrag()
- * @see #fakeDragBy(float)
- * @see #endFakeDrag()
- */
- public boolean isFakeDragging() {
- return mFakeDragging;
- }
-
- private void onSecondaryPointerUp(MotionEvent ev) {
- final int pointerIndex = MotionEventCompat.getActionIndex(ev);
- final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
- if (pointerId == mActivePointerId) {
- // This was our active pointer going up. Choose a new
- // active pointer and adjust accordingly.
- final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
- if (mOrientation == Orientation.VERTICAL) {
- mLastMotionY = MotionEventCompat.getY(ev, newPointerIndex);
- } else {
- mLastMotionX = MotionEventCompat.getX(ev, newPointerIndex);
- }
- mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
- if (mVelocityTracker != null) {
- mVelocityTracker.clear();
- }
- }
- }
-
- private void endDrag() {
- mIsBeingDragged = false;
- mIsUnableToDrag = false;
-
- if (mVelocityTracker != null) {
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- }
- }
-
- private void setScrollingCacheEnabled(boolean enabled) {
- if (mScrollingCacheEnabled != enabled) {
- mScrollingCacheEnabled = enabled;
- if (USE_CACHE) {
- final int size = getChildCount();
- for (int i = 0; i < size; ++i) {
- final View child = getChildAt(i);
- if (child.getVisibility() != GONE) {
- child.setDrawingCacheEnabled(enabled);
- }
- }
- }
- }
- }
-
- public boolean internalCanScrollVertically(int direction) {
- if (mAdapter == null) {
- return false;
- }
-
- final int size = getClientSize();
- final int scroll = (mOrientation == Orientation.VERTICAL) ? getScrollY() : getScrollX();
- if (direction < 0) {
- return (scroll > (int) (size * mFirstOffset));
- } else if (direction > 0) {
- return (scroll < (int) (size * mLastOffset));
- } else {
- return false;
- }
- }
-
- /**
- * Tests scrollability within child views of v given a delta of dx.
- *
- * @param v View to test for horizontal scrollability
- * @param checkV Whether the view v passed should itself be checked for scrollability (true),
- * or just its children (false).
- * @param delta Delta scrolled in pixels
- * @param x X coordinate of the active touch point
- * @param y Y coordinate of the active touch point
- * @return true if child views of v can be scrolled by delta of dx.
- */
- protected boolean canScroll(View v, boolean checkV, int delta, int x, int y) {
- if (v instanceof ViewGroup) {
- final ViewGroup group = (ViewGroup) v;
- final int scrollX = v.getScrollX();
- final int scrollY = v.getScrollY();
- final int count = group.getChildCount();
- // Count backwards - let topmost views consume scroll distance first.
- for (int i = count - 1; i >= 0; i--) {
- // TODO: Add versioned support here for transformed views.
- // This will not work for transformed views in Honeycomb+
- final View child = group.getChildAt(i);
- if (mOrientation == Orientation.VERTICAL) {
- if (y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
- x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
- canScroll(child, true, delta, x + scrollX - child.getLeft(),
- y + scrollY - child.getTop())) {
- return true;
- }
- } else {
- if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
- y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
- canScroll(child, true, delta, x + scrollX - child.getLeft(),
- y + scrollY - child.getTop())) {
- return true;
- }
- }
- }
- }
-
- return checkV && ViewCompat.canScrollVertically(v, -delta);
- }
-
- @Override
- public boolean dispatchKeyEvent( KeyEvent event) {
- // Let the focused view and/or our descendants get the key first
- return super.dispatchKeyEvent(event) || executeKeyEvent(event);
- }
-
- /**
- * You can call this function yourself to have the scroll view perform
- * scrolling from a key event, just as if the event had been dispatched to
- * it by the view hierarchy.
- *
- * @param event The key event to execute.
- * @return Return true if the event was handled, else false.
- */
- public boolean executeKeyEvent(KeyEvent event) {
- boolean handled = false;
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
- switch (event.getKeyCode()) {
- case KeyEvent.KEYCODE_DPAD_LEFT:
- handled = arrowScroll(FOCUS_LEFT);
- break;
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- handled = arrowScroll(FOCUS_RIGHT);
- break;
- case KeyEvent.KEYCODE_TAB:
- if (Build.VERSION.SDK_INT >= 11) {
- // The focus finder had a bug handling FOCUS_FORWARD and FOCUS_BACKWARD
- // before Android 3.0. Ignore the tab key on those devices.
- if (event.hasNoModifiers()) {
- handled = arrowScroll(FOCUS_FORWARD);
- } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
- handled = arrowScroll(FOCUS_BACKWARD);
+ final ItemInfo firstItem = mItems.get( 0 );
+ final ItemInfo lastItem = mItems.get( mItems.size() - 1 );
+ if ( firstItem.position != 0 ) {
+ topBound = firstItem.offset * height;
+ }
+ if ( lastItem.position != mAdapter.getCount() - 1 ) {
+ bottomBound = lastItem.offset * height;
}
- }
- break;
- }
- }
- return handled;
- }
- public boolean arrowScroll(int direction) {
- View currentFocused = findFocus();
- if (currentFocused == this) {
- currentFocused = null;
- } else if (currentFocused != null) {
- boolean isChild = false;
- for (ViewParent parent = currentFocused.getParent(); parent instanceof ViewGroup;
- parent = parent.getParent()) {
- if (parent == this) {
- isChild = true;
- break;
- }
- }
- if (!isChild) {
- // This would cause the focus search down below to fail in fun ways.
- final StringBuilder sb = new StringBuilder();
- sb.append(currentFocused.getClass().getSimpleName());
- for (ViewParent parent = currentFocused.getParent(); parent instanceof ViewGroup;
- parent = parent.getParent()) {
- sb.append(" => ").append(parent.getClass().getSimpleName());
- }
- Log.e(TAG, "arrowScroll tried to find focus based on non-child " +
- "current focused view " + sb.toString());
- currentFocused = null;
- }
- }
+ if ( scrollY < topBound ) {
+ scrollY = topBound;
+ } else if ( scrollY > bottomBound ) {
+ scrollY = bottomBound;
+ }
+ // Don't lose the rounded component
+ mLastMotionY += scrollY - ( int ) scrollY;
+ scrollTo( getScrollX(), ( int ) scrollY );
+ pageScrolled( ( int ) scrollY );
- boolean handled = false;
-
- View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused,
- direction);
- if (nextFocused != null && nextFocused != currentFocused) {
- if (direction == View.FOCUS_UP) {
- // If there is nothing to the left, or this is causing us to
- // jump to the right, then what we really want to do is page left.
- if (mOrientation == Orientation.VERTICAL) {
- final int nextTop = getChildRectInPagerCoordinates(mTempRect, nextFocused).top;
- final int currTop = getChildRectInPagerCoordinates(mTempRect, currentFocused).top;
- if (currentFocused != null && nextTop >= currTop) {
- handled = pageBack();
- } else {
- handled = nextFocused.requestFocus();
- }
+ // Synthesize an event for the VelocityTracker.
+ final long time = SystemClock.uptimeMillis();
+ final MotionEvent ev = MotionEvent.obtain( mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE,
+ 0, mLastMotionY, 0 );
+ mVelocityTracker.addMovement( ev );
+ ev.recycle();
} else {
- final int nextLeft = getChildRectInPagerCoordinates(mTempRect, nextFocused).left;
- final int currLeft = getChildRectInPagerCoordinates(mTempRect, currentFocused).left;
- if (currentFocused != null && nextLeft >= currLeft) {
- handled = pageBack();
- } else {
- handled = nextFocused.requestFocus();
- }
+ mLastMotionX += offset;
+
+ float oldScrollX = getScrollX();
+ float scrollX = oldScrollX - offset;
+ final int width = getClientSize();
+
+ float leftBound = width * mFirstOffset;
+ float rightBound = width * mLastOffset;
+
+ final ItemInfo firstItem = mItems.get( 0 );
+ final ItemInfo lastItem = mItems.get( mItems.size() - 1 );
+ if ( firstItem.position != 0 ) {
+ leftBound = firstItem.offset * width;
+ }
+ if ( lastItem.position != mAdapter.getCount() - 1 ) {
+ rightBound = lastItem.offset * width;
+ }
+
+ if ( scrollX < leftBound ) {
+ scrollX = leftBound;
+ } else if ( scrollX > rightBound ) {
+ scrollX = rightBound;
+ }
+ // Don't lose the rounded component
+ mLastMotionX += scrollX - ( int ) scrollX;
+ scrollTo( ( int ) scrollX, getScrollY() );
+ pageScrolled( ( int ) scrollX );
+
+ // Synthesize an event for the VelocityTracker.
+ final long time = SystemClock.uptimeMillis();
+ final MotionEvent ev = MotionEvent.obtain( mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE,
+ mLastMotionX, 0, 0 );
+ mVelocityTracker.addMovement( ev );
+ ev.recycle();
}
- } else if (direction == View.FOCUS_DOWN) {
- // If there is nothing to the right, or this is causing us to
- // jump to the left, then what we really want to do is page right.
- if (mOrientation == Orientation.VERTICAL) {
- final int nextDown = getChildRectInPagerCoordinates(mTempRect, nextFocused).bottom;
- final int currDown = getChildRectInPagerCoordinates(mTempRect, currentFocused).bottom;
- if (currentFocused != null && nextDown <= currDown) {
- handled = pageForward();
- } else {
- handled = nextFocused.requestFocus();
- }
+ }
+
+ /**
+ * Returns true if a fake drag is in progress.
+ *
+ * @return true if currently in a fake drag, false otherwise.
+ * @see #beginFakeDrag()
+ * @see #fakeDragBy(float)
+ * @see #endFakeDrag()
+ */
+ public boolean isFakeDragging() {
+ return mFakeDragging;
+ }
+
+ private void onSecondaryPointerUp( MotionEvent ev ) {
+ final int pointerIndex = MotionEventCompat.getActionIndex( ev );
+ final int pointerId = MotionEventCompat.getPointerId( ev, pointerIndex );
+ if ( pointerId == mActivePointerId ) {
+ // This was our active pointer going up. Choose a new
+ // active pointer and adjust accordingly.
+ final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
+ if ( mOrientation == Orientation.VERTICAL ) {
+ mLastMotionY = MotionEventCompat.getY( ev, newPointerIndex );
+ } else {
+ mLastMotionX = MotionEventCompat.getX( ev, newPointerIndex );
+ }
+ mActivePointerId = MotionEventCompat.getPointerId( ev, newPointerIndex );
+ if ( mVelocityTracker != null ) {
+ mVelocityTracker.clear();
+ }
+ }
+ }
+
+ private void endDrag() {
+ mIsBeingDragged = false;
+ mIsUnableToDrag = false;
+
+ if ( mVelocityTracker != null ) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ }
+
+ private void setScrollingCacheEnabled( boolean enabled ) {
+ if ( mScrollingCacheEnabled != enabled ) {
+ mScrollingCacheEnabled = enabled;
+ if ( USE_CACHE ) {
+ final int size = getChildCount();
+ for ( int i = 0; i < size; ++i ) {
+ final View child = getChildAt( i );
+ if ( child.getVisibility() != GONE ) {
+ child.setDrawingCacheEnabled( enabled );
+ }
+ }
+ }
+ }
+ }
+
+ public boolean internalCanScrollVertically( int direction ) {
+ if ( mAdapter == null ) {
+ return false;
+ }
+
+ final int size = getClientSize();
+ final int scroll = ( mOrientation == Orientation.VERTICAL ) ? getScrollY() : getScrollX();
+ if ( direction < 0 ) {
+ return ( scroll > ( int ) ( size * mFirstOffset ) );
+ } else if ( direction > 0 ) {
+ return ( scroll < ( int ) ( size * mLastOffset ) );
} else {
- final int nextLeft = getChildRectInPagerCoordinates(mTempRect, nextFocused).left;
- final int currLeft = getChildRectInPagerCoordinates(mTempRect, currentFocused).left;
- if (currentFocused != null && nextLeft <= currLeft) {
+ return false;
+ }
+ }
+
+ /**
+ * Tests scrollability within child views of v given a delta of dx.
+ *
+ * @param v View to test for horizontal scrollability
+ * @param checkV Whether the view v passed should itself be checked for scrollability (true),
+ * or just its children (false).
+ * @param delta Delta scrolled in pixels
+ * @param x X coordinate of the active touch point
+ * @param y Y coordinate of the active touch point
+ * @return true if child views of v can be scrolled by delta of dx.
+ */
+ protected boolean canScroll( View v, boolean checkV, int delta, int x, int y ) {
+ if ( v instanceof ViewGroup ) {
+ final ViewGroup group = ( ViewGroup ) v;
+ final int scrollX = v.getScrollX();
+ final int scrollY = v.getScrollY();
+ final int count = group.getChildCount();
+ // Count backwards - let topmost views consume scroll distance first.
+ for ( int i = count - 1; i >= 0; i-- ) {
+ // TODO: Add versioned support here for transformed views.
+ // This will not work for transformed views in Honeycomb+
+ final View child = group.getChildAt( i );
+ if ( mOrientation == Orientation.VERTICAL ) {
+ if ( y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
+ x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
+ canScroll( child, true, delta, x + scrollX - child.getLeft(),
+ y + scrollY - child.getTop() ) ) {
+ return true;
+ }
+ } else {
+ if ( x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
+ y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
+ canScroll( child, true, delta, x + scrollX - child.getLeft(),
+ y + scrollY - child.getTop() ) ) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return checkV && ViewCompat.canScrollVertically( v, -delta );
+ }
+
+ @Override
+ public boolean dispatchKeyEvent( KeyEvent event ) {
+ // Let the focused view and/or our descendants get the key first
+ return super.dispatchKeyEvent( event ) || executeKeyEvent( event );
+ }
+
+ /**
+ * You can call this function yourself to have the scroll view perform
+ * scrolling from a key event, just as if the event had been dispatched to
+ * it by the view hierarchy.
+ *
+ * @param event The key event to execute.
+ * @return Return true if the event was handled, else false.
+ */
+ public boolean executeKeyEvent( KeyEvent event ) {
+ boolean handled = false;
+ if ( event.getAction() == KeyEvent.ACTION_DOWN ) {
+ switch ( event.getKeyCode() ) {
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ handled = arrowScroll( FOCUS_LEFT );
+ break;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ handled = arrowScroll( FOCUS_RIGHT );
+ break;
+ case KeyEvent.KEYCODE_TAB:
+ if ( Build.VERSION.SDK_INT >= 11 ) {
+ // The focus finder had a bug handling FOCUS_FORWARD and FOCUS_BACKWARD
+ // before Android 3.0. Ignore the tab key on those devices.
+ if ( event.hasNoModifiers() ) {
+ handled = arrowScroll( FOCUS_FORWARD );
+ } else if ( event.hasModifiers( KeyEvent.META_SHIFT_ON ) ) {
+ handled = arrowScroll( FOCUS_BACKWARD );
+ }
+ }
+ break;
+ }
+ }
+ return handled;
+ }
+
+ public boolean arrowScroll( int direction ) {
+ View currentFocused = findFocus();
+ if ( currentFocused == this ) {
+ currentFocused = null;
+ } else if ( currentFocused != null ) {
+ boolean isChild = false;
+ for ( ViewParent parent = currentFocused.getParent(); parent instanceof ViewGroup;
+ parent = parent.getParent() ) {
+ if ( parent == this ) {
+ isChild = true;
+ break;
+ }
+ }
+ if ( !isChild ) {
+ // This would cause the focus search down below to fail in fun ways.
+ final StringBuilder sb = new StringBuilder();
+ sb.append( currentFocused.getClass().getSimpleName() );
+ for ( ViewParent parent = currentFocused.getParent(); parent instanceof ViewGroup;
+ parent = parent.getParent() ) {
+ sb.append( " => " ).append( parent.getClass().getSimpleName() );
+ }
+ Log.e( TAG, "arrowScroll tried to find focus based on non-child " +
+ "current focused view " + sb.toString() );
+ currentFocused = null;
+ }
+ }
+
+ boolean handled = false;
+
+ View nextFocused = FocusFinder.getInstance().findNextFocus( this, currentFocused,
+ direction );
+ if ( nextFocused != null && nextFocused != currentFocused ) {
+ if ( direction == View.FOCUS_UP ) {
+ // If there is nothing to the left, or this is causing us to
+ // jump to the right, then what we really want to do is page left.
+ if ( mOrientation == Orientation.VERTICAL ) {
+ final int nextTop = getChildRectInPagerCoordinates( mTempRect, nextFocused ).top;
+ final int currTop = getChildRectInPagerCoordinates( mTempRect, currentFocused ).top;
+ if ( currentFocused != null && nextTop >= currTop ) {
+ handled = pageBack();
+ } else {
+ handled = nextFocused.requestFocus();
+ }
+ } else {
+ final int nextLeft = getChildRectInPagerCoordinates( mTempRect, nextFocused ).left;
+ final int currLeft = getChildRectInPagerCoordinates( mTempRect, currentFocused ).left;
+ if ( currentFocused != null && nextLeft >= currLeft ) {
+ handled = pageBack();
+ } else {
+ handled = nextFocused.requestFocus();
+ }
+ }
+ } else if ( direction == View.FOCUS_DOWN ) {
+ // If there is nothing to the right, or this is causing us to
+ // jump to the left, then what we really want to do is page right.
+ if ( mOrientation == Orientation.VERTICAL ) {
+ final int nextDown = getChildRectInPagerCoordinates( mTempRect, nextFocused ).bottom;
+ final int currDown = getChildRectInPagerCoordinates( mTempRect, currentFocused ).bottom;
+ if ( currentFocused != null && nextDown <= currDown ) {
+ handled = pageForward();
+ } else {
+ handled = nextFocused.requestFocus();
+ }
+ } else {
+ final int nextLeft = getChildRectInPagerCoordinates( mTempRect, nextFocused ).left;
+ final int currLeft = getChildRectInPagerCoordinates( mTempRect, currentFocused ).left;
+ if ( currentFocused != null && nextLeft <= currLeft ) {
+ handled = pageForward();
+ } else {
+ handled = nextFocused.requestFocus();
+ }
+ }
+ }
+ } else if ( direction == FOCUS_UP || direction == FOCUS_BACKWARD ) {
+ // Trying to move left and nothing there; try to page.
+ handled = pageBack();
+ } else if ( direction == FOCUS_DOWN || direction == FOCUS_FORWARD ) {
+ // Trying to move right and nothing there; try to page.
handled = pageForward();
- } else {
- handled = nextFocused.requestFocus();
- }
}
- }
- } else if (direction == FOCUS_UP || direction == FOCUS_BACKWARD) {
- // Trying to move left and nothing there; try to page.
- handled = pageBack();
- } else if (direction == FOCUS_DOWN || direction == FOCUS_FORWARD) {
- // Trying to move right and nothing there; try to page.
- handled = pageForward();
- }
- if (handled) {
- playSoundEffect( SoundEffectConstants.getContantForFocusDirection(direction));
- }
- return handled;
- }
-
- private Rect getChildRectInPagerCoordinates(Rect outRect, View child) {
- if (outRect == null) {
- outRect = new Rect();
- }
- if (child == null) {
- outRect.set(0, 0, 0, 0);
- return outRect;
- }
- outRect.left = child.getLeft();
- outRect.right = child.getRight();
- outRect.top = child.getTop();
- outRect.bottom = child.getBottom();
-
- ViewParent parent = child.getParent();
- while (parent instanceof ViewGroup && parent != this) {
- final ViewGroup group = (ViewGroup) parent;
- outRect.left += group.getLeft();
- outRect.right += group.getRight();
- outRect.top += group.getTop();
- outRect.bottom += group.getBottom();
-
- parent = group.getParent();
- }
- return outRect;
- }
-
- boolean pageBack() {
- if (mCurItem > 0) {
- setCurrentItem(mCurItem - 1, true);
- return true;
- }
- return false;
- }
-
- boolean pageForward() {
- if (mAdapter != null && mCurItem < (mAdapter.getCount() - 1)) {
- setCurrentItem(mCurItem + 1, true);
- return true;
- }
- return false;
- }
-
- /**
- * We only want the current page that is being shown to be focusable.
- */
- @Override
- public void addFocusables(ArrayList views, int direction, int focusableMode) {
- final int focusableCount = views.size();
-
- final int descendantFocusability = getDescendantFocusability();
-
- if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
- for (int i = 0; i < getChildCount(); i++) {
- final View child = getChildAt(i);
- if (child.getVisibility() == VISIBLE) {
- ItemInfo ii = infoForChild(child);
- if (ii != null && ii.position == mCurItem) {
- child.addFocusables(views, direction, focusableMode);
- }
+ if ( handled ) {
+ playSoundEffect( SoundEffectConstants.getContantForFocusDirection( direction ) );
}
- }
+ return handled;
}
- // we add ourselves (if focusable) in all cases except for when we are
- // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable. this is
- // to avoid the focus search finding layouts when a more precise search
- // among the focusable children would be more interesting.
- if (
- descendantFocusability != FOCUS_AFTER_DESCENDANTS ||
- // No focusable descendants
- (focusableCount == views.size())) {
- // Note that we can't call the superclass here, because it will
- // add all views in. So we need to do the same thing View does.
- if (!isFocusable()) {
- return;
- }
- if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE &&
- isInTouchMode() && !isFocusableInTouchMode()) {
- return;
- }
- if (views != null) {
- views.add(this);
- }
- }
- }
-
- /**
- * We only want the current page that is being shown to be touchable.
- */
- @Override
- public void addTouchables(ArrayList views) {
- // Note that we don't call super.addTouchables(), which means that
- // we don't call View.addTouchables(). This is okay because a ViewPager
- // is itself not touchable.
- for (int i = 0; i < getChildCount(); i++) {
- final View child = getChildAt(i);
- if (child.getVisibility() == VISIBLE) {
- ItemInfo ii = infoForChild(child);
- if (ii != null && ii.position == mCurItem) {
- child.addTouchables(views);
+ private Rect getChildRectInPagerCoordinates( Rect outRect, View child ) {
+ if ( outRect == null ) {
+ outRect = new Rect();
}
- }
- }
- }
+ if ( child == null ) {
+ outRect.set( 0, 0, 0, 0 );
+ return outRect;
+ }
+ outRect.left = child.getLeft();
+ outRect.right = child.getRight();
+ outRect.top = child.getTop();
+ outRect.bottom = child.getBottom();
- /**
- * We only want the current page that is being shown to be focusable.
- */
- @Override
- protected boolean onRequestFocusInDescendants(int direction,
- Rect previouslyFocusedRect) {
- int index;
- int increment;
- int end;
- int count = getChildCount();
- if ((direction & FOCUS_FORWARD) != 0) {
- index = 0;
- increment = 1;
- end = count;
- } else {
- index = count - 1;
- increment = -1;
- end = -1;
+ ViewParent parent = child.getParent();
+ while ( parent instanceof ViewGroup && parent != this ) {
+ final ViewGroup group = ( ViewGroup ) parent;
+ outRect.left += group.getLeft();
+ outRect.right += group.getRight();
+ outRect.top += group.getTop();
+ outRect.bottom += group.getBottom();
+
+ parent = group.getParent();
+ }
+ return outRect;
}
- for (int i = index; i != end; i += increment) {
- View child = getChildAt(i);
- if (child.getVisibility() == VISIBLE) {
- ItemInfo ii = infoForChild(child);
- if (ii != null && ii.position == mCurItem) {
- if (child.requestFocus(direction, previouslyFocusedRect)) {
+
+ boolean pageBack() {
+ if ( mCurItem > 0 ) {
+ setCurrentItem( mCurItem - 1, true );
return true;
- }
- }
- }
- }
- return false;
- }
-
- @Override
- public boolean dispatchPopulateAccessibilityEvent( AccessibilityEvent event) {
- // Dispatch scroll events from this ViewPager.
- if (event.getEventType() == AccessibilityEventCompat.TYPE_VIEW_SCROLLED) {
- return super.dispatchPopulateAccessibilityEvent(event);
- }
-
- // Dispatch all other accessibility events from the current page.
- final int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- final View child = getChildAt(i);
- if (child.getVisibility() == VISIBLE) {
- final ItemInfo ii = infoForChild(child);
- if (ii != null && ii.position == mCurItem &&
- child.dispatchPopulateAccessibilityEvent(event)) {
- return true;
- }
- }
- }
-
- return false;
- }
-
- @Override
- protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
- return new LayoutParams();
- }
-
- @Override
- protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
- return generateDefaultLayoutParams();
- }
-
- @Override
- protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
- return p instanceof LayoutParams && super.checkLayoutParams(p);
- }
-
- @Override
- public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
- return new LayoutParams(getContext(), attrs);
- }
-
- class MyAccessibilityDelegate extends AccessibilityDelegateCompat {
-
- @Override
- public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(host, event);
- event.setClassName(ViewPager.class.getName());
- final AccessibilityRecordCompat recordCompat = AccessibilityRecordCompat.obtain();
- recordCompat.setScrollable(canScroll());
- if (event.getEventType() == AccessibilityEventCompat.TYPE_VIEW_SCROLLED
- && mAdapter != null) {
- recordCompat.setItemCount(mAdapter.getCount());
- recordCompat.setFromIndex(mCurItem);
- recordCompat.setToIndex(mCurItem);
- }
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
- super.onInitializeAccessibilityNodeInfo(host, info);
- info.setClassName(ViewPager.class.getName());
- info.setScrollable(canScroll());
- if (mOrientation == Orientation.VERTICAL) {
- if (internalCanScrollVertically(1)) {
- info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);
- }
- if (internalCanScrollVertically(-1)) {
- info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);
- }
- } else {
- if (canScrollHorizontally(1)) {
- info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);
- }
- if (canScrollHorizontally(-1)) {
- info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);
- }
- }
- }
-
- @Override
- public boolean performAccessibilityAction(View host, int action, Bundle args) {
- if (super.performAccessibilityAction(host, action, args)) {
- return true;
- }
- switch (action) {
- case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD: {
- if ((mOrientation == Orientation.VERTICAL && internalCanScrollVertically(1))
- || (mOrientation == Orientation.HORIZONTAL && canScrollHorizontally(1))) {
- setCurrentItem(mCurItem + 1);
- return true;
- }
}
return false;
- case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD: {
- if ((mOrientation == Orientation.VERTICAL && internalCanScrollVertically(-1))
- || (mOrientation == Orientation.HORIZONTAL && canScrollHorizontally(-1))) {
- setCurrentItem(mCurItem - 1);
+ }
+
+ boolean pageForward() {
+ if ( mAdapter != null && mCurItem < ( mAdapter.getCount() - 1 ) ) {
+ setCurrentItem( mCurItem + 1, true );
return true;
- }
}
return false;
- }
- return false;
}
- private boolean canScroll() {
- return (mAdapter != null) && (mAdapter.getCount() > 1);
- }
- }
-
- private class PagerObserver extends DataSetObserver {
+ /**
+ * We only want the current page that is being shown to be focusable.
+ */
@Override
- public void onChanged() {
- dataSetChanged();
+ public void addFocusables( ArrayList< View > views, int direction, int focusableMode ) {
+ final int focusableCount = views.size();
+
+ final int descendantFocusability = getDescendantFocusability();
+
+ if ( descendantFocusability != FOCUS_BLOCK_DESCENDANTS ) {
+ for ( int i = 0; i < getChildCount(); i++ ) {
+ final View child = getChildAt( i );
+ if ( child.getVisibility() == VISIBLE ) {
+ ItemInfo ii = infoForChild( child );
+ if ( ii != null && ii.position == mCurItem ) {
+ child.addFocusables( views, direction, focusableMode );
+ }
+ }
+ }
+ }
+
+ // we add ourselves (if focusable) in all cases except for when we are
+ // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable. this is
+ // to avoid the focus search finding layouts when a more precise search
+ // among the focusable children would be more interesting.
+ if (
+ descendantFocusability != FOCUS_AFTER_DESCENDANTS ||
+ // No focusable descendants
+ ( focusableCount == views.size() ) ) {
+ // Note that we can't call the superclass here, because it will
+ // add all views in. So we need to do the same thing View does.
+ if ( !isFocusable() ) {
+ return;
+ }
+ if ( ( focusableMode & FOCUSABLES_TOUCH_MODE ) == FOCUSABLES_TOUCH_MODE &&
+ isInTouchMode() && !isFocusableInTouchMode() ) {
+ return;
+ }
+ if ( views != null ) {
+ views.add( this );
+ }
+ }
+ }
+
+ /**
+ * We only want the current page that is being shown to be touchable.
+ */
+ @Override
+ public void addTouchables( ArrayList< View > views ) {
+ // Note that we don't call super.addTouchables(), which means that
+ // we don't call View.addTouchables(). This is okay because a ViewPager
+ // is itself not touchable.
+ for ( int i = 0; i < getChildCount(); i++ ) {
+ final View child = getChildAt( i );
+ if ( child.getVisibility() == VISIBLE ) {
+ ItemInfo ii = infoForChild( child );
+ if ( ii != null && ii.position == mCurItem ) {
+ child.addTouchables( views );
+ }
+ }
+ }
+ }
+
+ /**
+ * We only want the current page that is being shown to be focusable.
+ */
+ @Override
+ protected boolean onRequestFocusInDescendants( int direction,
+ Rect previouslyFocusedRect ) {
+ int index;
+ int increment;
+ int end;
+ int count = getChildCount();
+ if ( ( direction & FOCUS_FORWARD ) != 0 ) {
+ index = 0;
+ increment = 1;
+ end = count;
+ } else {
+ index = count - 1;
+ increment = -1;
+ end = -1;
+ }
+ for ( int i = index; i != end; i += increment ) {
+ View child = getChildAt( i );
+ if ( child.getVisibility() == VISIBLE ) {
+ ItemInfo ii = infoForChild( child );
+ if ( ii != null && ii.position == mCurItem ) {
+ if ( child.requestFocus( direction, previouslyFocusedRect ) ) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
}
@Override
- public void onInvalidated() {
- dataSetChanged();
- }
- }
+ public boolean dispatchPopulateAccessibilityEvent( AccessibilityEvent event ) {
+ // Dispatch scroll events from this ViewPager.
+ if ( event.getEventType() == AccessibilityEventCompat.TYPE_VIEW_SCROLLED ) {
+ return super.dispatchPopulateAccessibilityEvent( event );
+ }
- /**
- * Layout parameters that should be supplied for views added to a
- * ViewPager.
- */
- public static class LayoutParams extends ViewGroup.LayoutParams {
- /**
- * true if this view is a decoration on the pager itself and not
- * a view supplied by the adapter.
- */
- public boolean isDecor;
+ // Dispatch all other accessibility events from the current page.
+ final int childCount = getChildCount();
+ for ( int i = 0; i < childCount; i++ ) {
+ final View child = getChildAt( i );
+ if ( child.getVisibility() == VISIBLE ) {
+ final ItemInfo ii = infoForChild( child );
+ if ( ii != null && ii.position == mCurItem &&
+ child.dispatchPopulateAccessibilityEvent( event ) ) {
+ return true;
+ }
+ }
+ }
- /**
- * Gravity setting for use on decor views only:
- * Where to position the view page within the overall ViewPager
- * container; constants are defined in {@link android.view.Gravity}.
- */
- public int gravity;
-
- /**
- * Width as a 0-1 multiplier of the measured pager height
- */
- float heightFactor = 0.f;
-
- /**
- * Width as a 0-1 multiplier of the measured pager width
- */
- float widthFactor = 0.f;
-
- /**
- * true if this view was added during layout and needs to be measured
- * before being positioned.
- */
- boolean needsMeasure;
-
- /**
- * Adapter position this view is for if !isDecor
- */
- int position;
-
- /**
- * Current child index within the ViewPager that this view occupies
- */
- int childIndex;
-
- public LayoutParams() {
- super(FILL_PARENT, FILL_PARENT);
+ return false;
}
- public LayoutParams(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
- gravity = a.getInteger(0, Gravity.TOP);
- a.recycle();
- }
- }
-
- static class ViewPositionComparator implements Comparator {
@Override
- public int compare(View lhs, View rhs) {
- final LayoutParams llp = (LayoutParams) lhs.getLayoutParams();
- final LayoutParams rlp = (LayoutParams) rhs.getLayoutParams();
- if (llp.isDecor != rlp.isDecor) {
- return llp.isDecor ? 1 : -1;
- }
- return llp.position - rlp.position;
+ protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
+ return new LayoutParams();
}
- }
- // Following classes and the interface are needed for the Maven Central upload script to work properly.
- // They are being introduced here, sort of temporarily (until I find a better solution for this issue).
+ @Override
+ protected ViewGroup.LayoutParams generateLayoutParams( ViewGroup.LayoutParams p ) {
+ return generateDefaultLayoutParams();
+ }
- /**
- * Callbacks a {@link Parcelable} creator should implement.
- */
- public interface ParcelableCompatCreatorCallbacks {
+ @Override
+ protected boolean checkLayoutParams( ViewGroup.LayoutParams p ) {
+ return p instanceof LayoutParams && super.checkLayoutParams( p );
+ }
+
+ @Override
+ public ViewGroup.LayoutParams generateLayoutParams( AttributeSet attrs ) {
+ return new LayoutParams( getContext(), attrs );
+ }
+
+ class MyAccessibilityDelegate extends AccessibilityDelegateCompat {
+
+ @Override
+ public void onInitializeAccessibilityEvent( View host, AccessibilityEvent event ) {
+ super.onInitializeAccessibilityEvent( host, event );
+ event.setClassName( ViewPager.class.getName() );
+ final AccessibilityRecordCompat recordCompat = AccessibilityRecordCompat.obtain();
+ recordCompat.setScrollable( canScroll() );
+ if ( event.getEventType() == AccessibilityEventCompat.TYPE_VIEW_SCROLLED
+ && mAdapter != null ) {
+ recordCompat.setItemCount( mAdapter.getCount() );
+ recordCompat.setFromIndex( mCurItem );
+ recordCompat.setToIndex( mCurItem );
+ }
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo( View host, AccessibilityNodeInfoCompat info ) {
+ super.onInitializeAccessibilityNodeInfo( host, info );
+ info.setClassName( ViewPager.class.getName() );
+ info.setScrollable( canScroll() );
+ if ( mOrientation == Orientation.VERTICAL ) {
+ if ( internalCanScrollVertically( 1 ) ) {
+ info.addAction( AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD );
+ }
+ if ( internalCanScrollVertically( -1 ) ) {
+ info.addAction( AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD );
+ }
+ } else {
+ if ( canScrollHorizontally( 1 ) ) {
+ info.addAction( AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD );
+ }
+ if ( canScrollHorizontally( -1 ) ) {
+ info.addAction( AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD );
+ }
+ }
+ }
+
+ @Override
+ public boolean performAccessibilityAction( View host, int action, Bundle args ) {
+ if ( super.performAccessibilityAction( host, action, args ) ) {
+ return true;
+ }
+ switch ( action ) {
+ case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD: {
+ if ( ( mOrientation == Orientation.VERTICAL && internalCanScrollVertically( 1 ) )
+ || ( mOrientation == Orientation.HORIZONTAL && canScrollHorizontally( 1 ) ) ) {
+ setCurrentItem( mCurItem + 1 );
+ return true;
+ }
+ }
+ return false;
+ case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD: {
+ if ( ( mOrientation == Orientation.VERTICAL && internalCanScrollVertically( -1 ) )
+ || ( mOrientation == Orientation.HORIZONTAL && canScrollHorizontally( -1 ) ) ) {
+ setCurrentItem( mCurItem - 1 );
+ return true;
+ }
+ }
+ return false;
+ }
+ return false;
+ }
+
+ private boolean canScroll() {
+ return ( mAdapter != null ) && ( mAdapter.getCount() > 1 );
+ }
+ }
+
+ private class PagerObserver extends DataSetObserver {
+ @Override
+ public void onChanged() {
+ dataSetChanged();
+ }
+
+ @Override
+ public void onInvalidated() {
+ dataSetChanged();
+ }
+ }
/**
- * Create a new instance of the Parcelable class, instantiating it
- * from the given Parcel whose data had previously been written by
- * {@link Parcelable#writeToParcel Parcelable.writeToParcel()} and
- * using the given ClassLoader.
- *
- * @param in The Parcel to read the object's data from.
- * @param loader The ClassLoader that this object is being created in.
- * @return Returns a new instance of the Parcelable class.
+ * Layout parameters that should be supplied for views added to a
+ * ViewPager.
*/
- public T createFromParcel(Parcel in, ClassLoader loader);
+ public static class LayoutParams extends ViewGroup.LayoutParams {
+ /**
+ * true if this view is a decoration on the pager itself and not
+ * a view supplied by the adapter.
+ */
+ public boolean isDecor;
+
+ /**
+ * Gravity setting for use on decor views only:
+ * Where to position the view page within the overall ViewPager
+ * container; constants are defined in {@link android.view.Gravity}.
+ */
+ public int gravity;
+
+ /**
+ * Width as a 0-1 multiplier of the measured pager height
+ */
+ float heightFactor = 0.f;
+
+ /**
+ * Width as a 0-1 multiplier of the measured pager width
+ */
+ float widthFactor = 0.f;
+
+ /**
+ * true if this view was added during layout and needs to be measured
+ * before being positioned.
+ */
+ boolean needsMeasure;
+
+ /**
+ * Adapter position this view is for if !isDecor
+ */
+ int position;
+
+ /**
+ * Current child index within the ViewPager that this view occupies
+ */
+ int childIndex;
+
+ public LayoutParams() {
+ super( FILL_PARENT, FILL_PARENT );
+ }
+
+ public LayoutParams( Context context, AttributeSet attrs ) {
+ super( context, attrs );
+
+ final TypedArray a = context.obtainStyledAttributes( attrs, LAYOUT_ATTRS );
+ gravity = a.getInteger( 0, Gravity.TOP );
+ a.recycle();
+ }
+ }
+
+ static class ViewPositionComparator implements Comparator< View > {
+ @Override
+ public int compare( View lhs, View rhs ) {
+ final LayoutParams llp = ( LayoutParams ) lhs.getLayoutParams();
+ final LayoutParams rlp = ( LayoutParams ) rhs.getLayoutParams();
+ if ( llp.isDecor != rlp.isDecor ) {
+ return llp.isDecor ? 1 : -1;
+ }
+ return llp.position - rlp.position;
+ }
+ }
+
+ // Following classes and the interface are needed for the Maven Central upload script to work properly.
+ // They are being introduced here, sort of temporarily (until I find a better solution for this issue).
/**
- * Create a new array of the Parcelable class.
- *
- * @param size Size of the array.
- * @return Returns an array of the Parcelable class, with every entry
- * initialized to null.
+ * Callbacks a {@link Parcelable} creator should implement.
*/
- public T[] newArray(int size);
- }
+ public interface ParcelableCompatCreatorCallbacks< T > {
- /**
- * Helper for accessing features in {@link android.os.Parcelable}
- * introduced after API level 4 in a backwards compatible fashion.
- */
- public static class ParcelableCompat {
+ /**
+ * Create a new instance of the Parcelable class, instantiating it
+ * from the given Parcel whose data had previously been written by
+ * {@link Parcelable#writeToParcel Parcelable.writeToParcel()} and
+ * using the given ClassLoader.
+ *
+ * @param in The Parcel to read the object's data from.
+ * @param loader The ClassLoader that this object is being created in.
+ * @return Returns a new instance of the Parcelable class.
+ */
+ public T createFromParcel( Parcel in, ClassLoader loader );
+
+ /**
+ * Create a new array of the Parcelable class.
+ *
+ * @param size Size of the array.
+ * @return Returns an array of the Parcelable class, with every entry
+ * initialized to null.
+ */
+ public T[] newArray( int size );
+ }
/**
- * Factory method for {@link Parcelable.Creator}.
- *
- * @param callbacks Creator callbacks implementation.
- * @return New creator.
+ * Helper for accessing features in {@link android.os.Parcelable}
+ * introduced after API level 4 in a backwards compatible fashion.
*/
- public static Parcelable.Creator newCreator(
- OrientedViewPager.ParcelableCompatCreatorCallbacks callbacks) {
- if (android.os.Build.VERSION.SDK_INT >= 13) {
- return ParcelableCompatCreatorHoneycombMR2Stub.instantiate(callbacks);
- }
- return new CompatCreator(callbacks);
+ public static class ParcelableCompat {
+
+ /**
+ * Factory method for {@link Parcelable.Creator}.
+ *
+ * @param callbacks Creator callbacks implementation.
+ * @return New creator.
+ */
+ public static < T > Parcelable.Creator< T > newCreator(
+ OrientedViewPager.ParcelableCompatCreatorCallbacks< T > callbacks ) {
+ if ( android.os.Build.VERSION.SDK_INT >= 13 ) {
+ return ParcelableCompatCreatorHoneycombMR2Stub.instantiate( callbacks );
+ }
+ return new CompatCreator< T >( callbacks );
+ }
+
+ public static class CompatCreator< T > implements Parcelable.Creator< T > {
+ final OrientedViewPager.ParcelableCompatCreatorCallbacks< T > mCallbacks;
+
+ public CompatCreator( OrientedViewPager.ParcelableCompatCreatorCallbacks< T > callbacks ) {
+ mCallbacks = callbacks;
+ }
+
+ @Override
+ public T createFromParcel( Parcel source ) {
+ return mCallbacks.createFromParcel( source, null );
+ }
+
+ @Override
+ public T[] newArray( int size ) {
+ return mCallbacks.newArray( size );
+ }
+ }
}
- public static class CompatCreator implements Parcelable.Creator {
- final OrientedViewPager.ParcelableCompatCreatorCallbacks mCallbacks;
-
- public CompatCreator(OrientedViewPager.ParcelableCompatCreatorCallbacks callbacks) {
- mCallbacks = callbacks;
- }
-
- @Override
- public T createFromParcel(Parcel source) {
- return mCallbacks.createFromParcel(source, null);
- }
-
- @Override
- public T[] newArray(int size) {
- return mCallbacks.newArray(size);
- }
- }
- }
-
- static class ParcelableCompatCreatorHoneycombMR2Stub {
- public static Parcelable.Creator instantiate(
- OrientedViewPager.ParcelableCompatCreatorCallbacks callbacks) {
- return new ParcelableCompatCreatorHoneycombMR2(callbacks);
- }
- }
-
- static class ParcelableCompatCreatorHoneycombMR2 implements Parcelable.ClassLoaderCreator {
- private final OrientedViewPager.ParcelableCompatCreatorCallbacks mCallbacks;
-
- public ParcelableCompatCreatorHoneycombMR2(
- OrientedViewPager.ParcelableCompatCreatorCallbacks callbacks) {
- mCallbacks = callbacks;
+ static class ParcelableCompatCreatorHoneycombMR2Stub {
+ public static < T > Parcelable.Creator< T > instantiate(
+ OrientedViewPager.ParcelableCompatCreatorCallbacks< T > callbacks ) {
+ return new ParcelableCompatCreatorHoneycombMR2< T >( callbacks );
+ }
}
- public T createFromParcel(Parcel in) {
- return mCallbacks.createFromParcel(in, null);
- }
+ static class ParcelableCompatCreatorHoneycombMR2< T > implements Parcelable.ClassLoaderCreator< T > {
+ private final OrientedViewPager.ParcelableCompatCreatorCallbacks< T > mCallbacks;
- public T createFromParcel(Parcel in, ClassLoader loader) {
- return mCallbacks.createFromParcel(in, loader);
- }
+ public ParcelableCompatCreatorHoneycombMR2(
+ OrientedViewPager.ParcelableCompatCreatorCallbacks< T > callbacks ) {
+ mCallbacks = callbacks;
+ }
- public T[] newArray(int size) {
- return mCallbacks.newArray(size);
+ public T createFromParcel( Parcel in ) {
+ return mCallbacks.createFromParcel( in, null );
+ }
+
+ public T createFromParcel( Parcel in, ClassLoader loader ) {
+ return mCallbacks.createFromParcel( in, loader );
+ }
+
+ public T[] newArray( int size ) {
+ return mCallbacks.newArray( size );
+ }
}
- }
}
\ No newline at end of file
diff --git a/modules/mogo-module-main/src/main/res/layout/module_main_activity_main.xml b/modules/mogo-module-main/src/main/res/layout/module_main_activity_main.xml
index 3aec6055ea..dc896856ae 100644
--- a/modules/mogo-module-main/src/main/res/layout/module_main_activity_main.xml
+++ b/modules/mogo-module-main/src/main/res/layout/module_main_activity_main.xml
@@ -1,47 +1,48 @@
-
+ android:layout_height="match_parent"
+ android:orientation="vertical">
-
-
-
-
-
+
+
+ android:layout_weight="1"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintTop_toTopOf="parent">
-
-
-
-
-
+
-
+
-
-
-
\ No newline at end of file
+
+
+