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