/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package xyz.klinker.blur.launcher3.keyboard; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; import android.animation.RectEvaluator; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.annotation.TargetApi; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.os.Build.VERSION_CODES; import android.util.Property; import android.view.View; import android.view.View.OnFocusChangeListener; import xyz.klinker.blur.R; /** * A helper class to draw background of a focused view. */ @TargetApi(VERSION_CODES.LOLLIPOP) public abstract class FocusIndicatorHelper implements OnFocusChangeListener, AnimatorUpdateListener { private static final float MIN_VISIBLE_ALPHA = 0.2f; private static final long ANIM_DURATION = 150; public static final Property<FocusIndicatorHelper, Float> ALPHA = new Property<FocusIndicatorHelper, Float>(Float.TYPE, "alpha") { @Override public void set(FocusIndicatorHelper object, Float value) { object.setAlpha(value); } @Override public Float get(FocusIndicatorHelper object) { return object.mAlpha; } }; public static final Property<FocusIndicatorHelper, Float> SHIFT = new Property<FocusIndicatorHelper, Float>( Float.TYPE, "shift") { @Override public void set(FocusIndicatorHelper object, Float value) { object.mShift = value; } @Override public Float get(FocusIndicatorHelper object) { return object.mShift; } }; private static final RectEvaluator RECT_EVALUATOR = new RectEvaluator(new Rect()); private static final Rect sTempRect1 = new Rect(); private static final Rect sTempRect2 = new Rect(); private final View mContainer; private final Paint mPaint; private final int mMaxAlpha; private final Rect mDirtyRect = new Rect(); private boolean mIsDirty = false; private View mLastFocusedView; private View mCurrentView; private View mTargetView; /** * The fraction indicating the position of the focusRect between {@link #mCurrentView} * & {@link #mTargetView} */ private float mShift; private ObjectAnimator mCurrentAnimation; private float mAlpha; public FocusIndicatorHelper(View container) { mContainer = container; mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); int color = container.getResources().getColor(R.color.focused_background); mMaxAlpha = Color.alpha(color); mPaint.setColor(0xFF000000 | color); setAlpha(0); mShift = 0; } protected void setAlpha(float alpha) { mAlpha = alpha; mPaint.setAlpha((int) (mAlpha * mMaxAlpha)); } @Override public void onAnimationUpdate(ValueAnimator animation) { invalidateDirty(); } protected void invalidateDirty() { if (mIsDirty) { mContainer.invalidate(mDirtyRect); mIsDirty = false; } Rect newRect = getDrawRect(); if (newRect != null) { mContainer.invalidate(newRect); } } public void draw(Canvas c) { if (mAlpha > 0) { Rect newRect = getDrawRect(); if (newRect != null) { mDirtyRect.set(newRect); c.drawRect(mDirtyRect, mPaint); mIsDirty = true; } } } private Rect getDrawRect() { if (mCurrentView != null) { viewToRect(mCurrentView, sTempRect1); if (mShift > 0 && mTargetView != null) { viewToRect(mTargetView, sTempRect2); return RECT_EVALUATOR.evaluate(mShift, sTempRect1, sTempRect2); } else { return sTempRect1; } } return null; } @Override public void onFocusChange(View v, boolean hasFocus) { if (hasFocus) { endCurrentAnimation(); if (mAlpha > MIN_VISIBLE_ALPHA) { mTargetView = v; mCurrentAnimation = ObjectAnimator.ofPropertyValuesHolder(this, PropertyValuesHolder.ofFloat(ALPHA, 1), PropertyValuesHolder.ofFloat(SHIFT, 1)); mCurrentAnimation.addListener(new ViewSetListener(v, true)); } else { setCurrentView(v); mCurrentAnimation = ObjectAnimator.ofPropertyValuesHolder(this, PropertyValuesHolder.ofFloat(ALPHA, 1)); } mLastFocusedView = v; } else { if (mLastFocusedView == v) { mLastFocusedView = null; endCurrentAnimation(); mCurrentAnimation = ObjectAnimator.ofPropertyValuesHolder(this, PropertyValuesHolder.ofFloat(ALPHA, 0)); mCurrentAnimation.addListener(new ViewSetListener(null, false)); } } // invalidate once invalidateDirty(); mLastFocusedView = hasFocus ? v : null; if (mCurrentAnimation != null) { mCurrentAnimation.addUpdateListener(this); mCurrentAnimation.setDuration(ANIM_DURATION).start(); } } protected void endCurrentAnimation() { if (mCurrentAnimation != null) { mCurrentAnimation.cancel(); mCurrentAnimation = null; } } protected void setCurrentView(View v) { mCurrentView = v; mShift = 0; mTargetView = null; } /** * Gets the position of {@param v} relative to {@link #mContainer}. */ public abstract void viewToRect(View v, Rect outRect); private class ViewSetListener extends AnimatorListenerAdapter { private final View mViewToSet; private final boolean mCallOnCancel; private boolean mCalled = false; public ViewSetListener(View v, boolean callOnCancel) { mViewToSet = v; mCallOnCancel = callOnCancel; } @Override public void onAnimationCancel(Animator animation) { if (!mCallOnCancel) { mCalled = true; } } @Override public void onAnimationEnd(Animator animation) { if (!mCalled) { setCurrentView(mViewToSet); mCalled = true; } } } }