Outline.java revision 54de77470de4f605eef7f4b4e01718b301fe275e
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 android.graphics;
18
19import android.annotation.FloatRange;
20import android.annotation.IntDef;
21import android.annotation.NonNull;
22import android.graphics.drawable.Drawable;
23
24import java.lang.annotation.Retention;
25import java.lang.annotation.RetentionPolicy;
26
27/**
28 * Defines a simple shape, used for bounding graphical regions.
29 * <p>
30 * Can be computed for a View, or computed by a Drawable, to drive the shape of
31 * shadows cast by a View, or to clip the contents of the View.
32 *
33 * @see android.view.ViewOutlineProvider
34 * @see android.view.View#setOutlineProvider(android.view.ViewOutlineProvider)
35 * @see Drawable#getOutline(Outline)
36 */
37public final class Outline {
38    private static final float RADIUS_UNDEFINED = Float.NEGATIVE_INFINITY;
39
40    /** @hide */
41    public static final int MODE_EMPTY = 0;
42    /** @hide */
43    public static final int MODE_ROUND_RECT = 1;
44    /** @hide */
45    public static final int MODE_CONVEX_PATH = 2;
46
47    /** @hide */
48    @Retention(RetentionPolicy.SOURCE)
49    @IntDef(flag = false,
50            value = {
51                    MODE_EMPTY,
52                    MODE_ROUND_RECT,
53                    MODE_CONVEX_PATH,
54            })
55    public @interface Mode {}
56
57    /** @hide */
58    @Mode
59    public int mMode = MODE_EMPTY;
60
61    /** @hide */
62    public final Path mPath = new Path();
63
64    /** @hide */
65    public final Rect mRect = new Rect();
66    /** @hide */
67    public float mRadius = RADIUS_UNDEFINED;
68    /** @hide */
69    public float mAlpha;
70
71    /**
72     * Constructs an empty Outline. Call one of the setter methods to make
73     * the outline valid for use with a View.
74     */
75    public Outline() {}
76
77    /**
78     * Constructs an Outline with a copy of the data in src.
79     */
80    public Outline(@NonNull Outline src) {
81        set(src);
82    }
83
84    /**
85     * Sets the outline to be empty.
86     *
87     * @see #isEmpty()
88     */
89    public void setEmpty() {
90        mMode = MODE_EMPTY;
91        mPath.rewind();
92        mRect.setEmpty();
93        mRadius = RADIUS_UNDEFINED;
94    }
95
96    /**
97     * Returns whether the Outline is empty.
98     * <p>
99     * Outlines are empty when constructed, or if {@link #setEmpty()} is called,
100     * until a setter method is called
101     *
102     * @see #setEmpty()
103     */
104    public boolean isEmpty() {
105        return mMode == MODE_EMPTY;
106    }
107
108
109    /**
110     * Returns whether the outline can be used to clip a View.
111     * <p>
112     * Currently, only Outlines that can be represented as a rectangle, circle,
113     * or round rect support clipping.
114     *
115     * @see android.view.View#setClipToOutline(boolean)
116     */
117    public boolean canClip() {
118        return mMode != MODE_CONVEX_PATH;
119    }
120
121    /**
122     * Sets the alpha represented by the Outline - the degree to which the
123     * producer is guaranteed to be opaque over the Outline's shape.
124     * <p>
125     * An alpha value of <code>0.0f</code> either represents completely
126     * transparent content, or content that isn't guaranteed to fill the shape
127     * it publishes.
128     * <p>
129     * Content producing a fully opaque (alpha = <code>1.0f</code>) outline is
130     * assumed by the drawing system to fully cover content beneath it,
131     * meaning content beneath may be optimized away.
132     */
133    public void setAlpha(@FloatRange(from=0.0, to=1.0) float alpha) {
134        mAlpha = alpha;
135    }
136
137    /**
138     * Returns the alpha represented by the Outline.
139     */
140    public float getAlpha() {
141        return mAlpha;
142    }
143
144    /**
145     * Replace the contents of this Outline with the contents of src.
146     *
147     * @param src Source outline to copy from.
148     */
149    public void set(@NonNull Outline src) {
150        mMode = src.mMode;
151        mPath.set(src.mPath);
152        mRect.set(src.mRect);
153        mRadius = src.mRadius;
154        mAlpha = src.mAlpha;
155    }
156
157    /**
158     * Sets the Outline to the rounded rect defined by the input rect, and
159     * corner radius.
160     */
161    public void setRect(int left, int top, int right, int bottom) {
162        setRoundRect(left, top, right, bottom, 0.0f);
163    }
164
165    /**
166     * Convenience for {@link #setRect(int, int, int, int)}
167     */
168    public void setRect(@NonNull Rect rect) {
169        setRect(rect.left, rect.top, rect.right, rect.bottom);
170    }
171
172    /**
173     * Sets the Outline to the rounded rect defined by the input rect, and corner radius.
174     * <p>
175     * Passing a zero radius is equivalent to calling {@link #setRect(int, int, int, int)}
176     */
177    public void setRoundRect(int left, int top, int right, int bottom, float radius) {
178        if (left >= right || top >= bottom) {
179            setEmpty();
180            return;
181        }
182
183        mMode = MODE_ROUND_RECT;
184        mRect.set(left, top, right, bottom);
185        mRadius = radius;
186        mPath.rewind();
187    }
188
189    /**
190     * Convenience for {@link #setRoundRect(int, int, int, int, float)}
191     */
192    public void setRoundRect(@NonNull Rect rect, float radius) {
193        setRoundRect(rect.left, rect.top, rect.right, rect.bottom, radius);
194    }
195
196    /**
197     * Populates {@code outBounds} with the outline bounds, if set, and returns
198     * {@code true}. If no outline bounds are set, or if a path has been set
199     * via {@link #setConvexPath(Path)}, returns {@code false}.
200     *
201     * @param outRect the rect to populate with the outline bounds, if set
202     * @return {@code true} if {@code outBounds} was populated with outline
203     *         bounds, or {@code false} if no outline bounds are set
204     */
205    public boolean getRect(@NonNull Rect outRect) {
206        if (mMode != MODE_ROUND_RECT) {
207            return false;
208        }
209        outRect.set(mRect);
210        return true;
211    }
212
213    /**
214     * Returns the rounded rect radius, if set, or a value less than 0 if a path has
215     * been set via {@link #setConvexPath(Path)}. A return value of {@code 0}
216     * indicates a non-rounded rect.
217     *
218     * @return the rounded rect radius, or value < 0
219     */
220    public float getRadius() {
221        return mRadius;
222    }
223
224    /**
225     * Sets the outline to the oval defined by input rect.
226     */
227    public void setOval(int left, int top, int right, int bottom) {
228        if (left >= right || top >= bottom) {
229            setEmpty();
230            return;
231        }
232
233        if ((bottom - top) == (right - left)) {
234            // represent circle as round rect, for efficiency, and to enable clipping
235            setRoundRect(left, top, right, bottom, (bottom - top) / 2.0f);
236            return;
237        }
238
239        mMode = MODE_CONVEX_PATH;
240        mPath.rewind();
241        mPath.addOval(left, top, right, bottom, Path.Direction.CW);
242        mRect.setEmpty();
243        mRadius = RADIUS_UNDEFINED;
244    }
245
246    /**
247     * Convenience for {@link #setOval(int, int, int, int)}
248     */
249    public void setOval(@NonNull Rect rect) {
250        setOval(rect.left, rect.top, rect.right, rect.bottom);
251    }
252
253    /**
254     * Sets the Constructs an Outline from a
255     * {@link android.graphics.Path#isConvex() convex path}.
256     */
257    public void setConvexPath(@NonNull Path convexPath) {
258        if (convexPath.isEmpty()) {
259            setEmpty();
260            return;
261        }
262
263        if (!convexPath.isConvex()) {
264            throw new IllegalArgumentException("path must be convex");
265        }
266
267        mMode = MODE_CONVEX_PATH;
268        mPath.set(convexPath);
269        mRect.setEmpty();
270        mRadius = RADIUS_UNDEFINED;
271    }
272
273    /**
274     * Offsets the Outline by (dx,dy)
275     */
276    public void offset(int dx, int dy) {
277        if (mMode == MODE_ROUND_RECT) {
278            mRect.offset(dx, dy);
279        } else if (mMode == MODE_CONVEX_PATH) {
280            mPath.offset(dx, dy);
281        }
282    }
283}
284