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