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.support.ui.widget; import android.annotation.SuppressLint; 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.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.VelocityTracker; 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.support.ui.PullController; import com.albedinsky.android.support.ui.R; import com.albedinsky.android.support.ui.UiConfig; import com.albedinsky.android.support.ui.WidgetSizeAnimator; import com.albedinsky.android.support.ui.graphics.TintOptions; import com.albedinsky.android.support.ui.graphics.drawable.TintDrawable; import com.albedinsky.android.support.ui.interpolator.ScrollerInterpolator; import com.albedinsky.android.support.ui.widget.adapter.PagerAdapter; /** * Extended version of {@link android.support.v4.view.ViewPager}. This updated ViewPager supports * <b>pull</b> feature and some additional features described below, like setting custom duration * for pages scroll by {@link #setPageScrollDuration(int)} or enabling swiping of multiple pages * on fling at once by {@link #setPageFlingSwipingEnabled(boolean)}. * * <h3>Pulling</h3> * This view can be pulled at its start and also at its end, using the {@link com.albedinsky.android.support.ui.PullController PullController} * to support this feature. The ViewPagerWidget is view with {@link Pullable#HORIZONTAL} orientation, * so its content can be pulled at the left or at the right by offsetting its current position using * {@link #offsetLeftAndRight(int)} method. The Xml attributes below can be used to customize pull * feature for this view: * <ul> * <li>{@link R.attr#uiPullMode uiPullMode}</li> * <li>{@link R.attr#uiPullDistanceFraction uiPullDistanceFraction}</li> * <li>{@link R.attr#uiPullDistance uiPullDistance}</li> * <li>{@link R.attr#uiPullCollapseDuration uiPullCollapseDuration}</li> * <li>{@link R.attr#uiPullCollapseDelay uiPullCollapseDelay}</li> * <li>{@link R.attr#uiPullMinVelocity uiPullMinVelocity}</li> * </ul> * See class overview of {@link com.albedinsky.android.support.ui.PullController PullController} for * additional info. * * <h3>Sliding</h3> * This updated view allows updating of its current position along <b>x</b> and <b>y</b> axis by * changing <b>fraction</b> of these properties depends on its current size using the new animation * framework introduced in {@link android.os.Build.VERSION_CODES#HONEYCOMB HONEYCOMB} by * {@link android.animation.ObjectAnimator ObjectAnimator}s API. * <p> * Changing of fraction of X or Y is supported by these two methods: * <ul> * <li>{@link #setFractionX(float)}</li> * <li>{@link #setFractionY(float)}</li> * </ul> * <p> * For example if an instance of this view class needs to be slided to the right by whole width of * such a view, an Xml file with ObjectAnimator will 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> * If this layout class is used as a root of view hierarchy, this can be especially used for fragment * transitions framework, where for {@link android.support.v4.app.FragmentTransaction FragmentTransaction}, * used to change currently visible fragment by a new one, can be specified custom animations which * will change fragments by sliding them horizontally. * * <h3>Styling</h3> * The ViewPagerWidget view uses style specified by {@link R.attr#uiViewPagerStyle} within the current * theme to allow base set up. The Xml attributes below will be parsed from such a style: * <ul> * <li>{@link android.R.attr#background android:background}</li> * <li>{@link R.attr#uiPageMargin uiPageMargin}</li> * <li>{@link R.attr#uiPageMarginDrawable uiPageMarginDrawable}</li> * <li>{@link R.attr#uiPageSwipingEnabled uiPageSwipingEnabled}</li> * <li>{@link R.attr#uiPageFlingSwipingEnabled uiPageFlingSwipingEnabled}</li> * <li>{@link R.attr#uiPageFlingSwipingSensitivity uiPageFlingSwipingSensitivity}</li> * <li>{@link R.attr#uiPageScrollDuration uiPageScrollDuration}</li> * <li>{@link R.attr#uiPageScrollRelativeDurationEnabled uiPageScrollRelativeDurationEnabled}</li> * <li>{@link R.attr#uiCurrentPage uiCurrentPage}</li> * <li>{@link R.attr#uiOffScreenPageLimit uiOffScreenPageLimit}</li> * <li>{@link R.attr#uiPullEnabled uiPullEnabled}</li> * </ul> * * @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 debug output trough log-cat is enabled or not. */ // private static final boolean DEBUG_ENABLED = true; /** * Flag indicating whether the output trough log-cat is enabled or not. */ // private static final boolean LOG_ENABLED = true; /** * Flag indicating whether the swiping of pages on touch is enabled or not. */ private static final int PFLAG_PAGE_SWIPING_ENABLED = 0x00008000; /** * 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 = 0x00010000; /** * 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 = 0x00020000; /** * Static members ============================================================================== */ /** * Interpolator used for scrolling operations of this view. */ private static final Interpolator SCROLLER_INTERPOLATOR = new ScrollerInterpolator(); /** * Members ===================================================================================== */ /** * This view's dimension. */ private int mWidth, mHeight; /** * Animator used to animate size of this view. */ private WidgetSizeAnimator mSizeAnimator; /** * Controller used to support pull feature for this view. */ private PullController mPullController; /** * Index of the currently selected page. */ private int mCurrentPage; /** * Set of private flags specified for this view. */ private int mPrivateFlags = PrivateFlags.PFLAG_ALLOWS_DEFAULT_SELECTION | PFLAG_PAGE_SWIPING_ENABLED; /** * 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; /** * Data used when tinting components of this view. */ private BackgroundTintInfo mTintInfo; /** * 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); } /** * Creates a new instance of ViewPagerWidget within the given <var>context</var>. * * @param context Context in which will be this 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. */ public ViewPagerWidget(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs); /** * Process attributes. */ final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.Ui_Widget_ViewPager, defStyleAttr, 0); if (typedArray != null) { this.ensurePullController(); mPullController.setUpFromAttrs(context, attrs, defStyleAttr); final int n = typedArray.getIndexCount(); for (int i = 0; i < n; i++) { final int index = typedArray.getIndex(i); if (index == R.styleable.Ui_Widget_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_Widget_ViewPager_uiPageMargin) { setPageMargin(typedArray.getDimensionPixelSize(index, 0)); } else if (index == R.styleable.Ui_Widget_ViewPager_uiPageMarginDrawable) { setPageMarginDrawable(typedArray.getDrawable(index)); } else if (index == R.styleable.Ui_Widget_ViewPager_uiPageSwipingEnabled) { updatePrivateFlags(PFLAG_PAGE_SWIPING_ENABLED, typedArray.getBoolean(index, true)); } else if (index == R.styleable.Ui_Widget_ViewPager_uiPageFlingSwipingEnabled) { updatePrivateFlags(PFLAG_PAGE_FLING_SWIPING_ENABLED, typedArray.getBoolean(index, false)); } else if (index == R.styleable.Ui_Widget_ViewPager_uiPageFlingSwipingSensitivity) { this.mPageFlingSwipingSensitivity = Math.max(0, typedArray.getFloat(index, mPageFlingSwipingSensitivity)); } else if (index == R.styleable.Ui_Widget_ViewPager_uiCurrentPage) { this.mCurrentPage = typedArray.getInteger(index, 0); } else if (index == R.styleable.Ui_Widget_ViewPager_uiOffScreenPageLimit) { setOffscreenPageLimit(typedArray.getInt(index, getOffscreenPageLimit())); } else if (index == R.styleable.Ui_Widget_ViewPager_uiPageScrollDuration) { this.mPageScrollDuration = typedArray.getInteger(index, mPageScrollDuration); } else if (index == R.styleable.Ui_Widget_ViewPager_uiPageScrollRelativeDurationEnabled) { updatePrivateFlags(PFLAG_PAGE_SCROLL_RELATIVE_DURATION_ENABLED, typedArray.getBoolean(index, false)); } else if (index == R.styleable.Ui_Widget_ViewPager_uiPullEnabled) { setPullEnabled(typedArray.getBoolean(index, false)); } } } // Override default scroller so we can use custom durations for scroll if requested. this.mScroller = new WidgetScroller(context, SCROLLER_INTERPOLATOR); } /** * Methods ===================================================================================== */ /** * Public -------------------------------------------------------------------------------------- */ /** */ @NonNull @Override public WidgetSizeAnimator animateSize() { return (mSizeAnimator != null) ? mSizeAnimator : (mSizeAnimator = new WidgetSizeAnimator(this)); } /** */ @Override public void onInitializeAccessibilityEvent(@NonNull AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); event.setClassName(ViewPagerWidget.class.getName()); } /** */ @Override public void onInitializeAccessibilityNodeInfo(@NonNull AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); info.setClassName(ViewPagerWidget.class.getName()); } /** */ @Override public boolean onInterceptTouchEvent(MotionEvent event) { if (!hasPrivateFlag(PFLAG_PAGE_SWIPING_ENABLED)) { return false; } if (hasPrivateFlag(PrivateFlags.PFLAG_PULL_ENABLED) && mPullController.shouldInterceptTouchEvent(event)) { this.requestParentDisallowInterceptTouchEvent(true); return true; } return super.onInterceptTouchEvent(event); } /** */ @Override public boolean onTouchEvent(@NonNull MotionEvent event) { if (!hasPrivateFlag(PFLAG_PAGE_SWIPING_ENABLED)) { return false; } if (hasPrivateFlag(PrivateFlags.PFLAG_PULL_ENABLED) && mPullController.processTouchEvent(event)) { this.requestParentDisallowInterceptTouchEvent(true); return true; } if (hasPrivateFlag(PFLAG_PAGE_FLING_SWIPING_ENABLED)) { this.ensureVelocityTracker(); mVelocityTracker.addMovement(event); switch (event.getActionMasked()) { 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); } /** * Getters + Setters --------------------------------------------------------------------------- */ /** */ @Override public void setAdapter(PagerAdapter adapter) { super.setAdapter(mAdapter = adapter); } /** */ @Override public void setCurrentItem(int item) { if (mPageScrollDuration >= 0) { setCurrentItem(item, computePageScrollDuration(item)); return; } this.resetPageScrollDuration(); super.setCurrentItem(item); } /** */ @Override public void setCurrentItem(int item, boolean smoothScroll) { if (mPageScrollDuration >= 0 && smoothScroll) { setCurrentItem(item, computePageScrollDuration(item)); return; } super.setCurrentItem(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(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.mScroller = scroller; } /** * 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. */ public void setPageSwipingEnabled(boolean enabled) { 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() { return 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. */ public void setPageFlingSwipingEnabled(boolean enabled) { 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() { return 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 #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. */ 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. */ 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. */ 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. */ public void setPageScrollRelativeDurationEnabled(boolean enabled) { 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. */ public boolean isPageScrollRelativeDurationEnabled() { return hasPrivateFlag(PFLAG_PAGE_SCROLL_RELATIVE_DURATION_ENABLED); } /** */ @Override public void setPullEnabled(boolean enabled) { this.updatePrivateFlags(PrivateFlags.PFLAG_PULL_ENABLED, enabled); if (enabled) { this.ensurePullController(); } } /** */ @Override public boolean isPullEnabled() { return hasPrivateFlag(PrivateFlags.PFLAG_PULL_ENABLED); } /** */ @NonNull @Override public PullController getPullController() { this.ensurePullController(); return mPullController; } /** */ @Override public int getOrientation() { return HORIZONTAL; } /** */ @Override @SuppressWarnings("deprecation") public void setBackgroundDrawable(Drawable background) { super.setBackgroundDrawable(background); this.applyBackgroundTint(); } /** */ @Override @SuppressLint("NewApi") public void setBackgroundTintList(@Nullable ColorStateList tint) { if (UiConfig.LOLLIPOP) { super.setBackgroundTintList(tint); return; } this.ensureTintInfo(); mTintInfo.backgroundTintList = tint; mTintInfo.hasBackgroundTintList = true; this.applyBackgroundTint(); } /** */ @Nullable @Override @SuppressLint("NewApi") public ColorStateList getBackgroundTintList() { if (UiConfig.LOLLIPOP) { return super.getBackgroundTintList(); } return mTintInfo != null ? mTintInfo.backgroundTintList : null; } /** */ @Override @SuppressLint("NewApi") public void setBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) { if (UiConfig.LOLLIPOP) { super.setBackgroundTintMode(tintMode); return; } this.ensureTintInfo(); mTintInfo.backgroundTintMode = tintMode; mTintInfo.hasBackgroundTinMode = true; this.applyBackgroundTint(); } /** */ @Nullable @Override @SuppressLint("NewApi") public PorterDuff.Mode getBackgroundTintMode() { if (UiConfig.LOLLIPOP) { return super.getBackgroundTintMode(); } return mTintInfo != null ? mTintInfo.backgroundTintMode : null; } /** */ @Override public void setFractionX(float fraction) { setX(mWidth > 0 ? (getLeft() + (fraction * mWidth)) : OUT_OF_SCREEN); } /** */ @Override public float getFractionX() { return (mWidth > 0) ? (getLeft() + (getX() / mWidth)) : 0; } /** */ @Override public void setFractionY(float fraction) { setY(mHeight > 0 ? (getTop() + (fraction * mHeight)) : OUT_OF_SCREEN); } /** */ @Override public float getFractionY() { return (mHeight > 0) ? (getTop() + (getY() / mHeight)) : 0; } /** */ @Override public void setPressed(boolean pressed) { final boolean isPressed = isPressed(); super.setPressed(pressed); if (!isPressed && pressed) { onPressed(); } else if (isPressed) { onReleased(); } } /** */ @Override public void setSelected(boolean selected) { if (hasPrivateFlag(PrivateFlags.PFLAG_ALLOWS_DEFAULT_SELECTION)) { setSelectionState(selected); } } /** */ @Override public void setSelectionState(boolean selected) { super.setSelected(selected); } /** */ @Override public void setAllowDefaultSelection(boolean allow) { this.updatePrivateFlags(PrivateFlags.PFLAG_ALLOWS_DEFAULT_SELECTION, allow); } /** */ @Override public boolean allowsDefaultSelection() { return this.hasPrivateFlag(PrivateFlags.PFLAG_ALLOWS_DEFAULT_SELECTION); } /** * Protected ----------------------------------------------------------------------------------- */ /** */ @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); setCurrentItem(mCurrentPage); } /** * 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 protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); this.mWidth = w; this.mHeight = h; } /** * Private ------------------------------------------------------------------------------------- */ /** * Ensures that the tint info object is initialized. */ private void ensureTintInfo() { if (mTintInfo == null) { this.mTintInfo = new BackgroundTintInfo(); } } /** * Called from the constructor to process tint values for this view. <b>Note</b>, that for * {@link android.os.Build.VERSION_CODES#LOLLIPOP LOLLIPOP} is this call ignored. * * @param context The context passed to constructor. * @param typedArray TypedArray obtained for styleable attributes specific for this view. */ @SuppressWarnings("All") private void processTintValues(Context context, TypedArray typedArray) { // Do not handle for LOLLIPOP. if (UiConfig.LOLLIPOP) { return; } this.ensureTintInfo(); // Get tint colors. if (typedArray.hasValue(R.styleable.Ui_Widget_TextView_uiBackgroundTint)) { mTintInfo.backgroundTintList = typedArray .getColorStateList(R.styleable.Ui_Widget_TextView_uiBackgroundTint); } // Get tint modes. mTintInfo.backgroundTintMode = TintManager.parseTintMode( typedArray.getInt(R.styleable.Ui_Widget_TextView_uiBackgroundTintMode, 0), mTintInfo.backgroundTintList != null ? PorterDuff.Mode.SRC_IN : null); // If there is no tint mode specified within style/xml do not tint at all. if (mTintInfo.backgroundTintMode == null) { mTintInfo.backgroundTintList = null; } mTintInfo.hasBackgroundTintList = mTintInfo.backgroundTintList != null; mTintInfo.hasBackgroundTinMode = mTintInfo.backgroundTintMode != null; } /** * Applies current background tint from {@link #mTintInfo} to the current background drawable. * <b>Note</b>, that for {@link android.os.Build.VERSION_CODES#LOLLIPOP LOLLIPOP} is this call * ignored. * * @return {@code True} if the tint has been applied or cleared, {@code false} otherwise. */ @SuppressWarnings("deprecation") private boolean applyBackgroundTint() { final Drawable drawable = getBackground(); if (UiConfig.LOLLIPOP || mTintInfo == null || (!mTintInfo.hasBackgroundTintList && !mTintInfo.hasBackgroundTinMode) || drawable == null) { return false; } final TintOptions tintOptions = new TintOptions().tintList(mTintInfo.backgroundTintList) .tintMode(mTintInfo.backgroundTintMode); if (drawable instanceof TintDrawable) { if (!tintOptions.applyable()) { drawable.setCallback(null); drawable.clearColorFilter(); super.setBackgroundDrawable(((TintDrawable) drawable).getDrawable()); } else { ((TintDrawable) drawable).setTintOptions(tintOptions); } return true; } if (!tintOptions.applyable()) { drawable.clearColorFilter(); return true; } final TintDrawable tintDrawable = new TintDrawable(drawable); tintDrawable.setTintOptions(tintOptions); super.setBackgroundDrawable(tintDrawable); tintDrawable.attachCallback(); return true; } /** * 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)); } } /** * 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 (mScroller instanceof WidgetScroller) { ((WidgetScroller) mScroller).setScrollDuration(duration); } } /** * Resets the current scroll duration for pages. */ private void resetPageScrollDuration() { usePageScrollDuration(-1); } /** * Computes scroll duration for page at the specified <var>position</var>. The computed duration * will depends on if the relative duration is enabled or not by {@link #setPageScrollRelativeDurationEnabled(boolean)}. * * @param position The position of page for which to compute scroll duration. * @return Computed scroll duration in milliseconds. */ private int computePageScrollDuration(int position) { if (hasPrivateFlag(PFLAG_PAGE_SCROLL_RELATIVE_DURATION_ENABLED)) { return Math.abs(getCurrentItem() - position) * mPageScrollDuration; } return mPageScrollDuration; } /** * Ensures that the {@link #mPullController} is initialized. */ private void ensurePullController() { if (mPullController == null) { this.mPullController = new PullController(this); } } /** * Ensures that the {@link #mVelocityTracker} is initialized. */ private void ensureVelocityTracker() { if (mVelocityTracker == null) { this.mVelocityTracker = VelocityTracker.obtain(); } } /** * 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); } } /** * Updates the current private flags. * * @param flag Value of the desired flag to add/remove to/from the current private flags. * @param add Boolean flag indicating whether to add or remove the specified <var>flag</var>. */ @SuppressWarnings("unused") private void updatePrivateFlags(int flag, boolean add) { if (add) { this.mPrivateFlags |= flag; } else { this.mPrivateFlags &= ~flag; } } /** * Returns a boolean flag indicating whether the specified <var>flag</var> is contained within * the current private flags or not. * * @param flag Value of the flag to check. * @return {@code True} if the requested flag is contained, {@code false} otherwise. */ @SuppressWarnings("unused") private boolean hasPrivateFlag(int flag) { return (mPrivateFlags & flag) != 0; } /** * Inner classes =============================================================================== */ }