GuidanceStylist.java revision 50c611b216a4b2c8eb2bbd2a2848bb6da34677be
1/*
2 * Copyright (C) 2015 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.animation.Animator;
17import android.animation.AnimatorInflater;
18import android.content.Context;
19import android.content.res.TypedArray;
20import android.graphics.Paint;
21import android.graphics.Rect;
22import android.graphics.drawable.Drawable;
23import android.support.annotation.NonNull;
24import android.support.v17.leanback.R;
25import android.text.TextUtils;
26import android.util.DisplayMetrics;
27import android.util.TypedValue;
28import android.view.LayoutInflater;
29import android.view.View;
30import android.view.ViewGroup;
31import android.view.ViewTreeObserver;
32import android.view.WindowManager;
33import android.widget.ImageView;
34import android.widget.RelativeLayout;
35import android.widget.TextView;
36
37import java.util.List;
38
39/**
40 * GuidanceStylist is used within a {@link android.support.v17.leanback.app.GuidedStepFragment}
41 * to display contextual information for the decision(s) required at that step.
42 * <p>
43 * Many aspects of the base GuidanceStylist can be customized through theming; see the theme
44 * attributes below. Note that these attributes are not set on individual elements in layout
45 * XML, but instead would be set in a custom theme. See
46 * <a href="http://developer.android.com/guide/topics/ui/themes.html">Styles and Themes</a>
47 * for more information.
48 * <p>
49 * If these hooks are insufficient, this class may also be subclassed. Subclasses
50 * may wish to override the {@link #onProvideLayoutId} method to change the layout file used to
51 * display the guidance; more complex layouts may be supported by also providing a subclass of
52 * {@link GuidanceStylist.Guidance} with extra fields.
53 * <p>
54 * Note: If an alternate layout is provided, the following view IDs should be used to refer to base
55 * elements:
56 * <ul>
57 * <li>{@link android.support.v17.leanback.R.id#guidance_title}</li>
58 * <li>{@link android.support.v17.leanback.R.id#guidance_description}</li>
59 * <li>{@link android.support.v17.leanback.R.id#guidance_breadcrumb}</li>
60 * <li>{@link android.support.v17.leanback.R.id#guidance_icon}</li>
61 * </ul><p>
62 * View IDs are allowed to be missing, in which case the corresponding views will be null.
63 *
64 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepImeAppearingAnimation
65 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepImeDisappearingAnimation
66 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidanceContainerStyle
67 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidanceTitleStyle
68 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidanceDescriptionStyle
69 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidanceBreadcrumbStyle
70 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidanceIconStyle
71 * @see android.support.v17.leanback.app.GuidedStepFragment
72 * @see GuidanceStylist.Guidance
73 */
74public class GuidanceStylist implements FragmentAnimationProvider {
75
76    private View mGuidanceContainer;
77    private int mTitleKeylinePixels;
78    private float mTitleKeylinePercent;
79    private ViewTreeObserver.OnPreDrawListener mParentPreDrawListener
80            = new ViewTreeObserver.OnPreDrawListener() {
81
82        @Override
83        public boolean onPreDraw() {
84            mGuidanceContainer.getViewTreeObserver().removeOnPreDrawListener(this);
85            mTitleKeylinePixels = (int) (mGuidanceContainer.getHeight() * mTitleKeylinePercent/100);
86
87            if (mTitleView != null) {
88                Paint textPaint = mTitleView.getPaint();
89                int titleViewTextHeight = -textPaint.getFontMetricsInt().top;
90                int mBreadcrumbViewHeight = mBreadcrumbView.getHeight();
91                int guidanceTextContainerTop = mTitleKeylinePixels
92                        - titleViewTextHeight - mBreadcrumbViewHeight - mTitleView.getPaddingTop();
93                ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams)
94                        mBreadcrumbView.getLayoutParams();
95                lp.topMargin = guidanceTextContainerTop;
96                mBreadcrumbView.setLayoutParams(lp);
97            }
98
99            if (mIconView != null) {
100                Drawable drawable = mIconView.getDrawable();
101                if (drawable != null) {
102                    ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams)
103                            mIconView.getLayoutParams();
104                    lp.topMargin = (mTitleKeylinePixels - mIconView.getHeight() / 2);
105                    mIconView.setLayoutParams(lp);
106                }
107            }
108
109            return true;
110        }
111    };
112
113    /**
114     * A data class representing contextual information for a {@link
115     * android.support.v17.leanback.app.GuidedStepFragment}. Guidance consists of a short title,
116     * a longer description, a breadcrumb to help with global navigation (often indicating where
117     * the back button will lead), and an optional icon.  All this information is intended to
118     * provide users with the appropriate context to make the decision(s) required by the current
119     * step.
120     * <p>
121     * Clients may provide a subclass of this if they wish to remember auxiliary data for use in
122     * a customized GuidanceStylist.
123     */
124    public static class Guidance {
125        private final String mTitle;
126        private final String mDescription;
127        private final String mBreadcrumb;
128        private final Drawable mIconDrawable;
129
130        /**
131         * Constructs a Guidance object with the specified title, description, breadcrumb, and
132         * icon drawable.
133         * @param title The title for the current guided step.
134         * @param description The description for the current guided step.
135         * @param breadcrumb The breadcrumb for the current guided step.
136         * @param icon The icon drawable representing the current guided step.
137         */
138        public Guidance(String title, String description, String breadcrumb, Drawable icon) {
139            mBreadcrumb = breadcrumb;
140            mTitle = title;
141            mDescription = description;
142            mIconDrawable = icon;
143        }
144
145        /**
146         * Returns the title specified when this Guidance was constructed.
147         * @return The title for this Guidance.
148         */
149        public String getTitle() {
150            return mTitle;
151        }
152
153        /**
154         * Returns the description specified when this Guidance was constructed.
155         * @return The description for this Guidance.
156         */
157        public String getDescription() {
158            return mDescription;
159        }
160
161        /**
162         * Returns the breadcrumb specified when this Guidance was constructed.
163         * @return The breadcrumb for this Guidance.
164         */
165        public String getBreadcrumb() {
166            return mBreadcrumb;
167        }
168
169        /**
170         * Returns the icon drawable specified when this Guidance was constructed.
171         * @return The icon for this Guidance.
172         */
173        public Drawable getIconDrawable() {
174            return mIconDrawable;
175        }
176    }
177
178    private TextView mTitleView;
179    private TextView mDescriptionView;
180    private TextView mBreadcrumbView;
181    private ImageView mIconView;
182
183    /**
184     * Creates an appropriately configured view for the given Guidance, using the provided
185     * inflater and container.
186     * <p>
187     * <i>Note: Does not actually add the created view to the container; the caller should do
188     * this.</i>
189     * @param inflater The layout inflater to be used when constructing the view.
190     * @param container The view group to be passed in the call to
191     * <code>LayoutInflater.inflate</code>.
192     * @param guidance The guidance data for the view.
193     * @return The view to be added to the caller's view hierarchy.
194     */
195    public View onCreateView(final LayoutInflater inflater, ViewGroup container, Guidance guidance) {
196
197        TypedArray ta = inflater.getContext().getTheme().obtainStyledAttributes(
198                R.styleable.LeanbackGuidedStepTheme);
199
200        mTitleKeylinePercent = ta.getFloat(R.styleable.LeanbackGuidedStepTheme_guidedStepKeyline,
201                40);
202        ta.recycle();
203
204        View guidanceView = inflater.inflate(onProvideLayoutId(), container, false);
205        mTitleView = (TextView) guidanceView.findViewById(R.id.guidance_title);
206        mBreadcrumbView = (TextView) guidanceView.findViewById(R.id.guidance_breadcrumb);
207        mDescriptionView = (TextView) guidanceView.findViewById(R.id.guidance_description);
208        mIconView = (ImageView) guidanceView.findViewById(R.id.guidance_icon);
209        mGuidanceContainer = guidanceView.findViewById(R.id.guidance_container);
210
211        // We allow any of the cached subviews to be null, so that subclasses can choose not to
212        // display a particular piece of information.
213        if (mTitleView != null) {
214            mTitleView.setText(guidance.getTitle());
215        }
216
217        if (mBreadcrumbView != null) {
218            mBreadcrumbView.setText(guidance.getBreadcrumb());
219        }
220
221        if (mDescriptionView != null) {
222            mDescriptionView.setText(guidance.getDescription());
223        }
224
225        if (mIconView != null) {
226            if (guidance.getIconDrawable() != null) {
227                mIconView.setImageDrawable(guidance.getIconDrawable());
228            } else {
229                mIconView.setVisibility(View.GONE);
230            }
231        }
232
233        if (mGuidanceContainer != null) {
234            CharSequence contentDescription = mGuidanceContainer.getContentDescription();
235            if (TextUtils.isEmpty(contentDescription)) {
236                mGuidanceContainer.setContentDescription(new StringBuilder()
237                        .append(guidance.getBreadcrumb()).append('\n')
238                        .append(guidance.getTitle()).append('\n')
239                        .append(guidance.getDescription())
240                        .toString());
241            }
242
243            container.getViewTreeObserver().addOnPreDrawListener(mParentPreDrawListener);
244        }
245
246        return guidanceView;
247    }
248
249    /**
250     * Called when destroy the View created by GuidanceStylist.
251     */
252    public void onDestroyView() {
253        mBreadcrumbView = null;
254        mDescriptionView = null;
255        mIconView = null;
256        mTitleView = null;
257        mGuidanceContainer.getViewTreeObserver().removeOnPreDrawListener(mParentPreDrawListener);
258    }
259
260    /**
261     * Provides the resource ID of the layout defining the guidance view. Subclasses may override
262     * to provide their own customized layouts. The base implementation returns
263     * {@link android.support.v17.leanback.R.layout#lb_guidance}. If overridden, the substituted
264     * layout should contain matching IDs for any views that should be managed by the base class;
265     * this can be achieved by starting with a copy of the base layout file.
266     * @return The resource ID of the layout to be inflated to define the guidance view.
267     */
268    public int onProvideLayoutId() {
269        return R.layout.lb_guidance;
270    }
271
272    /**
273     * Returns the view displaying the title of the guidance.
274     * @return The text view object for the title.
275     */
276    public TextView getTitleView() {
277        return mTitleView;
278    }
279
280    /**
281     * Returns the view displaying the description of the guidance.
282     * @return The text view object for the description.
283     */
284    public TextView getDescriptionView() {
285        return mDescriptionView;
286    }
287
288    /**
289     * Returns the view displaying the breadcrumb of the guidance.
290     * @return The text view object for the breadcrumb.
291     */
292    public TextView getBreadcrumbView() {
293        return mBreadcrumbView;
294    }
295
296    /**
297     * Returns the view displaying the icon of the guidance.
298     * @return The image view object for the icon.
299     */
300    public ImageView getIconView() {
301        return mIconView;
302    }
303
304    /**
305     * {@inheritDoc}
306     */
307    @Override
308    public void onImeAppearing(@NonNull List<Animator> animators) {
309        addAnimator(animators, mTitleView, R.attr.guidedStepImeAppearingAnimation);
310        addAnimator(animators, mBreadcrumbView, R.attr.guidedStepImeAppearingAnimation);
311        addAnimator(animators, mDescriptionView, R.attr.guidedStepImeAppearingAnimation);
312        addAnimator(animators, mIconView, R.attr.guidedStepImeAppearingAnimation);
313    }
314
315    /**
316     * {@inheritDoc}
317     */
318    @Override
319    public void onImeDisappearing(@NonNull List<Animator> animators) {
320        addAnimator(animators, mTitleView, R.attr.guidedStepImeDisappearingAnimation);
321        addAnimator(animators, mBreadcrumbView, R.attr.guidedStepImeDisappearingAnimation);
322        addAnimator(animators, mDescriptionView, R.attr.guidedStepImeDisappearingAnimation);
323        addAnimator(animators, mIconView, R.attr.guidedStepImeDisappearingAnimation);
324    }
325
326    private void addAnimator(List<Animator> animators, View v, int attrId) {
327        if (v != null) {
328            Context ctx = v.getContext();
329            TypedValue typedValue = new TypedValue();
330            ctx.getTheme().resolveAttribute(attrId, typedValue, true);
331            Animator animator = AnimatorInflater.loadAnimator(ctx, typedValue.resourceId);
332            animator.setTarget(v);
333            animators.add(animator);
334        }
335    }
336}
337