blob: 928e34e27f75137cc0c80f6049b7e01a8bb2d25e [file] [log] [blame]
/*
* Copyright (C) 2014 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 android.widget;
import android.annotation.NonNull;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
import com.android.internal.R;
import java.util.ArrayList;
import java.util.List;
/**
* A standard toolbar for use within application content.
*
* <p>A Toolbar is a generalization of {@link android.app.ActionBar action bars} for use
* within application layouts. While an action bar is traditionally part of an
* {@link android.app.Activity Activity's} opaque window decor controlled by the framework,
* a Toolbar may be placed at any arbitrary level of nesting within a view hierarchy.
* An application may choose to designate a Toolbar as the action bar for an Activity
* using the {@link android.app.Activity#setActionBar(Toolbar) setActionBar()} method.</p>
*
* <p>Toolbar supports a more focused feature set than ActionBar. From start to end, a toolbar
* may contain a combination of the following optional elements:
*
* <ul>
* <li><em>A navigation button.</em> This may be an Up arrow, navigation menu toggle, close,
* collapse, done or another glyph of the app's choosing. This button should always be used
* to access other navigational destinations within the container of the Toolbar and
* its signified content or otherwise leave the current context signified by the Toolbar.</li>
* <li><em>A branded logo image.</em> This may extend to the height of the bar and can be
* arbitrarily wide.</li>
* <li><em>A title and subtitle.</em> The title should be a signpost for the Toolbar's current
* position in the navigation hierarchy and the content contained there. The subtitle,
* if present should indicate any extended information about the current content.
* If an app uses a logo image it should strongly consider omitting a title and subtitle.</li>
* <li><em>One or more custom views.</em> The application may add arbitrary child views
* to the Toolbar. They will appear at this position within the layout. If a child view's
* {@link LayoutParams} indicates a {@link Gravity} value of
* {@link Gravity#CENTER_HORIZONTAL CENTER_HORIZONTAL} the view will attempt to center
* within the available space remaining in the Toolbar after all other elements have been
* measured.</li>
* <li><em>An {@link ActionMenuView action menu}.</em> The menu of actions will pin to the
* end of the Toolbar offering a few
* <a href="http://developer.android.com/design/patterns/actionbar.html#ActionButtons">
* frequent, important or typical</a> actions along with an optional overflow menu for
* additional actions.</li>
* </ul>
* </p>
*
* <p>In modern Android UIs developers should lean more on a visually distinct color scheme for
* toolbars than on their application icon. The use of application icon plus title as a standard
* layout is discouraged on API 21 devices and newer.</p>
*/
public class Toolbar extends ViewGroup {
private ActionMenuView mMenuView;
private TextView mTitleTextView;
private TextView mSubtitleTextView;
private ImageButton mNavButtonView;
private ImageView mLogoView;
private int mTitleTextAppearance;
private int mSubtitleTextAppearance;
private int mTitleMarginStart;
private int mTitleMarginEnd;
private int mTitleMarginTop;
private int mTitleMarginBottom;
private int mGravity = Gravity.START | Gravity.CENTER_VERTICAL;
private CharSequence mTitleText;
private CharSequence mSubtitleText;
// Clear me after use.
private final ArrayList<View> mTempViews = new ArrayList<View>();
private OnMenuItemClickListener mOnMenuItemClickListener;
private final ActionMenuView.OnMenuItemClickListener mMenuViewItemClickListener =
new ActionMenuView.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
if (mOnMenuItemClickListener != null) {
return mOnMenuItemClickListener.onMenuItemClick(item);
}
return false;
}
};
public Toolbar(Context context) {
this(context, null);
}
public Toolbar(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.toolbarStyle);
}
public Toolbar(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public Toolbar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Toolbar,
defStyleAttr, defStyleRes);
mTitleTextAppearance = a.getResourceId(R.styleable.Toolbar_titleTextAppearance, 0);
mSubtitleTextAppearance = a.getResourceId(R.styleable.Toolbar_subtitleTextAppearance, 0);
mGravity = a.getInteger(R.styleable.Toolbar_gravity, mGravity);
mTitleMarginStart = mTitleMarginEnd = Math.max(0, a.getDimensionPixelOffset(
R.styleable.Toolbar_titleMargins, -1));
final int marginStart = a.getDimensionPixelOffset(R.styleable.Toolbar_titleMarginStart, -1);
if (marginStart >= 0) {
mTitleMarginStart = marginStart;
}
final int marginEnd = a.getDimensionPixelOffset(R.styleable.Toolbar_titleMarginEnd, -1);
if (marginEnd >= 0) {
mTitleMarginEnd = marginEnd;
}
final int marginTop = a.getDimensionPixelOffset(R.styleable.Toolbar_titleMarginTop, -1);
if (marginTop >= 0) {
mTitleMarginTop = marginTop;
}
final int marginBottom = a.getDimensionPixelOffset(R.styleable.Toolbar_titleMarginBottom,
-1);
if (marginBottom >= 0) {
mTitleMarginBottom = marginBottom;
}
final CharSequence title = a.getText(R.styleable.Toolbar_title);
if (!TextUtils.isEmpty(title)) {
setTitle(title);
}
final CharSequence subtitle = a.getText(R.styleable.Toolbar_subtitle);
if (!TextUtils.isEmpty(subtitle)) {
setSubtitle(title);
}
a.recycle();
}
/**
* Set a logo drawable from a resource id.
*
* <p>This drawable should generally take the place of title text. The logo cannot be
* clicked. Apps using a logo should also supply a description using
* {@link #setLogoDescription(int)}.</p>
*
* @param resId ID of a drawable resource
*/
public void setLogo(int resId) {
setLogo(getContext().getDrawable(resId));
}
/**
* Set a logo drawable.
*
* <p>This drawable should generally take the place of title text. The logo cannot be
* clicked. Apps using a logo should also supply a description using
* {@link #setLogoDescription(int)}.</p>
*
* @param drawable Drawable to use as a logo
*/
public void setLogo(Drawable drawable) {
if (drawable != null) {
if (mLogoView == null) {
mLogoView = new ImageView(getContext());
}
if (mLogoView.getParent() == null) {
addSystemView(mLogoView);
}
} else if (mLogoView != null && mLogoView.getParent() != null) {
removeView(mLogoView);
}
if (mLogoView != null) {
mLogoView.setImageDrawable(drawable);
}
}
/**
* Return the current logo drawable.
*
* @return The current logo drawable
* @see #setLogo(int)
* @see #setLogo(android.graphics.drawable.Drawable)
*/
public Drawable getLogo() {
return mLogoView != null ? mLogoView.getDrawable() : null;
}
/**
* Set a description of the toolbar's logo.
*
* <p>This description will be used for accessibility or other similar descriptions
* of the UI.</p>
*
* @param resId String resource id
*/
public void setLogoDescription(int resId) {
setLogoDescription(getContext().getText(resId));
}
/**
* Set a description of the toolbar's logo.
*
* <p>This description will be used for accessibility or other similar descriptions
* of the UI.</p>
*
* @param description Description to set
*/
public void setLogoDescription(CharSequence description) {
if (!TextUtils.isEmpty(description) && mLogoView == null) {
mLogoView = new ImageView(getContext());
}
if (mLogoView != null) {
mLogoView.setContentDescription(description);
}
}
/**
* Return the description of the toolbar's logo.
*
* @return A description of the logo
*/
public CharSequence getLogoDescription() {
return mLogoView != null ? mLogoView.getContentDescription() : null;
}
/**
* Return the current title displayed in the toolbar.
*
* @return The current title
*/
public CharSequence getTitle() {
return mTitleText;
}
/**
* Set 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>
*
* @param resId Resource ID of a string to set as the title
*/
public void setTitle(int resId) {
setTitle(getContext().getText(resId));
}
/**
* Set 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>
*
* @param title Title to set
*/
public void setTitle(CharSequence title) {
if (!TextUtils.isEmpty(title)) {
if (mTitleTextView == null) {
final Context context = getContext();
mTitleTextView = new TextView(context);
mTitleTextView.setTextAppearance(context, mTitleTextAppearance);
}
if (mTitleTextView.getParent() == null) {
addSystemView(mTitleTextView);
}
} else if (mTitleTextView != null && mTitleTextView.getParent() != null) {
removeView(mTitleTextView);
}
if (mTitleTextView != null) {
mTitleTextView.setText(title);
}
mTitleText = title;
}
/**
* Return the subtitle of this toolbar.
*
* @return The current subtitle
*/
public CharSequence getSubtitle() {
return mSubtitleText;
}
/**
* Set the subtitle of this toolbar.
*
* <p>Subtitles should express extended information about the current content.</p>
*
* @param resId String resource ID
*/
public void setSubtitle(int resId) {
setSubtitle(getContext().getText(resId));
}
/**
* Set the subtitle of this toolbar.
*
* <p>Subtitles should express extended information about the current content.</p>
*
* @param subtitle Subtitle to set
*/
public void setSubtitle(CharSequence subtitle) {
if (!TextUtils.isEmpty(subtitle)) {
if (mSubtitleTextView == null) {
final Context context = getContext();
mSubtitleTextView = new TextView(context);
mSubtitleTextView.setTextAppearance(context, mSubtitleTextAppearance);
}
if (mSubtitleTextView.getParent() == null) {
addSystemView(mSubtitleTextView);
}
} else if (mSubtitleTextView != null && mSubtitleTextView.getParent() != null) {
removeView(mSubtitleTextView);
}
if (mSubtitleTextView != null) {
mSubtitleTextView.setText(subtitle);
}
mSubtitleText = subtitle;
}
/**
* 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.</p>
*
* <p>If you use a navigation icon you should also set a description for its action using
* {@link #setNavigationDescription(int)}. This is used for accessibility and tooltips.</p>
*
* @param resId Resource ID of a drawable to set
*/
public void setNavigationIcon(int resId) {
setNavigationIcon(getContext().getDrawable(resId));
}
/**
* 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.</p>
*
* <p>If you use a navigation icon you should also set a description for its action using
* {@link #setNavigationDescription(int)}. This is used for accessibility and tooltips.</p>
*
* @param icon Drawable to set
*/
public void setNavigationIcon(Drawable icon) {
if (icon != null) {
ensureNavButtonView();
if (mNavButtonView.getParent() == null) {
addSystemView(mNavButtonView);
}
} else if (mNavButtonView != null && mNavButtonView.getParent() != null) {
removeView(mNavButtonView);
}
if (mNavButtonView != null) {
mNavButtonView.setImageDrawable(icon);
}
}
/**
* Return the current drawable used as the navigation icon.
*
* @return The navigation icon drawable
*/
public Drawable getNavigationIcon() {
return mNavButtonView != null ? mNavButtonView.getDrawable() : null;
}
/**
* Set a description for the navigation button.
*
* <p>This description string is used for accessibility, tooltips and other facilities
* to improve discoverability.</p>
*
* @param resId Resource ID of a string to set
*/
public void setNavigationDescription(int resId) {
setNavigationDescription(getContext().getText(resId));
}
/**
* Set a description for the navigation button.
*
* <p>This description string is used for accessibility, tooltips and other facilities
* to improve discoverability.</p>
*
* @param description String to set as the description
*/
public void setNavigationDescription(CharSequence description) {
if (!TextUtils.isEmpty(description)) {
ensureNavButtonView();
}
if (mNavButtonView != null) {
mNavButtonView.setContentDescription(description);
}
}
/**
* Set 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.</p>
*
* @param listener Listener to set
* @see #setNavigationIcon(android.graphics.drawable.Drawable)
*/
public void setNavigationOnClickListener(OnClickListener listener) {
ensureNavButtonView();
mNavButtonView.setOnClickListener(listener);
}
/**
* Return the Menu shown in the toolbar.
*
* <p>Applications that wish to populate the toolbar's menu can do so from here. To use
* an XML menu resource, use {@link #inflateMenu(int)}.</p>
*
* @return The toolbar's Menu
*/
public Menu getMenu() {
if (mMenuView == null) {
mMenuView = new ActionMenuView(getContext());
mMenuView.setOnMenuItemClickListener(mMenuViewItemClickListener);
addSystemView(mMenuView);
}
return mMenuView.getMenu();
}
private MenuInflater getMenuInflater() {
return new MenuInflater(getContext());
}
/**
* Inflate a menu resource into this toolbar.
*
* <p>Inflate an XML menu resource into this toolbar. Existing items in the menu will not
* be modified or removed.</p>
*
* @param resId ID of a menu resource to inflate
*/
public void inflateMenu(int resId) {
getMenuInflater().inflate(resId, getMenu());
}
/**
* Set a listener to respond to menu item click events.
*
* <p>This listener will be invoked whenever a user selects a menu item from
* the action buttons presented at the end of the toolbar or the associated overflow.</p>
*
* @param listener Listener to set
*/
public void setOnMenuItemClickListener(OnMenuItemClickListener listener) {
mOnMenuItemClickListener = listener;
}
private void ensureNavButtonView() {
if (mNavButtonView == null) {
mNavButtonView = new ImageButton(getContext(), null, R.attr.borderlessButtonStyle);
}
}
private void addSystemView(View v) {
final LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
lp.mViewType = LayoutParams.SYSTEM;
addView(v, lp);
}
@Override
protected Parcelable onSaveInstanceState() {
SavedState state = new SavedState(super.onSaveInstanceState());
return state;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
final SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = 0;
int height = 0;
int childState = 0;
// System views measure first.
if (shouldLayout(mNavButtonView)) {
measureChildWithMargins(mNavButtonView, widthMeasureSpec, width, heightMeasureSpec, 0);
width += mNavButtonView.getMeasuredWidth() + getHorizontalMargins(mNavButtonView);
height = Math.max(height, mNavButtonView.getMeasuredHeight() +
getVerticalMargins(mNavButtonView));
childState = combineMeasuredStates(childState, mNavButtonView.getMeasuredState());
}
if (shouldLayout(mMenuView)) {
measureChildWithMargins(mMenuView, widthMeasureSpec, width, heightMeasureSpec, 0);
width += mMenuView.getMeasuredWidth() + getHorizontalMargins(mMenuView);
height = Math.max(height, mMenuView.getMeasuredHeight() +
getVerticalMargins(mMenuView));
childState = combineMeasuredStates(childState, mMenuView.getMeasuredState());
}
if (shouldLayout(mLogoView)) {
measureChildWithMargins(mLogoView, widthMeasureSpec, width, heightMeasureSpec, 0);
width += mLogoView.getMeasuredWidth() + getHorizontalMargins(mLogoView);
height = Math.max(height, mLogoView.getMeasuredHeight() +
getVerticalMargins(mLogoView));
childState = combineMeasuredStates(childState, mLogoView.getMeasuredState());
}
int titleWidth = 0;
int titleHeight = 0;
final int titleVertMargins = mTitleMarginTop + mTitleMarginBottom;
final int titleHorizMargins = mTitleMarginStart + mTitleMarginEnd;
if (shouldLayout(mTitleTextView)) {
measureChildWithMargins(mTitleTextView, widthMeasureSpec, width + titleHorizMargins,
heightMeasureSpec, titleVertMargins);
titleWidth = mTitleTextView.getMeasuredWidth() + getHorizontalMargins(mTitleTextView);
titleHeight = mTitleTextView.getMeasuredHeight() + getVerticalMargins(mTitleTextView);
childState = combineMeasuredStates(childState, mTitleTextView.getMeasuredState());
}
if (shouldLayout(mSubtitleTextView)) {
measureChildWithMargins(mSubtitleTextView, widthMeasureSpec, width + titleHorizMargins,
heightMeasureSpec, titleHeight + titleVertMargins);
titleWidth = Math.max(titleWidth, mSubtitleTextView.getMeasuredWidth() +
getHorizontalMargins(mSubtitleTextView));
titleHeight += mSubtitleTextView.getMeasuredHeight() +
getVerticalMargins(mSubtitleTextView);
childState = combineMeasuredStates(childState, mSubtitleTextView.getMeasuredState());
}
width += titleWidth;
height = Math.max(height, titleHeight);
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp.mViewType == LayoutParams.SYSTEM || !shouldLayout(child)) {
// We already got all system views above. Skip them and GONE views.
continue;
}
measureChildWithMargins(child, widthMeasureSpec, width, heightMeasureSpec, 0);
width += child.getMeasuredWidth() + getHorizontalMargins(child);
height = Math.max(height, child.getMeasuredHeight() + getVerticalMargins(child));
childState = combineMeasuredStates(childState, child.getMeasuredState());
}
// Measurement already took padding into account for available space for the children,
// add it in for the final size.
width += getPaddingLeft() + getPaddingRight();
height += getPaddingTop() + getPaddingBottom();
final int measuredWidth = resolveSizeAndState(
Math.max(width, getSuggestedMinimumWidth()),
widthMeasureSpec, childState & MEASURED_STATE_MASK);
final int measuredHeight = resolveSizeAndState(
Math.max(height, getSuggestedMinimumHeight()),
heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT);
setMeasuredDimension(measuredWidth, measuredHeight);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
final int width = getWidth();
final int height = getHeight();
final int paddingLeft = getPaddingLeft();
final int paddingRight = getPaddingRight();
final int paddingTop = getPaddingTop();
final int paddingBottom = getPaddingBottom();
int left = paddingLeft;
int right = width - paddingRight;
if (shouldLayout(mNavButtonView)) {
if (isRtl) {
right = layoutChildRight(mNavButtonView, right);
} else {
left = layoutChildLeft(mNavButtonView, left);
}
}
if (shouldLayout(mMenuView)) {
if (isRtl) {
left = layoutChildLeft(mMenuView, left);
} else {
right = layoutChildRight(mMenuView, right);
}
}
if (shouldLayout(mLogoView)) {
if (isRtl) {
right = layoutChildRight(mLogoView, right);
} else {
left = layoutChildLeft(mLogoView, left);
}
}
final boolean layoutTitle = shouldLayout(mTitleTextView);
final boolean layoutSubtitle = shouldLayout(mSubtitleTextView);
int titleHeight = 0;
if (layoutTitle) {
final LayoutParams lp = (LayoutParams) mTitleTextView.getLayoutParams();
titleHeight += lp.topMargin + mTitleTextView.getMeasuredHeight() + lp.bottomMargin;
}
if (layoutSubtitle) {
final LayoutParams lp = (LayoutParams) mSubtitleTextView.getLayoutParams();
titleHeight += lp.bottomMargin + mTitleTextView.getMeasuredHeight() + lp.bottomMargin;
}
if (layoutTitle || layoutSubtitle) {
int titleTop;
switch (mGravity & Gravity.VERTICAL_GRAVITY_MASK) {
case Gravity.TOP:
titleTop = getPaddingTop();
break;
default:
case Gravity.CENTER_VERTICAL:
final View child = layoutTitle ? mTitleTextView : mSubtitleTextView;
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int space = height - paddingTop - paddingBottom;
int spaceAbove = (space - titleHeight) / 2;
if (spaceAbove < lp.topMargin + mTitleMarginTop) {
spaceAbove = lp.topMargin + mTitleMarginTop;
} else {
final int spaceBelow = height - paddingBottom - titleHeight -
spaceAbove - paddingTop;
if (spaceBelow < lp.bottomMargin + mTitleMarginBottom) {
spaceAbove = Math.max(0, spaceAbove -
(lp.bottomMargin + mTitleMarginBottom - spaceBelow));
}
}
titleTop = paddingTop + spaceAbove;
break;
case Gravity.BOTTOM:
titleTop = height - paddingBottom - titleHeight;
break;
}
if (isRtl) {
int titleRight = right;
int subtitleRight = right;
titleTop += mTitleMarginTop;
if (layoutTitle) {
final LayoutParams lp = (LayoutParams) mTitleTextView.getLayoutParams();
titleRight -= lp.rightMargin + mTitleMarginStart;
titleTop += lp.topMargin;
final int titleLeft = titleRight - mTitleTextView.getMeasuredWidth();
final int titleBottom = titleTop + mTitleTextView.getMeasuredHeight();
mTitleTextView.layout(titleLeft, titleTop, titleRight, titleBottom);
titleRight = titleLeft - lp.leftMargin - mTitleMarginEnd;
titleTop = titleBottom + lp.bottomMargin;
}
if (layoutSubtitle) {
final LayoutParams lp = (LayoutParams) mSubtitleTextView.getLayoutParams();
subtitleRight -= lp.rightMargin + mTitleMarginStart;
titleTop += lp.topMargin;
final int subtitleLeft = subtitleRight - mSubtitleTextView.getMeasuredWidth();
final int subtitleBottom = titleTop + mSubtitleTextView.getMeasuredHeight();
mSubtitleTextView.layout(subtitleLeft, titleTop, subtitleRight, subtitleBottom);
subtitleRight = subtitleRight - lp.leftMargin - mTitleMarginEnd;
titleTop = subtitleBottom + lp.bottomMargin;
}
right = Math.max(titleRight, subtitleRight);
} else {
int titleLeft = left;
int subtitleLeft = left;
titleTop += mTitleMarginTop;
if (layoutTitle) {
final LayoutParams lp = (LayoutParams) mTitleTextView.getLayoutParams();
titleLeft += lp.leftMargin + mTitleMarginStart;
titleTop += lp.topMargin;
final int titleRight = titleLeft + mTitleTextView.getMeasuredWidth();
final int titleBottom = titleTop + mTitleTextView.getMeasuredHeight();
mTitleTextView.layout(titleLeft, titleTop, titleRight, titleBottom);
titleLeft = titleRight + lp.rightMargin + mTitleMarginEnd;
titleTop = titleBottom + lp.bottomMargin;
}
if (layoutSubtitle) {
final LayoutParams lp = (LayoutParams) mSubtitleTextView.getLayoutParams();
subtitleLeft += lp.leftMargin + mTitleMarginStart;
titleTop += lp.topMargin;
final int subtitleRight = subtitleLeft + mSubtitleTextView.getMeasuredWidth();
final int subtitleBottom = titleTop + mSubtitleTextView.getMeasuredHeight();
mSubtitleTextView.layout(subtitleLeft, titleTop, subtitleRight, subtitleBottom);
subtitleLeft = subtitleRight + lp.rightMargin + mTitleMarginEnd;
titleTop = subtitleBottom + lp.bottomMargin;
}
left = Math.max(titleLeft, subtitleLeft);
}
}
// Get all remaining children sorted for layout. This is all prepared
// such that absolute layout direction can be used below.
addCustomViewsWithGravity(mTempViews, Gravity.LEFT);
final int leftViewsCount = mTempViews.size();
for (int i = 0; i < leftViewsCount; i++) {
left = layoutChildLeft(mTempViews.get(i), left);
}
addCustomViewsWithGravity(mTempViews, Gravity.RIGHT);
final int rightViewsCount = mTempViews.size();
for (int i = 0; i < rightViewsCount; i++) {
right = layoutChildRight(mTempViews.get(i), right);
}
// Centered views try to center with respect to the whole bar, but views pinned
// to the left or right can push the mass of centered views to one side or the other.
addCustomViewsWithGravity(mTempViews, Gravity.CENTER);
final int centerViewsWidth = getViewListMeasuredWidth(mTempViews);
final int parentCenter = paddingLeft + (width - paddingLeft - paddingRight) / 2;
final int halfCenterViewsWidth = centerViewsWidth / 2;
int centerLeft = parentCenter - halfCenterViewsWidth;
final int centerRight = centerLeft + centerViewsWidth;
if (centerLeft < left) {
centerLeft = left;
} else if (centerRight > right) {
centerLeft -= centerRight - right;
}
final int centerViewsCount = mTempViews.size();
for (int i = 0; i < centerViewsCount; i++) {
centerLeft = layoutChildLeft(mTempViews.get(i), centerLeft);
}
mTempViews.clear();
}
private int getViewListMeasuredWidth(List<View> views) {
int width = 0;
final int count = views.size();
for (int i = 0; i < count; i++) {
final View v = views.get(i);
final LayoutParams lp = (LayoutParams) v.getLayoutParams();
width += lp.leftMargin + v.getMeasuredWidth() + lp.rightMargin;
}
return width;
}
private int layoutChildLeft(View child, int left) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
left += lp.leftMargin;
final int top = getChildTop(child);
final int childWidth = child.getMeasuredWidth();
child.layout(left, top, left + childWidth, top + child.getMeasuredHeight());
left += childWidth + lp.rightMargin;
return left;
}
private int layoutChildRight(View child, int right) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
right -= lp.rightMargin;
final int top = getChildTop(child);
final int childWidth = child.getMeasuredWidth();
child.layout(right - childWidth, top, right, top + child.getMeasuredHeight());
right -= childWidth + lp.leftMargin;
return right;
}
private int getChildTop(View child) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
switch (getChildVerticalGravity(lp.gravity)) {
case Gravity.TOP:
return getPaddingTop();
case Gravity.BOTTOM:
return getPaddingBottom() - child.getMeasuredHeight() - lp.bottomMargin;
default:
case Gravity.CENTER_VERTICAL:
final int paddingTop = getPaddingTop();
final int paddingBottom = getPaddingBottom();
final int height = getHeight();
final int childHeight = child.getMeasuredHeight();
final int space = height - paddingTop - paddingBottom;
int spaceAbove = (space - childHeight) / 2;
if (spaceAbove < lp.topMargin) {
spaceAbove = lp.topMargin;
} else {
final int spaceBelow = height - paddingBottom - childHeight -
spaceAbove - paddingTop;
if (spaceBelow < lp.bottomMargin) {
spaceAbove = Math.max(0, spaceAbove - (lp.bottomMargin - spaceBelow));
}
}
return paddingTop + spaceAbove;
}
}
private int getChildVerticalGravity(int gravity) {
final int vgrav = gravity & Gravity.VERTICAL_GRAVITY_MASK;
switch (vgrav) {
case Gravity.TOP:
case Gravity.BOTTOM:
case Gravity.CENTER_VERTICAL:
return vgrav;
default:
return mGravity & Gravity.VERTICAL_GRAVITY_MASK;
}
}
/**
* Prepare a list of non-SYSTEM child views. If the layout direction is RTL
* this will be in reverse child order.
*
* @param views List to populate. It will be cleared before use.
* @param gravity Horizontal gravity to match against
*/
private void addCustomViewsWithGravity(List<View> views, int gravity) {
final boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
final int childCount = getChildCount();
final int absGrav = Gravity.getAbsoluteGravity(gravity, getLayoutDirection());
views.clear();
if (isRtl) {
for (int i = childCount - 1; i >= 0; i--) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp.mViewType != LayoutParams.SYSTEM && shouldLayout(child) &&
getChildHorizontalGravity(lp.gravity) == absGrav) {
views.add(child);
}
}
} else {
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp.mViewType != LayoutParams.SYSTEM && shouldLayout(child) &&
getChildHorizontalGravity(lp.gravity) == absGrav) {
views.add(child);
}
}
}
}
private int getChildHorizontalGravity(int gravity) {
final int ld = getLayoutDirection();
final int absGrav = Gravity.getAbsoluteGravity(gravity, ld);
final int hGrav = absGrav & Gravity.HORIZONTAL_GRAVITY_MASK;
switch (hGrav) {
case Gravity.LEFT:
case Gravity.RIGHT:
case Gravity.CENTER_HORIZONTAL:
return hGrav;
default:
return ld == LAYOUT_DIRECTION_RTL ? Gravity.RIGHT : Gravity.LEFT;
}
}
private boolean shouldLayout(View view) {
return view != null && view.getParent() == this && view.getVisibility() != GONE;
}
private int getHorizontalMargins(View v) {
final MarginLayoutParams mlp = (MarginLayoutParams) v.getLayoutParams();
return mlp.getMarginStart() + mlp.getMarginEnd();
}
private int getVerticalMargins(View v) {
final MarginLayoutParams mlp = (MarginLayoutParams) v.getLayoutParams();
return mlp.topMargin + mlp.bottomMargin;
}
@Override
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
return super.generateLayoutParams(attrs);
}
@Override
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
if (p instanceof LayoutParams) {
return new LayoutParams((LayoutParams) p);
} else if (p instanceof MarginLayoutParams) {
return new LayoutParams((MarginLayoutParams) p);
} else {
return new LayoutParams(p);
}
}
@Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return super.checkLayoutParams(p) && p instanceof LayoutParams;
}
private static boolean isCustomView(View child) {
return ((LayoutParams) child.getLayoutParams()).mViewType == LayoutParams.CUSTOM;
}
/**
* Interface responsible for receiving menu item click events if the items themselves
* do not have individual item click listeners.
*/
public interface OnMenuItemClickListener {
/**
* This method will be invoked when a menu item is clicked if the item itself did
* not already handle the event.
*
* @param item {@link MenuItem} that was clicked
* @return <code>true</code> if the event was handled, <code>false</code> otherwise.
*/
public boolean onMenuItemClick(MenuItem item);
}
/**
* Layout information for child views of Toolbars.
*
* @attr ref android.R.styleable#Toolbar_LayoutParams_layout_gravity
*/
public static class LayoutParams extends MarginLayoutParams {
/**
* Gravity for the view associated with these LayoutParams.
*
* @see android.view.Gravity
*/
@ViewDebug.ExportedProperty(category = "layout", mapping = {
@ViewDebug.IntToString(from = -1, to = "NONE"),
@ViewDebug.IntToString(from = Gravity.NO_GRAVITY, to = "NONE"),
@ViewDebug.IntToString(from = Gravity.TOP, to = "TOP"),
@ViewDebug.IntToString(from = Gravity.BOTTOM, to = "BOTTOM"),
@ViewDebug.IntToString(from = Gravity.LEFT, to = "LEFT"),
@ViewDebug.IntToString(from = Gravity.RIGHT, to = "RIGHT"),
@ViewDebug.IntToString(from = Gravity.START, to = "START"),
@ViewDebug.IntToString(from = Gravity.END, to = "END"),
@ViewDebug.IntToString(from = Gravity.CENTER_VERTICAL, to = "CENTER_VERTICAL"),
@ViewDebug.IntToString(from = Gravity.FILL_VERTICAL, to = "FILL_VERTICAL"),
@ViewDebug.IntToString(from = Gravity.CENTER_HORIZONTAL, to = "CENTER_HORIZONTAL"),
@ViewDebug.IntToString(from = Gravity.FILL_HORIZONTAL, to = "FILL_HORIZONTAL"),
@ViewDebug.IntToString(from = Gravity.CENTER, to = "CENTER"),
@ViewDebug.IntToString(from = Gravity.FILL, to = "FILL")
})
public int gravity = Gravity.NO_GRAVITY;
static final int CUSTOM = 0;
static final int SYSTEM = 1;
int mViewType = CUSTOM;
public LayoutParams(@NonNull Context c, AttributeSet attrs) {
super(c, attrs);
TypedArray a = c.obtainStyledAttributes(attrs,
com.android.internal.R.styleable.Toolbar_LayoutParams);
gravity = a.getInt(
com.android.internal.R.styleable.Toolbar_LayoutParams_layout_gravity,
Gravity.NO_GRAVITY);
a.recycle();
}
public LayoutParams(int width, int height) {
super(width, height);
this.gravity = Gravity.CENTER_VERTICAL | Gravity.START;
}
public LayoutParams(int width, int height, int gravity) {
super(width, height);
this.gravity = gravity;
}
public LayoutParams(int gravity) {
this(WRAP_CONTENT, MATCH_PARENT, gravity);
}
public LayoutParams(LayoutParams source) {
super(source);
this.gravity = source.gravity;
}
public LayoutParams(MarginLayoutParams source) {
super(source);
}
public LayoutParams(ViewGroup.LayoutParams source) {
super(source);
}
}
static class SavedState extends BaseSavedState {
public SavedState(Parcel source) {
super(source);
}
public SavedState(Parcelable superState) {
super(superState);
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
}
public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
@Override
public SavedState createFromParcel(Parcel source) {
return new SavedState(source);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
}