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