1 /*
2  * Copyright (C) 2014 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.content.Context;
20 import android.graphics.Bitmap;
21 import android.graphics.Canvas;
22 import android.graphics.Color;
23 import android.graphics.Paint;
24 import android.graphics.Rect;
25 import android.view.View;
26 import android.view.ViewDebug;
27 import android.view.ViewGroup;
28 
29 public class ClickShadowView extends View {
30 
31     private static final int SHADOW_SIZE_FACTOR = 3;
32     private static final int SHADOW_LOW_ALPHA = 30;
33     private static final int SHADOW_HIGH_ALPHA = 60;
34 
35     private final Paint mPaint;
36 
37     @ViewDebug.ExportedProperty(category = "launcher")
38     private final float mShadowOffset;
39     @ViewDebug.ExportedProperty(category = "launcher")
40     private final float mShadowPadding;
41 
42     private Bitmap mBitmap;
43 
ClickShadowView(Context context)44     public ClickShadowView(Context context) {
45         super(context);
46         mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
47         mPaint.setColor(Color.BLACK);
48 
49         mShadowPadding = getResources().getDimension(R.dimen.blur_size_click_shadow);
50         mShadowOffset = getResources().getDimension(R.dimen.click_shadow_high_shift);
51     }
52 
53     /**
54      * @return extra space required by the view to show the shadow.
55      */
getExtraSize()56     public int getExtraSize() {
57         return (int) (SHADOW_SIZE_FACTOR * mShadowPadding);
58     }
59 
60     /**
61      * Applies the new bitmap.
62      * @return true if the view was invalidated.
63      */
setBitmap(Bitmap b)64     public boolean setBitmap(Bitmap b) {
65         if (b != mBitmap){
66             mBitmap = b;
67             invalidate();
68             return true;
69         }
70         return false;
71     }
72 
73     @Override
onDraw(Canvas canvas)74     protected void onDraw(Canvas canvas) {
75         if (mBitmap != null) {
76             mPaint.setAlpha(SHADOW_LOW_ALPHA);
77             canvas.drawBitmap(mBitmap, 0, 0, mPaint);
78             mPaint.setAlpha(SHADOW_HIGH_ALPHA);
79             canvas.drawBitmap(mBitmap, 0, mShadowOffset, mPaint);
80         }
81     }
82 
animateShadow()83     public void animateShadow() {
84         setAlpha(0);
85         animate().alpha(1)
86             .setDuration(FastBitmapDrawable.CLICK_FEEDBACK_DURATION)
87             .setInterpolator(FastBitmapDrawable.CLICK_FEEDBACK_INTERPOLATOR)
88             .start();
89     }
90 
91     /**
92      * Aligns the shadow with {@param view}
93      * @param viewParent immediate parent of {@param view}. It must be a sibling of this view.
94      */
alignWithIconView(BubbleTextView view, ViewGroup viewParent, View clipAgainstView)95     public void alignWithIconView(BubbleTextView view, ViewGroup viewParent, View clipAgainstView) {
96         float leftShift = view.getLeft() + viewParent.getLeft() - getLeft();
97         float topShift = view.getTop() + viewParent.getTop() - getTop();
98         int iconWidth = view.getRight() - view.getLeft();
99         int iconHeight = view.getBottom() - view.getTop();
100         int iconHSpace = iconWidth - view.getCompoundPaddingRight() - view.getCompoundPaddingLeft();
101         float drawableWidth = view.getIcon().getBounds().width();
102 
103         if (clipAgainstView != null) {
104             // Set the bounds to clip against
105             int[] coords = new int[] {0, 0};
106             Utilities.getDescendantCoordRelativeToAncestor(clipAgainstView, (View) getParent(),
107                     coords, false);
108             int clipLeft = (int) Math.max(0, coords[0] - leftShift - mShadowPadding);
109             int clipTop = (int) Math.max(0, coords[1] - topShift - mShadowPadding) ;
110             setClipBounds(new Rect(clipLeft, clipTop, clipLeft + iconWidth, clipTop + iconHeight));
111         } else {
112             // Reset the clip bounds
113             setClipBounds(null);
114         }
115 
116         setTranslationX(leftShift
117                 + viewParent.getTranslationX()
118                 + view.getCompoundPaddingLeft() * view.getScaleX()
119                 + (iconHSpace - drawableWidth) * view.getScaleX() / 2  /* drawable gap */
120                 + iconWidth * (1 - view.getScaleX()) / 2  /* gap due to scale */
121                 - mShadowPadding  /* extra shadow size */
122                 );
123         setTranslationY(topShift
124                 + viewParent.getTranslationY()
125                 + view.getPaddingTop() * view.getScaleY()  /* drawable gap */
126                 + view.getHeight() * (1 - view.getScaleY()) / 2  /* gap due to scale */
127                 - mShadowPadding  /* extra shadow size */
128                 );
129     }
130 }
131