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); |
+ } |
+ } |
+ |
+} |