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