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.annotation.TargetApi;
     27 import android.graphics.Canvas;
     28 import android.graphics.Color;
     29 import android.graphics.Paint;
     30 import android.graphics.Rect;
     31 import android.os.Build.VERSION_CODES;
     32 import android.util.Property;
     33 import android.view.View;
     34 import android.view.View.OnFocusChangeListener;
     35 
     36 import com.android.launcher3.R;
     37 
     38 /**
     39  * A helper class to draw background of a focused view.
     40  */
     41 @TargetApi(VERSION_CODES.LOLLIPOP)
     42 public abstract class FocusIndicatorHelper implements
     43         OnFocusChangeListener, AnimatorUpdateListener {
     44 
     45     private static final float MIN_VISIBLE_ALPHA = 0.2f;
     46     private static final long ANIM_DURATION = 150;
     47 
     48     public static final Property<FocusIndicatorHelper, Float> ALPHA =
     49             new Property<FocusIndicatorHelper, Float>(Float.TYPE, "alpha") {
     50                 @Override
     51                 public void set(FocusIndicatorHelper object, Float value) {
     52                     object.setAlpha(value);
     53                 }
     54 
     55                 @Override
     56                 public Float get(FocusIndicatorHelper object) {
     57                     return object.mAlpha;
     58                 }
     59             };
     60 
     61     public static final Property<FocusIndicatorHelper, Float> SHIFT =
     62             new Property<FocusIndicatorHelper, Float>(
     63                     Float.TYPE, "shift") {
     64 
     65                 @Override
     66                 public void set(FocusIndicatorHelper object, Float value) {
     67                     object.mShift = value;
     68                 }
     69 
     70                 @Override
     71                 public Float get(FocusIndicatorHelper object) {
     72                     return object.mShift;
     73                 }
     74             };
     75 
     76     private static final RectEvaluator RECT_EVALUATOR = new RectEvaluator(new Rect());
     77     private static final Rect sTempRect1 = new Rect();
     78     private static final Rect sTempRect2 = new Rect();
     79 
     80     private final View mContainer;
     81     private final Paint mPaint;
     82     private final int mMaxAlpha;
     83 
     84     private final Rect mDirtyRect = new Rect();
     85     private boolean mIsDirty = false;
     86 
     87     private View mLastFocusedView;
     88 
     89     private View mCurrentView;
     90     private View mTargetView;
     91     /**
     92      * The fraction indicating the position of the focusRect between {@link #mCurrentView}
     93      * & {@link #mTargetView}
     94      */
     95     private float mShift;
     96 
     97     private ObjectAnimator mCurrentAnimation;
     98     private float mAlpha;
     99 
    100     public FocusIndicatorHelper(View container) {
    101         mContainer = container;
    102 
    103         mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    104         int color = container.getResources().getColor(R.color.focused_background);
    105         mMaxAlpha = Color.alpha(color);
    106         mPaint.setColor(0xFF000000 | color);
    107 
    108         setAlpha(0);
    109         mShift = 0;
    110     }
    111 
    112     protected void setAlpha(float alpha) {
    113         mAlpha = alpha;
    114         mPaint.setAlpha((int) (mAlpha * mMaxAlpha));
    115     }
    116 
    117     @Override
    118     public void onAnimationUpdate(ValueAnimator animation) {
    119         invalidateDirty();
    120     }
    121 
    122     protected void invalidateDirty() {
    123         if (mIsDirty) {
    124             mContainer.invalidate(mDirtyRect);
    125             mIsDirty = false;
    126         }
    127 
    128         Rect newRect = getDrawRect();
    129         if (newRect != null) {
    130             mContainer.invalidate(newRect);
    131         }
    132     }
    133 
    134     public void draw(Canvas c) {
    135         if (mAlpha > 0) {
    136             Rect newRect = getDrawRect();
    137             if (newRect != null) {
    138                 mDirtyRect.set(newRect);
    139                 c.drawRect(mDirtyRect, mPaint);
    140                 mIsDirty = true;
    141             }
    142         }
    143     }
    144 
    145     private Rect getDrawRect() {
    146         if (mCurrentView != null) {
    147             viewToRect(mCurrentView, sTempRect1);
    148 
    149             if (mShift > 0 && mTargetView != null) {
    150                 viewToRect(mTargetView, sTempRect2);
    151                 return RECT_EVALUATOR.evaluate(mShift, sTempRect1, sTempRect2);
    152             } else {
    153                 return sTempRect1;
    154             }
    155         }
    156         return null;
    157     }
    158 
    159     @Override
    160     public void onFocusChange(View v, boolean hasFocus) {
    161         if (hasFocus) {
    162             endCurrentAnimation();
    163 
    164             if (mAlpha > MIN_VISIBLE_ALPHA) {
    165                 mTargetView = v;
    166 
    167                 mCurrentAnimation = ObjectAnimator.ofPropertyValuesHolder(this,
    168                         PropertyValuesHolder.ofFloat(ALPHA, 1),
    169                         PropertyValuesHolder.ofFloat(SHIFT, 1));
    170                 mCurrentAnimation.addListener(new ViewSetListener(v, true));
    171             } else {
    172                 setCurrentView(v);
    173 
    174                 mCurrentAnimation = ObjectAnimator.ofPropertyValuesHolder(this,
    175                         PropertyValuesHolder.ofFloat(ALPHA, 1));
    176             }
    177 
    178             mLastFocusedView = v;
    179         } else {
    180             if (mLastFocusedView == v) {
    181                 mLastFocusedView = null;
    182                 endCurrentAnimation();
    183                 mCurrentAnimation = ObjectAnimator.ofPropertyValuesHolder(this,
    184                         PropertyValuesHolder.ofFloat(ALPHA, 0));
    185                 mCurrentAnimation.addListener(new ViewSetListener(null, false));
    186             }
    187         }
    188 
    189         // invalidate once
    190         invalidateDirty();
    191 
    192         mLastFocusedView = hasFocus ? v : null;
    193         if (mCurrentAnimation != null) {
    194             mCurrentAnimation.addUpdateListener(this);
    195             mCurrentAnimation.setDuration(ANIM_DURATION).start();
    196         }
    197     }
    198 
    199     protected void endCurrentAnimation() {
    200         if (mCurrentAnimation != null) {
    201             mCurrentAnimation.cancel();
    202             mCurrentAnimation = null;
    203         }
    204     }
    205 
    206     protected void setCurrentView(View v) {
    207         mCurrentView = v;
    208         mShift = 0;
    209         mTargetView = null;
    210     }
    211 
    212     /**
    213      * Gets the position of {@param v} relative to {@link #mContainer}.
    214      */
    215     public abstract void viewToRect(View v, Rect outRect);
    216 
    217     private class ViewSetListener extends AnimatorListenerAdapter {
    218         private final View mViewToSet;
    219         private final boolean mCallOnCancel;
    220         private boolean mCalled = false;
    221 
    222         public ViewSetListener(View v, boolean callOnCancel) {
    223             mViewToSet = v;
    224             mCallOnCancel = callOnCancel;
    225         }
    226 
    227         @Override
    228         public void onAnimationCancel(Animator animation) {
    229             if (!mCallOnCancel) {
    230                 mCalled = true;
    231             }
    232         }
    233 
    234         @Override
    235         public void onAnimationEnd(Animator animation) {
    236             if (!mCalled) {
    237                 setCurrentView(mViewToSet);
    238                 mCalled = true;
    239             }
    240         }
    241     }
    242 }
    243