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