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