diff options
Diffstat (limited to 'core/java/android/widget/Toolbar.java')
-rw-r--r-- | core/java/android/widget/Toolbar.java | 1048 |
1 files changed, 1048 insertions, 0 deletions
diff --git a/core/java/android/widget/Toolbar.java b/core/java/android/widget/Toolbar.java new file mode 100644 index 0000000..075feba --- /dev/null +++ b/core/java/android/widget/Toolbar.java @@ -0,0 +1,1048 @@ +/* + * 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(getChildAt(i), left); + } + + addCustomViewsWithGravity(mTempViews, Gravity.RIGHT); + final int rightViewsCount = mTempViews.size(); + for (int i = 0; i < rightViewsCount; i++) { + right = layoutChildRight(getChildAt(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(getChildAt(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; + int top = getChildTop(child); + child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight()); + left += lp.rightMargin; + return left; + } + + private int layoutChildRight(View child, int right) { + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + right -= lp.rightMargin; + int top = getChildTop(child); + child.layout(right - child.getMeasuredWidth(), top, right, top + child.getMeasuredHeight()); + right -= 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]; + } + }; + } +} |