豎向滑動的viewpager
阿新 • • 發佈:2019-01-31
package com.gome.share.widget;/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Modified by Martin Sonc
* http://gplus.to/sonc
* @LambergaR
*
* 3/2013
*/
import android.content.Context;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.*;
import android.support.v4.os.ParcelableCompat;
import android.support.v4.os.ParcelableCompatCreatorCallbacks;
import android.support.v4.view.*;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.support.v4.widget.EdgeEffectCompat;
import android.util.AttributeSet;
import android.util.FloatMath;
import android.util.Log;
import android.view.*;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.Interpolator;
import android.widget.Scroller;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
/**
* Layout manager that allows the user to flip left and right
* through pages of data. You supply an implementation of a
* {@link android.support.v4.view.PagerAdapter} to generate the pages that the view shows.
* <p/>
* <p>Note this class is currently under early design and
* development. The API will likely change in later updates of
* the compatibility library, requiring changes to the source code
* of apps when they are compiled against the newer version.</p>
* <p/>
* <p>ViewPager is most often used in conjunction with {@link android.app.Fragment},
* which is a convenient way to supply and manage the lifecycle of each page.
* There are standard adapters implemented for using fragments with the ViewPager,
* which cover the most common use cases. These are
* {@link android.support.v4.app.FragmentPagerAdapter},
* {@link android.support.v4.app.FragmentStatePagerAdapter},
* {@link android.support.v4.app.FragmentPagerAdapter}, and
* {@link android.support.v4.app.FragmentStatePagerAdapter}; each of these
* classes have simple code showing how to build a full user interface
* with them.
* <p/>
* <p>Here is a more complicated example of ViewPager, using it in conjuction
* with {@link android.app.ActionBar} tabs. You can find other examples of using
* ViewPager in the API 4+ Support Demos and API 13+ Support Demos sample code.
* <p/>
* {@sample development/samples/Support13Demos/src/com/example/android/supportv13/app/ActionBarTabsPager.java
* complete}
*/
public class VerticalViewPager extends ViewGroup {
private static final String TAG = "VerticalViewPager";
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
public View mFirstChild = null;
private static final int[] LAYOUT_ATTRS = new int[]{
android.R.attr.layout_gravity
};
static class ItemInfo {
Object object;
int position;
boolean scrolling;
float widthFactor;
float heightFactor;
float offset;
}
private static final Comparator<ItemInfo> COMPARATOR = new Comparator<ItemInfo>() {
@Override
public int compare(ItemInfo lhs, ItemInfo rhs) {
return lhs.position - rhs.position;
}
};
private static final Interpolator sInterpolator = new Interpolator() {
public float getInterpolation(float t) {
t -= 1.0f;
return t * t * t * t * t + 1.0f;
}
};
private final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>();
private final ItemInfo mTempItem = new ItemInfo();
private final Rect mTempRect = new Rect();
private 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 mLeftPageBounds;
private int mRightPageBounds;
// 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 boolean mInLayout;
private boolean mScrollingCacheEnabled;
private boolean mPopulatePending;
private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES;
private boolean mIsBeingDragged;
private boolean mIsUnableToDrag;
private int mDefaultGutterSize;
private int mGutterSize;
private int mTouchSlop;
private float mInitialMotionX;
private float mInitialMotionY;
/**
* Position of the last motion event.
*/
private float mLastMotionX;
private float mLastMotionY;
/**
* ID of the active pointer. This is used to retain consistency during
* drags/flings if multiple pointers are used.
*/
private int mActivePointerId = INVALID_POINTER;
/**
* Sentinel value for no current active pointer.
* Used by {@link #mActivePointerId}.
*/
private static final int INVALID_POINTER = -1;
/**
* Determines speed during touch scrolling
*/
private VelocityTracker mVelocityTracker;
private int mMinimumVelocity;
private int mMaximumVelocity;
private int mFlingDistance;
private int mCloseEnough;
private int mSeenPositionMin;
private int mSeenPositionMax;
// 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 mTopEdge;
private EdgeEffectCompat mBottomEdge;
private boolean mFirstLayout = true;
private boolean mCalledSuper;
private int mDecorChildCount;
private OnPageChangeListener mOnPageChangeListener;
private OnPageChangeListener mInternalPageChangeListener;
private OnAdapterChangeListener mAdapterChangeListener;
private 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;
/**
* Callback interface for responding to changing state of the selected page.
*/
public interface OnPageChangeListener {
/**
* This method will be invoked when the current page is scrolled, either as part
* of a programmatically initiated smooth scroll or a user initiated touch scroll.
*
* @param position Position index of the first page currently being displayed.
* Page position+1 will be visible if positionOffset is nonzero.
* @param positionOffset Value from [0, 1) indicating the offset from the page at position.
* @param positionOffsetPixels Value in pixels indicating the offset from position.
*/
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels);
/**
* This method will be invoked when a new page becomes selected. Animation is not
* necessarily complete.
*
* @param position Position index of the new selected page.
*/
public void onPageSelected(int position);
/**
* Called when the scroll state changes. Useful for discovering when the user
* begins dragging, when the pager is automatically settling to the current page,
* or when it is fully stopped/idle.
*
* @param state The new scroll state.
* @see com.gome.share.widget.VerticalViewPager#SCROLL_STATE_IDLE
* @see com.gome.share.widget.VerticalViewPager#SCROLL_STATE_DRAGGING
* @see com.gome.share.widget.VerticalViewPager#SCROLL_STATE_SETTLING
*/
public void onPageScrollStateChanged(int state);
}
/**
* Simple implementation of the {@link com.gome.share.widget.VerticalViewPager.OnPageChangeListener} interface with stub
* implementations of each method. Extend this if you do not intend to override
* every method of {@link com.gome.share.widget.VerticalViewPager.OnPageChangeListener}.
*/
public static class SimpleOnPageChangeListener implements OnPageChangeListener {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
// This space for rent
}
@Override
public void onPageSelected(int position) {
// This space for rent
}
@Override
public void onPageScrollStateChanged(int state) {
// This space for rent
}
}
/**
* A PageTransformer is invoked whenever a visible/attached page is scrolled.
* This offers an opportunity for the application to apply a custom transformation
* to the page views using animation properties.
* <p/>
* <p>As property animation is only supported as of Android 3.0 and forward,
* setting a PageTransformer on a ViewPager on earlier platform versions will
* be ignored.</p>
*/
public interface PageTransformer {
/**
* Apply a property transformation to the given page.
*
* @param page Apply the transformation to this page
* @param position Position of page relative to the current front-and-center
* position of the pager. 0 is front and center. 1 is one full
* page position to the right, and -1 is one page position to the left.
*/
public void transformPage(View page, float position);
}
/**
* Used internally to monitor when adapters are switched.
*/
interface OnAdapterChangeListener {
public void onAdapterChanged(PagerAdapter oldAdapter, PagerAdapter newAdapter);
}
/**
* Used internally to tag special types of child views that should be added as
* pager decorations by default.
*/
interface Decor {
}
public VerticalViewPager(Context context) {
super(context);
initViewPager();
}
public VerticalViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
initViewPager();
}
void initViewPager() {
setWillNotDraw(false);
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
setFocusable(true);
final Context context = getContext();
mScroller = new Scroller(context, sInterpolator);
final ViewConfiguration configuration = ViewConfiguration.get(context);
mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
mTopEdge = new EdgeEffectCompat(context);
mBottomEdge = new EdgeEffectCompat(context);
final float density = context.getResources().getDisplayMetrics().density;
mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density);
mCloseEnough = (int) (CLOSE_ENOUGH * density);
mDefaultGutterSize = (int) (DEFAULT_GUTTER_SIZE * density);
ViewCompat.setAccessibilityDelegate(this, new MyAccessibilityDelegate());
if (ViewCompat.getImportantForAccessibility(this)
== ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
ViewCompat.setImportantForAccessibility(this,
ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
}
}
@Override
protected void onDetachedFromWindow() {
removeCallbacks(mEndScrollRunnable);
super.onDetachedFromWindow();
}
private void setScrollState(int newState) {
if (mScrollState == newState) {
return;
}
mScrollState = newState;
if (newState == SCROLL_STATE_DRAGGING) {
mSeenPositionMin = mSeenPositionMax = -1;
}
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;
if (mAdapter != null) {
if (mObserver == null) {
mObserver = new PagerObserver();
}
mAdapter.registerDataSetObserver(mObserver);
mPopulatePending = false;
mFirstLayout = true;
if (mRestoredCurItem >= 0) {
mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader);
setCurrentItemInternal(mRestoredCurItem, false, true);
mRestoredCurItem = -1;
mRestoredAdapterState = null;
mRestoredClassLoader = null;
} else {
populate();
}
}
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;
}
/**
* 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;
populate(item);
scrollToItem(item, smoothScroll, velocity, dispatchSelected);
}
private void scrollToItem(int item, boolean smoothScroll, int velocity,
boolean dispatchSelected) {
final ItemInfo curInfo = infoForPosition(item);
int destY = 0;
if (curInfo != null) {
final int height = getHeight();
destY = (int) (height * Math.max(mFirstOffset,
Math.min(curInfo.offset, mLastOffset)));
}
if (smoothScroll) {
smoothScrollTo(0, destY, velocity);
if (dispatchSelected && mOnPageChangeListener != null) {
mOnPageChangeListener.onPageSelected(item);
}
if (dispatchSelected && mInternalPageChangeListener != null) {
mInternalPageChangeListener.onPageSelected(item);
}
} else {
if (dispatchSelected && mOnPageChangeListener != null) {
mOnPageChangeListener.onPageSelected(item);
}
if (dispatchSelected && mInternalPageChangeListener != null) {
mInternalPageChangeListener.onPageSelected(item);
}
completeScroll(false);
scrollTo(0, destY);
}
}
/**
* Set a listener that will be invoked whenever the page changes or is incrementally
* scrolled. See {@link com.gome.share.widget.VerticalViewPager.OnPageChangeListener}.
*
* @param listener Listener to set
*/
public void setOnPageChangeListener(OnPageChangeListener listener) {
mOnPageChangeListener = listener;
}
/**
* Set a {@link com.gome.share.widget.VerticalViewPager.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.
* <p/>
* <p><em>Note:</em> 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.</p>
*
* @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, 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 (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.
*/
OnPageChangeListener setInternalPageChangeListener(OnPageChangeListener listener) {
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.
* <p/>
* <p>This is offered as an optimization. If you know in advance the number
* of pages you will need to support or have lazy-loading mechanisms in place
* on your pages, tweaking this setting can have benefits in perceived smoothness
* of paging animations and interaction. If you have a small number of pages (3-4)
* that you can keep active all at once, less time will be spent in layout for
* newly created view subtrees as the user pages back and forth.</p>
* <p/>
* <p>You should keep this limit low, especially if your pages have complex layouts.
* This setting defaults to 1.</p>
*
* @param limit How many pages will be kept offscreen in an idle state.
*/
public void setOffscreenPageLimit(int limit) {
if (limit < DEFAULT_OFFSCREEN_PAGES) {
Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " +
DEFAULT_OFFSCREEN_PAGES);
limit = DEFAULT_OFFSCREEN_PAGES;
}
if (limit != mOffscreenPageLimit) {
mOffscreenPageLimit = limit;
populate();
}
}
/**
* Set the margin between pages.
*
* @param marginPixels Distance between adjacent pages in pixels
* @see #getPageMargin()
* @see #setPageMarginDrawable(android.graphics.drawable.Drawable)
* @see #setPageMarginDrawable(int)
*/
public void setPageMargin(int marginPixels) {
final int oldMargin = mPageMargin;
mPageMargin = marginPixels;
final int width = getWidth();
recomputeScrollPosition(width, width, marginPixels, oldMargin);
requestLayout();
}
/**
* Return the margin between pages.
*
* @return The size of the margin in pixels
*/
public int getPageMargin() {
return mPageMargin;
}
/**
* Set a drawable that will be used to fill the margin between pages.
*
* @param d Drawable to display between pages
*/
public void setPageMarginDrawable(Drawable d) {
mMarginDrawable = d;
if (d != null) refreshDrawableState();
setWillNotDraw(d == null);
invalidate();
}
/**
* Set a drawable that will be used to fill the margin between pages.
*
* @param resId Resource ID of a drawable to display between pages
*/
public void setPageMarginDrawable(int resId) {
setPageMarginDrawable(getContext().getResources().getDrawable(resId));
}
@Override
protected boolean verifyDrawable(Drawable who) {
return super.verifyDrawable(who) || who == mMarginDrawable;
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
final Drawable d = mMarginDrawable;
if (d != null && d.isStateful()) {
d.setState(getDrawableState());
}
}
// We want the duration of the page snap animation to be influenced by the distance that
// the screen has to travel, however, we don't want this duration to be effected in a
// purely linear fashion. Instead, we use this method to moderate the effect that the distance
// of travel has on the overall snap duration.
float distanceInfluenceForSnapDuration(float f) {
f -= 0.5f; // center the values about 0.
f *= 0.3f * Math.PI / 2.0f;
return (float) Math.sin(f);
}
/**
* Like {@link android.view.View#scrollBy}, but scroll smoothly instead of immediately.
*
* @param x the number of pixels to scroll by on the X axis
* @param y the number of pixels to scroll by on the Y axis
*/
void smoothScrollTo(int x, int y) {
smoothScrollTo(x, y, 0);
}
/**
* Like {@link android.view.View#scrollBy}, but scroll smoothly instead of immediately.
*
* @param x the number of pixels to scroll by on the X axis
* @param y the number of pixels to scroll by on the Y axis
* @param velocity the velocity associated with a fling, if applicable. (0 otherwise)
*/
void smoothScrollTo(int x, int y, int velocity) {
// void smoothScrollTo(int y, int x, 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 height = getHeight();
final int halfHeight = height / 2;
final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / height);
final float distance = halfHeight + halfHeight * distanceInfluenceForSnapDuration(distanceRatio);
int duration = 0;
velocity = Math.abs(velocity);
if (velocity > 0) {
duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
} else {
final float pageHeight = height * mAdapter.getPageHeight(mCurItem);
final float pageDelta = (float) Math.abs(dx) / (pageHeight + 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.widthFactor = mAdapter.getPageWidth(position);
ii.heightFactor = mAdapter.getPageHeight(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.
boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1 &&
mItems.size() < mAdapter.getCount();
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, mAdapter.getCount() - 1));
needPopulate = true;
}
continue;
}
if (ii.position != newPos) {
if (ii.position == mCurItem) {
// Our current item changed position. Follow it.
newCurrItem = newPos;
}
ii.position = newPos;
needPopulate = true;
}
}
if (isUpdating) {
mAdapter.finishUpdate(this);
}
Collections.sort(mItems, COMPARATOR);
if (needPopulate) {
// Reset our known page widths; populate will recompute them.
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (!lp.isDecor) {
lp.widthFactor = 0.f;
lp.heightFactor = 0.f;
}
}
setCurrentItemInternal(newCurrItem, false, true);
requestLayout();
}
}
void populate() {
populate(mCurItem);
}
void populate(int newCurrentItem) {
ItemInfo oldCurInfo = null;
if (mCurItem != newCurrentItem) {
oldCurInfo = infoForPosition(mCurItem);
mCurItem = newCurrentItem;
}
if (mAdapter == null) {
return;
}
// Bail now if we are waiting to populate. This is to hold off
// on creating views from the time the user releases their finger to
// fling to a new position until we have finished the scroll to
// that position, avoiding glitches from happening at that point.
if (mPopulatePending) {
if (DEBUG) Log.i(TAG, "populate is pending, skipping for now...");
return;
}
// Also, don't populate until we are attached to a window. This is to
// avoid trying to populate before we have restored our view hierarchy
// state and conflicting with what is restored.
if (getWindowToken() == null) {
return;
}
mAdapter.startUpdate(this);
final int pageLimit = mOffscreenPageLimit;
final int startPos = Math.max(0, mCurItem - pageLimit);
final int N = mAdapter.getCount();
final int endPos = Math.min(N - 1, mCurItem + pageLimit);
// 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 extraHeightLeft = 0f;
int itemIndex = curIndex - 1;
ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
final float topHeightNeeded = 2.f - curItem.heightFactor;
for (int pos = mCurItem - 1; pos >= 0; pos--) {
if (extraHeightLeft >= topHeightNeeded && 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) {
extraHeightLeft += ii.heightFactor;
itemIndex--;
ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
} else {
ii = addNewItem(pos, itemIndex + 1);
extraHeightLeft += ii.heightFactor;
curIndex++;
ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
}
}
float extraHeightBottom = curItem.heightFactor;
itemIndex = curIndex + 1;
if (extraHeightBottom < 2.f) {
ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
for (int pos = mCurItem + 1; pos < N; pos++) {
if (extraHeightBottom >= 2.f && 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) {
extraHeightBottom += ii.heightFactor;
itemIndex++;
ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
} else {
ii = addNewItem(pos, itemIndex);
itemIndex++;
extraHeightBottom += ii.heightFactor;
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 boolean sort = mDrawingOrder != DRAW_ORDER_DEFAULT;
if (sort) {
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);
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.heightFactor;
lp.position = ii.position;
}
}
if (sort) mDrawingOrderedChildren.add(child);
}
if (sort) {
Collections.sort(mDrawingOrderedChildren, sPositionComparator);
}
if (hasFocus()) {
View currentFocused = findFocus();
ItemInfo ii = currentFocused != null ? infoForAnyChild(currentFocused) : null;
if (ii == null || ii.position != mCurItem) {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
ii = infoForChild(child);
if (ii != null && ii.position == mCurItem) {
if (child.requestFocus(FOCUS_FORWARD)) {
break;
}
}
}
}
}
}
private void calculatePageOffsets(ItemInfo curItem, int curIndex, ItemInfo oldCurInfo) {
final int N = mAdapter.getCount();
final int height = getHeight();
final float marginOffset = height > 0 ? (float) mPageMargin / height : 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.heightFactor + 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.getPageHeight(pos) + marginOffset;
pos++;
}
ii.offset = offset;
offset += ii.heightFactor + 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.getPageHeight(pos) + marginOffset;
pos--;
}
offset -= ii.heightFactor + 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.heightFactor - 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.getPageHeight(pos--) + marginOffset;
}
offset -= ii.heightFactor + marginOffset;
ii.offset = offset;
if (ii.position == 0) mFirstOffset = offset;
}
offset = curItem.offset + curItem.heightFactor + 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.getPageHeight(pos++) + marginOffset;
}
if (ii.position == N - 1) {
mLastOffset = offset + ii.heightFactor - 1;
}
ii.offset = offset;
offset += ii.heightFactor + marginOffset;
}
// mNeedCalculatePageOffsets = false;
}
/**
* This is the persistent state that is saved by ViewPager. Only needed
* if you are creating a sublass of ViewPager that must save its own
* state, in which case it should implement a subclass of this which
* contains that state.
*/
public static class SavedState extends BaseSavedState {
int position;
Parcelable adapterState;
ClassLoader loader;
public SavedState(Parcelable superState) {
super(superState);
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt(position);
out.writeParcelable(adapterState, flags);
}
@Override
public String toString() {
return "FragmentPager.SavedState{"
+ Integer.toHexString(System.identityHashCode(this))
+ " position=" + position + "}";
}
public static final Creator<SavedState> CREATOR
= ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks<SavedState>() {
@Override
public SavedState createFromParcel(Parcel in, ClassLoader loader) {
return new SavedState(in, loader);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
});
SavedState(Parcel in, ClassLoader loader) {
super(in);
if (loader == null) {
loader = getClass().getClassLoader();
}
position = in.readInt();
adapterState = in.readParcelable(loader);
this.loader = loader;
}
}
@Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.position = mCurItem;
if (mAdapter != null) {
ss.adapterState = mAdapter.saveState();
}
return ss;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
if (!(state instanceof SavedState)) {
super.onRestoreInstanceState(state);
return;
}
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
if (mAdapter != null) {
mAdapter.restoreState(ss.adapterState, ss.loader);
setCurrentItemInternal(ss.position, false, true);
} else {
mRestoredCurItem = ss.position;
mRestoredAdapterState = ss.adapterState;
mRestoredClassLoader = ss.loader;
}
}
@Override
public void addView(View child, int index, 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);
}
}
}
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;
}
@SuppressWarnings("deprecation")
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (DEBUG) Log.d(TAG, "onMeasure");
// For simple implementation, or internal size is always 0.
// We depend on the container to specify the layout size of
// our view. We can't really know what it is since we will be
// adding and removing different arbitrary views and do not
// want the layout to change as this happens.
setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
getDefaultSize(0, heightMeasureSpec));
final int measuredWidth = getMeasuredWidth();
final int measuredHeight = getMeasuredHeight();
final int maxGutterSize = measuredHeight / 10;
mGutterSize = Math.min(maxGutterSize, mDefaultGutterSize);
// Children are just made to fill our space.
int childWidthSize = measuredWidth - getPaddingLeft() - getPaddingRight();
int childHeightSize = measuredHeight - 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, hei
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Modified by Martin Sonc
* http://gplus.to/sonc
* @LambergaR
*
* 3/2013
*/
import android.content.Context;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.*;
import android.support.v4.os.ParcelableCompat;
import android.support.v4.os.ParcelableCompatCreatorCallbacks;
import android.support.v4.view.*;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.support.v4.widget.EdgeEffectCompat;
import android.util.AttributeSet;
import android.util.FloatMath;
import android.util.Log;
import android.view.*;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.Interpolator;
import android.widget.Scroller;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
/**
* Layout manager that allows the user to flip left and right
* through pages of data. You supply an implementation of a
* {@link android.support.v4.view.PagerAdapter} to generate the pages that the view shows.
* <p/>
* <p>Note this class is currently under early design and
* development. The API will likely change in later updates of
* the compatibility library, requiring changes to the source code
* of apps when they are compiled against the newer version.</p>
* <p/>
* <p>ViewPager is most often used in conjunction with {@link android.app.Fragment},
* which is a convenient way to supply and manage the lifecycle of each page.
* There are standard adapters implemented for using fragments with the ViewPager,
* which cover the most common use cases. These are
* {@link android.support.v4.app.FragmentPagerAdapter},
* {@link android.support.v4.app.FragmentStatePagerAdapter},
* {@link android.support.v4.app.FragmentPagerAdapter}, and
* {@link android.support.v4.app.FragmentStatePagerAdapter}; each of these
* classes have simple code showing how to build a full user interface
* with them.
* <p/>
* <p>Here is a more complicated example of ViewPager, using it in conjuction
* with {@link android.app.ActionBar} tabs. You can find other examples of using
* ViewPager in the API 4+ Support Demos and API 13+ Support Demos sample code.
* <p/>
* {@sample development/samples/Support13Demos/src/com/example/android/supportv13/app/ActionBarTabsPager.java
* complete}
*/
public class VerticalViewPager extends ViewGroup {
private static final String TAG = "VerticalViewPager";
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
public View mFirstChild = null;
private static final int[] LAYOUT_ATTRS = new int[]{
android.R.attr.layout_gravity
};
static class ItemInfo {
Object object;
int position;
boolean scrolling;
float widthFactor;
float heightFactor;
float offset;
}
private static final Comparator<ItemInfo> COMPARATOR = new Comparator<ItemInfo>() {
@Override
public int compare(ItemInfo lhs, ItemInfo rhs) {
return lhs.position - rhs.position;
}
};
private static final Interpolator sInterpolator = new Interpolator() {
public float getInterpolation(float t) {
t -= 1.0f;
return t * t * t * t * t + 1.0f;
}
};
private final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>();
private final ItemInfo mTempItem = new ItemInfo();
private final Rect mTempRect = new Rect();
private 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 mLeftPageBounds;
private int mRightPageBounds;
// 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 boolean mInLayout;
private boolean mScrollingCacheEnabled;
private boolean mPopulatePending;
private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES;
private boolean mIsBeingDragged;
private boolean mIsUnableToDrag;
private int mDefaultGutterSize;
private int mGutterSize;
private int mTouchSlop;
private float mInitialMotionX;
private float mInitialMotionY;
/**
* Position of the last motion event.
*/
private float mLastMotionX;
private float mLastMotionY;
/**
* ID of the active pointer. This is used to retain consistency during
* drags/flings if multiple pointers are used.
*/
private int mActivePointerId = INVALID_POINTER;
/**
* Sentinel value for no current active pointer.
* Used by {@link #mActivePointerId}.
*/
private static final int INVALID_POINTER = -1;
/**
* Determines speed during touch scrolling
*/
private VelocityTracker mVelocityTracker;
private int mMinimumVelocity;
private int mMaximumVelocity;
private int mFlingDistance;
private int mCloseEnough;
private int mSeenPositionMin;
private int mSeenPositionMax;
// 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 mTopEdge;
private EdgeEffectCompat mBottomEdge;
private boolean mFirstLayout = true;
private boolean mCalledSuper;
private int mDecorChildCount;
private OnPageChangeListener mOnPageChangeListener;
private OnPageChangeListener mInternalPageChangeListener;
private OnAdapterChangeListener mAdapterChangeListener;
private 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;
/**
* Callback interface for responding to changing state of the selected page.
*/
public interface OnPageChangeListener {
/**
* This method will be invoked when the current page is scrolled, either as part
* of a programmatically initiated smooth scroll or a user initiated touch scroll.
*
* @param position Position index of the first page currently being displayed.
* Page position+1 will be visible if positionOffset is nonzero.
* @param positionOffset Value from [0, 1) indicating the offset from the page at position.
* @param positionOffsetPixels Value in pixels indicating the offset from position.
*/
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels);
/**
* This method will be invoked when a new page becomes selected. Animation is not
* necessarily complete.
*
* @param position Position index of the new selected page.
*/
public void onPageSelected(int position);
/**
* Called when the scroll state changes. Useful for discovering when the user
* begins dragging, when the pager is automatically settling to the current page,
* or when it is fully stopped/idle.
*
* @param state The new scroll state.
* @see com.gome.share.widget.VerticalViewPager#SCROLL_STATE_IDLE
* @see com.gome.share.widget.VerticalViewPager#SCROLL_STATE_DRAGGING
* @see com.gome.share.widget.VerticalViewPager#SCROLL_STATE_SETTLING
*/
public void onPageScrollStateChanged(int state);
}
/**
* Simple implementation of the {@link com.gome.share.widget.VerticalViewPager.OnPageChangeListener} interface with stub
* implementations of each method. Extend this if you do not intend to override
* every method of {@link com.gome.share.widget.VerticalViewPager.OnPageChangeListener}.
*/
public static class SimpleOnPageChangeListener implements OnPageChangeListener {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
// This space for rent
}
@Override
public void onPageSelected(int position) {
// This space for rent
}
@Override
public void onPageScrollStateChanged(int state) {
// This space for rent
}
}
/**
* A PageTransformer is invoked whenever a visible/attached page is scrolled.
* This offers an opportunity for the application to apply a custom transformation
* to the page views using animation properties.
* <p/>
* <p>As property animation is only supported as of Android 3.0 and forward,
* setting a PageTransformer on a ViewPager on earlier platform versions will
* be ignored.</p>
*/
public interface PageTransformer {
/**
* Apply a property transformation to the given page.
*
* @param page Apply the transformation to this page
* @param position Position of page relative to the current front-and-center
* position of the pager. 0 is front and center. 1 is one full
* page position to the right, and -1 is one page position to the left.
*/
public void transformPage(View page, float position);
}
/**
* Used internally to monitor when adapters are switched.
*/
interface OnAdapterChangeListener {
public void onAdapterChanged(PagerAdapter oldAdapter, PagerAdapter newAdapter);
}
/**
* Used internally to tag special types of child views that should be added as
* pager decorations by default.
*/
interface Decor {
}
public VerticalViewPager(Context context) {
super(context);
initViewPager();
}
public VerticalViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
initViewPager();
}
void initViewPager() {
setWillNotDraw(false);
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
setFocusable(true);
final Context context = getContext();
mScroller = new Scroller(context, sInterpolator);
final ViewConfiguration configuration = ViewConfiguration.get(context);
mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
mTopEdge = new EdgeEffectCompat(context);
mBottomEdge = new EdgeEffectCompat(context);
final float density = context.getResources().getDisplayMetrics().density;
mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density);
mCloseEnough = (int) (CLOSE_ENOUGH * density);
mDefaultGutterSize = (int) (DEFAULT_GUTTER_SIZE * density);
ViewCompat.setAccessibilityDelegate(this, new MyAccessibilityDelegate());
if (ViewCompat.getImportantForAccessibility(this)
== ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
ViewCompat.setImportantForAccessibility(this,
ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
}
}
@Override
protected void onDetachedFromWindow() {
removeCallbacks(mEndScrollRunnable);
super.onDetachedFromWindow();
}
private void setScrollState(int newState) {
if (mScrollState == newState) {
return;
}
mScrollState = newState;
if (newState == SCROLL_STATE_DRAGGING) {
mSeenPositionMin = mSeenPositionMax = -1;
}
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;
if (mAdapter != null) {
if (mObserver == null) {
mObserver = new PagerObserver();
}
mAdapter.registerDataSetObserver(mObserver);
mPopulatePending = false;
mFirstLayout = true;
if (mRestoredCurItem >= 0) {
mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader);
setCurrentItemInternal(mRestoredCurItem, false, true);
mRestoredCurItem = -1;
mRestoredAdapterState = null;
mRestoredClassLoader = null;
} else {
populate();
}
}
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;
}
/**
* 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;
populate(item);
scrollToItem(item, smoothScroll, velocity, dispatchSelected);
}
private void scrollToItem(int item, boolean smoothScroll, int velocity,
boolean dispatchSelected) {
final ItemInfo curInfo = infoForPosition(item);
int destY = 0;
if (curInfo != null) {
final int height = getHeight();
destY = (int) (height * Math.max(mFirstOffset,
Math.min(curInfo.offset, mLastOffset)));
}
if (smoothScroll) {
smoothScrollTo(0, destY, velocity);
if (dispatchSelected && mOnPageChangeListener != null) {
mOnPageChangeListener.onPageSelected(item);
}
if (dispatchSelected && mInternalPageChangeListener != null) {
mInternalPageChangeListener.onPageSelected(item);
}
} else {
if (dispatchSelected && mOnPageChangeListener != null) {
mOnPageChangeListener.onPageSelected(item);
}
if (dispatchSelected && mInternalPageChangeListener != null) {
mInternalPageChangeListener.onPageSelected(item);
}
completeScroll(false);
scrollTo(0, destY);
}
}
/**
* Set a listener that will be invoked whenever the page changes or is incrementally
* scrolled. See {@link com.gome.share.widget.VerticalViewPager.OnPageChangeListener}.
*
* @param listener Listener to set
*/
public void setOnPageChangeListener(OnPageChangeListener listener) {
mOnPageChangeListener = listener;
}
/**
* Set a {@link com.gome.share.widget.VerticalViewPager.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.
* <p/>
* <p><em>Note:</em> 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.</p>
*
* @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, 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 (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.
*/
OnPageChangeListener setInternalPageChangeListener(OnPageChangeListener listener) {
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.
* <p/>
* <p>This is offered as an optimization. If you know in advance the number
* of pages you will need to support or have lazy-loading mechanisms in place
* on your pages, tweaking this setting can have benefits in perceived smoothness
* of paging animations and interaction. If you have a small number of pages (3-4)
* that you can keep active all at once, less time will be spent in layout for
* newly created view subtrees as the user pages back and forth.</p>
* <p/>
* <p>You should keep this limit low, especially if your pages have complex layouts.
* This setting defaults to 1.</p>
*
* @param limit How many pages will be kept offscreen in an idle state.
*/
public void setOffscreenPageLimit(int limit) {
if (limit < DEFAULT_OFFSCREEN_PAGES) {
Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " +
DEFAULT_OFFSCREEN_PAGES);
limit = DEFAULT_OFFSCREEN_PAGES;
}
if (limit != mOffscreenPageLimit) {
mOffscreenPageLimit = limit;
populate();
}
}
/**
* Set the margin between pages.
*
* @param marginPixels Distance between adjacent pages in pixels
* @see #getPageMargin()
* @see #setPageMarginDrawable(android.graphics.drawable.Drawable)
* @see #setPageMarginDrawable(int)
*/
public void setPageMargin(int marginPixels) {
final int oldMargin = mPageMargin;
mPageMargin = marginPixels;
final int width = getWidth();
recomputeScrollPosition(width, width, marginPixels, oldMargin);
requestLayout();
}
/**
* Return the margin between pages.
*
* @return The size of the margin in pixels
*/
public int getPageMargin() {
return mPageMargin;
}
/**
* Set a drawable that will be used to fill the margin between pages.
*
* @param d Drawable to display between pages
*/
public void setPageMarginDrawable(Drawable d) {
mMarginDrawable = d;
if (d != null) refreshDrawableState();
setWillNotDraw(d == null);
invalidate();
}
/**
* Set a drawable that will be used to fill the margin between pages.
*
* @param resId Resource ID of a drawable to display between pages
*/
public void setPageMarginDrawable(int resId) {
setPageMarginDrawable(getContext().getResources().getDrawable(resId));
}
@Override
protected boolean verifyDrawable(Drawable who) {
return super.verifyDrawable(who) || who == mMarginDrawable;
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
final Drawable d = mMarginDrawable;
if (d != null && d.isStateful()) {
d.setState(getDrawableState());
}
}
// We want the duration of the page snap animation to be influenced by the distance that
// the screen has to travel, however, we don't want this duration to be effected in a
// purely linear fashion. Instead, we use this method to moderate the effect that the distance
// of travel has on the overall snap duration.
float distanceInfluenceForSnapDuration(float f) {
f -= 0.5f; // center the values about 0.
f *= 0.3f * Math.PI / 2.0f;
return (float) Math.sin(f);
}
/**
* Like {@link android.view.View#scrollBy}, but scroll smoothly instead of immediately.
*
* @param x the number of pixels to scroll by on the X axis
* @param y the number of pixels to scroll by on the Y axis
*/
void smoothScrollTo(int x, int y) {
smoothScrollTo(x, y, 0);
}
/**
* Like {@link android.view.View#scrollBy}, but scroll smoothly instead of immediately.
*
* @param x the number of pixels to scroll by on the X axis
* @param y the number of pixels to scroll by on the Y axis
* @param velocity the velocity associated with a fling, if applicable. (0 otherwise)
*/
void smoothScrollTo(int x, int y, int velocity) {
// void smoothScrollTo(int y, int x, 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 height = getHeight();
final int halfHeight = height / 2;
final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / height);
final float distance = halfHeight + halfHeight * distanceInfluenceForSnapDuration(distanceRatio);
int duration = 0;
velocity = Math.abs(velocity);
if (velocity > 0) {
duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
} else {
final float pageHeight = height * mAdapter.getPageHeight(mCurItem);
final float pageDelta = (float) Math.abs(dx) / (pageHeight + 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.widthFactor = mAdapter.getPageWidth(position);
ii.heightFactor = mAdapter.getPageHeight(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.
boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1 &&
mItems.size() < mAdapter.getCount();
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, mAdapter.getCount() - 1));
needPopulate = true;
}
continue;
}
if (ii.position != newPos) {
if (ii.position == mCurItem) {
// Our current item changed position. Follow it.
newCurrItem = newPos;
}
ii.position = newPos;
needPopulate = true;
}
}
if (isUpdating) {
mAdapter.finishUpdate(this);
}
Collections.sort(mItems, COMPARATOR);
if (needPopulate) {
// Reset our known page widths; populate will recompute them.
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (!lp.isDecor) {
lp.widthFactor = 0.f;
lp.heightFactor = 0.f;
}
}
setCurrentItemInternal(newCurrItem, false, true);
requestLayout();
}
}
void populate() {
populate(mCurItem);
}
void populate(int newCurrentItem) {
ItemInfo oldCurInfo = null;
if (mCurItem != newCurrentItem) {
oldCurInfo = infoForPosition(mCurItem);
mCurItem = newCurrentItem;
}
if (mAdapter == null) {
return;
}
// Bail now if we are waiting to populate. This is to hold off
// on creating views from the time the user releases their finger to
// fling to a new position until we have finished the scroll to
// that position, avoiding glitches from happening at that point.
if (mPopulatePending) {
if (DEBUG) Log.i(TAG, "populate is pending, skipping for now...");
return;
}
// Also, don't populate until we are attached to a window. This is to
// avoid trying to populate before we have restored our view hierarchy
// state and conflicting with what is restored.
if (getWindowToken() == null) {
return;
}
mAdapter.startUpdate(this);
final int pageLimit = mOffscreenPageLimit;
final int startPos = Math.max(0, mCurItem - pageLimit);
final int N = mAdapter.getCount();
final int endPos = Math.min(N - 1, mCurItem + pageLimit);
// 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 extraHeightLeft = 0f;
int itemIndex = curIndex - 1;
ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
final float topHeightNeeded = 2.f - curItem.heightFactor;
for (int pos = mCurItem - 1; pos >= 0; pos--) {
if (extraHeightLeft >= topHeightNeeded && 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) {
extraHeightLeft += ii.heightFactor;
itemIndex--;
ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
} else {
ii = addNewItem(pos, itemIndex + 1);
extraHeightLeft += ii.heightFactor;
curIndex++;
ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
}
}
float extraHeightBottom = curItem.heightFactor;
itemIndex = curIndex + 1;
if (extraHeightBottom < 2.f) {
ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
for (int pos = mCurItem + 1; pos < N; pos++) {
if (extraHeightBottom >= 2.f && 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) {
extraHeightBottom += ii.heightFactor;
itemIndex++;
ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
} else {
ii = addNewItem(pos, itemIndex);
itemIndex++;
extraHeightBottom += ii.heightFactor;
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 boolean sort = mDrawingOrder != DRAW_ORDER_DEFAULT;
if (sort) {
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);
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.heightFactor;
lp.position = ii.position;
}
}
if (sort) mDrawingOrderedChildren.add(child);
}
if (sort) {
Collections.sort(mDrawingOrderedChildren, sPositionComparator);
}
if (hasFocus()) {
View currentFocused = findFocus();
ItemInfo ii = currentFocused != null ? infoForAnyChild(currentFocused) : null;
if (ii == null || ii.position != mCurItem) {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
ii = infoForChild(child);
if (ii != null && ii.position == mCurItem) {
if (child.requestFocus(FOCUS_FORWARD)) {
break;
}
}
}
}
}
}
private void calculatePageOffsets(ItemInfo curItem, int curIndex, ItemInfo oldCurInfo) {
final int N = mAdapter.getCount();
final int height = getHeight();
final float marginOffset = height > 0 ? (float) mPageMargin / height : 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.heightFactor + 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.getPageHeight(pos) + marginOffset;
pos++;
}
ii.offset = offset;
offset += ii.heightFactor + 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.getPageHeight(pos) + marginOffset;
pos--;
}
offset -= ii.heightFactor + 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.heightFactor - 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.getPageHeight(pos--) + marginOffset;
}
offset -= ii.heightFactor + marginOffset;
ii.offset = offset;
if (ii.position == 0) mFirstOffset = offset;
}
offset = curItem.offset + curItem.heightFactor + 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.getPageHeight(pos++) + marginOffset;
}
if (ii.position == N - 1) {
mLastOffset = offset + ii.heightFactor - 1;
}
ii.offset = offset;
offset += ii.heightFactor + marginOffset;
}
// mNeedCalculatePageOffsets = false;
}
/**
* This is the persistent state that is saved by ViewPager. Only needed
* if you are creating a sublass of ViewPager that must save its own
* state, in which case it should implement a subclass of this which
* contains that state.
*/
public static class SavedState extends BaseSavedState {
int position;
Parcelable adapterState;
ClassLoader loader;
public SavedState(Parcelable superState) {
super(superState);
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt(position);
out.writeParcelable(adapterState, flags);
}
@Override
public String toString() {
return "FragmentPager.SavedState{"
+ Integer.toHexString(System.identityHashCode(this))
+ " position=" + position + "}";
}
public static final Creator<SavedState> CREATOR
= ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks<SavedState>() {
@Override
public SavedState createFromParcel(Parcel in, ClassLoader loader) {
return new SavedState(in, loader);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
});
SavedState(Parcel in, ClassLoader loader) {
super(in);
if (loader == null) {
loader = getClass().getClassLoader();
}
position = in.readInt();
adapterState = in.readParcelable(loader);
this.loader = loader;
}
}
@Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.position = mCurItem;
if (mAdapter != null) {
ss.adapterState = mAdapter.saveState();
}
return ss;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
if (!(state instanceof SavedState)) {
super.onRestoreInstanceState(state);
return;
}
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
if (mAdapter != null) {
mAdapter.restoreState(ss.adapterState, ss.loader);
setCurrentItemInternal(ss.position, false, true);
} else {
mRestoredCurItem = ss.position;
mRestoredAdapterState = ss.adapterState;
mRestoredClassLoader = ss.loader;
}
}
@Override
public void addView(View child, int index, 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);
}
}
}
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;
}
@SuppressWarnings("deprecation")
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (DEBUG) Log.d(TAG, "onMeasure");
// For simple implementation, or internal size is always 0.
// We depend on the container to specify the layout size of
// our view. We can't really know what it is since we will be
// adding and removing different arbitrary views and do not
// want the layout to change as this happens.
setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
getDefaultSize(0, heightMeasureSpec));
final int measuredWidth = getMeasuredWidth();
final int measuredHeight = getMeasuredHeight();
final int maxGutterSize = measuredHeight / 10;
mGutterSize = Math.min(maxGutterSize, mDefaultGutterSize);
// Children are just made to fill our space.
int childWidthSize = measuredWidth - getPaddingLeft() - getPaddingRight();
int childHeightSize = measuredHeight - 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, hei