GuidedActionsStylist.java revision 4158705d3f0751d419a08c47a659abeae5f6c196
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.animation.AnimatorListenerAdapter; 19import android.animation.AnimatorSet; 20import android.animation.ObjectAnimator; 21import android.content.Context; 22import android.content.pm.PackageManager; 23import android.content.res.Resources; 24import android.content.res.TypedArray; 25import android.graphics.drawable.Drawable; 26import android.net.Uri; 27import android.support.annotation.NonNull; 28import android.support.v17.leanback.R; 29import android.support.v17.leanback.widget.VerticalGridView; 30import android.support.v7.widget.RecyclerView; 31import android.support.v7.widget.RecyclerView.ViewHolder; 32import android.text.TextUtils; 33import android.util.Log; 34import android.util.TypedValue; 35import android.view.animation.DecelerateInterpolator; 36import android.view.LayoutInflater; 37import android.view.View; 38import android.view.ViewGroup; 39import android.view.ViewGroup.LayoutParams; 40import android.view.ViewPropertyAnimator; 41import android.view.ViewTreeObserver; 42import android.view.WindowManager; 43import android.widget.ImageView; 44import android.widget.TextView; 45 46import java.util.List; 47 48/** 49 * GuidedActionsStylist is used within a {@link android.support.v17.leanback.app.GuidedStepFragment} 50 * to supply the right-side panel where users can take actions. It consists of a container for the 51 * list of actions, and a stationary selector view that indicates visually the location of focus. 52 * <p> 53 * Many aspects of the base GuidedActionsStylist can be customized through theming; see the 54 * theme attributes below. Note that these attributes are not set on individual elements in layout 55 * XML, but instead would be set in a custom theme. See 56 * <a href="http://developer.android.com/guide/topics/ui/themes.html">Styles and Themes</a> 57 * for more information. 58 * <p> 59 * If these hooks are insufficient, this class may also be subclassed. Subclasses may wish to 60 * override the {@link #onProvideLayoutId} method to change the layout used to display the 61 * list container and selector, or the {@link #onProvideItemLayoutId} method to change the layout 62 * used to display each action. 63 * <p> 64 * Note: If an alternate list layout is provided, the following view IDs must be supplied: 65 * <ul> 66 * <li>{@link android.support.v17.leanback.R.id#guidedactions_selector}</li> 67 * <li>{@link android.support.v17.leanback.R.id#guidedactions_list}</li> 68 * </ul><p> 69 * These view IDs must be present in order for the stylist to function. The list ID must correspond 70 * to a {@link VerticalGridView} or subclass. 71 * <p> 72 * If an alternate item layout is provided, the following view IDs should be used to refer to base 73 * elements: 74 * <ul> 75 * <li>{@link android.support.v17.leanback.R.id#guidedactions_item_content}</li> 76 * <li>{@link android.support.v17.leanback.R.id#guidedactions_item_title}</li> 77 * <li>{@link android.support.v17.leanback.R.id#guidedactions_item_description}</li> 78 * <li>{@link android.support.v17.leanback.R.id#guidedactions_item_icon}</li> 79 * <li>{@link android.support.v17.leanback.R.id#guidedactions_item_checkmark}</li> 80 * <li>{@link android.support.v17.leanback.R.id#guidedactions_item_chevron}</li> 81 * </ul><p> 82 * These view IDs are allowed to be missing, in which case the corresponding views in {@link 83 * GuidedActionsStylist.ViewHolder} will be null. 84 * <p> 85 * In order to support editable actions, the view associated with guidedactions_item_title should 86 * be a subclass of {@link android.widget.EditText}, and should satisfy the {@link 87 * ImeKeyMonitor} interface. 88 * 89 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepEntryAnimation 90 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepExitAnimation 91 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepReentryAnimation 92 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepReturnAnimation 93 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepImeAppearingAnimation 94 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepImeDisappearingAnimation 95 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsEntryAnimation 96 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsSelectorShowAnimation 97 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsSelectorHideAnimation 98 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsContainerStyle 99 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsSelectorStyle 100 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsListStyle 101 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionItemContainerStyle 102 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionItemCheckmarkStyle 103 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionItemIconStyle 104 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionItemContentStyle 105 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionItemTitleStyle 106 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionItemDescriptionStyle 107 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionItemChevronStyle 108 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionCheckedAnimation 109 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionUncheckedAnimation 110 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionPressedAnimation 111 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionUnpressedAnimation 112 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionEnabledChevronAlpha 113 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionDisabledChevronAlpha 114 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionContentWidth 115 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionContentWidthNoIcon 116 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionTitleMinLines 117 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionTitleMaxLines 118 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionDescriptionMinLines 119 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionVerticalPadding 120 * @see android.support.v17.leanback.app.GuidedStepFragment 121 * @see GuidedAction 122 */ 123public class GuidedActionsStylist implements FragmentAnimationProvider { 124 125 /** 126 * ViewHolder caches information about the action item layouts' subviews. Subclasses of {@link 127 * GuidedActionsStylist} may also wish to subclass this in order to add fields. 128 * @see GuidedAction 129 */ 130 public static class ViewHolder { 131 132 public final View view; 133 134 private View mContentView; 135 private TextView mTitleView; 136 private TextView mDescriptionView; 137 private ImageView mIconView; 138 private ImageView mCheckmarkView; 139 private ImageView mChevronView; 140 141 /** 142 * Constructs an ViewHolder and caches the relevant subviews. 143 */ 144 public ViewHolder(View v) { 145 view = v; 146 147 mContentView = v.findViewById(R.id.guidedactions_item_content); 148 mTitleView = (TextView) v.findViewById(R.id.guidedactions_item_title); 149 mDescriptionView = (TextView) v.findViewById(R.id.guidedactions_item_description); 150 mIconView = (ImageView) v.findViewById(R.id.guidedactions_item_icon); 151 mCheckmarkView = (ImageView) v.findViewById(R.id.guidedactions_item_checkmark); 152 mChevronView = (ImageView) v.findViewById(R.id.guidedactions_item_chevron); 153 } 154 155 /** 156 * Returns the content view within this view holder's view, where title and description are 157 * shown. 158 */ 159 public View getContentView() { 160 return mContentView; 161 } 162 163 /** 164 * Returns the title view within this view holder's view. 165 */ 166 public TextView getTitleView() { 167 return mTitleView; 168 } 169 170 /** 171 * Returns the description view within this view holder's view. 172 */ 173 public TextView getDescriptionView() { 174 return mDescriptionView; 175 } 176 177 /** 178 * Returns the icon view within this view holder's view. 179 */ 180 public ImageView getIconView() { 181 return mIconView; 182 } 183 184 /** 185 * Returns the checkmark view within this view holder's view. 186 */ 187 public ImageView getCheckmarkView() { 188 return mCheckmarkView; 189 } 190 191 /** 192 * Returns the chevron view within this view holder's view. 193 */ 194 public ImageView getChevronView() { 195 return mChevronView; 196 } 197 198 } 199 200 private static String TAG = "GuidedActionsStylist"; 201 202 protected View mMainView; 203 protected VerticalGridView mActionsGridView; 204 protected View mSelectorView; 205 206 // Cached values from resources 207 private float mEnabledChevronAlpha; 208 private float mDisabledChevronAlpha; 209 private int mContentWidth; 210 private int mContentWidthNoIcon; 211 private int mTitleMinLines; 212 private int mTitleMaxLines; 213 private int mDescriptionMinLines; 214 private int mVerticalPadding; 215 private int mDisplayHeight; 216 217 /** 218 * Creates a view appropriate for displaying a list of GuidedActions, using the provided 219 * inflater and container. 220 * <p> 221 * <i>Note: Does not actually add the created view to the container; the caller should do 222 * this.</i> 223 * @param inflater The layout inflater to be used when constructing the view. 224 * @param container The view group to be passed in the call to 225 * <code>LayoutInflater.inflate</code>. 226 * @return The view to be added to the caller's view hierarchy. 227 */ 228 public View onCreateView(LayoutInflater inflater, ViewGroup container) { 229 mMainView = inflater.inflate(onProvideLayoutId(), container, false); 230 mSelectorView = mMainView.findViewById(R.id.guidedactions_selector); 231 if (mMainView instanceof VerticalGridView) { 232 mActionsGridView = (VerticalGridView) mMainView; 233 } else { 234 mActionsGridView = (VerticalGridView) mMainView.findViewById(R.id.guidedactions_list); 235 if (mActionsGridView == null) { 236 throw new IllegalStateException("No ListView exists."); 237 } 238 mActionsGridView.setWindowAlignmentOffset(0); 239 mActionsGridView.setWindowAlignmentOffsetPercent(50f); 240 mActionsGridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE); 241 if (mSelectorView != null) { 242 mActionsGridView.setOnScrollListener(new 243 SelectorAnimator(mSelectorView, mActionsGridView)); 244 } 245 } 246 247 mActionsGridView.requestFocusFromTouch(); 248 249 if (mSelectorView != null) { 250 // ALlow focus to move to other views 251 mActionsGridView.getViewTreeObserver().addOnGlobalFocusChangeListener( 252 new ViewTreeObserver.OnGlobalFocusChangeListener() { 253 private boolean mChildFocused; 254 255 @Override 256 public void onGlobalFocusChanged(View oldFocus, View newFocus) { 257 View focusedChild = mActionsGridView.getFocusedChild(); 258 if (focusedChild == null) { 259 mSelectorView.setVisibility(View.INVISIBLE); 260 mChildFocused = false; 261 } else if (!mChildFocused) { 262 mChildFocused = true; 263 mSelectorView.setVisibility(View.VISIBLE); 264 updateSelectorView(focusedChild); 265 } 266 } 267 }); 268 } 269 270 // Cache widths, chevron alpha values, max and min text lines, etc 271 Context ctx = mMainView.getContext(); 272 TypedValue val = new TypedValue(); 273 mEnabledChevronAlpha = getFloat(ctx, val, R.attr.guidedActionEnabledChevronAlpha); 274 mDisabledChevronAlpha = getFloat(ctx, val, R.attr.guidedActionDisabledChevronAlpha); 275 mContentWidth = getDimension(ctx, val, R.attr.guidedActionContentWidth); 276 mContentWidthNoIcon = getDimension(ctx, val, R.attr.guidedActionContentWidthNoIcon); 277 mTitleMinLines = getInteger(ctx, val, R.attr.guidedActionTitleMinLines); 278 mTitleMaxLines = getInteger(ctx, val, R.attr.guidedActionTitleMaxLines); 279 mDescriptionMinLines = getInteger(ctx, val, R.attr.guidedActionDescriptionMinLines); 280 mVerticalPadding = getDimension(ctx, val, R.attr.guidedActionVerticalPadding); 281 mDisplayHeight = ((WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE)) 282 .getDefaultDisplay().getHeight(); 283 284 return mMainView; 285 } 286 287 /** 288 * Returns the VerticalGridView that displays the list of GuidedActions. 289 * @return The VerticalGridView for this presenter. 290 */ 291 public VerticalGridView getActionsGridView() { 292 return mActionsGridView; 293 } 294 295 /** 296 * Provides the resource ID of the layout defining the host view for the list of guided actions. 297 * Subclasses may override to provide their own customized layouts. The base implementation 298 * returns {@link android.support.v17.leanback.R.layout#lb_guidedactions}. If overridden, the 299 * substituted layout should contain matching IDs for any views that should be managed by the 300 * base class; this can be achieved by starting with a copy of the base layout file. 301 * @return The resource ID of the layout to be inflated to define the host view for the list 302 * of GuidedActions. 303 */ 304 public int onProvideLayoutId() { 305 return R.layout.lb_guidedactions; 306 } 307 308 /** 309 * Provides the resource ID of the layout defining the view for an individual guided actions. 310 * Subclasses may override to provide their own customized layouts. The base implementation 311 * returns {@link android.support.v17.leanback.R.layout#lb_guidedactions_item}. If overridden, 312 * the substituted layout should contain matching IDs for any views that should be managed by 313 * the base class; this can be achieved by starting with a copy of the base layout file. 314 * @return The resource ID of the layout to be inflated to define the view to display an 315 * individual GuidedAction. 316 */ 317 public int onProvideItemLayoutId() { 318 return R.layout.lb_guidedactions_item; 319 } 320 321 /** 322 * Constructs a {@link ViewHolder} capable of representing {@link GuidedAction}s. Subclasses 323 * may choose to return a subclass of ViewHolder. 324 * <p> 325 * <i>Note: Should not actually add the created view to the parent; the caller will do 326 * this.</i> 327 * @param parent The view group to be used as the parent of the new view. 328 * @return The view to be added to the caller's view hierarchy. 329 */ 330 public ViewHolder onCreateViewHolder(ViewGroup parent) { 331 LayoutInflater inflater = LayoutInflater.from(parent.getContext()); 332 View v = inflater.inflate(onProvideItemLayoutId(), parent, false); 333 return new ViewHolder(v); 334 } 335 336 /** 337 * Binds a {@link ViewHolder} to a particular {@link GuidedAction}. 338 * @param vh The view holder to be associated with the given action. 339 * @param action The guided action to be displayed by the view holder's view. 340 * @return The view to be added to the caller's view hierarchy. 341 */ 342 public void onBindViewHolder(ViewHolder vh, GuidedAction action) { 343 344 if (vh.mTitleView != null) { 345 vh.mTitleView.setText(action.getTitle()); 346 } 347 if (vh.mDescriptionView != null) { 348 vh.mDescriptionView.setText(action.getDescription()); 349 vh.mDescriptionView.setVisibility(TextUtils.isEmpty(action.getDescription()) ? 350 View.GONE : View.VISIBLE); 351 } 352 // Clients might want the check mark view to be gone entirely, in which case, ignore it. 353 if (vh.mCheckmarkView != null && vh.mCheckmarkView.getVisibility() != View.GONE) { 354 vh.mCheckmarkView.setVisibility(action.isChecked() ? View.VISIBLE : View.INVISIBLE); 355 } 356 357 if (vh.mContentView != null) { 358 ViewGroup.LayoutParams contentLp = vh.mContentView.getLayoutParams(); 359 if (setIcon(vh.mIconView, action)) { 360 contentLp.width = mContentWidth; 361 } else { 362 contentLp.width = mContentWidthNoIcon; 363 } 364 vh.mContentView.setLayoutParams(contentLp); 365 } 366 367 if (vh.mChevronView != null) { 368 vh.mChevronView.setVisibility(action.hasNext() ? View.VISIBLE : View.INVISIBLE); 369 vh.mChevronView.setAlpha(action.isEnabled() ? mEnabledChevronAlpha : 370 mDisabledChevronAlpha); 371 } 372 373 if (action.hasMultilineDescription()) { 374 if (vh.mTitleView != null) { 375 vh.mTitleView.setMaxLines(mTitleMaxLines); 376 if (vh.mDescriptionView != null) { 377 vh.mDescriptionView.setMaxHeight(getDescriptionMaxHeight(vh.view.getContext(), 378 vh.mTitleView)); 379 } 380 } 381 } else { 382 if (vh.mTitleView != null) { 383 vh.mTitleView.setMaxLines(mTitleMinLines); 384 } 385 if (vh.mDescriptionView != null) { 386 vh.mDescriptionView.setMaxLines(mDescriptionMinLines); 387 } 388 } 389 } 390 391 /** 392 * Animates the view holder's view (or subviews thereof) when the action has had its focus 393 * state changed. 394 * @param vh The view holder associated with the relevant action. 395 * @param focused True if the action has become focused, false if it has lost focus. 396 */ 397 public void onAnimateItemFocused(ViewHolder vh, boolean focused) { 398 // No animations for this, currently, because the animation is done on 399 // mSelectorView 400 } 401 402 /** 403 * Animates the view holder's view (or subviews thereof) when the action has had its press 404 * state changed. 405 * @param vh The view holder associated with the relevant action. 406 * @param pressed True if the action has been pressed, false if it has been unpressed. 407 */ 408 public void onAnimateItemPressed(ViewHolder vh, boolean pressed) { 409 int attr = pressed ? R.attr.guidedActionPressedAnimation : 410 R.attr.guidedActionUnpressedAnimation; 411 createAnimator(vh.view, attr).start(); 412 } 413 414 /** 415 * Animates the view holder's view (or subviews thereof) when the action has had its check 416 * state changed. 417 * @param vh The view holder associated with the relevant action. 418 * @param checked True if the action has become checked, false if it has become unchecked. 419 */ 420 public void onAnimateItemChecked(ViewHolder vh, boolean checked) { 421 final View checkView = vh.mCheckmarkView; 422 if (checkView != null) { 423 if (checked) { 424 checkView.setVisibility(View.VISIBLE); 425 createAnimator(checkView, R.attr.guidedActionCheckedAnimation).start(); 426 } else { 427 Animator animator = createAnimator(checkView, 428 R.attr.guidedActionUncheckedAnimation); 429 animator.addListener(new AnimatorListenerAdapter() { 430 @Override 431 public void onAnimationEnd(Animator animation) { 432 checkView.setVisibility(View.INVISIBLE); 433 } 434 }); 435 animator.start(); 436 } 437 } 438 } 439 440 /* 441 * ========================================== 442 * FragmentAnimationProvider overrides 443 * ========================================== 444 */ 445 446 /** 447 * {@inheritDoc} 448 */ 449 @Override 450 public void onActivityEnter(@NonNull List<Animator> animators) { 451 animators.add(createAnimator(mMainView, R.attr.guidedActionsEntryAnimation)); 452 } 453 454 /** 455 * {@inheritDoc} 456 */ 457 @Override 458 public void onActivityExit(@NonNull List<Animator> animators) {} 459 460 /** 461 * {@inheritDoc} 462 */ 463 @Override 464 public void onFragmentEnter(@NonNull List<Animator> animators) { 465 animators.add(createAnimator(mActionsGridView, R.attr.guidedStepEntryAnimation)); 466 animators.add(createAnimator(mSelectorView, R.attr.guidedStepEntryAnimation)); 467 } 468 469 /** 470 * {@inheritDoc} 471 */ 472 @Override 473 public void onFragmentExit(@NonNull List<Animator> animators) { 474 animators.add(createAnimator(mActionsGridView, R.attr.guidedStepExitAnimation)); 475 animators.add(createAnimator(mSelectorView, R.attr.guidedStepExitAnimation)); 476 } 477 478 /** 479 * {@inheritDoc} 480 */ 481 @Override 482 public void onFragmentReenter(@NonNull List<Animator> animators) { 483 animators.add(createAnimator(mActionsGridView, R.attr.guidedStepReentryAnimation)); 484 animators.add(createAnimator(mSelectorView, R.attr.guidedStepReentryAnimation)); 485 } 486 487 /** 488 * {@inheritDoc} 489 */ 490 @Override 491 public void onFragmentReturn(@NonNull List<Animator> animators) { 492 animators.add(createAnimator(mActionsGridView, R.attr.guidedStepReturnAnimation)); 493 animators.add(createAnimator(mSelectorView, R.attr.guidedStepReturnAnimation)); 494 } 495 496 /** 497 * {@inheritDoc} 498 */ 499 @Override 500 public void onImeAppearing(@NonNull List<Animator> animators) { 501 animators.add(createAnimator(mActionsGridView, R.attr.guidedStepImeAppearingAnimation)); 502 animators.add(createAnimator(mSelectorView, R.attr.guidedStepImeAppearingAnimation)); 503 } 504 505 /** 506 * {@inheritDoc} 507 */ 508 @Override 509 public void onImeDisappearing(@NonNull List<Animator> animators) { 510 animators.add(createAnimator(mActionsGridView, R.attr.guidedStepImeDisappearingAnimation)); 511 animators.add(createAnimator(mSelectorView, R.attr.guidedStepImeDisappearingAnimation)); 512 } 513 514 /* 515 * ========================================== 516 * Private methods 517 * ========================================== 518 */ 519 520 private void updateSelectorView(View focusedChild) { 521 // Display the selector view. 522 int height = focusedChild.getHeight(); 523 LayoutParams lp = mSelectorView.getLayoutParams(); 524 lp.height = height; 525 mSelectorView.setLayoutParams(lp); 526 mSelectorView.setAlpha(1f); 527 } 528 529 private float getFloat(Context ctx, TypedValue typedValue, int attrId) { 530 ctx.getTheme().resolveAttribute(attrId, typedValue, true); 531 // Android resources don't have a native float type, so we have to use strings. 532 return Float.valueOf(ctx.getResources().getString(typedValue.resourceId)); 533 } 534 535 private int getInteger(Context ctx, TypedValue typedValue, int attrId) { 536 ctx.getTheme().resolveAttribute(attrId, typedValue, true); 537 return ctx.getResources().getInteger(typedValue.resourceId); 538 } 539 540 private int getDimension(Context ctx, TypedValue typedValue, int attrId) { 541 ctx.getTheme().resolveAttribute(attrId, typedValue, true); 542 return ctx.getResources().getDimensionPixelSize(typedValue.resourceId); 543 } 544 545 private static Animator createAnimator(View v, int attrId) { 546 Context ctx = v.getContext(); 547 TypedValue typedValue = new TypedValue(); 548 ctx.getTheme().resolveAttribute(attrId, typedValue, true); 549 Animator animator = AnimatorInflater.loadAnimator(ctx, typedValue.resourceId); 550 animator.setTarget(v); 551 return animator; 552 } 553 554 private boolean setIcon(final ImageView iconView, GuidedAction action) { 555 Drawable icon = null; 556 if (iconView != null) { 557 Context context = iconView.getContext(); 558 icon = action.getIcon(); 559 if (icon != null) { 560 // setImageDrawable resets the drawable's level unless we set the view level first. 561 iconView.setImageLevel(icon.getLevel()); 562 iconView.setImageDrawable(icon); 563 iconView.setVisibility(View.VISIBLE); 564 } else { 565 iconView.setVisibility(View.GONE); 566 } 567 } 568 return icon != null; 569 } 570 571 /** 572 * @return the max height in pixels the description can be such that the 573 * action nicely takes up the entire screen. 574 */ 575 private int getDescriptionMaxHeight(Context context, TextView title) { 576 // The 2 multiplier on the title height calculation is a 577 // conservative estimate for font padding which can not be 578 // calculated at this stage since the view hasn't been rendered yet. 579 return (int)(mDisplayHeight - 2*mVerticalPadding - 2*mTitleMaxLines*title.getLineHeight()); 580 } 581 582 /** 583 * SelectorAnimator 584 * Controls animation for selected item backgrounds 585 * TODO: Move into focus animation override? 586 */ 587 private static class SelectorAnimator extends RecyclerView.OnScrollListener { 588 589 private final View mSelectorView; 590 private final ViewGroup mParentView; 591 private volatile boolean mFadedOut = true; 592 593 SelectorAnimator(View selectorView, ViewGroup parentView) { 594 mSelectorView = selectorView; 595 mParentView = parentView; 596 } 597 598 // We want to fade in the selector if we've stopped scrolling on it. If 599 // we're scrolling, we want to ensure to dim the selector if we haven't 600 // already. We dim the last highlighted view so that while a user is 601 // scrolling, nothing is highlighted. 602 @Override 603 public void onScrollStateChanged(RecyclerView recyclerView, int newState) { 604 Animator animator = null; 605 boolean fadingOut = false; 606 if (newState == RecyclerView.SCROLL_STATE_IDLE) { 607 // The selector starts with a height of 0. In order to scale up from 608 // 0, we first need the set the height to 1 and scale from there. 609 View focusedChild = mParentView.getFocusedChild(); 610 if (focusedChild != null) { 611 int selectorHeight = mSelectorView.getHeight(); 612 float scaleY = (float) focusedChild.getHeight() / selectorHeight; 613 AnimatorSet animators = (AnimatorSet)createAnimator(mSelectorView, 614 R.attr.guidedActionsSelectorShowAnimation); 615 if (mFadedOut) { 616 // selector is completely faded out, so we can just scale before fading in. 617 mSelectorView.setScaleY(scaleY); 618 animator = animators.getChildAnimations().get(0); 619 } else { 620 // selector is not faded out, so we must animate the scale as we fade in. 621 ((ObjectAnimator)animators.getChildAnimations().get(1)) 622 .setFloatValues(scaleY); 623 animator = animators; 624 } 625 } 626 } else { 627 animator = createAnimator(mSelectorView, R.attr.guidedActionsSelectorHideAnimation); 628 fadingOut = true; 629 } 630 if (animator != null) { 631 animator.addListener(new Listener(fadingOut)); 632 animator.start(); 633 } 634 } 635 636 /** 637 * Sets {@link BaseScrollAdapterFragment#mFadedOut} 638 * {@link BaseScrollAdapterFragment#mFadedOut} is true, iff 639 * {@link BaseScrollAdapterFragment#mSelectorView} has an alpha of 0 640 * (faded out). If false the view either has an alpha of 1 (visible) or 641 * is in the process of animating. 642 */ 643 private class Listener implements Animator.AnimatorListener { 644 private boolean mFadingOut; 645 private boolean mCanceled; 646 647 public Listener(boolean fadingOut) { 648 mFadingOut = fadingOut; 649 } 650 651 @Override 652 public void onAnimationStart(Animator animation) { 653 if (!mFadingOut) { 654 mFadedOut = false; 655 } 656 } 657 658 @Override 659 public void onAnimationEnd(Animator animation) { 660 if (!mCanceled && mFadingOut) { 661 mFadedOut = true; 662 } 663 } 664 665 @Override 666 public void onAnimationCancel(Animator animation) { 667 mCanceled = true; 668 } 669 670 @Override 671 public void onAnimationRepeat(Animator animation) { 672 } 673 } 674 } 675 676} 677