1 /* 2 * Copyright (C) 2006 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.view; 18 19 import android.annotation.IntDef; 20 import android.annotation.NonNull; 21 import android.graphics.Rect; 22 23 import java.lang.annotation.Retention; 24 import java.lang.annotation.RetentionPolicy; 25 26 /** 27 * Standard constants and tools for placing an object within a potentially 28 * larger container. 29 */ 30 public class Gravity 31 { 32 /** Constant indicating that no gravity has been set **/ 33 public static final int NO_GRAVITY = 0x0000; 34 35 /** Raw bit indicating the gravity for an axis has been specified. */ 36 public static final int AXIS_SPECIFIED = 0x0001; 37 38 /** Raw bit controlling how the left/top edge is placed. */ 39 public static final int AXIS_PULL_BEFORE = 0x0002; 40 /** Raw bit controlling how the right/bottom edge is placed. */ 41 public static final int AXIS_PULL_AFTER = 0x0004; 42 /** Raw bit controlling whether the right/bottom edge is clipped to its 43 * container, based on the gravity direction being applied. */ 44 public static final int AXIS_CLIP = 0x0008; 45 46 /** Bits defining the horizontal axis. */ 47 public static final int AXIS_X_SHIFT = 0; 48 /** Bits defining the vertical axis. */ 49 public static final int AXIS_Y_SHIFT = 4; 50 51 /** Push object to the top of its container, not changing its size. */ 52 public static final int TOP = (AXIS_PULL_BEFORE|AXIS_SPECIFIED)<<AXIS_Y_SHIFT; 53 /** Push object to the bottom of its container, not changing its size. */ 54 public static final int BOTTOM = (AXIS_PULL_AFTER|AXIS_SPECIFIED)<<AXIS_Y_SHIFT; 55 /** Push object to the left of its container, not changing its size. */ 56 public static final int LEFT = (AXIS_PULL_BEFORE|AXIS_SPECIFIED)<<AXIS_X_SHIFT; 57 /** Push object to the right of its container, not changing its size. */ 58 public static final int RIGHT = (AXIS_PULL_AFTER|AXIS_SPECIFIED)<<AXIS_X_SHIFT; 59 60 /** Place object in the vertical center of its container, not changing its 61 * size. */ 62 public static final int CENTER_VERTICAL = AXIS_SPECIFIED<<AXIS_Y_SHIFT; 63 /** Grow the vertical size of the object if needed so it completely fills 64 * its container. */ 65 public static final int FILL_VERTICAL = TOP|BOTTOM; 66 67 /** Place object in the horizontal center of its container, not changing its 68 * size. */ 69 public static final int CENTER_HORIZONTAL = AXIS_SPECIFIED<<AXIS_X_SHIFT; 70 /** Grow the horizontal size of the object if needed so it completely fills 71 * its container. */ 72 public static final int FILL_HORIZONTAL = LEFT|RIGHT; 73 74 /** Place the object in the center of its container in both the vertical 75 * and horizontal axis, not changing its size. */ 76 public static final int CENTER = CENTER_VERTICAL|CENTER_HORIZONTAL; 77 78 /** Grow the horizontal and vertical size of the object if needed so it 79 * completely fills its container. */ 80 public static final int FILL = FILL_VERTICAL|FILL_HORIZONTAL; 81 82 /** Flag to clip the edges of the object to its container along the 83 * vertical axis. */ 84 public static final int CLIP_VERTICAL = AXIS_CLIP<<AXIS_Y_SHIFT; 85 86 /** Flag to clip the edges of the object to its container along the 87 * horizontal axis. */ 88 public static final int CLIP_HORIZONTAL = AXIS_CLIP<<AXIS_X_SHIFT; 89 90 /** Raw bit controlling whether the layout direction is relative or not (START/END instead of 91 * absolute LEFT/RIGHT). 92 */ 93 public static final int RELATIVE_LAYOUT_DIRECTION = 0x00800000; 94 95 /** 96 * Binary mask to get the absolute horizontal gravity of a gravity. 97 */ 98 public static final int HORIZONTAL_GRAVITY_MASK = (AXIS_SPECIFIED | 99 AXIS_PULL_BEFORE | AXIS_PULL_AFTER) << AXIS_X_SHIFT; 100 /** 101 * Binary mask to get the vertical gravity of a gravity. 102 */ 103 public static final int VERTICAL_GRAVITY_MASK = (AXIS_SPECIFIED | 104 AXIS_PULL_BEFORE | AXIS_PULL_AFTER) << AXIS_Y_SHIFT; 105 106 /** Special constant to enable clipping to an overall display along the 107 * vertical dimension. This is not applied by default by 108 * {@link #apply(int, int, int, Rect, int, int, Rect)}; you must do so 109 * yourself by calling {@link #applyDisplay}. 110 */ 111 public static final int DISPLAY_CLIP_VERTICAL = 0x10000000; 112 113 /** Special constant to enable clipping to an overall display along the 114 * horizontal dimension. This is not applied by default by 115 * {@link #apply(int, int, int, Rect, int, int, Rect)}; you must do so 116 * yourself by calling {@link #applyDisplay}. 117 */ 118 public static final int DISPLAY_CLIP_HORIZONTAL = 0x01000000; 119 120 /** Push object to x-axis position at the start of its container, not changing its size. */ 121 public static final int START = RELATIVE_LAYOUT_DIRECTION | LEFT; 122 123 /** Push object to x-axis position at the end of its container, not changing its size. */ 124 public static final int END = RELATIVE_LAYOUT_DIRECTION | RIGHT; 125 126 /** 127 * Binary mask for the horizontal gravity and script specific direction bit. 128 */ 129 public static final int RELATIVE_HORIZONTAL_GRAVITY_MASK = START | END; 130 131 132 /** 133 * @hide 134 */ 135 @Retention(RetentionPolicy.SOURCE) 136 @IntDef(flag = true, value = { 137 Gravity.FILL, 138 Gravity.FILL_HORIZONTAL, 139 Gravity.FILL_VERTICAL, 140 Gravity.START, 141 Gravity.END, 142 Gravity.LEFT, 143 Gravity.RIGHT, 144 Gravity.TOP, 145 Gravity.BOTTOM, 146 Gravity.CENTER, 147 Gravity.CENTER_HORIZONTAL, 148 Gravity.CENTER_VERTICAL, 149 Gravity.DISPLAY_CLIP_HORIZONTAL, 150 Gravity.DISPLAY_CLIP_VERTICAL, 151 Gravity.CLIP_HORIZONTAL, 152 Gravity.CLIP_VERTICAL, 153 Gravity.NO_GRAVITY 154 }) 155 public @interface GravityFlags {} 156 157 /** 158 * Apply a gravity constant to an object. This supposes that the layout direction is LTR. 159 * 160 * @param gravity The desired placement of the object, as defined by the 161 * constants in this class. 162 * @param w The horizontal size of the object. 163 * @param h The vertical size of the object. 164 * @param container The frame of the containing space, in which the object 165 * will be placed. Should be large enough to contain the 166 * width and height of the object. 167 * @param outRect Receives the computed frame of the object in its 168 * container. 169 */ apply(int gravity, int w, int h, Rect container, Rect outRect)170 public static void apply(int gravity, int w, int h, Rect container, Rect outRect) { 171 apply(gravity, w, h, container, 0, 0, outRect); 172 } 173 174 /** 175 * Apply a gravity constant to an object and take care if layout direction is RTL or not. 176 * 177 * @param gravity The desired placement of the object, as defined by the 178 * constants in this class. 179 * @param w The horizontal size of the object. 180 * @param h The vertical size of the object. 181 * @param container The frame of the containing space, in which the object 182 * will be placed. Should be large enough to contain the 183 * width and height of the object. 184 * @param outRect Receives the computed frame of the object in its 185 * container. 186 * @param layoutDirection The layout direction. 187 * 188 * @see View#LAYOUT_DIRECTION_LTR 189 * @see View#LAYOUT_DIRECTION_RTL 190 */ apply(int gravity, int w, int h, @NonNull Rect container, @NonNull Rect outRect, int layoutDirection)191 public static void apply(int gravity, int w, int h, @NonNull Rect container, 192 @NonNull Rect outRect, int layoutDirection) { 193 int absGravity = getAbsoluteGravity(gravity, layoutDirection); 194 apply(absGravity, w, h, container, 0, 0, outRect); 195 } 196 197 /** 198 * Apply a gravity constant to an object. 199 * 200 * @param gravity The desired placement of the object, as defined by the 201 * constants in this class. 202 * @param w The horizontal size of the object. 203 * @param h The vertical size of the object. 204 * @param container The frame of the containing space, in which the object 205 * will be placed. Should be large enough to contain the 206 * width and height of the object. 207 * @param xAdj Offset to apply to the X axis. If gravity is LEFT this 208 * pushes it to the right; if gravity is RIGHT it pushes it to 209 * the left; if gravity is CENTER_HORIZONTAL it pushes it to the 210 * right or left; otherwise it is ignored. 211 * @param yAdj Offset to apply to the Y axis. If gravity is TOP this pushes 212 * it down; if gravity is BOTTOM it pushes it up; if gravity is 213 * CENTER_VERTICAL it pushes it down or up; otherwise it is 214 * ignored. 215 * @param outRect Receives the computed frame of the object in its 216 * container. 217 */ apply(int gravity, int w, int h, @NonNull Rect container, int xAdj, int yAdj, @NonNull Rect outRect)218 public static void apply(int gravity, int w, int h, @NonNull Rect container, 219 int xAdj, int yAdj, @NonNull Rect outRect) { 220 switch (gravity&((AXIS_PULL_BEFORE|AXIS_PULL_AFTER)<<AXIS_X_SHIFT)) { 221 case 0: 222 outRect.left = container.left 223 + ((container.right - container.left - w)/2) + xAdj; 224 outRect.right = outRect.left + w; 225 if ((gravity&(AXIS_CLIP<<AXIS_X_SHIFT)) 226 == (AXIS_CLIP<<AXIS_X_SHIFT)) { 227 if (outRect.left < container.left) { 228 outRect.left = container.left; 229 } 230 if (outRect.right > container.right) { 231 outRect.right = container.right; 232 } 233 } 234 break; 235 case AXIS_PULL_BEFORE<<AXIS_X_SHIFT: 236 outRect.left = container.left + xAdj; 237 outRect.right = outRect.left + w; 238 if ((gravity&(AXIS_CLIP<<AXIS_X_SHIFT)) 239 == (AXIS_CLIP<<AXIS_X_SHIFT)) { 240 if (outRect.right > container.right) { 241 outRect.right = container.right; 242 } 243 } 244 break; 245 case AXIS_PULL_AFTER<<AXIS_X_SHIFT: 246 outRect.right = container.right - xAdj; 247 outRect.left = outRect.right - w; 248 if ((gravity&(AXIS_CLIP<<AXIS_X_SHIFT)) 249 == (AXIS_CLIP<<AXIS_X_SHIFT)) { 250 if (outRect.left < container.left) { 251 outRect.left = container.left; 252 } 253 } 254 break; 255 default: 256 outRect.left = container.left + xAdj; 257 outRect.right = container.right + xAdj; 258 break; 259 } 260 261 switch (gravity&((AXIS_PULL_BEFORE|AXIS_PULL_AFTER)<<AXIS_Y_SHIFT)) { 262 case 0: 263 outRect.top = container.top 264 + ((container.bottom - container.top - h)/2) + yAdj; 265 outRect.bottom = outRect.top + h; 266 if ((gravity&(AXIS_CLIP<<AXIS_Y_SHIFT)) 267 == (AXIS_CLIP<<AXIS_Y_SHIFT)) { 268 if (outRect.top < container.top) { 269 outRect.top = container.top; 270 } 271 if (outRect.bottom > container.bottom) { 272 outRect.bottom = container.bottom; 273 } 274 } 275 break; 276 case AXIS_PULL_BEFORE<<AXIS_Y_SHIFT: 277 outRect.top = container.top + yAdj; 278 outRect.bottom = outRect.top + h; 279 if ((gravity&(AXIS_CLIP<<AXIS_Y_SHIFT)) 280 == (AXIS_CLIP<<AXIS_Y_SHIFT)) { 281 if (outRect.bottom > container.bottom) { 282 outRect.bottom = container.bottom; 283 } 284 } 285 break; 286 case AXIS_PULL_AFTER<<AXIS_Y_SHIFT: 287 outRect.bottom = container.bottom - yAdj; 288 outRect.top = outRect.bottom - h; 289 if ((gravity&(AXIS_CLIP<<AXIS_Y_SHIFT)) 290 == (AXIS_CLIP<<AXIS_Y_SHIFT)) { 291 if (outRect.top < container.top) { 292 outRect.top = container.top; 293 } 294 } 295 break; 296 default: 297 outRect.top = container.top + yAdj; 298 outRect.bottom = container.bottom + yAdj; 299 break; 300 } 301 } 302 303 /** 304 * Apply a gravity constant to an object. 305 * 306 * @param gravity The desired placement of the object, as defined by the 307 * constants in this class. 308 * @param w The horizontal size of the object. 309 * @param h The vertical size of the object. 310 * @param container The frame of the containing space, in which the object 311 * will be placed. Should be large enough to contain the 312 * width and height of the object. 313 * @param xAdj Offset to apply to the X axis. If gravity is LEFT this 314 * pushes it to the right; if gravity is RIGHT it pushes it to 315 * the left; if gravity is CENTER_HORIZONTAL it pushes it to the 316 * right or left; otherwise it is ignored. 317 * @param yAdj Offset to apply to the Y axis. If gravity is TOP this pushes 318 * it down; if gravity is BOTTOM it pushes it up; if gravity is 319 * CENTER_VERTICAL it pushes it down or up; otherwise it is 320 * ignored. 321 * @param outRect Receives the computed frame of the object in its 322 * container. 323 * @param layoutDirection The layout direction. 324 * 325 * @see View#LAYOUT_DIRECTION_LTR 326 * @see View#LAYOUT_DIRECTION_RTL 327 */ apply(int gravity, int w, int h, @NonNull Rect container, int xAdj, int yAdj, @NonNull Rect outRect, int layoutDirection)328 public static void apply(int gravity, int w, int h, @NonNull Rect container, 329 int xAdj, int yAdj, @NonNull Rect outRect, int layoutDirection) { 330 int absGravity = getAbsoluteGravity(gravity, layoutDirection); 331 apply(absGravity, w, h, container, xAdj, yAdj, outRect); 332 } 333 334 /** 335 * Apply additional gravity behavior based on the overall "display" that an 336 * object exists in. This can be used after 337 * {@link #apply(int, int, int, Rect, int, int, Rect)} to place the object 338 * within a visible display. By default this moves or clips the object 339 * to be visible in the display; the gravity flags 340 * {@link #DISPLAY_CLIP_HORIZONTAL} and {@link #DISPLAY_CLIP_VERTICAL} 341 * can be used to change this behavior. 342 * 343 * @param gravity Gravity constants to modify the placement within the 344 * display. 345 * @param display The rectangle of the display in which the object is 346 * being placed. 347 * @param inoutObj Supplies the current object position; returns with it 348 * modified if needed to fit in the display. 349 */ applyDisplay(int gravity, @NonNull Rect display, @NonNull Rect inoutObj)350 public static void applyDisplay(int gravity, @NonNull Rect display, @NonNull Rect inoutObj) { 351 if ((gravity&DISPLAY_CLIP_VERTICAL) != 0) { 352 if (inoutObj.top < display.top) inoutObj.top = display.top; 353 if (inoutObj.bottom > display.bottom) inoutObj.bottom = display.bottom; 354 } else { 355 int off = 0; 356 if (inoutObj.top < display.top) off = display.top-inoutObj.top; 357 else if (inoutObj.bottom > display.bottom) off = display.bottom-inoutObj.bottom; 358 if (off != 0) { 359 if (inoutObj.height() > (display.bottom-display.top)) { 360 inoutObj.top = display.top; 361 inoutObj.bottom = display.bottom; 362 } else { 363 inoutObj.top += off; 364 inoutObj.bottom += off; 365 } 366 } 367 } 368 369 if ((gravity&DISPLAY_CLIP_HORIZONTAL) != 0) { 370 if (inoutObj.left < display.left) inoutObj.left = display.left; 371 if (inoutObj.right > display.right) inoutObj.right = display.right; 372 } else { 373 int off = 0; 374 if (inoutObj.left < display.left) off = display.left-inoutObj.left; 375 else if (inoutObj.right > display.right) off = display.right-inoutObj.right; 376 if (off != 0) { 377 if (inoutObj.width() > (display.right-display.left)) { 378 inoutObj.left = display.left; 379 inoutObj.right = display.right; 380 } else { 381 inoutObj.left += off; 382 inoutObj.right += off; 383 } 384 } 385 } 386 } 387 388 /** 389 * Apply additional gravity behavior based on the overall "display" that an 390 * object exists in. This can be used after 391 * {@link #apply(int, int, int, Rect, int, int, Rect)} to place the object 392 * within a visible display. By default this moves or clips the object 393 * to be visible in the display; the gravity flags 394 * {@link #DISPLAY_CLIP_HORIZONTAL} and {@link #DISPLAY_CLIP_VERTICAL} 395 * can be used to change this behavior. 396 * 397 * @param gravity Gravity constants to modify the placement within the 398 * display. 399 * @param display The rectangle of the display in which the object is 400 * being placed. 401 * @param inoutObj Supplies the current object position; returns with it 402 * modified if needed to fit in the display. 403 * @param layoutDirection The layout direction. 404 * 405 * @see View#LAYOUT_DIRECTION_LTR 406 * @see View#LAYOUT_DIRECTION_RTL 407 */ applyDisplay(int gravity, @NonNull Rect display, @NonNull Rect inoutObj, int layoutDirection)408 public static void applyDisplay(int gravity, @NonNull Rect display, @NonNull Rect inoutObj, 409 int layoutDirection) { 410 int absGravity = getAbsoluteGravity(gravity, layoutDirection); 411 applyDisplay(absGravity, display, inoutObj); 412 } 413 414 /** 415 * <p>Indicate whether the supplied gravity has a vertical pull.</p> 416 * 417 * @param gravity the gravity to check for vertical pull 418 * @return true if the supplied gravity has a vertical pull 419 */ isVertical(int gravity)420 public static boolean isVertical(int gravity) { 421 return gravity > 0 && (gravity & VERTICAL_GRAVITY_MASK) != 0; 422 } 423 424 /** 425 * <p>Indicate whether the supplied gravity has an horizontal pull.</p> 426 * 427 * @param gravity the gravity to check for horizontal pull 428 * @return true if the supplied gravity has an horizontal pull 429 */ isHorizontal(int gravity)430 public static boolean isHorizontal(int gravity) { 431 return gravity > 0 && (gravity & RELATIVE_HORIZONTAL_GRAVITY_MASK) != 0; 432 } 433 434 /** 435 * <p>Convert script specific gravity to absolute horizontal value.</p> 436 * 437 * if horizontal direction is LTR, then START will set LEFT and END will set RIGHT. 438 * if horizontal direction is RTL, then START will set RIGHT and END will set LEFT. 439 * 440 * 441 * @param gravity The gravity to convert to absolute (horizontal) values. 442 * @param layoutDirection The layout direction. 443 * @return gravity converted to absolute (horizontal) values. 444 */ getAbsoluteGravity(int gravity, int layoutDirection)445 public static int getAbsoluteGravity(int gravity, int layoutDirection) { 446 int result = gravity; 447 // If layout is script specific and gravity is horizontal relative (START or END) 448 if ((result & RELATIVE_LAYOUT_DIRECTION) > 0) { 449 if ((result & Gravity.START) == Gravity.START) { 450 // Remove the START bit 451 result &= ~START; 452 if (layoutDirection == View.LAYOUT_DIRECTION_RTL) { 453 // Set the RIGHT bit 454 result |= RIGHT; 455 } else { 456 // Set the LEFT bit 457 result |= LEFT; 458 } 459 } else if ((result & Gravity.END) == Gravity.END) { 460 // Remove the END bit 461 result &= ~END; 462 if (layoutDirection == View.LAYOUT_DIRECTION_RTL) { 463 // Set the LEFT bit 464 result |= LEFT; 465 } else { 466 // Set the RIGHT bit 467 result |= RIGHT; 468 } 469 } 470 // Don't need the script specific bit any more, so remove it as we are converting to 471 // absolute values (LEFT or RIGHT) 472 result &= ~RELATIVE_LAYOUT_DIRECTION; 473 } 474 return result; 475 } 476 477 /** 478 * @hide 479 */ toString(int gravity)480 public static String toString(int gravity) { 481 final StringBuilder result = new StringBuilder(); 482 if ((gravity & FILL) == FILL) { 483 result.append("FILL").append(' '); 484 } else { 485 if ((gravity & FILL_VERTICAL) == FILL_VERTICAL) { 486 result.append("FILL_VERTICAL").append(' '); 487 } else { 488 if ((gravity & TOP) == TOP) { 489 result.append("TOP").append(' '); 490 } 491 if ((gravity & BOTTOM) == BOTTOM) { 492 result.append("BOTTOM").append(' '); 493 } 494 } 495 if ((gravity & FILL_HORIZONTAL) == FILL_HORIZONTAL) { 496 result.append("FILL_HORIZONTAL").append(' '); 497 } else { 498 if ((gravity & START) == START) { 499 result.append("START").append(' '); 500 } else if ((gravity & LEFT) == LEFT) { 501 result.append("LEFT").append(' '); 502 } 503 if ((gravity & END) == END) { 504 result.append("END").append(' '); 505 } else if ((gravity & RIGHT) == RIGHT) { 506 result.append("RIGHT").append(' '); 507 } 508 } 509 } 510 if ((gravity & CENTER) == CENTER) { 511 result.append("CENTER").append(' '); 512 } else { 513 if ((gravity & CENTER_VERTICAL) == CENTER_VERTICAL) { 514 result.append("CENTER_VERTICAL").append(' '); 515 } 516 if ((gravity & CENTER_HORIZONTAL) == CENTER_HORIZONTAL) { 517 result.append("CENTER_HORIZONTAL").append(' '); 518 } 519 } 520 if (result.length() == 0) { 521 result.append("NO GRAVITY").append(' '); 522 } 523 if ((gravity & DISPLAY_CLIP_VERTICAL) == DISPLAY_CLIP_VERTICAL) { 524 result.append("DISPLAY_CLIP_VERTICAL").append(' '); 525 } 526 if ((gravity & DISPLAY_CLIP_HORIZONTAL) == DISPLAY_CLIP_HORIZONTAL) { 527 result.append("DISPLAY_CLIP_HORIZONTAL").append(' '); 528 } 529 result.deleteCharAt(result.length() - 1); 530 return result.toString(); 531 } 532 } 533