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

Side by Side Diff: chrome/android/java/src/org/chromium/chrome/browser/banners/AppBannerView.java

Issue 177863008: Update the AppBannerView appearance again (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Addressing comments hopefully Created 6 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright 2014 The Chromium Authors. All rights reserved. 1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 package org.chromium.chrome.browser.banners; 5 package org.chromium.chrome.browser.banners;
6 6
7 import android.animation.ObjectAnimator; 7 import android.animation.ObjectAnimator;
8 import android.content.Context; 8 import android.content.Context;
9 import android.content.res.Configuration; 9 import android.content.res.Configuration;
10 import android.content.res.Resources; 10 import android.content.res.Resources;
11 import android.graphics.Point;
12 import android.graphics.Rect; 11 import android.graphics.Rect;
13 import android.util.AttributeSet; 12 import android.util.AttributeSet;
14 import android.view.LayoutInflater; 13 import android.view.LayoutInflater;
14 import android.view.MotionEvent;
15 import android.view.View; 15 import android.view.View;
16 import android.view.ViewGroup; 16 import android.view.ViewGroup;
17 import android.widget.Button; 17 import android.widget.Button;
18 import android.widget.ImageView; 18 import android.widget.ImageView;
19 import android.widget.TextView; 19 import android.widget.TextView;
20 20
21 import org.chromium.base.ApiCompatibilityUtils; 21 import org.chromium.base.ApiCompatibilityUtils;
22 import org.chromium.chrome.R; 22 import org.chromium.chrome.R;
23 import org.chromium.content.browser.ContentView; 23 import org.chromium.content.browser.ContentView;
24 import org.chromium.ui.base.LocalizationUtils; 24 import org.chromium.ui.base.LocalizationUtils;
25 25
26 /** 26 /**
27 * Lays out a banner for showing info about an app on the Play Store. 27 * Lays out a banner for showing info about an app on the Play Store.
28 * Rather than utilizing the Android RatingBar, which would require some nasty s tyling, a custom 28 * The banner mimics the appearance of a Google Now card using a background Draw able with a shadow.
29 * View is used to paint a Drawable showing all the stars. Showing different ra tings is done by 29 *
30 * adjusting the clipping rectangle width. 30 * PADDING CALCULATIONS
31 * The banner has three different types of padding that need to be accounted for :
32 * 1) The background Drawable of the banner looks like card with a drop shadow. The Drawable
33 * defines a padding around the card that solely encompasses the space occupi ed by the drop
34 * shadow.
35 * 2) The card itself needs to have padding so that the widgets don't abut the b orders of the card.
36 * This is defined as mPaddingCard, and is equally applied to all four sides.
37 * 3) Controls other than the icon are further constrained by mPaddingControls, which applies only
38 * to the bottom and end margins.
39 * See {@link #AppBannerView.onMeasure(int, int)} for details.
40 *
41 * MARGIN CALCULATIONS
42 * Margin calculations for the banner are complicated by the background Drawable 's drop shadows,
43 * since the drop shadows are meant to be counted as being part of the margin. To deal with this,
44 * the margins are calculated by deducting the background Drawable's padding fro m the margins
45 * defined by the XML files.
46 *
47 * EVEN MORE LAYOUT QUIRKS
48 * The layout of the banner, which includes its widget sizes, may change when th e screen is rotated
49 * to account for less screen real estate. This means that all of the View's wi dgets and cached
50 * dimensions must be rebuilt from scratch.
31 */ 51 */
32 public class AppBannerView extends SwipableOverlayView implements View.OnClickLi stener { 52 public class AppBannerView extends SwipableOverlayView implements View.OnClickLi stener {
33 /** 53 /**
34 * Class that is alerted about things happening to the BannerView. 54 * Class that is alerted about things happening to the BannerView.
35 */ 55 */
36 public static interface Observer { 56 public static interface Observer {
37 /** 57 /**
38 * Called when the banner is dismissed. 58 * Called when the banner is dismissed.
39 * @param banner Banner being dismissed. 59 * @param banner Banner being dismissed.
40 */ 60 */
41 public void onBannerDismissed(AppBannerView banner); 61 public void onBannerDismissed(AppBannerView banner);
42 62
43 /** 63 /**
44 * Called when the install button has been clicked. 64 * Called when the install button has been clicked.
45 * @param banner Banner firing the event. 65 * @param banner Banner firing the event.
46 */ 66 */
47 public void onButtonClicked(AppBannerView banner); 67 public void onButtonClicked(AppBannerView banner);
48 68
49 /** 69 /**
50 * Called when something other than the button is clicked. 70 * Called when something other than the button is clicked.
51 * @param banner Banner firing the event. 71 * @param banner Banner firing the event.
52 */ 72 */
53 public void onBannerClicked(AppBannerView banner); 73 public void onBannerClicked(AppBannerView banner);
54 } 74 }
55 75
76 // Number of milliseconds to wait before showing the card highlight.
77 private static final long MS_HIGHLIGHT_APPEARANCE = 150;
78
56 // XML layout for the BannerView. 79 // XML layout for the BannerView.
57 private static final int BANNER_LAYOUT = R.layout.app_banner_view; 80 private static final int BANNER_LAYOUT = R.layout.app_banner_view;
58 81
82 // Maximum distance the finger can travel before dismissing the highlight.
83 private static final float HIGHLIGHT_DISTANCE = 20;
84
59 // True if the layout is in left-to-right layout mode (regular mode). 85 // True if the layout is in left-to-right layout mode (regular mode).
60 private final boolean mIsLayoutLTR; 86 private final boolean mIsLayoutLTR;
61 87
62 // Class to alert about BannerView events. 88 // Class to alert about BannerView events.
63 private AppBannerView.Observer mObserver; 89 private AppBannerView.Observer mObserver;
64 90
65 // Views comprising the app banner. 91 // Views comprising the app banner.
66 private ImageView mIconView; 92 private ImageView mIconView;
67 private TextView mTitleView; 93 private TextView mTitleView;
68 private Button mButtonView; 94 private Button mButtonView;
69 private RatingView mRatingView; 95 private RatingView mRatingView;
70 private ImageView mLogoView; 96 private View mLogoView;
97 private View mBannerHighlightView;
71 98
72 // Information about the package. 99 // Information about the package.
73 private AppData mAppData; 100 private AppData mAppData;
74 101
75 // Variables used during layout calculations and saved to avoid reallocation s.
76 private final Point mSpaceMain;
77 private final Point mSpaceForLogo;
78 private final Point mSpaceForRating;
79 private final Point mSpaceForTitle;
80
81 // Dimension values. 102 // Dimension values.
82 private int mDefinedMaxWidth; 103 private int mDefinedMaxWidth;
83 private int mPaddingContent; 104 private int mPaddingCard;
84 private int mMarginSide; 105 private int mPaddingControls;
106 private int mMarginLeft;
107 private int mMarginRight;
85 private int mMarginBottom; 108 private int mMarginBottom;
86 109
110 // Highlight variables.
111 private boolean mIsBannerPressed;
112 private float mInitialXForHighlight;
113
87 // Initial padding values. 114 // Initial padding values.
88 private final Rect mBackgroundDrawablePadding; 115 private final Rect mBackgroundDrawablePadding;
89 116
90 /** 117 /**
91 * Creates a BannerView and adds it to the given ContentView. 118 * Creates a BannerView and adds it to the given ContentView.
92 * @param contentView ContentView to display the AppBannerView for. 119 * @param contentView ContentView to display the AppBannerView for.
93 * @param observer Class that is alerted for AppBannerView events. 120 * @param observer Class that is alerted for AppBannerView events.
94 * @param data Data about the app. 121 * @param data Data about the app.
95 * @return The created banner. 122 * @return The created banner.
96 */ 123 */
97 public static AppBannerView create(ContentView contentView, Observer observe r, AppData data) { 124 public static AppBannerView create(ContentView contentView, Observer observe r, AppData data) {
98 Context context = contentView.getContext().getApplicationContext(); 125 Context context = contentView.getContext().getApplicationContext();
99 AppBannerView banner = 126 AppBannerView banner =
100 (AppBannerView) LayoutInflater.from(context).inflate(BANNER_LAYO UT, null); 127 (AppBannerView) LayoutInflater.from(context).inflate(BANNER_LAYO UT, null);
101 banner.initialize(observer, data); 128 banner.initialize(observer, data);
102 banner.addToView(contentView); 129 banner.addToView(contentView);
103 return banner; 130 return banner;
104 } 131 }
105 132
106 /** 133 /**
107 * Creates a BannerView from an XML layout. 134 * Creates a BannerView from an XML layout.
108 */ 135 */
109 public AppBannerView(Context context, AttributeSet attrs) { 136 public AppBannerView(Context context, AttributeSet attrs) {
110 super(context, attrs); 137 super(context, attrs);
111 mIsLayoutLTR = !LocalizationUtils.isSystemLayoutDirectionRtl(); 138 mIsLayoutLTR = !LocalizationUtils.isSystemLayoutDirectionRtl();
112 mSpaceMain = new Point();
113 mSpaceForLogo = new Point();
114 mSpaceForRating = new Point();
115 mSpaceForTitle = new Point();
116 139
117 // Store the background Drawable's padding. The background used for ban ners is a 9-patch, 140 // Store the background Drawable's padding. The background used for ban ners is a 9-patch,
118 // which means that it already defines padding. We need to take it into account when adding 141 // which means that it already defines padding. We need to take it into account when adding
119 // even more padding to the inside of it. 142 // even more padding to the inside of it.
120 mBackgroundDrawablePadding = new Rect(); 143 mBackgroundDrawablePadding = new Rect();
121 mBackgroundDrawablePadding.left = ApiCompatibilityUtils.getPaddingStart( this); 144 mBackgroundDrawablePadding.left = ApiCompatibilityUtils.getPaddingStart( this);
122 mBackgroundDrawablePadding.right = ApiCompatibilityUtils.getPaddingEnd(t his); 145 mBackgroundDrawablePadding.right = ApiCompatibilityUtils.getPaddingEnd(t his);
123 mBackgroundDrawablePadding.top = getPaddingTop(); 146 mBackgroundDrawablePadding.top = getPaddingTop();
124 mBackgroundDrawablePadding.bottom = getPaddingBottom(); 147 mBackgroundDrawablePadding.bottom = getPaddingBottom();
125 } 148 }
126 149
127 /** 150 /**
128 * Initialize the banner with information about the package. 151 * Initialize the banner with information about the package.
129 * @param observer Class to alert about changes to the banner. 152 * @param observer Class to alert about changes to the banner.
130 * @param data Information about the app being advertised. 153 * @param data Information about the app being advertised.
131 */ 154 */
132 private void initialize(Observer observer, AppData data) { 155 private void initialize(Observer observer, AppData data) {
133 mObserver = observer; 156 mObserver = observer;
134 mAppData = data; 157 mAppData = data;
135 initializeControls(); 158 initializeControls();
136 } 159 }
137 160
138 private void initializeControls() { 161 private void initializeControls() {
139 // Cache the banner dimensions, adjusting margins for drop shadows defin ed in the background 162 // Cache the banner dimensions, adjusting margins for drop shadows defin ed in the background
140 // Drawable. The Drawable is defined to have the same margin on both th e left and right. 163 // Drawable.
141 Resources res = getResources(); 164 Resources res = getResources();
142 mDefinedMaxWidth = res.getDimensionPixelSize(R.dimen.app_banner_max_widt h); 165 mDefinedMaxWidth = res.getDimensionPixelSize(R.dimen.app_banner_max_widt h);
143 mPaddingContent = res.getDimensionPixelSize(R.dimen.app_banner_padding_c ontent); 166 mPaddingCard = res.getDimensionPixelSize(R.dimen.app_banner_padding);
144 mMarginSide = res.getDimensionPixelSize(R.dimen.app_banner_margin_sides) 167 mPaddingControls = res.getDimensionPixelSize(R.dimen.app_banner_padding_ controls);
168 mMarginLeft = res.getDimensionPixelSize(R.dimen.app_banner_margin_sides)
145 - mBackgroundDrawablePadding.left; 169 - mBackgroundDrawablePadding.left;
170 mMarginRight = res.getDimensionPixelSize(R.dimen.app_banner_margin_sides )
171 - mBackgroundDrawablePadding.right;
146 mMarginBottom = res.getDimensionPixelSize(R.dimen.app_banner_margin_bott om) 172 mMarginBottom = res.getDimensionPixelSize(R.dimen.app_banner_margin_bott om)
147 - mBackgroundDrawablePadding.bottom; 173 - mBackgroundDrawablePadding.bottom;
148 if (getLayoutParams() != null) { 174 if (getLayoutParams() != null) {
149 MarginLayoutParams params = (MarginLayoutParams) getLayoutParams(); 175 MarginLayoutParams params = (MarginLayoutParams) getLayoutParams();
150 params.leftMargin = mMarginSide; 176 params.leftMargin = mMarginLeft;
151 params.rightMargin = mMarginSide; 177 params.rightMargin = mMarginRight;
152 params.bottomMargin = mMarginBottom; 178 params.bottomMargin = mMarginBottom;
153 } 179 }
154 180
155 // Add onto the padding defined by the Drawable's drop shadow.
156 int padding = res.getDimensionPixelSize(R.dimen.app_banner_padding);
157 int paddingStart = mBackgroundDrawablePadding.left + padding;
158 int paddingTop = mBackgroundDrawablePadding.top + padding;
159 int paddingEnd = mBackgroundDrawablePadding.right + padding;
160 int paddingBottom = mBackgroundDrawablePadding.bottom + padding;
161 ApiCompatibilityUtils.setPaddingRelative(
162 this, paddingStart, paddingTop, paddingEnd, paddingBottom);
163
164 // Pull out all of the controls we are expecting. 181 // Pull out all of the controls we are expecting.
165 mIconView = (ImageView) findViewById(R.id.app_icon); 182 mIconView = (ImageView) findViewById(R.id.app_icon);
166 mTitleView = (TextView) findViewById(R.id.app_title); 183 mTitleView = (TextView) findViewById(R.id.app_title);
167 mButtonView = (Button) findViewById(R.id.app_install_button); 184 mButtonView = (Button) findViewById(R.id.app_install_button);
168 mRatingView = (RatingView) findViewById(R.id.app_rating); 185 mRatingView = (RatingView) findViewById(R.id.app_rating);
169 mLogoView = (ImageView) findViewById(R.id.store_logo); 186 mLogoView = findViewById(R.id.store_logo);
187 mBannerHighlightView = findViewById(R.id.banner_highlight);
188
170 assert mIconView != null; 189 assert mIconView != null;
171 assert mTitleView != null; 190 assert mTitleView != null;
172 assert mButtonView != null; 191 assert mButtonView != null;
173 assert mLogoView != null; 192 assert mLogoView != null;
174 assert mRatingView != null; 193 assert mRatingView != null;
194 assert mBannerHighlightView != null;
175 195
176 // Set up the button to fire an event. 196 // Set up the button to fire an event.
177 mButtonView.setOnClickListener(this); 197 mButtonView.setOnClickListener(this);
178 198
179 // Configure the controls with the package information. 199 // Configure the controls with the package information.
180 mTitleView.setText(mAppData.title()); 200 mTitleView.setText(mAppData.title());
181 mIconView.setImageDrawable(mAppData.icon()); 201 mIconView.setImageDrawable(mAppData.icon());
182 mRatingView.initialize(mAppData.rating()); 202 mRatingView.initialize(mAppData.rating());
183 203
184 // Update the button state. 204 // Update the button state.
185 updateButtonState(); 205 updateButtonState();
186 } 206 }
187 207
188 @Override 208 @Override
189 public void onClick(View view) { 209 public void onClick(View view) {
190 if (mObserver != null && view == mButtonView) mObserver.onButtonClicked( this); 210 if (mObserver != null && view == mButtonView) mObserver.onButtonClicked( this);
191 } 211 }
192 212
193 @Override 213 @Override
194 protected void onViewClicked() { 214 protected void onViewClicked() {
195 if (mObserver != null) mObserver.onBannerClicked(this); 215 if (mObserver != null) mObserver.onBannerClicked(this);
196 } 216 }
197 217
198 @Override 218 @Override
199 protected ViewGroup.MarginLayoutParams createLayoutParams() { 219 protected ViewGroup.MarginLayoutParams createLayoutParams() {
200 // Define the margin around the entire banner that accounts for the drop shadow. 220 // Define the margin around the entire banner that accounts for the drop shadow.
201 ViewGroup.MarginLayoutParams params = super.createLayoutParams(); 221 ViewGroup.MarginLayoutParams params = super.createLayoutParams();
202 params.setMargins(mMarginSide, 0, mMarginSide, mMarginBottom); 222 params.setMargins(mMarginLeft, 0, mMarginRight, mMarginBottom);
203 return params; 223 return params;
204 } 224 }
205 225
206 /** 226 /**
207 * Removes this View from its parent and alerts any observers of the dismiss al. 227 * Removes this View from its parent and alerts any observers of the dismiss al.
208 * @return Whether or not the View was successfully dismissed. 228 * @return Whether or not the View was successfully dismissed.
209 */ 229 */
210 @Override 230 @Override
211 boolean removeFromParent() { 231 boolean removeFromParent() {
212 boolean removed = super.removeFromParent(); 232 boolean removed = super.removeFromParent();
213 if (removed) mObserver.onBannerDismissed(this); 233 if (removed) mObserver.onBannerDismissed(this);
214 return removed; 234 return removed;
215 } 235 }
216 236
217 /** 237 /**
218 * Returns data for the app the banner is being shown for. 238 * Returns data for the app the banner is being shown for.
219 * @return The AppData being used by the banner. 239 * @return The AppData being used by the banner.
220 */ 240 */
221 AppData getAppData() { 241 AppData getAppData() {
222 return mAppData; 242 return mAppData;
223 } 243 }
224 244
225 /** 245 /**
226 * Updates the text and color of the button displayed on the button. 246 * Updates the text and color of the button displayed on the button.
227 */ 247 */
228 void updateButtonState() { 248 void updateButtonState() {
229 if (mButtonView == null) return; 249 if (mButtonView == null) return;
230 250
231 int bgColor; 251 Resources res = getResources();
232 int fgColor; 252 int fgColor;
233 String text; 253 String text;
234 if (mAppData.installState() == AppData.INSTALL_STATE_INSTALLED) { 254 if (mAppData.installState() == AppData.INSTALL_STATE_INSTALLED) {
235 bgColor = getResources().getColor(R.color.app_banner_open_button_bg) ; 255 ApiCompatibilityUtils.setBackgroundForView(mButtonView,
236 fgColor = getResources().getColor(R.color.app_banner_open_button_fg) ; 256 res.getDrawable(R.drawable.app_banner_button_open));
237 text = getResources().getString(R.string.app_banner_open); 257 fgColor = res.getColor(R.color.app_banner_open_button_fg);
258 text = res.getString(R.string.app_banner_open);
238 } else { 259 } else {
239 bgColor = getResources().getColor(R.color.app_banner_install_button_ bg); 260 ApiCompatibilityUtils.setBackgroundForView(mButtonView,
240 fgColor = getResources().getColor(R.color.app_banner_install_button_ fg); 261 res.getDrawable(R.drawable.app_banner_button_install));
262 fgColor = res.getColor(R.color.app_banner_install_button_fg);
241 if (mAppData.installState() == AppData.INSTALL_STATE_NOT_INSTALLED) { 263 if (mAppData.installState() == AppData.INSTALL_STATE_NOT_INSTALLED) {
242 text = mAppData.installButtonText(); 264 text = mAppData.installButtonText();
243 } else { 265 } else {
244 text = getResources().getString(R.string.app_banner_installing); 266 text = res.getString(R.string.app_banner_installing);
245 } 267 }
246 } 268 }
247 269
248 mButtonView.setBackgroundColor(bgColor);
249 mButtonView.setTextColor(fgColor); 270 mButtonView.setTextColor(fgColor);
250 mButtonView.setText(text); 271 mButtonView.setText(text);
251 } 272 }
252 273
253 /** 274 /**
254 * Determine how big an icon needs to be for the Layout. 275 * Determine how big an icon needs to be for the Layout.
255 * @param context Context to grab resources from. 276 * @param context Context to grab resources from.
256 * @return How big the icon is expected to be, in pixels. 277 * @return How big the icon is expected to be, in pixels.
257 */ 278 */
258 static int getIconSize(Context context) { 279 static int getIconSize(Context context) {
259 return context.getResources().getDimensionPixelSize(R.dimen.app_banner_i con_size); 280 return context.getResources().getDimensionPixelSize(R.dimen.app_banner_i con_size);
260 } 281 }
261 282
262 /** 283 /**
284 * Highlight the banner when the user has held it for long enough and doesn' t move.
285 * Passes all touch events through to the parent.
286 */
287 @Override
288 public boolean onTouchEvent(MotionEvent event) {
289 int action = event.getActionMasked();
290 if (action == MotionEvent.ACTION_DOWN) {
291 mIsBannerPressed = true;
292 mInitialXForHighlight = event.getRawX();
293 getHandler().postDelayed(new Runnable() {
294 @Override
295 public void run() {
296 // Highlight the banner if the user is still holding onto it .
297 if (mIsBannerPressed) mBannerHighlightView.setVisibility(Vie w.VISIBLE);
298 }
299 }, MS_HIGHLIGHT_APPEARANCE);
300 } else if (mIsBannerPressed) {
301 float xDifference = Math.abs(event.getRawX() - mInitialXForHighlight );
302 if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_ CANCEL
303 || (action == MotionEvent.ACTION_MOVE && xDifference > HIGHL IGHT_DISTANCE)) {
304 mIsBannerPressed = false;
305 mBannerHighlightView.setVisibility(View.INVISIBLE);
306 }
307 }
308
309 return super.onTouchEvent(event);
310 }
311 /**
263 * Fade the banner back into view. 312 * Fade the banner back into view.
264 */ 313 */
265 @Override 314 @Override
266 protected void onAttachedToWindow() { 315 protected void onAttachedToWindow() {
267 super.onAttachedToWindow(); 316 super.onAttachedToWindow();
268 ObjectAnimator.ofFloat(this, "alpha", getAlpha(), 1.f).setDuration( 317 ObjectAnimator.ofFloat(this, "alpha", getAlpha(), 1.f).setDuration(
269 MS_ANIMATION_DURATION).start(); 318 MS_ANIMATION_DURATION).start();
270 setVisibility(VISIBLE); 319 setVisibility(VISIBLE);
271 } 320 }
272 321
(...skipping 20 matching lines...) Expand all
293 if (mDefinedMaxWidth == newDefinedWidth) return; 342 if (mDefinedMaxWidth == newDefinedWidth) return;
294 343
295 // Cannibalize another version of this layout to get Views using the new resources and 344 // Cannibalize another version of this layout to get Views using the new resources and
296 // sizes. 345 // sizes.
297 while (getChildCount() > 0) removeViewAt(0); 346 while (getChildCount() > 0) removeViewAt(0);
298 mIconView = null; 347 mIconView = null;
299 mTitleView = null; 348 mTitleView = null;
300 mButtonView = null; 349 mButtonView = null;
301 mRatingView = null; 350 mRatingView = null;
302 mLogoView = null; 351 mLogoView = null;
352 mBannerHighlightView = null;
303 353
304 AppBannerView cannibalized = 354 AppBannerView cannibalized =
305 (AppBannerView) LayoutInflater.from(getContext()).inflate(BANNER _LAYOUT, null); 355 (AppBannerView) LayoutInflater.from(getContext()).inflate(BANNER _LAYOUT, null);
306 while (cannibalized.getChildCount() > 0) { 356 while (cannibalized.getChildCount() > 0) {
307 View child = cannibalized.getChildAt(0); 357 View child = cannibalized.getChildAt(0);
308 cannibalized.removeViewAt(0); 358 cannibalized.removeViewAt(0);
309 addView(child); 359 addView(child);
310 } 360 }
311 initializeControls(); 361 initializeControls();
312 requestLayout(); 362 requestLayout();
313 } 363 }
314 364
315 /** 365 /**
316 * Measurement for components of the banner are performed using the followin g procedure: 366 * Measures the banner and its children Views for the given space.
317 * 367 *
318 * 00000000000000000000000000000000000000000000000000000 368 * DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
319 * 01111155555555555555555555555555555555555555555555550 369 * DPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPD
320 * 01111155555555555555555555555555555555555555555555550 370 * DP...... cPD
321 * 01111144444444444440000000000000000000000222222222220 371 * DP...... TITLE-------------------------cPD
322 * 01111133333333333330000000000000000000000222222222220 372 * DP.ICON. ***** cPD
323 * 00000000000000000000000000000000000000000000000000000 373 * DP...... LOGO BUTTONcPD
374 * DP...... cccccccccccccccccccccccccccccccPD
375 * DPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPD
376 * DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
324 * 377 *
325 * 0) A maximum width is enforced on the banner, based on the smallest width of the screen, 378 * The three paddings mentioned in the class Javadoc are denoted by:
326 * then padding defined by the 9-patch background Drawable is subtracted from all sides. 379 * D) Drop shadow padding.
327 * 1) The icon takes up the left side of the banner. 380 * P) Inner card padding.
328 * 2) The install button occupies the bottom-right of the banner. 381 * c) Control padding.
329 * 3) The Google Play logo occupies the space to the left of the button. 382 *
330 * 4) The rating is assigned space above the logo and below the title. 383 * Measurement for components of the banner are performed assuming that comp onents are laid out
331 * 5) The title is assigned whatever space is left. The maximum height of t he banner is defined 384 * inside of the banner's background as follows:
332 * by deducting the height of either the install button or the logo + rat ing, (which is 385 * 1) A maximum width is enforced on the banner to keep the whole thing on s creen and keep it a
333 * bigger). If the title cannot fit two lines comfortably, it is shrunk down to one. 386 * reasonable size.
387 * 2) The icon takes up the left side of the banner.
388 * 3) The install button occupies the bottom-right of the banner.
389 * 4) The Google Play logo occupies the space to the left of the button.
390 * 5) The rating is assigned space above the logo and below the title.
391 * 6) The title is assigned whatever space is left and sits on top of the ta llest stack of
392 * controls.
334 * 393 *
335 * See {@link #android.view.View.onMeasure(int, int)} for the parameters. 394 * See {@link #android.view.View.onMeasure(int, int)} for the parameters.
336 */ 395 */
337 @Override 396 @Override
338 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 397 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
339 // Enforce a maximum width on the banner. 398 // Enforce a maximum width on the banner, which is defined as the smalle st of:
399 // 1) The smallest width for the device (in either landscape or portrait mode).
400 // 2) The defined maximum width in the dimens.xml files.
401 // 3) The width passed in through the MeasureSpec.
340 Resources res = getResources(); 402 Resources res = getResources();
341 float density = res.getDisplayMetrics().density; 403 float density = res.getDisplayMetrics().density;
342 int screenSmallestWidth = (int) (res.getConfiguration().smallestScreenWi dthDp * density); 404 int screenSmallestWidth = (int) (res.getConfiguration().smallestScreenWi dthDp * density);
343 int specWidth = MeasureSpec.getSize(widthMeasureSpec); 405 int specWidth = MeasureSpec.getSize(widthMeasureSpec);
344 int maxWidth = Math.min(Math.min(specWidth, mDefinedMaxWidth), screenSma llestWidth); 406 int bannerWidth = Math.min(Math.min(specWidth, mDefinedMaxWidth), screen SmallestWidth);
345 int maxHeight = MeasureSpec.getSize(heightMeasureSpec);
346 407
347 // Track how much space is available for the banner content. 408 // Track how much space is available inside the banner's card-shaped bac kground Drawable.
348 mSpaceMain.x = maxWidth - ApiCompatibilityUtils.getPaddingStart(this) 409 // To calculate this, we need to account for both the padding of the bac kground (which
349 - ApiCompatibilityUtils.getPaddingEnd(this); 410 // is occupied by the card's drop shadows) as well as the padding define d on the inside of
350 mSpaceMain.y = maxHeight - getPaddingTop() - getPaddingBottom(); 411 // the card.
412 int bgPaddingWidth = mBackgroundDrawablePadding.left + mBackgroundDrawab lePadding.right;
413 int bgPaddingHeight = mBackgroundDrawablePadding.top + mBackgroundDrawab lePadding.bottom;
414 final int maxControlWidth = bannerWidth - bgPaddingWidth - (mPaddingCard * 2);
351 415
352 // Measure the icon, which hugs the banner's starting edge and defines t he banner's height. 416 // Control height is constrained to provide a reasonable aspect ratio.
353 measureChildForSpace(mIconView, mSpaceMain); 417 // In practice, the only controls which can cause an issue are the title and the install
354 mSpaceMain.x -= getChildWidthWithMargins(mIconView); 418 // button, since they have strings that can change size according to use r preference. The
355 mSpaceMain.y = getChildHeightWithMargins(mIconView) + getPaddingTop() + getPaddingBottom(); 419 // other controls are all defined to be a certain height.
420 int specHeight = MeasureSpec.getSize(heightMeasureSpec);
421 int reasonableHeight = maxControlWidth / 4;
422 int paddingHeight = bgPaddingHeight + (mPaddingCard * 2);
423 final int maxControlHeight = Math.min(specHeight, reasonableHeight) - pa ddingHeight;
424 final int maxStackedControlHeight = maxControlWidth / 3;
356 425
357 // Additional padding is defined by the mock for non-icon content on the end and bottom. 426 // Determine how big each component wants to be. The icon is measured s eparately because
358 mSpaceMain.x -= mPaddingContent; 427 // it is not stacked with the other controls.
359 mSpaceMain.y -= mPaddingContent; 428 measureChildForSpace(mIconView, maxControlWidth, maxControlHeight);
429 for (int i = 0; i < getChildCount(); i++) {
430 if (getChildAt(i) != mIconView) {
431 measureChildForSpace(getChildAt(i), maxControlWidth, maxStackedC ontrolHeight);
432 }
433 }
360 434
361 // Measure the install button, which sits in the bottom-right corner. 435 // Determine how tall the banner needs to be to fit everything by calcul ating the combined
362 measureChildForSpace(mButtonView, mSpaceMain); 436 // height of the stacked controls. There are three competing stacks to measure:
437 // 1) The icon.
438 // 2) The app title + control padding + star rating + store logo.
439 // 3) The app title + control padding + install button.
440 // The control padding is extra padding that applies only to the non-ico n widgets.
441 int iconStackHeight = getHeightWithMargins(mIconView);
442 int logoStackHeight = getHeightWithMargins(mTitleView) + mPaddingControl s
443 + getHeightWithMargins(mRatingView) + getHeightWithMargins(mLogo View);
444 int buttonStackHeight = getHeightWithMargins(mTitleView) + mPaddingContr ols
445 + getHeightWithMargins(mButtonView);
446 int biggestStackHeight =
447 Math.max(iconStackHeight, Math.max(logoStackHeight, buttonStackH eight));
363 448
364 // Measure the logo, which sits in the bottom-left corner next to the ic on. 449 // The icon hugs the banner's starting edge, from the top of the banner to the bottom.
365 mSpaceForLogo.x = mSpaceMain.x - getChildWidthWithMargins(mButtonView); 450 final int iconSize = biggestStackHeight;
366 mSpaceForLogo.y = mSpaceMain.y; 451 measureChildForSpaceExactly(mIconView, iconSize, iconSize);
367 measureChildForSpace(mLogoView, mSpaceForLogo); 452
453 // The rest of the content is laid out to the right of the icon.
454 // Additional padding is defined for non-icon content on the end and bot tom.
455 final int contentWidth =
456 maxControlWidth - getWidthWithMargins(mIconView) - mPaddingContr ols;
457 final int contentHeight = biggestStackHeight - mPaddingControls;
458 measureChildForSpace(mButtonView, contentWidth, contentHeight);
459 measureChildForSpace(mLogoView, contentWidth, contentHeight);
368 460
369 // Measure the star rating, which sits below the title and above the log o. 461 // Measure the star rating, which sits below the title and above the log o.
370 mSpaceForRating.x = mSpaceForLogo.x; 462 final int ratingWidth = contentWidth;
371 mSpaceForRating.y = mSpaceForLogo.y - getChildHeightWithMargins(mLogoVie w); 463 final int ratingHeight = contentHeight - getHeightWithMargins(mLogoView) ;
372 measureChildForSpace(mRatingView, mSpaceForRating); 464 measureChildForSpace(mRatingView, ratingWidth, ratingHeight);
373 465
374 // The app title spans the top of the banner. 466 // The app title spans the top of the banner and sits on top of the othe r controls.
375 mSpaceForTitle.x = mSpaceMain.x; 467 int biggerStack = Math.max(getHeightWithMargins(mButtonView),
376 mSpaceForTitle.y = mSpaceMain.y - getChildHeightWithMargins(mLogoView) 468 getHeightWithMargins(mLogoView) + getHeightWithMargins(mRatingVi ew));
377 - getChildHeightWithMargins(mRatingView); 469 final int titleWidth = contentWidth;
378 measureChildForSpace(mTitleView, mSpaceForTitle); 470 final int titleHeight = contentHeight - biggerStack;
471 measureChildForSpace(mTitleView, titleWidth, titleHeight);
379 472
380 // Set the measured dimensions for the banner. 473 // Set the measured dimensions for the banner. The banner's height is d efined by the
381 int measuredHeight = mIconView.getMeasuredHeight() + getPaddingTop() + g etPaddingBottom(); 474 // tallest stack of components, the padding of the banner's card backgro und, and the extra
382 setMeasuredDimension(maxWidth, measuredHeight); 475 // padding around the banner's components.
476 int bannerPadding = mBackgroundDrawablePadding.top + mBackgroundDrawable Padding.bottom
477 + (mPaddingCard * 2);
478 int bannerHeight = biggestStackHeight + bannerPadding;
479 setMeasuredDimension(bannerWidth, bannerHeight);
480
481 // Make the banner highlight view be the exact same size as the banner's card background.
482 final int cardWidth = bannerWidth - bgPaddingWidth;
483 final int cardHeight = bannerHeight - bgPaddingHeight;
484 measureChildForSpaceExactly(mBannerHighlightView, cardWidth, cardHeight) ;
383 } 485 }
384 486
385 /** 487 /**
386 * Lays out the controls according to the algorithm in {@link #onMeasure}. 488 * Lays out the controls according to the algorithm in {@link #onMeasure}.
387 * See {@link #android.view.View.onLayout(boolean, int, int, int, int)} for the parameters. 489 * See {@link #android.view.View.onLayout(boolean, int, int, int, int)} for the parameters.
388 */ 490 */
389 @Override 491 @Override
390 protected void onLayout(boolean changed, int l, int t, int r, int b) { 492 protected void onLayout(boolean changed, int l, int t, int r, int b) {
391 super.onLayout(changed, l, t, r, b); 493 super.onLayout(changed, l, t, r, b);
494 int top = mBackgroundDrawablePadding.top;
495 int bottom = getMeasuredHeight() - mBackgroundDrawablePadding.bottom;
496 int start = mBackgroundDrawablePadding.left;
497 int end = getMeasuredWidth() - mBackgroundDrawablePadding.right;
392 498
393 int top = getPaddingTop(); 499 // The highlight overlay covers the entire banner (minus drop shadow pad ding).
394 int bottom = getMeasuredHeight() - getPaddingBottom(); 500 mBannerHighlightView.layout(start, top, end, bottom);
395 int start = ApiCompatibilityUtils.getPaddingStart(this); 501
396 int end = getMeasuredWidth() - ApiCompatibilityUtils.getPaddingEnd(this) ; 502 // Apply the padding for the rest of the widgets.
503 top += mPaddingCard;
504 bottom -= mPaddingCard;
505 start += mPaddingCard;
506 end -= mPaddingCard;
397 507
398 // Lay out the icon. 508 // Lay out the icon.
399 int iconWidth = mIconView.getMeasuredWidth(); 509 int iconWidth = mIconView.getMeasuredWidth();
400 int iconLeft = mIsLayoutLTR ? start : (getMeasuredWidth() - start - icon Width); 510 int iconLeft = mIsLayoutLTR ? start : (getMeasuredWidth() - start - icon Width);
401 mIconView.layout(iconLeft, top, iconLeft + iconWidth, top + mIconView.ge tMeasuredHeight()); 511 mIconView.layout(iconLeft, top, iconLeft + iconWidth, top + mIconView.ge tMeasuredHeight());
402 start += getChildWidthWithMargins(mIconView); 512 start += getWidthWithMargins(mIconView);
403 513
404 // Factor in the additional padding. 514 // Factor in the additional padding, which is only tacked onto the end a nd bottom.
405 end -= mPaddingContent; 515 end -= mPaddingControls;
406 bottom -= mPaddingContent; 516 bottom -= mPaddingControls;
407 517
408 // Lay out the app title text. 518 // Lay out the app title text.
409 int titleWidth = mTitleView.getMeasuredWidth(); 519 int titleWidth = mTitleView.getMeasuredWidth();
410 int titleTop = top + ((MarginLayoutParams) mTitleView.getLayoutParams()) .topMargin; 520 int titleTop = top + ((MarginLayoutParams) mTitleView.getLayoutParams()) .topMargin;
411 int titleBottom = titleTop + mTitleView.getMeasuredHeight(); 521 int titleBottom = titleTop + mTitleView.getMeasuredHeight();
412 int titleLeft = mIsLayoutLTR ? start : (getMeasuredWidth() - start - tit leWidth); 522 int titleLeft = mIsLayoutLTR ? start : (getMeasuredWidth() - start - tit leWidth);
413 mTitleView.layout(titleLeft, titleTop, titleLeft + titleWidth, titleBott om); 523 mTitleView.layout(titleLeft, titleTop, titleLeft + titleWidth, titleBott om);
414 524
415 // The mock shows the margin eating into the descender area of the TextV iew. 525 // The mock shows the margin eating into the descender area of the TextV iew.
416 int textBaseline = mTitleView.getLineBounds(mTitleView.getLineCount() - 1, null); 526 int textBaseline = mTitleView.getLineBounds(mTitleView.getLineCount() - 1, null);
(...skipping 16 matching lines...) Expand all
433 543
434 // Lay out the install button in the bottom-right corner. 544 // Lay out the install button in the bottom-right corner.
435 int buttonHeight = mButtonView.getMeasuredHeight(); 545 int buttonHeight = mButtonView.getMeasuredHeight();
436 int buttonWidth = mButtonView.getMeasuredWidth(); 546 int buttonWidth = mButtonView.getMeasuredWidth();
437 int buttonRight = mIsLayoutLTR ? end : (getMeasuredWidth() - end + butto nWidth); 547 int buttonRight = mIsLayoutLTR ? end : (getMeasuredWidth() - end + butto nWidth);
438 int buttonLeft = buttonRight - buttonWidth; 548 int buttonLeft = buttonRight - buttonWidth;
439 mButtonView.layout(buttonLeft, bottom - buttonHeight, buttonRight, botto m); 549 mButtonView.layout(buttonLeft, bottom - buttonHeight, buttonRight, botto m);
440 } 550 }
441 551
442 /** 552 /**
553 * Measures a child for the given space, accounting for defined heights and margins.
554 * @param child View to measure.
555 * @param availableWidth Available width for the view.
556 * @param availableHeight Available height for the view.
557 */
558 private void measureChildForSpace(View child, int availableWidth, int availa bleHeight) {
559 // Handle margins.
560 availableWidth -= getMarginWidth(child);
561 availableHeight -= getMarginHeight(child);
562
563 // Account for any layout-defined dimensions for the view.
564 int childWidth = child.getLayoutParams().width;
565 int childHeight = child.getLayoutParams().height;
566 if (childWidth >= 0) availableWidth = Math.min(availableWidth, childWidt h);
567 if (childHeight >= 0) availableHeight = Math.min(availableHeight, childH eight);
568
569 int widthSpec = MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec. AT_MOST);
570 int heightSpec = MeasureSpec.makeMeasureSpec(availableHeight, MeasureSpe c.AT_MOST);
571 child.measure(widthSpec, heightSpec);
572 }
573
574 /**
575 * Forces a child to exactly occupy the given space.
576 * @param child View to measure.
577 * @param availableWidth Available width for the view.
578 * @param availableHeight Available height for the view.
579 */
580 private void measureChildForSpaceExactly(View child, int availableWidth, int availableHeight) {
581 int widthSpec = MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec. EXACTLY);
582 int heightSpec = MeasureSpec.makeMeasureSpec(availableHeight, MeasureSpe c.EXACTLY);
583 child.measure(widthSpec, heightSpec);
584 }
585
586 /**
587 * Calculates how wide the margins are for the given View.
588 * @param view View to measure.
589 * @return Measured width of the margins.
590 */
591 private static int getMarginWidth(View view) {
592 MarginLayoutParams params = (MarginLayoutParams) view.getLayoutParams();
593 return params.leftMargin + params.rightMargin;
594 }
595
596 /**
443 * Calculates how wide the given View has been measured to be, including its margins. 597 * Calculates how wide the given View has been measured to be, including its margins.
444 * @param child Child to measure. 598 * @param view View to measure.
445 * @return Measured width of the child plus its margins. 599 * @return Measured width of the view plus its margins.
446 */ 600 */
447 private int getChildWidthWithMargins(View child) { 601 private static int getWidthWithMargins(View view) {
448 MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams() ; 602 return view.getMeasuredWidth() + getMarginWidth(view);
449 return child.getMeasuredWidth() + ApiCompatibilityUtils.getMarginStart(p arams) 603 }
450 + ApiCompatibilityUtils.getMarginEnd(params); 604
605 /**
606 * Calculates how tall the margins are for the given View.
607 * @param view View to measure.
608 * @return Measured height of the margins.
609 */
610 private static int getMarginHeight(View view) {
611 MarginLayoutParams params = (MarginLayoutParams) view.getLayoutParams();
612 return params.topMargin + params.bottomMargin;
451 } 613 }
452 614
453 /** 615 /**
454 * Calculates how tall the given View has been measured to be, including its margins. 616 * Calculates how tall the given View has been measured to be, including its margins.
455 * @param child Child to measure. 617 * @param view View to measure.
456 * @return Measured height of the child plus its margins. 618 * @return Measured height of the view plus its margins.
457 */ 619 */
458 private static int getChildHeightWithMargins(View child) { 620 private static int getHeightWithMargins(View view) {
459 MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams() ; 621 return view.getMeasuredHeight() + getMarginHeight(view);
460 return child.getMeasuredHeight() + params.topMargin + params.bottomMargi n;
461 }
462
463 /**
464 * Measures a child so that it fits within the given space, taking into acco unt heights defined
465 * in the layout.
466 * @param child View to measure.
467 * @param available Available space, with width stored in the x coordinate a nd height in the y.
468 */
469 private void measureChildForSpace(View child, Point available) {
470 int childHeight = child.getLayoutParams().height;
471 int maxHeight = childHeight > 0 ? Math.min(available.y, childHeight) : a vailable.y;
472 int widthSpec = MeasureSpec.makeMeasureSpec(available.x, MeasureSpec.AT_ MOST);
473 int heightSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_M OST);
474 measureChildWithMargins(child, widthSpec, 0, heightSpec, 0);
475 } 622 }
476 } 623 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698