1 /*
2  * Copyright (C) 2015 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 package com.android.launcher3.allapps;
17 
18 import android.animation.ObjectAnimator;
19 import android.content.Context;
20 import android.content.res.Resources;
21 import android.graphics.Canvas;
22 import android.graphics.ColorFilter;
23 import android.graphics.PixelFormat;
24 import android.graphics.Rect;
25 import android.graphics.drawable.Drawable;
26 import android.view.ContextThemeWrapper;
27 import android.view.Gravity;
28 
29 import com.android.launcher3.LauncherAnimUtils;
30 import com.android.launcher3.R;
31 import com.android.launcher3.util.Themes;
32 
33 /**
34  * This is a custom composite drawable that has a fixed virtual size and dynamically lays out its
35  * children images relatively within its bounds.  This way, we can reduce the memory usage of a
36  * single, large sparsely populated image.
37  */
38 public class AllAppsBackgroundDrawable extends Drawable {
39 
40     /**
41      * A helper class to position and orient a drawable to be drawn.
42      */
43     protected static class TransformedImageDrawable {
44         private Drawable mImage;
45         private float mXPercent;
46         private float mYPercent;
47         private int mGravity;
48         private int mAlpha;
49 
50         /**
51          * @param gravity If one of the Gravity center values, the x and y offset will take the width
52          *                and height of the image into account to center the image to the offset.
53          */
TransformedImageDrawable(Context context, int resourceId, float xPct, float yPct, int gravity)54         public TransformedImageDrawable(Context context, int resourceId, float xPct, float yPct,
55                 int gravity) {
56             mImage = context.getDrawable(resourceId);
57             mXPercent = xPct;
58             mYPercent = yPct;
59             mGravity = gravity;
60         }
61 
setAlpha(int alpha)62         public void setAlpha(int alpha) {
63             mImage.setAlpha(alpha);
64             mAlpha = alpha;
65         }
66 
getAlpha()67         public int getAlpha() {
68             return mAlpha;
69         }
70 
updateBounds(Rect bounds)71         public void updateBounds(Rect bounds) {
72             int width = mImage.getIntrinsicWidth();
73             int height = mImage.getIntrinsicHeight();
74             int left = bounds.left + (int) (mXPercent * bounds.width());
75             int top = bounds.top + (int) (mYPercent * bounds.height());
76             if ((mGravity & Gravity.CENTER_HORIZONTAL) == Gravity.CENTER_HORIZONTAL) {
77                 left -= (width / 2);
78             }
79             if ((mGravity & Gravity.CENTER_VERTICAL) == Gravity.CENTER_VERTICAL) {
80                 top -= (height / 2);
81             }
82             mImage.setBounds(left, top, left + width, top + height);
83         }
84 
draw(Canvas canvas)85         public void draw(Canvas canvas) {
86             mImage.draw(canvas);
87         }
88 
getBounds()89         public Rect getBounds() {
90             return mImage.getBounds();
91         }
92     }
93 
94     protected final TransformedImageDrawable mHand;
95     protected final TransformedImageDrawable[] mIcons;
96     private final int mWidth;
97     private final int mHeight;
98 
99     private ObjectAnimator mBackgroundAnim;
100 
AllAppsBackgroundDrawable(Context context)101     public AllAppsBackgroundDrawable(Context context) {
102         Resources res = context.getResources();
103         mWidth = res.getDimensionPixelSize(R.dimen.all_apps_background_canvas_width);
104         mHeight = res.getDimensionPixelSize(R.dimen.all_apps_background_canvas_height);
105 
106         context = new ContextThemeWrapper(context,
107                 Themes.getAttrBoolean(context, R.attr.isMainColorDark)
108                         ? R.style.AllAppsEmptySearchBackground_Dark
109                         : R.style.AllAppsEmptySearchBackground);
110         mHand = new TransformedImageDrawable(context, R.drawable.ic_all_apps_bg_hand,
111                 0.575f, 0.f, Gravity.CENTER_HORIZONTAL);
112         mIcons = new TransformedImageDrawable[4];
113         mIcons[0] = new TransformedImageDrawable(context, R.drawable.ic_all_apps_bg_icon_1,
114                 0.375f, 0, Gravity.CENTER_HORIZONTAL);
115         mIcons[1] = new TransformedImageDrawable(context, R.drawable.ic_all_apps_bg_icon_2,
116                 0.3125f, 0.2f, Gravity.CENTER_HORIZONTAL);
117         mIcons[2] = new TransformedImageDrawable(context, R.drawable.ic_all_apps_bg_icon_3,
118                 0.475f, 0.26f, Gravity.CENTER_HORIZONTAL);
119         mIcons[3] = new TransformedImageDrawable(context, R.drawable.ic_all_apps_bg_icon_4,
120                 0.7f, 0.125f, Gravity.CENTER_HORIZONTAL);
121     }
122 
123     /**
124      * Animates the background alpha.
125      */
animateBgAlpha(float finalAlpha, int duration)126     public void animateBgAlpha(float finalAlpha, int duration) {
127         int finalAlphaI = (int) (finalAlpha * 255f);
128         if (getAlpha() != finalAlphaI) {
129             mBackgroundAnim = cancelAnimator(mBackgroundAnim);
130             mBackgroundAnim = ObjectAnimator.ofInt(this, LauncherAnimUtils.DRAWABLE_ALPHA,
131                     finalAlphaI);
132             mBackgroundAnim.setDuration(duration);
133             mBackgroundAnim.start();
134         }
135     }
136 
137     /**
138      * Sets the background alpha immediately.
139      */
setBgAlpha(float finalAlpha)140     public void setBgAlpha(float finalAlpha) {
141         int finalAlphaI = (int) (finalAlpha * 255f);
142         if (getAlpha() != finalAlphaI) {
143             mBackgroundAnim = cancelAnimator(mBackgroundAnim);
144             setAlpha(finalAlphaI);
145         }
146     }
147 
148     @Override
getIntrinsicWidth()149     public int getIntrinsicWidth() {
150         return mWidth;
151     }
152 
153     @Override
getIntrinsicHeight()154     public int getIntrinsicHeight() {
155         return mHeight;
156     }
157 
158     @Override
draw(Canvas canvas)159     public void draw(Canvas canvas) {
160         mHand.draw(canvas);
161         for (int i = 0; i < mIcons.length; i++) {
162             mIcons[i].draw(canvas);
163         }
164     }
165 
166     @Override
onBoundsChange(Rect bounds)167     protected void onBoundsChange(Rect bounds) {
168         super.onBoundsChange(bounds);
169         mHand.updateBounds(bounds);
170         for (int i = 0; i < mIcons.length; i++) {
171             mIcons[i].updateBounds(bounds);
172         }
173         invalidateSelf();
174     }
175 
176     @Override
setAlpha(int alpha)177     public void setAlpha(int alpha) {
178         mHand.setAlpha(alpha);
179         for (int i = 0; i < mIcons.length; i++) {
180             mIcons[i].setAlpha(alpha);
181         }
182         invalidateSelf();
183     }
184 
185     @Override
getAlpha()186     public int getAlpha() {
187         return mHand.getAlpha();
188     }
189 
190     @Override
setColorFilter(ColorFilter colorFilter)191     public void setColorFilter(ColorFilter colorFilter) {
192         // Do nothing
193     }
194 
195     @Override
getOpacity()196     public int getOpacity() {
197         return PixelFormat.TRANSLUCENT;
198     }
199 
cancelAnimator(ObjectAnimator animator)200     private ObjectAnimator cancelAnimator(ObjectAnimator animator) {
201         if (animator != null) {
202             animator.cancel();
203         }
204         return null;
205     }
206 }
207