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      * Currently, only Outlines that can be represented as a rectangle, circle,
122      * or round rect support clipping.
123      *
124      * @see android.view.View#setClipToOutline(boolean)
125      */
canClip()126     public boolean canClip() {
127         return mMode != MODE_PATH;
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 rounded rect defined by the input rect, and
173      * corner radius.
174      */
setRect(int left, int top, int right, int bottom)175     public void setRect(int left, int top, int right, int bottom) {
176         setRoundRect(left, top, right, bottom, 0.0f);
177     }
178 
179     /**
180      * Convenience for {@link #setRect(int, int, int, int)}
181      */
setRect(@onNull Rect rect)182     public void setRect(@NonNull Rect rect) {
183         setRect(rect.left, rect.top, rect.right, rect.bottom);
184     }
185 
186     /**
187      * Sets the Outline to the rounded rect defined by the input rect, and corner radius.
188      * <p>
189      * Passing a zero radius is equivalent to calling {@link #setRect(int, int, int, int)}
190      */
setRoundRect(int left, int top, int right, int bottom, float radius)191     public void setRoundRect(int left, int top, int right, int bottom, float radius) {
192         if (left >= right || top >= bottom) {
193             setEmpty();
194             return;
195         }
196 
197         if (mMode == MODE_PATH) {
198             // rewind here to avoid thrashing the allocations, but could alternately clear ref
199             mPath.rewind();
200         }
201         mMode = MODE_ROUND_RECT;
202         mRect.set(left, top, right, bottom);
203         mRadius = radius;
204     }
205 
206     /**
207      * Convenience for {@link #setRoundRect(int, int, int, int, float)}
208      */
setRoundRect(@onNull Rect rect, float radius)209     public void setRoundRect(@NonNull Rect rect, float radius) {
210         setRoundRect(rect.left, rect.top, rect.right, rect.bottom, radius);
211     }
212 
213     /**
214      * Populates {@code outBounds} with the outline bounds, if set, and returns
215      * {@code true}. If no outline bounds are set, or if a path has been set
216      * via {@link #setPath(Path)}, returns {@code false}.
217      *
218      * @param outRect the rect to populate with the outline bounds, if set
219      * @return {@code true} if {@code outBounds} was populated with outline
220      *         bounds, or {@code false} if no outline bounds are set
221      */
getRect(@onNull Rect outRect)222     public boolean getRect(@NonNull Rect outRect) {
223         if (mMode != MODE_ROUND_RECT) {
224             return false;
225         }
226         outRect.set(mRect);
227         return true;
228     }
229 
230     /**
231      * Returns the rounded rect radius, if set, or a value less than 0 if a path has
232      * been set via {@link #setPath(Path)}. A return value of {@code 0}
233      * indicates a non-rounded rect.
234      *
235      * @return the rounded rect radius, or value < 0
236      */
getRadius()237     public float getRadius() {
238         return mRadius;
239     }
240 
241     /**
242      * Sets the outline to the oval defined by input rect.
243      */
setOval(int left, int top, int right, int bottom)244     public void setOval(int left, int top, int right, int bottom) {
245         if (left >= right || top >= bottom) {
246             setEmpty();
247             return;
248         }
249 
250         if ((bottom - top) == (right - left)) {
251             // represent circle as round rect, for efficiency, and to enable clipping
252             setRoundRect(left, top, right, bottom, (bottom - top) / 2.0f);
253             return;
254         }
255 
256         if (mPath == null) {
257             mPath = new Path();
258         } else {
259             mPath.rewind();
260         }
261 
262         mMode = MODE_PATH;
263         mPath.addOval(left, top, right, bottom, Path.Direction.CW);
264         mRect.setEmpty();
265         mRadius = RADIUS_UNDEFINED;
266     }
267 
268     /**
269      * Convenience for {@link #setOval(int, int, int, int)}
270      */
setOval(@onNull Rect rect)271     public void setOval(@NonNull Rect rect) {
272         setOval(rect.left, rect.top, rect.right, rect.bottom);
273     }
274 
275     /**
276      * Sets the Outline to a
277      * {@link android.graphics.Path#isConvex() convex path}.
278      *
279      * @param convexPath used to construct the Outline. As of
280      * {@link android.os.Build.VERSION_CODES#Q}, it is no longer required to be
281      * convex.
282      *
283      * @deprecated As of {@link android.os.Build.VERSION_CODES#Q}, the restriction
284      * that the path must be convex is removed. However, the API is misnamed until
285      * {@link android.os.Build.VERSION_CODES#R}, when {@link #setPath} is
286      * introduced. Use {@link #setPath} instead.
287      */
288     @Deprecated
setConvexPath(@onNull Path convexPath)289     public void setConvexPath(@NonNull Path convexPath) {
290         setPath(convexPath);
291     }
292 
293     /**
294      * Sets the Outline to a {@link android.graphics.Path path}.
295      *
296      * @param path used to construct the Outline.
297      */
setPath(@onNull Path path)298     public void setPath(@NonNull Path path) {
299         if (path.isEmpty()) {
300             setEmpty();
301             return;
302         }
303 
304         if (mPath == null) {
305             mPath = new Path();
306         }
307 
308         mMode = MODE_PATH;
309         mPath.set(path);
310         mRect.setEmpty();
311         mRadius = RADIUS_UNDEFINED;
312     }
313 
314     /**
315      * Offsets the Outline by (dx,dy)
316      */
offset(int dx, int dy)317     public void offset(int dx, int dy) {
318         if (mMode == MODE_ROUND_RECT) {
319             mRect.offset(dx, dy);
320         } else if (mMode == MODE_PATH) {
321             mPath.offset(dx, dy);
322         }
323     }
324 }
325