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