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