Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(2135)

Unified Diff: chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBarLayout.java

Issue 24109002: [InfoBar] Upstram basic infobar flow for Android. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@upstream_infobar_full
Patch Set: Fix License header in two more files Created 7 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBarLayout.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBarLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBarLayout.java
new file mode 100644
index 0000000000000000000000000000000000000000..7a4ea4cd021ed6055bd4d5c11425e817eeb17742
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBarLayout.java
@@ -0,0 +1,775 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.infobar;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.text.method.LinkMovementMethod;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import org.chromium.chrome.R;
+
+import org.chromium.ui.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.
+ * - A message describing the action that the user can take.
+ * - A close button on the right side.
+ *
+ * Views should never be added with anything but a call to addGroup() to ensure that groups are not
+ * broken apart.
+ *
+ * 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;
+
+ /** Whether the View is meant for the main row. */
+ public boolean isInMainRow;
+
+ /** Views grouped together are laid out together immediately adjacent to each other. */
+ public boolean isGroupedWithNextView;
+
+ /** When on the main row, indicates whether the control floats on the left or the right. */
+ public int align;
+
+ /** If the control is a button, ID of the resource that was last used as its background. */
+ public int background;
+
+ public LayoutParams() {
+ super(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+ align = ALIGN_END;
+ isInMainRow = true;
+ }
+
+ public LayoutParams(LayoutParams other) {
+ super(other);
+ isGroupedWithNextView = other.isGroupedWithNextView;
+ align = other.align;
+ isInMainRow = other.isInMainRow;
+ }
+ }
+
+ private static class GroupInfo {
+ public int numViews;
+ public int width;
+ public int greatestMemberWidth;
+ public int endIndex;
+ public boolean hasButton;
+ };
+
+ private final int mDimensionMinSize;
+ private final int mDimensionMargin;
+ private final int mDimensionIconSize;
+ private final boolean mLayoutRTL;
+ private final InfoBarView mInfoBarView;
+
+ private ImageView mIconView;
+ private TextView mMessageView;
+ private ImageButton mCloseButton;
+
+ /** Background resource IDs to use for the buttons. */
+ private final int mBackgroundFloating;
+ private final int mBackgroundFullLeft;
+ private final int mBackgroundFullRight;
+
+ /**
+ * 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.
+ */
+ private ArrayList<Integer> mIndicesOfRows;
+
+ /**
+ * Constructs the layout for the specified InfoBar.
+ * @param context The context used to render.
+ * @param infoBarView InfoBarView that listens to events.
+ * @param backgroundType Type of InfoBar background being shown.
+ * @param iconResourceId ID of the icon to use for the InfoBar.
+ * @param message Message to display.
+ */
+ public InfoBarLayout(Context context, InfoBarView infoBarView, int backgroundType,
+ int iconResourceId) {
+ super(context);
+ mIndicesOfRows = new ArrayList<Integer>();
+ mLayoutRTL = LocalizationUtils.isSystemLayoutDirectionRtl();
+ mInfoBarView = infoBarView;
+
+ // Determine what backgrounds we'll be needing for the buttons.
+ if (backgroundType == InfoBar.BACKGROUND_TYPE_INFO) {
+ mBackgroundFloating = R.drawable.infobar_button_normal_floating;
+ mBackgroundFullLeft = R.drawable.infobar_button_normal_full_left;
+ mBackgroundFullRight = R.drawable.infobar_button_normal_full_right;
+ } else {
+ mBackgroundFloating = R.drawable.infobar_button_warning_floating;
+ mBackgroundFullLeft = R.drawable.infobar_button_warning_full_left;
+ mBackgroundFullRight = R.drawable.infobar_button_warning_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);
+
+ // Create the main controls.
+ 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_dismiss);
+ mCloseButton.setBackgroundResource(R.drawable.infobar_close_bg);
+ mCloseButton.setOnClickListener(this);
+
+ mCloseButton.setContentDescription(getResources().getString(R.string.infobar_close));
+
+ // Set up the icon.
+ mIconView.setFocusable(false);
+ if (iconResourceId != 0) {
+ mIconView.setImageResource(iconResourceId);
+ } else {
+ mIconView.setVisibility(View.INVISIBLE);
+ }
+
+ // Set up the TextView.
+ mMessageView.setMovementMethod(LinkMovementMethod.getInstance());
+ mMessageView.setText(infoBarView.getMessageText(context), TextView.BufferType.SPANNABLE);
+
+ // 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);
+ } else {
+ assert closePadding >= 0 : "Assets are too large for this layout.";
+ }
+
+ // Add all of the other InfoBar specific controls.
+ infoBarView.createContent(this);
+ }
+
+ @Override
+ protected LayoutParams generateDefaultLayoutParams() {
+ return new LayoutParams();
+ }
+
+ /**
+ * 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.
+ */
+ @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);
+ }
+ }
+
+ /**
+ * Add a group of Views that are measured and laid out together.
+ */
+ 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);
+ }
+ }
+
+ /**
+ * 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.
+ */
+ 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);
+ }
+ }
+
+ @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.
+ *
+ * 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.
+ */
+ private int layoutMainRow(int width) {
+ final int rowStart = mIndicesOfRows.get(0);
+ final int rowEnd = mIndicesOfRows.get(1);
+ final int rowHeight = computeMainRowHeight(rowStart, rowEnd);
+
+ // 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);
+
+ // 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());
+ }
+
+ // 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;
+ }
+
+ // 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());
+ }
+
+ return rowHeight;
+ }
+
+ /**
+ * 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.
+ */
+ 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));
+
+ 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());
+ }
+
+ return rowTop + computeRowHeight(rowStart, rowEnd);
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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);
+ }
+ }
+
+ /**
+ * 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;
+
+ 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++;
+ }
+ groupInfo.endIndex++;
+
+ LayoutParams params = (LayoutParams) groupChild.getLayoutParams();
+ if (!params.isGroupedWithNextView) break;
+ }
+
+ return groupInfo;
+ }
+
+ @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);
+ }
+
+ // 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);
+
+ // 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));
+ }
+
+ /**
+ * 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.
+ */
+ private int measureMainRow(int maxWidth) {
+ final int childCount = getChildCount();
+
+ // 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);
+
+ // 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;
+
+ if (widthWithMargins <= remainingWidth) {
+ // If the group fits on the main row, add it.
+ currentChildIndex = groupInfo.endIndex;
+ remainingWidth -= widthWithMargins;
+ } else {
+ // We can't fit the current group on the main row.
+ break;
+ }
+ }
+ 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);
+
+ // 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;
+ }
+
+ // 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);
+
+ return currentChildIndex;
+ }
+
+ /**
+ * 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.
+ */
+ 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;
+ }
+
+ // 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);
+ }
+ }
+
+ currentChildIndex = groupInfo.endIndex;
+ }
+
+ addRowStartIndex(childCount);
+ }
+
+ /**
+ * Calculate how tall the layout is, accounting for margins and children.
+ * @return How big the layout should be.
+ */
+ private int computeHeight() {
+ int cumulativeHeight = 0;
+
+ // 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);
+
+ if (row == 0) {
+ cumulativeHeight += computeMainRowHeight(rowStart, rowEnd);
+ } else {
+ cumulativeHeight += computeRowHeight(rowStart, rowEnd);
+ }
+ }
+
+ return cumulativeHeight;
+ }
+
+ /**
+ * Computes how tall the main row is.
+ * @param rowStart Index of the first child.
+ * @param rowEnd One past the index of the last child.
+ */
+ 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;
+ }
+
+ /**
+ * 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.
+ */
+ 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);
+ }
+ return rowHeight;
+ }
+
+ /**
+ * 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.
+ */
+ private boolean isButton(View child) {
+ return child.getId() == R.id.button_secondary || child.getId() == R.id.button_primary;
+ }
+
+ /**
+ * 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.
+ */
+ private void updateBackgroundsForButtons() {
+ boolean bothButtonsExist = findViewById(R.id.button_primary) != null
+ && findViewById(R.id.button_secondary) != null;
+
+ 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);
+ }
+ }
+ }
+ }
+
+ /**
+ * Listens for View clicks.
+ * Classes that override this function MUST call this one.
+ * @param view View that was clicked on.
+ */
+ @Override
+ public void onClick(View view) {
+ mInfoBarView.setControlsEnabled(false);
+ if (view.getId() == R.id.infobar_close_button) {
+ mInfoBarView.onCloseButtonClicked();
+ } else if (view.getId() == R.id.button_primary) {
+ mInfoBarView.onButtonClicked(true);
+ } else if (view.getId() == R.id.button_secondary) {
+ mInfoBarView.onButtonClicked(false);
+ }
+ }
+
+}

Powered by Google App Engine
This is Rietveld 408576698