java.lang.Object
↳ViewGroup
↳androidx.car.widget.CarToolbar
Gradle dependencies
compile group: 'androidx.car', name: 'car', version: '1.0.0-alpha7'
- groupId: androidx.car
- artifactId: car
- version: 1.0.0-alpha7
Artifact androidx.car:car:1.0.0-alpha7 it located at Google repository (https://maven.google.com/)
Androidx artifact mapping:
androidx.car:car com.android.support:car
Overview
A toolbar for building car applications.
CarToolbar provides a subset of features of Toolbar through a driving safe UI. From
start to end, a CarToolbar provides the following elements:
- A navigation button. Similar to that in Toolbar, navigation button should
always provide access to other navigational destinations. If navigation button is to
be used as Up Button, its OnClickListener needs to explicitly invoke
AppCompatActivity.onSupportNavigateUp()
- A title icon. A @Drawable shown before the title.
- A title. A single line primary text that ellipsizes at the end.
- A subtitle. A single line secondary text that ellipsizes at the end.
- An overflow button. A button that opens the overflow menu.
CarMenuItem in overflow menu will be shown as a CarListDialog. Overflow menu
items with icons are not supported yet, i.e. only texts from CarMenuItem.getTitle() will
be displayed.
One distinction between CarToolbar and Toolbar is that CarToolbar cannot be used as action bar
through AppCompatActivity.setSupportActionBar(Toolbar).
The CarToolbar has a fixed height of R.dimen.car_app_bar_height.
Summary
Constructors |
---|
public | CarToolbar(Context context)
|
public | CarToolbar(Context context, AttributeSet attrs)
|
public | CarToolbar(Context context, AttributeSet attrs, int defStyleAttr)
|
public | CarToolbar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
|
Methods |
---|
protected boolean | checkLayoutParams(ViewGroup.LayoutParams p)
|
protected LayoutParams | generateDefaultLayoutParams()
|
public LayoutParams | generateLayoutParams(AttributeSet attrs)
|
protected ViewGroup.LayoutParams | generateLayoutParams(ViewGroup.LayoutParams p)
|
public java.util.List<CarMenuItem> | getMenuItems()
Returns a list of this CarToolbar's CarMenuItems, or
null if none were set. |
public java.lang.CharSequence | getSubtitle()
Returns the subtitle of this toolbar. |
protected int | getSuggestedMinimumHeight()
|
public java.lang.CharSequence | getTitle()
Returns the title of this toolbar. |
public void | hideOverflowMenu()
Hides the overflow menu. |
public boolean | isOverflowMenuShowing()
Returns true if the overflow menu is showing. |
protected void | onLayout(boolean changed, int left, int top, int right, int bottom)
|
protected void | onMeasure(int widthMeasureSpec, int heightMeasureSpec)
|
public void | setMenuItems(java.util.List<CarMenuItem> items)
Sets the list of CarMenuItems that will be displayed on this CarToolbar. |
public void | setNavigationIcon(Drawable icon)
Set the icon to use for the toolbar's navigation button. |
public void | setNavigationIcon(int iconResId)
Set the icon to use for the toolbar's navigation button. |
public void | setNavigationIconContainerWidth(int width)
Sets the width of container for navigation icon. |
public void | setNavigationIconOnClickListener(View.OnClickListener listener)
Sets a listener to respond to navigation events. |
public void | setNavigationIconTint(ColorFilter colorFilter)
Sets the given as the tint for the navigation icon. |
public void | setNavigationIconTint(int tint)
Sets the tint color for the navigation icon. |
public void | setOverflowIcon(Drawable icon)
Sets the icon of the overflow menu button. |
public void | setOverflowIcon(int iconResId)
Sets the icon of the overflow menu button. |
public void | setSubtitle(java.lang.CharSequence subtitle)
Sets the subtitle of this toolbar. |
public void | setSubtitle(int resId)
Sets the subtitle of this toolbar. |
public void | setSubtitleTextAppearance(int resId)
Sets the text color, size, style, hint color, and highlight color
from the specified TextAppearance resource. |
public void | setTitle(java.lang.CharSequence title)
Sets the title of this toolbar. |
public void | setTitle(int resId)
Sets the title of this toolbar. |
public void | setTitleIcon(Drawable icon)
Sets the title icon to use in the toolbar. |
public void | setTitleIcon(int iconResId)
Sets the title icon to use in the toolbar. |
public void | setTitleIconEndMargin(int margin)
Sets the end margin of the title icon. |
public void | setTitleIconSize(int size)
Sets a new size for the title icon. |
public void | setTitleIconStartMargin(int margin)
Sets the start margin of the title icon. |
public void | setTitleTextAppearance(int resId)
Sets the text color, size, style, hint color, and highlight color
from the specified TextAppearance resource. |
public void | showOverflowMenu()
Shows the overflow menu. |
from java.lang.Object | clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait |
Constructors
public
CarToolbar(Context context)
public
CarToolbar(Context context, AttributeSet attrs)
public
CarToolbar(Context context, AttributeSet attrs, int defStyleAttr)
public
CarToolbar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
Methods
protected int
getSuggestedMinimumHeight()
protected void
onMeasure(int widthMeasureSpec, int heightMeasureSpec)
protected void
onLayout(boolean changed, int left, int top, int right, int bottom)
public void
setNavigationIcon(int iconResId)
Set the icon to use for the toolbar's navigation button.
The navigation button appears at the start of the toolbar if present. Setting an icon
will make the navigation button visible.
Parameters:
iconResId: The resource id of the icon to set on the navigatino button.
public void
setNavigationIcon(Drawable icon)
Set the icon to use for the toolbar's navigation button.
The navigation button appears at the start of the toolbar if present. Setting an icon
will make the navigation button visible.
Parameters:
icon: Icon to set; null will hide the icon.
public void
setNavigationIconTint(int tint)
Sets the tint color for the navigation icon.
Parameters:
tint: Color tint to apply.
public void
setNavigationIconTint(ColorFilter colorFilter)
Sets the given as the tint for the navigation icon. A null
ColorFilter will clear any set color filters.
Parameters:
colorFilter: Color filter to apply for the tint.
public void
setNavigationIconOnClickListener(View.OnClickListener listener)
Sets a listener to respond to navigation events.
This listener will be called whenever the user clicks the navigation button
at the start of the toolbar. An icon must be set for the navigation button to appear.
Parameters:
listener: Listener to set.
See also: CarToolbar.setNavigationIcon(Drawable)
public void
setNavigationIconContainerWidth(int width)
Sets the width of container for navigation icon.
Navigation icon will be horizontally centered in its container. If the width of container
is less than that of navigation icon, there will be no space on both ends of navigation icon.
Parameters:
width: Width of container in pixels.
public void
setTitleIcon(int iconResId)
Sets the title icon to use in the toolbar.
The title icon is positioned between the navigation button and the title.
Parameters:
iconResId: Resource id of the drawable to use as the title icon.
public void
setTitleIcon(Drawable icon)
Sets the title icon to use in the toolbar.
The title icon is positioned between the navigation button and the title.
Parameters:
icon: Icon to set; null will hide the icon.
public void
setTitleIconStartMargin(int margin)
Sets the start margin of the title icon.
Parameters:
margin: Start margin of the title icon in pixels.
public void
setTitleIconEndMargin(int margin)
Sets the end margin of the title icon.
Parameters:
margin: End margin of the title icon in pixels.
public void
setTitleIconSize(int size)
Sets a new size for the title icon.
Parameters:
size: Size of the title icon dimensions in pixels.
public java.lang.CharSequence
getTitle()
Returns the title of this toolbar.
Returns:
The current title.
public void
setTitle(int resId)
Sets the title of this toolbar.
A title should be used as the anchor for a section of content. It should
describe or name the content being viewed.
Parameters:
resId: Resource ID of a string to set as the title.
public void
setTitle(java.lang.CharSequence title)
Sets the title of this toolbar.
A title should be used as the anchor for a section of content. It should
describe or name the content being viewed.
null or empty string will hide the title.
Parameters:
title: Title to set.
public java.lang.CharSequence
getSubtitle()
Returns the subtitle of this toolbar.
Returns:
The current subtitle, or null if none has been set.
public void
setSubtitle(int resId)
Sets the subtitle of this toolbar.
Subtitles should express extended information about the current content.
Subtitle will appear underneath the title if the title exists.
Parameters:
resId: Resource ID of a string to set as the subtitle.
public void
setSubtitle(java.lang.CharSequence subtitle)
Sets the subtitle of this toolbar.
Subtitle should express extended information about the current content.
Subtitle will appear underneath the title if the title exists.
Parameters:
subtitle: Subtitle to set. null or empty string will hide the subtitle.
public void
setTitleTextAppearance(int resId)
Sets the text color, size, style, hint color, and highlight color
from the specified TextAppearance resource.
Parameters:
resId: Resource id of TextAppearance.
public void
setSubtitleTextAppearance(int resId)
Sets the text color, size, style, hint color, and highlight color
from the specified TextAppearance resource.
Parameters:
resId: Resource id of TextAppearance.
public void
setMenuItems(java.util.List<CarMenuItem> items)
Sets the list of CarMenuItems that will be displayed on this CarToolbar.
Parameters:
items: List of CarMenuItems to display, null to remove all items.
public java.util.List<CarMenuItem>
getMenuItems()
Returns a list of this CarToolbar's CarMenuItems, or
null if none were set.
public void
setOverflowIcon(int iconResId)
Sets the icon of the overflow menu button.
Parameters:
iconResId: Resource id of the drawable to use for the overflow menu button.
public void
setOverflowIcon(Drawable icon)
Sets the icon of the overflow menu button.
Parameters:
icon: Icon to set.
public boolean
isOverflowMenuShowing()
Returns true if the overflow menu is showing.
public void
showOverflowMenu()
Shows the overflow menu.
public void
hideOverflowMenu()
Hides the overflow menu.
public LayoutParams
generateLayoutParams(AttributeSet attrs)
protected LayoutParams
generateDefaultLayoutParams()
protected ViewGroup.LayoutParams
generateLayoutParams(ViewGroup.LayoutParams p)
protected boolean
checkLayoutParams(ViewGroup.LayoutParams p)
Source
/*
* Copyright (C) 2018 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.car.widget;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.ColorFilter;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.Switch;
import android.widget.TextView;
import androidx.annotation.ColorInt;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.Px;
import androidx.annotation.StringRes;
import androidx.annotation.StyleRes;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.car.R;
import androidx.car.app.CarListDialog;
import androidx.core.view.MarginLayoutParamsCompat;
import java.util.ArrayList;
import java.util.List;
/**
* A toolbar for building car applications.
*
* <p>CarToolbar provides a subset of features of {@link Toolbar} through a driving safe UI. From
* start to end, a CarToolbar provides the following elements:
* <ul>
* <li><em>A navigation button.</em> Similar to that in Toolbar, navigation button should
* always provide access to other navigational destinations. If navigation button is to
* be used as Up Button, its {@code OnClickListener} needs to explicitly invoke
* {@link AppCompatActivity#onSupportNavigateUp()}
* <li><em>A title icon.</em> A @{@code Drawable} shown before the title.
* <li><em>A title.</em> A single line primary text that ellipsizes at the end.
* <li><em>A subtitle.</em> A single line secondary text that ellipsizes at the end.
* <li><em>An overflow button.</em> A button that opens the overflow menu.
* </ul>
*
* <p>{@link CarMenuItem} in overflow menu will be shown as a {@link CarListDialog}. Overflow menu
* items with icons are not supported yet, i.e. only texts from {@link CarMenuItem#getTitle()} will
* be displayed.
*
* <p>One distinction between CarToolbar and Toolbar is that CarToolbar cannot be used as action bar
* through {@link androidx.appcompat.app.AppCompatActivity#setSupportActionBar(Toolbar)}.
*
* <p>The CarToolbar has a fixed height of {@code R.dimen.car_app_bar_height}.
*/
public class CarToolbar extends ViewGroup {
// Max number of Action items displayed on the toolbar, only applies to IF_ROOM items.
@VisibleForTesting
static final int ACTION_ITEM_COUNT_LIMIT = 4;
private static final String TAG = "CarToolbar";
private final ImageButton mNavButtonView;
private final int mEdgeButtonIconSize;
private final ImageView mTitleIconView;
private final ImageButton mOverflowButtonView;
private final int mToolbarHeight;
private final int mTextVerticalPadding;
private final int mActionButtonPadding;
private final int mActionButtonIconBound;
private final int mActionButtonHeight;
private int mTitleIconSize;
// There is no actual container for edge buttons (Navigation / Overflow). This value is used
// to calculate a horizontal margin on both ends of the edge buttons so that they're centered.
// We use dedicated attribute over horizontal margin so that the API for setting space before
// title (i.e. @dimen/car_margin) is simpler.
private int mEdgeButtonContainerWidth;
private final TextView mTitleTextView;
private CharSequence mTitleText;
private final TextView mSubtitleTextView;
private CharSequence mSubtitleText;
@Nullable
private List<CarMenuItem> mMenuItems;
@Nullable
private CarListDialog mOverflowDialog;
private final List<InflatedMenuItem> mAllMenuItems = new ArrayList<>();
private final List<CarMenuItem> mOverflowMenuItems = new ArrayList<>();
private final List<View> mToolbarItems = new ArrayList<>();
// Number of items that are always displayed on the toolbar.
private int mAlwaysItemCount;
// Minimum width required to show a menu item as action item on the toolbar.
// This var is used as threshold for showing if_room items, therefore its value is subject
// to change - DO NOT depend on it.
private final int mMinActionButtonWidth;
/**
* OnClickListener that handles the overflow dialog clicks by calling the appropriate
* {@link CarMenuItem.OnClickListener} of the overflow {@link CarMenuItem}s.
*/
private DialogInterface.OnClickListener mOverflowDialogClickListener = (dialog, which) -> {
CarMenuItem item = mOverflowMenuItems.get(which);
if (item.getOnClickListener() != null) {
item.getOnClickListener().onClick(item);
}
};
public CarToolbar(Context context) {
this(context, /* attrs= */ null);
}
public CarToolbar(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.carToolbarStyle);
}
public CarToolbar(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, R.style.Widget_Car_CarToolbar);
}
public CarToolbar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
Resources res = context.getResources();
mToolbarHeight = res.getDimensionPixelSize(R.dimen.car_app_bar_height);
mEdgeButtonIconSize = res.getDimensionPixelSize(R.dimen.car_primary_icon_size);
mTextVerticalPadding = res.getDimensionPixelSize(R.dimen.car_padding_1);
mActionButtonPadding = res.getDimensionPixelSize(R.dimen.car_padding_2);
mActionButtonIconBound = res.getDimensionPixelSize(R.dimen.car_app_bar_action_icon_bound);
mActionButtonHeight = res.getDimensionPixelSize(R.dimen.car_button_height);
LayoutInflater.from(context).inflate(R.layout.car_toolbar, this);
// Ensure min touch target size for nav button.
mNavButtonView = findViewById(R.id.nav_button);
int minTouchSize = getContext().getResources().getDimensionPixelSize(
R.dimen.car_touch_target_size);
MinTouchTargetHelper.ensureThat(mNavButtonView).hasMinTouchSize(minTouchSize);
mTitleTextView = findViewById(R.id.title);
mTitleIconView = findViewById(R.id.title_icon);
mSubtitleTextView = findViewById(R.id.subtitle);
mOverflowButtonView = findViewById(R.id.overflow_menu);
mMinActionButtonWidth = res.getDimensionPixelOffset(R.dimen.car_button_min_width);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CarToolbar, defStyleAttr,
/* defStyleRes= */ 0);
try {
CharSequence title = a.getText(R.styleable.CarToolbar_title);
setTitle(title);
setTitleTextAppearance(a.getResourceId(R.styleable.CarToolbar_titleTextAppearance,
R.style.TextAppearance_Car_Body1_Medium_Light));
setNavigationIcon(a.getResourceId(R.styleable.CarToolbar_navigationIcon,
R.drawable.ic_nav_arrow_back));
int navigationIconTintResId =
a.getResourceId(R.styleable.CarToolbar_navigationIconTint, -1);
if (navigationIconTintResId != -1) {
setNavigationIconTint(context.getColor(navigationIconTintResId));
}
int titleIconResId = a.getResourceId(R.styleable.CarToolbar_titleIcon, -1);
setTitleIcon(titleIconResId != -1
? context.getDrawable(titleIconResId)
: null);
setTitleIconStartMargin(
a.getDimensionPixelSize(R.styleable.CarToolbar_titleIconStartMargin, 0));
setTitleIconEndMargin(
a.getDimensionPixelSize(R.styleable.CarToolbar_titleIconEndMargin, 0));
setTitleIconSize(a.getDimensionPixelSize(R.styleable.CarToolbar_titleIconSize,
res.getDimensionPixelSize(R.dimen.car_application_icon_size)));
CharSequence subtitle = a.getText(R.styleable.CarToolbar_subtitle);
setSubtitle(subtitle);
setSubtitleTextAppearance(a.getResourceId(R.styleable.CarToolbar_subtitleTextAppearance,
R.style.TextAppearance_Car_Body2_Light));
setOverflowIcon(a.getResourceId(R.styleable.CarToolbar_overflowIcon,
R.drawable.ic_more_vert));
mOverflowButtonView.setOnClickListener(v -> {
populateOverflowMenu();
mOverflowDialog.show();
});
mEdgeButtonContainerWidth = a.getDimensionPixelSize(
R.styleable.CarToolbar_navigationIconContainerWidth,
res.getDimensionPixelSize(R.dimen.car_margin));
} finally {
a.recycle();
}
}
@Override
protected int getSuggestedMinimumHeight() {
// Car Toolbar uses fixed height.
return mToolbarHeight;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Desired height should be the height constraint for all child views.
int desiredHeight = getPaddingTop() + getSuggestedMinimumHeight() + getPaddingBottom();
int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
desiredHeight, MeasureSpec.AT_MOST);
int width = 0;
// Measure the two edge buttons first because they have a higher
// display priority than the title, subtitle, or the titleIcon.
if (mNavButtonView.getVisibility() != GONE) {
// Size of nav button is fixed.
int measureSpec = MeasureSpec.makeMeasureSpec(mEdgeButtonIconSize, MeasureSpec.EXACTLY);
mNavButtonView.measure(measureSpec, measureSpec);
// Nav button width includes its container.
int navWidth = Math.max(mEdgeButtonContainerWidth, mNavButtonView.getMeasuredWidth());
width += navWidth + getHorizontalMargins(mNavButtonView);
}
mToolbarItems.forEach(this::removeView);
mToolbarItems.clear();
mOverflowMenuItems.clear();
// Measure items that will always be shown on the toolbar.
int alwaysItemsWidth = measureAlwaysItems(width, widthMeasureSpec, childHeightMeasureSpec);
width += alwaysItemsWidth;
// Decide how many IF_ROOM items to show, and measure them.
width += measureIfRoomItems(width, alwaysItemsWidth, widthMeasureSpec,
childHeightMeasureSpec);
// Split the items appropriately based on the display location.
// Done post measurement to ensure the order in the two lists matches the passed-in items.
for (InflatedMenuItem inflatedItem : mAllMenuItems) {
if (inflatedItem.mIsDisplayedOnToolbar) {
mToolbarItems.add(inflatedItem.mView);
} else {
mOverflowMenuItems.add(inflatedItem.mItem);
}
}
// Show the overflow menu button if there are any overflow menu items.
mOverflowButtonView.setVisibility(mOverflowMenuItems.isEmpty() ? GONE : VISIBLE);
if (mOverflowButtonView.getVisibility() != GONE) {
int measureSpec = MeasureSpec.makeMeasureSpec(mEdgeButtonIconSize, MeasureSpec.EXACTLY);
mOverflowButtonView.measure(measureSpec, measureSpec);
width += Math.max(mEdgeButtonContainerWidth, mOverflowButtonView.getMeasuredWidth())
+ getHorizontalMargins(mOverflowButtonView);
}
if (mTitleIconView.getVisibility() != GONE) {
int measureSpec = MeasureSpec.makeMeasureSpec(mTitleIconSize, MeasureSpec.EXACTLY);
mTitleIconView.measure(measureSpec, measureSpec);
width += mTitleIconView.getMeasuredWidth() + getHorizontalMargins(mTitleIconView);
}
int titleLength = 0;
int subtitleLength = 0;
if (mTitleTextView.getVisibility() != GONE) {
measureChild(mTitleTextView, widthMeasureSpec, width, childHeightMeasureSpec, 0);
titleLength = mTitleTextView.getMeasuredWidth() + getHorizontalMargins(mTitleTextView);
}
if (mSubtitleTextView.getVisibility() != GONE) {
measureChild(mSubtitleTextView, widthMeasureSpec, width, childHeightMeasureSpec, 0);
subtitleLength = mSubtitleTextView.getMeasuredWidth()
+ getHorizontalMargins(mSubtitleTextView);
}
width += Math.max(titleLength, subtitleLength);
setMeasuredDimension(resolveSize(width, widthMeasureSpec),
resolveSize(desiredHeight, heightMeasureSpec));
}
/**
* Measures ALWAYS menu items and adds them to the layout.
*
* @param widthUsed Total width used by other child views so far.
* @param widthMeasureSpec Parent width measure spec.
* @param heightMeasureSpec Parent height measure spec.
* @return Total width occupied by ALWAYS items.
*/
private int measureAlwaysItems(int widthUsed, int widthMeasureSpec, int heightMeasureSpec) {
int width = 0;
for (InflatedMenuItem inflatedItem : mAllMenuItems) {
if (inflatedItem.mItem.getDisplayBehavior() == CarMenuItem.DisplayBehavior.ALWAYS) {
View action = inflatedItem.mView;
measureChild(action, widthMeasureSpec, widthUsed + width, heightMeasureSpec, 0);
inflatedItem.mIsDisplayedOnToolbar = true;
addView(action);
width += action.getMeasuredWidth() + getHorizontalMargins(action);
}
}
return width;
}
/**
* Measures IF_ROOM menu items and adds them to the layout. Items past
* {@link #ACTION_ITEM_COUNT_LIMIT} or half the toolbar width will not be measured/added.
*
* @param widthUsed Total width used by other child views so far.
* @param alwaysItemsWidth Total width used by ALWAYS item views.
* @param widthMeasureSpec Parent width measure spec.
* @param heightMeasureSpec Parent height measure spec.
* @return Total width occupied by IF_ROOM items.
*/
private int measureIfRoomItems(int widthUsed, int alwaysItemsWidth,
int widthMeasureSpec, int heightMeasureSpec) {
int width = 0;
// If mode is unspecified, all IF_ROOM items should be measured.
boolean isWidthUnspecified =
MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.UNSPECIFIED;
// If specified, available width for IF_ROOM items. (50% of toolbar)
int widthCapacity = (MeasureSpec.getSize(widthMeasureSpec) / 2) - alwaysItemsWidth;
// Count of IF_ROOM items to be displayed on the toolbar.
int allowedCount = ACTION_ITEM_COUNT_LIMIT - mAlwaysItemCount;
for (InflatedMenuItem inflatedItem : mAllMenuItems) {
if (allowedCount < 1
|| (!isWidthUnspecified && widthCapacity <= mMinActionButtonWidth)) {
// Cannot show more IF_ROOM items because either max count is reached, or remaining
// width is not sufficient.
break;
}
if (inflatedItem.mItem.getDisplayBehavior() == CarMenuItem.DisplayBehavior.IF_ROOM) {
View action = inflatedItem.mView;
measureChild(action, widthMeasureSpec, widthUsed + width, heightMeasureSpec, 0);
int viewWidth = action.getMeasuredWidth() + getHorizontalMargins(action);
if (isWidthUnspecified || widthCapacity - viewWidth > 0) {
allowedCount--;
widthCapacity -= viewWidth;
width += viewWidth;
inflatedItem.mIsDisplayedOnToolbar = true;
addView(action);
}
}
}
return width;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
int height = bottom - top;
int layoutLeft = getPaddingLeft();
int layoutRight = getPaddingRight();
if (mNavButtonView.getVisibility() != GONE) {
// Nav button is centered in container.
int navButtonWidth = mNavButtonView.getMeasuredWidth();
int containerWidth = Math.max(mEdgeButtonContainerWidth, navButtonWidth);
int navButtonLeft = (containerWidth - navButtonWidth) / 2;
layoutViewFromLeftVerticallyCentered(mNavButtonView, navButtonLeft, height);
layoutLeft += containerWidth;
}
if (mOverflowButtonView.getVisibility() != GONE) {
int horizontalMargin = (mEdgeButtonContainerWidth
- mOverflowButtonView.getMeasuredWidth()) / 2;
layoutViewFromRightVerticallyCentered(mOverflowButtonView,
right - horizontalMargin, height);
layoutRight += Math.max(mEdgeButtonContainerWidth,
mOverflowButtonView.getMeasuredWidth());
}
for (View view : mToolbarItems) {
layoutViewFromRightVerticallyCentered(view, right - layoutRight, height);
layoutRight += view.getMeasuredWidth();
}
if (mTitleIconView.getVisibility() != GONE) {
MarginLayoutParams marginParams = (MarginLayoutParams) mTitleIconView.getLayoutParams();
layoutLeft += marginParams.getMarginStart();
layoutViewFromLeftVerticallyCentered(mTitleIconView, layoutLeft, height);
layoutLeft += mTitleIconView.getMeasuredWidth() + marginParams.getMarginEnd();
}
if (mTitleTextView.getVisibility() != GONE && mSubtitleTextView.getVisibility() != GONE) {
layoutTextViewsVerticallyCentered(mTitleTextView, mSubtitleTextView, layoutLeft,
height);
} else if (mTitleTextView.getVisibility() != GONE) {
layoutViewFromLeftVerticallyCentered(mTitleTextView, layoutLeft, height);
} else if (mSubtitleTextView.getVisibility() != GONE) {
layoutViewFromLeftVerticallyCentered(mSubtitleTextView, layoutLeft, height);
}
}
/**
* Set the icon to use for the toolbar's navigation button.
*
* <p>The navigation button appears at the start of the toolbar if present. Setting an icon
* will make the navigation button visible.
*
* @param iconResId The resource id of the icon to set on the navigatino button.
*
* {@link R.attr#navigationIcon}
*/
public void setNavigationIcon(@DrawableRes int iconResId) {
setNavigationIcon(getContext().getDrawable(iconResId));
}
/**
* Set the icon to use for the toolbar's navigation button.
*
* <p>The navigation button appears at the start of the toolbar if present. Setting an icon
* will make the navigation button visible.
*
* @param icon Icon to set; {@code null} will hide the icon.
*
* {@link R.attr#navigationIcon}
*/
public void setNavigationIcon(@Nullable Drawable icon) {
if (icon == null) {
mNavButtonView.setVisibility(GONE);
mNavButtonView.setImageDrawable(null);
return;
}
mNavButtonView.setVisibility(VISIBLE);
mNavButtonView.setImageDrawable(icon);
}
/**
* Sets the tint color for the navigation icon.
*
* @param tint Color tint to apply.
*
* {@link R.attr#navigationIconTint}
*/
public void setNavigationIconTint(@ColorInt int tint) {
mNavButtonView.setColorFilter(tint);
}
/**
* Sets the given {@link ColorFilter} as the tint for the navigation icon. A {@code null}
* {@code ColorFilter} will clear any set color filters.
*
* @param colorFilter Color filter to apply for the tint.
*
* {@link R.attr#navigationIconTint}
*/
public void setNavigationIconTint(@Nullable ColorFilter colorFilter) {
mNavButtonView.setColorFilter(colorFilter);
}
/**
* Sets a listener to respond to navigation events.
*
* <p>This listener will be called whenever the user clicks the navigation button
* at the start of the toolbar. An icon must be set for the navigation button to appear.
*
* @param listener Listener to set.
* @see #setNavigationIcon(Drawable)
*/
public void setNavigationIconOnClickListener(@Nullable View.OnClickListener listener) {
mNavButtonView.setOnClickListener(listener);
}
/**
* Sets the width of container for navigation icon.
*
* <p>Navigation icon will be horizontally centered in its container. If the width of container
* is less than that of navigation icon, there will be no space on both ends of navigation icon.
*
* @param width Width of container in pixels.
*/
public void setNavigationIconContainerWidth(@Px int width) {
mEdgeButtonContainerWidth = width;
requestLayout();
}
/**
* Sets the title icon to use in the toolbar.
*
* <p>The title icon is positioned between the navigation button and the title.
*
* @param iconResId Resource id of the drawable to use as the title icon.
* {@link R.attr#titleIcon}
*/
public void setTitleIcon(@DrawableRes int iconResId) {
setTitleIcon(getContext().getDrawable(iconResId));
}
/**
* Sets the title icon to use in the toolbar.
*
* <p>The title icon is positioned between the navigation button and the title.
*
* @param icon Icon to set; {@code null} will hide the icon.
* {@link R.attr#titleIcon}
*/
public void setTitleIcon(@Nullable Drawable icon) {
if (icon == null) {
mTitleIconView.setVisibility(GONE);
mTitleIconView.setImageDrawable(null);
return;
}
mTitleIconView.setVisibility(VISIBLE);
mTitleIconView.setImageDrawable(icon);
}
/**
* Sets the start margin of the title icon.
*
* @param margin Start margin of the title icon in pixels.
* @attr ref R.styleable#CarToolbar_titleIconStartMargin
*/
public void setTitleIconStartMargin(@Px int margin) {
MarginLayoutParams marginParams = (MarginLayoutParams) mTitleIconView.getLayoutParams();
marginParams.setMarginStart(margin);
requestLayout();
}
/**
* Sets the end margin of the title icon.
*
* @param margin End margin of the title icon in pixels.
* @attr ref R.styleable#CarToolbar_titleIconEndMargin
*/
public void setTitleIconEndMargin(@Px int margin) {
MarginLayoutParams marginParams = (MarginLayoutParams) mTitleIconView.getLayoutParams();
marginParams.setMarginEnd(margin);
requestLayout();
}
/**
* Sets a new size for the title icon.
*
* @param size Size of the title icon dimensions in pixels.
* {@link R.attr#titleIconSize}
*/
public void setTitleIconSize(@Px int size) {
mTitleIconSize = size;
requestLayout();
}
/**
* Returns the title of this toolbar.
*
* @return The current title.
*/
public CharSequence getTitle() {
return mTitleText;
}
/**
* Sets the title of this toolbar.
*
* <p>A title should be used as the anchor for a section of content. It should
* describe or name the content being viewed.
*
* @param resId Resource ID of a string to set as the title.
*/
public void setTitle(@StringRes int resId) {
setTitle(getContext().getText(resId));
}
/**
* Sets the title of this toolbar.
*
* <p>A title should be used as the anchor for a section of content. It should
* describe or name the content being viewed.
*
* <p>{@code null} or empty string will hide the title.
*
* @param title Title to set.
*/
public void setTitle(CharSequence title) {
mTitleText = title;
mTitleTextView.setText(title);
mTitleTextView.setVisibility(TextUtils.isEmpty(title) ? GONE : VISIBLE);
}
/**
* Returns the subtitle of this toolbar.
*
* @return The current subtitle, or {@code null} if none has been set.
*/
@Nullable
public CharSequence getSubtitle() {
return mSubtitleText;
}
/**
* Sets the subtitle of this toolbar.
*
* <p>Subtitles should express extended information about the current content.
* Subtitle will appear underneath the title if the title exists.
* @param resId Resource ID of a string to set as the subtitle.
*/
public void setSubtitle(@StringRes int resId) {
setSubtitle(getContext().getText(resId));
}
/**
* Sets the subtitle of this toolbar.
*
* <p>Subtitle should express extended information about the current content.
* Subtitle will appear underneath the title if the title exists.
*
* @param subtitle Subtitle to set. {@code null} or empty string will hide the subtitle.
*/
public void setSubtitle(@Nullable CharSequence subtitle) {
mSubtitleText = subtitle;
mSubtitleTextView.setText(subtitle);
mSubtitleTextView.setVisibility(TextUtils.isEmpty(subtitle) ? GONE : VISIBLE);
}
/**
* Sets the text color, size, style, hint color, and highlight color
* from the specified TextAppearance resource.
*
* @param resId Resource id of TextAppearance.
*/
public void setTitleTextAppearance(@StyleRes int resId) {
mTitleTextView.setTextAppearance(resId);
}
/**
* Sets the text color, size, style, hint color, and highlight color
* from the specified TextAppearance resource.
*
* @param resId Resource id of TextAppearance.
*/
public void setSubtitleTextAppearance(@StyleRes int resId) {
mSubtitleTextView.setTextAppearance(resId);
}
/**
* Sets the list of {@link CarMenuItem}s that will be displayed on this {@code CarToolbar}.
*
* @param items List of {@link CarMenuItem}s to display, {@code null} to remove all items.
*/
public void setMenuItems(@Nullable List<CarMenuItem> items) {
mMenuItems = items;
mAllMenuItems.clear();
mAlwaysItemCount = 0;
if (mMenuItems == null) {
requestLayout();
return;
}
// Create Views for all ALWAYS and IF_ROOM items.
for (CarMenuItem item : mMenuItems) {
View action;
switch (item.getDisplayBehavior()) {
case ALWAYS:
mAlwaysItemCount++;
// Fall-through
case IF_ROOM:
action = item.isCheckable() ? createCheckableAction(item) : createAction(item);
break;
case NEVER:
action = null;
break;
default:
throw new IllegalStateException(
"Unknown display behavior: " + item.getDisplayBehavior());
}
mAllMenuItems.add(new InflatedMenuItem(item, action));
}
requestLayout();
}
/**
* Returns a list of this {@code CarToolbar}'s {@link CarMenuItem}s, or
* {@code null} if none were set.
*/
@Nullable
public List<CarMenuItem> getMenuItems() {
return mMenuItems;
}
/**
* Creates an Action {@link Button} item configured for the given {@link CarMenuItem}.
*
* @param item The {@link CarMenuItem} used to create the {@link Button}.
* @return A configured {@link Button} view.
*/
private Button createAction(CarMenuItem item) {
Context context = getContext();
Button button = new Button(context, null, 0, item.getStyleResId());
button.setLayoutParams(
new MarginLayoutParams(LayoutParams.WRAP_CONTENT, mActionButtonHeight));
CharSequence title = item.getTitle();
button.setText(title);
if (item.getIcon() != null) {
Drawable icon = item.getIcon().loadDrawable(context);
icon.setBounds(0, 0, mActionButtonIconBound, mActionButtonIconBound);
// Set the Drawable on the left side.
button.setCompoundDrawables(icon, null, null, null);
if (!TextUtils.isEmpty(title)) {
// Add padding after the icon only if there's a title.
button.setCompoundDrawablePadding(mActionButtonPadding);
}
}
button.setEnabled(item.isEnabled());
button.setOnClickListener(v -> {
CarMenuItem.OnClickListener onClickListener = item.getOnClickListener();
if (onClickListener != null) {
onClickListener.onClick(item);
}
});
return button;
}
/**
* Creates an Action {@link Switch} item configured for the given {@link CarMenuItem}.
*
* @param item The checkable {@link CarMenuItem} used to create the {@link Switch}.
* @return A configured {@link Switch} view.
*/
private View createCheckableAction(CarMenuItem item) {
Context context = getContext();
ViewGroup checkableAction = (ViewGroup) LayoutInflater.from(context)
.inflate(R.layout.checkable_action_item, this, false);
Switch switchWidget = checkableAction.findViewById(R.id.switch_widget);
switchWidget.setEnabled(item.isEnabled());
switchWidget.setChecked(item.isChecked());
if (item.isEnabled()) {
checkableAction.setOnClickListener(v -> {
switchWidget.toggle();
item.setChecked(switchWidget.isChecked());
CarMenuItem.OnClickListener itemOnClickListener = item.getOnClickListener();
if (itemOnClickListener != null) {
itemOnClickListener.onClick(item);
}
});
} else {
checkableAction.setClickable(false);
}
CharSequence title = item.getTitle();
if (!TextUtils.isEmpty(title)) {
Button button = new Button(context, null, 0, item.getStyleResId());
// The button is added programmatically so that we can apply a custom style.
button.setText(title);
checkableAction.addView(button);
}
return checkableAction;
}
/**
* Adds the overflow items to the overflow menu dialog.
*/
private void populateOverflowMenu() {
if (mOverflowMenuItems.isEmpty()) {
mOverflowDialog = null;
return;
}
CharSequence[] titles = mOverflowMenuItems.stream()
.map(CarMenuItem::getTitle)
.toArray(CharSequence[]::new);
mOverflowDialog = new CarListDialog.Builder(getContext())
.setItems(titles, mOverflowDialogClickListener)
.create();
}
/**
* Sets the icon of the overflow menu button.
*
* @param iconResId Resource id of the drawable to use for the overflow menu button.
*
* @attr ref R.styleable#CarToolbar_overflowIcon
*/
public void setOverflowIcon(@DrawableRes int iconResId) {
mOverflowButtonView.setImageDrawable(getContext().getDrawable(iconResId));
}
/**
* Sets the icon of the overflow menu button.
*
* @param icon Icon to set.
*
* @attr ref R.styleable#CarToolbar_overflowIcon
*/
public void setOverflowIcon(@NonNull Drawable icon) {
if (icon == null) {
throw new IllegalArgumentException("Provided overflow icon cannot be null.");
}
mOverflowButtonView.setImageDrawable(icon);
}
/**
* Returns {@code true} if the overflow menu is showing.
*/
public boolean isOverflowMenuShowing() {
return mOverflowDialog != null && mOverflowDialog.isShowing();
}
/**
* Shows the overflow menu.
*/
public void showOverflowMenu() {
populateOverflowMenu();
if (mOverflowDialog != null) {
mOverflowDialog.show();
}
}
/**
* Hides the overflow menu.
*/
public void hideOverflowMenu() {
if (mOverflowDialog != null) {
mOverflowDialog.dismiss();
}
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
}
@Override
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return new MarginLayoutParams(p);
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof MarginLayoutParams;
}
/**
* Lays out a view on the left side so that it's vertically centered in its parent.
*
* @param view The view to layout.
* @param left Position from the left.
* @param parentHeight Height of the parent view.
*/
private void layoutViewFromLeftVerticallyCentered(View view, int left, int parentHeight) {
int height = view.getMeasuredHeight();
int top = (parentHeight - height) / 2;
view.layout(left, top, left + view.getMeasuredWidth(), top + height);
}
/**
* Lays out a view on the right side so that it's vertically centered in its parent.
*
* @param view The view to layout.
* @param right Position from the right.
* @param parentHeight Height of the parent view.
*/
private void layoutViewFromRightVerticallyCentered(View view, int right, int parentHeight) {
int height = view.getMeasuredHeight();
int top = (parentHeight - height) / 2;
view.layout(right - view.getMeasuredWidth(), top, right, top + height);
}
private void layoutTextViewsVerticallyCentered(View title, View subtitle, int left,
int height) {
int titleHeight = title.getMeasuredHeight();
int titleWidth = title.getMeasuredWidth();
int subtitleHeight = subtitle.getMeasuredHeight();
int subtitleWidth = subtitle.getMeasuredWidth();
int titleTop = (height - titleHeight - subtitleHeight - mTextVerticalPadding) / 2;
title.layout(left, titleTop, left + titleWidth, titleTop + titleHeight);
int subtitleTop = title.getBottom() + mTextVerticalPadding;
subtitle.layout(left, subtitleTop, left + subtitleWidth, subtitleTop + subtitleHeight);
}
private int getHorizontalMargins(View v) {
MarginLayoutParams mlp = (MarginLayoutParams) v.getLayoutParams();
return MarginLayoutParamsCompat.getMarginStart(mlp)
+ MarginLayoutParamsCompat.getMarginEnd(mlp);
}
/**
* Measure child view.
*
* @param child Child view to measure.
* @param parentWidthSpec Parent width MeasureSpec.
* @param widthUsed Width used so far by other child views; used as part of padding for current
* child view in MeasureSpec calculation.
* @param parentHeightSpec Parent height MeasureSpec.
* @param heightUsed Height used so far by other child views; used as part of padding for
* current child view in MeasureSpec calculation.
*/
private void measureChild(View child, int parentWidthSpec, int widthUsed,
int parentHeightSpec, int heightUsed) {
// Calculate the padding and margin of current dimension,
// including the width/height used by other child views.
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
int childWidthSpec = getChildMeasureSpec(parentWidthSpec,
getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin + widthUsed,
lp.width);
int childHeightSpec = getChildMeasureSpec(parentHeightSpec,
getPaddingTop() + getPaddingBottom() + lp.topMargin + lp.bottomMargin + heightUsed,
lp.height);
child.measure(childWidthSpec, childHeightSpec);
}
/**
* Class that keeps track of a {@link CarMenuItem} and its inflated {@link View}.
*/
private static class InflatedMenuItem {
final CarMenuItem mItem;
@Nullable
final View mView;
boolean mIsDisplayedOnToolbar;
// |view| is Nullable since overflow items do not have a View associated with them.
InflatedMenuItem(@NonNull CarMenuItem item, @Nullable View view) {
mItem = item;
mView = view;
}
}
}