GuidedActionsStylist.java revision ed6ddac644df9949403f1a01e1224a37cb568feb
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 void setActivated(boolean activated) { 353 mActivatorView.setActivated(activated); 354 if (itemView instanceof GuidedActionItemContainer) { 355 ((GuidedActionItemContainer) itemView).setFocusOutAllowed(!activated); 356 } 357 } 358 } 359 360 private static String TAG = "GuidedActionsStylist"; 361 362 private ViewGroup mMainView; 363 private VerticalGridView mActionsGridView; 364 private VerticalGridView mSubActionsGridView; 365 private View mBgView; 366 private View mContentView; 367 private boolean mButtonActions; 368 369 // Cached values from resources 370 private float mEnabledTextAlpha; 371 private float mDisabledTextAlpha; 372 private float mEnabledDescriptionAlpha; 373 private float mDisabledDescriptionAlpha; 374 private float mEnabledChevronAlpha; 375 private float mDisabledChevronAlpha; 376 private int mTitleMinLines; 377 private int mTitleMaxLines; 378 private int mDescriptionMinLines; 379 private int mVerticalPadding; 380 private int mDisplayHeight; 381 382 private EditListener mEditListener; 383 384 private GuidedAction mExpandedAction = null; 385 private Object mExpandTransition; 386 387 /** 388 * Creates a view appropriate for displaying a list of GuidedActions, using the provided 389 * inflater and container. 390 * <p> 391 * <i>Note: Does not actually add the created view to the container; the caller should do 392 * this.</i> 393 * @param inflater The layout inflater to be used when constructing the view. 394 * @param container The view group to be passed in the call to 395 * <code>LayoutInflater.inflate</code>. 396 * @return The view to be added to the caller's view hierarchy. 397 */ 398 public View onCreateView(LayoutInflater inflater, ViewGroup container) { 399 mMainView = (ViewGroup) inflater.inflate(onProvideLayoutId(), container, false); 400 mContentView = mMainView.findViewById(mButtonActions ? R.id.guidedactions_content2 : 401 R.id.guidedactions_content); 402 mBgView = mMainView.findViewById(mButtonActions ? R.id.guidedactions_list_background2 : 403 R.id.guidedactions_list_background); 404 if (mMainView instanceof VerticalGridView) { 405 mActionsGridView = (VerticalGridView) mMainView; 406 } else { 407 mActionsGridView = (VerticalGridView) mMainView.findViewById(mButtonActions ? 408 R.id.guidedactions_list2 : R.id.guidedactions_list); 409 if (mActionsGridView == null) { 410 throw new IllegalStateException("No ListView exists."); 411 } 412 mActionsGridView.setWindowAlignmentOffset(0); 413 mActionsGridView.setWindowAlignmentOffsetPercent(50f); 414 mActionsGridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE); 415 if (!mButtonActions) { 416 mSubActionsGridView = (VerticalGridView) mMainView.findViewById( 417 R.id.guidedactions_sub_list); 418 } 419 } 420 421 // Cache widths, chevron alpha values, max and min text lines, etc 422 Context ctx = mMainView.getContext(); 423 TypedValue val = new TypedValue(); 424 mEnabledChevronAlpha = getFloat(ctx, val, R.attr.guidedActionEnabledChevronAlpha); 425 mDisabledChevronAlpha = getFloat(ctx, val, R.attr.guidedActionDisabledChevronAlpha); 426 mTitleMinLines = getInteger(ctx, val, R.attr.guidedActionTitleMinLines); 427 mTitleMaxLines = getInteger(ctx, val, R.attr.guidedActionTitleMaxLines); 428 mDescriptionMinLines = getInteger(ctx, val, R.attr.guidedActionDescriptionMinLines); 429 mVerticalPadding = getDimension(ctx, val, R.attr.guidedActionVerticalPadding); 430 mDisplayHeight = ((WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE)) 431 .getDefaultDisplay().getHeight(); 432 433 mEnabledTextAlpha = Float.valueOf(ctx.getResources().getString(R.string 434 .lb_guidedactions_item_unselected_text_alpha)); 435 mDisabledTextAlpha = Float.valueOf(ctx.getResources().getString(R.string 436 .lb_guidedactions_item_disabled_text_alpha)); 437 mEnabledDescriptionAlpha = Float.valueOf(ctx.getResources().getString(R.string 438 .lb_guidedactions_item_unselected_description_text_alpha)); 439 mDisabledDescriptionAlpha = Float.valueOf(ctx.getResources().getString(R.string 440 .lb_guidedactions_item_disabled_description_text_alpha)); 441 return mMainView; 442 } 443 444 /** 445 * Choose the layout resource for button actions in {@link #onProvideLayoutId()}. 446 */ 447 public void setAsButtonActions() { 448 if (mMainView != null) { 449 throw new IllegalStateException("setAsButtonActions() must be called before creating " 450 + "views"); 451 } 452 mButtonActions = true; 453 } 454 455 /** 456 * Returns true if it is button actions list, false for normal actions list. 457 * @return True if it is button actions list, false for normal actions list. 458 */ 459 public boolean isButtonActions() { 460 return mButtonActions; 461 } 462 463 /** 464 * Called when destroy the View created by GuidedActionsStylist. 465 */ 466 public void onDestroyView() { 467 mExpandedAction = null; 468 mExpandTransition = null; 469 mActionsGridView = null; 470 mSubActionsGridView = null; 471 mContentView = null; 472 mBgView = null; 473 mMainView = null; 474 } 475 476 /** 477 * Returns the VerticalGridView that displays the list of GuidedActions. 478 * @return The VerticalGridView for this presenter. 479 */ 480 public VerticalGridView getActionsGridView() { 481 return mActionsGridView; 482 } 483 484 /** 485 * Returns the VerticalGridView that displays the sub actions list of an expanded action. 486 * @return The VerticalGridView that displays the sub actions list of an expanded action. 487 */ 488 public VerticalGridView getSubActionsGridView() { 489 return mSubActionsGridView; 490 } 491 492 /** 493 * Provides the resource ID of the layout defining the host view for the list of guided actions. 494 * Subclasses may override to provide their own customized layouts. The base implementation 495 * returns {@link android.support.v17.leanback.R.layout#lb_guidedactions} or 496 * {@link android.support.v17.leanback.R.layout#lb_guidedbuttonactions} if 497 * {@link #isButtonActions()} is true. If overridden, the substituted layout should contain 498 * matching IDs for any views that should be managed by the base class; this can be achieved by 499 * starting with a copy of the base layout file. 500 * 501 * @return The resource ID of the layout to be inflated to define the host view for the list of 502 * GuidedActions. 503 */ 504 public int onProvideLayoutId() { 505 return mButtonActions ? R.layout.lb_guidedbuttonactions : R.layout.lb_guidedactions; 506 } 507 508 /** 509 * Return view type of action, each different type can have differently associated layout Id. 510 * Default implementation returns {@link #VIEW_TYPE_DEFAULT}. 511 * @param action The action object. 512 * @return View type that used in {@link #onProvideItemLayoutId(int)}. 513 */ 514 public int getItemViewType(GuidedAction action) { 515 if (action instanceof GuidedDatePickerAction) { 516 return VIEW_TYPE_DATE_PICKER; 517 } 518 return VIEW_TYPE_DEFAULT; 519 } 520 521 /** 522 * Provides the resource ID of the layout defining the view for an individual guided actions. 523 * Subclasses may override to provide their own customized layouts. The base implementation 524 * returns {@link android.support.v17.leanback.R.layout#lb_guidedactions_item}. If overridden, 525 * the substituted layout should contain matching IDs for any views that should be managed by 526 * the base class; this can be achieved by starting with a copy of the base layout file. Note 527 * that in order for the item to support editing, the title view should both subclass {@link 528 * android.widget.EditText} and implement {@link ImeKeyMonitor}; see {@link 529 * GuidedActionEditText}. To support different types of Layouts, override {@link 530 * #onProvideItemLayoutId(int)}. 531 * @return The resource ID of the layout to be inflated to define the view to display an 532 * individual GuidedAction. 533 */ 534 public int onProvideItemLayoutId() { 535 return R.layout.lb_guidedactions_item; 536 } 537 538 /** 539 * Provides the resource ID of the layout defining the view for an individual guided actions. 540 * Subclasses may override to provide their own customized layouts. The base implementation 541 * supports: 542 * <li>{@link android.support.v17.leanback.R.layout#lb_guidedactions_item} 543 * <li>{{@link android.support.v17.leanback.R.layout#lb_guidedactions_datepicker_item}. If 544 * overridden, the substituted layout should contain matching IDs for any views that should be 545 * managed by the base class; this can be achieved by starting with a copy of the base layout 546 * file. Note that in order for the item to support editing, the title view should both subclass 547 * {@link android.widget.EditText} and implement {@link ImeKeyMonitor}; see 548 * {@link GuidedActionEditText}. 549 * 550 * @param viewType View type returned by {@link #getItemViewType(GuidedAction)} 551 * @return The resource ID of the layout to be inflated to define the view to display an 552 * individual GuidedAction. 553 */ 554 public int onProvideItemLayoutId(int viewType) { 555 if (viewType == VIEW_TYPE_DEFAULT) { 556 return onProvideItemLayoutId(); 557 } else if (viewType == VIEW_TYPE_DATE_PICKER) { 558 return R.layout.lb_guidedactions_datepicker_item; 559 } else { 560 throw new RuntimeException("ViewType " + viewType + 561 " not supported in GuidedActionsStylist"); 562 } 563 } 564 565 /** 566 * Constructs a {@link ViewHolder} capable of representing {@link GuidedAction}s. Subclasses 567 * may choose to return a subclass of ViewHolder. To support different view types, override 568 * {@link #onCreateViewHolder(ViewGroup, int)} 569 * <p> 570 * <i>Note: Should not actually add the created view to the parent; the caller will do 571 * this.</i> 572 * @param parent The view group to be used as the parent of the new view. 573 * @return The view to be added to the caller's view hierarchy. 574 */ 575 public ViewHolder onCreateViewHolder(ViewGroup parent) { 576 LayoutInflater inflater = LayoutInflater.from(parent.getContext()); 577 View v = inflater.inflate(onProvideItemLayoutId(), parent, false); 578 return new ViewHolder(v, parent == mSubActionsGridView); 579 } 580 581 /** 582 * Constructs a {@link ViewHolder} capable of representing {@link GuidedAction}s. Subclasses 583 * may choose to return a subclass of ViewHolder. 584 * <p> 585 * <i>Note: Should not actually add the created view to the parent; the caller will do 586 * this.</i> 587 * @param parent The view group to be used as the parent of the new view. 588 * @param viewType The viewType returned by {@link #getItemViewType(GuidedAction)} 589 * @return The view to be added to the caller's view hierarchy. 590 */ 591 public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 592 if (viewType == VIEW_TYPE_DEFAULT) { 593 return onCreateViewHolder(parent); 594 } 595 LayoutInflater inflater = LayoutInflater.from(parent.getContext()); 596 View v = inflater.inflate(onProvideItemLayoutId(viewType), parent, false); 597 return new ViewHolder(v, parent == mSubActionsGridView); 598 } 599 600 /** 601 * Binds a {@link ViewHolder} to a particular {@link GuidedAction}. 602 * @param vh The view holder to be associated with the given action. 603 * @param action The guided action to be displayed by the view holder's view. 604 * @return The view to be added to the caller's view hierarchy. 605 */ 606 public void onBindViewHolder(ViewHolder vh, GuidedAction action) { 607 608 vh.mAction = action; 609 if (vh.mTitleView != null) { 610 vh.mTitleView.setText(action.getTitle()); 611 vh.mTitleView.setAlpha(action.isEnabled() ? mEnabledTextAlpha : mDisabledTextAlpha); 612 vh.mTitleView.setFocusable(false); 613 vh.mTitleView.setClickable(false); 614 vh.mTitleView.setLongClickable(false); 615 } 616 if (vh.mDescriptionView != null) { 617 vh.mDescriptionView.setText(action.getDescription()); 618 vh.mDescriptionView.setVisibility(TextUtils.isEmpty(action.getDescription()) ? 619 View.GONE : View.VISIBLE); 620 vh.mDescriptionView.setAlpha(action.isEnabled() ? mEnabledDescriptionAlpha : 621 mDisabledDescriptionAlpha); 622 vh.mDescriptionView.setFocusable(false); 623 vh.mDescriptionView.setClickable(false); 624 vh.mDescriptionView.setLongClickable(false); 625 } 626 // Clients might want the check mark view to be gone entirely, in which case, ignore it. 627 if (vh.mCheckmarkView != null) { 628 onBindCheckMarkView(vh, action); 629 } 630 setIcon(vh.mIconView, action); 631 632 if (action.hasMultilineDescription()) { 633 if (vh.mTitleView != null) { 634 setMaxLines(vh.mTitleView, mTitleMaxLines); 635 if (vh.mDescriptionView != null) { 636 vh.mDescriptionView.setMaxHeight(getDescriptionMaxHeight( 637 vh.itemView.getContext(), vh.mTitleView)); 638 } 639 } 640 } else { 641 if (vh.mTitleView != null) { 642 setMaxLines(vh.mTitleView, mTitleMinLines); 643 } 644 if (vh.mDescriptionView != null) { 645 setMaxLines(vh.mDescriptionView, mDescriptionMinLines); 646 } 647 } 648 if (vh.mActivatorView != null) { 649 onBindActivatorView(vh, action); 650 } 651 setEditingMode(vh, action, false); 652 if (action.isFocusable()) { 653 vh.itemView.setFocusable(true); 654 ((ViewGroup) vh.itemView).setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); 655 } else { 656 vh.itemView.setFocusable(false); 657 ((ViewGroup) vh.itemView).setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); 658 } 659 setupImeOptions(vh, action); 660 661 updateChevronAndVisibility(vh); 662 } 663 664 private static void setMaxLines(TextView view, int maxLines) { 665 // setSingleLine must be called before setMaxLines because it resets maximum to 666 // Integer.MAX_VALUE. 667 if (maxLines == 1) { 668 view.setSingleLine(true); 669 } else { 670 view.setSingleLine(false); 671 view.setMaxLines(maxLines); 672 } 673 } 674 675 /** 676 * Called by {@link #onBindViewHolder(ViewHolder, GuidedAction)} to setup IME options. Default 677 * implementation assigns {@link EditorInfo#IME_ACTION_DONE}. Subclass may override. 678 * @param vh The view holder to be associated with the given action. 679 * @param action The guided action to be displayed by the view holder's view. 680 */ 681 protected void setupImeOptions(ViewHolder vh, GuidedAction action) { 682 setupNextImeOptions(vh.getEditableTitleView()); 683 setupNextImeOptions(vh.getEditableDescriptionView()); 684 } 685 686 private void setupNextImeOptions(EditText edit) { 687 if (edit != null) { 688 edit.setImeOptions(EditorInfo.IME_ACTION_NEXT); 689 } 690 } 691 692 public void setEditingMode(ViewHolder vh, GuidedAction action, boolean editing) { 693 if (editing != vh.isInEditing() && !isInExpandTransition()) { 694 onEditingModeChange(vh, action, editing); 695 } 696 } 697 698 protected void onEditingModeChange(ViewHolder vh, GuidedAction action, boolean editing) { 699 action = vh.getAction(); 700 TextView titleView = vh.getTitleView(); 701 TextView descriptionView = vh.getDescriptionView(); 702 if (editing) { 703 CharSequence editTitle = action.getEditTitle(); 704 if (titleView != null && editTitle != null) { 705 titleView.setText(editTitle); 706 } 707 CharSequence editDescription = action.getEditDescription(); 708 if (descriptionView != null && editDescription != null) { 709 descriptionView.setText(editDescription); 710 } 711 if (action.isDescriptionEditable()) { 712 if (descriptionView != null) { 713 descriptionView.setVisibility(View.VISIBLE); 714 descriptionView.setInputType(action.getDescriptionEditInputType()); 715 } 716 vh.mEditingMode = EDITING_DESCRIPTION; 717 } else if (action.isEditable()){ 718 if (titleView != null) { 719 titleView.setInputType(action.getEditInputType()); 720 } 721 vh.mEditingMode = EDITING_TITLE; 722 } else if (vh.mActivatorView != null) { 723 onEditActivatorView(vh, action, editing); 724 vh.mEditingMode = EDITING_ACTIVATOR_VIEW; 725 } 726 } else { 727 if (titleView != null) { 728 titleView.setText(action.getTitle()); 729 } 730 if (descriptionView != null) { 731 descriptionView.setText(action.getDescription()); 732 } 733 if (vh.mEditingMode == EDITING_DESCRIPTION) { 734 if (descriptionView != null) { 735 descriptionView.setVisibility(TextUtils.isEmpty(action.getDescription()) ? 736 View.GONE : View.VISIBLE); 737 descriptionView.setInputType(action.getDescriptionInputType()); 738 } 739 } else if (vh.mEditingMode == EDITING_TITLE) { 740 if (titleView != null) { 741 titleView.setInputType(action.getInputType()); 742 } 743 } else if (vh.mEditingMode == EDITING_ACTIVATOR_VIEW) { 744 if (vh.mActivatorView != null) { 745 onEditActivatorView(vh, action, editing); 746 } 747 } 748 vh.mEditingMode = EDITING_NONE; 749 } 750 } 751 752 /** 753 * Animates the view holder's view (or subviews thereof) when the action has had its focus 754 * state changed. 755 * @param vh The view holder associated with the relevant action. 756 * @param focused True if the action has become focused, false if it has lost focus. 757 */ 758 public void onAnimateItemFocused(ViewHolder vh, boolean focused) { 759 // No animations for this, currently, because the animation is done on 760 // mSelectorView 761 } 762 763 /** 764 * Animates the view holder's view (or subviews thereof) when the action has had its press 765 * state changed. 766 * @param vh The view holder associated with the relevant action. 767 * @param pressed True if the action has been pressed, false if it has been unpressed. 768 */ 769 public void onAnimateItemPressed(ViewHolder vh, boolean pressed) { 770 int attr = pressed ? R.attr.guidedActionPressedAnimation : 771 R.attr.guidedActionUnpressedAnimation; 772 createAnimator(vh.itemView, attr).start(); 773 } 774 775 /** 776 * Resets the view holder's view to unpressed state. 777 * @param vh The view holder associated with the relevant action. 778 */ 779 public void onAnimateItemPressedCancelled(ViewHolder vh) { 780 createAnimator(vh.itemView, R.attr.guidedActionUnpressedAnimation).end(); 781 } 782 783 /** 784 * Animates the view holder's view (or subviews thereof) when the action has had its check state 785 * changed. Default implementation calls setChecked() if {@link ViewHolder#getCheckmarkView()} 786 * is instance of {@link Checkable}. 787 * 788 * @param vh The view holder associated with the relevant action. 789 * @param checked True if the action has become checked, false if it has become unchecked. 790 * @see #onBindCheckMarkView(ViewHolder, GuidedAction) 791 */ 792 public void onAnimateItemChecked(ViewHolder vh, boolean checked) { 793 if (vh.mCheckmarkView instanceof Checkable) { 794 ((Checkable) vh.mCheckmarkView).setChecked(checked); 795 } 796 } 797 798 /** 799 * Sets states of check mark view, called by {@link #onBindViewHolder(ViewHolder, GuidedAction)} 800 * when action's checkset Id is other than {@link GuidedAction#NO_CHECK_SET}. Default 801 * implementation assigns drawable loaded from theme attribute 802 * {@link android.R.attr#listChoiceIndicatorMultiple} for checkbox or 803 * {@link android.R.attr#listChoiceIndicatorSingle} for radio button. Subclass rarely needs 804 * override the method, instead app can provide its own drawable that supports transition 805 * animations, change theme attributes {@link android.R.attr#listChoiceIndicatorMultiple} and 806 * {@link android.R.attr#listChoiceIndicatorSingle} in {android.support.v17.leanback.R. 807 * styleable#LeanbackGuidedStepTheme}. 808 * 809 * @param vh The view holder associated with the relevant action. 810 * @param action The GuidedAction object to bind to. 811 * @see #onAnimateItemChecked(ViewHolder, boolean) 812 */ 813 public void onBindCheckMarkView(ViewHolder vh, GuidedAction action) { 814 if (action.getCheckSetId() != GuidedAction.NO_CHECK_SET) { 815 vh.mCheckmarkView.setVisibility(View.VISIBLE); 816 int attrId = action.getCheckSetId() == GuidedAction.CHECKBOX_CHECK_SET_ID ? 817 android.R.attr.listChoiceIndicatorMultiple : 818 android.R.attr.listChoiceIndicatorSingle; 819 final Context context = vh.mCheckmarkView.getContext(); 820 Drawable drawable = null; 821 TypedValue typedValue = new TypedValue(); 822 if (context.getTheme().resolveAttribute(attrId, typedValue, true)) { 823 drawable = ContextCompat.getDrawable(context, typedValue.resourceId); 824 } 825 vh.mCheckmarkView.setImageDrawable(drawable); 826 if (vh.mCheckmarkView instanceof Checkable) { 827 ((Checkable) vh.mCheckmarkView).setChecked(action.isChecked()); 828 } 829 } else { 830 vh.mCheckmarkView.setVisibility(View.GONE); 831 } 832 } 833 834 /** 835 * Performs binding activator view value to action. Default implementation supports 836 * GuidedDatePickerAction, subclass may override to add support of other views. 837 * @param vh ViewHolder of activator view. 838 * @param action GuidedAction to bind. 839 */ 840 public void onBindActivatorView(ViewHolder vh, GuidedAction action) { 841 if (action instanceof GuidedDatePickerAction) { 842 GuidedDatePickerAction dateAction = (GuidedDatePickerAction) action; 843 DatePicker dateView = (DatePicker) vh.mActivatorView; 844 dateView.setDatePickerFormat(dateAction.getDatePickerFormat()); 845 if (dateAction.getMinDate() != Long.MIN_VALUE) { 846 dateView.setMinDate(dateAction.getMinDate()); 847 } 848 if (dateAction.getMaxDate() != Long.MAX_VALUE) { 849 dateView.setMaxDate(dateAction.getMaxDate()); 850 } 851 Calendar c = Calendar.getInstance(); 852 c.setTimeInMillis(dateAction.getDate()); 853 dateView.updateDate(c.get(Calendar.YEAR), c.get(Calendar.MONTH), 854 c.get(Calendar.DAY_OF_MONTH), false); 855 } 856 } 857 858 /** 859 * Performs updating GuidedAction from activator view. Default implementation supports 860 * GuidedDatePickerAction, subclass may override to add support of other views. 861 * @param vh ViewHolder of activator view. 862 * @param action GuidedAction to update. 863 * @return True if value has been updated, false otherwise. 864 */ 865 public boolean onUpdateActivatorView(ViewHolder vh, GuidedAction action) { 866 if (action instanceof GuidedDatePickerAction) { 867 GuidedDatePickerAction dateAction = (GuidedDatePickerAction) action; 868 DatePicker dateView = (DatePicker) vh.mActivatorView; 869 if (dateAction.getDate() != dateView.getDate()) { 870 dateAction.setDate(dateView.getDate()); 871 return true; 872 } 873 } 874 return false; 875 } 876 877 /** 878 * Sets listener for reporting view being edited. 879 * @hide 880 */ 881 public void setEditListener(EditListener listener) { 882 mEditListener = listener; 883 } 884 885 void onEditActivatorView(final ViewHolder vh, final GuidedAction action, 886 boolean editing) { 887 if (editing) { 888 vh.itemView.setFocusable(false); 889 vh.mActivatorView.requestFocus(); 890 setExpandedViewHolder(vh); 891 vh.mActivatorView.setOnClickListener(new View.OnClickListener() { 892 @Override 893 public void onClick(View v) { 894 if (!isInExpandTransition()) { 895 setEditingMode(vh, action, false); 896 } 897 } 898 }); 899 } else { 900 if (onUpdateActivatorView(vh, action)) { 901 if (mEditListener != null) { 902 mEditListener.onGuidedActionEdited(action); 903 } 904 } 905 vh.itemView.setFocusable(true); 906 vh.itemView.requestFocus(); 907 setExpandedViewHolder(null); 908 vh.mActivatorView.setOnClickListener(null); 909 vh.mActivatorView.setClickable(false); 910 } 911 } 912 913 /** 914 * Sets states of chevron view, called by {@link #onBindViewHolder(ViewHolder, GuidedAction)}. 915 * Subclass may override. 916 * 917 * @param vh The view holder associated with the relevant action. 918 * @param action The GuidedAction object to bind to. 919 */ 920 public void onBindChevronView(ViewHolder vh, GuidedAction action) { 921 final boolean hasNext = action.hasNext(); 922 final boolean hasSubActions = action.hasSubActions(); 923 if (hasNext || hasSubActions) { 924 vh.mChevronView.setVisibility(View.VISIBLE); 925 vh.mChevronView.setAlpha(action.isEnabled() ? mEnabledChevronAlpha : 926 mDisabledChevronAlpha); 927 if (hasNext) { 928 float r = mMainView != null 929 && mMainView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? 180f : 0f; 930 vh.mChevronView.setRotation(r); 931 } else if (action == mExpandedAction) { 932 vh.mChevronView.setRotation(270); 933 } else { 934 vh.mChevronView.setRotation(90); 935 } 936 } else { 937 vh.mChevronView.setVisibility(View.GONE); 938 939 } 940 } 941 942 /** 943 * Expands or collapse the sub actions list view. 944 * @param avh When not null, fill sub actions list of this ViewHolder into sub actions list and 945 * hide the other items in main list. When null, collapse the sub actions list. 946 */ 947 public void setExpandedViewHolder(ViewHolder avh) { 948 if (isInExpandTransition()) { 949 return; 950 } 951 if (isExpandTransitionSupported()) { 952 startExpandedTransition(avh); 953 } else { 954 onUpdateExpandedViewHolder(avh); 955 } 956 } 957 958 /** 959 * Returns true if it is running an expanding or collapsing transition, false otherwise. 960 * @return True if it is running an expanding or collapsing transition, false otherwise. 961 */ 962 public boolean isInExpandTransition() { 963 return mExpandTransition != null; 964 } 965 966 /** 967 * Returns if expand/collapse animation is supported. When this method returns true, 968 * {@link #startExpandedTransition(ViewHolder)} will be used. When this method returns false, 969 * {@link #onUpdateExpandedViewHolder(ViewHolder)} will be called. 970 * @return True if it is running an expanding or collapsing transition, false otherwise. 971 */ 972 public boolean isExpandTransitionSupported() { 973 return VERSION.SDK_INT >= 21; 974 } 975 976 /** 977 * Start transition to expand or collapse GuidedActionStylist. 978 * @param avh When not null, the GuidedActionStylist expands the sub actions of avh. When null 979 * the GuidedActionStylist will collapse sub actions. 980 */ 981 public void startExpandedTransition(ViewHolder avh) { 982 ViewHolder focusAvh = null; // expand / collapse view holder 983 final int count = mActionsGridView.getChildCount(); 984 for (int i = 0; i < count; i++) { 985 ViewHolder vh = (ViewHolder) mActionsGridView 986 .getChildViewHolder(mActionsGridView.getChildAt(i)); 987 if (avh == null && vh.itemView.getVisibility() == View.VISIBLE) { 988 // going to collapse this one. 989 focusAvh = vh; 990 break; 991 } else if (avh != null && vh.getAction() == avh.getAction()) { 992 // going to expand this one. 993 focusAvh = vh; 994 break; 995 } 996 } 997 if (focusAvh == null) { 998 // huh? 999 onUpdateExpandedViewHolder(avh); 1000 return; 1001 } 1002 boolean isSubActionTransition = focusAvh.getAction().hasSubActions(); 1003 Object set = TransitionHelper.createTransitionSet(false); 1004 float slideDistance = isSubActionTransition ? focusAvh.itemView.getHeight() : 1005 focusAvh.itemView.getHeight() * 0.5f; 1006 Object slideAndFade = TransitionHelper.createFadeAndShortSlide(Gravity.TOP | Gravity.BOTTOM, 1007 slideDistance); 1008 Object changeFocusItemTransform = TransitionHelper.createChangeTransform(); 1009 Object changeFocusItemBounds = TransitionHelper.createChangeBounds(false); 1010 Object fade = TransitionHelper.createFadeTransition(TransitionHelper.FADE_IN | 1011 TransitionHelper.FADE_OUT); 1012 Object changeGridBounds = TransitionHelper.createChangeBounds(false); 1013 if (avh == null) { 1014 TransitionHelper.setStartDelay(slideAndFade, 150); 1015 TransitionHelper.setStartDelay(changeFocusItemTransform, 100); 1016 TransitionHelper.setStartDelay(changeFocusItemBounds, 100); 1017 } else { 1018 TransitionHelper.setStartDelay(fade, 100); 1019 TransitionHelper.setStartDelay(changeGridBounds, 100); 1020 TransitionHelper.setStartDelay(changeFocusItemTransform, 50); 1021 TransitionHelper.setStartDelay(changeFocusItemBounds, 50); 1022 } 1023 for (int i = 0; i < count; i++) { 1024 ViewHolder vh = (ViewHolder) mActionsGridView 1025 .getChildViewHolder(mActionsGridView.getChildAt(i)); 1026 if (vh == focusAvh) { 1027 // going to expand/collapse this one. 1028 if (isSubActionTransition) { 1029 TransitionHelper.include(changeFocusItemTransform, vh.itemView); 1030 TransitionHelper.include(changeFocusItemBounds, vh.itemView); 1031 } 1032 } else { 1033 // going to slide this item to top / bottom. 1034 TransitionHelper.include(slideAndFade, vh.itemView); 1035 TransitionHelper.exclude(fade, vh.itemView, true); 1036 } 1037 } 1038 TransitionHelper.include(changeGridBounds, mSubActionsGridView); 1039 TransitionHelper.addTransition(set, slideAndFade); 1040 // note that we don't run ChangeBounds for activating view due to the rounding problem 1041 // of multiple level views ChangeBounds animation causing vertical jittering. 1042 if (isSubActionTransition) { 1043 TransitionHelper.addTransition(set, changeFocusItemTransform); 1044 TransitionHelper.addTransition(set, changeFocusItemBounds); 1045 } 1046 TransitionHelper.addTransition(set, fade); 1047 TransitionHelper.addTransition(set, changeGridBounds); 1048 mExpandTransition = set; 1049 TransitionHelper.addTransitionListener(mExpandTransition, new TransitionListener() { 1050 @Override 1051 public void onTransitionEnd(Object transition) { 1052 mExpandTransition = null; 1053 } 1054 }); 1055 if (avh != null && mSubActionsGridView.getTop() != avh.itemView.getTop()) { 1056 // For expanding, set the initial position of subActionsGridView before running 1057 // a ChangeBounds on it. 1058 final ViewHolder toUpdate = avh; 1059 mSubActionsGridView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { 1060 @Override 1061 public void onLayoutChange(View v, int left, int top, int right, int bottom, 1062 int oldLeft, int oldTop, int oldRight, int oldBottom) { 1063 if (mSubActionsGridView == null) { 1064 return; 1065 } 1066 mSubActionsGridView.removeOnLayoutChangeListener(this); 1067 mMainView.post(new Runnable() { 1068 public void run() { 1069 if (mMainView == null) { 1070 return; 1071 } 1072 TransitionHelper.beginDelayedTransition(mMainView, mExpandTransition); 1073 onUpdateExpandedViewHolder(toUpdate); 1074 } 1075 }); 1076 } 1077 }); 1078 ViewGroup.MarginLayoutParams lp = 1079 (ViewGroup.MarginLayoutParams) mSubActionsGridView.getLayoutParams(); 1080 lp.topMargin = avh.itemView.getTop(); 1081 lp.height = 0; 1082 mSubActionsGridView.setLayoutParams(lp); 1083 return; 1084 } 1085 TransitionHelper.beginDelayedTransition(mMainView, mExpandTransition); 1086 onUpdateExpandedViewHolder(avh); 1087 } 1088 1089 /** 1090 * @return True if sub actions list is expanded. 1091 */ 1092 public boolean isSubActionsExpanded() { 1093 return mExpandedAction != null; 1094 } 1095 1096 /** 1097 * @return Current expanded GuidedAction or null if not expanded. 1098 */ 1099 public GuidedAction getExpandedAction() { 1100 return mExpandedAction; 1101 } 1102 1103 /** 1104 * Expand or collapse GuidedActionStylist. 1105 * @param avh When not null, the GuidedActionStylist expands the sub actions of avh. When null 1106 * the GuidedActionStylist will collapse sub actions. 1107 */ 1108 public void onUpdateExpandedViewHolder(ViewHolder avh) { 1109 if (avh == null) { 1110 mExpandedAction = null; 1111 } else if (avh.getAction() != mExpandedAction) { 1112 mExpandedAction = avh.getAction(); 1113 } 1114 // In expanding mode, notifyItemChange on expanded item will reset the translationY by 1115 // the default ItemAnimator. So disable ItemAnimation in expanding mode. 1116 mActionsGridView.setAnimateChildLayout(false); 1117 final int count = mActionsGridView.getChildCount(); 1118 for (int i = 0; i < count; i++) { 1119 ViewHolder vh = (ViewHolder) mActionsGridView 1120 .getChildViewHolder(mActionsGridView.getChildAt(i)); 1121 updateChevronAndVisibility(vh); 1122 } 1123 if (mSubActionsGridView != null) { 1124 if (avh != null && avh.getAction().hasSubActions()) { 1125 ViewGroup.MarginLayoutParams lp = 1126 (ViewGroup.MarginLayoutParams) mSubActionsGridView.getLayoutParams(); 1127 lp.topMargin = avh.itemView.getTop(); 1128 lp.height = ViewGroup.MarginLayoutParams.MATCH_PARENT; 1129 mSubActionsGridView.setLayoutParams(lp); 1130 mSubActionsGridView.setVisibility(View.VISIBLE); 1131 mSubActionsGridView.requestFocus(); 1132 mSubActionsGridView.setSelectedPosition(0); 1133 ((GuidedActionAdapter) mSubActionsGridView.getAdapter()) 1134 .setActions(avh.getAction().getSubActions()); 1135 } else if (mSubActionsGridView.getVisibility() == View.VISIBLE) { 1136 mSubActionsGridView.setVisibility(View.INVISIBLE); 1137 ViewGroup.MarginLayoutParams lp = 1138 (ViewGroup.MarginLayoutParams) mSubActionsGridView.getLayoutParams(); 1139 lp.height = 0; 1140 mSubActionsGridView.setLayoutParams(lp); 1141 ((GuidedActionAdapter) mSubActionsGridView.getAdapter()) 1142 .setActions(Collections.EMPTY_LIST); 1143 mActionsGridView.requestFocus(); 1144 } 1145 } 1146 } 1147 1148 private void updateChevronAndVisibility(ViewHolder vh) { 1149 if (!vh.isSubAction()) { 1150 if (mExpandedAction == null) { 1151 vh.itemView.setVisibility(View.VISIBLE); 1152 vh.itemView.setTranslationY(0); 1153 if (vh.mActivatorView != null) { 1154 vh.setActivated(false); 1155 } 1156 } else if (vh.getAction() == mExpandedAction) { 1157 vh.itemView.setVisibility(View.VISIBLE); 1158 if (vh.getAction().hasSubActions()) { 1159 vh.itemView.setTranslationY(- vh.itemView.getHeight()); 1160 } else if (vh.mActivatorView != null) { 1161 vh.itemView.setTranslationY(0); 1162 vh.setActivated(true); 1163 } 1164 } else { 1165 vh.itemView.setVisibility(View.INVISIBLE); 1166 vh.itemView.setTranslationY(0); 1167 } 1168 } 1169 if (vh.mChevronView != null) { 1170 onBindChevronView(vh, vh.getAction()); 1171 } 1172 } 1173 1174 /* 1175 * ========================================== 1176 * FragmentAnimationProvider overrides 1177 * ========================================== 1178 */ 1179 1180 /** 1181 * {@inheritDoc} 1182 */ 1183 @Override 1184 public void onImeAppearing(@NonNull List<Animator> animators) { 1185 animators.add(createAnimator(mContentView, R.attr.guidedStepImeAppearingAnimation)); 1186 } 1187 1188 /** 1189 * {@inheritDoc} 1190 */ 1191 @Override 1192 public void onImeDisappearing(@NonNull List<Animator> animators) { 1193 animators.add(createAnimator(mContentView, R.attr.guidedStepImeDisappearingAnimation)); 1194 } 1195 1196 /* 1197 * ========================================== 1198 * Private methods 1199 * ========================================== 1200 */ 1201 1202 private float getFloat(Context ctx, TypedValue typedValue, int attrId) { 1203 ctx.getTheme().resolveAttribute(attrId, typedValue, true); 1204 // Android resources don't have a native float type, so we have to use strings. 1205 return Float.valueOf(ctx.getResources().getString(typedValue.resourceId)); 1206 } 1207 1208 private int getInteger(Context ctx, TypedValue typedValue, int attrId) { 1209 ctx.getTheme().resolveAttribute(attrId, typedValue, true); 1210 return ctx.getResources().getInteger(typedValue.resourceId); 1211 } 1212 1213 private int getDimension(Context ctx, TypedValue typedValue, int attrId) { 1214 ctx.getTheme().resolveAttribute(attrId, typedValue, true); 1215 return ctx.getResources().getDimensionPixelSize(typedValue.resourceId); 1216 } 1217 1218 private static Animator createAnimator(View v, int attrId) { 1219 Context ctx = v.getContext(); 1220 TypedValue typedValue = new TypedValue(); 1221 ctx.getTheme().resolveAttribute(attrId, typedValue, true); 1222 Animator animator = AnimatorInflater.loadAnimator(ctx, typedValue.resourceId); 1223 animator.setTarget(v); 1224 return animator; 1225 } 1226 1227 private boolean setIcon(final ImageView iconView, GuidedAction action) { 1228 Drawable icon = null; 1229 if (iconView != null) { 1230 Context context = iconView.getContext(); 1231 icon = action.getIcon(); 1232 if (icon != null) { 1233 // setImageDrawable resets the drawable's level unless we set the view level first. 1234 iconView.setImageLevel(icon.getLevel()); 1235 iconView.setImageDrawable(icon); 1236 iconView.setVisibility(View.VISIBLE); 1237 } else { 1238 iconView.setVisibility(View.GONE); 1239 } 1240 } 1241 return icon != null; 1242 } 1243 1244 /** 1245 * @return the max height in pixels the description can be such that the 1246 * action nicely takes up the entire screen. 1247 */ 1248 private int getDescriptionMaxHeight(Context context, TextView title) { 1249 // The 2 multiplier on the title height calculation is a 1250 // conservative estimate for font padding which can not be 1251 // calculated at this stage since the view hasn't been rendered yet. 1252 return (int)(mDisplayHeight - 2*mVerticalPadding - 2*mTitleMaxLines*title.getLineHeight()); 1253 } 1254 1255} 1256