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