GuidedActionsStylist.java revision 1ed9dc77616514e20c51baa67a04adab42e4135e
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.Rect; 26import android.graphics.drawable.Drawable; 27import android.net.Uri; 28import android.os.Build.VERSION; 29import android.support.annotation.NonNull; 30import android.support.v17.leanback.R; 31import android.support.v17.leanback.transition.TransitionHelper; 32import android.support.v17.leanback.transition.TransitionListener; 33import android.support.v17.leanback.widget.VerticalGridView; 34import android.support.v4.content.ContextCompat; 35import android.support.v4.view.ViewCompat; 36import android.support.v7.widget.RecyclerView; 37import android.support.v7.widget.RecyclerView.ViewHolder; 38import android.text.TextUtils; 39import android.util.Log; 40import android.util.TypedValue; 41import android.view.animation.DecelerateInterpolator; 42import android.view.inputmethod.EditorInfo; 43import android.view.Gravity; 44import android.view.LayoutInflater; 45import android.view.View; 46import android.view.ViewGroup; 47import android.view.ViewGroup.LayoutParams; 48import android.view.ViewPropertyAnimator; 49import android.view.ViewTreeObserver; 50import android.view.WindowManager; 51import android.widget.Checkable; 52import android.widget.EditText; 53import android.widget.ImageView; 54import android.widget.TextView; 55 56import java.util.Collections; 57import java.util.List; 58 59/** 60 * GuidedActionsStylist is used within a {@link android.support.v17.leanback.app.GuidedStepFragment} 61 * to supply the right-side panel where users can take actions. It consists of a container for the 62 * list of actions, and a stationary selector view that indicates visually the location of focus. 63 * GuidedActionsStylist has two different layouts: default is for normal actions including text, 64 * radio, checkbox etc, the other when {@link #setAsButtonActions()} is called is recommended for 65 * button actions such as "yes", "no". 66 * <p> 67 * Many aspects of the base GuidedActionsStylist can be customized through theming; see the 68 * theme attributes below. Note that these attributes are not set on individual elements in layout 69 * XML, but instead would be set in a custom theme. See 70 * <a href="http://developer.android.com/guide/topics/ui/themes.html">Styles and Themes</a> 71 * for more information. 72 * <p> 73 * If these hooks are insufficient, this class may also be subclassed. Subclasses may wish to 74 * override the {@link #onProvideLayoutId} method to change the layout used to display the 75 * list container and selector, or the {@link #onProvideItemLayoutId} method to change the layout 76 * used to display each action. 77 * <p> 78 * Note: If an alternate list layout is provided, the following view IDs must be supplied: 79 * <ul> 80 * <li>{@link android.support.v17.leanback.R.id#guidedactions_selector}</li> 81 * <li>{@link android.support.v17.leanback.R.id#guidedactions_list}</li> 82 * </ul><p> 83 * These view IDs must be present in order for the stylist to function. The list ID must correspond 84 * to a {@link VerticalGridView} or subclass. 85 * <p> 86 * If an alternate item layout is provided, the following view IDs should be used to refer to base 87 * elements: 88 * <ul> 89 * <li>{@link android.support.v17.leanback.R.id#guidedactions_item_content}</li> 90 * <li>{@link android.support.v17.leanback.R.id#guidedactions_item_title}</li> 91 * <li>{@link android.support.v17.leanback.R.id#guidedactions_item_description}</li> 92 * <li>{@link android.support.v17.leanback.R.id#guidedactions_item_icon}</li> 93 * <li>{@link android.support.v17.leanback.R.id#guidedactions_item_checkmark}</li> 94 * <li>{@link android.support.v17.leanback.R.id#guidedactions_item_chevron}</li> 95 * </ul><p> 96 * These view IDs are allowed to be missing, in which case the corresponding views in {@link 97 * GuidedActionsStylist.ViewHolder} will be null. 98 * <p> 99 * In order to support editable actions, the view associated with guidedactions_item_title should 100 * be a subclass of {@link android.widget.EditText}, and should satisfy the {@link 101 * ImeKeyMonitor} interface. 102 * 103 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepImeAppearingAnimation 104 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepImeDisappearingAnimation 105 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsSelectorShowAnimation 106 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsSelectorHideAnimation 107 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsSelectorStyle 108 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsListStyle 109 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedSubActionsListStyle 110 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedButtonActionsListStyle 111 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionItemContainerStyle 112 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionItemCheckmarkStyle 113 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionItemIconStyle 114 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionItemContentStyle 115 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionItemTitleStyle 116 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionItemDescriptionStyle 117 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionItemChevronStyle 118 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionPressedAnimation 119 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionUnpressedAnimation 120 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionEnabledChevronAlpha 121 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionDisabledChevronAlpha 122 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionTitleMinLines 123 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionTitleMaxLines 124 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionDescriptionMinLines 125 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionVerticalPadding 126 * @see android.R.styleable#Theme_listChoiceIndicatorSingle 127 * @see android.R.styleable#Theme_listChoiceIndicatorMultiple 128 * @see android.support.v17.leanback.app.GuidedStepFragment 129 * @see GuidedAction 130 */ 131public class GuidedActionsStylist implements FragmentAnimationProvider { 132 133 /** 134 * Default viewType that associated with default layout Id for the action item. 135 * @see #getItemViewType(GuidedAction) 136 * @see #onProvideItemLayoutId(int) 137 * @see #onCreateViewHolder(ViewGroup, int) 138 */ 139 public static final int VIEW_TYPE_DEFAULT = 0; 140 141 /** 142 * ViewHolder caches information about the action item layouts' subviews. Subclasses of {@link 143 * GuidedActionsStylist} may also wish to subclass this in order to add fields. 144 * @see GuidedAction 145 */ 146 public static class ViewHolder extends RecyclerView.ViewHolder { 147 148 private GuidedAction mAction; 149 private View mContentView; 150 private TextView mTitleView; 151 private TextView mDescriptionView; 152 private ImageView mIconView; 153 private ImageView mCheckmarkView; 154 private ImageView mChevronView; 155 private boolean mInEditing; 156 private boolean mInEditingDescription; 157 private final boolean mIsSubAction; 158 159 /** 160 * Constructs an ViewHolder and caches the relevant subviews. 161 */ 162 public ViewHolder(View v) { 163 this(v, false); 164 } 165 166 /** 167 * Constructs an ViewHolder for sub action and caches the relevant subviews. 168 */ 169 public ViewHolder(View v, boolean isSubAction) { 170 super(v); 171 172 mContentView = v.findViewById(R.id.guidedactions_item_content); 173 mTitleView = (TextView) v.findViewById(R.id.guidedactions_item_title); 174 mDescriptionView = (TextView) v.findViewById(R.id.guidedactions_item_description); 175 mIconView = (ImageView) v.findViewById(R.id.guidedactions_item_icon); 176 mCheckmarkView = (ImageView) v.findViewById(R.id.guidedactions_item_checkmark); 177 mChevronView = (ImageView) v.findViewById(R.id.guidedactions_item_chevron); 178 mIsSubAction = isSubAction; 179 } 180 181 /** 182 * Returns the content view within this view holder's view, where title and description are 183 * shown. 184 */ 185 public View getContentView() { 186 return mContentView; 187 } 188 189 /** 190 * Returns the title view within this view holder's view. 191 */ 192 public TextView getTitleView() { 193 return mTitleView; 194 } 195 196 /** 197 * Convenience method to return an editable version of the title, if possible, 198 * or null if the title view isn't an EditText. 199 */ 200 public EditText getEditableTitleView() { 201 return (mTitleView instanceof EditText) ? (EditText)mTitleView : null; 202 } 203 204 /** 205 * Returns the description view within this view holder's view. 206 */ 207 public TextView getDescriptionView() { 208 return mDescriptionView; 209 } 210 211 /** 212 * Convenience method to return an editable version of the description, if possible, 213 * or null if the description view isn't an EditText. 214 */ 215 public EditText getEditableDescriptionView() { 216 return (mDescriptionView instanceof EditText) ? (EditText)mDescriptionView : null; 217 } 218 219 /** 220 * Returns the icon view within this view holder's view. 221 */ 222 public ImageView getIconView() { 223 return mIconView; 224 } 225 226 /** 227 * Returns the checkmark view within this view holder's view. 228 */ 229 public ImageView getCheckmarkView() { 230 return mCheckmarkView; 231 } 232 233 /** 234 * Returns the chevron view within this view holder's view. 235 */ 236 public ImageView getChevronView() { 237 return mChevronView; 238 } 239 240 /** 241 * Returns true if the TextView is in editing title or description, false otherwise. 242 */ 243 public boolean isInEditing() { 244 return mInEditing; 245 } 246 247 /** 248 * Returns true if the TextView is in editing description, false otherwise. 249 */ 250 public boolean isInEditingDescription() { 251 return mInEditingDescription; 252 } 253 254 /** 255 * @return Current editing title view or description view or null if not in editing. 256 */ 257 public View getEditingView() { 258 if (mInEditing) { 259 return mInEditingDescription ? mDescriptionView : mTitleView; 260 } else { 261 return null; 262 } 263 } 264 265 /** 266 * @return True if bound action is inside {@link GuidedAction#getSubActions()}, false 267 * otherwise. 268 */ 269 public boolean isSubAction() { 270 return mIsSubAction; 271 } 272 273 /** 274 * @return Currently bound action. 275 */ 276 public GuidedAction getAction() { 277 return mAction; 278 } 279 } 280 281 private static String TAG = "GuidedActionsStylist"; 282 283 private ViewGroup mMainView; 284 private VerticalGridView mActionsGridView; 285 private VerticalGridView mSubActionsGridView; 286 private View mBgView; 287 private View mSelectorView; 288 private View mContentView; 289 private boolean mButtonActions; 290 291 private Animator mSelectorAnimator; 292 293 // Cached values from resources 294 private float mEnabledTextAlpha; 295 private float mDisabledTextAlpha; 296 private float mEnabledDescriptionAlpha; 297 private float mDisabledDescriptionAlpha; 298 private float mEnabledChevronAlpha; 299 private float mDisabledChevronAlpha; 300 private int mTitleMinLines; 301 private int mTitleMaxLines; 302 private int mDescriptionMinLines; 303 private int mVerticalPadding; 304 private int mDisplayHeight; 305 306 private GuidedAction mExpandedAction = null; 307 private Object mExpandTransition; 308 309 private final RecyclerView.OnScrollListener mOnGridScrollListener = 310 new RecyclerView.OnScrollListener() { 311 @Override 312 public void onScrollStateChanged(RecyclerView recyclerView, int newState) { 313 if (newState == RecyclerView.SCROLL_STATE_IDLE) { 314 if (mSelectorView.getAlpha() != 1f) { 315 updateSelectorView(true); 316 } 317 } 318 } 319 }; 320 321 /** 322 * Creates a view appropriate for displaying a list of GuidedActions, using the provided 323 * inflater and container. 324 * <p> 325 * <i>Note: Does not actually add the created view to the container; the caller should do 326 * this.</i> 327 * @param inflater The layout inflater to be used when constructing the view. 328 * @param container The view group to be passed in the call to 329 * <code>LayoutInflater.inflate</code>. 330 * @return The view to be added to the caller's view hierarchy. 331 */ 332 public View onCreateView(LayoutInflater inflater, ViewGroup container) { 333 mMainView = (ViewGroup) inflater.inflate(onProvideLayoutId(), container, false); 334 mContentView = mMainView.findViewById(mButtonActions ? R.id.guidedactions_content2 : 335 R.id.guidedactions_content); 336 mSelectorView = mMainView.findViewById(mButtonActions ? R.id.guidedactions_selector2 : 337 R.id.guidedactions_selector); 338 mSelectorView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { 339 @Override 340 public void onLayoutChange(View v, int left, int top, int right, int bottom, 341 int oldLeft, int oldTop, int oldRight, int oldBottom) { 342 updateSelectorView(false); 343 } 344 }); 345 mBgView = mMainView.findViewById(mButtonActions ? R.id.guidedactions_list_background2 : 346 R.id.guidedactions_list_background); 347 if (mMainView instanceof VerticalGridView) { 348 mActionsGridView = (VerticalGridView) mMainView; 349 } else { 350 mActionsGridView = (VerticalGridView) mMainView.findViewById(mButtonActions ? 351 R.id.guidedactions_list2 : R.id.guidedactions_list); 352 if (mActionsGridView == null) { 353 throw new IllegalStateException("No ListView exists."); 354 } 355 mActionsGridView.setWindowAlignmentOffset(0); 356 mActionsGridView.setWindowAlignmentOffsetPercent(50f); 357 mActionsGridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE); 358 if (mSelectorView != null) { 359 mActionsGridView.setOnScrollListener(mOnGridScrollListener); 360 } 361 if (!mButtonActions) { 362 mSubActionsGridView = (VerticalGridView) mMainView.findViewById( 363 R.id.guidedactions_sub_list); 364 if (mSelectorView != null && mSubActionsGridView != null) { 365 mSubActionsGridView.setOnScrollListener(mOnGridScrollListener); 366 } 367 } 368 } 369 370 if (mSelectorView != null) { 371 // ALlow focus to move to other views 372 mMainView.getViewTreeObserver().addOnGlobalFocusChangeListener( 373 mGlobalFocusChangeListener); 374 } 375 376 // Cache widths, chevron alpha values, max and min text lines, etc 377 Context ctx = mMainView.getContext(); 378 TypedValue val = new TypedValue(); 379 mEnabledChevronAlpha = getFloat(ctx, val, R.attr.guidedActionEnabledChevronAlpha); 380 mDisabledChevronAlpha = getFloat(ctx, val, R.attr.guidedActionDisabledChevronAlpha); 381 mTitleMinLines = getInteger(ctx, val, R.attr.guidedActionTitleMinLines); 382 mTitleMaxLines = getInteger(ctx, val, R.attr.guidedActionTitleMaxLines); 383 mDescriptionMinLines = getInteger(ctx, val, R.attr.guidedActionDescriptionMinLines); 384 mVerticalPadding = getDimension(ctx, val, R.attr.guidedActionVerticalPadding); 385 mDisplayHeight = ((WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE)) 386 .getDefaultDisplay().getHeight(); 387 388 mEnabledTextAlpha = Float.valueOf(ctx.getResources().getString(R.string 389 .lb_guidedactions_item_unselected_text_alpha)); 390 mDisabledTextAlpha = Float.valueOf(ctx.getResources().getString(R.string 391 .lb_guidedactions_item_disabled_text_alpha)); 392 mEnabledDescriptionAlpha = Float.valueOf(ctx.getResources().getString(R.string 393 .lb_guidedactions_item_unselected_description_text_alpha)); 394 mDisabledDescriptionAlpha = Float.valueOf(ctx.getResources().getString(R.string 395 .lb_guidedactions_item_disabled_description_text_alpha)); 396 return mMainView; 397 } 398 399 /** 400 * Choose the layout resource for button actions in {@link #onProvideLayoutId()}. 401 */ 402 public void setAsButtonActions() { 403 if (mMainView != null) { 404 throw new IllegalStateException("setAsButtonActions() must be called before creating " 405 + "views"); 406 } 407 mButtonActions = true; 408 } 409 410 /** 411 * Returns true if it is button actions list, false for normal actions list. 412 * @return True if it is button actions list, false for normal actions list. 413 */ 414 public boolean isButtonActions() { 415 return mButtonActions; 416 } 417 418 final ViewTreeObserver.OnGlobalFocusChangeListener mGlobalFocusChangeListener = 419 new ViewTreeObserver.OnGlobalFocusChangeListener() { 420 421 @Override 422 public void onGlobalFocusChanged(View oldFocus, View newFocus) { 423 updateSelectorView(false); 424 } 425 }; 426 427 /** 428 * Called when destroy the View created by GuidedActionsStylist. 429 */ 430 public void onDestroyView() { 431 if (mSelectorView != null) { 432 mMainView.getViewTreeObserver().removeOnGlobalFocusChangeListener( 433 mGlobalFocusChangeListener); 434 } 435 endSelectorAnimator(); 436 mExpandedAction = null; 437 mExpandTransition = null; 438 mActionsGridView = null; 439 mSubActionsGridView = null; 440 mSelectorView = null; 441 mContentView = null; 442 mBgView = null; 443 mMainView = null; 444 } 445 446 /** 447 * Returns the VerticalGridView that displays the list of GuidedActions. 448 * @return The VerticalGridView for this presenter. 449 */ 450 public VerticalGridView getActionsGridView() { 451 return mActionsGridView; 452 } 453 454 /** 455 * Returns the VerticalGridView that displays the sub actions list of an expanded action. 456 * @return The VerticalGridView that displays the sub actions list of an expanded action. 457 */ 458 public VerticalGridView getSubActionsGridView() { 459 return mSubActionsGridView; 460 } 461 462 /** 463 * Provides the resource ID of the layout defining the host view for the list of guided actions. 464 * Subclasses may override to provide their own customized layouts. The base implementation 465 * returns {@link android.support.v17.leanback.R.layout#lb_guidedactions} or 466 * {@link android.support.v17.leanback.R.layout#lb_guidedbuttonactions} if 467 * {@link #isButtonActions()} is true. If overridden, the substituted layout should contain 468 * matching IDs for any views that should be managed by the base class; this can be achieved by 469 * starting with a copy of the base layout file. 470 * 471 * @return The resource ID of the layout to be inflated to define the host view for the list of 472 * GuidedActions. 473 */ 474 public int onProvideLayoutId() { 475 return mButtonActions ? R.layout.lb_guidedbuttonactions : R.layout.lb_guidedactions; 476 } 477 478 /** 479 * Return view type of action, each different type can have differently associated layout Id. 480 * Default implementation returns {@link #VIEW_TYPE_DEFAULT}. 481 * @param action The action object. 482 * @return View type that used in {@link #onProvideItemLayoutId(int)}. 483 */ 484 public int getItemViewType(GuidedAction action) { 485 return VIEW_TYPE_DEFAULT; 486 } 487 488 /** 489 * Provides the resource ID of the layout defining the view for an individual guided actions. 490 * Subclasses may override to provide their own customized layouts. The base implementation 491 * returns {@link android.support.v17.leanback.R.layout#lb_guidedactions_item}. If overridden, 492 * the substituted layout should contain matching IDs for any views that should be managed by 493 * the base class; this can be achieved by starting with a copy of the base layout file. Note 494 * that in order for the item to support editing, the title view should both subclass {@link 495 * android.widget.EditText} and implement {@link ImeKeyMonitor}; see {@link 496 * GuidedActionEditText}. To support different types of Layouts, override {@link 497 * #onProvideItemLayoutId(int)}. 498 * @return The resource ID of the layout to be inflated to define the view to display an 499 * individual GuidedAction. 500 */ 501 public int onProvideItemLayoutId() { 502 return R.layout.lb_guidedactions_item; 503 } 504 505 /** 506 * Provides the resource ID of the layout defining the view for an individual guided actions. 507 * Subclasses may override to provide their own customized layouts. The base implementation 508 * returns {@link android.support.v17.leanback.R.layout#lb_guidedactions_item}. If overridden, 509 * the substituted layout should contain matching IDs for any views that should be managed by 510 * the base class; this can be achieved by starting with a copy of the base layout file. Note 511 * that in order for the item to support editing, the title view should both subclass {@link 512 * android.widget.EditText} and implement {@link ImeKeyMonitor}; see {@link 513 * GuidedActionEditText}. 514 * @param viewType View type returned by {@link #getItemViewType(GuidedAction)} 515 * @return The resource ID of the layout to be inflated to define the view to display an 516 * individual GuidedAction. 517 */ 518 public int onProvideItemLayoutId(int viewType) { 519 if (viewType == VIEW_TYPE_DEFAULT) { 520 return onProvideItemLayoutId(); 521 } else { 522 throw new RuntimeException("ViewType " + viewType + 523 " not supported in GuidedActionsStylist"); 524 } 525 } 526 527 /** 528 * Constructs a {@link ViewHolder} capable of representing {@link GuidedAction}s. Subclasses 529 * may choose to return a subclass of ViewHolder. To support different view types, override 530 * {@link #onCreateViewHolder(ViewGroup, int)} 531 * <p> 532 * <i>Note: Should not actually add the created view to the parent; the caller will do 533 * this.</i> 534 * @param parent The view group to be used as the parent of the new view. 535 * @return The view to be added to the caller's view hierarchy. 536 */ 537 public ViewHolder onCreateViewHolder(ViewGroup parent) { 538 LayoutInflater inflater = LayoutInflater.from(parent.getContext()); 539 View v = inflater.inflate(onProvideItemLayoutId(), parent, false); 540 return new ViewHolder(v, parent == mSubActionsGridView); 541 } 542 543 /** 544 * Constructs a {@link ViewHolder} capable of representing {@link GuidedAction}s. Subclasses 545 * may choose to return a subclass of ViewHolder. 546 * <p> 547 * <i>Note: Should not actually add the created view to the parent; the caller will do 548 * this.</i> 549 * @param parent The view group to be used as the parent of the new view. 550 * @param viewType The viewType returned by {@link #getItemViewType(GuidedAction)} 551 * @return The view to be added to the caller's view hierarchy. 552 */ 553 public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 554 if (viewType == VIEW_TYPE_DEFAULT) { 555 return onCreateViewHolder(parent); 556 } 557 LayoutInflater inflater = LayoutInflater.from(parent.getContext()); 558 View v = inflater.inflate(onProvideItemLayoutId(viewType), parent, false); 559 return new ViewHolder(v, parent == mSubActionsGridView); 560 } 561 562 /** 563 * Binds a {@link ViewHolder} to a particular {@link GuidedAction}. 564 * @param vh The view holder to be associated with the given action. 565 * @param action The guided action to be displayed by the view holder's view. 566 * @return The view to be added to the caller's view hierarchy. 567 */ 568 public void onBindViewHolder(ViewHolder vh, GuidedAction action) { 569 570 vh.mAction = action; 571 if (vh.mTitleView != null) { 572 vh.mTitleView.setText(action.getTitle()); 573 vh.mTitleView.setAlpha(action.isEnabled() ? mEnabledTextAlpha : mDisabledTextAlpha); 574 vh.mTitleView.setFocusable(action.isEditable()); 575 } 576 if (vh.mDescriptionView != null) { 577 vh.mDescriptionView.setText(action.getDescription()); 578 vh.mDescriptionView.setVisibility(TextUtils.isEmpty(action.getDescription()) ? 579 View.GONE : View.VISIBLE); 580 vh.mDescriptionView.setAlpha(action.isEnabled() ? mEnabledDescriptionAlpha : 581 mDisabledDescriptionAlpha); 582 vh.mDescriptionView.setFocusable(action.isDescriptionEditable()); 583 } 584 // Clients might want the check mark view to be gone entirely, in which case, ignore it. 585 if (vh.mCheckmarkView != null) { 586 onBindCheckMarkView(vh, action); 587 } 588 589 if (action.hasMultilineDescription()) { 590 if (vh.mTitleView != null) { 591 vh.mTitleView.setMaxLines(mTitleMaxLines); 592 if (vh.mDescriptionView != null) { 593 vh.mDescriptionView.setMaxHeight(getDescriptionMaxHeight( 594 vh.itemView.getContext(), vh.mTitleView)); 595 } 596 } 597 } else { 598 if (vh.mTitleView != null) { 599 vh.mTitleView.setMaxLines(mTitleMinLines); 600 } 601 if (vh.mDescriptionView != null) { 602 vh.mDescriptionView.setMaxLines(mDescriptionMinLines); 603 } 604 } 605 setEditingMode(vh, action, false); 606 if (action.isFocusable()) { 607 vh.itemView.setFocusable(true); 608 ((ViewGroup) vh.itemView).setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); 609 } else { 610 vh.itemView.setFocusable(false); 611 ((ViewGroup) vh.itemView).setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); 612 } 613 setupImeOptions(vh, action); 614 615 updateChevronAndVisibility(vh); 616 } 617 618 /** 619 * Called by {@link #onBindViewHolder(ViewHolder, GuidedAction)} to setup IME options. Default 620 * implementation assigns {@link EditorInfo#IME_ACTION_DONE}. Subclass may override. 621 * @param vh The view holder to be associated with the given action. 622 * @param action The guided action to be displayed by the view holder's view. 623 */ 624 protected void setupImeOptions(ViewHolder vh, GuidedAction action) { 625 setupNextImeOptions(vh.getEditableTitleView()); 626 setupNextImeOptions(vh.getEditableDescriptionView()); 627 } 628 629 private void setupNextImeOptions(EditText edit) { 630 if (edit != null) { 631 edit.setImeOptions(EditorInfo.IME_ACTION_NEXT); 632 } 633 } 634 635 public void setEditingMode(ViewHolder vh, GuidedAction action, boolean editing) { 636 if (editing != vh.mInEditing) { 637 vh.mInEditing = editing; 638 onEditingModeChange(vh, action, editing); 639 } 640 } 641 642 protected void onEditingModeChange(ViewHolder vh, GuidedAction action, boolean editing) { 643 action = vh.getAction(); 644 TextView titleView = vh.getTitleView(); 645 TextView descriptionView = vh.getDescriptionView(); 646 if (editing) { 647 CharSequence editTitle = action.getEditTitle(); 648 if (titleView != null && editTitle != null) { 649 titleView.setText(editTitle); 650 } 651 CharSequence editDescription = action.getEditDescription(); 652 if (descriptionView != null && editDescription != null) { 653 descriptionView.setText(editDescription); 654 } 655 if (action.isDescriptionEditable()) { 656 if (descriptionView != null) { 657 descriptionView.setVisibility(View.VISIBLE); 658 descriptionView.setInputType(action.getDescriptionEditInputType()); 659 } 660 vh.mInEditingDescription = true; 661 } else { 662 vh.mInEditingDescription = false; 663 if (titleView != null) { 664 titleView.setInputType(action.getEditInputType()); 665 } 666 } 667 } else { 668 if (titleView != null) { 669 titleView.setText(action.getTitle()); 670 } 671 if (descriptionView != null) { 672 descriptionView.setText(action.getDescription()); 673 } 674 if (vh.mInEditingDescription) { 675 if (descriptionView != null) { 676 descriptionView.setVisibility(TextUtils.isEmpty(action.getDescription()) ? 677 View.GONE : View.VISIBLE); 678 descriptionView.setInputType(action.getDescriptionInputType()); 679 } 680 vh.mInEditingDescription = false; 681 } else { 682 if (titleView != null) { 683 titleView.setInputType(action.getInputType()); 684 } 685 } 686 } 687 } 688 689 /** 690 * Animates the view holder's view (or subviews thereof) when the action has had its focus 691 * state changed. 692 * @param vh The view holder associated with the relevant action. 693 * @param focused True if the action has become focused, false if it has lost focus. 694 */ 695 public void onAnimateItemFocused(ViewHolder vh, boolean focused) { 696 // No animations for this, currently, because the animation is done on 697 // mSelectorView 698 } 699 700 /** 701 * Animates the view holder's view (or subviews thereof) when the action has had its press 702 * state changed. 703 * @param vh The view holder associated with the relevant action. 704 * @param pressed True if the action has been pressed, false if it has been unpressed. 705 */ 706 public void onAnimateItemPressed(ViewHolder vh, boolean pressed) { 707 int attr = pressed ? R.attr.guidedActionPressedAnimation : 708 R.attr.guidedActionUnpressedAnimation; 709 createAnimator(vh.itemView, attr).start(); 710 } 711 712 /** 713 * Resets the view holder's view to unpressed state. 714 * @param vh The view holder associated with the relevant action. 715 */ 716 public void onAnimateItemPressedCancelled(ViewHolder vh) { 717 createAnimator(vh.itemView, R.attr.guidedActionUnpressedAnimation).end(); 718 } 719 720 /** 721 * Animates the view holder's view (or subviews thereof) when the action has had its check state 722 * changed. Default implementation calls setChecked() if {@link ViewHolder#getCheckmarkView()} 723 * is instance of {@link Checkable}. 724 * 725 * @param vh The view holder associated with the relevant action. 726 * @param checked True if the action has become checked, false if it has become unchecked. 727 * @see #onBindCheckMarkView(ViewHolder, GuidedAction) 728 */ 729 public void onAnimateItemChecked(ViewHolder vh, boolean checked) { 730 if (vh.mCheckmarkView instanceof Checkable) { 731 ((Checkable) vh.mCheckmarkView).setChecked(checked); 732 } 733 } 734 735 /** 736 * Sets states of check mark view, called by {@link #onBindViewHolder(ViewHolder, GuidedAction)} 737 * when action's checkset Id is other than {@link GuidedAction#NO_CHECK_SET}. Default 738 * implementation assigns drawable loaded from theme attribute 739 * {@link android.R.attr#listChoiceIndicatorMultiple} for checkbox or 740 * {@link android.R.attr#listChoiceIndicatorSingle} for radio button. Subclass rarely needs 741 * override the method, instead app can provide its own drawable that supports transition 742 * animations, change theme attributes {@link android.R.attr#listChoiceIndicatorMultiple} and 743 * {@link android.R.attr#listChoiceIndicatorSingle} in {android.support.v17.leanback.R. 744 * styleable#LeanbackGuidedStepTheme}. 745 * 746 * @param vh The view holder associated with the relevant action. 747 * @param action The GuidedAction object to bind to. 748 * @see #onAnimateItemChecked(ViewHolder, boolean) 749 */ 750 public void onBindCheckMarkView(ViewHolder vh, GuidedAction action) { 751 if (action.getCheckSetId() != GuidedAction.NO_CHECK_SET) { 752 vh.mCheckmarkView.setVisibility(View.VISIBLE); 753 int attrId = action.getCheckSetId() == GuidedAction.CHECKBOX_CHECK_SET_ID ? 754 android.R.attr.listChoiceIndicatorMultiple : 755 android.R.attr.listChoiceIndicatorSingle; 756 final Context context = vh.mCheckmarkView.getContext(); 757 Drawable drawable = null; 758 TypedValue typedValue = new TypedValue(); 759 if (context.getTheme().resolveAttribute(attrId, typedValue, true)) { 760 drawable = ContextCompat.getDrawable(context, typedValue.resourceId); 761 } 762 vh.mCheckmarkView.setImageDrawable(drawable); 763 if (vh.mCheckmarkView instanceof Checkable) { 764 ((Checkable) vh.mCheckmarkView).setChecked(action.isChecked()); 765 } 766 } else { 767 vh.mCheckmarkView.setVisibility(View.GONE); 768 } 769 } 770 771 /** 772 * Sets states of chevron view, called by {@link #onBindViewHolder(ViewHolder, GuidedAction)}. 773 * Subclass may override. 774 * 775 * @param vh The view holder associated with the relevant action. 776 * @param action The GuidedAction object to bind to. 777 */ 778 public void onBindChevronView(ViewHolder vh, GuidedAction action) { 779 final boolean hasNext = action.hasNext(); 780 final boolean hasSubActions = action.hasSubActions(); 781 if (hasNext || hasSubActions) { 782 vh.mChevronView.setVisibility(View.VISIBLE); 783 vh.mChevronView.setAlpha(action.isEnabled() ? mEnabledChevronAlpha : 784 mDisabledChevronAlpha); 785 if (hasNext) { 786 vh.mChevronView.setRotation(0f); 787 } else if (action == mExpandedAction) { 788 vh.mChevronView.setRotation(270); 789 } else { 790 vh.mChevronView.setRotation(90); 791 } 792 } else { 793 vh.mChevronView.setVisibility(View.GONE); 794 795 } 796 } 797 798 /** 799 * Expands or collapse the sub actions list view. 800 * @param avh When not null, fill sub actions list of this ViewHolder into sub actions list and 801 * hide the other items in main list. When null, collapse the sub actions list. 802 */ 803 public void setExpandedViewHolder(ViewHolder avh) { 804 if (mSubActionsGridView == null || isInExpandTransition()) { 805 return; 806 } 807 if (isExpandTransitionSupported()) { 808 startExpandedTransition(avh); 809 } else { 810 onUpdateExpandedViewHolder(avh); 811 } 812 } 813 814 /** 815 * Returns true if it is running an expanding or collapsing transition, false otherwise. 816 * @return True if it is running an expanding or collapsing transition, false otherwise. 817 */ 818 public boolean isInExpandTransition() { 819 return mExpandTransition != null; 820 } 821 822 /** 823 * Returns if expand/collapse animation is supported. When this method returns true, 824 * {@link #startExpandedTransition(ViewHolder)} will be used. When this method returns false, 825 * {@link #onUpdateExpandedViewHolder(ViewHolder)} will be called. 826 * @return True if it is running an expanding or collapsing transition, false otherwise. 827 */ 828 public boolean isExpandTransitionSupported() { 829 return VERSION.SDK_INT >= 21; 830 } 831 832 /** 833 * Start transition to expand or collapse GuidedActionStylist. 834 * @param avh When not null, the GuidedActionStylist expands the sub actions of avh. When null 835 * the GuidedActionStylist will collapse sub actions. 836 */ 837 public void startExpandedTransition(ViewHolder avh) { 838 ViewHolder focusAvh = null; // expand / collapse view holder 839 final int count = mActionsGridView.getChildCount(); 840 for (int i = 0; i < count; i++) { 841 ViewHolder vh = (ViewHolder) mActionsGridView 842 .getChildViewHolder(mActionsGridView.getChildAt(i)); 843 if (avh == null && vh.itemView.getVisibility() == View.VISIBLE) { 844 // going to collapse this one. 845 focusAvh = vh; 846 break; 847 } else if (avh != null && vh.getAction() == avh.getAction()) { 848 // going to expand this one. 849 focusAvh = vh; 850 break; 851 } 852 } 853 if (focusAvh == null) { 854 // huh? 855 onUpdateExpandedViewHolder(avh); 856 return; 857 } 858 Object set = TransitionHelper.createTransitionSet(false); 859 Object slideAndFade = TransitionHelper.createFadeAndShortSlide(Gravity.TOP | Gravity.BOTTOM, 860 (float) focusAvh.itemView.getHeight()); 861 Object changeFocusItemTransform = TransitionHelper.createChangeTransform(); 862 Object changeFocusItemBounds = TransitionHelper.createChangeBounds(false); 863 Object fadeGrid = TransitionHelper.createFadeTransition(TransitionHelper.FADE_IN | 864 TransitionHelper.FADE_OUT); 865 Object changeGridBounds = TransitionHelper.createChangeBounds(false); 866 if (avh == null) { 867 TransitionHelper.setStartDelay(slideAndFade, 150); 868 TransitionHelper.setStartDelay(changeFocusItemTransform, 100); 869 TransitionHelper.setStartDelay(changeFocusItemBounds, 100); 870 } else { 871 TransitionHelper.setStartDelay(fadeGrid, 100); 872 TransitionHelper.setStartDelay(changeGridBounds, 100); 873 TransitionHelper.setStartDelay(changeFocusItemTransform, 50); 874 TransitionHelper.setStartDelay(changeFocusItemBounds, 50); 875 } 876 for (int i = 0; i < count; i++) { 877 ViewHolder vh = (ViewHolder) mActionsGridView 878 .getChildViewHolder(mActionsGridView.getChildAt(i)); 879 if (vh == focusAvh) { 880 // going to expand/collapse this one. 881 TransitionHelper.include(changeFocusItemTransform, vh.itemView); 882 TransitionHelper.include(changeFocusItemBounds, vh.itemView); 883 } else { 884 // going to slide this item to top / bottom. 885 TransitionHelper.include(slideAndFade, vh.itemView); 886 } 887 } 888 if (mSelectorView != null) { 889 TransitionHelper.include(changeFocusItemTransform, mSelectorView); 890 TransitionHelper.include(changeFocusItemBounds, mSelectorView); 891 } 892 TransitionHelper.include(fadeGrid, mSubActionsGridView); 893 TransitionHelper.include(changeGridBounds, mSubActionsGridView); 894 TransitionHelper.addTransition(set, slideAndFade); 895 TransitionHelper.addTransition(set, changeFocusItemTransform); 896 TransitionHelper.addTransition(set, changeFocusItemBounds); 897 TransitionHelper.addTransition(set, fadeGrid); 898 TransitionHelper.addTransition(set, changeGridBounds); 899 mExpandTransition = set; 900 TransitionHelper.addTransitionListener(mExpandTransition, new TransitionListener() { 901 @Override 902 public void onTransitionEnd(Object transition) { 903 mExpandTransition = null; 904 } 905 }); 906 if (avh != null && mSubActionsGridView.getTop() != avh.itemView.getTop()) { 907 // For expanding, set the initial position of subActionsGridView before running 908 // a ChangeBounds on it. 909 final ViewHolder toUpdate = avh; 910 mSubActionsGridView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { 911 @Override 912 public void onLayoutChange(View v, int left, int top, int right, int bottom, 913 int oldLeft, int oldTop, int oldRight, int oldBottom) { 914 if (mSubActionsGridView == null) { 915 return; 916 } 917 mSubActionsGridView.removeOnLayoutChangeListener(this); 918 mMainView.post(new Runnable() { 919 public void run() { 920 if (mMainView == null) { 921 return; 922 } 923 TransitionHelper.beginDelayedTransition(mMainView, mExpandTransition); 924 onUpdateExpandedViewHolder(toUpdate); 925 } 926 }); 927 } 928 }); 929 ViewGroup.MarginLayoutParams lp = 930 (ViewGroup.MarginLayoutParams) mSubActionsGridView.getLayoutParams(); 931 lp.topMargin = avh.itemView.getTop(); 932 lp.height = 0; 933 mSubActionsGridView.setLayoutParams(lp); 934 return; 935 } 936 TransitionHelper.beginDelayedTransition(mMainView, mExpandTransition); 937 onUpdateExpandedViewHolder(avh); 938 } 939 940 /** 941 * @return True if sub actions list is expanded. 942 */ 943 public boolean isSubActionsExpanded() { 944 return mExpandedAction != null; 945 } 946 947 /** 948 * @return Current expanded GuidedAction or null if not expanded. 949 */ 950 public GuidedAction getExpandedAction() { 951 return mExpandedAction; 952 } 953 954 /** 955 * Expand or collapse GuidedActionStylist. 956 * @param avh When not null, the GuidedActionStylist expands the sub actions of avh. When null 957 * the GuidedActionStylist will collapse sub actions. 958 */ 959 public void onUpdateExpandedViewHolder(ViewHolder avh) { 960 if (avh == null) { 961 mExpandedAction = null; 962 } else if (avh.getAction() != mExpandedAction) { 963 mExpandedAction = avh.getAction(); 964 } 965 final int count = mActionsGridView.getChildCount(); 966 for (int i = 0; i < count; i++) { 967 ViewHolder vh = (ViewHolder) mActionsGridView 968 .getChildViewHolder(mActionsGridView.getChildAt(i)); 969 updateChevronAndVisibility(vh); 970 } 971 if (mSubActionsGridView != null) { 972 if (avh != null) { 973 ViewGroup.MarginLayoutParams lp = 974 (ViewGroup.MarginLayoutParams) mSubActionsGridView.getLayoutParams(); 975 lp.topMargin = avh.itemView.getTop(); 976 lp.height = ViewGroup.MarginLayoutParams.MATCH_PARENT; 977 mSubActionsGridView.setLayoutParams(lp); 978 mSubActionsGridView.setVisibility(View.VISIBLE); 979 mSubActionsGridView.requestFocus(); 980 mSubActionsGridView.setSelectedPosition(0); 981 ((GuidedActionAdapter) mSubActionsGridView.getAdapter()) 982 .setActions(avh.getAction().getSubActions()); 983 } else { 984 mSubActionsGridView.setVisibility(View.INVISIBLE); 985 ViewGroup.MarginLayoutParams lp = 986 (ViewGroup.MarginLayoutParams) mSubActionsGridView.getLayoutParams(); 987 lp.height = 0; 988 mSubActionsGridView.setLayoutParams(lp); 989 ((GuidedActionAdapter) mSubActionsGridView.getAdapter()) 990 .setActions(Collections.EMPTY_LIST); 991 mActionsGridView.requestFocus(); 992 } 993 } 994 } 995 996 private void updateChevronAndVisibility(ViewHolder vh) { 997 if (!vh.isSubAction()) { 998 if (mExpandedAction == null) { 999 vh.itemView.setVisibility(View.VISIBLE); 1000 vh.itemView.setTranslationY(0); 1001 } else if (vh.getAction() == mExpandedAction) { 1002 vh.itemView.setVisibility(View.VISIBLE); 1003 vh.itemView.setTranslationY(- vh.itemView.getHeight()); 1004 } else { 1005 vh.itemView.setVisibility(View.INVISIBLE); 1006 vh.itemView.setTranslationY(0); 1007 } 1008 } 1009 if (vh.mChevronView != null) { 1010 onBindChevronView(vh, vh.getAction()); 1011 } 1012 } 1013 1014 /* 1015 * ========================================== 1016 * FragmentAnimationProvider overrides 1017 * ========================================== 1018 */ 1019 1020 /** 1021 * {@inheritDoc} 1022 */ 1023 @Override 1024 public void onImeAppearing(@NonNull List<Animator> animators) { 1025 animators.add(createAnimator(mContentView, R.attr.guidedStepImeAppearingAnimation)); 1026 } 1027 1028 /** 1029 * {@inheritDoc} 1030 */ 1031 @Override 1032 public void onImeDisappearing(@NonNull List<Animator> animators) { 1033 animators.add(createAnimator(mContentView, R.attr.guidedStepImeDisappearingAnimation)); 1034 } 1035 1036 /* 1037 * ========================================== 1038 * Private methods 1039 * ========================================== 1040 */ 1041 1042 private float getFloat(Context ctx, TypedValue typedValue, int attrId) { 1043 ctx.getTheme().resolveAttribute(attrId, typedValue, true); 1044 // Android resources don't have a native float type, so we have to use strings. 1045 return Float.valueOf(ctx.getResources().getString(typedValue.resourceId)); 1046 } 1047 1048 private int getInteger(Context ctx, TypedValue typedValue, int attrId) { 1049 ctx.getTheme().resolveAttribute(attrId, typedValue, true); 1050 return ctx.getResources().getInteger(typedValue.resourceId); 1051 } 1052 1053 private int getDimension(Context ctx, TypedValue typedValue, int attrId) { 1054 ctx.getTheme().resolveAttribute(attrId, typedValue, true); 1055 return ctx.getResources().getDimensionPixelSize(typedValue.resourceId); 1056 } 1057 1058 private static Animator createAnimator(View v, int attrId) { 1059 Context ctx = v.getContext(); 1060 TypedValue typedValue = new TypedValue(); 1061 ctx.getTheme().resolveAttribute(attrId, typedValue, true); 1062 Animator animator = AnimatorInflater.loadAnimator(ctx, typedValue.resourceId); 1063 animator.setTarget(v); 1064 return animator; 1065 } 1066 1067 private boolean setIcon(final ImageView iconView, GuidedAction action) { 1068 Drawable icon = null; 1069 if (iconView != null) { 1070 Context context = iconView.getContext(); 1071 icon = action.getIcon(); 1072 if (icon != null) { 1073 // setImageDrawable resets the drawable's level unless we set the view level first. 1074 iconView.setImageLevel(icon.getLevel()); 1075 iconView.setImageDrawable(icon); 1076 iconView.setVisibility(View.VISIBLE); 1077 } else { 1078 iconView.setVisibility(View.GONE); 1079 } 1080 } 1081 return icon != null; 1082 } 1083 1084 /** 1085 * @return the max height in pixels the description can be such that the 1086 * action nicely takes up the entire screen. 1087 */ 1088 private int getDescriptionMaxHeight(Context context, TextView title) { 1089 // The 2 multiplier on the title height calculation is a 1090 // conservative estimate for font padding which can not be 1091 // calculated at this stage since the view hasn't been rendered yet. 1092 return (int)(mDisplayHeight - 2*mVerticalPadding - 2*mTitleMaxLines*title.getLineHeight()); 1093 } 1094 1095 private void endSelectorAnimator() { 1096 if (mSelectorAnimator != null) { 1097 mSelectorAnimator.end(); 1098 mSelectorAnimator = null; 1099 } 1100 } 1101 1102 private void updateSelectorView(boolean animate) { 1103 if ((mActionsGridView == null && mSubActionsGridView == null) 1104 || mSelectorView == null || mSelectorView.getHeight() <= 0) { 1105 return; 1106 } 1107 RecyclerView actionsGridView = null; 1108 View focusedChild = mActionsGridView.getFocusedChild(); 1109 if (focusedChild == null && mSubActionsGridView != null) { 1110 focusedChild = mSubActionsGridView.getFocusedChild(); 1111 if (focusedChild != null) { 1112 actionsGridView = mSubActionsGridView; 1113 } 1114 } else { 1115 actionsGridView = mActionsGridView; 1116 } 1117 endSelectorAnimator(); 1118 if (focusedChild == null || !actionsGridView.hasFocus() 1119 || actionsGridView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) { 1120 if (animate) { 1121 mSelectorAnimator = createAnimator(mSelectorView, 1122 R.attr.guidedActionsSelectorHideAnimation); 1123 mSelectorAnimator.start(); 1124 } else { 1125 mSelectorView.setAlpha(0f); 1126 } 1127 } else { 1128 final float scaleY = (float) focusedChild.getHeight() / mSelectorView.getHeight(); 1129 Rect r = new Rect(0, 0, focusedChild.getWidth(), focusedChild.getHeight()); 1130 mMainView.offsetDescendantRectToMyCoords(focusedChild, r); 1131 mMainView.offsetRectIntoDescendantCoords(mSelectorView, r); 1132 mSelectorView.setTranslationY(r.exactCenterY() - mSelectorView.getHeight() * 0.5f); 1133 if (animate) { 1134 mSelectorAnimator = createAnimator(mSelectorView, 1135 R.attr.guidedActionsSelectorShowAnimation); 1136 ((ObjectAnimator) ((AnimatorSet) mSelectorAnimator).getChildAnimations().get(1)) 1137 .setFloatValues(scaleY); 1138 mSelectorAnimator.start(); 1139 } else { 1140 mSelectorView.setAlpha(1f); 1141 mSelectorView.setScaleY(scaleY); 1142 } 1143 } 1144 } 1145 1146} 1147