Home | History | Annotate | Download | only in keyboard
      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 
     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 
    109     protected void setAlpha(float alpha) {
    110         mAlpha = alpha;
    111         mPaint.setAlpha((int) (mAlpha * mMaxAlpha));
    112     }
    113 
    114     @Override
    115     public void onAnimationUpdate(ValueAnimator animation) {
    116         invalidateDirty();
    117     }
    118 
    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 
    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 
    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
    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 
    196     protected void endCurrentAnimation() {
    197         if (mCurrentAnimation != null) {
    198             mCurrentAnimation.cancel();
    199             mCurrentAnimation = null;
    200         }
    201     }
    202 
    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      */
    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 
    219         public ViewSetListener(View v, boolean callOnCancel) {
    220             mViewToSet = v;
    221             mCallOnCancel = callOnCancel;
    222         }
    223 
    224         @Override
    225         public void onAnimationCancel(Animator animation) {
    226             if (!mCallOnCancel) {
    227                 mCalled = true;
    228             }
    229         }
    230 
    231         @Override
    232         public void onAnimationEnd(Animator animation) {
    233             if (!mCalled) {
    234                 setCurrentView(mViewToSet);
    235                 mCalled = true;
    236             }
    237         }
    238     }
    239 }
    240