• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.support.wearable.view;
18 
19 import com.android.cts.verifier.R;
20 
21 import android.content.Context;
22 import android.content.res.TypedArray;
23 import android.graphics.Rect;
24 import android.graphics.drawable.Drawable;
25 import android.util.AttributeSet;
26 import android.view.Gravity;
27 import android.view.View;
28 import android.view.ViewGroup;
29 import android.view.WindowInsets;
30 import android.widget.FrameLayout;
31 
32 /**
33  * BoxInsetLayout is a screen shape-aware FrameLayout that can box its children
34  * in the center square of a round screen by using the
35  * {@code layout_box} attribute. The values for this attribute specify the
36  * child's edges to be boxed in:
37  * {@code left|top|right|bottom} or {@code all}.
38  * The {@code layout_box} attribute is ignored on a device with a rectangular
39  * screen.
40  */
41 public class BoxInsetLayout extends FrameLayout {
42 
43     private static float FACTOR = 0.146467f; //(1 - sqrt(2)/2)/2
44     private static final int DEFAULT_CHILD_GRAVITY = Gravity.TOP | Gravity.START;
45 
46     private Rect mForegroundPadding;
47     private boolean mLastKnownRound;
48     private Rect mInsets;
49 
BoxInsetLayout(Context context)50     public BoxInsetLayout(Context context) {
51         this(context, null);
52     }
53 
BoxInsetLayout(Context context, AttributeSet attrs)54     public BoxInsetLayout(Context context, AttributeSet attrs) {
55         this(context, attrs, 0);
56     }
57 
BoxInsetLayout(Context context, AttributeSet attrs, int defStyle)58     public BoxInsetLayout(Context context, AttributeSet attrs, int defStyle) {
59         super(context, attrs, defStyle);
60         // make sure we have foreground padding object
61         if (mForegroundPadding == null) {
62             mForegroundPadding = new Rect();
63         }
64         if (mInsets == null) {
65             mInsets = new Rect();
66         }
67     }
68 
69     @Override
onAttachedToWindow()70     protected void onAttachedToWindow() {
71         super.onAttachedToWindow();
72         requestApplyInsets();
73     }
74 
75     @Override
onApplyWindowInsets(WindowInsets insets)76     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
77         insets = super.onApplyWindowInsets(insets);
78         final boolean round = insets.isRound();
79         if (round != mLastKnownRound) {
80             mLastKnownRound = round;
81             requestLayout();
82         }
83         mInsets.set(
84                 insets.getSystemWindowInsetLeft(),
85                 insets.getSystemWindowInsetTop(),
86                 insets.getSystemWindowInsetRight(),
87                 insets.getSystemWindowInsetBottom());
88         return insets;
89     }
90 
91     /**
92      * determine screen shape
93      * @return true if on a round screen
94      */
isRound()95     public boolean isRound() {
96         return mLastKnownRound;
97     }
98 
99     /**
100      * @return the system window insets Rect
101      */
getInsets()102     public Rect getInsets() {
103         return mInsets;
104     }
105 
106     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)107     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
108         int count = getChildCount();
109         // find max size
110         int maxWidth = 0;
111         int maxHeight = 0;
112         int childState = 0;
113         for (int i = 0; i < count; i++) {
114             final View child = getChildAt(i);
115             if (child.getVisibility() != GONE) {
116                 LayoutParams lp = (BoxInsetLayout.LayoutParams) child.getLayoutParams();
117                 int marginLeft = 0;
118                 int marginRight = 0;
119                 int marginTop = 0;
120                 int marginBottom = 0;
121                 if (mLastKnownRound) {
122                     // round screen, check boxed, don't use margins on boxed
123                     if ((lp.boxedEdges & LayoutParams.BOX_LEFT) == 0) {
124                         marginLeft = lp.leftMargin;
125                     }
126                     if ((lp.boxedEdges & LayoutParams.BOX_RIGHT) == 0) {
127                         marginRight = lp.rightMargin;
128                     }
129                     if ((lp.boxedEdges & LayoutParams.BOX_TOP) == 0) {
130                         marginTop = lp.topMargin;
131                     }
132                     if ((lp.boxedEdges & LayoutParams.BOX_BOTTOM) == 0) {
133                         marginBottom = lp.bottomMargin;
134                     }
135                 } else {
136                     // rectangular, ignore boxed, use margins
137                     marginLeft = lp.leftMargin;
138                     marginTop = lp.topMargin;
139                     marginRight = lp.rightMargin;
140                     marginBottom = lp.bottomMargin;
141                 }
142                 measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
143                 maxWidth = Math.max(maxWidth,
144                         child.getMeasuredWidth() + marginLeft + marginRight);
145                 maxHeight = Math.max(maxHeight,
146                         child.getMeasuredHeight() + marginTop + marginBottom);
147                 childState = combineMeasuredStates(childState, child.getMeasuredState());
148             }
149         }
150         // Account for padding too
151         maxWidth += getPaddingLeft() + mForegroundPadding.left
152                 + getPaddingRight() + mForegroundPadding.right;
153         maxHeight += getPaddingTop() + mForegroundPadding.top
154                 + getPaddingBottom() + mForegroundPadding.bottom;
155 
156         // Check against our minimum height and width
157         maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
158         maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
159 
160         // Check against our foreground's minimum height and width
161         final Drawable drawable = getForeground();
162         if (drawable != null) {
163             maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
164             maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
165         }
166 
167         setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
168                 resolveSizeAndState(maxHeight, heightMeasureSpec,
169                         childState << MEASURED_HEIGHT_STATE_SHIFT));
170 
171         // determine boxed inset
172         int boxInset = (int) (FACTOR * Math.max(getMeasuredWidth(), getMeasuredHeight()));
173         // adjust the match parent children
174         for (int i = 0; i < count; i++) {
175             final View child = getChildAt(i);
176 
177             final LayoutParams lp = (BoxInsetLayout.LayoutParams) child.getLayoutParams();
178             int childWidthMeasureSpec;
179             int childHeightMeasureSpec;
180             int plwf = getPaddingLeft() + mForegroundPadding.left;
181             int prwf = getPaddingRight() + mForegroundPadding.right;
182             int ptwf = getPaddingTop() + mForegroundPadding.top;
183             int pbwf = getPaddingBottom() + mForegroundPadding.bottom;
184 
185             // adjust width
186             int totalPadding = 0;
187             int totalMargin = 0;
188             // BoxInset is a padding. Ignore margin when we want to do BoxInset.
189             if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_LEFT) != 0)) {
190                 totalPadding = boxInset;
191             } else {
192                 totalMargin = plwf + lp.leftMargin;
193             }
194             if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_RIGHT) != 0)) {
195                 totalPadding += boxInset;
196             } else {
197                 totalMargin += prwf + lp.rightMargin;
198             }
199             if (lp.width == LayoutParams.MATCH_PARENT) {
200                 //  Only subtract margin from the actual width, leave the padding in.
201                 childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
202                         getMeasuredWidth() - totalMargin, MeasureSpec.EXACTLY);
203             } else {
204                 childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
205                         totalPadding + totalMargin, lp.width);
206             }
207 
208             // adjust height
209             if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_TOP) != 0)) {
210                 totalPadding = boxInset;
211             } else {
212                 totalMargin = ptwf + lp.topMargin;
213             }
214             if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_BOTTOM) != 0)) {
215                 totalPadding += boxInset;
216             } else {
217                 totalMargin += pbwf + lp.bottomMargin;
218             }
219 
220             if (lp.height == LayoutParams.MATCH_PARENT) {
221                 childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
222                         getMeasuredHeight() - totalMargin, MeasureSpec.EXACTLY);
223             } else {
224                 childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
225                         totalPadding + totalMargin, lp.height);
226             }
227 
228             child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
229         }
230     }
231 
232 
233     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)234     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
235         layoutBoxChildren(left, top, right, bottom, false /* no force left gravity */);
236     }
237 
layoutBoxChildren(int left, int top, int right, int bottom, boolean forceLeftGravity)238     private void layoutBoxChildren(int left, int top, int right, int bottom,
239             boolean forceLeftGravity) {
240         final int count = getChildCount();
241         int boxInset = (int)(FACTOR * Math.max(right - left, bottom - top));
242 
243         final int parentLeft = getPaddingLeft() + mForegroundPadding.left;
244         final int parentRight = right - left - getPaddingRight() - mForegroundPadding.right;
245 
246         final int parentTop = getPaddingTop() + mForegroundPadding.top;
247         final int parentBottom = bottom - top - getPaddingBottom() - mForegroundPadding.bottom;
248 
249         for (int i = 0; i < count; i++) {
250             final View child = getChildAt(i);
251             if (child.getVisibility() != GONE) {
252                 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
253 
254                 final int width = child.getMeasuredWidth();
255                 final int height = child.getMeasuredHeight();
256 
257                 int childLeft;
258                 int childTop;
259 
260                 int gravity = lp.gravity;
261                 if (gravity == -1) {
262                     gravity = DEFAULT_CHILD_GRAVITY;
263                 }
264 
265                 final int layoutDirection = getLayoutDirection();
266                 final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
267                 final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
268 
269                 // These values are replaced with boxInset below as necessary.
270                 int paddingLeft = child.getPaddingLeft();
271                 int paddingRight = child.getPaddingRight();
272                 int paddingTop = child.getPaddingTop();
273                 int paddingBottom = child.getPaddingBottom();
274 
275                 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
276                     case Gravity.CENTER_HORIZONTAL:
277                         childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
278                                 lp.leftMargin - lp.rightMargin;
279                         break;
280                     case Gravity.RIGHT:
281                         if (!forceLeftGravity) {
282                             if (mLastKnownRound
283                                     && ((lp.boxedEdges & LayoutParams.BOX_RIGHT) != 0)) {
284                                 paddingRight = boxInset;
285                                 childLeft = right - left - width;
286                             } else {
287                                 childLeft = parentRight - width - lp.rightMargin;
288                             }
289                             break;
290                         }
291                     case Gravity.LEFT:
292                     default:
293                         if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_LEFT) != 0)) {
294                             paddingLeft = boxInset;
295                             childLeft = 0;
296                         } else {
297                             childLeft = parentLeft + lp.leftMargin;
298                         }
299                 }
300 
301                 switch (verticalGravity) {
302                     case Gravity.TOP:
303                         if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_TOP) != 0)) {
304                             paddingTop = boxInset;
305                             childTop = 0;
306                         } else {
307                             childTop = parentTop + lp.topMargin;
308                         }
309                         break;
310                     case Gravity.CENTER_VERTICAL:
311                         childTop = parentTop + (parentBottom - parentTop - height) / 2 +
312                                 lp.topMargin - lp.bottomMargin;
313                         break;
314                     case Gravity.BOTTOM:
315                         if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_BOTTOM) != 0)) {
316                             paddingBottom = boxInset;
317                             childTop = bottom - top - height;
318                         } else {
319                             childTop = parentBottom - height - lp.bottomMargin;
320                         }
321                         break;
322                     default:
323                         childTop = parentTop + lp.topMargin;
324                 }
325 
326                 child.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
327                 child.layout(childLeft, childTop, childLeft + width, childTop + height);
328             }
329         }
330     }
331 
setForeground(Drawable drawable)332     public void setForeground(Drawable drawable) {
333         super.setForeground(drawable);
334         if (mForegroundPadding == null) {
335             mForegroundPadding = new Rect();
336         }
337         drawable.getPadding(mForegroundPadding);
338     }
339 
340     @Override
checkLayoutParams(ViewGroup.LayoutParams p)341     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
342         return p instanceof LayoutParams;
343     }
344 
345     @Override
generateLayoutParams(ViewGroup.LayoutParams p)346     protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
347         return new LayoutParams(p);
348     }
349 
350     @Override
generateLayoutParams(AttributeSet attrs)351     public LayoutParams generateLayoutParams(AttributeSet attrs) {
352         return new BoxInsetLayout.LayoutParams(getContext(), attrs);
353     }
354 
355     /**
356      * adds {@code layout_box} attribute to layout parameters
357      */
358     public static class LayoutParams extends FrameLayout.LayoutParams {
359 
360         public static final int BOX_NONE = 0x0;
361         public static final int BOX_LEFT = 0x01;
362         public static final int BOX_TOP = 0x02;
363         public static final int BOX_RIGHT = 0x04;
364         public static final int BOX_BOTTOM = 0x08;
365         public static final int BOX_ALL = 0x0F;
366 
367         public int boxedEdges = BOX_NONE;
368 
LayoutParams(Context context, AttributeSet attrs)369         public LayoutParams(Context context, AttributeSet attrs) {
370             super(context, attrs);
371             TypedArray a = context.obtainStyledAttributes(attrs,  R.styleable.BoxInsetLayout_Layout, 0, 0);
372             boxedEdges = a.getInt(R.styleable.BoxInsetLayout_Layout_layout_box, BOX_NONE);
373             a.recycle();
374         }
375 
LayoutParams(int width, int height)376         public LayoutParams(int width, int height) {
377             super(width, height);
378         }
379 
LayoutParams(int width, int height, int gravity)380         public LayoutParams(int width, int height, int gravity) {
381             super(width, height, gravity);
382         }
383 
LayoutParams(int width, int height, int gravity, int boxed)384         public LayoutParams(int width, int height, int gravity, int boxed) {
385             super(width, height, gravity);
386             boxedEdges = boxed;
387         }
388 
LayoutParams(ViewGroup.LayoutParams source)389         public LayoutParams(ViewGroup.LayoutParams source) {
390             super(source);
391         }
392 
LayoutParams(ViewGroup.MarginLayoutParams source)393         public LayoutParams(ViewGroup.MarginLayoutParams source) {
394             super(source);
395         }
396 
LayoutParams(FrameLayout.LayoutParams source)397         public LayoutParams(FrameLayout.LayoutParams source) {
398             super(source);
399         }
400 
LayoutParams(LayoutParams source)401         public LayoutParams(LayoutParams source) {
402             super(source);
403             this.boxedEdges = source.boxedEdges;
404             this.gravity = source.gravity;
405         }
406 
407     }
408 }
409