SearchPanelCircleViewpublic class SearchPanelCircleView extends android.widget.FrameLayout
Fields Summary |
---|
private final int | mCircleMinSize | private final int | mBaseMargin | private final int | mStaticOffset | private final android.graphics.Paint | mBackgroundPaint | private final android.graphics.Paint | mRipplePaint | private final android.graphics.Rect | mCircleRect | private final android.graphics.Rect | mStaticRect | private final android.view.animation.Interpolator | mFastOutSlowInInterpolator | private final android.view.animation.Interpolator | mAppearInterpolator | private final android.view.animation.Interpolator | mDisappearInterpolator | private boolean | mClipToOutline | private final int | mMaxElevation | private boolean | mAnimatingOut | private float | mOutlineAlpha | private float | mOffset | private float | mCircleSize | private boolean | mHorizontal | private boolean | mCircleHidden | private android.widget.ImageView | mLogo | private boolean | mDraggedFarEnough | private boolean | mOffsetAnimatingIn | private float | mCircleAnimationEndValue | private ArrayList | mRipples | private android.animation.ValueAnimator | mOffsetAnimator | private android.animation.ValueAnimator | mCircleAnimator | private android.animation.ValueAnimator | mFadeOutAnimator | private ValueAnimator.AnimatorUpdateListener | mCircleUpdateListener | private android.animation.AnimatorListenerAdapter | mClearAnimatorListener | private ValueAnimator.AnimatorUpdateListener | mOffsetUpdateListener |
Constructors Summary |
---|
public SearchPanelCircleView(android.content.Context context)
this(context, null);
| public SearchPanelCircleView(android.content.Context context, android.util.AttributeSet attrs)
this(context, attrs, 0);
| public SearchPanelCircleView(android.content.Context context, android.util.AttributeSet attrs, int defStyleAttr)
this(context, attrs, defStyleAttr, 0);
| public SearchPanelCircleView(android.content.Context context, android.util.AttributeSet attrs, int defStyleAttr, int defStyleRes)
super(context, attrs, defStyleAttr, defStyleRes);
setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
if (mCircleSize > 0.0f) {
outline.setOval(mCircleRect);
} else {
outline.setEmpty();
}
outline.setAlpha(mOutlineAlpha);
}
});
setWillNotDraw(false);
mCircleMinSize = context.getResources().getDimensionPixelSize(
R.dimen.search_panel_circle_size);
mBaseMargin = context.getResources().getDimensionPixelSize(
R.dimen.search_panel_circle_base_margin);
mStaticOffset = context.getResources().getDimensionPixelSize(
R.dimen.search_panel_circle_travel_distance);
mMaxElevation = context.getResources().getDimensionPixelSize(
R.dimen.search_panel_circle_elevation);
mAppearInterpolator = AnimationUtils.loadInterpolator(mContext,
android.R.interpolator.linear_out_slow_in);
mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(mContext,
android.R.interpolator.fast_out_slow_in);
mDisappearInterpolator = AnimationUtils.loadInterpolator(mContext,
android.R.interpolator.fast_out_linear_in);
mBackgroundPaint.setAntiAlias(true);
mBackgroundPaint.setColor(getResources().getColor(R.color.search_panel_circle_color));
mRipplePaint.setColor(getResources().getColor(R.color.search_panel_ripple_color));
mRipplePaint.setAntiAlias(true);
|
Methods Summary |
---|
private void | addRipple()
if (mRipples.size() > 1) {
// we only want 2 ripples at the time
return;
}
float xInterpolation, yInterpolation;
if (mHorizontal) {
xInterpolation = 0.75f;
yInterpolation = 0.5f;
} else {
xInterpolation = 0.5f;
yInterpolation = 0.75f;
}
float circleCenterX = mStaticRect.left * (1.0f - xInterpolation)
+ mStaticRect.right * xInterpolation;
float circleCenterY = mStaticRect.top * (1.0f - yInterpolation)
+ mStaticRect.bottom * yInterpolation;
float radius = Math.max(mCircleSize, mCircleMinSize * 1.25f) * 0.75f;
Ripple ripple = new Ripple(circleCenterX, circleCenterY, radius);
ripple.start();
| private void | applyCircleSize(float circleSize)
mCircleSize = circleSize;
updateLayout();
| private void | drawBackground(android.graphics.Canvas canvas)
canvas.drawCircle(mCircleRect.centerX(), mCircleRect.centerY(), mCircleSize / 2,
mBackgroundPaint);
| private void | drawRipples(android.graphics.Canvas canvas)
for (int i = 0; i < mRipples.size(); i++) {
Ripple ripple = mRipples.get(i);
ripple.draw(canvas);
}
| public boolean | hasOverlappingRendering()
// not really true but it's ok during an animation, as it's never permanent
return false;
| public boolean | isAnimatingOut()
return mAnimatingOut;
| public boolean | isAnimationRunning(boolean enterAnimation)Check if an animation is currently running
return mOffsetAnimator != null && (enterAnimation == mOffsetAnimatingIn);
| protected void | onDraw(android.graphics.Canvas canvas)
super.onDraw(canvas);
drawBackground(canvas);
drawRipples(canvas);
| protected void | onFinishInflate()
super.onFinishInflate();
mLogo = (ImageView) findViewById(R.id.search_logo);
| protected void | onLayout(boolean changed, int l, int t, int r, int b)
mLogo.layout(0, 0, mLogo.getMeasuredWidth(), mLogo.getMeasuredHeight());
if (changed) {
updateCircleRect(mStaticRect, mStaticOffset, true);
}
| private void | performExitFadeOutAnimation(int startDelay, int duration, java.lang.Runnable endRunnable)
mFadeOutAnimator = ValueAnimator.ofFloat(mBackgroundPaint.getAlpha() / 255.0f, 0.0f);
// Linear since we are animating multiple values
mFadeOutAnimator.setInterpolator(new LinearInterpolator());
mFadeOutAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float animatedFraction = animation.getAnimatedFraction();
float logoValue = animatedFraction > 0.5f ? 1.0f : animatedFraction / 0.5f;
logoValue = PhoneStatusBar.ALPHA_OUT.getInterpolation(1.0f - logoValue);
float backgroundValue = animatedFraction < 0.2f ? 0.0f :
PhoneStatusBar.ALPHA_OUT.getInterpolation((animatedFraction - 0.2f) / 0.8f);
backgroundValue = 1.0f - backgroundValue;
mBackgroundPaint.setAlpha((int) (backgroundValue * 255));
mOutlineAlpha = backgroundValue;
mLogo.setAlpha(logoValue);
invalidateOutline();
invalidate();
}
});
mFadeOutAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (endRunnable != null) {
endRunnable.run();
}
mLogo.setAlpha(1.0f);
mBackgroundPaint.setAlpha(255);
mOutlineAlpha = 1.0f;
mFadeOutAnimator = null;
}
});
mFadeOutAnimator.setStartDelay(startDelay);
mFadeOutAnimator.setDuration(duration);
mFadeOutAnimator.start();
| public void | performOnAnimationFinished(java.lang.Runnable runnable)
if (mOffsetAnimator != null) {
mOffsetAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (runnable != null) {
runnable.run();
}
}
});
} else {
if (runnable != null) {
runnable.run();
}
}
| public void | reset()
mDraggedFarEnough = false;
mAnimatingOut = false;
mCircleHidden = true;
mClipToOutline = false;
if (mFadeOutAnimator != null) {
mFadeOutAnimator.cancel();
}
mBackgroundPaint.setAlpha(255);
mOutlineAlpha = 1.0f;
| private float | rubberband(float diff)
return (float) Math.pow(Math.abs(diff), 0.6f);
| public void | setAnimatingOut(boolean animatingOut)
mAnimatingOut = animatingOut;
| public void | setCircleSize(float circleSize)
setCircleSize(circleSize, false, null, 0, null);
| public void | setCircleSize(float circleSize, boolean animated, java.lang.Runnable endRunnable, int startDelay, android.view.animation.Interpolator interpolator)
boolean isAnimating = mCircleAnimator != null;
boolean animationPending = isAnimating && !mCircleAnimator.isRunning();
boolean animatingOut = isAnimating && mCircleAnimationEndValue == 0;
if (animated || animationPending || animatingOut) {
if (isAnimating) {
if (circleSize == mCircleAnimationEndValue) {
return;
}
mCircleAnimator.cancel();
}
mCircleAnimator = ValueAnimator.ofFloat(mCircleSize, circleSize);
mCircleAnimator.addUpdateListener(mCircleUpdateListener);
mCircleAnimator.addListener(mClearAnimatorListener);
mCircleAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (endRunnable != null) {
endRunnable.run();
}
}
});
Interpolator desiredInterpolator = interpolator != null ? interpolator
: circleSize == 0 ? mDisappearInterpolator : mAppearInterpolator;
mCircleAnimator.setInterpolator(desiredInterpolator);
mCircleAnimator.setDuration(300);
mCircleAnimator.setStartDelay(startDelay);
mCircleAnimator.start();
mCircleAnimationEndValue = circleSize;
} else {
if (isAnimating) {
float diff = circleSize - mCircleAnimationEndValue;
PropertyValuesHolder[] values = mCircleAnimator.getValues();
values[0].setFloatValues(diff, circleSize);
mCircleAnimator.setCurrentPlayTime(mCircleAnimator.getCurrentPlayTime());
mCircleAnimationEndValue = circleSize;
} else {
applyCircleSize(circleSize);
updateElevation();
}
}
| public void | setDragDistance(float distance)
if (!mAnimatingOut && (!mCircleHidden || mDraggedFarEnough)) {
float circleSize = mCircleMinSize + rubberband(distance);
setCircleSize(circleSize);
}
| public void | setDraggedFarEnough(boolean farEnough)
if (farEnough != mDraggedFarEnough) {
if (farEnough) {
if (mCircleHidden) {
startEnterAnimation();
}
if (mOffsetAnimator == null) {
addRipple();
} else {
postDelayed(new Runnable() {
@Override
public void run() {
addRipple();
}
}, 100);
}
} else {
startAbortAnimation(null);
}
mDraggedFarEnough = farEnough;
}
| public void | setHorizontal(boolean horizontal)
mHorizontal = horizontal;
updateCircleRect(mStaticRect, mStaticOffset, true);
updateLayout();
| public void | setOffset(float offset)Sets the offset to the edge of the screen. By default this not not animated.
setOffset(offset, false, 0, null, null);
| private void | setOffset(float offset, boolean animate, int startDelay, android.view.animation.Interpolator interpolator, java.lang.Runnable endRunnable)Sets the offset to the edge of the screen.
if (!animate) {
mOffset = offset;
updateLayout();
if (endRunnable != null) {
endRunnable.run();
}
} else {
if (mOffsetAnimator != null) {
mOffsetAnimator.removeAllListeners();
mOffsetAnimator.cancel();
}
mOffsetAnimator = ValueAnimator.ofFloat(mOffset, offset);
mOffsetAnimator.addUpdateListener(mOffsetUpdateListener);
mOffsetAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mOffsetAnimator = null;
if (endRunnable != null) {
endRunnable.run();
}
}
});
Interpolator desiredInterpolator = interpolator != null ?
interpolator : offset == 0 ? mDisappearInterpolator : mAppearInterpolator;
mOffsetAnimator.setInterpolator(desiredInterpolator);
mOffsetAnimator.setStartDelay(startDelay);
mOffsetAnimator.setDuration(300);
mOffsetAnimator.start();
mOffsetAnimatingIn = offset != 0;
}
| public void | startAbortAnimation(java.lang.Runnable endRunnable)
if (mAnimatingOut) {
if (endRunnable != null) {
endRunnable.run();
}
return;
}
setCircleSize(0, true, null, 0, null);
setOffset(0, true, 0, null, endRunnable);
mCircleHidden = true;
| public void | startEnterAnimation()
if (mAnimatingOut) {
return;
}
applyCircleSize(0);
setOffset(0);
setCircleSize(mCircleMinSize, true, null, 50, null);
setOffset(mStaticOffset, true, 50, null, null);
mCircleHidden = false;
| public void | startExitAnimation(java.lang.Runnable endRunnable)
if (!mHorizontal) {
float offset = getHeight() / 2.0f;
setOffset(offset - mBaseMargin, true, 50, mFastOutSlowInInterpolator, null);
float xMax = getWidth() / 2;
float yMax = getHeight() / 2;
float maxRadius = (float) Math.ceil(Math.hypot(xMax, yMax) * 2);
setCircleSize(maxRadius, true, null, 50, mFastOutSlowInInterpolator);
performExitFadeOutAnimation(50, 300, endRunnable);
} else {
// when in landscape, we don't wan't the animation as it interferes with the general
// rotation animation to the homescreen.
endRunnable.run();
}
| private void | updateCircleRect()
updateCircleRect(mCircleRect, mOffset, false);
| private void | updateCircleRect(android.graphics.Rect rect, float offset, boolean useStaticSize)
int left, top;
float circleSize = useStaticSize ? mCircleMinSize : mCircleSize;
if (mHorizontal) {
left = (int) (getWidth() - circleSize / 2 - mBaseMargin - offset);
top = (int) ((getHeight() - circleSize) / 2);
} else {
left = (int) (getWidth() - circleSize) / 2;
top = (int) (getHeight() - circleSize / 2 - mBaseMargin - offset);
}
rect.set(left, top, (int) (left + circleSize), (int) (top + circleSize));
| private void | updateClipping()
boolean clip = mCircleSize < mCircleMinSize || !mRipples.isEmpty();
if (clip != mClipToOutline) {
setClipToOutline(clip);
mClipToOutline = clip;
}
| private void | updateElevation()
float t = (mStaticOffset - mOffset) / (float) mStaticOffset;
t = 1.0f - Math.max(t, 0.0f);
float offset = t * mMaxElevation;
setElevation(offset);
| private void | updateLayout()
updateCircleRect();
updateLogo();
invalidateOutline();
invalidate();
updateClipping();
| private void | updateLogo()
boolean exitAnimationRunning = mFadeOutAnimator != null;
Rect rect = exitAnimationRunning ? mCircleRect : mStaticRect;
float translationX = (rect.left + rect.right) / 2.0f - mLogo.getWidth() / 2.0f;
float translationY = (rect.top + rect.bottom) / 2.0f - mLogo.getHeight() / 2.0f;
float t = (mStaticOffset - mOffset) / (float) mStaticOffset;
if (!exitAnimationRunning) {
if (mHorizontal) {
translationX += t * mStaticOffset * 0.3f;
} else {
translationY += t * mStaticOffset * 0.3f;
}
float alpha = 1.0f-t;
alpha = Math.max((alpha - 0.5f) * 2.0f, 0);
mLogo.setAlpha(alpha);
} else {
translationY += (mOffset - mStaticOffset) / 2;
}
mLogo.setTranslationX(translationX);
mLogo.setTranslationY(translationY);
|
|