1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.launcher3.keyboard; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.ObjectAnimator; 22 import android.animation.PropertyValuesHolder; 23 import android.animation.RectEvaluator; 24 import android.animation.ValueAnimator; 25 import android.animation.ValueAnimator.AnimatorUpdateListener; 26 import android.graphics.Canvas; 27 import android.graphics.Color; 28 import android.graphics.Paint; 29 import android.graphics.Rect; 30 import android.util.Property; 31 import android.view.View; 32 import android.view.View.OnFocusChangeListener; 33 34 import com.android.launcher3.R; 35 36 /** 37 * A helper class to draw background of a focused view. 38 */ 39 public abstract class FocusIndicatorHelper implements 40 OnFocusChangeListener, AnimatorUpdateListener { 41 42 private static final float MIN_VISIBLE_ALPHA = 0.2f; 43 private static final long ANIM_DURATION = 150; 44 45 public static final Property<FocusIndicatorHelper, Float> ALPHA = 46 new Property<FocusIndicatorHelper, Float>(Float.TYPE, "alpha") { 47 @Override 48 public void set(FocusIndicatorHelper object, Float value) { 49 object.setAlpha(value); 50 } 51 52 @Override 53 public Float get(FocusIndicatorHelper object) { 54 return object.mAlpha; 55 } 56 }; 57 58 public static final Property<FocusIndicatorHelper, Float> SHIFT = 59 new Property<FocusIndicatorHelper, Float>( 60 Float.TYPE, "shift") { 61 62 @Override 63 public void set(FocusIndicatorHelper object, Float value) { 64 object.mShift = value; 65 } 66 67 @Override 68 public Float get(FocusIndicatorHelper object) { 69 return object.mShift; 70 } 71 }; 72 73 private static final RectEvaluator RECT_EVALUATOR = new RectEvaluator(new Rect()); 74 private static final Rect sTempRect1 = new Rect(); 75 private static final Rect sTempRect2 = new Rect(); 76 77 private final View mContainer; 78 private final Paint mPaint; 79 private final int mMaxAlpha; 80 81 private final Rect mDirtyRect = new Rect(); 82 private boolean mIsDirty = false; 83 84 private View mLastFocusedView; 85 86 private View mCurrentView; 87 private View mTargetView; 88 /** 89 * The fraction indicating the position of the focusRect between {@link #mCurrentView} 90 * & {@link #mTargetView} 91 */ 92 private float mShift; 93 94 private ObjectAnimator mCurrentAnimation; 95 private float mAlpha; 96 FocusIndicatorHelper(View container)97 public FocusIndicatorHelper(View container) { 98 mContainer = container; 99 100 mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 101 int color = container.getResources().getColor(R.color.focused_background); 102 mMaxAlpha = Color.alpha(color); 103 mPaint.setColor(0xFF000000 | color); 104 105 setAlpha(0); 106 mShift = 0; 107 } 108 setAlpha(float alpha)109 protected void setAlpha(float alpha) { 110 mAlpha = alpha; 111 mPaint.setAlpha((int) (mAlpha * mMaxAlpha)); 112 } 113 114 @Override onAnimationUpdate(ValueAnimator animation)115 public void onAnimationUpdate(ValueAnimator animation) { 116 invalidateDirty(); 117 } 118 invalidateDirty()119 protected void invalidateDirty() { 120 if (mIsDirty) { 121 mContainer.invalidate(mDirtyRect); 122 mIsDirty = false; 123 } 124 125 Rect newRect = getDrawRect(); 126 if (newRect != null) { 127 mContainer.invalidate(newRect); 128 } 129 } 130 draw(Canvas c)131 public void draw(Canvas c) { 132 if (mAlpha > 0) { 133 Rect newRect = getDrawRect(); 134 if (newRect != null) { 135 mDirtyRect.set(newRect); 136 c.drawRect(mDirtyRect, mPaint); 137 mIsDirty = true; 138 } 139 } 140 } 141 getDrawRect()142 private Rect getDrawRect() { 143 if (mCurrentView != null && mCurrentView.isAttachedToWindow()) { 144 viewToRect(mCurrentView, sTempRect1); 145 146 if (mShift > 0 && mTargetView != null) { 147 viewToRect(mTargetView, sTempRect2); 148 return RECT_EVALUATOR.evaluate(mShift, sTempRect1, sTempRect2); 149 } else { 150 return sTempRect1; 151 } 152 } 153 return null; 154 } 155 156 @Override onFocusChange(View v, boolean hasFocus)157 public void onFocusChange(View v, boolean hasFocus) { 158 if (hasFocus) { 159 endCurrentAnimation(); 160 161 if (mAlpha > MIN_VISIBLE_ALPHA) { 162 mTargetView = v; 163 164 mCurrentAnimation = ObjectAnimator.ofPropertyValuesHolder(this, 165 PropertyValuesHolder.ofFloat(ALPHA, 1), 166 PropertyValuesHolder.ofFloat(SHIFT, 1)); 167 mCurrentAnimation.addListener(new ViewSetListener(v, true)); 168 } else { 169 setCurrentView(v); 170 171 mCurrentAnimation = ObjectAnimator.ofPropertyValuesHolder(this, 172 PropertyValuesHolder.ofFloat(ALPHA, 1)); 173 } 174 175 mLastFocusedView = v; 176 } else { 177 if (mLastFocusedView == v) { 178 mLastFocusedView = null; 179 endCurrentAnimation(); 180 mCurrentAnimation = ObjectAnimator.ofPropertyValuesHolder(this, 181 PropertyValuesHolder.ofFloat(ALPHA, 0)); 182 mCurrentAnimation.addListener(new ViewSetListener(null, false)); 183 } 184 } 185 186 // invalidate once 187 invalidateDirty(); 188 189 mLastFocusedView = hasFocus ? v : null; 190 if (mCurrentAnimation != null) { 191 mCurrentAnimation.addUpdateListener(this); 192 mCurrentAnimation.setDuration(ANIM_DURATION).start(); 193 } 194 } 195 endCurrentAnimation()196 protected void endCurrentAnimation() { 197 if (mCurrentAnimation != null) { 198 mCurrentAnimation.cancel(); 199 mCurrentAnimation = null; 200 } 201 } 202 setCurrentView(View v)203 protected void setCurrentView(View v) { 204 mCurrentView = v; 205 mShift = 0; 206 mTargetView = null; 207 } 208 209 /** 210 * Gets the position of {@param v} relative to {@link #mContainer}. 211 */ viewToRect(View v, Rect outRect)212 public abstract void viewToRect(View v, Rect outRect); 213 214 private class ViewSetListener extends AnimatorListenerAdapter { 215 private final View mViewToSet; 216 private final boolean mCallOnCancel; 217 private boolean mCalled = false; 218 ViewSetListener(View v, boolean callOnCancel)219 public ViewSetListener(View v, boolean callOnCancel) { 220 mViewToSet = v; 221 mCallOnCancel = callOnCancel; 222 } 223 224 @Override onAnimationCancel(Animator animation)225 public void onAnimationCancel(Animator animation) { 226 if (!mCallOnCancel) { 227 mCalled = true; 228 } 229 } 230 231 @Override onAnimationEnd(Animator animation)232 public void onAnimationEnd(Animator animation) { 233 if (!mCalled) { 234 setCurrentView(mViewToSet); 235 mCalled = true; 236 } 237 } 238 } 239 240 /** 241 * Simple subclass which assumes that the target view is a child of the container. 242 */ 243 public static class SimpleFocusIndicatorHelper extends FocusIndicatorHelper { 244 SimpleFocusIndicatorHelper(View container)245 public SimpleFocusIndicatorHelper(View container) { 246 super(container); 247 } 248 249 @Override viewToRect(View v, Rect outRect)250 public void viewToRect(View v, Rect outRect) { 251 outRect.set(v.getLeft(), v.getTop(), v.getRight(), v.getBottom()); 252 } 253 } 254 } 255