public class

Button

extends java.lang.Object

implements LayoutElementBuilders.LayoutElement

 java.lang.Object

↳androidx.wear.tiles.material.Button

Gradle dependencies

compile group: 'androidx.wear.tiles', name: 'tiles-material', version: '1.1.0-alpha07'

  • groupId: androidx.wear.tiles
  • artifactId: tiles-material
  • version: 1.1.0-alpha07

Artifact androidx.wear.tiles:tiles-material:1.1.0-alpha07 it located at Google repository (https://maven.google.com/)

Overview

Tiles component Button that represents clickable button with the given content.

The Button is circular in shape. The recommended sizes are defined in ButtonDefaults.

The recommended set of ButtonColors styles can be obtained from ButtonDefaults., e.g. ButtonDefaults.PRIMARY_BUTTON_COLORS to get a color scheme for a primary Button.

Summary

Methods
public ButtonColorsgetButtonColors()

Returns button color of this Button.

public ModifiersBuilders.ClickablegetClickable()

Returns click event action associated with this Button.

public LayoutElementBuilders.LayoutElementgetContent()

Returns the content of this Button.

public java.lang.CharSequencegetContentDescription()

Returns content description for this Button.

public DimensionBuilders.ContainerDimensiongetSize()

Returns size for this Button.

public LayoutElementProto.LayoutElementtoLayoutElementProto()

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Methods

Returns the content of this Button.

public ModifiersBuilders.Clickable getClickable()

Returns click event action associated with this Button.

public java.lang.CharSequence getContentDescription()

Returns content description for this Button.

Returns size for this Button.

public ButtonColors getButtonColors()

Returns button color of this Button.

public LayoutElementProto.LayoutElement toLayoutElementProto()

Source

/*
 * Copyright 2021 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.tiles.material;

import static androidx.annotation.Dimension.DP;
import static androidx.wear.tiles.DimensionBuilders.dp;
import static androidx.wear.tiles.LayoutElementBuilders.CONTENT_SCALE_MODE_FILL_BOUNDS;
import static androidx.wear.tiles.material.ButtonDefaults.DEFAULT_BUTTON_SIZE;
import static androidx.wear.tiles.material.ButtonDefaults.EXTRA_LARGE_BUTTON_SIZE;
import static androidx.wear.tiles.material.ButtonDefaults.LARGE_BUTTON_SIZE;
import static androidx.wear.tiles.material.ButtonDefaults.PRIMARY_BUTTON_COLORS;
import static androidx.wear.tiles.material.Helper.checkNotNull;
import static androidx.wear.tiles.material.Helper.radiusOf;

import android.content.Context;

import androidx.annotation.Dimension;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.annotation.RestrictTo.Scope;
import androidx.wear.tiles.ColorBuilders.ColorProp;
import androidx.wear.tiles.DimensionBuilders.ContainerDimension;
import androidx.wear.tiles.DimensionBuilders.DpProp;
import androidx.wear.tiles.LayoutElementBuilders;
import androidx.wear.tiles.LayoutElementBuilders.Box;
import androidx.wear.tiles.LayoutElementBuilders.ColorFilter;
import androidx.wear.tiles.LayoutElementBuilders.FontStyle;
import androidx.wear.tiles.LayoutElementBuilders.Image;
import androidx.wear.tiles.LayoutElementBuilders.LayoutElement;
import androidx.wear.tiles.ModifiersBuilders;
import androidx.wear.tiles.ModifiersBuilders.Background;
import androidx.wear.tiles.ModifiersBuilders.Clickable;
import androidx.wear.tiles.ModifiersBuilders.Corner;
import androidx.wear.tiles.ModifiersBuilders.Modifiers;
import androidx.wear.tiles.ModifiersBuilders.Semantics;
import androidx.wear.tiles.material.Typography.TypographyName;
import androidx.wear.tiles.proto.LayoutElementProto;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * Tiles component {@link Button} that represents clickable button with the given content.
 *
 * <p>The Button is circular in shape. The recommended sizes are defined in {@link ButtonDefaults}.
 *
 * <p>The recommended set of {@link ButtonColors} styles can be obtained from {@link
 * ButtonDefaults}., e.g. {@link ButtonDefaults#PRIMARY_BUTTON_COLORS} to get a color scheme for a
 * primary {@link Button}.
 */
public class Button implements LayoutElement {
    @NonNull private final Box mElement;

    Button(@NonNull Box element) {
        mElement = element;
    }

    /** Builder class for {@link Button}. */
    public static final class Builder implements LayoutElement.Builder {
        private static final int NOT_SET = -1;
        private static final int ICON = 0;
        private static final int TEXT = 1;
        private static final int IMAGE = 2;
        private static final int CUSTOM_CONTENT = 3;

        /** @hide */
        @RestrictTo(RestrictTo.Scope.LIBRARY)
        @Retention(RetentionPolicy.SOURCE)
        @IntDef({NOT_SET, ICON, TEXT, IMAGE, CUSTOM_CONTENT})
        @interface ButtonType {}

        @NonNull private final Context mContext;
        @Nullable private LayoutElement mCustomContent;
        @NonNull private final Clickable mClickable;
        @NonNull private CharSequence mContentDescription = "";
        @NonNull private DpProp mSize = DEFAULT_BUTTON_SIZE;
        @Nullable private String mText = null;
        @Nullable private Integer mTypographyName = null;
        @Nullable private String mIcon = null;
        @Nullable private DpProp mIconSize = null;
        @Nullable private String mImage = null;
        @NonNull private ButtonColors mButtonColors = PRIMARY_BUTTON_COLORS;
        @ButtonType private int mType = NOT_SET;

        /**
         * Creates a builder for the {@link Button} from the given content. Custom content should be
         * later set with one of the following ({@link #setIconContent}, {@link #setTextContent},
         * {@link #setImageContent}.
         *
         * @param context The application's context.
         * @param clickable Associated {@link Clickable} for click events. When the Button is
         *     clicked it will fire the associated action.
         */
        public Builder(@NonNull Context context, @NonNull Clickable clickable) {
            mClickable = clickable;
            mContext = context;
        }

        /**
         * Sets the content description for the {@link Button}. It is highly recommended to provide
         * this for button containing icon or image.
         */
        @NonNull
        public Builder setContentDescription(@NonNull CharSequence contentDescription) {
            this.mContentDescription = contentDescription;
            return this;
        }

        /**
         * Sets the size for the {@link Button}. Strongly recommended values are {@link
         * ButtonDefaults#DEFAULT_BUTTON_SIZE}, {@link ButtonDefaults#LARGE_BUTTON_SIZE} and {@link
         * ButtonDefaults#EXTRA_LARGE_BUTTON_SIZE}. If not set, {@link
         * ButtonDefaults#DEFAULT_BUTTON_SIZE} will be used.
         */
        @NonNull
        public Builder setSize(@NonNull DpProp size) {
            mSize = size;
            return this;
        }

        /**
         * Sets the size for the {@link Button}. Strongly recommended values are {@link
         * ButtonDefaults#DEFAULT_BUTTON_SIZE}, {@link ButtonDefaults#LARGE_BUTTON_SIZE} and {@link
         * ButtonDefaults#EXTRA_LARGE_BUTTON_SIZE}. If not set, {@link
         * ButtonDefaults#DEFAULT_BUTTON_SIZE} will be used.
         */
        @NonNull
        public Builder setSize(@Dimension(unit = DP) float size) {
            mSize = dp(size);
            return this;
        }

        /**
         * Sets the colors for the {@link Button}. If set, {@link ButtonColors#getBackgroundColor()}
         * will be used for the background of the button. If not set, {@link
         * ButtonDefaults#PRIMARY_BUTTON_COLORS} will be used.
         */
        @NonNull
        public Builder setButtonColors(@NonNull ButtonColors buttonColors) {
            mButtonColors = buttonColors;
            return this;
        }

        /**
         * Sets the custom content for this Button. Any previously added content will be overridden.
         */
        @NonNull
        public Builder setContent(@NonNull LayoutElement content) {
            resetContent();
            this.mCustomContent = content;
            this.mType = CUSTOM_CONTENT;
            return this;
        }

        /**
         * Sets the content of this Button to be the given icon. Any previously added content will
         * be overridden. Provided icon will be tinted to the given content color from {@link
         * ButtonColors} and with the given size. This icon should be image with chosen alpha
         * channel and not an actual image.
         */
        // There are multiple methods to set different type of content, but there is general getter
        // getContent that will return LayoutElement set by any of them. b/217197259
        @NonNull
        @SuppressWarnings("MissingGetterMatchingBuilder")
        public Builder setIconContent(@NonNull String resourceId, @NonNull DpProp size) {
            resetContent();
            this.mIcon = resourceId;
            this.mType = ICON;
            this.mIconSize = size;
            return this;
        }

        /**
         * Sets the content of this Button to be the given icon with the default size that is half
         * of the set size of the button. Any previously added content will be overridden. Provided
         * icon will be tinted to the given content color from {@link ButtonColors}. This icon
         * should be image with chosen alpha channel and not an actual image.
         */
        // There are multiple methods to set different type of content, but there is general getter
        // getContent that will return LayoutElement set by any of them. b/217197259
        @NonNull
        @SuppressWarnings("MissingGetterMatchingBuilder")
        public Builder setIconContent(@NonNull String resourceId) {
            resetContent();
            this.mIcon = resourceId;
            this.mType = ICON;
            return this;
        }

        /**
         * Sets the content of this Button to be the given text with the default font for the set
         * size (for the {@link ButtonDefaults#DEFAULT_BUTTON_SIZE}, {@link
         * ButtonDefaults#LARGE_BUTTON_SIZE} and {@link ButtonDefaults#EXTRA_LARGE_BUTTON_SIZE} is
         * {@link Typography#TYPOGRAPHY_TITLE2}, {@link Typography#TYPOGRAPHY_TITLE1} and {@link
         * Typography#TYPOGRAPHY_DISPLAY3} respectively). Any previously added content will be
         * overridden. Text should contain no more than 3 characters, otherwise it will overflow
         * from the edges.
         */
        // There are multiple methods to set different type of content, but there is general getter
        // getContent that will return LayoutElement set by any of them. b/217197259
        @NonNull
        @SuppressWarnings("MissingGetterMatchingBuilder")
        public Builder setTextContent(@NonNull String text) {
            resetContent();
            this.mText = text;
            this.mType = TEXT;
            return this;
        }

        /**
         * Sets the content of this Button to be the given text with the given font. If you need
         * more font related customization, consider using {@link #setContent} with {@link Text}
         * component. Any previously added content will be overridden. Text should contain no more
         * than 3 characters, otherwise it will overflow from the edges.
         */
        // There are multiple methods to set different type of content, but there is general getter
        // getContent that will return LayoutElement set by any of them. b/217197259
        @NonNull
        @SuppressWarnings("MissingGetterMatchingBuilder")
        public Builder setTextContent(@NonNull String text, @TypographyName int typographyName) {
            resetContent();
            this.mText = text;
            this.mTypographyName = typographyName;
            this.mType = TEXT;
            return this;
        }

        /**
         * Sets the content of this Button to be the given icon with the default size that is half
         * of the size of the button. Any previously added content will be overridden. Provided icon
         * will be tinted to the given content color from {@link ButtonColors}. This icon should be
         * image with chosen alpha channel and not an actual image.
         */
        // There are multiple methods to set different type of content, but there is general getter
        // getContent that will return LayoutElement set by any of them. b/217197259
        @NonNull
        @SuppressWarnings("MissingGetterMatchingBuilder")
        public Builder setImageContent(@NonNull String resourceId) {
            resetContent();
            this.mImage = resourceId;
            this.mType = IMAGE;
            return this;
        }

        private void resetContent() {
            this.mText = null;
            this.mTypographyName = null;
            this.mIcon = null;
            this.mImage = null;
            this.mCustomContent = null;
            this.mIconSize = null;
        }

        /** Constructs and returns {@link Button} with the provided field and look. */
        @NonNull
        @Override
        public Button build() {
            Modifiers.Builder modifiers =
                    new Modifiers.Builder()
                            .setClickable(mClickable)
                            .setBackground(
                                    new Background.Builder()
                                            .setColor(mButtonColors.getBackgroundColor())
                                            .setCorner(
                                                    new Corner.Builder()
                                                            .setRadius(radiusOf(mSize))
                                                            .build())
                                            .build());
            if (mContentDescription.length() > 0) {
                modifiers.setSemantics(
                        new ModifiersBuilders.Semantics.Builder()
                                .setContentDescription(mContentDescription.toString())
                                .build());
            }

            Box.Builder element =
                    new Box.Builder()
                            .setHeight(mSize)
                            .setWidth(mSize)
                            .setModifiers(modifiers.build());

            element.addContent(getCorrectContent());

            return new Button(element.build());
        }

        @NonNull
        private LayoutElement getCorrectContent() {
            assertContentFields();

            LayoutElement.Builder content;
            switch (mType) {
                case ICON:
                {
                    DpProp iconSize =
                            mIconSize != null
                                    ? mIconSize
                                    : ButtonDefaults.recommendedIconSize(mSize);
                    content =
                            new Image.Builder()
                                    .setResourceId(checkNotNull(mIcon))
                                    .setHeight(checkNotNull(iconSize))
                                    .setWidth(iconSize)
                                    .setContentScaleMode(CONTENT_SCALE_MODE_FILL_BOUNDS)
                                    .setColorFilter(
                                            new ColorFilter.Builder()
                                                    .setTint(mButtonColors.getContentColor())
                                                    .build());

                    return content.build();
                }
                case TEXT:
                {
                    @TypographyName
                    int typographyName =
                            mTypographyName != null
                                    ? mTypographyName
                                    : getDefaultTypographyForSize(mSize);
                    content =
                            new Text.Builder(mContext, checkNotNull(mText))
                                    .setMaxLines(1)
                                    .setTypography(typographyName)
                                    .setColor(mButtonColors.getContentColor());

                    return content.build();
                }
                case IMAGE:
                {
                    content =
                            new Image.Builder()
                                    .setResourceId(checkNotNull(mImage))
                                    .setHeight(mSize)
                                    .setWidth(mSize)
                                    .setContentScaleMode(CONTENT_SCALE_MODE_FILL_BOUNDS);
                    return content.build();
                }
                case CUSTOM_CONTENT:
                    return checkNotNull(mCustomContent);
                case NOT_SET:
                    // Shouldn't happen.
                default:
                    // Shouldn't happen.
                    throw new IllegalArgumentException("Wrong Button type");
            }
        }

        private void assertContentFields() {
            int numOfNonNull = 0;
            if (mText != null) {
                numOfNonNull++;
            }
            if (mIcon != null) {
                numOfNonNull++;
            }
            if (mImage != null) {
                numOfNonNull++;
            }
            if (mCustomContent != null) {
                numOfNonNull++;
            }
            if (numOfNonNull == 0 || mType == NOT_SET) {
                throw new IllegalArgumentException("Content is not set.");
            }
            if (numOfNonNull > 1) {
                throw new IllegalArgumentException(
                        "Too many contents are set. Only one content should be set in the button.");
            }
        }

        private @TypographyName int getDefaultTypographyForSize(@NonNull DpProp size) {
            if (size.getValue() == LARGE_BUTTON_SIZE.getValue()) {
                return Typography.TYPOGRAPHY_TITLE1;
            } else {
                if (size.getValue() == EXTRA_LARGE_BUTTON_SIZE.getValue()) {
                    return Typography.TYPOGRAPHY_DISPLAY3;
                } else {
                    return Typography.TYPOGRAPHY_TITLE2;
                }
            }
        }
    }

    /** Returns the content of this Button. */
    @NonNull
    public LayoutElement getContent() {
        return checkNotNull(mElement.getContents().get(0));
    }

    /** Returns click event action associated with this Button. */
    @NonNull
    public Clickable getClickable() {
        return checkNotNull(checkNotNull(mElement.getModifiers()).getClickable());
    }

    /** Returns content description for this Button. */
    @Nullable
    public CharSequence getContentDescription() {
        Semantics semantics = checkNotNull(mElement.getModifiers()).getSemantics();
        if (semantics == null) {
            return null;
        }
        return semantics.getContentDescription();
    }

    /** Returns size for this Button. */
    @NonNull
    public ContainerDimension getSize() {
        return checkNotNull(mElement.getWidth());
    }

    private ColorProp getBackgroundColor() {
        return checkNotNull(
                checkNotNull(checkNotNull(mElement.getModifiers()).getBackground()).getColor());
    }

    /** Returns button color of this Button. */
    @NonNull
    public ButtonColors getButtonColors() {
        ColorProp backgroundColor = getBackgroundColor();
        ColorProp contentColor;

        if (mElement.getContents().size() > 0) {
            LayoutElement mainElement = mElement.getContents().get(0);
            int type = getType(mainElement);
            // Default color so we can construct ButtonColors object in case where content color is
            // not set (i.e. button with an actual image doesn't use content color, or content is
            // set to custom one by the user.
            switch (type) {
                case Builder.ICON:
                    contentColor =
                            checkNotNull(
                                    checkNotNull(((Image) mainElement).getColorFilter()).getTint());
                    break;
                case Builder.TEXT:
                    contentColor =
                            checkNotNull(
                                    checkNotNull(
                                                    ((LayoutElementBuilders.Text) mainElement)
                                                            .getFontStyle())
                                            .getColor());
                    break;
                case Builder.IMAGE:
                case Builder.CUSTOM_CONTENT:
                case Builder.NOT_SET:
                default:
                    // No content color.
                    contentColor = new ColorProp.Builder().build();
                    break;
            }
        } else {
            contentColor = new ColorProp.Builder().build();
        }

        return new ButtonColors(backgroundColor, contentColor);
    }

    /**
     * Returns the type of the Button. Types are defined in {@link Builder}. This is used in {@link
     * #getButtonColors} to ease if the current Button has content color.
     *
     * <p>Type is determined by the content of the outer Box layout and if that content has the
     * color because if the content is set by some of the provided setters in the Builder it will
     * have color.
     */
    @Builder.ButtonType
    private int getType(LayoutElement element) {
        // To elementary Text class as Material Text when it goes to proto disappears.
        if (element instanceof LayoutElementBuilders.Text) {
            FontStyle fontStyle = ((LayoutElementBuilders.Text) element).getFontStyle();
            if (fontStyle != null && fontStyle.getColor() != null) {
                return Builder.TEXT;
            }
        }
        if (element instanceof Image) {
            // This means that the Button is either Button with an image or Button with an icon.
            // Only Button with an icon will have the tint color set.
            ColorFilter colorFilter = ((Image) element).getColorFilter();
            if (colorFilter != null && colorFilter.getTint() != null) {
                return Builder.ICON;
            } else {
                return Builder.IMAGE;
            }
        }
        return Builder.CUSTOM_CONTENT;
    }

    /** @hide */
    @NonNull
    @Override
    @RestrictTo(Scope.LIBRARY_GROUP)
    public LayoutElementProto.LayoutElement toLayoutElementProto() {
        return checkNotNull(mElement.toLayoutElementProto());
    }
}