Home | History | Annotate | Download | only in launcher3
      1 /*
      2  * Copyright (C) 2008 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;
     18 
     19 import android.animation.ObjectAnimator;
     20 import android.annotation.TargetApi;
     21 import android.content.Context;
     22 import android.content.res.ColorStateList;
     23 import android.content.res.Resources;
     24 import android.content.res.Resources.Theme;
     25 import android.content.res.TypedArray;
     26 import android.graphics.Bitmap;
     27 import android.graphics.Canvas;
     28 import android.graphics.Paint;
     29 import android.graphics.Region;
     30 import android.graphics.drawable.Drawable;
     31 import android.os.Build;
     32 import android.util.AttributeSet;
     33 import android.util.SparseArray;
     34 import android.util.TypedValue;
     35 import android.view.KeyEvent;
     36 import android.view.MotionEvent;
     37 import android.view.ViewConfiguration;
     38 import android.view.ViewParent;
     39 import android.view.animation.AccelerateInterpolator;
     40 import android.view.animation.DecelerateInterpolator;
     41 import android.widget.TextView;
     42 import com.android.launcher3.IconCache.IconLoadRequest;
     43 import com.android.launcher3.model.PackageItemInfo;
     44 
     45 /**
     46  * TextView that draws a bubble behind the text. We cannot use a LineBackgroundSpan
     47  * because we want to make the bubble taller than the text and TextView's clip is
     48  * too aggressive.
     49  */
     50 public class BubbleTextView extends TextView
     51         implements BaseRecyclerViewFastScrollBar.FastScrollFocusableView {
     52 
     53     private static SparseArray<Theme> sPreloaderThemes = new SparseArray<Theme>(2);
     54 
     55     private static final float SHADOW_LARGE_RADIUS = 4.0f;
     56     private static final float SHADOW_SMALL_RADIUS = 1.75f;
     57     private static final float SHADOW_Y_OFFSET = 2.0f;
     58     private static final int SHADOW_LARGE_COLOUR = 0xDD000000;
     59     private static final int SHADOW_SMALL_COLOUR = 0xCC000000;
     60 
     61     private static final int DISPLAY_WORKSPACE = 0;
     62     private static final int DISPLAY_ALL_APPS = 1;
     63 
     64     private static final float FAST_SCROLL_FOCUS_MAX_SCALE = 1.15f;
     65     private static final int FAST_SCROLL_FOCUS_MODE_NONE = 0;
     66     private static final int FAST_SCROLL_FOCUS_MODE_SCALE_ICON = 1;
     67     private static final int FAST_SCROLL_FOCUS_MODE_DRAW_CIRCLE_BG = 2;
     68     private static final int FAST_SCROLL_FOCUS_FADE_IN_DURATION = 175;
     69     private static final int FAST_SCROLL_FOCUS_FADE_OUT_DURATION = 125;
     70 
     71     private final Launcher mLauncher;
     72     private Drawable mIcon;
     73     private final Drawable mBackground;
     74     private final CheckLongPressHelper mLongPressHelper;
     75     private final HolographicOutlineHelper mOutlineHelper;
     76     private final StylusEventHelper mStylusEventHelper;
     77 
     78     private boolean mBackgroundSizeChanged;
     79 
     80     private Bitmap mPressedBackground;
     81 
     82     private float mSlop;
     83 
     84     private final boolean mDeferShadowGenerationOnTouch;
     85     private final boolean mCustomShadowsEnabled;
     86     private final boolean mLayoutHorizontal;
     87     private final int mIconSize;
     88     private int mTextColor;
     89 
     90     private boolean mStayPressed;
     91     private boolean mIgnorePressedStateChange;
     92     private boolean mDisableRelayout = false;
     93 
     94     private ObjectAnimator mFastScrollFocusAnimator;
     95     private Paint mFastScrollFocusBgPaint;
     96     private float mFastScrollFocusFraction;
     97     private boolean mFastScrollFocused;
     98     private final int mFastScrollMode = FAST_SCROLL_FOCUS_MODE_SCALE_ICON;
     99 
    100     private IconLoadRequest mIconLoadRequest;
    101 
    102     public BubbleTextView(Context context) {
    103         this(context, null, 0);
    104     }
    105 
    106     public BubbleTextView(Context context, AttributeSet attrs) {
    107         this(context, attrs, 0);
    108     }
    109 
    110     public BubbleTextView(Context context, AttributeSet attrs, int defStyle) {
    111         super(context, attrs, defStyle);
    112         mLauncher = (Launcher) context;
    113         DeviceProfile grid = mLauncher.getDeviceProfile();
    114 
    115         TypedArray a = context.obtainStyledAttributes(attrs,
    116                 R.styleable.BubbleTextView, defStyle, 0);
    117         mCustomShadowsEnabled = a.getBoolean(R.styleable.BubbleTextView_customShadows, true);
    118         mLayoutHorizontal = a.getBoolean(R.styleable.BubbleTextView_layoutHorizontal, false);
    119         mDeferShadowGenerationOnTouch =
    120                 a.getBoolean(R.styleable.BubbleTextView_deferShadowGeneration, false);
    121 
    122         int display = a.getInteger(R.styleable.BubbleTextView_iconDisplay, DISPLAY_WORKSPACE);
    123         int defaultIconSize = grid.iconSizePx;
    124         if (display == DISPLAY_WORKSPACE) {
    125             setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.iconTextSizePx);
    126         } else if (display == DISPLAY_ALL_APPS) {
    127             setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.allAppsIconTextSizePx);
    128             defaultIconSize = grid.allAppsIconSizePx;
    129         }
    130 
    131         mIconSize = a.getDimensionPixelSize(R.styleable.BubbleTextView_iconSizeOverride,
    132                 defaultIconSize);
    133 
    134         a.recycle();
    135 
    136         if (mCustomShadowsEnabled) {
    137             // Draw the background itself as the parent is drawn twice.
    138             mBackground = getBackground();
    139             setBackground(null);
    140         } else {
    141             mBackground = null;
    142         }
    143 
    144         mLongPressHelper = new CheckLongPressHelper(this);
    145         mStylusEventHelper = new StylusEventHelper(this);
    146 
    147         mOutlineHelper = HolographicOutlineHelper.obtain(getContext());
    148         if (mCustomShadowsEnabled) {
    149             setShadowLayer(SHADOW_LARGE_RADIUS, 0.0f, SHADOW_Y_OFFSET, SHADOW_LARGE_COLOUR);
    150         }
    151 
    152         if (mFastScrollMode == FAST_SCROLL_FOCUS_MODE_DRAW_CIRCLE_BG) {
    153             mFastScrollFocusBgPaint = new Paint();
    154             mFastScrollFocusBgPaint.setAntiAlias(true);
    155             mFastScrollFocusBgPaint.setColor(
    156                     getResources().getColor(R.color.container_fastscroll_thumb_active_color));
    157         }
    158 
    159         setAccessibilityDelegate(LauncherAppState.getInstance().getAccessibilityDelegate());
    160     }
    161 
    162     public void applyFromShortcutInfo(ShortcutInfo info, IconCache iconCache) {
    163         applyFromShortcutInfo(info, iconCache, false);
    164     }
    165 
    166     public void applyFromShortcutInfo(ShortcutInfo info, IconCache iconCache,
    167             boolean promiseStateChanged) {
    168         Bitmap b = info.getIcon(iconCache);
    169 
    170         FastBitmapDrawable iconDrawable = mLauncher.createIconDrawable(b);
    171         iconDrawable.setGhostModeEnabled(info.isDisabled != 0);
    172 
    173         setIcon(iconDrawable, mIconSize);
    174         if (info.contentDescription != null) {
    175             setContentDescription(info.contentDescription);
    176         }
    177         setText(info.title);
    178         setTag(info);
    179 
    180         if (promiseStateChanged || info.isPromise()) {
    181             applyState(promiseStateChanged);
    182         }
    183     }
    184 
    185     public void applyFromApplicationInfo(AppInfo info) {
    186         setIcon(mLauncher.createIconDrawable(info.iconBitmap), mIconSize);
    187         setText(info.title);
    188         if (info.contentDescription != null) {
    189             setContentDescription(info.contentDescription);
    190         }
    191         // We don't need to check the info since it's not a ShortcutInfo
    192         super.setTag(info);
    193 
    194         // Verify high res immediately
    195         verifyHighRes();
    196     }
    197 
    198     public void applyFromPackageItemInfo(PackageItemInfo info) {
    199         setIcon(mLauncher.createIconDrawable(info.iconBitmap), mIconSize);
    200         setText(info.title);
    201         if (info.contentDescription != null) {
    202             setContentDescription(info.contentDescription);
    203         }
    204         // We don't need to check the info since it's not a ShortcutInfo
    205         super.setTag(info);
    206 
    207         // Verify high res immediately
    208         verifyHighRes();
    209     }
    210 
    211     /**
    212      * Overrides the default long press timeout.
    213      */
    214     public void setLongPressTimeout(int longPressTimeout) {
    215         mLongPressHelper.setLongPressTimeout(longPressTimeout);
    216     }
    217 
    218     @Override
    219     protected boolean setFrame(int left, int top, int right, int bottom) {
    220         if (getLeft() != left || getRight() != right || getTop() != top || getBottom() != bottom) {
    221             mBackgroundSizeChanged = true;
    222         }
    223         return super.setFrame(left, top, right, bottom);
    224     }
    225 
    226     @Override
    227     protected boolean verifyDrawable(Drawable who) {
    228         return who == mBackground || super.verifyDrawable(who);
    229     }
    230 
    231     @Override
    232     public void setTag(Object tag) {
    233         if (tag != null) {
    234             LauncherModel.checkItemInfo((ItemInfo) tag);
    235         }
    236         super.setTag(tag);
    237     }
    238 
    239     @Override
    240     public void setPressed(boolean pressed) {
    241         super.setPressed(pressed);
    242 
    243         if (!mIgnorePressedStateChange) {
    244             updateIconState();
    245         }
    246     }
    247 
    248     /** Returns the icon for this view. */
    249     public Drawable getIcon() {
    250         return mIcon;
    251     }
    252 
    253     /** Returns whether the layout is horizontal. */
    254     public boolean isLayoutHorizontal() {
    255         return mLayoutHorizontal;
    256     }
    257 
    258     private void updateIconState() {
    259         if (mIcon instanceof FastBitmapDrawable) {
    260             ((FastBitmapDrawable) mIcon).setPressed(isPressed() || mStayPressed);
    261         }
    262     }
    263 
    264     @Override
    265     public boolean onTouchEvent(MotionEvent event) {
    266         // Call the superclass onTouchEvent first, because sometimes it changes the state to
    267         // isPressed() on an ACTION_UP
    268         boolean result = super.onTouchEvent(event);
    269 
    270         // Check for a stylus button press, if it occurs cancel any long press checks.
    271         if (mStylusEventHelper.checkAndPerformStylusEvent(event)) {
    272             mLongPressHelper.cancelLongPress();
    273             result = true;
    274         }
    275 
    276         switch (event.getAction()) {
    277             case MotionEvent.ACTION_DOWN:
    278                 // So that the pressed outline is visible immediately on setStayPressed(),
    279                 // we pre-create it on ACTION_DOWN (it takes a small but perceptible amount of time
    280                 // to create it)
    281                 if (!mDeferShadowGenerationOnTouch && mPressedBackground == null) {
    282                     mPressedBackground = mOutlineHelper.createMediumDropShadow(this);
    283                 }
    284 
    285                 // If we're in a stylus button press, don't check for long press.
    286                 if (!mStylusEventHelper.inStylusButtonPressed()) {
    287                     mLongPressHelper.postCheckForLongPress();
    288                 }
    289                 break;
    290             case MotionEvent.ACTION_CANCEL:
    291             case MotionEvent.ACTION_UP:
    292                 // If we've touched down and up on an item, and it's still not "pressed", then
    293                 // destroy the pressed outline
    294                 if (!isPressed()) {
    295                     mPressedBackground = null;
    296                 }
    297 
    298                 mLongPressHelper.cancelLongPress();
    299                 break;
    300             case MotionEvent.ACTION_MOVE:
    301                 if (!Utilities.pointInView(this, event.getX(), event.getY(), mSlop)) {
    302                     mLongPressHelper.cancelLongPress();
    303                 }
    304                 break;
    305         }
    306         return result;
    307     }
    308 
    309     void setStayPressed(boolean stayPressed) {
    310         mStayPressed = stayPressed;
    311         if (!stayPressed) {
    312             mPressedBackground = null;
    313         } else {
    314             if (mPressedBackground == null) {
    315                 mPressedBackground = mOutlineHelper.createMediumDropShadow(this);
    316             }
    317         }
    318 
    319         // Only show the shadow effect when persistent pressed state is set.
    320         ViewParent parent = getParent();
    321         if (parent != null && parent.getParent() instanceof BubbleTextShadowHandler) {
    322             ((BubbleTextShadowHandler) parent.getParent()).setPressedIcon(
    323                     this, mPressedBackground);
    324         }
    325 
    326         updateIconState();
    327     }
    328 
    329     void clearPressedBackground() {
    330         setPressed(false);
    331         setStayPressed(false);
    332     }
    333 
    334     @Override
    335     public boolean onKeyDown(int keyCode, KeyEvent event) {
    336         if (super.onKeyDown(keyCode, event)) {
    337             // Pre-create shadow so show immediately on click.
    338             if (mPressedBackground == null) {
    339                 mPressedBackground = mOutlineHelper.createMediumDropShadow(this);
    340             }
    341             return true;
    342         }
    343         return false;
    344     }
    345 
    346     @Override
    347     public boolean onKeyUp(int keyCode, KeyEvent event) {
    348         // Unlike touch events, keypress event propagate pressed state change immediately,
    349         // without waiting for onClickHandler to execute. Disable pressed state changes here
    350         // to avoid flickering.
    351         mIgnorePressedStateChange = true;
    352         boolean result = super.onKeyUp(keyCode, event);
    353 
    354         mPressedBackground = null;
    355         mIgnorePressedStateChange = false;
    356         updateIconState();
    357         return result;
    358     }
    359 
    360     @Override
    361     public void draw(Canvas canvas) {
    362         if (!mCustomShadowsEnabled) {
    363             // Draw the fast scroll focus bg if we have one
    364             if (mFastScrollMode == FAST_SCROLL_FOCUS_MODE_DRAW_CIRCLE_BG &&
    365                     mFastScrollFocusFraction > 0f) {
    366                 DeviceProfile grid = mLauncher.getDeviceProfile();
    367                 int iconCenterX = getScrollX() + (getWidth() / 2);
    368                 int iconCenterY = getScrollY() + getPaddingTop() + (grid.iconSizePx / 2);
    369                 canvas.drawCircle(iconCenterX, iconCenterY,
    370                         mFastScrollFocusFraction * (getWidth() / 2), mFastScrollFocusBgPaint);
    371             }
    372 
    373             super.draw(canvas);
    374 
    375             return;
    376         }
    377 
    378         final Drawable background = mBackground;
    379         if (background != null) {
    380             final int scrollX = getScrollX();
    381             final int scrollY = getScrollY();
    382 
    383             if (mBackgroundSizeChanged) {
    384                 background.setBounds(0, 0,  getRight() - getLeft(), getBottom() - getTop());
    385                 mBackgroundSizeChanged = false;
    386             }
    387 
    388             if ((scrollX | scrollY) == 0) {
    389                 background.draw(canvas);
    390             } else {
    391                 canvas.translate(scrollX, scrollY);
    392                 background.draw(canvas);
    393                 canvas.translate(-scrollX, -scrollY);
    394             }
    395         }
    396 
    397         // If text is transparent, don't draw any shadow
    398         if (getCurrentTextColor() == getResources().getColor(android.R.color.transparent)) {
    399             getPaint().clearShadowLayer();
    400             super.draw(canvas);
    401             return;
    402         }
    403 
    404         // We enhance the shadow by drawing the shadow twice
    405         getPaint().setShadowLayer(SHADOW_LARGE_RADIUS, 0.0f, SHADOW_Y_OFFSET, SHADOW_LARGE_COLOUR);
    406         super.draw(canvas);
    407         canvas.save(Canvas.CLIP_SAVE_FLAG);
    408         canvas.clipRect(getScrollX(), getScrollY() + getExtendedPaddingTop(),
    409                 getScrollX() + getWidth(),
    410                 getScrollY() + getHeight(), Region.Op.INTERSECT);
    411         getPaint().setShadowLayer(SHADOW_SMALL_RADIUS, 0.0f, 0.0f, SHADOW_SMALL_COLOUR);
    412         super.draw(canvas);
    413         canvas.restore();
    414     }
    415 
    416     @Override
    417     protected void onAttachedToWindow() {
    418         super.onAttachedToWindow();
    419 
    420         if (mBackground != null) mBackground.setCallback(this);
    421 
    422         if (mIcon instanceof PreloadIconDrawable) {
    423             ((PreloadIconDrawable) mIcon).applyPreloaderTheme(getPreloaderTheme());
    424         }
    425         mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
    426     }
    427 
    428     @Override
    429     protected void onDetachedFromWindow() {
    430         super.onDetachedFromWindow();
    431         if (mBackground != null) mBackground.setCallback(null);
    432     }
    433 
    434     @Override
    435     public void setTextColor(int color) {
    436         mTextColor = color;
    437         super.setTextColor(color);
    438     }
    439 
    440     @Override
    441     public void setTextColor(ColorStateList colors) {
    442         mTextColor = colors.getDefaultColor();
    443         super.setTextColor(colors);
    444     }
    445 
    446     public void setTextVisibility(boolean visible) {
    447         Resources res = getResources();
    448         if (visible) {
    449             super.setTextColor(mTextColor);
    450         } else {
    451             super.setTextColor(res.getColor(android.R.color.transparent));
    452         }
    453     }
    454 
    455     @Override
    456     public void cancelLongPress() {
    457         super.cancelLongPress();
    458 
    459         mLongPressHelper.cancelLongPress();
    460     }
    461 
    462     public void applyState(boolean promiseStateChanged) {
    463         if (getTag() instanceof ShortcutInfo) {
    464             ShortcutInfo info = (ShortcutInfo) getTag();
    465             final boolean isPromise = info.isPromise();
    466             final int progressLevel = isPromise ?
    467                     ((info.hasStatusFlag(ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE) ?
    468                             info.getInstallProgress() : 0)) : 100;
    469 
    470             if (mIcon != null) {
    471                 final PreloadIconDrawable preloadDrawable;
    472                 if (mIcon instanceof PreloadIconDrawable) {
    473                     preloadDrawable = (PreloadIconDrawable) mIcon;
    474                 } else {
    475                     preloadDrawable = new PreloadIconDrawable(mIcon, getPreloaderTheme());
    476                     setIcon(preloadDrawable, mIconSize);
    477                 }
    478 
    479                 preloadDrawable.setLevel(progressLevel);
    480                 if (promiseStateChanged) {
    481                     preloadDrawable.maybePerformFinishedAnimation();
    482                 }
    483             }
    484         }
    485     }
    486 
    487     private Theme getPreloaderTheme() {
    488         Object tag = getTag();
    489         int style = ((tag != null) && (tag instanceof ShortcutInfo) &&
    490                 (((ShortcutInfo) tag).container >= 0)) ? R.style.PreloadIcon_Folder
    491                         : R.style.PreloadIcon;
    492         Theme theme = sPreloaderThemes.get(style);
    493         if (theme == null) {
    494             theme = getResources().newTheme();
    495             theme.applyStyle(style, true);
    496             sPreloaderThemes.put(style, theme);
    497         }
    498         return theme;
    499     }
    500 
    501     /**
    502      * Sets the icon for this view based on the layout direction.
    503      */
    504     @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    505     private Drawable setIcon(Drawable icon, int iconSize) {
    506         mIcon = icon;
    507         if (iconSize != -1) {
    508             mIcon.setBounds(0, 0, iconSize, iconSize);
    509         }
    510         if (mLayoutHorizontal) {
    511             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
    512                 setCompoundDrawablesRelative(mIcon, null, null, null);
    513             } else {
    514                 setCompoundDrawables(mIcon, null, null, null);
    515             }
    516         } else {
    517             setCompoundDrawables(null, mIcon, null, null);
    518         }
    519         return icon;
    520     }
    521 
    522     @Override
    523     public void requestLayout() {
    524         if (!mDisableRelayout) {
    525             super.requestLayout();
    526         }
    527     }
    528 
    529     /**
    530      * Applies the item info if it is same as what the view is pointing to currently.
    531      */
    532     public void reapplyItemInfo(final ItemInfo info) {
    533         if (getTag() == info) {
    534             mIconLoadRequest = null;
    535             mDisableRelayout = true;
    536             if (info instanceof AppInfo) {
    537                 applyFromApplicationInfo((AppInfo) info);
    538             } else if (info instanceof ShortcutInfo) {
    539                 applyFromShortcutInfo((ShortcutInfo) info,
    540                         LauncherAppState.getInstance().getIconCache());
    541             } else if (info instanceof PackageItemInfo) {
    542                 applyFromPackageItemInfo((PackageItemInfo) info);
    543             }
    544             mDisableRelayout = false;
    545         }
    546     }
    547 
    548     /**
    549      * Verifies that the current icon is high-res otherwise posts a request to load the icon.
    550      */
    551     public void verifyHighRes() {
    552         if (mIconLoadRequest != null) {
    553             mIconLoadRequest.cancel();
    554             mIconLoadRequest = null;
    555         }
    556         if (getTag() instanceof AppInfo) {
    557             AppInfo info = (AppInfo) getTag();
    558             if (info.usingLowResIcon) {
    559                 mIconLoadRequest = LauncherAppState.getInstance().getIconCache()
    560                         .updateIconInBackground(BubbleTextView.this, info);
    561             }
    562         } else if (getTag() instanceof ShortcutInfo) {
    563             ShortcutInfo info = (ShortcutInfo) getTag();
    564             if (info.usingLowResIcon) {
    565                 mIconLoadRequest = LauncherAppState.getInstance().getIconCache()
    566                         .updateIconInBackground(BubbleTextView.this, info);
    567             }
    568         } else if (getTag() instanceof PackageItemInfo) {
    569             PackageItemInfo info = (PackageItemInfo) getTag();
    570             if (info.usingLowResIcon) {
    571                 mIconLoadRequest = LauncherAppState.getInstance().getIconCache()
    572                         .updateIconInBackground(BubbleTextView.this, info);
    573             }
    574         }
    575     }
    576 
    577     // Setters & getters for the animation
    578     public void setFastScrollFocus(float fraction) {
    579         mFastScrollFocusFraction = fraction;
    580         if (mFastScrollMode == FAST_SCROLL_FOCUS_MODE_SCALE_ICON) {
    581             setScaleX(1f + fraction * (FAST_SCROLL_FOCUS_MAX_SCALE - 1f));
    582             setScaleY(1f + fraction * (FAST_SCROLL_FOCUS_MAX_SCALE - 1f));
    583         } else {
    584             invalidate();
    585         }
    586     }
    587 
    588     public float getFastScrollFocus() {
    589         return mFastScrollFocusFraction;
    590     }
    591 
    592     @Override
    593     public void setFastScrollFocused(final boolean focused, boolean animated) {
    594         if (mFastScrollMode == FAST_SCROLL_FOCUS_MODE_NONE) {
    595             return;
    596         }
    597 
    598         if (mFastScrollFocused != focused) {
    599             mFastScrollFocused = focused;
    600 
    601             if (animated) {
    602                 // Clean up the previous focus animator
    603                 if (mFastScrollFocusAnimator != null) {
    604                     mFastScrollFocusAnimator.cancel();
    605                 }
    606                 mFastScrollFocusAnimator = ObjectAnimator.ofFloat(this, "fastScrollFocus",
    607                         focused ? 1f : 0f);
    608                 if (focused) {
    609                     mFastScrollFocusAnimator.setInterpolator(new DecelerateInterpolator());
    610                 } else {
    611                     mFastScrollFocusAnimator.setInterpolator(new AccelerateInterpolator());
    612                 }
    613                 mFastScrollFocusAnimator.setDuration(focused ?
    614                         FAST_SCROLL_FOCUS_FADE_IN_DURATION : FAST_SCROLL_FOCUS_FADE_OUT_DURATION);
    615                 mFastScrollFocusAnimator.start();
    616             } else {
    617                 mFastScrollFocusFraction = focused ? 1f : 0f;
    618             }
    619         }
    620     }
    621 
    622     /**
    623      * Interface to be implemented by the grand parent to allow click shadow effect.
    624      */
    625     public static interface BubbleTextShadowHandler {
    626         void setPressedIcon(BubbleTextView icon, Bitmap background);
    627     }
    628 }
    629