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