blob: ca3a2dd09eedecc9bdba08c9b50dd5ffbd4130bc [file] [log] [blame]
package com.android.launcher3.pageindicators;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Handler;
import android.os.Looper;
import android.support.v4.graphics.ColorUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Property;
import android.view.ViewConfiguration;
import android.widget.ImageView;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.dynamicui.ExtractedColors;
/**
* A PageIndicator that briefly shows a fraction of a line when moving between pages.
*
* The fraction is 1 / number of pages and the position is based on the progress of the page scroll.
*/
public class PageIndicatorLineCaret extends PageIndicator {
private static final String TAG = "PageIndicatorLine";
private static final int[] sTempCoords = new int[2];
private static final int LINE_ANIMATE_DURATION = ViewConfiguration.getScrollBarFadeDuration();
private static final int LINE_FADE_DELAY = ViewConfiguration.getScrollDefaultDelay();
public static final int WHITE_ALPHA = (int) (0.70f * 255);
public static final int BLACK_ALPHA = (int) (0.65f * 255);
private static final int LINE_ALPHA_ANIMATOR_INDEX = 0;
private static final int NUM_PAGES_ANIMATOR_INDEX = 1;
private static final int TOTAL_SCROLL_ANIMATOR_INDEX = 2;
private ValueAnimator[] mAnimators = new ValueAnimator[3];
private final Handler mDelayedLineFadeHandler = new Handler(Looper.getMainLooper());
private boolean mShouldAutoHide = true;
// The alpha of the line when it is showing.
private int mActiveAlpha = 0;
// The alpha that the line is being animated to or already at (either 0 or mActiveAlpha).
private int mToAlpha;
// A float value representing the number of pages, to allow for an animation when it changes.
private float mNumPagesFloat;
private int mCurrentScroll;
private int mTotalScroll;
private Paint mLinePaint;
private Launcher mLauncher;
private final int mLineHeight;
private ImageView mAllAppsHandle;
private static final Property<PageIndicatorLineCaret, Integer> PAINT_ALPHA
= new Property<PageIndicatorLineCaret, Integer>(Integer.class, "paint_alpha") {
@Override
public Integer get(PageIndicatorLineCaret obj) {
return obj.mLinePaint.getAlpha();
}
@Override
public void set(PageIndicatorLineCaret obj, Integer alpha) {
obj.mLinePaint.setAlpha(alpha);
obj.invalidate();
}
};
private static final Property<PageIndicatorLineCaret, Float> NUM_PAGES
= new Property<PageIndicatorLineCaret, Float>(Float.class, "num_pages") {
@Override
public Float get(PageIndicatorLineCaret obj) {
return obj.mNumPagesFloat;
}
@Override
public void set(PageIndicatorLineCaret obj, Float numPages) {
obj.mNumPagesFloat = numPages;
obj.invalidate();
}
};
private static final Property<PageIndicatorLineCaret, Integer> TOTAL_SCROLL
= new Property<PageIndicatorLineCaret, Integer>(Integer.class, "total_scroll") {
@Override
public Integer get(PageIndicatorLineCaret obj) {
return obj.mTotalScroll;
}
@Override
public void set(PageIndicatorLineCaret obj, Integer totalScroll) {
obj.mTotalScroll = totalScroll;
obj.invalidate();
}
};
private Runnable mHideLineRunnable = new Runnable() {
@Override
public void run() {
animateLineToAlpha(0);
}
};
public PageIndicatorLineCaret(Context context) {
this(context, null);
}
public PageIndicatorLineCaret(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PageIndicatorLineCaret(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
Resources res = context.getResources();
mLinePaint = new Paint();
mLinePaint.setAlpha(0);
mLauncher = (Launcher) context;
mLineHeight = res.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_line_height);
setCaretDrawable(new CaretDrawable(context));
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mAllAppsHandle = (ImageView) findViewById(R.id.all_apps_handle);
mAllAppsHandle.setImageDrawable(getCaretDrawable());
mAllAppsHandle.setOnTouchListener(mLauncher.getHapticFeedbackTouchListener());
mAllAppsHandle.setOnClickListener(mLauncher);
mAllAppsHandle.setOnLongClickListener(mLauncher);
mAllAppsHandle.setOnFocusChangeListener(mLauncher.mFocusHandler);
mLauncher.setAllAppsButton(mAllAppsHandle);
}
@Override
protected void onDraw(Canvas canvas) {
if (mTotalScroll == 0 || mNumPagesFloat == 0) {
return;
}
// Compute and draw line rect.
float progress = Utilities.boundToRange(((float) mCurrentScroll) / mTotalScroll, 0f, 1f);
int availableWidth = canvas.getWidth();
int lineWidth = (int) (availableWidth / mNumPagesFloat);
int lineLeft = (int) (progress * (availableWidth - lineWidth));
int lineRight = lineLeft + lineWidth;
canvas.drawRect(lineLeft, canvas.getHeight() - mLineHeight, lineRight, canvas.getHeight(),
mLinePaint);
}
@Override
public void setScroll(int currentScroll, int totalScroll) {
if (getAlpha() == 0) {
return;
}
animateLineToAlpha(mActiveAlpha);
mCurrentScroll = currentScroll;
if (mTotalScroll == 0) {
mTotalScroll = totalScroll;
} else if (mTotalScroll != totalScroll) {
animateToTotalScroll(totalScroll);
} else {
invalidate();
}
if (mShouldAutoHide) {
hideAfterDelay();
}
}
private void hideAfterDelay() {
mDelayedLineFadeHandler.removeCallbacksAndMessages(null);
mDelayedLineFadeHandler.postDelayed(mHideLineRunnable, LINE_FADE_DELAY);
}
@Override
public void setActiveMarker(int activePage) {
}
@Override
protected void onPageCountChanged() {
if (Float.compare(mNumPages, mNumPagesFloat) != 0) {
animateToNumPages(mNumPages);
}
}
public void setShouldAutoHide(boolean shouldAutoHide) {
mShouldAutoHide = shouldAutoHide;
if (shouldAutoHide && mLinePaint.getAlpha() > 0) {
hideAfterDelay();
} else if (!shouldAutoHide) {
mDelayedLineFadeHandler.removeCallbacksAndMessages(null);
}
}
/**
* The line's color will be:
* - mostly opaque white if the hotseat is white (ignoring alpha)
* - mostly opaque black if the hotseat is black (ignoring alpha)
*/
public void updateColor(ExtractedColors extractedColors) {
int originalLineAlpha = mLinePaint.getAlpha();
int color = extractedColors.getColor(ExtractedColors.HOTSEAT_INDEX, Color.TRANSPARENT);
if (color != Color.TRANSPARENT) {
color = ColorUtils.setAlphaComponent(color, 255);
if (color == Color.BLACK) {
mActiveAlpha = BLACK_ALPHA;
} else if (color == Color.WHITE) {
mActiveAlpha = WHITE_ALPHA;
} else {
Log.e(TAG, "Setting workspace page indicators to an unsupported color: #"
+ Integer.toHexString(color));
}
mLinePaint.setColor(color);
mLinePaint.setAlpha(originalLineAlpha);
}
}
private void animateLineToAlpha(int alpha) {
if (alpha == mToAlpha) {
// Ignore the new animation if it is going to the same alpha as the current animation.
return;
}
mToAlpha = alpha;
setupAndRunAnimation(ObjectAnimator.ofInt(this, PAINT_ALPHA, alpha),
LINE_ALPHA_ANIMATOR_INDEX);
}
private void animateToNumPages(int numPages) {
setupAndRunAnimation(ObjectAnimator.ofFloat(this, NUM_PAGES, numPages),
NUM_PAGES_ANIMATOR_INDEX);
}
private void animateToTotalScroll(int totalScroll) {
setupAndRunAnimation(ObjectAnimator.ofInt(this, TOTAL_SCROLL, totalScroll),
TOTAL_SCROLL_ANIMATOR_INDEX);
}
/**
* Starts the given animator and stores it in the provided index in {@link #mAnimators} until
* the animation ends.
*
* If an animator is already at the index (i.e. it is already playing), it is canceled and
* replaced with the new animator.
*/
private void setupAndRunAnimation(ValueAnimator animator, final int animatorIndex) {
if (mAnimators[animatorIndex] != null) {
mAnimators[animatorIndex].cancel();
}
mAnimators[animatorIndex] = animator;
mAnimators[animatorIndex].addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mAnimators[animatorIndex] = null;
}
});
mAnimators[animatorIndex].setDuration(LINE_ANIMATE_DURATION);
mAnimators[animatorIndex].start();
}
}