• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  */
14 package android.support.v17.leanback.widget;
15 
16 import android.content.Context;
17 import android.content.res.TypedArray;
18 import android.graphics.drawable.Drawable;
19 import android.support.annotation.ColorInt;
20 import android.support.v17.leanback.R;
21 import android.util.AttributeSet;
22 import android.view.ContextThemeWrapper;
23 import android.view.LayoutInflater;
24 import android.view.View;
25 import android.view.ViewGroup;
26 import android.widget.ImageView;
27 import android.widget.ImageView.ScaleType;
28 import android.widget.RelativeLayout;
29 import 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  */
117 public 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
ImageCardView(Context context, int themeResId)147     public ImageCardView(Context context, int themeResId) {
148         this(new ContextThemeWrapper(context, themeResId));
149     }
150 
151     /**
152      * @see #View(Context, AttributeSet, int)
153      */
ImageCardView(Context context, AttributeSet attrs, int defStyleAttr)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 
buildImageCardView(AttributeSet attrs, int defStyleAttr, int defStyle)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      */
ImageCardView(Context context)274     public ImageCardView(Context context) {
275         this(context, null);
276     }
277 
278     /**
279      * @see #View(Context, AttributeSet)
280      */
ImageCardView(Context context, AttributeSet attrs)281     public ImageCardView(Context context, AttributeSet attrs) {
282         this(context, attrs, R.attr.imageCardViewStyle);
283     }
284 
285     /**
286      * Returns the main image view.
287      */
getMainImageView()288     public final ImageView getMainImageView() {
289         return mImageView;
290     }
291 
292     /**
293      * Enables or disables adjustment of view bounds on the main image.
294      */
setMainImageAdjustViewBounds(boolean adjustViewBounds)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      */
setMainImageScaleType(ScaleType scaleType)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      */
setMainImage(Drawable drawable)313     public void setMainImage(Drawable drawable) {
314         setMainImage(drawable, true);
315     }
316 
317     /**
318      * Sets the image drawable with optional fade-in animation.
319      */
setMainImage(Drawable drawable, boolean fade)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      */
setMainImageDimensions(int width, int height)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      */
getMainImage()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      */
getInfoAreaBackground()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      */
setInfoAreaBackground(Drawable drawable)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      */
setInfoAreaBackgroundColor(@olorInt int color)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      */
setTitleText(CharSequence text)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      */
getTitleText()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      */
setContentText(CharSequence text)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      */
getContentText()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      */
setBadgeImage(Drawable drawable)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      */
getBadgeImage()450     public Drawable getBadgeImage() {
451         if (mBadgeImage == null) {
452             return null;
453         }
454 
455         return mBadgeImage.getDrawable();
456     }
457 
fadeIn()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
hasOverlappingRendering()467     public boolean hasOverlappingRendering() {
468         return false;
469     }
470 
471     @Override
onAttachedToWindow()472     protected void onAttachedToWindow() {
473         super.onAttachedToWindow();
474         mAttachedToWindow = true;
475         if (mImageView.getAlpha() == 0) {
476             fadeIn();
477         }
478     }
479 
480     @Override
onDetachedFromWindow()481     protected void onDetachedFromWindow() {
482         mAttachedToWindow = false;
483         mImageView.animate().cancel();
484         mImageView.setAlpha(1f);
485         super.onDetachedFromWindow();
486     }
487 
488 }
489