| Index: chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBarLayout.java
|
| ===================================================================
|
| --- chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBarLayout.java (revision 284463)
|
| +++ chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBarLayout.java (working copy)
|
| @@ -5,8 +5,13 @@
|
| package org.chromium.chrome.browser.infobar;
|
|
|
| import android.content.Context;
|
| +import android.content.res.Resources;
|
| +import android.content.res.TypedArray;
|
| +import android.graphics.Color;
|
| +import android.graphics.drawable.Drawable;
|
| import android.text.TextUtils;
|
| import android.text.method.LinkMovementMethod;
|
| +import android.view.Gravity;
|
| import android.view.LayoutInflater;
|
| import android.view.View;
|
| import android.view.ViewGroup;
|
| @@ -15,732 +20,525 @@
|
| import android.widget.ImageView;
|
| import android.widget.TextView;
|
|
|
| +import org.chromium.base.ApiCompatibilityUtils;
|
| import org.chromium.chrome.R;
|
| -import org.chromium.ui.base.LocalizationUtils;
|
|
|
| -import java.util.ArrayList;
|
| -
|
| /**
|
| - * Layout that can be used to arrange an InfoBar's View.
|
| - * All InfoBars consist of at least:
|
| - * - An icon representing the InfoBar's purpose on the left side.
|
| + * Layout that arranges an InfoBar's views. An InfoBarLayout consists of:
|
| * - A message describing the action that the user can take.
|
| * - A close button on the right side.
|
| + * - (optional) An icon representing the infobar's purpose on the left side.
|
| + * - (optional) Additional "custom" views (e.g. a checkbox and text, or a pair of spinners)
|
| + * - (optional) One or two buttons with text at the bottom.
|
| *
|
| - * Views should never be added with anything but a call to addGroup() to ensure that groups are not
|
| - * broken apart.
|
| + * When adding custom views, widths and heights defined in the LayoutParams will be ignored.
|
| + * However, setting a minimum width in another way, like TextView.getMinWidth(), should still be
|
| + * obeyed.
|
| *
|
| - * Widths and heights defined in the LayoutParams will be overwritten due to the nature of the
|
| - * layout algorithm. However, setting a minimum width in another way, like TextView.getMinWidth(),
|
| - * should still be obeyed.
|
| - *
|
| * Logic for what happens when things are clicked should be implemented by the InfoBarView.
|
| */
|
| public class InfoBarLayout extends ViewGroup implements View.OnClickListener {
|
| - private static final String TAG = "InfoBarLayout";
|
|
|
| /**
|
| * Parameters used for laying out children.
|
| */
|
| - public static class LayoutParams extends ViewGroup.LayoutParams {
|
| - /** Alignment parameters that determine where in the main row an item will float. */
|
| - public static final int ALIGN_START = 0;
|
| - public static final int ALIGN_END = 1;
|
| + private static class LayoutParams extends ViewGroup.LayoutParams {
|
|
|
| - /** Whether the View is meant for the main row. */
|
| - public boolean isInMainRow;
|
| + public int startMargin;
|
| + public int endMargin;
|
| + public int topMargin;
|
| + public int bottomMargin;
|
|
|
| - /** Views grouped together are laid out together immediately adjacent to each other. */
|
| - public boolean isGroupedWithNextView;
|
| + // Where this view will be laid out. These values are assigned in onMeasure() and used in
|
| + // onLayout().
|
| + public int start;
|
| + public int top;
|
|
|
| - /** When on the main row, indicates whether the control floats on the left or the right. */
|
| - public int align;
|
| + LayoutParams(int startMargin, int topMargin, int endMargin, int bottomMargin) {
|
| + super(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
|
| + this.startMargin = startMargin;
|
| + this.topMargin = topMargin;
|
| + this.endMargin = endMargin;
|
| + this.bottomMargin = bottomMargin;
|
| + }
|
| + }
|
|
|
| - /** If the control is a button, ID of the resource that was last used as its background. */
|
| - public int background;
|
| + private static class Group {
|
| + public View[] views;
|
|
|
| - public LayoutParams() {
|
| - super(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
|
| - align = ALIGN_END;
|
| - isInMainRow = true;
|
| + /**
|
| + * The gravity of each view in Group. Must be either Gravity.START, Gravity.END, or
|
| + * Gravity.FILL_HORIZONTAL.
|
| + */
|
| + public int gravity = Gravity.START;
|
| +
|
| + /** Whether the views are vertically stacked. */
|
| + public boolean isStacked;
|
| +
|
| + void setHorizontalMode(int horizontalSpacing, int startMargin, int endMargin) {
|
| + isStacked = false;
|
| + for (int i = 0; i < views.length; i++) {
|
| + LayoutParams lp = (LayoutParams) views[i].getLayoutParams();
|
| + lp.startMargin = i == 0 ? startMargin : horizontalSpacing;
|
| + lp.topMargin = 0;
|
| + lp.endMargin = i == views.length - 1 ? endMargin : 0;
|
| + lp.bottomMargin = 0;
|
| + }
|
| +
|
| }
|
|
|
| - public LayoutParams(LayoutParams other) {
|
| - super(other);
|
| - isGroupedWithNextView = other.isGroupedWithNextView;
|
| - align = other.align;
|
| - isInMainRow = other.isInMainRow;
|
| + void setVerticalMode(int verticalSpacing, int bottomMargin) {
|
| + isStacked = true;
|
| + for (int i = 0; i < views.length; i++) {
|
| + LayoutParams lp = (LayoutParams) views[i].getLayoutParams();
|
| + lp.startMargin = 0;
|
| + lp.topMargin = i == 0 ? 0 : verticalSpacing;
|
| + lp.endMargin = 0;
|
| + lp.bottomMargin = i == views.length - 1 ? bottomMargin : 0;
|
| + }
|
| }
|
| }
|
|
|
| - private static class GroupInfo {
|
| - public int numViews;
|
| - public int width;
|
| - public int greatestMemberWidth;
|
| - public int endIndex;
|
| - public boolean hasButton;
|
| - };
|
| + private static final int ROW_MAIN = 1;
|
| + private static final int ROW_OTHER = 2;
|
|
|
| - private final int mDimensionMinSize;
|
| - private final int mDimensionMargin;
|
| - private final int mDimensionIconSize;
|
| - private final boolean mLayoutRTL;
|
| - private final InfoBarView mInfoBarView;
|
| + private final int mMargin;
|
| + private final int mIconSize;
|
| + private final int mMinWidth;
|
| + private final int mAccentColor;
|
|
|
| - private final ImageView mIconView;
|
| + private final InfoBarView mInfoBarView;
|
| private final TextView mMessageView;
|
| private final ImageButton mCloseButton;
|
| + private ImageView mIconView;
|
|
|
| - /** Background resource IDs to use for the buttons. */
|
| - private final int mBackgroundFloating;
|
| - private final int mBackgroundFullLeft;
|
| - private final int mBackgroundFullRight;
|
| + private Group mMainGroup;
|
| + private Group mCustomGroup;
|
| + private Group mButtonGroup;
|
|
|
| /**
|
| - * Indices of child Views that start new layout rows.
|
| - * The last entry is the number of child Views, allowing calculation of the size of each row by
|
| - * taking the difference between subsequent indices.
|
| + * These values are used during onMeasure() to track where the next view will be placed.
|
| + *
|
| + * mWidth is the infobar width.
|
| + * [mStart, mEnd) is the range of unoccupied space on the current row.
|
| + * mTop and mBottom are the top and bottom of the current row.
|
| + *
|
| + * These values, along with a view's gravity, are used to position the next view.
|
| + * These values are updated after placing a view and after starting a new row.
|
| */
|
| - private final ArrayList<Integer> mIndicesOfRows;
|
| + private int mWidth;
|
| + private int mStart;
|
| + private int mEnd;
|
| + private int mTop;
|
| + private int mBottom;
|
|
|
| /**
|
| - * Constructs the layout for the specified InfoBar.
|
| + * Constructs a layout for the specified InfoBar. After calling this, be sure to set the
|
| + * message, the buttons, and/or the custom content using setMessage(), setButtons(), and
|
| + * setCustomContent().
|
| + *
|
| * @param context The context used to render.
|
| * @param infoBarView InfoBarView that listens to events.
|
| * @param iconResourceId ID of the icon to use for the InfoBar.
|
| + * @param message The message to show in the infobar.
|
| */
|
| - public InfoBarLayout(Context context, InfoBarView infoBarView, int iconResourceId) {
|
| + public InfoBarLayout(Context context, InfoBarView infoBarView, int iconResourceId,
|
| + CharSequence message) {
|
| super(context);
|
| - mIndicesOfRows = new ArrayList<Integer>();
|
| - mLayoutRTL = LocalizationUtils.isLayoutRtl();
|
| mInfoBarView = infoBarView;
|
|
|
| - // Determine what backgrounds we'll be needing for the buttons.
|
| - mBackgroundFloating = R.drawable.infobar_button_normal_floating;
|
| - mBackgroundFullLeft = R.drawable.infobar_button_normal_full_left;
|
| - mBackgroundFullRight = R.drawable.infobar_button_normal_full_right;
|
| -
|
| // Grab the dimensions.
|
| - mDimensionMinSize =
|
| - context.getResources().getDimensionPixelSize(R.dimen.infobar_min_size);
|
| - mDimensionMargin =
|
| - context.getResources().getDimensionPixelSize(R.dimen.infobar_margin);
|
| - mDimensionIconSize =
|
| - context.getResources().getDimensionPixelSize(R.dimen.infobar_icon_size);
|
| + Resources res = getResources();
|
| + mMargin = res.getDimensionPixelOffset(R.dimen.infobar_margin);
|
| + mIconSize = res.getDimensionPixelSize(R.dimen.infobar_icon_size);
|
| + mMinWidth = res.getDimensionPixelSize(R.dimen.infobar_min_width);
|
| + mAccentColor = res.getColor(R.color.infobar_accent_blue);
|
|
|
| - // Create the main controls.
|
| + // Set up the close button. Apply padding so it has a big touch target.
|
| mCloseButton = new ImageButton(context);
|
| - mIconView = new ImageView(context);
|
| - mMessageView = (TextView) LayoutInflater.from(context).inflate(R.layout.infobar_text, null);
|
| - addGroup(mCloseButton, mIconView, mMessageView);
|
| -
|
| - // Set up the close button.
|
| mCloseButton.setId(R.id.infobar_close_button);
|
| mCloseButton.setImageResource(R.drawable.infobar_close_button);
|
| - mCloseButton.setBackgroundResource(R.drawable.infobar_close_bg);
|
| + TypedArray a = getContext().obtainStyledAttributes(
|
| + new int [] {android.R.attr.selectableItemBackground});
|
| + Drawable closeButtonBackground = a.getDrawable(0);
|
| + a.recycle();
|
| + ApiCompatibilityUtils.setBackgroundForView(mCloseButton, closeButtonBackground);
|
| + mCloseButton.setPadding(mMargin, mMargin, mMargin, mMargin);
|
| mCloseButton.setOnClickListener(this);
|
| + mCloseButton.setContentDescription(res.getString(R.string.infobar_close));
|
| + mCloseButton.setLayoutParams(new LayoutParams(0, -mMargin, -mMargin, -mMargin));
|
| + addView(mCloseButton);
|
|
|
| - mCloseButton.setContentDescription(getResources().getString(R.string.infobar_close));
|
| -
|
| // Set up the icon.
|
| - mIconView.setFocusable(false);
|
| if (iconResourceId != 0) {
|
| + mIconView = new ImageView(context);
|
| mIconView.setImageResource(iconResourceId);
|
| - } else {
|
| - mIconView.setVisibility(View.INVISIBLE);
|
| + mIconView.setFocusable(false);
|
| + mIconView.setLayoutParams(new LayoutParams(0, 0, mMargin / 2, 0));
|
| + mIconView.getLayoutParams().width = mIconSize;
|
| + mIconView.getLayoutParams().height = mIconSize;
|
| }
|
|
|
| - // Set up the TextView.
|
| + // Set up the message view.
|
| + mMessageView = (TextView) LayoutInflater.from(context).inflate(R.layout.infobar_text, null);
|
| + mMessageView.setText(message, TextView.BufferType.SPANNABLE);
|
| mMessageView.setMovementMethod(LinkMovementMethod.getInstance());
|
| - mMessageView.setText(infoBarView.getMessageText(context), TextView.BufferType.SPANNABLE);
|
| + mMessageView.setLinkTextColor(mAccentColor);
|
| + mMessageView.setLayoutParams(new LayoutParams(0, mMargin / 4, 0, 0));
|
|
|
| - // Only the close button floats to the right; the icon and the message both float left.
|
| - ((LayoutParams) mIconView.getLayoutParams()).align = LayoutParams.ALIGN_START;
|
| - ((LayoutParams) mMessageView.getLayoutParams()).align = LayoutParams.ALIGN_START;
|
| -
|
| - // Vertically center the icon and close buttons of an unstretched InfoBar. If the InfoBar
|
| - // is stretched, they both stay in place.
|
| - mIconView.getLayoutParams().width = mDimensionIconSize;
|
| - mIconView.getLayoutParams().height = mDimensionIconSize;
|
| -
|
| - // We apply padding to the close button so that it has a big touch target.
|
| - int closeButtonHeight = mCloseButton.getDrawable().getIntrinsicHeight();
|
| - int closePadding = (mDimensionMinSize - closeButtonHeight) / 2;
|
| - if (closePadding >= 0) {
|
| - mCloseButton.setPadding(closePadding, closePadding, closePadding, closePadding);
|
| + if (mIconView != null) {
|
| + mMainGroup = addGroup(mIconView, mMessageView);
|
| } else {
|
| - assert closePadding >= 0 : "Assets are too large for this layout.";
|
| + mMainGroup = addGroup(mMessageView);
|
| }
|
| -
|
| - // Add all of the other InfoBar specific controls.
|
| - infoBarView.createContent(this);
|
| }
|
|
|
| - @Override
|
| - protected LayoutParams generateDefaultLayoutParams() {
|
| - return new LayoutParams();
|
| + /**
|
| + * Sets the message to show on the infobar.
|
| + */
|
| + public void setMessage(CharSequence message) {
|
| + mMessageView.setText(message, TextView.BufferType.SPANNABLE);
|
| }
|
|
|
| /**
|
| - * Add a view to the Layout.
|
| - * This function must never be called with an index that isn't -1 to ensure that groups aren't
|
| - * broken apart.
|
| + * Sets the custom content of the infobar. These views will be displayed in addition to the
|
| + * standard infobar controls (icon, text, buttons). Depending on the available space, view1 and
|
| + * view2 will be laid out:
|
| + * - Side by side on the main row,
|
| + * - Side by side on a separate row, each taking up half the width of the infobar,
|
| + * - Stacked above each other on two separate rows, taking up the full width of the infobar.
|
| */
|
| - @Override
|
| - public void addView(View child, int index, ViewGroup.LayoutParams params) {
|
| - if (index == -1) {
|
| - super.addView(child, index, params);
|
| - } else {
|
| - assert false : "Adding children at random places can break group structure.";
|
| - super.addView(child, -1, params);
|
| - }
|
| + public void setCustomContent(View view1, View view2) {
|
| + mCustomGroup = addGroup(view1, view2);
|
| }
|
|
|
| /**
|
| - * Add a group of Views that are measured and laid out together.
|
| + * Sets the custom content of the infobar to a single view. This view will be displayed in
|
| + * addition to the standard infobar controls. Depending on the available space, the view will be
|
| + * displayed:
|
| + * - On the main row, start-aligned or end-aligned depending on whether there are also
|
| + * buttons on the main row, OR
|
| + * - On a separate row, start-aligned
|
| */
|
| - public void addGroup(View... group) {
|
| - for (int i = 0; i < group.length; i++) {
|
| - final View member = group[i];
|
| - addView(member);
|
| -
|
| - LayoutParams params = (LayoutParams) member.getLayoutParams();
|
| - params.isGroupedWithNextView = (i != group.length - 1);
|
| - }
|
| + public void setCustomContent(View view) {
|
| + mCustomGroup = addGroup(view);
|
| }
|
|
|
| /**
|
| - * Add up to two buttons to the layout.
|
| - *
|
| - * Buttons with null text are hidden from view. The secondary button may only exist if the
|
| - * primary button does.
|
| - *
|
| - * @param primaryText Text for the primary button.
|
| - * @param secondaryText Text for the secondary button.
|
| + * Calls setButtons(primaryText, secondaryText, null).
|
| */
|
| - public void addButtons(String primaryText, String secondaryText) {
|
| - Button primaryButton = null;
|
| - Button secondaryButton = null;
|
| -
|
| - if (!TextUtils.isEmpty(secondaryText)) {
|
| - secondaryButton = (Button) LayoutInflater.from(getContext()).inflate(
|
| - R.layout.infobar_button, null);
|
| - secondaryButton.setId(R.id.button_secondary);
|
| - secondaryButton.setOnClickListener(this);
|
| - secondaryButton.setText(secondaryText);
|
| - }
|
| -
|
| - if (!TextUtils.isEmpty(primaryText)) {
|
| - primaryButton = (Button) LayoutInflater.from(getContext()).inflate(
|
| - R.layout.infobar_button, null);
|
| - primaryButton.setId(R.id.button_primary);
|
| - primaryButton.setOnClickListener(this);
|
| - primaryButton.setText(primaryText);
|
| - }
|
| -
|
| - // Group the buttons together so that they are laid out next to each other.
|
| - if (primaryButton == null && secondaryButton != null) {
|
| - assert false : "When using only one button, make it the primary button.";
|
| - } else if (primaryButton != null && secondaryButton != null) {
|
| - addGroup(secondaryButton, primaryButton);
|
| - } else if (primaryButton != null) {
|
| - addGroup(primaryButton);
|
| - }
|
| + public void setButtons(String primaryText, String secondaryText) {
|
| + setButtons(primaryText, secondaryText, null);
|
| }
|
|
|
| - @Override
|
| - protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
| - final int rowWidth = right - left;
|
| - int rowTop = layoutMainRow(rowWidth);
|
| - for (int row = 1; row < mIndicesOfRows.size() - 1; row++) {
|
| - rowTop = layoutRow(row, rowTop, rowWidth);
|
| - }
|
| - }
|
| -
|
| /**
|
| - * Lays out the controls in the main row.
|
| + * Adds one, two, or three buttons to the layout.
|
| *
|
| - * This method is complicated mainly because of the arbitrariness for when a control can
|
| - * float either left or right, and whether we're doing an RTL layout.
|
| - *
|
| - * Layout proceeds in three phases:
|
| - * - Laying out of the icon and close button are done separately from the rest of the controls
|
| - * because they are locked into their respective corners. These two controls bound the rest
|
| - * of the controls in the main row.
|
| - *
|
| - * - Items floating to the left are then laid out, traversing the children array in a forwards
|
| - * manner. This includes the InfoBar message.
|
| - *
|
| - * - A final pass lays out items aligned to the end of the bar, traversing the children array
|
| - * backwards so that the correct ordering of the children is preserved. Going forwards would
|
| - * cause buttons to flip (e.g.).
|
| - *
|
| - * @param width Maximum width of the row.
|
| - * @return How tall the main row is.
|
| + * @param primaryText Text for the primary button.
|
| + * @param secondaryText Text for the secondary button, or null if there isn't a second button.
|
| + * @param tertiaryText Text for the tertiary button, or null if there isn't a third button.
|
| */
|
| - private int layoutMainRow(int width) {
|
| - final int rowStart = mIndicesOfRows.get(0);
|
| - final int rowEnd = mIndicesOfRows.get(1);
|
| - final int rowHeight = computeMainRowHeight(rowStart, rowEnd);
|
| + public void setButtons(String primaryText, String secondaryText, String tertiaryText) {
|
| + if (TextUtils.isEmpty(primaryText)) return;
|
|
|
| - // Lay out the icon and the close button.
|
| - int closeLeft;
|
| - int iconPadding = (mDimensionMinSize - mDimensionIconSize) / 2;
|
| - int iconLeft = iconPadding;
|
| - if (mLayoutRTL) {
|
| - iconLeft += width - mDimensionMinSize;
|
| - closeLeft = 0;
|
| - } else {
|
| - closeLeft = width - mCloseButton.getMeasuredWidth();
|
| - }
|
| - mIconView.layout(iconLeft, iconPadding, iconLeft + mDimensionIconSize,
|
| - iconPadding + mDimensionIconSize);
|
| - mCloseButton.layout(closeLeft, 0, closeLeft + mDimensionMinSize, mDimensionMinSize);
|
| + LayoutInflater inflater = LayoutInflater.from(getContext());
|
| + Button primaryButton = (Button) inflater.inflate(R.layout.infobar_button, null);
|
| + primaryButton.setId(R.id.button_primary);
|
| + primaryButton.setOnClickListener(this);
|
| + primaryButton.setText(primaryText);
|
| + primaryButton.setBackgroundResource(R.drawable.btn_infobar_blue);
|
| + primaryButton.setTextColor(Color.WHITE);
|
|
|
| - // Go from left to right to catch all items aligned with the start of the InfoBar.
|
| - int rowLeft = mDimensionMinSize;
|
| - int rowRight = width - mDimensionMinSize;
|
| - for (int i = rowStart; i < rowEnd; i++) {
|
| - final View child = getChildAt(i);
|
| - LayoutParams params = (LayoutParams) child.getLayoutParams();
|
| - if (params.align != LayoutParams.ALIGN_START || child.getVisibility() == View.GONE
|
| - || child == mCloseButton || child == mIconView) {
|
| - continue;
|
| - }
|
| -
|
| - // Everything is vertically centered.
|
| - int childTop = (rowHeight - child.getMeasuredHeight()) / 2;
|
| - int childLeft;
|
| -
|
| - if (mLayoutRTL) {
|
| - if (!isMainControl(child)) rowRight -= mDimensionMargin;
|
| - childLeft = rowRight - child.getMeasuredWidth();
|
| - rowRight -= child.getMeasuredWidth();
|
| - } else {
|
| - if (!isMainControl(child)) rowLeft += mDimensionMargin;
|
| - childLeft = rowLeft;
|
| - rowLeft += child.getMeasuredWidth();
|
| - }
|
| -
|
| - child.layout(childLeft, childTop, childLeft + child.getMeasuredWidth(),
|
| - childTop + child.getMeasuredHeight());
|
| + if (TextUtils.isEmpty(secondaryText)) {
|
| + mButtonGroup = addGroup(primaryButton);
|
| + return;
|
| }
|
|
|
| - // Go from right to left to catch all items aligned with the end of the InfoBar.
|
| - for (int i = rowEnd - 1; i >= rowStart; i--) {
|
| - final View child = getChildAt(i);
|
| - LayoutParams params = (LayoutParams) child.getLayoutParams();
|
| - if (params.align != LayoutParams.ALIGN_END || child.getVisibility() == View.GONE
|
| - || child == mCloseButton || child == mIconView) {
|
| - continue;
|
| - }
|
| + Button secondaryButton = (Button) inflater.inflate(R.layout.infobar_button, null);
|
| + secondaryButton.setId(R.id.button_secondary);
|
| + secondaryButton.setOnClickListener(this);
|
| + secondaryButton.setText(secondaryText);
|
| + secondaryButton.setTextColor(mAccentColor);
|
|
|
| - // Everything is vertically centered.
|
| - int childTop = (rowHeight - child.getMeasuredHeight()) / 2;
|
| - int childLeft;
|
| -
|
| - if (!mLayoutRTL) {
|
| - childLeft = rowRight - child.getMeasuredWidth();
|
| - rowRight -= child.getMeasuredWidth();
|
| - if (!isMainControl(child)) rowRight -= mDimensionMargin;
|
| - } else {
|
| - childLeft = rowLeft;
|
| - rowLeft += child.getMeasuredWidth();
|
| - if (!isMainControl(child)) rowLeft += mDimensionMargin;
|
| - }
|
| -
|
| - child.layout(childLeft, childTop, childLeft + child.getMeasuredWidth(),
|
| - childTop + child.getMeasuredHeight());
|
| + if (TextUtils.isEmpty(tertiaryText)) {
|
| + mButtonGroup = addGroup(secondaryButton, primaryButton);
|
| + return;
|
| }
|
|
|
| - return rowHeight;
|
| + Button tertiaryButton = (Button) inflater.inflate(R.layout.infobar_button, null);
|
| + tertiaryButton.setId(R.id.button_tertiary);
|
| + tertiaryButton.setOnClickListener(this);
|
| + tertiaryButton.setText(tertiaryText);
|
| + tertiaryButton.setPadding(mMargin / 2, tertiaryButton.getPaddingTop(), mMargin / 2,
|
| + tertiaryButton.getPaddingBottom());
|
| + tertiaryButton.setTextColor(
|
| + getContext().getResources().getColor(R.color.infobar_tertiary_button_text));
|
| +
|
| + mButtonGroup = addGroup(tertiaryButton, secondaryButton, primaryButton);
|
| }
|
|
|
| /**
|
| - * Lays out the controls in the row other than the main one.
|
| - *
|
| - * This case is much simpler than the main row since the items are all equally sized and simply
|
| - * entails moving through the children and laying them down from the start of the InfoBar to the
|
| - * end.
|
| - *
|
| - * @param row Index of the row
|
| - * @param rowTop Y-coordinate of the layout the controls should be aligned to.
|
| - * @param width Maximum width of the row.
|
| - * @return How tall the row is.
|
| + * Adds a group of Views that are measured and laid out together.
|
| */
|
| - private int layoutRow(int row, int rowTop, int width) {
|
| - final int rowStart = mIndicesOfRows.get(row);
|
| - final int rowEnd = mIndicesOfRows.get(row + 1);
|
| - final boolean hasButton = isButton(getChildAt(rowStart));
|
| + private Group addGroup(View... views) {
|
| + Group group = new Group();
|
| + group.views = views;
|
|
|
| - int rowLeft = hasButton ? 0 : mDimensionMargin;
|
| - int rowRight = width - (hasButton ? 0 : mDimensionMargin);
|
| -
|
| - for (int i = rowStart; i < rowEnd; i++) {
|
| - final View child = getChildAt(i);
|
| - if (child.getVisibility() == View.GONE) continue;
|
| -
|
| - int childLeft;
|
| - if (mLayoutRTL) {
|
| - childLeft = rowRight - child.getMeasuredWidth();
|
| - rowRight -= child.getMeasuredWidth() + (hasButton ? 0 : mDimensionMargin);
|
| - } else {
|
| - childLeft = rowLeft;
|
| - rowLeft += child.getMeasuredWidth() + (hasButton ? 0 : mDimensionMargin);
|
| - }
|
| -
|
| - child.layout(childLeft, rowTop, childLeft + child.getMeasuredWidth(),
|
| - rowTop + child.getMeasuredHeight());
|
| + for (View v : views) {
|
| + addView(v);
|
| }
|
| -
|
| - return rowTop + computeRowHeight(rowStart, rowEnd);
|
| + return group;
|
| }
|
|
|
| - /**
|
| - * Checks if the child is one of the main InfoBar controls.
|
| - * @param child View to check.
|
| - * @return True if the child is one of the main controls.
|
| - */
|
| - private boolean isMainControl(View child) {
|
| - return child == mIconView || child == mMessageView || child == mCloseButton;
|
| + @Override
|
| + protected LayoutParams generateDefaultLayoutParams() {
|
| + return new LayoutParams(0, 0, 0, 0);
|
| }
|
|
|
| - /**
|
| - * Marks that the given index is the start of its own row.
|
| - * @param rowStartIndex Index of the child view at the start of the next row.
|
| - */
|
| - private void addRowStartIndex(int rowStartIndex) {
|
| - if (mIndicesOfRows.size() == 0
|
| - || rowStartIndex != mIndicesOfRows.get(mIndicesOfRows.size() - 1)) {
|
| - mIndicesOfRows.add(rowStartIndex);
|
| - }
|
| - }
|
| + @Override
|
| + protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
| + // Place all the views in the positions already determined during onMeasure().
|
| + int width = right - left;
|
| + boolean isRtl = ApiCompatibilityUtils.isLayoutRtl(this);
|
|
|
| - /**
|
| - * Computes properties of the next group of Views to assign to rows.
|
| - * @param startIndex Index of the first child in the group.
|
| - * @return GroupInfo containing information about the current group.
|
| - */
|
| - private GroupInfo getNextGroup(int startIndex) {
|
| - GroupInfo groupInfo = new GroupInfo();
|
| - groupInfo.endIndex = startIndex;
|
| + for (int i = 0; i < getChildCount(); i++) {
|
| + View child = getChildAt(i);
|
| + LayoutParams lp = (LayoutParams) child.getLayoutParams();
|
| + int childLeft = lp.start;
|
| + int childRight = lp.start + child.getMeasuredWidth();
|
|
|
| - final int childCount = getChildCount();
|
| - int currentChildIndex = startIndex;
|
| - while (groupInfo.endIndex < childCount) {
|
| - final View groupChild = getChildAt(groupInfo.endIndex);
|
| - if (groupChild.getVisibility() != View.GONE) {
|
| - groupInfo.hasButton |= isButton(groupChild);
|
| - groupInfo.width += groupChild.getMeasuredWidth();
|
| - groupInfo.greatestMemberWidth =
|
| - Math.max(groupInfo.greatestMemberWidth, groupChild.getMeasuredWidth());
|
| - groupInfo.numViews++;
|
| + if (isRtl) {
|
| + int tmp = width - childRight;
|
| + childRight = width - childLeft;
|
| + childLeft = tmp;
|
| }
|
| - groupInfo.endIndex++;
|
|
|
| - LayoutParams params = (LayoutParams) groupChild.getLayoutParams();
|
| - if (!params.isGroupedWithNextView) break;
|
| + child.layout(childLeft, lp.top, childRight, lp.top + child.getMeasuredHeight());
|
| }
|
| -
|
| - return groupInfo;
|
| }
|
|
|
| + /**
|
| + * Measures *and* assigns positions to all of the views in the infobar. These positions are
|
| + * saved in each view's LayoutParams (lp.start and lp.top) and used during onLayout(). All of
|
| + * the interesting logic happens inside onMeasure(); onLayout() just assigns the already-
|
| + * determined positions and mirrors everything for RTL, if needed.
|
| + */
|
| @Override
|
| - protected void measureChild(View child, int widthSpec, int heightSpec) {
|
| - // If a control is on the main row, then it should be only as large as it wants to be.
|
| - // Otherwise, it must occupy the same amount of space as everything else on its row.
|
| - LayoutParams params = (LayoutParams) child.getLayoutParams();
|
| - params.width = params.isInMainRow ? LayoutParams.WRAP_CONTENT : LayoutParams.MATCH_PARENT;
|
| - super.measureChild(child, widthSpec, heightSpec);
|
| - }
|
| -
|
| - @Override
|
| protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
| assert getLayoutParams().height == LayoutParams.WRAP_CONTENT
|
| : "InfoBar heights cannot be constrained.";
|
|
|
| - final int maxWidth = MeasureSpec.getSize(widthMeasureSpec);
|
| - mIndicesOfRows.clear();
|
| -
|
| - // Measure all children with the assumption that they may take up the full size of the
|
| - // parent. This determines how big each child wants to be.
|
| - final int childCount = getChildCount();
|
| - for (int numChild = 0; numChild < childCount; numChild++) {
|
| - final View child = getChildAt(numChild);
|
| - if (child.getVisibility() == View.GONE) continue;
|
| - ((LayoutParams) child.getLayoutParams()).isInMainRow = true;
|
| - measureChild(child, widthMeasureSpec, heightMeasureSpec);
|
| + // Measure all children without imposing any size constraints on them. This determines how
|
| + // big each child wants to be.
|
| + int unspecifiedSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
|
| + for (int i = 0; i < getChildCount(); i++) {
|
| + measureChild(getChildAt(i), unspecifiedSpec, unspecifiedSpec);
|
| }
|
|
|
| - // Allocate as many Views as possible to the main row, then place everything else on the
|
| - // following rows.
|
| - int currentChildIndex = measureMainRow(maxWidth);
|
| - measureRemainingRows(maxWidth, currentChildIndex);
|
| + // Avoid overlapping views, division by zero, infinite heights, and other fun problems that
|
| + // could arise with extremely narrow infobars.
|
| + mWidth = Math.max(MeasureSpec.getSize(widthMeasureSpec), mMinWidth);
|
| + mTop = mBottom = 0;
|
| + placeGroups();
|
|
|
| - // Buttons must have their backgrounds manually changed to give the illusion of having a
|
| - // single pixel boundary between them.
|
| - updateBackgroundsForButtons();
|
| -
|
| - // Determine how tall the container should be by measuring all the children in their rows.
|
| - int layoutHeight = computeHeight();
|
| - setMeasuredDimension(resolveSize(maxWidth, widthMeasureSpec),
|
| - resolveSize(layoutHeight, heightMeasureSpec));
|
| + setMeasuredDimension(mWidth, resolveSize(mBottom, heightMeasureSpec));
|
| }
|
|
|
| /**
|
| - * Assign as many Views as can fit onto the main row.
|
| - *
|
| - * The main row consists of at least the icon, the close button, and the message. Groups of
|
| - * controls are added to the main row as long as they can fit within the width of the InfoBar.
|
| - *
|
| - * @param maxWidth The maximum width of the main row.
|
| - * @return The index of the last child that couldn't fit on the main row.
|
| + * Assigns positions to all of the views in the infobar. The icon, text, and close button are
|
| + * placed on the main row. The custom content and finally the buttons are placed on the main row
|
| + * if they fit. Otherwise, they go on their own rows.
|
| */
|
| - private int measureMainRow(int maxWidth) {
|
| - final int childCount = getChildCount();
|
| + private void placeGroups() {
|
| + startRow();
|
| + placeChild(mCloseButton, Gravity.END);
|
| + placeGroup(mMainGroup);
|
|
|
| - // The main row has the icon and the close button taking the upper left and upper right
|
| - // corners of the InfoBar, each of which occupies a square of
|
| - // mDimensionMinSize x mDimensionMinSize pixels.
|
| - GroupInfo mainControlInfo = getNextGroup(0);
|
| - int remainingWidth = maxWidth - (mDimensionMinSize * 2) - mMessageView.getMeasuredWidth();
|
| - addRowStartIndex(0);
|
| + int customGroupWidth = 0;
|
| + if (mCustomGroup != null) {
|
| + updateCustomGroupForRow(ROW_MAIN);
|
| + customGroupWidth = getWidthWithMargins(mCustomGroup);
|
| + }
|
|
|
| - // Go through the rest of the Views and keep adding them until they can't fit.
|
| - int currentChildIndex = mainControlInfo.endIndex;
|
| - while (currentChildIndex < childCount && remainingWidth > 0) {
|
| - GroupInfo groupInfo = getNextGroup(currentChildIndex);
|
| - int widthWithMargins = groupInfo.width + mDimensionMargin * groupInfo.numViews;
|
| + int buttonGroupWidth = 0;
|
| + if (mButtonGroup != null) {
|
| + updateButtonGroupForRow(ROW_MAIN);
|
| + buttonGroupWidth = getWidthWithMargins(mButtonGroup);
|
| + }
|
|
|
| - if (widthWithMargins <= remainingWidth) {
|
| - // If the group fits on the main row, add it.
|
| - currentChildIndex = groupInfo.endIndex;
|
| - remainingWidth -= widthWithMargins;
|
| + boolean customGroupOnMainRow = customGroupWidth <= availableWidth();
|
| + boolean buttonGroupOnMainRow = customGroupWidth + buttonGroupWidth <= availableWidth();
|
| +
|
| + if (mCustomGroup != null) {
|
| + if (customGroupOnMainRow) {
|
| + mCustomGroup.gravity = (mButtonGroup != null && buttonGroupOnMainRow)
|
| + ? Gravity.START : Gravity.END;
|
| } else {
|
| - // We can't fit the current group on the main row.
|
| - break;
|
| + startRow();
|
| + updateCustomGroupForRow(ROW_OTHER);
|
| }
|
| + placeGroup(mCustomGroup);
|
| }
|
| - addRowStartIndex(currentChildIndex);
|
|
|
| - // The icon and the close button are set to be squares occupying the upper left and
|
| - // upper right corners of the InfoBar.
|
| - int specWidth = MeasureSpec.makeMeasureSpec(mDimensionMinSize, MeasureSpec.EXACTLY);
|
| - int specHeight = MeasureSpec.makeMeasureSpec(mDimensionMinSize, MeasureSpec.EXACTLY);
|
| - measureChild(mIconView, specWidth, specHeight);
|
| - measureChild(mCloseButton, specWidth, specHeight);
|
| + if (mButtonGroup != null) {
|
| + if (!buttonGroupOnMainRow) {
|
| + startRow();
|
| + updateButtonGroupForRow(ROW_OTHER);
|
|
|
| - // Measure out everything else except the message.
|
| - remainingWidth = maxWidth - (mDimensionMinSize * 2);
|
| - for (int i = 0; i < currentChildIndex; i++) {
|
| - final View child = getChildAt(i);
|
| - if (child.getVisibility() == View.GONE || isMainControl(child)) continue;
|
| -
|
| - specWidth = MeasureSpec.makeMeasureSpec(remainingWidth, MeasureSpec.AT_MOST);
|
| - specHeight = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
|
| - measureChild(child, specWidth, specHeight);
|
| - remainingWidth -= child.getMeasuredWidth() + mDimensionMargin;
|
| + // If the infobar consists of just a main row and a buttons row, the buttons must be
|
| + // at least 32dp below the bottom of the message text.
|
| + if (mCustomGroup == null) {
|
| + LayoutParams lp = (LayoutParams) mMessageView.getLayoutParams();
|
| + int messageBottom = lp.top + mMessageView.getMeasuredHeight();
|
| + mTop = Math.max(mTop, messageBottom + 2 * mMargin);
|
| + }
|
| + }
|
| + placeGroup(mButtonGroup);
|
| }
|
|
|
| - // The message sucks up the remaining width on the line after all other controls
|
| - // have gotten all the space they requested.
|
| - specWidth = MeasureSpec.makeMeasureSpec(remainingWidth, MeasureSpec.AT_MOST);
|
| - specHeight = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
|
| - measureChild(mMessageView, specWidth, specHeight);
|
| + startRow();
|
|
|
| - return currentChildIndex;
|
| + // If everything fits on a single row, center everything vertically.
|
| + if (buttonGroupOnMainRow) {
|
| + int layoutHeight = mBottom;
|
| + for (int i = 0; i < getChildCount(); i++) {
|
| + View child = getChildAt(i);
|
| + int extraSpace = layoutHeight - child.getMeasuredHeight();
|
| + LayoutParams lp = (LayoutParams) child.getLayoutParams();
|
| + lp.top = extraSpace / 2;
|
| + }
|
| + }
|
| }
|
|
|
| /**
|
| - * Assign children to rows in the layout.
|
| - *
|
| - * We first try to assign children in the same group to the same row, but only if they fit when
|
| - * they are of equal width. Otherwise, we split the group onto multiple rows.
|
| - *
|
| - * @param maxWidth Maximum width that the row can take.
|
| - * @param currentChildIndex Start index of the current group.
|
| + * Places a group of views on the current row, or stacks them over multiple rows if
|
| + * group.isStacked is true. mStart, mEnd, and mBottom are updated to reflect the space taken by
|
| + * the group.
|
| */
|
| - private void measureRemainingRows(int maxWidth, int currentChildIndex) {
|
| - final int childCount = getChildCount();
|
| - final int specHeight = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
|
| -
|
| - while (currentChildIndex < childCount) {
|
| - GroupInfo groupInfo = getNextGroup(currentChildIndex);
|
| -
|
| - int availableWidth;
|
| - int boundaryMargins;
|
| - if (groupInfo.hasButton) {
|
| - // Buttons take up the full width of the InfoBar.
|
| - availableWidth = maxWidth;
|
| - boundaryMargins = 0;
|
| - } else {
|
| - // Other controls obey the side boundaries, and have boundaries between them.
|
| - availableWidth = maxWidth - mDimensionMargin * 2;
|
| - boundaryMargins = (groupInfo.numViews - 1) * mDimensionMargin;
|
| + private void placeGroup(Group group) {
|
| + if (group.gravity == Gravity.END) {
|
| + for (int i = group.views.length - 1; i >= 0; i--) {
|
| + placeChild(group.views[i], group.gravity);
|
| + if (group.isStacked && i != 0) startRow();
|
| }
|
| -
|
| - // Determine how wide each item would be on the same row, including boundaries.
|
| - int evenWidth = (availableWidth - boundaryMargins) / groupInfo.numViews;
|
| -
|
| - if (groupInfo.greatestMemberWidth <= evenWidth) {
|
| - // Fit everything on the same row.
|
| - int specWidth = MeasureSpec.makeMeasureSpec(evenWidth, MeasureSpec.EXACTLY);
|
| - for (int i = currentChildIndex; i < groupInfo.endIndex; i++) {
|
| - final View child = getChildAt(i);
|
| - if (child.getVisibility() == View.GONE) continue;
|
| - ((LayoutParams) child.getLayoutParams()).isInMainRow = false;
|
| - measureChild(child, specWidth, specHeight);
|
| - }
|
| - addRowStartIndex(currentChildIndex);
|
| - } else {
|
| - // Add each member of the group to its own row.
|
| - int specWidth = MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.EXACTLY);
|
| - for (int i = currentChildIndex; i < groupInfo.endIndex; i++) {
|
| - final View child = getChildAt(i);
|
| - if (child.getVisibility() == View.GONE) continue;
|
| - ((LayoutParams) child.getLayoutParams()).isInMainRow = false;
|
| - measureChild(child, specWidth, specHeight);
|
| - addRowStartIndex(i);
|
| - }
|
| + } else { // group.gravity is Gravity.START or Gravity.FILL_HORIZONTAL
|
| + for (int i = 0; i < group.views.length; i++) {
|
| + placeChild(group.views[i], group.gravity);
|
| + if (group.isStacked && i != group.views.length - 1) startRow();
|
| }
|
| -
|
| - currentChildIndex = groupInfo.endIndex;
|
| }
|
| -
|
| - addRowStartIndex(childCount);
|
| }
|
|
|
| /**
|
| - * Calculate how tall the layout is, accounting for margins and children.
|
| - * @return How big the layout should be.
|
| + * Places a single view on the current row, and updates the view's layout parameters to remember
|
| + * its position. mStart, mEnd, and mBottom are updated to reflect the space taken by the view.
|
| */
|
| - private int computeHeight() {
|
| - int cumulativeHeight = 0;
|
| + private void placeChild(View child, int gravity) {
|
| + LayoutParams lp = (LayoutParams) child.getLayoutParams();
|
|
|
| - // Calculate how big each row is.
|
| - final int numRows = mIndicesOfRows.size() - 1;
|
| - for (int row = 0; row < numRows; row++) {
|
| - final int rowStart = mIndicesOfRows.get(row);
|
| - final int rowEnd = mIndicesOfRows.get(row + 1);
|
| + int availableWidth = Math.max(0, mEnd - mStart - lp.startMargin - lp.endMargin);
|
| + if (child.getMeasuredWidth() > availableWidth || gravity == Gravity.FILL_HORIZONTAL) {
|
| + measureChildWithFixedWidth(child, availableWidth);
|
| + }
|
|
|
| - if (row == 0) {
|
| - cumulativeHeight += computeMainRowHeight(rowStart, rowEnd);
|
| - } else {
|
| - cumulativeHeight += computeRowHeight(rowStart, rowEnd);
|
| - }
|
| + if (gravity == Gravity.START || gravity == Gravity.FILL_HORIZONTAL) {
|
| + lp.start = mStart + lp.startMargin;
|
| + mStart = lp.start + child.getMeasuredWidth() + lp.endMargin;
|
| + } else { // gravity == Gravity.END
|
| + lp.start = mEnd - lp.endMargin - child.getMeasuredWidth();
|
| + mEnd = lp.start - lp.startMargin;
|
| }
|
|
|
| - return cumulativeHeight;
|
| + lp.top = mTop + lp.topMargin;
|
| + mBottom = Math.max(mBottom, lp.top + child.getMeasuredHeight() + lp.bottomMargin);
|
| }
|
|
|
| /**
|
| - * Computes how tall the main row is.
|
| - * @param rowStart Index of the first child.
|
| - * @param rowEnd One past the index of the last child.
|
| + * Advances the current position to the next row and adds margins on the left, right, and top
|
| + * of the new row.
|
| */
|
| - private int computeMainRowHeight(int rowStart, int rowEnd) {
|
| - // The icon and close button already have their margins baked into their padding values,
|
| - // but the other Views have a margin above and below.
|
| - final int verticalMargins = mDimensionMargin * 2;
|
| - int rowHeight = mDimensionMinSize;
|
| - for (int i = rowStart; i < rowEnd; i++) {
|
| - View child = getChildAt(i);
|
| - if (child == mCloseButton || child == mIconView || child.getVisibility() == View.GONE) {
|
| - continue;
|
| - }
|
| - rowHeight = Math.max(rowHeight, child.getMeasuredHeight() + verticalMargins);
|
| - }
|
| - return rowHeight;
|
| + private void startRow() {
|
| + mStart = mMargin;
|
| + mEnd = mWidth - mMargin;
|
| + mTop = mBottom + mMargin;
|
| + mBottom = mTop;
|
| }
|
|
|
| + private int availableWidth() {
|
| + return mEnd - mStart;
|
| + }
|
| +
|
| /**
|
| - * Computes how tall a row below the main row is.
|
| - *
|
| - * Margins are only applied downward since the rows above are handling the margin on their side.
|
| - * Buttons ignore margins since they have to be right against the boundary.
|
| - *
|
| - * @param rowStart Index of the first child.
|
| - * @param rowEnd One past the index of the last child.
|
| + * @return The width of the group, including the items' margins.
|
| */
|
| - private int computeRowHeight(int rowStart, int rowEnd) {
|
| - boolean isButtonRow = isButton(getChildAt(rowStart));
|
| - final int verticalMargins = isButtonRow ? 0 : mDimensionMargin;
|
| - int rowHeight = 0;
|
| - for (int i = rowStart; i < rowEnd; i++) {
|
| - final View child = getChildAt(i);
|
| - if (child.getVisibility() == View.GONE) continue;
|
| - rowHeight = Math.max(rowHeight, child.getMeasuredHeight() + verticalMargins);
|
| + private int getWidthWithMargins(Group group) {
|
| + if (group.isStacked) return getWidthWithMargins(group.views[0]);
|
| +
|
| + int width = 0;
|
| + for (View v : group.views) {
|
| + width += getWidthWithMargins(v);
|
| }
|
| - return rowHeight;
|
| + return width;
|
| }
|
|
|
| + private int getWidthWithMargins(View child) {
|
| + LayoutParams lp = (LayoutParams) child.getLayoutParams();
|
| + return child.getMeasuredWidth() + lp.startMargin + lp.endMargin;
|
| + }
|
| +
|
| + private void measureChildWithFixedWidth(View child, int width) {
|
| + int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
|
| + int heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
|
| + child.measure(widthSpec, heightSpec);
|
| + }
|
| +
|
| /**
|
| - * Determines if the given View is either the primary or secondary button.
|
| - * @param child View to check.
|
| - * @return Whether the child is the primary or secondary button.
|
| + * The button group has different layout properties (margins, gravity, etc) when placed on the
|
| + * main row as opposed to on a separate row. This updates the layout properties of the button
|
| + * group to prepare for placing it on either the main row or a separate row.
|
| + *
|
| + * @param row One of ROW_MAIN or ROW_OTHER.
|
| */
|
| - private boolean isButton(View child) {
|
| - return child.getId() == R.id.button_secondary || child.getId() == R.id.button_primary;
|
| + private void updateButtonGroupForRow(int row) {
|
| + int startEndMargin = row == ROW_MAIN ? mMargin : 0;
|
| + mButtonGroup.setHorizontalMode(mMargin / 2, startEndMargin, startEndMargin);
|
| + mButtonGroup.gravity = Gravity.END;
|
| +
|
| + if (row == ROW_OTHER && mButtonGroup.views.length >= 2) {
|
| + int extraWidth = availableWidth() - getWidthWithMargins(mButtonGroup);
|
| + if (extraWidth < 0) {
|
| + // Group is too wide to fit on a single row, so stack the group items vertically.
|
| + mButtonGroup.setVerticalMode(mMargin / 2, 0);
|
| + mButtonGroup.gravity = Gravity.FILL_HORIZONTAL;
|
| + } else if (mButtonGroup.views.length == 3) {
|
| + // Align tertiary button at the start and the other two buttons at the end.
|
| + ((LayoutParams) mButtonGroup.views[0].getLayoutParams()).endMargin += extraWidth;
|
| + }
|
| + }
|
| }
|
|
|
| /**
|
| - * Update the backgrounds for the buttons to account for their current positioning.
|
| - * The primary and secondary buttons are special-cased in that their backgrounds change to
|
| - * create the illusion of a single-stroke boundary between them.
|
| + * Analagous to updateButtonGroupForRow(), but for the custom group istead of the button group.
|
| */
|
| - private void updateBackgroundsForButtons() {
|
| - boolean bothButtonsExist = findViewById(R.id.button_primary) != null
|
| - && findViewById(R.id.button_secondary) != null;
|
| + private void updateCustomGroupForRow(int row) {
|
| + int startEndMargin = row == ROW_MAIN ? mMargin : 0;
|
| + mCustomGroup.setHorizontalMode(mMargin, startEndMargin, startEndMargin);
|
| + mCustomGroup.gravity = Gravity.START;
|
|
|
| - for (int row = 0; row < mIndicesOfRows.size() - 1; row++) {
|
| - final int rowStart = mIndicesOfRows.get(row);
|
| - final int rowEnd = mIndicesOfRows.get(row + 1);
|
| - final int rowSize = rowEnd - rowStart;
|
| -
|
| - for (int i = rowStart; i < rowEnd; i++) {
|
| - final View child = getChildAt(i);
|
| - if (child.getVisibility() == View.GONE || !isButton(child)) continue;
|
| -
|
| - // Determine which background we need to show.
|
| - int background;
|
| - if (row == 0) {
|
| - // Button will be floating.
|
| - background = mBackgroundFloating;
|
| - } else if (rowSize == 1 || !bothButtonsExist) {
|
| - // Button takes up the full width of the screen.
|
| - background = mBackgroundFullRight;
|
| - } else if (mLayoutRTL) {
|
| - // Primary button will be to the left of the secondary.
|
| - background = child.getId() == R.id.button_primary
|
| - ? mBackgroundFullLeft : mBackgroundFullRight;
|
| - } else {
|
| - // Primary button will be to the right of the secondary.
|
| - background = child.getId() == R.id.button_primary
|
| - ? mBackgroundFullRight : mBackgroundFullLeft;
|
| - }
|
| -
|
| - // Update the background.
|
| - LayoutParams params = (LayoutParams) child.getLayoutParams();
|
| - if (params.background != background) {
|
| - params.background = background;
|
| -
|
| - // Save the padding; Android decides to overwrite it on some builds.
|
| - int paddingLeft = child.getPaddingLeft();
|
| - int paddingTop = child.getPaddingTop();
|
| - int paddingRight = child.getPaddingRight();
|
| - int paddingBottom = child.getPaddingBottom();
|
| - int buttonWidth = child.getMeasuredWidth();
|
| - int buttonHeight = child.getMeasuredHeight();
|
| -
|
| - // Set the background, then restore the padding.
|
| - child.setBackgroundResource(background);
|
| - child.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
|
| -
|
| - // Re-measuring is necessary to correct the text gravity.
|
| - int specWidth = MeasureSpec.makeMeasureSpec(buttonWidth, MeasureSpec.EXACTLY);
|
| - int specHeight = MeasureSpec.makeMeasureSpec(buttonHeight, MeasureSpec.EXACTLY);
|
| - measureChild(child, specWidth, specHeight);
|
| - }
|
| + if (row == ROW_OTHER && mCustomGroup.views.length == 2) {
|
| + int extraWidth = availableWidth() - getWidthWithMargins(mCustomGroup);
|
| + if (extraWidth < 0) {
|
| + // Group is too wide to fit on a single row, so stack the group items vertically.
|
| + mCustomGroup.setVerticalMode(0, mMargin);
|
| + mCustomGroup.gravity = Gravity.FILL_HORIZONTAL;
|
| + } else {
|
| + // Expand the children to take up the entire row.
|
| + View view0 = mCustomGroup.views[0];
|
| + View view1 = mCustomGroup.views[1];
|
| + int extraWidth0 = extraWidth / 2;
|
| + int extraWidth1 = extraWidth - extraWidth0;
|
| + measureChildWithFixedWidth(view0, view0.getMeasuredWidth() + extraWidth0);
|
| + measureChildWithFixedWidth(view1, view1.getMeasuredWidth() + extraWidth1);
|
| }
|
| }
|
| }
|
| @@ -759,7 +557,8 @@
|
| mInfoBarView.onButtonClicked(true);
|
| } else if (view.getId() == R.id.button_secondary) {
|
| mInfoBarView.onButtonClicked(false);
|
| + } else if (view.getId() == R.id.button_tertiary) {
|
| + mInfoBarView.onLinkClicked();
|
| }
|
| }
|
| -
|
| }
|
|
|