Java tutorial
/* * ================================================================================================= * Copyright (C) 2014 Martin Albedinsky * ================================================================================================= * Licensed under the Apache License, Version 2.0 or later (further "License" only). * ------------------------------------------------------------------------------------------------- * You may use this file only in compliance with the License. More details and copy of this License * you may obtain at * * http://www.apache.org/licenses/LICENSE-2.0 * * You can redistribute, modify or publish any part of the code written within this file but as it * is described in the License, the software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES or CONDITIONS OF ANY KIND. * * See the License for the specific language governing permissions and limitations under the License. * ================================================================================================= */ package com.albedinsky.android.ui.widget; import android.annotation.TargetApi; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.view.PagerAdapter; import android.support.v4.view.ViewPager; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.ViewGroup; import android.view.ViewParent; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.Interpolator; import android.widget.Scroller; import com.albedinsky.android.ui.R; import com.albedinsky.android.ui.UiConfig; import com.albedinsky.android.ui.controller.PullController; import com.albedinsky.android.ui.graphics.drawable.TintDrawable; import com.albedinsky.android.ui.interpolator.ScrollerInterpolator; import java.lang.reflect.Field; /** * Extended version of {@link android.support.v4.view.ViewPager}. This updated ViewPager supports * tinting for the Android versions below {@link android.os.Build.VERSION_CODES#LOLLIPOP LOLLIPOP} * and other useful features described below including <b>pulling</b> feature. * * <h3>Tinting</h3> * Tinting is supported via Xml attributes listed below: * <ul> * <li>{@link R.attr#uiBackgroundTint uiBackgroundTint}</li> * <li>{@link R.attr#uiBackgroundTintMode uiBackgroundTintMode}</li> * </ul> * <p> * <b>Note, that on {@link android.os.Build.VERSION_CODES#LOLLIPOP LOLLIPOP} and above SDK versions * can be also used Xml attributes listed above where in such case will be used the native tinting.</b> * <p> * This widget group also overrides all SDK methods used to tint its components like {@link #setBackgroundTintList(android.content.res.ColorStateList)} * or {@link #setBackgroundTintMode(android.graphics.PorterDuff.Mode)}, so these can be used regardless * the current version of SDK but invoking of these methods below {@link android.os.Build.VERSION_CODES#LOLLIPOP LOLLIPOP} * can be done only directly upon instance of this widget group otherwise {@link NoSuchMethodException} * will be thrown. * * <h3>Pulling</h3> * This feature can be enabled/disabled via {@link #setPullEnabled(boolean)} or via Xml attribute * {@link R.attr#uiPullEnabled uiPullEnabled}. For configuration of all parameters used to support * pulling feature see {@link PullController} by which is this feature supported within this widget. * To receive callback about initiated and performed pull gesture you need to register {@link OnPullListener} * via {@link PullController#registerOnPullListener(OnPullListener)}. Each pullable widget uses its * own PullController that can be accessed via {@link #getPullController()}. This widget, according * to its {@link Orientation#HORIZONTAL HORIZONTAL} orientation, can be pulled at its left or right * whenever its content is scrolled at the start or at the end of its total size. * * <h3>Sliding</h3> * This updated view group allows updating of its current position along <b>x</b> and <b>y</b> axis * by changing <b>fraction</b> of these properties depending on its current size using the new animation * framework introduced in {@link android.os.Build.VERSION_CODES#HONEYCOMB HONEYCOMB} via * {@link android.animation.ObjectAnimator ObjectAnimator}s API. * <p> * Changing of fraction of X or Y is supported via these two methods: * <ul> * <li>{@link #setFractionX(float)}</li> * <li>{@link #setFractionY(float)}</li> * </ul> * <p> * For example if an instance of this view group class needs to be slided to the right by its whole * width, an Xml file with ObjectAnimator would look like this: * <pre> * <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" * android:propertyName="fractionX" * android:valueFrom="0.0" * android:valueTo="1.0" * android:duration="300"/> * </pre> * This can be especially useful for fragment transitions framework, where this view group would be * used as a root for a view hierarchy of a specific fragment. * * <h3>XML attributes</h3> * See {@link ViewPager}, * {@link R.styleable#Ui_ViewPager ViewPagerWidget Attributes} * * <h3>Default style attribute</h3> * {@link R.attr#uiViewPagerStyle uiViewPagerStyle} * * @author Martin Albedinsky */ public class ViewPagerWidget extends ViewPager implements Widget, Pullable { /** * Interface =================================================================================== */ /** * Constants =================================================================================== */ /** * Log TAG. */ private static final String TAG = "ViewPagerWidget"; /** * Flag indicating whether the swiping of pages on touch is enabled or not. */ private static final int PFLAG_PAGE_SWIPING_ENABLED = 0x00000001 << 16; /** * Flag indicating whether the swiping of multiple pages on fling at once is enabled or not. */ private static final int PFLAG_PAGE_FLING_SWIPING_ENABLED = 0x00000001 << 17; /** * Flag indicating whether scroll duration for pages scrolling should be computed as relative * depends on the count of pages to be scrolled or just fixed. */ private static final int PFLAG_PAGE_SCROLL_RELATIVE_DURATION_ENABLED = 0x00000001 << 18; /** * Name of the scroller field within the super ViewPager used to attach custom scroller to this * ViewPager implementation. */ private static final String SCROLLER_FIELD_NAME = "mScroller"; /** * Static members ============================================================================== */ /** * Interpolator used for scrolling operations of this view. */ private static final Interpolator SCROLLER_INTERPOLATOR = new ScrollerInterpolator(); /** * Members ===================================================================================== */ /** * Decorator used to extend API of this widget group by functionality otherwise not supported or * not available due to current API level. */ private Decorator mDecorator; /** * Index of the currently selected page. */ private int mCurrentPage; /** * Duration for transition used to scroll between pages. If <b>negative</b>, the default duration * will be used. */ private int mPageScrollDuration = -1; /** * Helper used to track velocity of fling to determine whether to initiate swipe of page or not. */ private VelocityTracker mVelocityTracker; /** * Minimum sensitivity to initiate swipe of page on fling. */ private float mPageFlingSwipingSensitivity = 6000; /** * Adapter with data set for this view pager. */ private PagerAdapter mAdapter; /** * Custom scroller set to this view pager to provide custom scrolling logic. */ private Scroller mCustomScroller; /** * Constructors ================================================================================ */ /** * Same as {@link #ViewPagerWidget(android.content.Context, android.util.AttributeSet)} without * attributes. */ public ViewPagerWidget(Context context) { this(context, null); } /** * Same as {@link #ViewPagerWidget(android.content.Context, android.util.AttributeSet, int)} * with {@link R.attr#uiViewPagerStyle} as attribute for default style. */ public ViewPagerWidget(Context context, AttributeSet attrs) { this(context, attrs, R.attr.uiViewPagerStyle); } /** * Same as {@link #ViewPagerWidget(android.content.Context, android.util.AttributeSet, int, int)} * with {@code 0} as default style. */ public ViewPagerWidget(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } /** * Creates a new instance of ViewPagerWidget within the given <var>context</var>. * * @param context Context in which will be the new view presented. * @param attrs Set of Xml attributes used to configure the new instance of this view. * @param defStyleAttr An attribute which contains a reference to a default style resource for * this view within a theme of the given context. * @param defStyleRes Resource id of the default style for the new view. */ public ViewPagerWidget(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs); this.init(context, attrs, defStyleAttr, defStyleRes); } /** * Methods ===================================================================================== */ /** * Called from one of constructors of this view to perform its initialization. * <p> * Initialization is done via parsing of the specified <var>attrs</var> set and obtaining for * this view specific data from it that can be used to configure this new view instance. The * specified <var>defStyleAttr</var> and <var>defStyleRes</var> are used to obtain default data * from the current theme provided by the specified <var>context</var>. */ private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { this.ensureDecorator(); mDecorator.processAttributes(context, attrs, defStyleAttr, defStyleRes); final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.Ui_ViewPager, defStyleAttr, defStyleRes); if (typedArray != null) { final int n = typedArray.getIndexCount(); for (int i = 0; i < n; i++) { final int index = typedArray.getIndex(i); if (index == R.styleable.Ui_ViewPager_android_background) { int resID = typedArray.getResourceId(index, -1); if (resID != -1) { setBackgroundResource(resID); } else { setBackgroundColor(typedArray.getColor(0, Color.TRANSPARENT)); } } else if (index == R.styleable.Ui_ViewPager_uiPageMargin) { setPageMargin(typedArray.getDimensionPixelSize(index, 0)); } else if (index == R.styleable.Ui_ViewPager_uiPageMarginDrawable) { setPageMarginDrawable(typedArray.getDrawable(index)); } else if (index == R.styleable.Ui_ViewPager_uiPageSwipingEnabled) { mDecorator.updatePrivateFlags(PFLAG_PAGE_SWIPING_ENABLED, typedArray.getBoolean(index, true)); } else if (index == R.styleable.Ui_ViewPager_uiPageFlingSwipingEnabled) { mDecorator.updatePrivateFlags(PFLAG_PAGE_FLING_SWIPING_ENABLED, typedArray.getBoolean(index, false)); } else if (index == R.styleable.Ui_ViewPager_uiPageFlingSwipingSensitivity) { this.mPageFlingSwipingSensitivity = Math.max(0, typedArray.getFloat(index, mPageFlingSwipingSensitivity)); } else if (index == R.styleable.Ui_ViewPager_uiCurrentPage) { this.mCurrentPage = typedArray.getInteger(index, 0); } else if (index == R.styleable.Ui_ViewPager_uiOffScreenPageLimit) { setOffscreenPageLimit(typedArray.getInt(index, getOffscreenPageLimit())); } else if (index == R.styleable.Ui_ViewPager_uiPageScrollDuration) { this.mPageScrollDuration = typedArray.getInteger(index, mPageScrollDuration); } else if (index == R.styleable.Ui_ViewPager_uiPageScrollRelativeDurationEnabled) { mDecorator.updatePrivateFlags(PFLAG_PAGE_SCROLL_RELATIVE_DURATION_ENABLED, typedArray.getBoolean(index, false)); } else if (index == R.styleable.Ui_ViewPager_uiPullEnabled) { setPullEnabled(typedArray.getBoolean(index, false)); } } typedArray.recycle(); } // Override default scroller so we can use custom durations for scroll if requested. setScroller(new WidgetScroller(context, SCROLLER_INTERPOLATOR)); } /** * Ensures that the decorator for this view is initialized. */ private void ensureDecorator() { if (mDecorator == null) this.mDecorator = new Decorator(this); } /** */ @Override @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) public void onInitializeAccessibilityEvent(@NonNull AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); event.setClassName(ViewPagerWidget.class.getName()); } /** */ @Override @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) public void onInitializeAccessibilityNodeInfo(@NonNull AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); info.setClassName(ViewPagerWidget.class.getName()); } /** */ @Override @SuppressWarnings("deprecation") public void setBackgroundDrawable(Drawable background) { super.setBackgroundDrawable(background); this.ensureDecorator(); mDecorator.applyBackgroundTint(); } /** * <b>Note, that on pre {@link android.os.Build.VERSION_CODES#LOLLIPOP LOLLIPOP} Android versions * this method will return an instance of {@link TintDrawable TintDrawable} if tint has been applied * via {@link #setBackgroundTintList(ColorStateList)}.</b> * <p> * The original wrapped drawable can be obtained via {@link TintDrawable#getDrawable()}. */ @Override public Drawable getBackground() { return super.getBackground(); } /** */ @Override public void setBackgroundTintList(@Nullable ColorStateList tint) { this.ensureDecorator(); mDecorator.setBackgroundTintList(tint); } /** */ @Nullable @Override public ColorStateList getBackgroundTintList() { this.ensureDecorator(); return mDecorator.getBackgroundTintList(); } /** */ @Override public void setBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) { this.ensureDecorator(); mDecorator.setBackgroundTintMode(tintMode); } /** */ @Nullable @Override public PorterDuff.Mode getBackgroundTintMode() { this.ensureDecorator(); return mDecorator.getBackgroundTintMode(); } /** */ @Override public void setFractionX(float fraction) { this.ensureDecorator(); mDecorator.setFractionX(fraction); } /** */ @Override public float getFractionX() { this.ensureDecorator(); return mDecorator.getFractionX(); } /** */ @Override public void setFractionY(float fraction) { this.ensureDecorator(); mDecorator.setFractionY(fraction); } /** */ @Override public float getFractionY() { this.ensureDecorator(); return mDecorator.getFractionY(); } /** */ @Override public void setPressed(boolean pressed) { final boolean isPressed = isPressed(); super.setPressed(pressed); if (!isPressed && pressed) onPressed(); else if (isPressed) onReleased(); } /** * Invoked whenever {@link #setPressed(boolean)} is called with {@code true} and this view * isn't in the pressed state yet. */ protected void onPressed() { } /** * Invoked whenever {@link #setPressed(boolean)} is called with {@code false} and this view * is currently in the pressed state. */ protected void onReleased() { } /** */ @Override public void setSelected(boolean selected) { this.ensureDecorator(); mDecorator.setSelected(selected); } /** */ @Override public void setSelectionState(boolean selected) { this.ensureDecorator(); mDecorator.setSelectionState(selected); } /** */ @Override public void setAllowDefaultSelection(boolean allow) { this.ensureDecorator(); mDecorator.setAllowDefaultSelection(allow); } /** */ @Override public boolean allowsDefaultSelection() { this.ensureDecorator(); return mDecorator.allowsDefaultSelection(); } /** */ @NonNull @Override public WidgetSizeAnimator animateSize() { this.ensureDecorator(); return mDecorator.animateSize(); } /** */ @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); this.ensureDecorator(); mDecorator.onSizeChanged(w, h, oldw, oldh); } /** */ @Override public boolean onInterceptTouchEvent(MotionEvent event) { this.ensureDecorator(); if (!mDecorator.hasPrivateFlag(PFLAG_PAGE_SWIPING_ENABLED)) { return false; } if (mDecorator.onInterceptTouchEvent(event)) { this.requestParentDisallowInterceptTouchEvent(true); return true; } return super.onInterceptTouchEvent(event); } /** */ @Override public boolean onTouchEvent(@NonNull MotionEvent event) { this.ensureDecorator(); if (!mDecorator.hasPrivateFlag(PFLAG_PAGE_SWIPING_ENABLED)) { return false; } if (mDecorator.onTouchEvent(event)) { this.requestParentDisallowInterceptTouchEvent(true); return true; } if (mDecorator.hasPrivateFlag(PFLAG_PAGE_FLING_SWIPING_ENABLED)) { this.ensureVelocityTracker(); mVelocityTracker.addMovement(event); switch (event.getAction() & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: mVelocityTracker.computeCurrentVelocity(UiConfig.VELOCITY_UNITS); final float xVelocity = mVelocityTracker.getXVelocity(); if (Math.abs(xVelocity) > mPageFlingSwipingSensitivity) { super.onTouchEvent(event); this.handleFling(xVelocity); return true; } } } return super.onTouchEvent(event); } /** * Ensures that the {@link #mVelocityTracker} is initialized. */ private void ensureVelocityTracker() { if (mVelocityTracker == null) this.mVelocityTracker = VelocityTracker.obtain(); } /** * Handles fling event performed by a user with the specified <var>velocity</var>. This will * compute how many pages should be scrolled and than will call {@link #setCurrentItem(int)} * for the computed page position. * * @param velocity The velocity with which has been fling performed. */ private void handleFling(float velocity) { if (mAdapter == null) { return; } int scrollPages = Math.round(Math.abs(velocity) / mPageFlingSwipingSensitivity); if (velocity > 0) { setCurrentItem(Math.max(0, getCurrentItem() - scrollPages)); } else { setCurrentItem(Math.min(mAdapter.getCount() - 1, getCurrentItem() + scrollPages)); } } /** */ @Override public void setAdapter(PagerAdapter adapter) { super.setAdapter(mAdapter = adapter); } /** */ @Override public void setPullEnabled(boolean enabled) { this.ensureDecorator(); mDecorator.setPullEnabled(enabled); } /** */ @Override public boolean isPullEnabled() { this.ensureDecorator(); return mDecorator.isPullEnabled(); } /** * Returns the controller used to support the <b>pullable</b> feature for this view. * * @return PullController of this pullable view. */ @NonNull public PullController getPullController() { this.ensureDecorator(); return mDecorator.getPullController(); } /** */ @Override public int getOrientation() { return Orientation.HORIZONTAL; } /** */ @Override public boolean isScrolledAtStart() { this.ensureDecorator(); return mDecorator.isScrolledAtStart(); } /** */ @Override public boolean isScrolledAtEnd() { this.ensureDecorator(); return mDecorator.isScrolledAtEnd(); } /** */ @Override protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) { super.onOverScrolled(scrollX, scrollY, clampedX, clampedY); this.ensureDecorator(); mDecorator.onOverScrolled(scrollX, scrollY, clampedX, clampedY); } /** */ @Override public void setCurrentItem(int item) { if (mPageScrollDuration >= 0) { setCurrentItem(item, calculatePageScrollDuration(item)); return; } this.resetPageScrollDuration(); super.setCurrentItem(mCurrentPage = item); } /** */ @Override public void setCurrentItem(int item, boolean smoothScroll) { if (mPageScrollDuration >= 0 && smoothScroll) { setCurrentItem(item, calculatePageScrollDuration(item)); return; } super.setCurrentItem(mCurrentPage = item, smoothScroll); } /** * Same as {@link #setCurrentItem(int)}, but this will use the specified <var>duration</var> for * scroll. <b>Note</b>, that this duration will be used only for this particular call, if you * want to use a specific duration for all {@link #setCurrentItem(int)} related calls, set your * desired duration by {@link #setPageScrollDuration(int)}. * * @param item A position of page to scroll to. * @param duration The desired scroll duration used to scroll to the specified page in milliseconds. */ public void setCurrentItem(int item, int duration) { this.usePageScrollDuration(duration); super.setCurrentItem(mCurrentPage = item, duration > 0); this.resetPageScrollDuration(); } /** * Sets the scroller for this view pager used to apply and animate scroll to the pages of this * pager. * * @param scroller The desired scroller to be used by this pager. */ public void setScroller(@NonNull Scroller scroller) { this.setSuperScroller(mCustomScroller = scroller); } /** * Sets a custom scroller to the super of this view pager. * * @param scroller The scroller that should be set to the super ViewPager. */ private void setSuperScroller(Scroller scroller) { final Class parentClass = ViewPagerWidget.class.getSuperclass(); try { final Field scrollerField = parentClass.getDeclaredField(SCROLLER_FIELD_NAME); scrollerField.setAccessible(true); scrollerField.set(this, scroller); } catch (Exception e) { Log.e(TAG, "Failed to attach custom scroller to the super ViewPager.", e); } } /** * Sets a flag indicating whether the swiping of pages on a user's touch/drag is enabled or not. * This enables/disables only interaction for the user, calls to {@link #setCurrentItem(int)} and * similar methods will still be working. * * @param enabled {@code True} if page swiping should be enabled, {@code false} otherwise. * @see R.attr#uiPageSwipingEnabled ui:uiPageSwipingEnabled * @see #isPageFlingSwipingEnabled() */ public void setPageSwipingEnabled(boolean enabled) { this.ensureDecorator(); mDecorator.updatePrivateFlags(PFLAG_PAGE_SWIPING_ENABLED, enabled); } /** * Returns a flag indicating whether the swiping of pages is enabled for a user or not. * * @return {@code True} if page swiping is enabled, {@code false} otherwise. * @see #setPageSwipingEnabled(boolean) */ public boolean isPageSwipingEnabled() { this.ensureDecorator(); return mDecorator.hasPrivateFlag(PFLAG_PAGE_SWIPING_ENABLED); } /** * Sets a flag indicating whether the swiping of multiple pages on a user's fling is enabled or * not. Enabling this feature means, that the user can by fling scroll multiple pages at once. * How many pages will be scrolled at fling depends on the velocity of a specific fling. Sensitivity * for the fling can be set by {@link #setPageFlingSwipingSensitivity(float)}. * * @param enabled {@code True} if page swiping on fling should be enabled, {@code false} otherwise. * @see R.attr#uiPageFlingSwipingEnabled ui:uiPageFlingSwipingEnabled * @see #isPageFlingSwipingEnabled() */ public void setPageFlingSwipingEnabled(boolean enabled) { this.ensureDecorator(); mDecorator.updatePrivateFlags(PFLAG_PAGE_FLING_SWIPING_ENABLED, enabled); } /** * Returns a flag indicating whether the swiping of multiple pages on fling is enabled or not. * * @return {@code True} if page swiping on fling is enabled, {@code false} otherwise. * @see #setPageFlingSwipingEnabled(boolean) */ public boolean isPageFlingSwipingEnabled() { this.ensureDecorator(); return mDecorator.hasPrivateFlag(PFLAG_PAGE_FLING_SWIPING_ENABLED); } /** * Sets a sensitivity for the velocity of a user's fling used to determine whether to handle a * particular fling to swipe multiple pages at once or not. * <p> * <b>Note</b>, that velocity of fling is computed in {@link UiConfig#VELOCITY_UNITS}. * * @param sensitivity The desired sensitivity. The bigger sensitivity is, the less pages will * be scrolled at once and in reverse. * @see R.attr#uiPageFlingSwipingSensitivity ui:uiPageFlingSwipingSensitivity * @see #getPageFlingSwipingSensitivity() * @see #setPageFlingSwipingEnabled(boolean) */ public void setPageFlingSwipingSensitivity(float sensitivity) { this.mPageFlingSwipingSensitivity = Math.max(0, sensitivity); } /** * Returns the current sensitivity for the fling velocity used to determine whether to handle * a user's fling or not. * <p> * Default value: <b>6000</b> * * @return Swiping sensitivity. * @see #setPageFlingSwipingSensitivity(float) */ public float getPageFlingSwipingSensitivity() { return mPageFlingSwipingSensitivity; } /** * Sets the duration for scroll used whenever {@link #setCurrentItem(int)} or similar methods * are called upon this view pager. * * @param duration The desired scroll duration in milliseconds. Pass here {@code -1} to clear * the current one, so the default one will be used. * @see #getPageScrollDuration() */ public void setPageScrollDuration(int duration) { this.mPageScrollDuration = duration; } /** * Returns the current duration used for scrolling of pages of this pager. * * @return Scroll duration in milliseconds. * @see #setPageScrollDuration(int) */ public int getPageScrollDuration() { return mPageScrollDuration; } /** * Sets a flag indicating whether the duration of page scroll should be computed as a relative * one depends on the count of pages to be scrolled, or as a fixed one regardless how many pages * will be scrolled. * <p> * If the relative duration is enabled, the scroll duration requested by {@link #setPageScrollDuration(int)} * is used as a base, so a duration for the current page scroll is computed like so: * <b>{@code Math.abs(getCurrentItem() - position) * pageScrollDuration}</b> * * @param enabled {@code True} if relative duration of page scroll should be enabled, {@code false} * otherwise. * @see #isPageScrollRelativeDurationEnabled() */ public void setPageScrollRelativeDurationEnabled(boolean enabled) { this.ensureDecorator(); mDecorator.updatePrivateFlags(PFLAG_PAGE_SCROLL_RELATIVE_DURATION_ENABLED, enabled); } /** * Returns a flag indicating whether the duration of pages scroll is computed as a relative one * or not. * * @return {@code True} if duration of page scroll is computed as relative one, {@code false} as * fixed one. * @see #setPageScrollRelativeDurationEnabled(boolean) */ public boolean isPageScrollRelativeDurationEnabled() { this.ensureDecorator(); return mDecorator.hasPrivateFlag(PFLAG_PAGE_SCROLL_RELATIVE_DURATION_ENABLED); } /** */ @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); this.ensureDecorator(); mDecorator.onAttachedToWindow(); setCurrentItem(mCurrentPage); } /** * Updates the scroll duration for the current scroller (if instance of {@link WidgetScroller}), * so this duration will be used when {@link WidgetScroller#startScroll(int, int, int, int, int)} * will be next time called upon this scroller. * * @param duration The desired scroll duration in milliseconds. Pass here {@code -1} to clear * the current one, so the default one will be used. */ private void usePageScrollDuration(int duration) { if (mCustomScroller instanceof WidgetScroller) { ((WidgetScroller) mCustomScroller).setScrollDuration(duration); } } /** * Resets the current scroll duration for pages. */ private void resetPageScrollDuration() { usePageScrollDuration(-1); } /** * Calculates scroll duration for page at the specified <var>position</var>. The calculated * duration will depends on if the relative duration is enabled or not via * {@link #setPageScrollRelativeDurationEnabled(boolean)}. * * @param position The position of page for which to calculate scroll duration. * @return Calculated scroll duration in milliseconds. */ private int calculatePageScrollDuration(int position) { this.ensureDecorator(); if (mDecorator.hasPrivateFlag(PFLAG_PAGE_SCROLL_RELATIVE_DURATION_ENABLED)) { return Math.abs(getCurrentItem() - position) * mPageScrollDuration; } return mPageScrollDuration; } /** * Requests the current parent to disallow intercepting of touch event by {@link ViewParent#requestDisallowInterceptTouchEvent(boolean)}. * * @param disallow {@code True} to disallow, {@code false} otherwise. */ private void requestParentDisallowInterceptTouchEvent(boolean disallow) { final ViewParent parent = getParent(); if (parent != null) { parent.requestDisallowInterceptTouchEvent(disallow); } } /** */ @Override @SuppressWarnings("NewApi") public boolean isAttachedToWindow() { this.ensureDecorator(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { return super.isAttachedToWindow(); } else { return mDecorator.hasPrivateFlag(PrivateFlags.PFLAG_ATTACHED_TO_WINDOW); } } /** */ @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); this.ensureDecorator(); mDecorator.onDetachedFromWindow(); } /** * Inner classes =============================================================================== */ /** * Decorator implementation for this widget group. */ private final class Decorator extends PullableDecorator<ViewPagerWidget> { /** * See {@link WidgetGroupDecorator#WidgetGroupDecorator(ViewGroup)}. */ Decorator(ViewPagerWidget widgetGroup) { super(widgetGroup, R.styleable.Ui_ViewPager); updatePrivateFlags(PFLAG_PAGE_SWIPING_ENABLED, true); } /** */ @Override @SuppressWarnings("ResourceType") void onProcessTintValues(Context context, TypedArray tintArray, int tintColor) { if (UiConfig.MATERIALIZED) { if (tintArray.hasValue(R.styleable.Ui_ViewPager_uiBackgroundTint)) { setBackgroundTintList(tintArray.getColorStateList(R.styleable.Ui_ViewPager_uiBackgroundTint)); } if (tintArray.hasValue(R.styleable.Ui_ViewPager_uiBackgroundTintMode)) { setBackgroundTintMode(TintManager.parseTintMode( tintArray.getInt(R.styleable.Ui_ViewPager_uiBackgroundTintMode, 0), PorterDuff.Mode.SRC_IN)); } } else { if (tintArray.hasValue(R.styleable.Ui_ViewPager_uiBackgroundTint)) { mTintInfo.backgroundTintList = tintArray .getColorStateList(R.styleable.Ui_ViewPager_uiBackgroundTint); } mTintInfo.backgroundTintMode = TintManager.parseTintMode( tintArray.getInt(R.styleable.Ui_ViewPager_uiBackgroundTintMode, 0), mTintInfo.backgroundTintList != null ? PorterDuff.Mode.SRC_IN : null); } } /** */ @Override void superSetSelected(boolean selected) { ViewPagerWidget.super.setSelected(selected); } /** */ @Override @SuppressWarnings("deprecation") void superSetBackgroundDrawable(Drawable drawable) { ViewPagerWidget.super.setBackgroundDrawable(drawable); } /** */ @Override @TargetApi(Build.VERSION_CODES.LOLLIPOP) void superSetBackgroundTintList(ColorStateList tint) { ViewPagerWidget.super.setBackgroundTintList(tint); } /** */ @Override @TargetApi(Build.VERSION_CODES.LOLLIPOP) ColorStateList superGetBackgroundTintList() { return ViewPagerWidget.super.getBackgroundTintList(); } /** */ @Override @TargetApi(Build.VERSION_CODES.LOLLIPOP) void superSetBackgroundTintMode(PorterDuff.Mode tintMode) { ViewPagerWidget.super.setBackgroundTintMode(tintMode); } /** */ @Override @TargetApi(Build.VERSION_CODES.LOLLIPOP) PorterDuff.Mode superGetBackgroundTintMode() { return ViewPagerWidget.super.getBackgroundTintMode(); } } }