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