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 = findViewById(R.id.main_image);
179        if (mImageView.getDrawable() == null) {
180            mImageView.setVisibility(View.INVISIBLE);
181        }
182
183        mInfoArea = 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 its 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