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