FileDocCategorySizeDatePackage
SearchPanelCircleView.javaAPI DocAndroid 5.1 API21943Thu Mar 12 22:22:42 GMT 2015com.android.systemui

SearchPanelCircleView

public 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 voidaddRipple()

        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 voidapplyCircleSize(float circleSize)

        mCircleSize = circleSize;
        updateLayout();
    
private voiddrawBackground(android.graphics.Canvas canvas)

        canvas.drawCircle(mCircleRect.centerX(), mCircleRect.centerY(), mCircleSize / 2,
                mBackgroundPaint);
    
private voiddrawRipples(android.graphics.Canvas canvas)

        for (int i = 0; i < mRipples.size(); i++) {
            Ripple ripple = mRipples.get(i);
            ripple.draw(canvas);
        }
    
public booleanhasOverlappingRendering()

        // not really true but it's ok during an animation, as it's never permanent
        return false;
    
public booleanisAnimatingOut()

return
Whether the circle is currently launching to the search activity or aborting the interaction

        return mAnimatingOut;
    
public booleanisAnimationRunning(boolean enterAnimation)
Check if an animation is currently running

param
enterAnimation Is the animating queried the enter animation.

        return mOffsetAnimator != null && (enterAnimation == mOffsetAnimatingIn);
    
protected voidonDraw(android.graphics.Canvas canvas)

        super.onDraw(canvas);
        drawBackground(canvas);
        drawRipples(canvas);
    
protected voidonFinishInflate()

        super.onFinishInflate();
        mLogo = (ImageView) findViewById(R.id.search_logo);
    
protected voidonLayout(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 voidperformExitFadeOutAnimation(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 voidperformOnAnimationFinished(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 voidreset()

        mDraggedFarEnough = false;
        mAnimatingOut = false;
        mCircleHidden = true;
        mClipToOutline = false;
        if (mFadeOutAnimator != null) {
            mFadeOutAnimator.cancel();
        }
        mBackgroundPaint.setAlpha(255);
        mOutlineAlpha = 1.0f;
    
private floatrubberband(float diff)

        return (float) Math.pow(Math.abs(diff), 0.6f);
    
public voidsetAnimatingOut(boolean animatingOut)

        mAnimatingOut = animatingOut;
    
public voidsetCircleSize(float circleSize)

        setCircleSize(circleSize, false, null, 0, null);
    
public voidsetCircleSize(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 voidsetDragDistance(float distance)

        if (!mAnimatingOut && (!mCircleHidden || mDraggedFarEnough)) {
            float circleSize = mCircleMinSize + rubberband(distance);
            setCircleSize(circleSize);
        }

    
public voidsetDraggedFarEnough(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 voidsetHorizontal(boolean horizontal)

        mHorizontal = horizontal;
        updateCircleRect(mStaticRect, mStaticOffset, true);
        updateLayout();
    
public voidsetOffset(float offset)
Sets the offset to the edge of the screen. By default this not not animated.

param
offset The offset to apply.

        setOffset(offset, false, 0, null, null);
    
private voidsetOffset(float offset, boolean animate, int startDelay, android.view.animation.Interpolator interpolator, java.lang.Runnable endRunnable)
Sets the offset to the edge of the screen.

param
offset The offset to apply.
param
animate Whether an animation should be performed.
param
startDelay The desired start delay if animated.
param
interpolator The desired interpolator if animated. If null, a default interpolator will be taken designed for appearing or disappearing.
param
endRunnable The end runnable which should be executed when the animation is finished.

        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 voidstartAbortAnimation(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 voidstartEnterAnimation()

        if (mAnimatingOut) {
            return;
        }
        applyCircleSize(0);
        setOffset(0);
        setCircleSize(mCircleMinSize, true, null, 50, null);
        setOffset(mStaticOffset, true, 50, null, null);
        mCircleHidden = false;
    
public voidstartExitAnimation(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 voidupdateCircleRect()

        updateCircleRect(mCircleRect, mOffset, false);
    
private voidupdateCircleRect(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 voidupdateClipping()

        boolean clip = mCircleSize < mCircleMinSize || !mRipples.isEmpty();
        if (clip != mClipToOutline) {
            setClipToOutline(clip);
            mClipToOutline = clip;
        }
    
private voidupdateElevation()

        float t = (mStaticOffset - mOffset) / (float) mStaticOffset;
        t = 1.0f - Math.max(t, 0.0f);
        float offset = t * mMaxElevation;
        setElevation(offset);
    
private voidupdateLayout()

        updateCircleRect();
        updateLogo();
        invalidateOutline();
        invalidate();
        updateClipping();
    
private voidupdateLogo()

        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);