1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the License 10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 * or implied. See the License for the specific language governing permissions and limitations under 12 * the License. 13 */ 14package android.support.v17.leanback.widget; 15 16import android.content.Context; 17import android.content.res.TypedArray; 18import android.graphics.drawable.Drawable; 19import android.support.annotation.ColorInt; 20import android.support.v17.leanback.R; 21import android.util.AttributeSet; 22import android.view.ContextThemeWrapper; 23import android.view.LayoutInflater; 24import android.view.View; 25import android.view.ViewGroup; 26import android.widget.ImageView; 27import android.widget.ImageView.ScaleType; 28import android.widget.RelativeLayout; 29import android.widget.TextView; 30 31/** 32 * A subclass of {@link BaseCardView} with an {@link ImageView} as its main region. The 33 * {@link ImageCardView} is highly customizable and can be used for various use-cases by adjusting 34 * the ImageViewCard's type to any combination of Title, Content, Badge or ImageOnly. 35 * <p> 36 * <h3>Styling</h3> There are two different ways to style the ImageCardView. <br> 37 * No matter what way you use, all your styles applied to an ImageCardView have to extend the style 38 * {@link R.style#Widget_Leanback_ImageCardViewStyle}. 39 * <p> 40 * <u>Example:</u><br> 41 * 42 * <pre> 43 * {@code 44 * <style name="CustomImageCardViewStyle" parent="Widget.Leanback.ImageCardViewStyle"> 45 <item name="cardBackground">#F0F</item> 46 <item name="lbImageCardViewType">Title|Content</item> 47 </style> 48 <style name="CustomImageCardTheme" parent="Theme.Leanback"> 49 <item name="imageCardViewStyle">@style/CustomImageCardViewStyle</item> 50 <item name="imageCardViewInfoAreaStyle">@style/ImageCardViewColoredInfoArea</item> 51 <item name="imageCardViewTitleStyle">@style/ImageCardViewColoredTitle</item> 52 </style>} 53 * </pre> 54 * <p> 55 * The first possibility is to set custom Styles in the Leanback Theme's attributes 56 * <code>imageCardViewStyle</code>, <code>imageCardViewTitleStyle</code> etc. The styles set here, 57 * is the default style for all ImageCardViews. 58 * <p> 59 * The second possibility allows you to style a particular ImageCardView. This is useful if you 60 * want to create multiple types of cards. E.g. you might want to display a card with only a title 61 * and another one with title and content. Thus you need to define two different 62 * <code>ImageCardViewStyles</code> and two different themes and apply them to the ImageCardViews. 63 * You can do this by using a the {@link #ImageCardView(Context)} constructor and passing a 64 * ContextThemeWrapper with the custom ImageCardView theme id. 65 * <p> 66 * <u>Example (using constructor):</u><br> 67 * 68 * <pre> 69 * {@code 70 * new ImageCardView(new ContextThemeWrapper(context, R.style.CustomImageCardTheme)); 71 * } 72 * </pre> 73 * 74 * <p> 75 * You can style all ImageCardView's components such as the title, content, badge, infoArea and the 76 * image itself by extending the corresponding style and overriding the specific attribute in your 77 * custom ImageCardView theme. 78 * 79 * <h3>Components</h3> The ImageCardView contains three components which can be combined in any 80 * combination: 81 * <ul> 82 * <li>Title: The card's title</li> 83 * <li>Content: A short description</li> 84 * <li>Badge: An icon which can be displayed on the right or left side of the card.</li> 85 * </ul> 86 * In order to choose the components you want to use in your ImageCardView, you have to specify them 87 * in the <code>lbImageCardViewType</code> attribute of your custom <code>ImageCardViewStyle</code>. 88 * You can combine the following values: 89 * <code>Title, Content, IconOnRight, IconOnLeft, ImageOnly</code>. 90 * <p> 91 * <u>Examples:</u><br> 92 * 93 * <pre> 94 * {@code <style name="CustomImageCardViewStyle" parent="Widget.Leanback.ImageCardViewStyle"> 95 ... 96 <item name="lbImageCardViewType">Title|Content|IconOnLeft</item> 97 ... 98 </style>} 99 * </pre> 100 * 101 * <pre> 102 * {@code <style name="CustomImageCardViewStyle" parent="Widget.Leanback.ImageCardViewStyle"> 103 ... 104 <item name="lbImageCardViewType">ImageOnly</item> 105 ... 106 </style>} 107 * </pre> 108 * 109 * @attr ref android.support.v17.leanback.R.styleable#LeanbackTheme_imageCardViewStyle 110 * @attr ref android.support.v17.leanback.R.styleable#lbImageCardView_lbImageCardViewType 111 * @attr ref android.support.v17.leanback.R.styleable#LeanbackTheme_imageCardViewTitleStyle 112 * @attr ref android.support.v17.leanback.R.styleable#LeanbackTheme_imageCardViewContentStyle 113 * @attr ref android.support.v17.leanback.R.styleable#LeanbackTheme_imageCardViewBadgeStyle 114 * @attr ref android.support.v17.leanback.R.styleable#LeanbackTheme_imageCardViewImageStyle 115 * @attr ref android.support.v17.leanback.R.styleable#LeanbackTheme_imageCardViewInfoAreaStyle 116 */ 117public class ImageCardView extends BaseCardView { 118 119 public static final int CARD_TYPE_FLAG_IMAGE_ONLY = 0; 120 public static final int CARD_TYPE_FLAG_TITLE = 1; 121 public static final int CARD_TYPE_FLAG_CONTENT = 2; 122 public static final int CARD_TYPE_FLAG_ICON_RIGHT = 4; 123 public static final int CARD_TYPE_FLAG_ICON_LEFT = 8; 124 125 private ImageView mImageView; 126 private ViewGroup mInfoArea; 127 private TextView mTitleView; 128 private TextView mContentView; 129 private ImageView mBadgeImage; 130 private boolean mAttachedToWindow; 131 132 /** 133 * Create an ImageCardView using a given theme for customization. 134 * 135 * @param context 136 * The Context the view is running in, through which it can 137 * access the current theme, resources, etc. 138 * @param themeResId 139 * The resourceId of the theme you want to apply to the ImageCardView. The theme 140 * includes attributes "imageCardViewStyle", "imageCardViewTitleStyle", 141 * "imageCardViewContentStyle" etc. to customize individual part of ImageCardView. 142 * @deprecated Calling this constructor inefficiently creates one ContextThemeWrapper per card, 143 * you should share it in card Presenter: wrapper = new ContextThemeWrapper(context, themResId); 144 * return new ImageCardView(wrapper); 145 */ 146 @Deprecated 147 public ImageCardView(Context context, int themeResId) { 148 this(new ContextThemeWrapper(context, themeResId)); 149 } 150 151 /** 152 * @see #View(Context, AttributeSet, int) 153 */ 154 public ImageCardView(Context context, AttributeSet attrs, int defStyleAttr) { 155 super(context, attrs, defStyleAttr); 156 buildImageCardView(attrs, defStyleAttr, R.style.Widget_Leanback_ImageCardView); 157 } 158 159 private void buildImageCardView(AttributeSet attrs, int defStyleAttr, int defStyle) { 160 // Make sure the ImageCardView is focusable. 161 setFocusable(true); 162 setFocusableInTouchMode(true); 163 164 LayoutInflater inflater = LayoutInflater.from(getContext()); 165 inflater.inflate(R.layout.lb_image_card_view, this); 166 TypedArray cardAttrs = getContext().obtainStyledAttributes(attrs, 167 R.styleable.lbImageCardView, defStyleAttr, defStyle); 168 int cardType = cardAttrs 169 .getInt(R.styleable.lbImageCardView_lbImageCardViewType, CARD_TYPE_FLAG_IMAGE_ONLY); 170 171 boolean hasImageOnly = cardType == CARD_TYPE_FLAG_IMAGE_ONLY; 172 boolean hasTitle = (cardType & CARD_TYPE_FLAG_TITLE) == CARD_TYPE_FLAG_TITLE; 173 boolean hasContent = (cardType & CARD_TYPE_FLAG_CONTENT) == CARD_TYPE_FLAG_CONTENT; 174 boolean hasIconRight = (cardType & CARD_TYPE_FLAG_ICON_RIGHT) == CARD_TYPE_FLAG_ICON_RIGHT; 175 boolean hasIconLeft = 176 !hasIconRight && (cardType & CARD_TYPE_FLAG_ICON_LEFT) == CARD_TYPE_FLAG_ICON_LEFT; 177 178 mImageView = (ImageView) findViewById(R.id.main_image); 179 if (mImageView.getDrawable() == null) { 180 mImageView.setVisibility(View.INVISIBLE); 181 } 182 183 mInfoArea = (ViewGroup) findViewById(R.id.info_field); 184 if (hasImageOnly) { 185 removeView(mInfoArea); 186 cardAttrs.recycle(); 187 return; 188 } 189 // Create children 190 if (hasTitle) { 191 mTitleView = (TextView) inflater.inflate(R.layout.lb_image_card_view_themed_title, 192 mInfoArea, false); 193 mInfoArea.addView(mTitleView); 194 } 195 196 if (hasContent) { 197 mContentView = (TextView) inflater.inflate(R.layout.lb_image_card_view_themed_content, 198 mInfoArea, false); 199 mInfoArea.addView(mContentView); 200 } 201 202 if (hasIconRight || hasIconLeft) { 203 int layoutId = R.layout.lb_image_card_view_themed_badge_right; 204 if (hasIconLeft) { 205 layoutId = R.layout.lb_image_card_view_themed_badge_left; 206 } 207 mBadgeImage = (ImageView) inflater.inflate(layoutId, mInfoArea, false); 208 mInfoArea.addView(mBadgeImage); 209 } 210 211 // Set up LayoutParams for children 212 if (hasTitle && !hasContent && mBadgeImage != null) { 213 RelativeLayout.LayoutParams relativeLayoutParams = 214 (RelativeLayout.LayoutParams) mTitleView.getLayoutParams(); 215 // Adjust title TextView if there is an icon but no content 216 if (hasIconLeft) { 217 relativeLayoutParams.addRule(RelativeLayout.END_OF, mBadgeImage.getId()); 218 } else { 219 relativeLayoutParams.addRule(RelativeLayout.START_OF, mBadgeImage.getId()); 220 } 221 mTitleView.setLayoutParams(relativeLayoutParams); 222 } 223 224 // Set up LayoutParams for children 225 if (hasContent) { 226 RelativeLayout.LayoutParams relativeLayoutParams = 227 (RelativeLayout.LayoutParams) mContentView.getLayoutParams(); 228 if (!hasTitle) { 229 relativeLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP); 230 } 231 // Adjust content TextView if icon is on the left 232 if (hasIconLeft) { 233 relativeLayoutParams.removeRule(RelativeLayout.START_OF); 234 relativeLayoutParams.removeRule(RelativeLayout.ALIGN_PARENT_START); 235 relativeLayoutParams.addRule(RelativeLayout.END_OF, mBadgeImage.getId()); 236 } 237 mContentView.setLayoutParams(relativeLayoutParams); 238 } 239 240 if (mBadgeImage != null) { 241 RelativeLayout.LayoutParams relativeLayoutParams = 242 (RelativeLayout.LayoutParams) mBadgeImage.getLayoutParams(); 243 if (hasContent) { 244 relativeLayoutParams.addRule(RelativeLayout.ALIGN_BOTTOM, mContentView.getId()); 245 } else if (hasTitle) { 246 relativeLayoutParams.addRule(RelativeLayout.ALIGN_BOTTOM, mTitleView.getId()); 247 } 248 mBadgeImage.setLayoutParams(relativeLayoutParams); 249 } 250 251 // Backward compatibility: Newly created ImageCardViews should change 252 // the InfoArea's background color in XML using the corresponding style. 253 // However, since older implementations might make use of the 254 // 'infoAreaBackground' attribute, we have to make sure to support it. 255 // If the user has set a specific value here, it will differ from null. 256 // In this case, we do want to override the value set in the style. 257 Drawable background = cardAttrs.getDrawable(R.styleable.lbImageCardView_infoAreaBackground); 258 if (null != background) { 259 setInfoAreaBackground(background); 260 } 261 // Backward compatibility: There has to be an icon in the default 262 // version. If there is one, we have to set it's visibility to 'GONE'. 263 // Disabling 'adjustIconVisibility' allows the user to set the icon's 264 // visibility state in XML rather than code. 265 if (mBadgeImage != null && mBadgeImage.getDrawable() == null) { 266 mBadgeImage.setVisibility(View.GONE); 267 } 268 cardAttrs.recycle(); 269 } 270 271 /** 272 * @see #View(Context) 273 */ 274 public ImageCardView(Context context) { 275 this(context, null); 276 } 277 278 /** 279 * @see #View(Context, AttributeSet) 280 */ 281 public ImageCardView(Context context, AttributeSet attrs) { 282 this(context, attrs, R.attr.imageCardViewStyle); 283 } 284 285 /** 286 * Returns the main image view. 287 */ 288 public final ImageView getMainImageView() { 289 return mImageView; 290 } 291 292 /** 293 * Enables or disables adjustment of view bounds on the main image. 294 */ 295 public void setMainImageAdjustViewBounds(boolean adjustViewBounds) { 296 if (mImageView != null) { 297 mImageView.setAdjustViewBounds(adjustViewBounds); 298 } 299 } 300 301 /** 302 * Sets the ScaleType of the main image. 303 */ 304 public void setMainImageScaleType(ScaleType scaleType) { 305 if (mImageView != null) { 306 mImageView.setScaleType(scaleType); 307 } 308 } 309 310 /** 311 * Sets the image drawable with fade-in animation. 312 */ 313 public void setMainImage(Drawable drawable) { 314 setMainImage(drawable, true); 315 } 316 317 /** 318 * Sets the image drawable with optional fade-in animation. 319 */ 320 public void setMainImage(Drawable drawable, boolean fade) { 321 if (mImageView == null) { 322 return; 323 } 324 325 mImageView.setImageDrawable(drawable); 326 if (drawable == null) { 327 mImageView.animate().cancel(); 328 mImageView.setAlpha(1f); 329 mImageView.setVisibility(View.INVISIBLE); 330 } else { 331 mImageView.setVisibility(View.VISIBLE); 332 if (fade) { 333 fadeIn(); 334 } else { 335 mImageView.animate().cancel(); 336 mImageView.setAlpha(1f); 337 } 338 } 339 } 340 341 /** 342 * Sets the layout dimensions of the ImageView. 343 */ 344 public void setMainImageDimensions(int width, int height) { 345 ViewGroup.LayoutParams lp = mImageView.getLayoutParams(); 346 lp.width = width; 347 lp.height = height; 348 mImageView.setLayoutParams(lp); 349 } 350 351 /** 352 * Returns the ImageView drawable. 353 */ 354 public Drawable getMainImage() { 355 if (mImageView == null) { 356 return null; 357 } 358 359 return mImageView.getDrawable(); 360 } 361 362 /** 363 * Returns the info area background drawable. 364 */ 365 public Drawable getInfoAreaBackground() { 366 if (mInfoArea != null) { 367 return mInfoArea.getBackground(); 368 } 369 return null; 370 } 371 372 /** 373 * Sets the info area background drawable. 374 */ 375 public void setInfoAreaBackground(Drawable drawable) { 376 if (mInfoArea != null) { 377 mInfoArea.setBackground(drawable); 378 } 379 } 380 381 /** 382 * Sets the info area background color. 383 */ 384 public void setInfoAreaBackgroundColor(@ColorInt int color) { 385 if (mInfoArea != null) { 386 mInfoArea.setBackgroundColor(color); 387 } 388 } 389 390 /** 391 * Sets the title text. 392 */ 393 public void setTitleText(CharSequence text) { 394 if (mTitleView == null) { 395 return; 396 } 397 mTitleView.setText(text); 398 } 399 400 /** 401 * Returns the title text. 402 */ 403 public CharSequence getTitleText() { 404 if (mTitleView == null) { 405 return null; 406 } 407 408 return mTitleView.getText(); 409 } 410 411 /** 412 * Sets the content text. 413 */ 414 public void setContentText(CharSequence text) { 415 if (mContentView == null) { 416 return; 417 } 418 mContentView.setText(text); 419 } 420 421 /** 422 * Returns the content text. 423 */ 424 public CharSequence getContentText() { 425 if (mContentView == null) { 426 return null; 427 } 428 429 return mContentView.getText(); 430 } 431 432 /** 433 * Sets the badge image drawable. 434 */ 435 public void setBadgeImage(Drawable drawable) { 436 if (mBadgeImage == null) { 437 return; 438 } 439 mBadgeImage.setImageDrawable(drawable); 440 if (drawable != null) { 441 mBadgeImage.setVisibility(View.VISIBLE); 442 } else { 443 mBadgeImage.setVisibility(View.GONE); 444 } 445 } 446 447 /** 448 * Returns the badge image drawable. 449 */ 450 public Drawable getBadgeImage() { 451 if (mBadgeImage == null) { 452 return null; 453 } 454 455 return mBadgeImage.getDrawable(); 456 } 457 458 private void fadeIn() { 459 mImageView.setAlpha(0f); 460 if (mAttachedToWindow) { 461 mImageView.animate().alpha(1f).setDuration( 462 mImageView.getResources().getInteger(android.R.integer.config_shortAnimTime)); 463 } 464 } 465 466 @Override 467 public boolean hasOverlappingRendering() { 468 return false; 469 } 470 471 @Override 472 protected void onAttachedToWindow() { 473 super.onAttachedToWindow(); 474 mAttachedToWindow = true; 475 if (mImageView.getAlpha() == 0) { 476 fadeIn(); 477 } 478 } 479 480 @Override 481 protected void onDetachedFromWindow() { 482 mAttachedToWindow = false; 483 mImageView.animate().cancel(); 484 mImageView.setAlpha(1f); 485 super.onDetachedFromWindow(); 486 } 487 488} 489