Index: chrome/android/java/src/org/chromium/chrome/browser/banners/AppBannerView.java |
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/banners/AppBannerView.java b/chrome/android/java/src/org/chromium/chrome/browser/banners/AppBannerView.java |
index acfe1c245227412e15bcb60ab4dfc171120de68c..92a5d7b5c0cd1d27830c8843b84e11bb55f032fa 100644 |
--- a/chrome/android/java/src/org/chromium/chrome/browser/banners/AppBannerView.java |
+++ b/chrome/android/java/src/org/chromium/chrome/browser/banners/AppBannerView.java |
@@ -8,10 +8,10 @@ import android.animation.ObjectAnimator; |
import android.content.Context; |
import android.content.res.Configuration; |
import android.content.res.Resources; |
-import android.graphics.Point; |
import android.graphics.Rect; |
import android.util.AttributeSet; |
import android.view.LayoutInflater; |
+import android.view.MotionEvent; |
import android.view.View; |
import android.view.ViewGroup; |
import android.widget.Button; |
@@ -25,9 +25,29 @@ import org.chromium.ui.base.LocalizationUtils; |
/** |
* Lays out a banner for showing info about an app on the Play Store. |
- * Rather than utilizing the Android RatingBar, which would require some nasty styling, a custom |
- * View is used to paint a Drawable showing all the stars. Showing different ratings is done by |
- * adjusting the clipping rectangle width. |
+ * The banner mimics the appearance of a Google Now card using a background Drawable with a shadow. |
+ * |
+ * PADDING CALCULATIONS |
+ * The banner has three different types of padding that need to be accounted for: |
+ * 1) The background Drawable of the banner looks like card with a drop shadow. The Drawable |
+ * defines a padding around the card that solely encompasses the space occupied by the drop |
+ * shadow. |
+ * 2) The card itself needs to have padding so that the widgets don't abut the borders of the card. |
+ * This is defined as mPaddingCard, and is equally applied to all four sides. |
+ * 3) Controls other than the icon are further constrained by mPaddingControls, which applies only |
+ * to the bottom and end margins. |
+ * See {@link #AppBannerView.onMeasure(int, int)} for details. |
+ * |
+ * MARGIN CALCULATIONS |
+ * Margin calculations for the banner are complicated by the background Drawable's drop shadows, |
+ * since the drop shadows are meant to be counted as being part of the margin. To deal with this, |
+ * the margins are calculated by deducting the background Drawable's padding from the margins |
+ * defined by the XML files. |
+ * |
+ * EVEN MORE LAYOUT QUIRKS |
+ * The layout of the banner, which includes its widget sizes, may change when the screen is rotated |
+ * to account for less screen real estate. This means that all of the View's widgets and cached |
+ * dimensions must be rebuilt from scratch. |
*/ |
public class AppBannerView extends SwipableOverlayView implements View.OnClickListener { |
/** |
@@ -53,9 +73,15 @@ public class AppBannerView extends SwipableOverlayView implements View.OnClickLi |
public void onBannerClicked(AppBannerView banner); |
} |
+ // Number of milliseconds to wait before showing the card highlight. |
+ private static final long MS_HIGHLIGHT_APPEARANCE = 150; |
+ |
// XML layout for the BannerView. |
private static final int BANNER_LAYOUT = R.layout.app_banner_view; |
+ // Maximum distance the finger can travel before dismissing the highlight. |
+ private static final float HIGHLIGHT_DISTANCE = 20; |
+ |
// True if the layout is in left-to-right layout mode (regular mode). |
private final boolean mIsLayoutLTR; |
@@ -67,23 +93,24 @@ public class AppBannerView extends SwipableOverlayView implements View.OnClickLi |
private TextView mTitleView; |
private Button mButtonView; |
private RatingView mRatingView; |
- private ImageView mLogoView; |
+ private View mLogoView; |
+ private View mBannerHighlightView; |
// Information about the package. |
private AppData mAppData; |
- // Variables used during layout calculations and saved to avoid reallocations. |
- private final Point mSpaceMain; |
- private final Point mSpaceForLogo; |
- private final Point mSpaceForRating; |
- private final Point mSpaceForTitle; |
- |
// Dimension values. |
private int mDefinedMaxWidth; |
- private int mPaddingContent; |
- private int mMarginSide; |
+ private int mPaddingCard; |
+ private int mPaddingControls; |
+ private int mMarginLeft; |
+ private int mMarginRight; |
private int mMarginBottom; |
+ // Highlight variables. |
+ private boolean mIsBannerPressed; |
+ private float mInitialXForHighlight; |
+ |
// Initial padding values. |
private final Rect mBackgroundDrawablePadding; |
@@ -109,10 +136,6 @@ public class AppBannerView extends SwipableOverlayView implements View.OnClickLi |
public AppBannerView(Context context, AttributeSet attrs) { |
super(context, attrs); |
mIsLayoutLTR = !LocalizationUtils.isLayoutRtl(); |
- mSpaceMain = new Point(); |
- mSpaceForLogo = new Point(); |
- mSpaceForRating = new Point(); |
- mSpaceForTitle = new Point(); |
// Store the background Drawable's padding. The background used for banners is a 9-patch, |
// which means that it already defines padding. We need to take it into account when adding |
@@ -137,41 +160,38 @@ public class AppBannerView extends SwipableOverlayView implements View.OnClickLi |
private void initializeControls() { |
// Cache the banner dimensions, adjusting margins for drop shadows defined in the background |
- // Drawable. The Drawable is defined to have the same margin on both the left and right. |
+ // Drawable. |
Resources res = getResources(); |
mDefinedMaxWidth = res.getDimensionPixelSize(R.dimen.app_banner_max_width); |
- mPaddingContent = res.getDimensionPixelSize(R.dimen.app_banner_padding_content); |
- mMarginSide = res.getDimensionPixelSize(R.dimen.app_banner_margin_sides) |
+ mPaddingCard = res.getDimensionPixelSize(R.dimen.app_banner_padding); |
+ mPaddingControls = res.getDimensionPixelSize(R.dimen.app_banner_padding_controls); |
+ mMarginLeft = res.getDimensionPixelSize(R.dimen.app_banner_margin_sides) |
- mBackgroundDrawablePadding.left; |
+ mMarginRight = res.getDimensionPixelSize(R.dimen.app_banner_margin_sides) |
+ - mBackgroundDrawablePadding.right; |
mMarginBottom = res.getDimensionPixelSize(R.dimen.app_banner_margin_bottom) |
- mBackgroundDrawablePadding.bottom; |
if (getLayoutParams() != null) { |
MarginLayoutParams params = (MarginLayoutParams) getLayoutParams(); |
- params.leftMargin = mMarginSide; |
- params.rightMargin = mMarginSide; |
+ params.leftMargin = mMarginLeft; |
+ params.rightMargin = mMarginRight; |
params.bottomMargin = mMarginBottom; |
} |
- // Add onto the padding defined by the Drawable's drop shadow. |
- int padding = res.getDimensionPixelSize(R.dimen.app_banner_padding); |
- int paddingStart = mBackgroundDrawablePadding.left + padding; |
- int paddingTop = mBackgroundDrawablePadding.top + padding; |
- int paddingEnd = mBackgroundDrawablePadding.right + padding; |
- int paddingBottom = mBackgroundDrawablePadding.bottom + padding; |
- ApiCompatibilityUtils.setPaddingRelative( |
- this, paddingStart, paddingTop, paddingEnd, paddingBottom); |
- |
// Pull out all of the controls we are expecting. |
mIconView = (ImageView) findViewById(R.id.app_icon); |
mTitleView = (TextView) findViewById(R.id.app_title); |
mButtonView = (Button) findViewById(R.id.app_install_button); |
mRatingView = (RatingView) findViewById(R.id.app_rating); |
- mLogoView = (ImageView) findViewById(R.id.store_logo); |
+ mLogoView = findViewById(R.id.store_logo); |
+ mBannerHighlightView = findViewById(R.id.banner_highlight); |
+ |
assert mIconView != null; |
assert mTitleView != null; |
assert mButtonView != null; |
assert mLogoView != null; |
assert mRatingView != null; |
+ assert mBannerHighlightView != null; |
// Set up the button to fire an event. |
mButtonView.setOnClickListener(this); |
@@ -199,7 +219,7 @@ public class AppBannerView extends SwipableOverlayView implements View.OnClickLi |
protected ViewGroup.MarginLayoutParams createLayoutParams() { |
// Define the margin around the entire banner that accounts for the drop shadow. |
ViewGroup.MarginLayoutParams params = super.createLayoutParams(); |
- params.setMargins(mMarginSide, 0, mMarginSide, mMarginBottom); |
+ params.setMargins(mMarginLeft, 0, mMarginRight, mMarginBottom); |
return params; |
} |
@@ -228,24 +248,25 @@ public class AppBannerView extends SwipableOverlayView implements View.OnClickLi |
void updateButtonState() { |
if (mButtonView == null) return; |
- int bgColor; |
+ Resources res = getResources(); |
int fgColor; |
String text; |
if (mAppData.installState() == AppData.INSTALL_STATE_INSTALLED) { |
- bgColor = getResources().getColor(R.color.app_banner_open_button_bg); |
- fgColor = getResources().getColor(R.color.app_banner_open_button_fg); |
- text = getResources().getString(R.string.app_banner_open); |
+ ApiCompatibilityUtils.setBackgroundForView(mButtonView, |
+ res.getDrawable(R.drawable.app_banner_button_open)); |
+ fgColor = res.getColor(R.color.app_banner_open_button_fg); |
+ text = res.getString(R.string.app_banner_open); |
} else { |
- bgColor = getResources().getColor(R.color.app_banner_install_button_bg); |
- fgColor = getResources().getColor(R.color.app_banner_install_button_fg); |
+ ApiCompatibilityUtils.setBackgroundForView(mButtonView, |
+ res.getDrawable(R.drawable.app_banner_button_install)); |
+ fgColor = res.getColor(R.color.app_banner_install_button_fg); |
if (mAppData.installState() == AppData.INSTALL_STATE_NOT_INSTALLED) { |
text = mAppData.installButtonText(); |
} else { |
- text = getResources().getString(R.string.app_banner_installing); |
+ text = res.getString(R.string.app_banner_installing); |
} |
} |
- mButtonView.setBackgroundColor(bgColor); |
mButtonView.setTextColor(fgColor); |
mButtonView.setText(text); |
} |
@@ -260,6 +281,34 @@ public class AppBannerView extends SwipableOverlayView implements View.OnClickLi |
} |
/** |
+ * Highlight the banner when the user has held it for long enough and doesn't move. |
+ * Passes all touch events through to the parent. |
+ */ |
+ @Override |
+ public boolean onTouchEvent(MotionEvent event) { |
+ int action = event.getActionMasked(); |
+ if (action == MotionEvent.ACTION_DOWN) { |
+ mIsBannerPressed = true; |
+ mInitialXForHighlight = event.getRawX(); |
+ getHandler().postDelayed(new Runnable() { |
+ @Override |
+ public void run() { |
+ // Highlight the banner if the user is still holding onto it. |
+ if (mIsBannerPressed) mBannerHighlightView.setVisibility(View.VISIBLE); |
+ } |
+ }, MS_HIGHLIGHT_APPEARANCE); |
+ } else if (mIsBannerPressed) { |
+ float xDifference = Math.abs(event.getRawX() - mInitialXForHighlight); |
+ if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL |
+ || (action == MotionEvent.ACTION_MOVE && xDifference > HIGHLIGHT_DISTANCE)) { |
+ mIsBannerPressed = false; |
+ mBannerHighlightView.setVisibility(View.INVISIBLE); |
+ } |
+ } |
+ |
+ return super.onTouchEvent(event); |
+ } |
+ /** |
* Fade the banner back into view. |
*/ |
@Override |
@@ -300,6 +349,7 @@ public class AppBannerView extends SwipableOverlayView implements View.OnClickLi |
mButtonView = null; |
mRatingView = null; |
mLogoView = null; |
+ mBannerHighlightView = null; |
AppBannerView cannibalized = |
(AppBannerView) LayoutInflater.from(getContext()).inflate(BANNER_LAYOUT, null); |
@@ -313,73 +363,125 @@ public class AppBannerView extends SwipableOverlayView implements View.OnClickLi |
} |
/** |
- * Measurement for components of the banner are performed using the following procedure: |
+ * Measures the banner and its children Views for the given space. |
+ * |
+ * DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD |
+ * DPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPD |
+ * DP...... cPD |
+ * DP...... TITLE-------------------------cPD |
+ * DP.ICON. ***** cPD |
+ * DP...... LOGO BUTTONcPD |
+ * DP...... cccccccccccccccccccccccccccccccPD |
+ * DPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPD |
+ * DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD |
* |
- * 00000000000000000000000000000000000000000000000000000 |
- * 01111155555555555555555555555555555555555555555555550 |
- * 01111155555555555555555555555555555555555555555555550 |
- * 01111144444444444440000000000000000000000222222222220 |
- * 01111133333333333330000000000000000000000222222222220 |
- * 00000000000000000000000000000000000000000000000000000 |
+ * The three paddings mentioned in the class Javadoc are denoted by: |
+ * D) Drop shadow padding. |
+ * P) Inner card padding. |
+ * c) Control padding. |
* |
- * 0) A maximum width is enforced on the banner, based on the smallest width of the screen, |
- * then padding defined by the 9-patch background Drawable is subtracted from all sides. |
- * 1) The icon takes up the left side of the banner. |
- * 2) The install button occupies the bottom-right of the banner. |
- * 3) The Google Play logo occupies the space to the left of the button. |
- * 4) The rating is assigned space above the logo and below the title. |
- * 5) The title is assigned whatever space is left. The maximum height of the banner is defined |
- * by deducting the height of either the install button or the logo + rating, (which is |
- * bigger). If the title cannot fit two lines comfortably, it is shrunk down to one. |
+ * Measurement for components of the banner are performed assuming that components are laid out |
+ * inside of the banner's background as follows: |
+ * 1) A maximum width is enforced on the banner to keep the whole thing on screen and keep it a |
+ * reasonable size. |
+ * 2) The icon takes up the left side of the banner. |
+ * 3) The install button occupies the bottom-right of the banner. |
+ * 4) The Google Play logo occupies the space to the left of the button. |
+ * 5) The rating is assigned space above the logo and below the title. |
+ * 6) The title is assigned whatever space is left and sits on top of the tallest stack of |
+ * controls. |
* |
* See {@link #android.view.View.onMeasure(int, int)} for the parameters. |
*/ |
@Override |
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
- // Enforce a maximum width on the banner. |
+ // Enforce a maximum width on the banner, which is defined as the smallest of: |
+ // 1) The smallest width for the device (in either landscape or portrait mode). |
+ // 2) The defined maximum width in the dimens.xml files. |
+ // 3) The width passed in through the MeasureSpec. |
Resources res = getResources(); |
float density = res.getDisplayMetrics().density; |
int screenSmallestWidth = (int) (res.getConfiguration().smallestScreenWidthDp * density); |
int specWidth = MeasureSpec.getSize(widthMeasureSpec); |
- int maxWidth = Math.min(Math.min(specWidth, mDefinedMaxWidth), screenSmallestWidth); |
- int maxHeight = MeasureSpec.getSize(heightMeasureSpec); |
- |
- // Track how much space is available for the banner content. |
- mSpaceMain.x = maxWidth - ApiCompatibilityUtils.getPaddingStart(this) |
- - ApiCompatibilityUtils.getPaddingEnd(this); |
- mSpaceMain.y = maxHeight - getPaddingTop() - getPaddingBottom(); |
- |
- // Measure the icon, which hugs the banner's starting edge and defines the banner's height. |
- measureChildForSpace(mIconView, mSpaceMain); |
- mSpaceMain.x -= getChildWidthWithMargins(mIconView); |
- mSpaceMain.y = getChildHeightWithMargins(mIconView) + getPaddingTop() + getPaddingBottom(); |
- |
- // Additional padding is defined by the mock for non-icon content on the end and bottom. |
- mSpaceMain.x -= mPaddingContent; |
- mSpaceMain.y -= mPaddingContent; |
- |
- // Measure the install button, which sits in the bottom-right corner. |
- measureChildForSpace(mButtonView, mSpaceMain); |
+ int bannerWidth = Math.min(Math.min(specWidth, mDefinedMaxWidth), screenSmallestWidth); |
+ |
+ // Track how much space is available inside the banner's card-shaped background Drawable. |
+ // To calculate this, we need to account for both the padding of the background (which |
+ // is occupied by the card's drop shadows) as well as the padding defined on the inside of |
+ // the card. |
+ int bgPaddingWidth = mBackgroundDrawablePadding.left + mBackgroundDrawablePadding.right; |
+ int bgPaddingHeight = mBackgroundDrawablePadding.top + mBackgroundDrawablePadding.bottom; |
+ final int maxControlWidth = bannerWidth - bgPaddingWidth - (mPaddingCard * 2); |
+ |
+ // Control height is constrained to provide a reasonable aspect ratio. |
+ // In practice, the only controls which can cause an issue are the title and the install |
+ // button, since they have strings that can change size according to user preference. The |
+ // other controls are all defined to be a certain height. |
+ int specHeight = MeasureSpec.getSize(heightMeasureSpec); |
+ int reasonableHeight = maxControlWidth / 4; |
+ int paddingHeight = bgPaddingHeight + (mPaddingCard * 2); |
+ final int maxControlHeight = Math.min(specHeight, reasonableHeight) - paddingHeight; |
+ final int maxStackedControlHeight = maxControlWidth / 3; |
+ |
+ // Determine how big each component wants to be. The icon is measured separately because |
+ // it is not stacked with the other controls. |
+ measureChildForSpace(mIconView, maxControlWidth, maxControlHeight); |
+ for (int i = 0; i < getChildCount(); i++) { |
+ if (getChildAt(i) != mIconView) { |
+ measureChildForSpace(getChildAt(i), maxControlWidth, maxStackedControlHeight); |
+ } |
+ } |
- // Measure the logo, which sits in the bottom-left corner next to the icon. |
- mSpaceForLogo.x = mSpaceMain.x - getChildWidthWithMargins(mButtonView); |
- mSpaceForLogo.y = mSpaceMain.y; |
- measureChildForSpace(mLogoView, mSpaceForLogo); |
+ // Determine how tall the banner needs to be to fit everything by calculating the combined |
+ // height of the stacked controls. There are three competing stacks to measure: |
+ // 1) The icon. |
+ // 2) The app title + control padding + star rating + store logo. |
+ // 3) The app title + control padding + install button. |
+ // The control padding is extra padding that applies only to the non-icon widgets. |
+ int iconStackHeight = getHeightWithMargins(mIconView); |
+ int logoStackHeight = getHeightWithMargins(mTitleView) + mPaddingControls |
+ + getHeightWithMargins(mRatingView) + getHeightWithMargins(mLogoView); |
+ int buttonStackHeight = getHeightWithMargins(mTitleView) + mPaddingControls |
+ + getHeightWithMargins(mButtonView); |
+ int biggestStackHeight = |
+ Math.max(iconStackHeight, Math.max(logoStackHeight, buttonStackHeight)); |
+ |
+ // The icon hugs the banner's starting edge, from the top of the banner to the bottom. |
+ final int iconSize = biggestStackHeight; |
+ measureChildForSpaceExactly(mIconView, iconSize, iconSize); |
+ |
+ // The rest of the content is laid out to the right of the icon. |
+ // Additional padding is defined for non-icon content on the end and bottom. |
+ final int contentWidth = |
+ maxControlWidth - getWidthWithMargins(mIconView) - mPaddingControls; |
+ final int contentHeight = biggestStackHeight - mPaddingControls; |
+ measureChildForSpace(mButtonView, contentWidth, contentHeight); |
+ measureChildForSpace(mLogoView, contentWidth, contentHeight); |
// Measure the star rating, which sits below the title and above the logo. |
- mSpaceForRating.x = mSpaceForLogo.x; |
- mSpaceForRating.y = mSpaceForLogo.y - getChildHeightWithMargins(mLogoView); |
- measureChildForSpace(mRatingView, mSpaceForRating); |
- |
- // The app title spans the top of the banner. |
- mSpaceForTitle.x = mSpaceMain.x; |
- mSpaceForTitle.y = mSpaceMain.y - getChildHeightWithMargins(mLogoView) |
- - getChildHeightWithMargins(mRatingView); |
- measureChildForSpace(mTitleView, mSpaceForTitle); |
- |
- // Set the measured dimensions for the banner. |
- int measuredHeight = mIconView.getMeasuredHeight() + getPaddingTop() + getPaddingBottom(); |
- setMeasuredDimension(maxWidth, measuredHeight); |
+ final int ratingWidth = contentWidth; |
+ final int ratingHeight = contentHeight - getHeightWithMargins(mLogoView); |
+ measureChildForSpace(mRatingView, ratingWidth, ratingHeight); |
+ |
+ // The app title spans the top of the banner and sits on top of the other controls. |
+ int biggerStack = Math.max(getHeightWithMargins(mButtonView), |
+ getHeightWithMargins(mLogoView) + getHeightWithMargins(mRatingView)); |
+ final int titleWidth = contentWidth; |
+ final int titleHeight = contentHeight - biggerStack; |
+ measureChildForSpace(mTitleView, titleWidth, titleHeight); |
+ |
+ // Set the measured dimensions for the banner. The banner's height is defined by the |
+ // tallest stack of components, the padding of the banner's card background, and the extra |
+ // padding around the banner's components. |
+ int bannerPadding = mBackgroundDrawablePadding.top + mBackgroundDrawablePadding.bottom |
+ + (mPaddingCard * 2); |
+ int bannerHeight = biggestStackHeight + bannerPadding; |
+ setMeasuredDimension(bannerWidth, bannerHeight); |
+ |
+ // Make the banner highlight view be the exact same size as the banner's card background. |
+ final int cardWidth = bannerWidth - bgPaddingWidth; |
+ final int cardHeight = bannerHeight - bgPaddingHeight; |
+ measureChildForSpaceExactly(mBannerHighlightView, cardWidth, cardHeight); |
} |
/** |
@@ -389,21 +491,29 @@ public class AppBannerView extends SwipableOverlayView implements View.OnClickLi |
@Override |
protected void onLayout(boolean changed, int l, int t, int r, int b) { |
super.onLayout(changed, l, t, r, b); |
+ int top = mBackgroundDrawablePadding.top; |
+ int bottom = getMeasuredHeight() - mBackgroundDrawablePadding.bottom; |
+ int start = mBackgroundDrawablePadding.left; |
+ int end = getMeasuredWidth() - mBackgroundDrawablePadding.right; |
+ |
+ // The highlight overlay covers the entire banner (minus drop shadow padding). |
+ mBannerHighlightView.layout(start, top, end, bottom); |
- int top = getPaddingTop(); |
- int bottom = getMeasuredHeight() - getPaddingBottom(); |
- int start = ApiCompatibilityUtils.getPaddingStart(this); |
- int end = getMeasuredWidth() - ApiCompatibilityUtils.getPaddingEnd(this); |
+ // Apply the padding for the rest of the widgets. |
+ top += mPaddingCard; |
+ bottom -= mPaddingCard; |
+ start += mPaddingCard; |
+ end -= mPaddingCard; |
// Lay out the icon. |
int iconWidth = mIconView.getMeasuredWidth(); |
int iconLeft = mIsLayoutLTR ? start : (getMeasuredWidth() - start - iconWidth); |
mIconView.layout(iconLeft, top, iconLeft + iconWidth, top + mIconView.getMeasuredHeight()); |
- start += getChildWidthWithMargins(mIconView); |
+ start += getWidthWithMargins(mIconView); |
- // Factor in the additional padding. |
- end -= mPaddingContent; |
- bottom -= mPaddingContent; |
+ // Factor in the additional padding, which is only tacked onto the end and bottom. |
+ end -= mPaddingControls; |
+ bottom -= mPaddingControls; |
// Lay out the app title text. |
int titleWidth = mTitleView.getMeasuredWidth(); |
@@ -440,37 +550,74 @@ public class AppBannerView extends SwipableOverlayView implements View.OnClickLi |
} |
/** |
+ * Measures a child for the given space, accounting for defined heights and margins. |
+ * @param child View to measure. |
+ * @param availableWidth Available width for the view. |
+ * @param availableHeight Available height for the view. |
+ */ |
+ private void measureChildForSpace(View child, int availableWidth, int availableHeight) { |
+ // Handle margins. |
+ availableWidth -= getMarginWidth(child); |
+ availableHeight -= getMarginHeight(child); |
+ |
+ // Account for any layout-defined dimensions for the view. |
+ int childWidth = child.getLayoutParams().width; |
+ int childHeight = child.getLayoutParams().height; |
+ if (childWidth >= 0) availableWidth = Math.min(availableWidth, childWidth); |
+ if (childHeight >= 0) availableHeight = Math.min(availableHeight, childHeight); |
+ |
+ int widthSpec = MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST); |
+ int heightSpec = MeasureSpec.makeMeasureSpec(availableHeight, MeasureSpec.AT_MOST); |
+ child.measure(widthSpec, heightSpec); |
+ } |
+ |
+ /** |
+ * Forces a child to exactly occupy the given space. |
+ * @param child View to measure. |
+ * @param availableWidth Available width for the view. |
+ * @param availableHeight Available height for the view. |
+ */ |
+ private void measureChildForSpaceExactly(View child, int availableWidth, int availableHeight) { |
+ int widthSpec = MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.EXACTLY); |
+ int heightSpec = MeasureSpec.makeMeasureSpec(availableHeight, MeasureSpec.EXACTLY); |
+ child.measure(widthSpec, heightSpec); |
+ } |
+ |
+ /** |
+ * Calculates how wide the margins are for the given View. |
+ * @param view View to measure. |
+ * @return Measured width of the margins. |
+ */ |
+ private static int getMarginWidth(View view) { |
+ MarginLayoutParams params = (MarginLayoutParams) view.getLayoutParams(); |
+ return params.leftMargin + params.rightMargin; |
+ } |
+ |
+ /** |
* Calculates how wide the given View has been measured to be, including its margins. |
- * @param child Child to measure. |
- * @return Measured width of the child plus its margins. |
+ * @param view View to measure. |
+ * @return Measured width of the view plus its margins. |
*/ |
- private int getChildWidthWithMargins(View child) { |
- MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams(); |
- return child.getMeasuredWidth() + ApiCompatibilityUtils.getMarginStart(params) |
- + ApiCompatibilityUtils.getMarginEnd(params); |
+ private static int getWidthWithMargins(View view) { |
+ return view.getMeasuredWidth() + getMarginWidth(view); |
} |
/** |
- * Calculates how tall the given View has been measured to be, including its margins. |
- * @param child Child to measure. |
- * @return Measured height of the child plus its margins. |
+ * Calculates how tall the margins are for the given View. |
+ * @param view View to measure. |
+ * @return Measured height of the margins. |
*/ |
- private static int getChildHeightWithMargins(View child) { |
- MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams(); |
- return child.getMeasuredHeight() + params.topMargin + params.bottomMargin; |
+ private static int getMarginHeight(View view) { |
+ MarginLayoutParams params = (MarginLayoutParams) view.getLayoutParams(); |
+ return params.topMargin + params.bottomMargin; |
} |
/** |
- * Measures a child so that it fits within the given space, taking into account heights defined |
- * in the layout. |
- * @param child View to measure. |
- * @param available Available space, with width stored in the x coordinate and height in the y. |
+ * Calculates how tall the given View has been measured to be, including its margins. |
+ * @param view View to measure. |
+ * @return Measured height of the view plus its margins. |
*/ |
- private void measureChildForSpace(View child, Point available) { |
- int childHeight = child.getLayoutParams().height; |
- int maxHeight = childHeight > 0 ? Math.min(available.y, childHeight) : available.y; |
- int widthSpec = MeasureSpec.makeMeasureSpec(available.x, MeasureSpec.AT_MOST); |
- int heightSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST); |
- measureChildWithMargins(child, widthSpec, 0, heightSpec, 0); |
+ private static int getHeightWithMargins(View view) { |
+ return view.getMeasuredHeight() + getMarginHeight(view); |
} |
} |