Index: chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBarDualControlLayout.java |
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBarDualControlLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBarDualControlLayout.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..0bf011d5076cb2506c0c7c113aac86fcbad4a575 |
--- /dev/null |
+++ b/chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBarDualControlLayout.java |
@@ -0,0 +1,189 @@ |
+// Copyright 2015 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.content.res.Resources; |
+import android.util.AttributeSet; |
+import android.view.View; |
+import android.view.ViewGroup; |
+ |
+import org.chromium.base.ApiCompatibilityUtils; |
+import org.chromium.base.VisibleForTesting; |
+import org.chromium.chrome.R; |
+ |
+/** |
+ * Automatically lays out one or two Views for an infobar, placing them on the same row if possible |
+ * and stacking them otherwise. |
+ * |
+ * Use cases of this Layout include placement of infobar buttons and placement of TextViews inside |
+ * of spinner controls (http://goto.google.com/infobar-spec). |
+ * |
+ * Layout parameters (i.e. margins) are ignored to enforce infobar consistency. Alignment defines |
+ * where the controls are placed (for RTL, flip everything): |
+ * |
+ * ALIGN_START ALIGN_APART ALIGN_END |
+ * ----------------------------- ----------------------------- ----------------------------- |
+ * | PRIMARY SECONDARY | | SECONDARY PRIMARY | | SECONDARY PRIMARY | |
+ * ----------------------------- ----------------------------- ----------------------------- |
+ * |
+ * Controls are stacked automatically when they don't fit on the same row, with each control taking |
+ * up the full available width and with the primary control sitting on top of the secondary. |
+ * ----------------------------- |
+ * | PRIMARY------------------ | |
+ * | SECONDARY---------------- | |
+ * ----------------------------- |
+ * |
+ * TODO(dfalcantara): Remove the VisibleForTesting annotations when controls that this land. |
+ */ |
+@VisibleForTesting |
+public final class InfoBarDualControlLayout extends ViewGroup { |
+ public static final int ALIGN_START = 0; |
+ public static final int ALIGN_END = 1; |
+ public static final int ALIGN_APART = 2; |
+ |
+ private final int mHorizontalMarginBetweenViews; |
+ |
+ private int mAlignment = ALIGN_START; |
+ private int mStackedMargin; |
+ |
+ private boolean mIsStacked; |
+ private View mPrimaryView; |
+ private View mSecondaryView; |
+ |
+ /** |
+ * Construct a new InfoBarDualControlLayout. |
+ * |
+ * See {@link ViewGroup} for parameter details. attrs may be null if constructed dynamically. |
+ */ |
+ @VisibleForTesting |
+ public InfoBarDualControlLayout(Context context, AttributeSet attrs) { |
+ super(context, attrs); |
+ |
+ // Cache dimensions. |
+ Resources resources = getContext().getResources(); |
+ mHorizontalMarginBetweenViews = |
+ resources.getDimensionPixelSize(R.dimen.infobar_control_margin_between_items); |
+ } |
+ |
+ /** |
+ * Define how the controls will be laid out. |
+ * |
+ * @param alignment One of ALIGN_START, ALIGN_APART, ALIGN_END. |
+ */ |
+ @VisibleForTesting |
+ void setAlignment(int alignment) { |
+ mAlignment = alignment; |
+ } |
+ |
+ /** |
+ * Sets the margin between the controls when they're stacked. By default, there is no margin. |
+ */ |
+ @VisibleForTesting |
+ void setStackedMargin(int stackedMargin) { |
+ mStackedMargin = stackedMargin; |
+ } |
+ |
+ @Override |
+ public void onViewAdded(View child) { |
+ super.onViewAdded(child); |
+ |
+ if (mPrimaryView == null) { |
+ mPrimaryView = child; |
+ } else if (mSecondaryView == null) { |
+ mSecondaryView = child; |
+ } else { |
+ throw new IllegalStateException("Too many children added to InfoBarDualControlLayout"); |
+ } |
+ } |
+ |
+ @Override |
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
+ mIsStacked = false; |
+ |
+ // Measure the primary View, allowing it to be as wide as the Layout. |
+ int maxWidth = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.UNSPECIFIED |
+ ? Integer.MAX_VALUE : MeasureSpec.getSize(widthMeasureSpec); |
+ int unspecifiedSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); |
+ measureChild(mPrimaryView, unspecifiedSpec, unspecifiedSpec); |
+ |
+ int layoutWidth = mPrimaryView.getMeasuredWidth(); |
+ int layoutHeight = mPrimaryView.getMeasuredHeight(); |
+ |
+ if (mSecondaryView != null) { |
+ // Measure the secondary View, allowing it to be as wide as the layout. |
+ measureChild(mSecondaryView, unspecifiedSpec, unspecifiedSpec); |
+ int combinedWidth = mPrimaryView.getMeasuredWidth() |
+ + mHorizontalMarginBetweenViews + mSecondaryView.getMeasuredWidth(); |
+ |
+ if (combinedWidth > maxWidth) { |
+ // Stack the Views on top of each other. |
+ mIsStacked = true; |
+ |
+ int widthSpec = MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.EXACTLY); |
+ mPrimaryView.measure(widthSpec, unspecifiedSpec); |
+ mSecondaryView.measure(widthSpec, unspecifiedSpec); |
+ |
+ layoutWidth = maxWidth; |
+ layoutHeight = mPrimaryView.getMeasuredHeight() + mStackedMargin |
+ + mSecondaryView.getMeasuredHeight(); |
+ } else { |
+ // The Views fit side by side. Check which is taller to find the layout height. |
+ layoutWidth = combinedWidth; |
+ layoutHeight = Math.max(layoutHeight, mSecondaryView.getMeasuredHeight()); |
+ } |
+ } |
+ |
+ setMeasuredDimension(resolveSize(layoutWidth, widthMeasureSpec), |
+ resolveSize(layoutHeight, heightMeasureSpec)); |
+ } |
+ |
+ @Override |
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) { |
+ int width = right - left; |
+ boolean isRtl = ApiCompatibilityUtils.isLayoutRtl(this); |
+ boolean isPrimaryOnRight = (isRtl && mAlignment == ALIGN_START) |
+ || (!isRtl && (mAlignment == ALIGN_APART || mAlignment == ALIGN_END)); |
+ |
+ int primaryRight = isPrimaryOnRight ? width : mPrimaryView.getMeasuredWidth(); |
+ int primaryLeft = primaryRight - mPrimaryView.getMeasuredWidth(); |
+ int primaryHeight = mPrimaryView.getMeasuredHeight(); |
+ mPrimaryView.layout(primaryLeft, 0, primaryRight, primaryHeight); |
+ |
+ if (mIsStacked) { |
+ // Fill out the row. onMeasure() should have already applied the correct width. |
+ int secondaryTop = primaryHeight + mStackedMargin; |
+ int secondaryBottom = secondaryTop + mSecondaryView.getMeasuredHeight(); |
+ mSecondaryView.layout( |
+ 0, secondaryTop, mSecondaryView.getMeasuredWidth(), secondaryBottom); |
+ } else if (mSecondaryView != null) { |
+ // Center the secondary View vertically with the primary View. |
+ int secondaryHeight = mSecondaryView.getMeasuredHeight(); |
+ int primaryCenter = primaryHeight / 2; |
+ int secondaryTop = primaryCenter - (secondaryHeight / 2); |
+ int secondaryBottom = secondaryTop + secondaryHeight; |
+ |
+ // Determine where to place the secondary View. |
+ int secondaryLeft; |
+ int secondaryRight; |
+ if (mAlignment == ALIGN_APART) { |
+ // Put the second View on the other side of the Layout from the primary View. |
+ secondaryLeft = isPrimaryOnRight ? 0 : width - mSecondaryView.getMeasuredWidth(); |
+ secondaryRight = secondaryLeft + mSecondaryView.getMeasuredWidth(); |
+ } else if (isPrimaryOnRight) { |
+ // Sit to the left of the primary View. |
+ secondaryRight = primaryLeft - mHorizontalMarginBetweenViews; |
+ secondaryLeft = secondaryRight - mSecondaryView.getMeasuredWidth(); |
+ } else { |
+ // Sit to the right of the primary View. |
+ secondaryLeft = primaryRight + mHorizontalMarginBetweenViews; |
+ secondaryRight = secondaryLeft + mSecondaryView.getMeasuredWidth(); |
+ } |
+ |
+ mSecondaryView.layout( |
+ secondaryLeft, secondaryTop, secondaryRight, secondaryBottom); |
+ } |
+ } |
+} |