OLD | NEW |
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 Loading... |
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 Loading... |
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 } |
OLD | NEW |