java.lang.Object
↳ViewGroup
↳androidx.wear.widget.BoxInsetLayout
Gradle dependencies
compile group: 'androidx.wear', name: 'wear', version: '1.3.0-alpha02'
- groupId: androidx.wear
- artifactId: wear
- version: 1.3.0-alpha02
Artifact androidx.wear:wear:1.3.0-alpha02 it located at Google repository (https://maven.google.com/)
Androidx artifact mapping:
androidx.wear:wear com.android.support:wear
Androidx class mapping:
androidx.wear.widget.BoxInsetLayout android.support.wear.widget.BoxInsetLayout
Overview
BoxInsetLayout is a screen shape-aware ViewGroup that can box its children in the center
square of a round screen by using the layout_boxedEdges attribute. The values for this attribute
specify the child's edges to be boxed in: left|top|right|bottom or all. The
layout_boxedEdges attribute is ignored on a device with a rectangular screen.
Summary
Constructors |
---|
public | BoxInsetLayout(Context context)
Simple constructor to use when creating a view from code. |
public | BoxInsetLayout(Context context, AttributeSet attrs)
Constructor that is called when inflating a view from XML. |
public | BoxInsetLayout(Context context, AttributeSet attrs, int defStyle)
Perform inflation from XML and apply a class-specific base style from a theme attribute. |
Methods |
---|
protected boolean | checkLayoutParams(ViewGroup.LayoutParams p)
|
public BoxInsetLayout.LayoutParams | generateLayoutParams(AttributeSet attrs)
|
protected ViewGroup.LayoutParams | generateLayoutParams(ViewGroup.LayoutParams p)
|
protected void | onAttachedToWindow()
|
protected void | onLayout(boolean changed, int left, int top, int right, int bottom)
|
protected void | onMeasure(int widthMeasureSpec, int heightMeasureSpec)
|
public void | setForeground(Drawable drawable)
|
from java.lang.Object | clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait |
Constructors
public
BoxInsetLayout(Context context)
Simple constructor to use when creating a view from code.
Parameters:
context: The the view is running in, through which it can access
the current theme, resources, etc.
public
BoxInsetLayout(Context context, AttributeSet attrs)
Constructor that is called when inflating a view from XML. This is called when a view is
being constructed from an XML file, supplying attributes that were specified in the XML
file. This version uses a default style of 0, so the only attribute values applied are those
in the Context's Theme and the given AttributeSet.
The method onFinishInflate() will be called after all children have been added.
Parameters:
context: The the view is running in, through which it can access
the current theme, resources, etc.
attrs: The attributes of the XML tag that is inflating the view.
public
BoxInsetLayout(Context context, AttributeSet attrs, int defStyle)
Perform inflation from XML and apply a class-specific base style from a theme attribute.
This constructor allows subclasses to use their own base style when they are inflating.
Parameters:
context: The the view is running in, through which it can
access the current theme, resources, etc.
attrs: The attributes of the XML tag that is inflating the view.
defStyle: An attribute in the current theme that contains a reference to a style
resource that supplies default values for the view. Can be 0 to not look for
defaults.
Methods
public void
setForeground(Drawable drawable)
protected void
onAttachedToWindow()
protected void
onMeasure(int widthMeasureSpec, int heightMeasureSpec)
protected void
onLayout(boolean changed, int left, int top, int right, int bottom)
protected boolean
checkLayoutParams(ViewGroup.LayoutParams p)
protected ViewGroup.LayoutParams
generateLayoutParams(ViewGroup.LayoutParams p)
Source
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.wear.widget;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.widget.FrameLayout;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.annotation.StyleRes;
import androidx.annotation.UiThread;
import androidx.wear.R;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* BoxInsetLayout is a screen shape-aware ViewGroup that can box its children in the center
* square of a round screen by using the {@code layout_boxedEdges} attribute. The values for this attribute
* specify the child's edges to be boxed in: {@code left|top|right|bottom} or {@code all}. The
* {@code layout_boxedEdges} attribute is ignored on a device with a rectangular screen.
*/
@UiThread
public class BoxInsetLayout extends ViewGroup {
private static final float FACTOR = 0.146447f; //(1 - sqrt(2)/2)/2
private static final int DEFAULT_CHILD_GRAVITY = Gravity.TOP | Gravity.START;
private final int mScreenHeight;
private final int mScreenWidth;
private boolean mIsRound;
private Rect mForegroundPadding;
private Rect mInsets;
private Drawable mForegroundDrawable;
/**
* Simple constructor to use when creating a view from code.
*
* @param context The {@link Context} the view is running in, through which it can access
* the current theme, resources, etc.
*/
public BoxInsetLayout(@NonNull Context context) {
this(context, null);
}
/**
* Constructor that is called when inflating a view from XML. This is called when a view is
* being constructed from an XML file, supplying attributes that were specified in the XML
* file. This version uses a default style of 0, so the only attribute values applied are those
* in the Context's Theme and the given AttributeSet.
* <p>
* <p>
* The method onFinishInflate() will be called after all children have been added.
*
* @param context The {@link Context} the view is running in, through which it can access
* the current theme, resources, etc.
* @param attrs The attributes of the XML tag that is inflating the view.
*/
public BoxInsetLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
/**
* Perform inflation from XML and apply a class-specific base style from a theme attribute.
* This constructor allows subclasses to use their own base style when they are inflating.
*
* @param context The {@link Context} the view is running in, through which it can
* access the current theme, resources, etc.
* @param attrs The attributes of the XML tag that is inflating the view.
* @param defStyle An attribute in the current theme that contains a reference to a style
* resource that supplies default values for the view. Can be 0 to not look for
* defaults.
*/
public BoxInsetLayout(@NonNull Context context, @Nullable AttributeSet attrs, @StyleRes int
defStyle) {
super(context, attrs, defStyle);
// make sure we have a foreground padding object
if (mForegroundPadding == null) {
mForegroundPadding = new Rect();
}
if (mInsets == null) {
mInsets = new Rect();
}
mScreenHeight = Resources.getSystem().getDisplayMetrics().heightPixels;
mScreenWidth = Resources.getSystem().getDisplayMetrics().widthPixels;
}
@Override
public void setForeground(Drawable drawable) {
super.setForeground(drawable);
mForegroundDrawable = drawable;
if (mForegroundPadding == null) {
mForegroundPadding = new Rect();
}
if (mForegroundDrawable != null) {
drawable.getPadding(mForegroundPadding);
}
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new BoxInsetLayout.LayoutParams(getContext(), attrs);
}
@Override
@SuppressWarnings("deprecation") /* getSystemWindowInsetXXXX */
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mIsRound = getResources().getConfiguration().isScreenRound();
WindowInsets insets = getRootWindowInsets();
mInsets.set(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(),
insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom());
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
// find max size
int maxWidth = 0;
int maxHeight = 0;
int childState = 0;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
LayoutParams lp = (BoxInsetLayout.LayoutParams) child.getLayoutParams();
int marginLeft = 0;
int marginRight = 0;
int marginTop = 0;
int marginBottom = 0;
if (mIsRound) {
// round screen, check boxed, don't use margins on boxed
if ((lp.boxedEdges & LayoutParams.BOX_LEFT) == 0) {
marginLeft = lp.leftMargin;
}
if ((lp.boxedEdges & LayoutParams.BOX_RIGHT) == 0) {
marginRight = lp.rightMargin;
}
if ((lp.boxedEdges & LayoutParams.BOX_TOP) == 0) {
marginTop = lp.topMargin;
}
if ((lp.boxedEdges & LayoutParams.BOX_BOTTOM) == 0) {
marginBottom = lp.bottomMargin;
}
} else {
// rectangular, ignore boxed, use margins
marginLeft = lp.leftMargin;
marginTop = lp.topMargin;
marginRight = lp.rightMargin;
marginBottom = lp.bottomMargin;
}
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
maxWidth = Math.max(maxWidth, child.getMeasuredWidth() + marginLeft + marginRight);
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + marginTop + marginBottom);
childState = combineMeasuredStates(childState, child.getMeasuredState());
}
}
// Account for padding too
maxWidth += getPaddingLeft() + mForegroundPadding.left + getPaddingRight()
+ mForegroundPadding.right;
maxHeight += getPaddingTop() + mForegroundPadding.top + getPaddingBottom()
+ mForegroundPadding.bottom;
// Check against our minimum height and width
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// Check against our foreground's minimum height and width
if (mForegroundDrawable != null) {
maxHeight = Math.max(maxHeight, mForegroundDrawable.getMinimumHeight());
maxWidth = Math.max(maxWidth, mForegroundDrawable.getMinimumWidth());
}
int measuredWidth = resolveSizeAndState(maxWidth, widthMeasureSpec, childState);
int measuredHeight = resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT);
setMeasuredDimension(measuredWidth, measuredHeight);
// determine boxed inset
int boxInset = calculateInset(measuredWidth, measuredHeight);
// adjust the the children measures, if necessary
for (int i = 0; i < count; i++) {
measureChild(widthMeasureSpec, heightMeasureSpec, boxInset, i);
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
final int count = getChildCount();
final int parentLeft = getPaddingLeft() + mForegroundPadding.left;
final int parentRight = right - left - getPaddingRight() - mForegroundPadding.right;
final int parentTop = getPaddingTop() + mForegroundPadding.top;
final int parentBottom = bottom - top - getPaddingBottom() - mForegroundPadding.bottom;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
int childLeft;
int childTop;
int gravity = lp.gravity;
if (gravity == -1) {
gravity = DEFAULT_CHILD_GRAVITY;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
final int horizontalGravity = gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
int desiredInset = calculateInset(getMeasuredWidth(), getMeasuredHeight());
// If the child's width is match_parent then we can ignore gravity.
int leftChildMargin = calculateChildLeftMargin(lp, horizontalGravity, desiredInset);
int rightChildMargin = calculateChildRightMargin(lp, horizontalGravity,
desiredInset);
if (lp.width == LayoutParams.MATCH_PARENT) {
childLeft = parentLeft + leftChildMargin;
} else {
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = parentLeft + (parentRight - parentLeft - width) / 2
+ leftChildMargin - rightChildMargin;
break;
case Gravity.RIGHT:
childLeft = parentRight - width - rightChildMargin;
break;
case Gravity.LEFT:
default:
childLeft = parentLeft + leftChildMargin;
}
}
// If the child's height is match_parent then we can ignore gravity.
int topChildMargin = calculateChildTopMargin(lp, verticalGravity, desiredInset);
int bottomChildMargin = calculateChildBottomMargin(lp, verticalGravity,
desiredInset);
if (lp.height == LayoutParams.MATCH_PARENT) {
childTop = parentTop + topChildMargin;
} else {
switch (verticalGravity) {
case Gravity.CENTER_VERTICAL:
childTop = parentTop + (parentBottom - parentTop - height) / 2
+ topChildMargin - bottomChildMargin;
break;
case Gravity.BOTTOM:
childTop = parentBottom - height - bottomChildMargin;
break;
case Gravity.TOP:
default:
childTop = parentTop + topChildMargin;
}
}
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof LayoutParams;
}
@Override
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return new LayoutParams(p);
}
private void measureChild(int widthMeasureSpec, int heightMeasureSpec, int desiredMinInset,
int i) {
final View child = getChildAt(i);
final LayoutParams childLayoutParams = (LayoutParams) child.getLayoutParams();
int gravity = childLayoutParams.gravity;
if (gravity == -1) {
gravity = DEFAULT_CHILD_GRAVITY;
}
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
final int horizontalGravity = gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
int childWidthMeasureSpec;
int childHeightMeasureSpec;
int leftParentPadding = getPaddingLeft() + mForegroundPadding.left;
int rightParentPadding = getPaddingRight() + mForegroundPadding.right;
int topParentPadding = getPaddingTop() + mForegroundPadding.top;
int bottomParentPadding = getPaddingBottom() + mForegroundPadding.bottom;
// adjust width
int totalWidthMargin = leftParentPadding + rightParentPadding + calculateChildLeftMargin(
childLayoutParams, horizontalGravity, desiredMinInset) + calculateChildRightMargin(
childLayoutParams, horizontalGravity, desiredMinInset);
// adjust height
int totalHeightMargin = topParentPadding + bottomParentPadding + calculateChildTopMargin(
childLayoutParams, verticalGravity, desiredMinInset) + calculateChildBottomMargin(
childLayoutParams, verticalGravity, desiredMinInset);
childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, totalWidthMargin,
childLayoutParams.width);
childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, totalHeightMargin,
childLayoutParams.height);
int maxAllowedWidth = getMeasuredWidth() - totalWidthMargin;
int maxAllowedHeight = getMeasuredHeight() - totalHeightMargin;
if (child.getMeasuredWidth() > maxAllowedWidth
|| child.getMeasuredHeight() > maxAllowedHeight) {
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
private int calculateChildLeftMargin(LayoutParams lp, int horizontalGravity, int
desiredMinInset) {
if (mIsRound && ((lp.boxedEdges & LayoutParams.BOX_LEFT) != 0)) {
if (lp.width == LayoutParams.MATCH_PARENT || horizontalGravity == Gravity.LEFT) {
return lp.leftMargin + desiredMinInset;
}
}
return lp.leftMargin;
}
private int calculateChildRightMargin(LayoutParams lp, int horizontalGravity, int
desiredMinInset) {
if (mIsRound && ((lp.boxedEdges & LayoutParams.BOX_RIGHT) != 0)) {
if (lp.width == LayoutParams.MATCH_PARENT || horizontalGravity == Gravity.RIGHT) {
return lp.rightMargin + desiredMinInset;
}
}
return lp.rightMargin;
}
private int calculateChildTopMargin(LayoutParams lp, int verticalGravity, int desiredMinInset) {
if (mIsRound && ((lp.boxedEdges & LayoutParams.BOX_TOP) != 0)) {
if (lp.height == LayoutParams.MATCH_PARENT || verticalGravity == Gravity.TOP) {
return lp.topMargin + desiredMinInset;
}
}
return lp.topMargin;
}
private int calculateChildBottomMargin(LayoutParams lp, int verticalGravity, int
desiredMinInset) {
if (mIsRound && ((lp.boxedEdges & LayoutParams.BOX_BOTTOM) != 0)) {
if (lp.height == LayoutParams.MATCH_PARENT || verticalGravity == Gravity.BOTTOM) {
return lp.bottomMargin + desiredMinInset;
}
}
return lp.bottomMargin;
}
private int calculateInset(int measuredWidth, int measuredHeight) {
int rightEdge = Math.min(measuredWidth, mScreenWidth);
int bottomEdge = Math.min(measuredHeight, mScreenHeight);
return (int) (FACTOR * Math.max(rightEdge, bottomEdge));
}
/**
* Per-child layout information for layouts that support margins, gravity and boxedEdges.
* See {@link R.styleable#BoxInsetLayout_Layout BoxInsetLayout Layout Attributes} for a list
* of all child view attributes that this class supports.
*
* {@link androidx.wear.R.attr#layout_boxedEdges}
*/
public static class LayoutParams extends FrameLayout.LayoutParams {
/** @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY)
@IntDef({BOX_NONE, BOX_LEFT, BOX_TOP, BOX_RIGHT, BOX_BOTTOM, BOX_ALL})
@Retention(RetentionPolicy.SOURCE)
public @interface BoxedEdges {}
/** Default boxing setting. There are no insets forced on the child views. */
public static final int BOX_NONE = 0x0;
/** The view will force an inset on the left edge of the children. */
public static final int BOX_LEFT = 0x01;
/** The view will force an inset on the top edge of the children. */
public static final int BOX_TOP = 0x02;
/** The view will force an inset on the right edge of the children. */
public static final int BOX_RIGHT = 0x04;
/** The view will force an inset on the bottom edge of the children. */
public static final int BOX_BOTTOM = 0x08;
/** The view will force an inset on all of the edges of the children. */
public static final int BOX_ALL = 0x0F;
/** Specifies the screen-specific insets for each of the child edges. */
@BoxedEdges
public int boxedEdges = BOX_NONE;
/**
* Creates a new set of layout parameters. The values are extracted from the supplied
* attributes set and context.
*
* @param context the application environment
* @param attrs the set of attributes from which to extract the layout parameters' values
*/
@SuppressWarnings("ResourceType")
public LayoutParams(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.BoxInsetLayout_Layout,
0, 0);
int boxedEdgesResourceKey = R.styleable.BoxInsetLayout_Layout_layout_boxedEdges;
if (!a.hasValueOrEmpty(R.styleable.BoxInsetLayout_Layout_layout_boxedEdges)){
boxedEdgesResourceKey = R.styleable.BoxInsetLayout_Layout_boxedEdges;
}
boxedEdges = a.getInt(boxedEdgesResourceKey, BOX_NONE);
a.recycle();
}
/**
* Creates a new set of layout parameters with the specified width and height.
*
* @param width the width, either {@link #MATCH_PARENT},
* {@link #WRAP_CONTENT} or a fixed size in pixels
* @param height the height, either {@link #MATCH_PARENT},
* {@link #WRAP_CONTENT} or a fixed size in pixelsy
*/
public LayoutParams(int width, int height) {
super(width, height);
}
/**
* Creates a new set of layout parameters with the specified width, height
* and gravity.
*
* @param width the width, either {@link #MATCH_PARENT},
* {@link #WRAP_CONTENT} or a fixed size in pixels
* @param height the height, either {@link #MATCH_PARENT},
* {@link #WRAP_CONTENT} or a fixed size in pixels
* @param gravity the gravity
*
* @see android.view.Gravity
*/
public LayoutParams(int width, int height, int gravity) {
super(width, height, gravity);
}
public LayoutParams(int width, int height, int gravity, @BoxedEdges int boxed) {
super(width, height, gravity);
boxedEdges = boxed;
}
/**
* Copy constructor. Clones the width and height of the source.
*
* @param source The layout params to copy from.
*/
public LayoutParams(@NonNull ViewGroup.LayoutParams source) {
super(source);
}
/**
* Copy constructor. Clones the width, height and margin values.
*
* @param source The layout params to copy from.
*/
public LayoutParams(@NonNull ViewGroup.MarginLayoutParams source) {
super(source);
}
/**
* Copy constructor. Clones the width, height, margin values, and
* gravity of the source.
*
* @param source The layout params to copy from.
*/
public LayoutParams(@NonNull FrameLayout.LayoutParams source) {
super(source);
}
/**
* Copy constructor. Clones the width, height, margin values, boxedEdges and
* gravity of the source.
*
* @param source The layout params to copy from.
*/
public LayoutParams(@NonNull LayoutParams source) {
super(source);
this.boxedEdges = source.boxedEdges;
this.gravity = source.gravity;
}
}
}