|
@@ -1,2990 +0,0 @@
|
|
-/*
|
|
|
|
- * Copyright (C) 2011 The Android Open Source Project
|
|
|
|
- *
|
|
|
|
- * Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
- * you may not use this file except in compliance with the License.
|
|
|
|
- * You may obtain a copy of the License at
|
|
|
|
- *
|
|
|
|
- * http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
- *
|
|
|
|
- * Unless required by applicable law or agreed to in writing, software
|
|
|
|
- * distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
- * See the License for the specific language governing permissions and
|
|
|
|
- * limitations under the License.
|
|
|
|
- */
|
|
|
|
-
|
|
|
|
-package eu.kanade.tachiyomi.ui.reader.viewer.pager.vertical;
|
|
|
|
-
|
|
|
|
-import android.content.Context;
|
|
|
|
-import android.content.res.Resources;
|
|
|
|
-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.Build;
|
|
|
|
-import android.os.Bundle;
|
|
|
|
-import android.os.Parcel;
|
|
|
|
-import android.os.Parcelable;
|
|
|
|
-import android.os.SystemClock;
|
|
|
|
-import android.support.annotation.CallSuper;
|
|
|
|
-import android.support.annotation.DrawableRes;
|
|
|
|
-import android.support.v4.os.ParcelableCompat;
|
|
|
|
-import android.support.v4.os.ParcelableCompatCreatorCallbacks;
|
|
|
|
-import android.support.v4.view.AccessibilityDelegateCompat;
|
|
|
|
-import android.support.v4.view.MotionEventCompat;
|
|
|
|
-import android.support.v4.view.PagerAdapter;
|
|
|
|
-import android.support.v4.view.VelocityTrackerCompat;
|
|
|
|
-import android.support.v4.view.ViewCompat;
|
|
|
|
-import android.support.v4.view.ViewConfigurationCompat;
|
|
|
|
-import android.support.v4.view.accessibility.AccessibilityEventCompat;
|
|
|
|
-import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
|
|
|
|
-import android.support.v4.view.accessibility.AccessibilityRecordCompat;
|
|
|
|
-import android.support.v4.widget.EdgeEffectCompat;
|
|
|
|
-import android.util.AttributeSet;
|
|
|
|
-import android.util.Log;
|
|
|
|
-import android.view.FocusFinder;
|
|
|
|
-import android.view.Gravity;
|
|
|
|
-import android.view.KeyEvent;
|
|
|
|
-import android.view.MotionEvent;
|
|
|
|
-import android.view.SoundEffectConstants;
|
|
|
|
-import android.view.VelocityTracker;
|
|
|
|
-import android.view.View;
|
|
|
|
-import android.view.ViewConfiguration;
|
|
|
|
-import android.view.ViewGroup;
|
|
|
|
-import android.view.ViewParent;
|
|
|
|
-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;
|
|
|
|
-import java.util.List;
|
|
|
|
-
|
|
|
|
-/**
|
|
|
|
- * Layout manager that allows the user to flip left and right
|
|
|
|
- * through pages of data. You supply an implementation of a
|
|
|
|
- * {@link PagerAdapter} to generate the pages that the view shows.
|
|
|
|
- *
|
|
|
|
- * <p>Note this class is currently under early design and
|
|
|
|
- * development. The API will likely change in later updates of
|
|
|
|
- * the compatibility library, requiring changes to the source code
|
|
|
|
- * of apps when they are compiled against the newer version.</p>
|
|
|
|
- *
|
|
|
|
- * <p>ViewPager is most often used in conjunction with {@link android.app.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} 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>For more information about how to use ViewPager, read <a
|
|
|
|
- * href="{@docRoot}training/implementing-navigation/lateral.html">Creating Swipe Views with
|
|
|
|
- * Tabs</a>.</p>
|
|
|
|
- *
|
|
|
|
- * <p>Below is a more complicated example of ViewPager, using it in conjunction
|
|
|
|
- * 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.
|
|
|
|
- *
|
|
|
|
- * {@sample development/samples/Support13Demos/src/com/example/android/supportv13/app/ActionBarTabsPager.java
|
|
|
|
- * complete}
|
|
|
|
- */
|
|
|
|
-@SuppressWarnings("deprecation")
|
|
|
|
-public class VerticalViewPagerImpl extends ViewGroup {
|
|
|
|
- private static final String TAG = "ViewPager";
|
|
|
|
- private static final boolean DEBUG = false;
|
|
|
|
-
|
|
|
|
- private static final boolean USE_CACHE = false;
|
|
|
|
-
|
|
|
|
- private static final int DEFAULT_OFFSCREEN_PAGES = 1;
|
|
|
|
- private static final int MAX_SETTLE_DURATION = 600; // ms
|
|
|
|
- private static final int MIN_DISTANCE_FOR_FLING = 25; // dips
|
|
|
|
-
|
|
|
|
- private static final int DEFAULT_GUTTER_SIZE = 16; // dips
|
|
|
|
-
|
|
|
|
- private static final int MIN_FLING_VELOCITY = 400; // dips
|
|
|
|
-
|
|
|
|
- private static final int[] LAYOUT_ATTRS = new int[] {
|
|
|
|
- android.R.attr.layout_gravity
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * Used to track what the expected number of items in the adapter should be.
|
|
|
|
- * If the app changes this when we don't expect it, we'll throw a big obnoxious exception.
|
|
|
|
- */
|
|
|
|
- private int mExpectedAdapterCount;
|
|
|
|
-
|
|
|
|
- static class ItemInfo {
|
|
|
|
- private Object object;
|
|
|
|
- private int position;
|
|
|
|
- private boolean scrolling;
|
|
|
|
- private float heightFactor;
|
|
|
|
- private 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 int mChildHeightMeasureSpec;
|
|
|
|
- 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;
|
|
|
|
- /**
|
|
|
|
- * 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 mTopEdge;
|
|
|
|
- private EdgeEffectCompat mBottomEdge;
|
|
|
|
-
|
|
|
|
- private boolean mFirstLayout = true;
|
|
|
|
- private boolean mNeedCalculatePageOffsets = false;
|
|
|
|
- private boolean mCalledSuper;
|
|
|
|
- private int mDecorChildCount;
|
|
|
|
-
|
|
|
|
- private List<OnPageChangeListener> mOnPageChangeListeners;
|
|
|
|
- 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 VerticalViewPagerImpl#SCROLL_STATE_IDLE
|
|
|
|
- * @see VerticalViewPagerImpl#SCROLL_STATE_DRAGGING
|
|
|
|
- * @see VerticalViewPagerImpl#SCROLL_STATE_SETTLING
|
|
|
|
- */
|
|
|
|
- public void onPageScrollStateChanged(int state);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * Simple implementation of the {@link OnPageChangeListener} interface with stub
|
|
|
|
- * implementations of each method. Extend this if you do not intend to override
|
|
|
|
- * every method of {@link 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>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 VerticalViewPagerImpl(Context context) {
|
|
|
|
- super(context);
|
|
|
|
- initViewPager();
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- public VerticalViewPagerImpl(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();
|
|
|
|
- mTopEdge = new EdgeEffectCompat(context);
|
|
|
|
- mBottomEdge = new EdgeEffectCompat(context);
|
|
|
|
-
|
|
|
|
- mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density);
|
|
|
|
- mCloseEnough = (int) (CLOSE_ENOUGH * density);
|
|
|
|
- mDefaultGutterSize = (int) (DEFAULT_GUTTER_SIZE * density);
|
|
|
|
-
|
|
|
|
- ViewCompat.setAccessibilityDelegate(this, new MyAccessibilityDelegate());
|
|
|
|
-
|
|
|
|
- if (ViewCompat.getImportantForAccessibility(this)
|
|
|
|
- == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
|
|
|
|
- ViewCompat.setImportantForAccessibility(this,
|
|
|
|
- ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- @Override
|
|
|
|
- protected void onDetachedFromWindow() {
|
|
|
|
- removeCallbacks(mEndScrollRunnable);
|
|
|
|
- super.onDetachedFromWindow();
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private void setScrollState(int newState) {
|
|
|
|
- if (mScrollState == newState) {
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- mScrollState = newState;
|
|
|
|
- if (mPageTransformer != null) {
|
|
|
|
- // PageTransformers can do complex things that benefit from hardware layers.
|
|
|
|
- enableLayers(newState != SCROLL_STATE_IDLE);
|
|
|
|
- }
|
|
|
|
- dispatchOnScrollStateChanged(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 getClientWidth() {
|
|
|
|
-// return getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
|
|
|
|
-// }
|
|
|
|
-
|
|
|
|
- private int getClientHeight() {
|
|
|
|
- return getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * 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.isEmpty()) {
|
|
|
|
- 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) {
|
|
|
|
- dispatchOnPageSelected(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 destY = 0;
|
|
|
|
- if (curInfo != null) {
|
|
|
|
- final int height = getClientHeight();
|
|
|
|
- destY = (int) (height * Math.max(mFirstOffset,
|
|
|
|
- Math.min(curInfo.offset, mLastOffset)));
|
|
|
|
- }
|
|
|
|
- if (smoothScroll) {
|
|
|
|
- smoothScrollTo(0, destY, velocity);
|
|
|
|
- if (dispatchSelected) {
|
|
|
|
- dispatchOnPageSelected(item);
|
|
|
|
- }
|
|
|
|
- } else {
|
|
|
|
- if (dispatchSelected) {
|
|
|
|
- dispatchOnPageSelected(item);
|
|
|
|
- }
|
|
|
|
- completeScroll(false);
|
|
|
|
- scrollTo(0, destY);
|
|
|
|
- pageScrolled(destY);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * Set a listener that will be invoked whenever the page changes or is incrementally
|
|
|
|
- * scrolled. See {@link OnPageChangeListener}.
|
|
|
|
- *
|
|
|
|
- * @param listener Listener to set
|
|
|
|
- *
|
|
|
|
- * @deprecated Use {@link #addOnPageChangeListener(OnPageChangeListener)}
|
|
|
|
- * and {@link #removeOnPageChangeListener(OnPageChangeListener)} instead.
|
|
|
|
- */
|
|
|
|
- @Deprecated
|
|
|
|
- public void setOnPageChangeListener(OnPageChangeListener listener) {
|
|
|
|
- mOnPageChangeListener = listener;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * Add a listener that will be invoked whenever the page changes or is incrementally
|
|
|
|
- * scrolled. See {@link OnPageChangeListener}.
|
|
|
|
- *
|
|
|
|
- * <p>Components that add a listener should take care to remove it when finished.
|
|
|
|
- * Other components that take ownership of a view may call {@link #clearOnPageChangeListeners()}
|
|
|
|
- * to remove all attached listeners.</p>
|
|
|
|
- *
|
|
|
|
- * @param listener listener to add
|
|
|
|
- */
|
|
|
|
- public void addOnPageChangeListener(OnPageChangeListener listener) {
|
|
|
|
- if (mOnPageChangeListeners == null) {
|
|
|
|
- mOnPageChangeListeners = new ArrayList<>();
|
|
|
|
- }
|
|
|
|
- mOnPageChangeListeners.add(listener);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * Remove a listener that was previously added via
|
|
|
|
- * {@link #addOnPageChangeListener(OnPageChangeListener)}.
|
|
|
|
- *
|
|
|
|
- * @param listener listener to remove
|
|
|
|
- */
|
|
|
|
- public void removeOnPageChangeListener(OnPageChangeListener listener) {
|
|
|
|
- if (mOnPageChangeListeners != null) {
|
|
|
|
- mOnPageChangeListeners.remove(listener);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * Remove all listeners that are notified of any changes in scroll state or position.
|
|
|
|
- */
|
|
|
|
- public void clearOnPageChangeListeners() {
|
|
|
|
- if (mOnPageChangeListeners != null) {
|
|
|
|
- mOnPageChangeListeners.clear();
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * Set a {@link 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><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 (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.
|
|
|
|
- */
|
|
|
|
- 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>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>You should keep this limit low, especially if your pages have complex layouts.
|
|
|
|
- * This setting defaults to 1.</p>
|
|
|
|
- *
|
|
|
|
- * @param limit How many pages will be kept offscreen in an idle state.
|
|
|
|
- */
|
|
|
|
- public void setOffscreenPageLimit(int limit) {
|
|
|
|
- if (limit < DEFAULT_OFFSCREEN_PAGES) {
|
|
|
|
- Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " +
|
|
|
|
- DEFAULT_OFFSCREEN_PAGES);
|
|
|
|
- limit = DEFAULT_OFFSCREEN_PAGES;
|
|
|
|
- }
|
|
|
|
- if (limit != mOffscreenPageLimit) {
|
|
|
|
- mOffscreenPageLimit = limit;
|
|
|
|
- populate();
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * Set the margin between pages.
|
|
|
|
- *
|
|
|
|
- * @param marginPixels Distance between adjacent pages in pixels
|
|
|
|
- * @see #getPageMargin()
|
|
|
|
- * @see #setPageMarginDrawable(Drawable)
|
|
|
|
- * @see #setPageMarginDrawable(int)
|
|
|
|
- */
|
|
|
|
- public void setPageMargin(int marginPixels) {
|
|
|
|
- final int oldMargin = mPageMargin;
|
|
|
|
- mPageMargin = marginPixels;
|
|
|
|
-
|
|
|
|
- final int height = getHeight();
|
|
|
|
- recomputeScrollPosition(height, height, 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(@DrawableRes 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 height = getClientHeight();
|
|
|
|
- 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;
|
|
|
|
- velocity = Math.abs(velocity);
|
|
|
|
- if (velocity > 0) {
|
|
|
|
- duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
|
|
|
|
- } else {
|
|
|
|
- final float pageHeight = height * mAdapter.getPageWidth(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.heightFactor = 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;
|
|
|
|
- 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 extraHeightTop = 0.f;
|
|
|
|
- int itemIndex = curIndex - 1;
|
|
|
|
- ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
|
|
|
|
- final int clientHeight = getClientHeight();
|
|
|
|
- final float topHeightNeeded = clientHeight <= 0 ? 0 :
|
|
|
|
- 2.f - curItem.heightFactor + (float) getPaddingLeft() / (float) clientHeight;
|
|
|
|
- for (int pos = mCurItem - 1; pos >= 0; pos--) {
|
|
|
|
- if (extraHeightTop >= 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) {
|
|
|
|
- extraHeightTop += ii.heightFactor;
|
|
|
|
- itemIndex--;
|
|
|
|
- ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
|
|
|
|
- } else {
|
|
|
|
- ii = addNewItem(pos, itemIndex + 1);
|
|
|
|
- extraHeightTop += 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;
|
|
|
|
- final float bottomHeightNeeded = clientHeight <= 0 ? 0 :
|
|
|
|
- (float) getPaddingRight() / (float) clientHeight + 2.f;
|
|
|
|
- for (int pos = mCurItem + 1; pos < N; pos++) {
|
|
|
|
- if (extraHeightBottom >= bottomHeightNeeded && 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 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;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- 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 && 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 height = getClientHeight();
|
|
|
|
- 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;
|
|
|
|
- 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.getPageWidth(pos) + marginOffset;
|
|
|
|
- pos++;
|
|
|
|
- }
|
|
|
|
- ii.offset = offset;
|
|
|
|
- offset += ii.heightFactor + marginOffset;
|
|
|
|
- }
|
|
|
|
- } else if (oldCurPosition > curItem.position) {
|
|
|
|
- int itemIndex = mItems.size() - 1;
|
|
|
|
- ItemInfo ii;
|
|
|
|
- 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.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.getPageWidth(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.getPageWidth(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 {
|
|
|
|
- private int position;
|
|
|
|
- private Parcelable adapterState;
|
|
|
|
- private ClassLoader loader;
|
|
|
|
-
|
|
|
|
- public static final Parcelable.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];
|
|
|
|
- }
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
- public SavedState(Parcelable superState) {
|
|
|
|
- super(superState);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- 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 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 + "}";
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- @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);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- @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 measuredHeight = getMeasuredHeight();
|
|
|
|
- final int maxGutterSize = measuredHeight / 10;
|
|
|
|
- mGutterSize = Math.min(maxGutterSize, mDefaultGutterSize);
|
|
|
|
-
|
|
|
|
- // Children are just made to fill our space.
|
|
|
|
- int childWidthSize = getMeasuredWidth() - 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, 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;
|
|
|
|
-
|
|
|
|
- // Page views next.
|
|
|
|
- size = getChildCount();
|
|
|
|
- for (int i = 0; i < size; ++i) {
|
|
|
|
- final View child = getChildAt(i);
|
|
|
|
- if (child.getVisibility() != GONE) {
|
|
|
|
- if (DEBUG) Log.v(TAG, "Measuring #" + i + " " + child
|
|
|
|
- + ": " + mChildWidthMeasureSpec);
|
|
|
|
-
|
|
|
|
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
|
|
|
|
- if (lp == null || !lp.isDecor) {
|
|
|
|
- final int heightSpec = MeasureSpec.makeMeasureSpec(
|
|
|
|
- (int) (childHeightSize * lp.heightFactor), MeasureSpec.EXACTLY);
|
|
|
|
- child.measure(mChildWidthMeasureSpec, heightSpec);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- @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 (h != oldh) {
|
|
|
|
- recomputeScrollPosition(h, oldh, mPageMargin, mPageMargin);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private void recomputeScrollPosition(int height, int oldHeight, int margin, int oldMargin) {
|
|
|
|
- if (oldHeight > 0 && !mItems.isEmpty()) {
|
|
|
|
- final int heightWithMargin = height - getPaddingTop() - getPaddingBottom() + margin;
|
|
|
|
- final int oldHeightWithMargin = oldHeight - 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 * height), newDuration);
|
|
|
|
- }
|
|
|
|
- } else {
|
|
|
|
- final ItemInfo ii = infoForPosition(mCurItem);
|
|
|
|
- final float scrollOffset = ii != null ? Math.min(ii.offset, mLastOffset) : 0;
|
|
|
|
- final int scrollPos = (int) (scrollOffset *
|
|
|
|
- (height - getPaddingTop() - getPaddingBottom()));
|
|
|
|
- if (scrollPos != getScrollY()) {
|
|
|
|
- completeScroll(false);
|
|
|
|
- scrollTo(getScrollX(), scrollPos);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- @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 scrollY = getScrollY();
|
|
|
|
-
|
|
|
|
- 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;
|
|
|
|
- int childTop;
|
|
|
|
- if (lp.isDecor) {
|
|
|
|
- final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
|
|
|
|
- final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
|
|
|
|
- switch (hgrav) {
|
|
|
|
- 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;
|
|
|
|
- default:
|
|
|
|
- childLeft = paddingLeft;
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
- switch (vgrav) {
|
|
|
|
- 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;
|
|
|
|
- default:
|
|
|
|
- childTop = paddingTop;
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
- childTop += scrollY;
|
|
|
|
- child.layout(childLeft, childTop,
|
|
|
|
- childLeft + child.getMeasuredWidth(),
|
|
|
|
- childTop + child.getMeasuredHeight());
|
|
|
|
- decorCount++;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- final int childHeight = height - paddingTop - paddingBottom;
|
|
|
|
- // 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 toff = (int) (childHeight * ii.offset);
|
|
|
|
- int childLeft = paddingLeft;
|
|
|
|
- int childTop = paddingTop + toff;
|
|
|
|
- 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) (childHeight * lp.heightFactor),
|
|
|
|
- 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());
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- mLeftPageBounds = paddingLeft;
|
|
|
|
- mRightPageBounds = width - paddingRight;
|
|
|
|
- mDecorChildCount = decorCount;
|
|
|
|
-
|
|
|
|
- if (mFirstLayout) {
|
|
|
|
- scrollToItem(mCurItem, false, 0, false);
|
|
|
|
- }
|
|
|
|
- mFirstLayout = false;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- @Override
|
|
|
|
- public void computeScroll() {
|
|
|
|
- if (!mScroller.isFinished() && mScroller.computeScrollOffset()) {
|
|
|
|
- int oldX = getScrollX();
|
|
|
|
- int oldY = getScrollY();
|
|
|
|
- int x = mScroller.getCurrX();
|
|
|
|
- int y = mScroller.getCurrY();
|
|
|
|
-
|
|
|
|
- if (oldX != x || oldY != y) {
|
|
|
|
- scrollTo(x, y);
|
|
|
|
- if (!pageScrolled(y)) {
|
|
|
|
- mScroller.abortAnimation();
|
|
|
|
- scrollTo(x, 0);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // Keep on drawing until the animation has finished.
|
|
|
|
- ViewCompat.postInvalidateOnAnimation(this);
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // Done with scroll, clean up state.
|
|
|
|
- completeScroll(true);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private boolean pageScrolled(int ypos) {
|
|
|
|
- if (mItems.isEmpty()) {
|
|
|
|
- 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 height = getClientHeight();
|
|
|
|
- final int heightWithMargin = height + mPageMargin;
|
|
|
|
- final float marginOffset = (float) mPageMargin / height;
|
|
|
|
- final int currentPage = ii.position;
|
|
|
|
- final float pageOffset = (((float) ypos / height) - ii.offset) /
|
|
|
|
- (ii.heightFactor + marginOffset);
|
|
|
|
- final int offsetPixels = (int) (pageOffset * heightWithMargin);
|
|
|
|
-
|
|
|
|
- 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.
|
|
|
|
- */
|
|
|
|
- @CallSuper
|
|
|
|
- protected void onPageScrolled(int position, float offset, int offsetPixels) {
|
|
|
|
- // Offset any decor views if needed - keep them on-screen at all times.
|
|
|
|
- if (mDecorChildCount > 0) {
|
|
|
|
- final int 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;
|
|
|
|
- switch (vgrav) {
|
|
|
|
- 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;
|
|
|
|
- default:
|
|
|
|
- childTop = paddingTop;
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
- childTop += scrollY;
|
|
|
|
-
|
|
|
|
- final int childOffset = childTop - child.getTop();
|
|
|
|
- if (childOffset != 0) {
|
|
|
|
- child.offsetTopAndBottom(childOffset);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- dispatchOnPageScrolled(position, offset, offsetPixels);
|
|
|
|
-
|
|
|
|
- if (mPageTransformer != null) {
|
|
|
|
- final int scrollY = getScrollY();
|
|
|
|
- final int childCount = getChildCount();
|
|
|
|
- for (int i = 0; i < childCount; i++) {
|
|
|
|
- final View child = getChildAt(i);
|
|
|
|
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
|
|
|
|
-
|
|
|
|
- if (lp.isDecor) continue;
|
|
|
|
-
|
|
|
|
- final float transformPos = (float) (child.getTop() - scrollY) / getClientHeight();
|
|
|
|
- mPageTransformer.transformPage(child, transformPos);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- mCalledSuper = true;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private void dispatchOnPageScrolled(int position, float offset, int offsetPixels) {
|
|
|
|
- if (mOnPageChangeListener != null) {
|
|
|
|
- mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels);
|
|
|
|
- }
|
|
|
|
- if (mOnPageChangeListeners != null) {
|
|
|
|
- for (int i = 0, z = mOnPageChangeListeners.size(); i < z; i++) {
|
|
|
|
- OnPageChangeListener listener = mOnPageChangeListeners.get(i);
|
|
|
|
- if (listener != null) {
|
|
|
|
- listener.onPageScrolled(position, offset, offsetPixels);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- if (mInternalPageChangeListener != null) {
|
|
|
|
- mInternalPageChangeListener.onPageScrolled(position, offset, offsetPixels);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private void dispatchOnPageSelected(int position) {
|
|
|
|
- if (mOnPageChangeListener != null) {
|
|
|
|
- mOnPageChangeListener.onPageSelected(position);
|
|
|
|
- }
|
|
|
|
- if (mOnPageChangeListeners != null) {
|
|
|
|
- for (int i = 0, z = mOnPageChangeListeners.size(); i < z; i++) {
|
|
|
|
- OnPageChangeListener listener = mOnPageChangeListeners.get(i);
|
|
|
|
- if (listener != null) {
|
|
|
|
- listener.onPageSelected(position);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- if (mInternalPageChangeListener != null) {
|
|
|
|
- mInternalPageChangeListener.onPageSelected(position);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private void dispatchOnScrollStateChanged(int state) {
|
|
|
|
- if (mOnPageChangeListener != null) {
|
|
|
|
- mOnPageChangeListener.onPageScrollStateChanged(state);
|
|
|
|
- }
|
|
|
|
- if (mOnPageChangeListeners != null) {
|
|
|
|
- for (int i = 0, z = mOnPageChangeListeners.size(); i < z; i++) {
|
|
|
|
- OnPageChangeListener listener = mOnPageChangeListeners.get(i);
|
|
|
|
- if (listener != null) {
|
|
|
|
- listener.onPageScrollStateChanged(state);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- if (mInternalPageChangeListener != null) {
|
|
|
|
- mInternalPageChangeListener.onPageScrollStateChanged(state);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- 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);
|
|
|
|
- if (y != oldY) {
|
|
|
|
- pageScrolled(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 y, float dy) {
|
|
|
|
- return (y < mGutterSize && dy > 0) || (y > getHeight() - mGutterSize && dy < 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!");
|
|
|
|
- resetTouch();
|
|
|
|
- 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);
|
|
|
|
- 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;
|
|
|
|
- }
|
|
|
|
- // Scroll to follow the motion event
|
|
|
|
- if (mIsBeingDragged && performDrag(y)) {
|
|
|
|
- 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 (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;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (DEBUG) Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY
|
|
|
|
- + " mIsBeingDragged=" + mIsBeingDragged
|
|
|
|
- + "mIsUnableToDrag=" + mIsUnableToDrag);
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- case MotionEventCompat.ACTION_POINTER_UP:
|
|
|
|
- onSecondaryPointerUp(ev);
|
|
|
|
- break;
|
|
|
|
- default:
|
|
|
|
- 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);
|
|
|
|
- if (pointerIndex == -1) {
|
|
|
|
- // A child has consumed some touch events and put us into an inconsistent state.
|
|
|
|
- needsInvalidate = resetTouch();
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
- 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 (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);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- // Not else! Note that mIsBeingDragged can be set above.
|
|
|
|
- if (mIsBeingDragged) {
|
|
|
|
- // Scroll to follow the motion event
|
|
|
|
- final int activePointerIndex = MotionEventCompat.findPointerIndex(
|
|
|
|
- ev, mActivePointerId);
|
|
|
|
- final float y = MotionEventCompat.getY(ev, activePointerIndex);
|
|
|
|
- needsInvalidate |= performDrag(y);
|
|
|
|
- }
|
|
|
|
- break;
|
|
|
|
- case MotionEvent.ACTION_UP:
|
|
|
|
- if (mIsBeingDragged) {
|
|
|
|
- final VelocityTracker velocityTracker = mVelocityTracker;
|
|
|
|
- velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
|
|
|
|
- int initialVelocity = (int) VelocityTrackerCompat.getYVelocity(
|
|
|
|
- velocityTracker, mActivePointerId);
|
|
|
|
- mPopulatePending = true;
|
|
|
|
- final int height = getClientHeight();
|
|
|
|
- final int scrollY = getScrollY();
|
|
|
|
- final ItemInfo ii = infoForCurrentScrollPosition();
|
|
|
|
- final int currentPage = ii.position;
|
|
|
|
- final float pageOffset = (((float) scrollY / height) - ii.offset) / ii.heightFactor;
|
|
|
|
- final int activePointerIndex =
|
|
|
|
- MotionEventCompat.findPointerIndex(ev, mActivePointerId);
|
|
|
|
- final float y = MotionEventCompat.getY(ev, activePointerIndex);
|
|
|
|
- final int totalDelta = (int) (y - mInitialMotionY);
|
|
|
|
- int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
|
|
|
|
- totalDelta);
|
|
|
|
- setCurrentItemInternal(nextPage, true, true, initialVelocity);
|
|
|
|
-
|
|
|
|
- needsInvalidate = resetTouch();
|
|
|
|
- }
|
|
|
|
- break;
|
|
|
|
- case MotionEvent.ACTION_CANCEL:
|
|
|
|
- if (mIsBeingDragged) {
|
|
|
|
- scrollToItem(mCurItem, true, 0, false);
|
|
|
|
- needsInvalidate = resetTouch();
|
|
|
|
- }
|
|
|
|
- break;
|
|
|
|
- case MotionEventCompat.ACTION_POINTER_DOWN: {
|
|
|
|
- final int index = MotionEventCompat.getActionIndex(ev);
|
|
|
|
- final float y = MotionEventCompat.getY(ev, index);
|
|
|
|
- mLastMotionY = y;
|
|
|
|
- mActivePointerId = MotionEventCompat.getPointerId(ev, index);
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
- case MotionEventCompat.ACTION_POINTER_UP:
|
|
|
|
- onSecondaryPointerUp(ev);
|
|
|
|
- mLastMotionY = MotionEventCompat.getY(ev,
|
|
|
|
- MotionEventCompat.findPointerIndex(ev, mActivePointerId));
|
|
|
|
- break;
|
|
|
|
- default:
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
- if (needsInvalidate) {
|
|
|
|
- ViewCompat.postInvalidateOnAnimation(this);
|
|
|
|
- }
|
|
|
|
- return true;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private boolean resetTouch() {
|
|
|
|
- boolean needsInvalidate;
|
|
|
|
- mActivePointerId = INVALID_POINTER;
|
|
|
|
- endDrag();
|
|
|
|
- needsInvalidate = mTopEdge.onRelease() | mBottomEdge.onRelease();
|
|
|
|
- return needsInvalidate;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private void requestParentDisallowInterceptTouchEvent(boolean disallowIntercept) {
|
|
|
|
- final ViewParent parent = getParent();
|
|
|
|
- if (parent != null) {
|
|
|
|
- parent.requestDisallowInterceptTouchEvent(disallowIntercept);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private boolean performDrag(float y) {
|
|
|
|
- boolean needsInvalidate = false;
|
|
|
|
-
|
|
|
|
- final float deltaY = mLastMotionY - y;
|
|
|
|
- mLastMotionY = y;
|
|
|
|
-
|
|
|
|
- float oldScrollY = getScrollY();
|
|
|
|
- float scrollY = oldScrollY + deltaY;
|
|
|
|
- final int height = getClientHeight();
|
|
|
|
-
|
|
|
|
- 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 = mTopEdge.onPull(Math.abs(over) / height);
|
|
|
|
- }
|
|
|
|
- scrollY = topBound;
|
|
|
|
- } else if (scrollY > bottomBound) {
|
|
|
|
- if (bottomAbsolute) {
|
|
|
|
- float over = scrollY - bottomBound;
|
|
|
|
- needsInvalidate = mBottomEdge.onPull(Math.abs(over) / height);
|
|
|
|
- }
|
|
|
|
- scrollY = bottomBound;
|
|
|
|
- }
|
|
|
|
- // Don't lose the rounded component
|
|
|
|
- mLastMotionY += scrollY - (int) scrollY;
|
|
|
|
- scrollTo(getScrollX(), (int) scrollY);
|
|
|
|
- pageScrolled((int) scrollY);
|
|
|
|
-
|
|
|
|
- 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 height = getClientHeight();
|
|
|
|
- final float scrollOffset = height > 0 ? (float) getScrollY() / height : 0;
|
|
|
|
- final float marginOffset = height > 0 ? (float) mPageMargin / height : 0;
|
|
|
|
- int lastPos = -1;
|
|
|
|
- float lastOffset = 0.f;
|
|
|
|
- float lastHeight = 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 + lastHeight + marginOffset;
|
|
|
|
- ii.position = lastPos + 1;
|
|
|
|
- ii.heightFactor = mAdapter.getPageWidth(ii.position);
|
|
|
|
- i--;
|
|
|
|
- }
|
|
|
|
- offset = ii.offset;
|
|
|
|
-
|
|
|
|
- final float topBound = offset;
|
|
|
|
- final float bottomBound = offset + ii.heightFactor + marginOffset;
|
|
|
|
- if (first || scrollOffset >= topBound) {
|
|
|
|
- if (scrollOffset < bottomBound || i == mItems.size() - 1) {
|
|
|
|
- return ii;
|
|
|
|
- }
|
|
|
|
- } else {
|
|
|
|
- return lastItem;
|
|
|
|
- }
|
|
|
|
- first = false;
|
|
|
|
- lastPos = ii.position;
|
|
|
|
- lastOffset = offset;
|
|
|
|
- lastHeight = ii.heightFactor;
|
|
|
|
- lastItem = ii;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- return lastItem;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private int determineTargetPage(int currentPage, float pageOffset, int velocity, int deltaY) {
|
|
|
|
- int targetPage;
|
|
|
|
- if (Math.abs(deltaY) > 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.isEmpty()) {
|
|
|
|
- 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 (!mTopEdge.isFinished()) {
|
|
|
|
- final int restoreCount = canvas.save();
|
|
|
|
- final int height = getHeight();
|
|
|
|
- final int width = getWidth() - getPaddingLeft() - getPaddingRight();
|
|
|
|
-
|
|
|
|
- canvas.translate(getPaddingLeft(), mFirstOffset * height);
|
|
|
|
- mTopEdge.setSize(width, height);
|
|
|
|
- needsInvalidate |= mTopEdge.draw(canvas);
|
|
|
|
- canvas.restoreToCount(restoreCount);
|
|
|
|
- }
|
|
|
|
- if (!mBottomEdge.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);
|
|
|
|
- mBottomEdge.setSize(width, height);
|
|
|
|
- needsInvalidate |= mBottomEdge.draw(canvas);
|
|
|
|
- canvas.restoreToCount(restoreCount);
|
|
|
|
- }
|
|
|
|
- } else {
|
|
|
|
- mTopEdge.finish();
|
|
|
|
- mBottomEdge.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.isEmpty() && mAdapter != null) {
|
|
|
|
- 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.heightFactor) * height;
|
|
|
|
- offset = ii.offset + ii.heightFactor + marginOffset;
|
|
|
|
- } else {
|
|
|
|
- float heightFactor = mAdapter.getPageWidth(pos);
|
|
|
|
- drawAt = (offset + heightFactor) * height;
|
|
|
|
- offset += heightFactor + marginOffset;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (drawAt + mPageMargin > scrollY) {
|
|
|
|
- mMarginDrawable.setBounds(mLeftPageBounds, (int) drawAt,
|
|
|
|
- mRightPageBounds, (int) (drawAt + mPageMargin + 0.5f));
|
|
|
|
- mMarginDrawable.draw(canvas);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (drawAt > scrollY + height) {
|
|
|
|
- break; // No more visible, no sense in continuing
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * Start a fake drag of the pager.
|
|
|
|
- *
|
|
|
|
- * <p>A fake drag can be useful if you want to synchronize the motion of the ViewPager
|
|
|
|
- * with the touch scrolling of another view, while still letting the 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.
|
|
|
|
- *
|
|
|
|
- * <p>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);
|
|
|
|
- mInitialMotionY = mLastMotionY = 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);
|
|
|
|
- int initialVelocity = (int) VelocityTrackerCompat.getYVelocity(
|
|
|
|
- velocityTracker, mActivePointerId);
|
|
|
|
- mPopulatePending = true;
|
|
|
|
- final int height = getClientHeight();
|
|
|
|
- final int scrollY = getScrollY();
|
|
|
|
- final ItemInfo ii = infoForCurrentScrollPosition();
|
|
|
|
- final int currentPage = ii.position;
|
|
|
|
- final float pageOffset = (((float) scrollY / height) - ii.offset) / ii.heightFactor;
|
|
|
|
- final int totalDelta = (int) (mLastMotionY - mInitialMotionY);
|
|
|
|
- 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 yOffset Offset in pixels to drag by.
|
|
|
|
- * @see #beginFakeDrag()
|
|
|
|
- * @see #endFakeDrag()
|
|
|
|
- */
|
|
|
|
- public void fakeDragBy(float yOffset) {
|
|
|
|
- if (!mFakeDragging) {
|
|
|
|
- throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- mLastMotionY += yOffset;
|
|
|
|
-
|
|
|
|
- float oldScrollY = getScrollY();
|
|
|
|
- float scrollY = oldScrollY - yOffset;
|
|
|
|
- final int height = getClientHeight();
|
|
|
|
-
|
|
|
|
- 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();
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * 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;
|
|
|
|
- mLastMotionY = MotionEventCompat.getY(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 height = getClientHeight();
|
|
|
|
- final int scrollY = getScrollY();
|
|
|
|
- if (direction < 0) {
|
|
|
|
- return (scrollY > (int) (height * mFirstOffset));
|
|
|
|
- } else if (direction > 0) {
|
|
|
|
- return (scrollY < (int) (height * 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 dy 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 dy, 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 (y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
|
|
|
|
- x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
|
|
|
|
- canScroll(child, true, dy, x + scrollX - child.getLeft(),
|
|
|
|
- y + scrollY - child.getTop())) {
|
|
|
|
- return true;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- return checkV && ViewCompat.canScrollVertically(v, -dy);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- @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 (event.hasNoModifiers()) {
|
|
|
|
- handled = arrowScroll(FOCUS_FORWARD);
|
|
|
|
- } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
|
|
|
|
- handled = arrowScroll(FOCUS_BACKWARD);
|
|
|
|
- }
|
|
|
|
- break;
|
|
|
|
- default:
|
|
|
|
- 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.
|
|
|
|
- final int nextTop = getChildRectInPagerCoordinates(mTempRect, nextFocused).top;
|
|
|
|
- final int currTop = getChildRectInPagerCoordinates(mTempRect, currentFocused).top;
|
|
|
|
- if (currentFocused != null && nextTop >= currTop) {
|
|
|
|
- handled = pageUp();
|
|
|
|
- } 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.
|
|
|
|
- final int nextDown = getChildRectInPagerCoordinates(mTempRect, nextFocused).bottom;
|
|
|
|
- final int currDown = getChildRectInPagerCoordinates(mTempRect, currentFocused).bottom;
|
|
|
|
- if (currentFocused != null && nextDown <= currDown) {
|
|
|
|
- handled = pageDown();
|
|
|
|
- } else {
|
|
|
|
- handled = nextFocused.requestFocus();
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- } else if (direction == FOCUS_UP || direction == FOCUS_BACKWARD) {
|
|
|
|
- // Trying to move left and nothing there; try to page.
|
|
|
|
- handled = pageUp();
|
|
|
|
- } else if (direction == FOCUS_DOWN || direction == FOCUS_FORWARD) {
|
|
|
|
- // Trying to move right and nothing there; try to page.
|
|
|
|
- handled = pageDown();
|
|
|
|
- }
|
|
|
|
- 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 pageUp() {
|
|
|
|
- if (mCurItem > 0) {
|
|
|
|
- setCurrentItem(mCurItem-1, true);
|
|
|
|
- return true;
|
|
|
|
- }
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- boolean pageDown() {
|
|
|
|
- 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<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 && child.requestFocus(direction, previouslyFocusedRect)) {
|
|
|
|
- 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(VerticalViewPagerImpl.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(VerticalViewPagerImpl.class.getName());
|
|
|
|
- info.setScrollable(canScroll());
|
|
|
|
- if (internalCanScrollVertically(1)) {
|
|
|
|
- info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);
|
|
|
|
- }
|
|
|
|
- if (internalCanScrollVertically(-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 (internalCanScrollVertically(1)) {
|
|
|
|
- setCurrentItem(mCurItem + 1);
|
|
|
|
- return true;
|
|
|
|
- }
|
|
|
|
- } return false;
|
|
|
|
- case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD: {
|
|
|
|
- if (internalCanScrollVertically(-1)) {
|
|
|
|
- setCurrentItem(mCurItem - 1);
|
|
|
|
- return true;
|
|
|
|
- }
|
|
|
|
- } return false;
|
|
|
|
- default:
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
- 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();
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * 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;
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * 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 width
|
|
|
|
- */
|
|
|
|
- private float heightFactor = 0.f;
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * true if this view was added during layout and needs to be measured
|
|
|
|
- * before being positioned.
|
|
|
|
- */
|
|
|
|
- private boolean needsMeasure;
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * Adapter position this view is for if !isDecor
|
|
|
|
- */
|
|
|
|
- private int position;
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * Current child index within the ViewPager that this view occupies
|
|
|
|
- */
|
|
|
|
- private 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;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-}
|
|
|