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