• 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.graphics.drawable.Drawable;
23 
24 import java.lang.annotation.Retention;
25 import 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  */
37 public 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      */
Outline()75     public Outline() {}
76 
77     /**
78      * Constructs an Outline with a copy of the data in src.
79      */
Outline(@onNull Outline src)80     public Outline(@NonNull Outline src) {
81         set(src);
82     }
83 
84     /**
85      * Sets the outline to be empty.
86      *
87      * @see #isEmpty()
88      */
setEmpty()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      */
isEmpty()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 {@link android.view.View#setClipToOutline(boolean)}
116      */
canClip()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      */
setAlpha(@loatRangefrom=0.0, to=1.0) float alpha)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      */
getAlpha()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      */
set(@onNull Outline src)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      */
setRect(int left, int top, int right, int bottom)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      */
setRect(@onNull Rect rect)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      */
setRoundRect(int left, int top, int right, int bottom, float radius)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      */
setRoundRect(@onNull Rect rect, float radius)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      */
getRect(@onNull Rect outRect)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      */
getRadius()220     public float getRadius() {
221         return mRadius;
222     }
223 
224     /**
225      * Sets the outline to the oval defined by input rect.
226      */
setOval(int left, int top, int right, int bottom)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      */
setOval(@onNull Rect rect)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      */
setConvexPath(@onNull Path convexPath)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      */
offset(int dx, int dy)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