blob: 320981ea0f82e43eb711ec0f554e55f2207d1837 [file] [log] [blame]
// Copyright 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.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.
* - 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 final ImageView mIconView;
private final TextView mMessageView;
private final 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 final 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.
*/
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);
}
}
}