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