Home | History | Annotate | Download | only in pageindicators
      1 package com.android.launcher3.pageindicators;
      2 
      3 import android.animation.Animator;
      4 import android.animation.AnimatorListenerAdapter;
      5 import android.animation.ObjectAnimator;
      6 import android.animation.ValueAnimator;
      7 import android.content.Context;
      8 import android.content.res.Resources;
      9 import android.graphics.Canvas;
     10 import android.graphics.Color;
     11 import android.graphics.Paint;
     12 import android.graphics.Rect;
     13 import android.os.Handler;
     14 import android.os.Looper;
     15 import android.util.AttributeSet;
     16 import android.util.Property;
     17 import android.view.Gravity;
     18 import android.view.View;
     19 import android.view.ViewConfiguration;
     20 import android.widget.FrameLayout;
     21 
     22 import com.android.launcher3.DeviceProfile;
     23 import com.android.launcher3.Insettable;
     24 import com.android.launcher3.Launcher;
     25 import com.android.launcher3.R;
     26 import com.android.launcher3.Utilities;
     27 import com.android.launcher3.uioverrides.WallpaperColorInfo;
     28 
     29 /**
     30  * A PageIndicator that briefly shows a fraction of a line when moving between pages
     31  *
     32  * The fraction is 1 / number of pages and the position is based on the progress of the page scroll.
     33  */
     34 public class WorkspacePageIndicator extends View implements Insettable, PageIndicator {
     35 
     36     private static final int LINE_ANIMATE_DURATION = ViewConfiguration.getScrollBarFadeDuration();
     37     private static final int LINE_FADE_DELAY = ViewConfiguration.getScrollDefaultDelay();
     38     public static final int WHITE_ALPHA = (int) (0.70f * 255);
     39     public static final int BLACK_ALPHA = (int) (0.65f * 255);
     40 
     41     private static final int LINE_ALPHA_ANIMATOR_INDEX = 0;
     42     private static final int NUM_PAGES_ANIMATOR_INDEX = 1;
     43     private static final int TOTAL_SCROLL_ANIMATOR_INDEX = 2;
     44     private static final int ANIMATOR_COUNT = 3;
     45 
     46     private ValueAnimator[] mAnimators = new ValueAnimator[ANIMATOR_COUNT];
     47 
     48     private final Handler mDelayedLineFadeHandler = new Handler(Looper.getMainLooper());
     49     private final Launcher mLauncher;
     50 
     51     private boolean mShouldAutoHide = true;
     52 
     53     // The alpha of the line when it is showing.
     54     private int mActiveAlpha = 0;
     55     // The alpha that the line is being animated to or already at (either 0 or mActiveAlpha).
     56     private int mToAlpha;
     57     // A float value representing the number of pages, to allow for an animation when it changes.
     58     private float mNumPagesFloat;
     59     private int mCurrentScroll;
     60     private int mTotalScroll;
     61     private Paint mLinePaint;
     62     private final int mLineHeight;
     63 
     64     private static final Property<WorkspacePageIndicator, Integer> PAINT_ALPHA
     65             = new Property<WorkspacePageIndicator, Integer>(Integer.class, "paint_alpha") {
     66         @Override
     67         public Integer get(WorkspacePageIndicator obj) {
     68             return obj.mLinePaint.getAlpha();
     69         }
     70 
     71         @Override
     72         public void set(WorkspacePageIndicator obj, Integer alpha) {
     73             obj.mLinePaint.setAlpha(alpha);
     74             obj.invalidate();
     75         }
     76     };
     77 
     78     private static final Property<WorkspacePageIndicator, Float> NUM_PAGES
     79             = new Property<WorkspacePageIndicator, Float>(Float.class, "num_pages") {
     80         @Override
     81         public Float get(WorkspacePageIndicator obj) {
     82             return obj.mNumPagesFloat;
     83         }
     84 
     85         @Override
     86         public void set(WorkspacePageIndicator obj, Float numPages) {
     87             obj.mNumPagesFloat = numPages;
     88             obj.invalidate();
     89         }
     90     };
     91 
     92     private static final Property<WorkspacePageIndicator, Integer> TOTAL_SCROLL
     93             = new Property<WorkspacePageIndicator, Integer>(Integer.class, "total_scroll") {
     94         @Override
     95         public Integer get(WorkspacePageIndicator obj) {
     96             return obj.mTotalScroll;
     97         }
     98 
     99         @Override
    100         public void set(WorkspacePageIndicator obj, Integer totalScroll) {
    101             obj.mTotalScroll = totalScroll;
    102             obj.invalidate();
    103         }
    104     };
    105 
    106     private Runnable mHideLineRunnable = () -> animateLineToAlpha(0);
    107 
    108     public WorkspacePageIndicator(Context context) {
    109         this(context, null);
    110     }
    111 
    112     public WorkspacePageIndicator(Context context, AttributeSet attrs) {
    113         this(context, attrs, 0);
    114     }
    115 
    116     public WorkspacePageIndicator(Context context, AttributeSet attrs, int defStyle) {
    117         super(context, attrs, defStyle);
    118 
    119         Resources res = context.getResources();
    120         mLinePaint = new Paint();
    121         mLinePaint.setAlpha(0);
    122 
    123         mLauncher = Launcher.getLauncher(context);
    124         mLineHeight = res.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_line_height);
    125 
    126         boolean darkText = WallpaperColorInfo.getInstance(context).supportsDarkText();
    127         mActiveAlpha = darkText ? BLACK_ALPHA : WHITE_ALPHA;
    128         mLinePaint.setColor(darkText ? Color.BLACK : Color.WHITE);
    129     }
    130 
    131     @Override
    132     protected void onDraw(Canvas canvas) {
    133         if (mTotalScroll == 0 || mNumPagesFloat == 0) {
    134             return;
    135         }
    136 
    137         // Compute and draw line rect.
    138         float progress = Utilities.boundToRange(((float) mCurrentScroll) / mTotalScroll, 0f, 1f);
    139         int availableWidth = getWidth();
    140         int lineWidth = (int) (availableWidth / mNumPagesFloat);
    141         int lineLeft = (int) (progress * (availableWidth - lineWidth));
    142         int lineRight = lineLeft + lineWidth;
    143 
    144         canvas.drawRoundRect(lineLeft, getHeight() / 2 - mLineHeight / 2, lineRight,
    145                 getHeight() / 2 + mLineHeight / 2, mLineHeight, mLineHeight, mLinePaint);
    146     }
    147 
    148     @Override
    149     public void setScroll(int currentScroll, int totalScroll) {
    150         if (getAlpha() == 0) {
    151             return;
    152         }
    153         animateLineToAlpha(mActiveAlpha);
    154 
    155         mCurrentScroll = currentScroll;
    156         if (mTotalScroll == 0) {
    157             mTotalScroll = totalScroll;
    158         } else if (mTotalScroll != totalScroll) {
    159             animateToTotalScroll(totalScroll);
    160         } else {
    161             invalidate();
    162         }
    163 
    164         if (mShouldAutoHide) {
    165             hideAfterDelay();
    166         }
    167     }
    168 
    169     private void hideAfterDelay() {
    170         mDelayedLineFadeHandler.removeCallbacksAndMessages(null);
    171         mDelayedLineFadeHandler.postDelayed(mHideLineRunnable, LINE_FADE_DELAY);
    172     }
    173 
    174     @Override
    175     public void setActiveMarker(int activePage) { }
    176 
    177     @Override
    178     public void setMarkersCount(int numMarkers) {
    179         if (Float.compare(numMarkers, mNumPagesFloat) != 0) {
    180             setupAndRunAnimation(ObjectAnimator.ofFloat(this, NUM_PAGES, numMarkers),
    181                     NUM_PAGES_ANIMATOR_INDEX);
    182         } else {
    183             if (mAnimators[NUM_PAGES_ANIMATOR_INDEX] != null) {
    184                 mAnimators[NUM_PAGES_ANIMATOR_INDEX].cancel();
    185                 mAnimators[NUM_PAGES_ANIMATOR_INDEX] = null;
    186             }
    187         }
    188     }
    189 
    190     public void setShouldAutoHide(boolean shouldAutoHide) {
    191         mShouldAutoHide = shouldAutoHide;
    192         if (shouldAutoHide && mLinePaint.getAlpha() > 0) {
    193             hideAfterDelay();
    194         } else if (!shouldAutoHide) {
    195             mDelayedLineFadeHandler.removeCallbacksAndMessages(null);
    196         }
    197     }
    198 
    199     private void animateLineToAlpha(int alpha) {
    200         if (alpha == mToAlpha) {
    201             // Ignore the new animation if it is going to the same alpha as the current animation.
    202             return;
    203         }
    204         mToAlpha = alpha;
    205         setupAndRunAnimation(ObjectAnimator.ofInt(this, PAINT_ALPHA, alpha),
    206                 LINE_ALPHA_ANIMATOR_INDEX);
    207     }
    208 
    209     private void animateToTotalScroll(int totalScroll) {
    210         setupAndRunAnimation(ObjectAnimator.ofInt(this, TOTAL_SCROLL, totalScroll),
    211                 TOTAL_SCROLL_ANIMATOR_INDEX);
    212     }
    213 
    214     /**
    215      * Starts the given animator and stores it in the provided index in {@link #mAnimators} until
    216      * the animation ends.
    217      *
    218      * If an animator is already at the index (i.e. it is already playing), it is canceled and
    219      * replaced with the new animator.
    220      */
    221     private void setupAndRunAnimation(ValueAnimator animator, final int animatorIndex) {
    222         if (mAnimators[animatorIndex] != null) {
    223             mAnimators[animatorIndex].cancel();
    224         }
    225         mAnimators[animatorIndex] = animator;
    226         mAnimators[animatorIndex].addListener(new AnimatorListenerAdapter() {
    227             @Override
    228             public void onAnimationEnd(Animator animation) {
    229                 mAnimators[animatorIndex] = null;
    230             }
    231         });
    232         mAnimators[animatorIndex].setDuration(LINE_ANIMATE_DURATION);
    233         mAnimators[animatorIndex].start();
    234     }
    235 
    236     /**
    237      * Pauses all currently running animations.
    238      */
    239     public void pauseAnimations() {
    240         for (int i = 0; i < ANIMATOR_COUNT; i++) {
    241             if (mAnimators[i] != null) {
    242                 mAnimators[i].pause();
    243             }
    244         }
    245     }
    246 
    247     /**
    248      * Force-ends all currently running or paused animations.
    249      */
    250     public void skipAnimationsToEnd() {
    251         for (int i = 0; i < ANIMATOR_COUNT; i++) {
    252             if (mAnimators[i] != null) {
    253                 mAnimators[i].end();
    254             }
    255         }
    256     }
    257 
    258     @Override
    259     public void setInsets(Rect insets) {
    260         DeviceProfile grid = mLauncher.getDeviceProfile();
    261         FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
    262 
    263         if (grid.isVerticalBarLayout()) {
    264             Rect padding = grid.workspacePadding;
    265             lp.leftMargin = padding.left + grid.workspaceCellPaddingXPx;
    266             lp.rightMargin = padding.right + grid.workspaceCellPaddingXPx;
    267             lp.bottomMargin = padding.bottom;
    268         } else {
    269             lp.leftMargin = lp.rightMargin = 0;
    270             lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
    271             lp.bottomMargin = grid.hotseatBarSizePx + insets.bottom;
    272         }
    273         setLayoutParams(lp);
    274     }
    275 }
    276