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.os.Handler; 13 import android.os.Looper; 14 import android.support.v4.graphics.ColorUtils; 15 import android.util.AttributeSet; 16 import android.util.Log; 17 import android.util.Property; 18 import android.view.ViewConfiguration; 19 import android.widget.ImageView; 20 21 import com.android.launcher3.Launcher; 22 import com.android.launcher3.R; 23 import com.android.launcher3.Utilities; 24 import com.android.launcher3.dynamicui.ExtractedColors; 25 26 /** 27 * A PageIndicator that briefly shows a fraction of a line when moving between pages. 28 * 29 * The fraction is 1 / number of pages and the position is based on the progress of the page scroll. 30 */ 31 public class PageIndicatorLineCaret extends PageIndicator { 32 private static final String TAG = "PageIndicatorLine"; 33 34 private static final int[] sTempCoords = new int[2]; 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 45 private ValueAnimator[] mAnimators = new ValueAnimator[3]; 46 47 private final Handler mDelayedLineFadeHandler = new Handler(Looper.getMainLooper()); 48 49 private boolean mShouldAutoHide = true; 50 51 // The alpha of the line when it is showing. 52 private int mActiveAlpha = 0; 53 // The alpha that the line is being animated to or already at (either 0 or mActiveAlpha). 54 private int mToAlpha; 55 // A float value representing the number of pages, to allow for an animation when it changes. 56 private float mNumPagesFloat; 57 private int mCurrentScroll; 58 private int mTotalScroll; 59 private Paint mLinePaint; 60 private Launcher mLauncher; 61 private final int mLineHeight; 62 private ImageView mAllAppsHandle; 63 64 private static final Property<PageIndicatorLineCaret, Integer> PAINT_ALPHA 65 = new Property<PageIndicatorLineCaret, Integer>(Integer.class, "paint_alpha") { 66 @Override 67 public Integer get(PageIndicatorLineCaret obj) { 68 return obj.mLinePaint.getAlpha(); 69 } 70 71 @Override 72 public void set(PageIndicatorLineCaret obj, Integer alpha) { 73 obj.mLinePaint.setAlpha(alpha); 74 obj.invalidate(); 75 } 76 }; 77 78 private static final Property<PageIndicatorLineCaret, Float> NUM_PAGES 79 = new Property<PageIndicatorLineCaret, Float>(Float.class, "num_pages") { 80 @Override 81 public Float get(PageIndicatorLineCaret obj) { 82 return obj.mNumPagesFloat; 83 } 84 85 @Override 86 public void set(PageIndicatorLineCaret obj, Float numPages) { 87 obj.mNumPagesFloat = numPages; 88 obj.invalidate(); 89 } 90 }; 91 92 private static final Property<PageIndicatorLineCaret, Integer> TOTAL_SCROLL 93 = new Property<PageIndicatorLineCaret, Integer>(Integer.class, "total_scroll") { 94 @Override 95 public Integer get(PageIndicatorLineCaret obj) { 96 return obj.mTotalScroll; 97 } 98 99 @Override 100 public void set(PageIndicatorLineCaret obj, Integer totalScroll) { 101 obj.mTotalScroll = totalScroll; 102 obj.invalidate(); 103 } 104 }; 105 106 private Runnable mHideLineRunnable = new Runnable() { 107 @Override 108 public void run() { 109 animateLineToAlpha(0); 110 } 111 }; 112 PageIndicatorLineCaret(Context context)113 public PageIndicatorLineCaret(Context context) { 114 this(context, null); 115 } 116 PageIndicatorLineCaret(Context context, AttributeSet attrs)117 public PageIndicatorLineCaret(Context context, AttributeSet attrs) { 118 this(context, attrs, 0); 119 } 120 PageIndicatorLineCaret(Context context, AttributeSet attrs, int defStyle)121 public PageIndicatorLineCaret(Context context, AttributeSet attrs, int defStyle) { 122 super(context, attrs, defStyle); 123 124 Resources res = context.getResources(); 125 mLinePaint = new Paint(); 126 mLinePaint.setAlpha(0); 127 128 mLauncher = Launcher.getLauncher(context); 129 mLineHeight = res.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_line_height); 130 setCaretDrawable(new CaretDrawable(context)); 131 } 132 133 @Override onFinishInflate()134 protected void onFinishInflate() { 135 super.onFinishInflate(); 136 mAllAppsHandle = (ImageView) findViewById(R.id.all_apps_handle); 137 mAllAppsHandle.setImageDrawable(getCaretDrawable()); 138 mAllAppsHandle.setOnTouchListener(mLauncher.getHapticFeedbackTouchListener()); 139 mAllAppsHandle.setOnClickListener(mLauncher); 140 mAllAppsHandle.setOnLongClickListener(mLauncher); 141 mAllAppsHandle.setOnFocusChangeListener(mLauncher.mFocusHandler); 142 mLauncher.setAllAppsButton(mAllAppsHandle); 143 } 144 145 @Override setAccessibilityDelegate(AccessibilityDelegate delegate)146 public void setAccessibilityDelegate(AccessibilityDelegate delegate) { 147 mAllAppsHandle.setAccessibilityDelegate(delegate); 148 } 149 150 @Override onDraw(Canvas canvas)151 protected void onDraw(Canvas canvas) { 152 if (mTotalScroll == 0 || mNumPagesFloat == 0) { 153 return; 154 } 155 156 // Compute and draw line rect. 157 float progress = Utilities.boundToRange(((float) mCurrentScroll) / mTotalScroll, 0f, 1f); 158 int availableWidth = canvas.getWidth(); 159 int lineWidth = (int) (availableWidth / mNumPagesFloat); 160 int lineLeft = (int) (progress * (availableWidth - lineWidth)); 161 int lineRight = lineLeft + lineWidth; 162 canvas.drawRect(lineLeft, canvas.getHeight() - mLineHeight, lineRight, canvas.getHeight(), 163 mLinePaint); 164 } 165 166 @Override setContentDescription(CharSequence contentDescription)167 public void setContentDescription(CharSequence contentDescription) { 168 mAllAppsHandle.setContentDescription(contentDescription); 169 } 170 171 @Override setScroll(int currentScroll, int totalScroll)172 public void setScroll(int currentScroll, int totalScroll) { 173 if (getAlpha() == 0) { 174 return; 175 } 176 animateLineToAlpha(mActiveAlpha); 177 178 mCurrentScroll = currentScroll; 179 if (mTotalScroll == 0) { 180 mTotalScroll = totalScroll; 181 } else if (mTotalScroll != totalScroll) { 182 animateToTotalScroll(totalScroll); 183 } else { 184 invalidate(); 185 } 186 187 if (mShouldAutoHide) { 188 hideAfterDelay(); 189 } 190 } 191 hideAfterDelay()192 private void hideAfterDelay() { 193 mDelayedLineFadeHandler.removeCallbacksAndMessages(null); 194 mDelayedLineFadeHandler.postDelayed(mHideLineRunnable, LINE_FADE_DELAY); 195 } 196 197 @Override setActiveMarker(int activePage)198 public void setActiveMarker(int activePage) { 199 } 200 201 @Override onPageCountChanged()202 protected void onPageCountChanged() { 203 if (Float.compare(mNumPages, mNumPagesFloat) != 0) { 204 animateToNumPages(mNumPages); 205 } 206 } 207 setShouldAutoHide(boolean shouldAutoHide)208 public void setShouldAutoHide(boolean shouldAutoHide) { 209 mShouldAutoHide = shouldAutoHide; 210 if (shouldAutoHide && mLinePaint.getAlpha() > 0) { 211 hideAfterDelay(); 212 } else if (!shouldAutoHide) { 213 mDelayedLineFadeHandler.removeCallbacksAndMessages(null); 214 } 215 } 216 217 /** 218 * The line's color will be: 219 * - mostly opaque white if the hotseat is white (ignoring alpha) 220 * - mostly opaque black if the hotseat is black (ignoring alpha) 221 */ updateColor(ExtractedColors extractedColors)222 public void updateColor(ExtractedColors extractedColors) { 223 int originalLineAlpha = mLinePaint.getAlpha(); 224 int color = extractedColors.getColor(ExtractedColors.HOTSEAT_INDEX, Color.TRANSPARENT); 225 if (color != Color.TRANSPARENT) { 226 color = ColorUtils.setAlphaComponent(color, 255); 227 if (color == Color.BLACK) { 228 mActiveAlpha = BLACK_ALPHA; 229 } else if (color == Color.WHITE) { 230 mActiveAlpha = WHITE_ALPHA; 231 } else { 232 Log.e(TAG, "Setting workspace page indicators to an unsupported color: #" 233 + Integer.toHexString(color)); 234 } 235 mLinePaint.setColor(color); 236 mLinePaint.setAlpha(originalLineAlpha); 237 } 238 } 239 animateLineToAlpha(int alpha)240 private void animateLineToAlpha(int alpha) { 241 if (alpha == mToAlpha) { 242 // Ignore the new animation if it is going to the same alpha as the current animation. 243 return; 244 } 245 mToAlpha = alpha; 246 setupAndRunAnimation(ObjectAnimator.ofInt(this, PAINT_ALPHA, alpha), 247 LINE_ALPHA_ANIMATOR_INDEX); 248 } 249 animateToNumPages(int numPages)250 private void animateToNumPages(int numPages) { 251 setupAndRunAnimation(ObjectAnimator.ofFloat(this, NUM_PAGES, numPages), 252 NUM_PAGES_ANIMATOR_INDEX); 253 } 254 animateToTotalScroll(int totalScroll)255 private void animateToTotalScroll(int totalScroll) { 256 setupAndRunAnimation(ObjectAnimator.ofInt(this, TOTAL_SCROLL, totalScroll), 257 TOTAL_SCROLL_ANIMATOR_INDEX); 258 } 259 260 /** 261 * Starts the given animator and stores it in the provided index in {@link #mAnimators} until 262 * the animation ends. 263 * 264 * If an animator is already at the index (i.e. it is already playing), it is canceled and 265 * replaced with the new animator. 266 */ setupAndRunAnimation(ValueAnimator animator, final int animatorIndex)267 private void setupAndRunAnimation(ValueAnimator animator, final int animatorIndex) { 268 if (mAnimators[animatorIndex] != null) { 269 mAnimators[animatorIndex].cancel(); 270 } 271 mAnimators[animatorIndex] = animator; 272 mAnimators[animatorIndex].addListener(new AnimatorListenerAdapter() { 273 @Override 274 public void onAnimationEnd(Animator animation) { 275 mAnimators[animatorIndex] = null; 276 } 277 }); 278 mAnimators[animatorIndex].setDuration(LINE_ANIMATE_DURATION); 279 mAnimators[animatorIndex].start(); 280 } 281 } 282