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