PagedViewIcon.java revision 5f2aa4efeeb8b0133d891715d71553138d9f9ca7
1/*
2 * Copyright (C) 2010 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
17package com.android.launcher2;
18
19import android.content.Context;
20import android.content.pm.PackageManager;
21import android.content.pm.ResolveInfo;
22import android.content.res.Resources;
23import android.graphics.Bitmap;
24import android.graphics.BlurMaskFilter;
25import android.graphics.Canvas;
26import android.graphics.Matrix;
27import android.graphics.Paint;
28import android.graphics.PointF;
29import android.graphics.PorterDuff;
30import android.graphics.PorterDuffXfermode;
31import android.graphics.Rect;
32import android.graphics.Region.Op;
33import android.graphics.drawable.Drawable;
34import android.util.AttributeSet;
35import android.util.DisplayMetrics;
36import android.util.Log;
37import android.widget.Checkable;
38import android.widget.TextView;
39
40import com.android.launcher2.PagedView.PagedViewIconCache;
41
42class HolographicOutlineHelper {
43    private float mDensity;
44    private final Paint mHolographicPaint = new Paint();
45    private final Paint mBlurPaint = new Paint();
46    private final Paint mErasePaint = new Paint();
47    private static final Matrix mIdentity = new Matrix();;
48    private static final float BLUR_FACTOR = 3.5f;
49
50    public static final float DEFAULT_STROKE_WIDTH = 6.0f;
51    public static final int HOLOGRAPHIC_BLUE = 0xFF6699FF;
52    public static final int HOLOGRAPHIC_GREEN = 0xFF51E633;
53
54    HolographicOutlineHelper(float density) {
55        mDensity = density;
56        mHolographicPaint.setColor(HOLOGRAPHIC_BLUE);
57        mHolographicPaint.setFilterBitmap(true);
58        mHolographicPaint.setAntiAlias(true);
59        mBlurPaint.setMaskFilter(new BlurMaskFilter(BLUR_FACTOR * density,
60                BlurMaskFilter.Blur.OUTER));
61        mBlurPaint.setFilterBitmap(true);
62        mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
63        mErasePaint.setFilterBitmap(true);
64        mErasePaint.setAntiAlias(true);
65    }
66
67    private float cubic(float r) {
68        return (float) (Math.pow(r-1, 3) + 1);
69    }
70
71    /**
72     * Returns the interpolated holographic highlight alpha for the effect we want when scrolling
73     * pages.
74     */
75    public float highlightAlphaInterpolator(float r) {
76        final float pivot = 0.3f;
77        if (r < pivot) {
78            return Math.max(0.5f, 0.65f*cubic(r/pivot));
79        } else {
80            return Math.min(1.0f, 0.65f*cubic(1 - (r-pivot)/(1-pivot)));
81        }
82    }
83
84    /**
85     * Returns the interpolated view alpha for the effect we want when scrolling pages.
86     */
87    public float viewAlphaInterpolator(float r) {
88        final float pivot = 0.6f;
89        if (r < pivot) {
90            return r/pivot;
91        } else {
92            return 1.0f;
93        }
94    }
95
96    /**
97     * Sets the color of the holographic paint to be used when applying the outline/blur.
98     */
99    void setColor(int color) {
100        mHolographicPaint.setColor(color);
101    }
102
103    /**
104     * Applies an outline to whatever is currently drawn in the specified bitmap.
105     */
106    void applyOutline(Bitmap srcDst, Canvas srcDstCanvas, float strokeWidth, PointF offset) {
107        strokeWidth *= mDensity;
108        Bitmap mask = srcDst.extractAlpha();
109        Matrix m = new Matrix();
110        final int width = srcDst.getWidth();
111        final int height = srcDst.getHeight();
112        float xScale = strokeWidth*2.0f/width;
113        float yScale = strokeWidth*2.0f/height;
114        m.preScale(1+xScale, 1+yScale, (width / 2.0f) + offset.x,
115                (height / 2.0f) + offset.y);
116
117        srcDstCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
118        srcDstCanvas.drawBitmap(mask, m, mHolographicPaint);
119        srcDstCanvas.drawBitmap(mask, mIdentity, mErasePaint);
120        mask.recycle();
121    }
122
123    /**
124     * Applies an blur to whatever is currently drawn in the specified bitmap.
125     */
126    void applyBlur(Bitmap srcDst, Canvas srcDstCanvas) {
127        int[] xy = new int[2];
128        Bitmap mask = srcDst.extractAlpha(mBlurPaint, xy);
129        srcDstCanvas.drawBitmap(mask, xy[0], xy[1], mHolographicPaint);
130        mask.recycle();
131    }
132}
133
134/**
135 * An icon on a PagedView, specifically for items in the launcher's paged view (with compound
136 * drawables on the top).
137 */
138public class PagedViewIcon extends TextView implements Checkable {
139    private static final String TAG = "PagedViewIcon";
140
141    // holographic outline
142    private final Paint mPaint = new Paint();
143    private static HolographicOutlineHelper sHolographicOutlineHelper;
144    private Bitmap mCheckedOutline;
145    private Bitmap mHolographicOutline;
146    private Canvas mHolographicOutlineCanvas;
147    private boolean mIsHolographicUpdatePass;
148    private Rect mDrawableClipRect;
149
150    private Object mIconCacheKey;
151    private PagedViewIconCache mIconCache;
152
153    private int mAlpha;
154    private int mHolographicAlpha;
155
156    private boolean mIsChecked;
157
158    public PagedViewIcon(Context context) {
159        this(context, null);
160    }
161
162    public PagedViewIcon(Context context, AttributeSet attrs) {
163        this(context, attrs, 0);
164    }
165
166    public PagedViewIcon(Context context, AttributeSet attrs, int defStyle) {
167        super(context, attrs, defStyle);
168
169        if (sHolographicOutlineHelper == null) {
170            final Resources resources = context.getResources();
171            final DisplayMetrics metrics = resources.getDisplayMetrics();
172            final float density = metrics.density;
173            sHolographicOutlineHelper = new HolographicOutlineHelper(density);
174        }
175        mDrawableClipRect = new Rect();
176
177        setFocusable(true);
178        setBackgroundDrawable(null);
179    }
180
181    public void applyFromApplicationInfo(ApplicationInfo info, PagedViewIconCache cache) {
182        mIconCache = cache;
183        mIconCacheKey = info;
184        mHolographicOutline = mIconCache.getOutline(mIconCacheKey);
185
186        setCompoundDrawablesWithIntrinsicBounds(null,
187                new FastBitmapDrawable(info.iconBitmap), null, null);
188        setText(info.title);
189        setTag(info);
190    }
191
192    public void applyFromResolveInfo(ResolveInfo info, PackageManager packageManager,
193            PagedViewIconCache cache) {
194        mIconCache = cache;
195        mIconCacheKey = info;
196        mHolographicOutline = mIconCache.getOutline(mIconCacheKey);
197
198        Drawable image = info.loadIcon(packageManager);
199        image.setBounds(0, 0, image.getIntrinsicWidth(), image.getIntrinsicHeight());
200        setCompoundDrawablesWithIntrinsicBounds(null, image, null, null);
201        setText(info.loadLabel(packageManager));
202        setTag(info);
203    }
204
205    @Override
206    public void setAlpha(float alpha) {
207        final float viewAlpha = sHolographicOutlineHelper.viewAlphaInterpolator(alpha);
208        final float holographicAlpha = sHolographicOutlineHelper.highlightAlphaInterpolator(alpha);
209        mAlpha = (int) (viewAlpha * 255);
210        mHolographicAlpha = (int) (holographicAlpha * 255);
211        super.setAlpha(viewAlpha);
212    }
213
214    public void invalidateCheckedImage() {
215        if (mCheckedOutline != null) {
216            mCheckedOutline.recycle();
217            mCheckedOutline = null;
218        }
219    }
220
221    @Override
222    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
223        super.onLayout(changed, left, top, right, bottom);
224
225        if (mHolographicOutline == null) {
226            final PointF offset = new PointF(0,
227                    -(getCompoundPaddingBottom() + getCompoundPaddingTop())/2.0f);
228
229            // update the clipping rect to be used in the holographic pass below
230            getDrawingRect(mDrawableClipRect);
231            mDrawableClipRect.bottom = getPaddingTop() + getCompoundPaddingTop();
232
233            // set a flag to indicate that we are going to draw the view at full alpha with the text
234            // clipped for the generation of the holographic icon
235            mIsHolographicUpdatePass = true;
236            mHolographicOutline = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(),
237                    Bitmap.Config.ARGB_8888);
238            mHolographicOutlineCanvas = new Canvas(mHolographicOutline);
239            mHolographicOutlineCanvas.concat(getMatrix());
240            draw(mHolographicOutlineCanvas);
241            sHolographicOutlineHelper.setColor(HolographicOutlineHelper.HOLOGRAPHIC_BLUE);
242            sHolographicOutlineHelper.applyOutline(mHolographicOutline, mHolographicOutlineCanvas,
243                    HolographicOutlineHelper.DEFAULT_STROKE_WIDTH, offset);
244            sHolographicOutlineHelper.applyBlur(mHolographicOutline, mHolographicOutlineCanvas);
245            mIsHolographicUpdatePass = false;
246            mIconCache.addOutline(mIconCacheKey, mHolographicOutline);
247            mHolographicOutlineCanvas = null;
248        }
249    }
250
251    @Override
252    protected void onDraw(Canvas canvas) {
253        // draw the view itself
254        if (mIsHolographicUpdatePass) {
255            // only clip to the text view (restore its alpha so that we get a proper outline)
256            canvas.save();
257            canvas.clipRect(mDrawableClipRect, Op.REPLACE);
258            final float alpha = getAlpha();
259            super.setAlpha(1.0f);
260            super.onDraw(canvas);
261            super.setAlpha(alpha);
262            canvas.restore();
263        } else {
264            if (mAlpha > 0) {
265                super.onDraw(canvas);
266            }
267        }
268
269        // draw any blended overlays
270        if (!mIsHolographicUpdatePass) {
271            if (mCheckedOutline == null) {
272                if (mHolographicOutline != null && mHolographicAlpha > 0) {
273                    mPaint.setAlpha(mHolographicAlpha);
274                    canvas.drawBitmap(mHolographicOutline, 0, 0, mPaint);
275                }
276            } else {
277                mPaint.setAlpha(255);
278                canvas.drawBitmap(mCheckedOutline, 0, 0, mPaint);
279            }
280        }
281    }
282
283    @Override
284    public boolean isChecked() {
285        return mIsChecked;
286    }
287
288    @Override
289    public void setChecked(boolean checked) {
290        if (mIsChecked != checked) {
291            mIsChecked = checked;
292
293            if (mIsChecked) {
294                final PointF offset = new PointF(0,
295                        -(getCompoundPaddingBottom() + getCompoundPaddingTop())/2.0f);
296
297                // set a flag to indicate that we are going to draw the view at full alpha with the text
298                // clipped for the generation of the holographic icon
299                mIsHolographicUpdatePass = true;
300                mCheckedOutline = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(),
301                        Bitmap.Config.ARGB_8888);
302                mHolographicOutlineCanvas = new Canvas(mCheckedOutline);
303                mHolographicOutlineCanvas.concat(getMatrix());
304                draw(mHolographicOutlineCanvas);
305                sHolographicOutlineHelper.setColor(HolographicOutlineHelper.HOLOGRAPHIC_GREEN);
306                sHolographicOutlineHelper.applyOutline(mCheckedOutline, mHolographicOutlineCanvas,
307                        HolographicOutlineHelper.DEFAULT_STROKE_WIDTH + 1.0f, offset);
308                mIsHolographicUpdatePass = false;
309                mHolographicOutlineCanvas = null;
310            } else {
311                invalidateCheckedImage();
312            }
313
314            invalidate();
315        }
316    }
317
318    @Override
319    public void toggle() {
320        setChecked(!mIsChecked);
321    }
322}
323