StyledCornersBitmapDrawable.java revision ad6ca3f895022ded1a11f3eedc50d70ea90cd4da
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
17package com.android.bitmap.drawable;
18
19import android.content.res.Resources;
20import android.graphics.Canvas;
21import android.graphics.Color;
22import android.graphics.Paint;
23import android.graphics.Paint.Style;
24import android.graphics.Path;
25import android.graphics.Rect;
26import android.graphics.RectF;
27
28import com.android.bitmap.BitmapCache;
29
30/**
31 * A custom ExtendedBitmapDrawable that styles the corners in configurable ways.
32 *
33 * All four corners can be configured as {@link #CORNER_STYLE_SHARP},
34 * {@link #CORNER_STYLE_ROUND}, or {@link #CORNER_STYLE_FLAP}.
35 * This is accomplished applying a non-rectangular clip applied to the canvas.
36 *
37 * A border is draw that conforms to the styled corners.
38 *
39 * {@link #CORNER_STYLE_FLAP} corners have a colored flap drawn within the bounds.
40 */
41public class StyledCornersBitmapDrawable extends ExtendedBitmapDrawable {
42
43    public static final int CORNER_STYLE_SHARP = 0;
44    public static final int CORNER_STYLE_ROUND = 1;
45    public static final int CORNER_STYLE_FLAP = 2;
46
47    private static final int START_RIGHT = 0;
48    private static final int START_BOTTOM = 90;
49    private static final int START_LEFT = 180;
50    private static final int START_TOP = 270;
51    private static final int QUARTER_CIRCLE = 90;
52    private static final RectF sRectF = new RectF();
53
54    private final Paint mFlapPaint = new Paint();
55    private final Paint mBorderPaint = new Paint();
56    private final Path mClipPath = new Path();
57    private final float mCornerRoundRadius;
58    private final float mCornerFlapSide;
59
60    private int mTopLeftCornerStyle = CORNER_STYLE_SHARP;
61    private int mTopRightCornerStyle = CORNER_STYLE_SHARP;
62    private int mBottomRightCornerStyle = CORNER_STYLE_SHARP;
63    private int mBottomLeftCornerStyle = CORNER_STYLE_SHARP;
64    private int mScrimColor;
65    private float mBorderWidth;
66
67    /**
68     * Create a new StyledCornersBitmapDrawable.
69     */
70    public StyledCornersBitmapDrawable(Resources res, BitmapCache cache,
71            boolean limitDensity, ExtendedOptions opts, float cornerRoundRadius,
72            float cornerFlapSide) {
73        super(res, cache, limitDensity, opts);
74
75        mCornerRoundRadius = cornerRoundRadius;
76        mCornerFlapSide = cornerFlapSide;
77
78        mFlapPaint.setColor(Color.TRANSPARENT);
79        mFlapPaint.setStyle(Style.FILL);
80
81        mBorderPaint.setColor(Color.TRANSPARENT);
82        mBorderPaint.setStyle(Style.STROKE);
83        mBorderPaint.setStrokeWidth(mBorderWidth);
84        mBorderPaint.setAntiAlias(true);
85
86        mScrimColor = Color.TRANSPARENT;
87    }
88
89    /**
90     * Set the border stroke width of this drawable.
91     */
92    public void setBorderWidth(final float borderWidth) {
93        final boolean changed = mBorderPaint.getStrokeWidth() != borderWidth;
94        mBorderPaint.setStrokeWidth(borderWidth);
95        mBorderWidth = borderWidth;
96
97        if (changed) {
98            invalidateSelf();
99        }
100    }
101
102    /**
103     * Set the border stroke color of this drawable. Set to {@link Color#TRANSPARENT} to disable.
104     */
105    public void setBorderColor(final int color) {
106        final boolean changed = mBorderPaint.getColor() != color;
107        mBorderPaint.setColor(color);
108
109        if (changed) {
110            invalidateSelf();
111        }
112    }
113
114    /** Set the corner styles for all four corners */
115    public void setCornerStyles(int topLeft, int topRight, int bottomRight, int bottomLeft) {
116        boolean changed = mTopLeftCornerStyle != topLeft
117                || mTopRightCornerStyle != topRight
118                || mBottomRightCornerStyle != bottomRight
119                || mBottomLeftCornerStyle != bottomLeft;
120
121        mTopLeftCornerStyle = topLeft;
122        mTopRightCornerStyle = topRight;
123        mBottomRightCornerStyle = bottomRight;
124        mBottomLeftCornerStyle = bottomLeft;
125
126        if (changed) {
127            recalculatePath();
128        }
129    }
130
131    /**
132     * Set the flap color for all corners that have style {@link #CORNER_STYLE_SHARP}.
133     *
134     * Use {@link android.graphics.Color#TRANSPARENT} to disable flap colors.
135     */
136    public void setFlapColor(int flapColor) {
137        boolean changed = mFlapPaint.getColor() != flapColor;
138        mFlapPaint.setColor(flapColor);
139
140        if (changed) {
141            invalidateSelf();
142        }
143    }
144
145    /**
146     * Get the color of the scrim that is drawn over the contents, but under the flaps and borders.
147     */
148    public int getScrimColor() {
149        return mScrimColor;
150    }
151
152    /**
153     * Set the color of the scrim that is drawn over the contents, but under the flaps and borders.
154     *
155     * Use {@link android.graphics.Color#TRANSPARENT} to disable the scrim.
156     */
157    public void setScrimColor(int color) {
158        boolean changed = mScrimColor != color;
159        mScrimColor = color;
160
161        if (changed) {
162            invalidateSelf();
163        }
164    }
165
166    @Override
167    protected void onBoundsChange(Rect bounds) {
168        super.onBoundsChange(bounds);
169
170        recalculatePath();
171    }
172
173    /**
174     * Override draw(android.graphics.Canvas) instead of
175     * {@link #onDraw(android.graphics.Canvas)} to clip all the drawable layers.
176     */
177    @Override
178    public void draw(Canvas canvas) {
179        final Rect bounds = getBounds();
180        if (bounds.isEmpty()) {
181            return;
182        }
183
184        // Clip to path.
185        canvas.save();
186        canvas.clipPath(mClipPath);
187
188        // Draw parent within path.
189        super.draw(canvas);
190
191        // Draw scrim on top of parent.
192        canvas.drawColor(mScrimColor);
193
194        // Draw flap.
195        float left = bounds.left + mBorderWidth / 2;
196        float top = bounds.top + mBorderWidth / 2;
197        float right = bounds.right - mBorderWidth / 2;
198        float bottom = bounds.bottom - mBorderWidth / 2;
199        RectF flapCornerRectF = sRectF;
200        flapCornerRectF.set(0, 0, mCornerFlapSide + mCornerRoundRadius,
201                mCornerFlapSide + mCornerRoundRadius);
202
203        if (mTopLeftCornerStyle == CORNER_STYLE_FLAP) {
204            flapCornerRectF.offsetTo(left, top);
205            canvas.drawRoundRect(flapCornerRectF, mCornerRoundRadius,
206                    mCornerRoundRadius, mFlapPaint);
207        }
208        if (mTopRightCornerStyle == CORNER_STYLE_FLAP) {
209            flapCornerRectF.offsetTo(right - mCornerFlapSide, top);
210            canvas.drawRoundRect(flapCornerRectF, mCornerRoundRadius,
211                    mCornerRoundRadius, mFlapPaint);
212        }
213        if (mBottomRightCornerStyle == CORNER_STYLE_FLAP) {
214            flapCornerRectF.offsetTo(right - mCornerFlapSide, bottom - mCornerFlapSide);
215            canvas.drawRoundRect(flapCornerRectF, mCornerRoundRadius,
216                    mCornerRoundRadius, mFlapPaint);
217        }
218        if (mBottomLeftCornerStyle == CORNER_STYLE_FLAP) {
219            flapCornerRectF.offsetTo(left, bottom - mCornerFlapSide);
220            canvas.drawRoundRect(flapCornerRectF, mCornerRoundRadius,
221                    mCornerRoundRadius, mFlapPaint);
222        }
223
224        canvas.restore();
225
226        // Draw border around path.
227        canvas.drawPath(mClipPath, mBorderPaint);
228    }
229
230    protected Path getClipPath() {
231        return mClipPath;
232    }
233
234    private void recalculatePath() {
235        Rect bounds = getBounds();
236
237        if (bounds.isEmpty()) {
238            return;
239        }
240
241        // Setup.
242        float left = bounds.left + mBorderWidth / 2;
243        float top = bounds.top + mBorderWidth / 2;
244        float right = bounds.right - mBorderWidth / 2;
245        float bottom = bounds.bottom - mBorderWidth / 2;
246        RectF roundedCornerRectF = sRectF;
247        roundedCornerRectF.set(0, 0, 2 * mCornerRoundRadius, 2 * mCornerRoundRadius);
248        mClipPath.rewind();
249
250        switch (mTopLeftCornerStyle) {
251            case CORNER_STYLE_SHARP:
252                mClipPath.moveTo(left, top);
253                break;
254            case CORNER_STYLE_ROUND:
255                roundedCornerRectF.offsetTo(left, top);
256                mClipPath.arcTo(roundedCornerRectF, START_LEFT, QUARTER_CIRCLE);
257                break;
258            case CORNER_STYLE_FLAP:
259                mClipPath.moveTo(left, top - mCornerFlapSide);
260                mClipPath.lineTo(left + mCornerFlapSide, top);
261                break;
262        }
263
264        switch (mTopRightCornerStyle) {
265            case CORNER_STYLE_SHARP:
266                mClipPath.lineTo(right, top);
267                break;
268            case CORNER_STYLE_ROUND:
269                roundedCornerRectF.offsetTo(right - roundedCornerRectF.width(), top);
270                mClipPath.arcTo(roundedCornerRectF, START_TOP, QUARTER_CIRCLE);
271                break;
272            case CORNER_STYLE_FLAP:
273                mClipPath.lineTo(right - mCornerFlapSide, top);
274                mClipPath.lineTo(right, top + mCornerFlapSide);
275                break;
276        }
277
278        switch (mBottomRightCornerStyle) {
279            case CORNER_STYLE_SHARP:
280                mClipPath.lineTo(right, bottom);
281                break;
282            case CORNER_STYLE_ROUND:
283                roundedCornerRectF.offsetTo(right - roundedCornerRectF.width(),
284                        bottom - roundedCornerRectF.height());
285                mClipPath.arcTo(roundedCornerRectF, START_RIGHT, QUARTER_CIRCLE);
286                break;
287            case CORNER_STYLE_FLAP:
288                mClipPath.lineTo(right, bottom - mCornerFlapSide);
289                mClipPath.lineTo(right - mCornerFlapSide, bottom);
290                break;
291        }
292
293        switch (mBottomLeftCornerStyle) {
294            case CORNER_STYLE_SHARP:
295                mClipPath.lineTo(left, bottom);
296                break;
297            case CORNER_STYLE_ROUND:
298                roundedCornerRectF.offsetTo(left, bottom - roundedCornerRectF.height());
299                mClipPath.arcTo(roundedCornerRectF, START_BOTTOM, QUARTER_CIRCLE);
300                break;
301            case CORNER_STYLE_FLAP:
302                mClipPath.lineTo(left + mCornerFlapSide, bottom);
303                mClipPath.lineTo(left, bottom - mCornerFlapSide);
304                break;
305        }
306
307        // Finish.
308        mClipPath.close();
309    }
310}
311