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