1// CHECKSTYLE:OFF Generated code 2/* This file is auto-generated from GuidedStepSupportFragment.java. DO NOT MODIFY. */ 3 4/* 5 * Copyright (C) 2015 The Android Open Source Project 6 * 7 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 8 * in compliance with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software distributed under the License 13 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 14 * or implied. See the License for the specific language governing permissions and limitations under 15 * the License. 16 */ 17package android.support.v17.leanback.app; 18 19import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; 20 21import android.animation.Animator; 22import android.animation.AnimatorSet; 23import android.content.Context; 24import android.os.Build; 25import android.os.Bundle; 26import android.support.annotation.NonNull; 27import android.support.annotation.RestrictTo; 28import android.support.v17.leanback.R; 29import android.support.v17.leanback.transition.TransitionHelper; 30import android.support.v17.leanback.widget.GuidanceStylist; 31import android.support.v17.leanback.widget.GuidanceStylist.Guidance; 32import android.support.v17.leanback.widget.GuidedAction; 33import android.support.v17.leanback.widget.GuidedActionAdapter; 34import android.support.v17.leanback.widget.GuidedActionAdapterGroup; 35import android.support.v17.leanback.widget.GuidedActionsStylist; 36import android.support.v17.leanback.widget.NonOverlappingLinearLayout; 37import android.support.v4.app.ActivityCompat; 38import android.app.Fragment; 39import android.app.Activity; 40import android.app.FragmentManager; 41import android.app.FragmentManager.BackStackEntry; 42import android.app.FragmentTransaction; 43import android.support.v7.widget.RecyclerView; 44import android.util.Log; 45import android.util.TypedValue; 46import android.view.ContextThemeWrapper; 47import android.view.Gravity; 48import android.view.LayoutInflater; 49import android.view.View; 50import android.view.ViewGroup; 51import android.widget.FrameLayout; 52import android.widget.LinearLayout; 53 54import java.util.ArrayList; 55import java.util.List; 56 57/** 58 * A GuidedStepFragment is used to guide the user through a decision or series of decisions. 59 * It is composed of a guidance view on the left and a view on the right containing a list of 60 * possible actions. 61 * <p> 62 * <h3>Basic Usage</h3> 63 * <p> 64 * Clients of GuidedStepFragment must create a custom subclass to attach to their Activities. 65 * This custom subclass provides the information necessary to construct the user interface and 66 * respond to user actions. At a minimum, subclasses should override: 67 * <ul> 68 * <li>{@link #onCreateGuidance}, to provide instructions to the user</li> 69 * <li>{@link #onCreateActions}, to provide a set of {@link GuidedAction}s the user can take</li> 70 * <li>{@link #onGuidedActionClicked}, to respond to those actions</li> 71 * </ul> 72 * <p> 73 * Clients use following helper functions to add GuidedStepFragment to Activity or FragmentManager: 74 * <ul> 75 * <li>{@link #addAsRoot(Activity, GuidedStepFragment, int)}, to be called during Activity onCreate, 76 * adds GuidedStepFragment as the first Fragment in activity.</li> 77 * <li>{@link #add(FragmentManager, GuidedStepFragment)} or {@link #add(FragmentManager, 78 * GuidedStepFragment, int)}, to add GuidedStepFragment on top of existing Fragments or 79 * replacing existing GuidedStepFragment when moving forward to next step.</li> 80 * <li>{@link #finishGuidedStepFragments()} can either finish the activity or pop all 81 * GuidedStepFragment from stack. 82 * <li>If app chooses not to use the helper function, it is the app's responsibility to call 83 * {@link #setUiStyle(int)} to select fragment transition and remember the stack entry where it 84 * need pops to. 85 * </ul> 86 * <h3>Theming and Stylists</h3> 87 * <p> 88 * GuidedStepFragment delegates its visual styling to classes called stylists. The {@link 89 * GuidanceStylist} is responsible for the left guidance view, while the {@link 90 * GuidedActionsStylist} is responsible for the right actions view. The stylists use theme 91 * attributes to derive values associated with the presentation, such as colors, animations, etc. 92 * Most simple visual aspects of GuidanceStylist and GuidedActionsStylist can be customized 93 * via theming; see their documentation for more information. 94 * <p> 95 * GuidedStepFragments must have access to an appropriate theme in order for the stylists to 96 * function properly. Specifically, the fragment must receive {@link 97 * android.support.v17.leanback.R.style#Theme_Leanback_GuidedStep}, or a theme whose parent is 98 * is set to that theme. Themes can be provided in one of three ways: 99 * <ul> 100 * <li>The simplest way is to set the theme for the host Activity to the GuidedStep theme or a 101 * theme that derives from it.</li> 102 * <li>If the Activity already has a theme and setting its parent theme is inconvenient, the 103 * existing Activity theme can have an entry added for the attribute {@link 104 * android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepTheme}. If present, 105 * this theme will be used by GuidedStepFragment as an overlay to the Activity's theme.</li> 106 * <li>Finally, custom subclasses of GuidedStepFragment may provide a theme through the {@link 107 * #onProvideTheme} method. This can be useful if a subclass is used across multiple 108 * Activities.</li> 109 * </ul> 110 * <p> 111 * If the theme is provided in multiple ways, the onProvideTheme override has priority, followed by 112 * the Activity's theme. (Themes whose parent theme is already set to the guided step theme do not 113 * need to set the guidedStepTheme attribute; if set, it will be ignored.) 114 * <p> 115 * If themes do not provide enough customizability, the stylists themselves may be subclassed and 116 * provided to the GuidedStepFragment through the {@link #onCreateGuidanceStylist} and {@link 117 * #onCreateActionsStylist} methods. The stylists have simple hooks so that subclasses 118 * may override layout files; subclasses may also have more complex logic to determine styling. 119 * <p> 120 * <h3>Guided sequences</h3> 121 * <p> 122 * GuidedStepFragments can be grouped together to provide a guided sequence. GuidedStepFragments 123 * grouped as a sequence use custom animations provided by {@link GuidanceStylist} and 124 * {@link GuidedActionsStylist} (or subclasses) during transitions between steps. Clients 125 * should use {@link #add} to place subsequent GuidedFragments onto the fragment stack so that 126 * custom animations are properly configured. (Custom animations are triggered automatically when 127 * the fragment stack is subsequently popped by any normal mechanism.) 128 * <p> 129 * <i>Note: Currently GuidedStepFragments grouped in this way must all be defined programmatically, 130 * rather than in XML. This restriction may be removed in the future.</i> 131 * 132 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepTheme 133 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepBackground 134 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionContentWidthWeight 135 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionContentWidthWeightTwoPanels 136 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsBackground 137 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsBackgroundDark 138 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsElevation 139 * @see GuidanceStylist 140 * @see GuidanceStylist.Guidance 141 * @see GuidedAction 142 * @see GuidedActionsStylist 143 */ 144public class GuidedStepFragment extends Fragment implements GuidedActionAdapter.FocusListener { 145 146 private static final String TAG_LEAN_BACK_ACTIONS_FRAGMENT = "leanBackGuidedStepFragment"; 147 private static final String EXTRA_ACTION_PREFIX = "action_"; 148 private static final String EXTRA_BUTTON_ACTION_PREFIX = "buttonaction_"; 149 150 private static final String ENTRY_NAME_REPLACE = "GuidedStepDefault"; 151 152 private static final String ENTRY_NAME_ENTRANCE = "GuidedStepEntrance"; 153 154 private static final boolean IS_FRAMEWORK_FRAGMENT = true; 155 156 /** 157 * Fragment argument name for UI style. The argument value is persisted in fragment state and 158 * used to select fragment transition. The value is initially {@link #UI_STYLE_ENTRANCE} and 159 * might be changed in one of the three helper functions: 160 * <ul> 161 * <li>{@link #addAsRoot(Activity, GuidedStepFragment, int)} sets to 162 * {@link #UI_STYLE_ACTIVITY_ROOT}</li> 163 * <li>{@link #add(FragmentManager, GuidedStepFragment)} or {@link #add(FragmentManager, 164 * GuidedStepFragment, int)} sets it to {@link #UI_STYLE_REPLACE} if there is already a 165 * GuidedStepFragment on stack.</li> 166 * <li>{@link #finishGuidedStepFragments()} changes current GuidedStepFragment to 167 * {@link #UI_STYLE_ENTRANCE} for the non activity case. This is a special case that changes 168 * the transition settings after fragment has been created, in order to force current 169 * GuidedStepFragment run a return transition of {@link #UI_STYLE_ENTRANCE}</li> 170 * </ul> 171 * <p> 172 * Argument value can be either: 173 * <ul> 174 * <li>{@link #UI_STYLE_REPLACE}</li> 175 * <li>{@link #UI_STYLE_ENTRANCE}</li> 176 * <li>{@link #UI_STYLE_ACTIVITY_ROOT}</li> 177 * </ul> 178 */ 179 public static final String EXTRA_UI_STYLE = "uiStyle"; 180 181 /** 182 * This is the case that we use GuidedStepFragment to replace another existing 183 * GuidedStepFragment when moving forward to next step. Default behavior of this style is: 184 * <ul> 185 * <li>Enter transition slides in from END(right), exit transition same as 186 * {@link #UI_STYLE_ENTRANCE}. 187 * </li> 188 * </ul> 189 */ 190 public static final int UI_STYLE_REPLACE = 0; 191 192 /** 193 * @deprecated Same value as {@link #UI_STYLE_REPLACE}. 194 */ 195 @Deprecated 196 public static final int UI_STYLE_DEFAULT = 0; 197 198 /** 199 * Default value for argument {@link #EXTRA_UI_STYLE}. The default value is assigned in 200 * GuidedStepFragment constructor. This is the case that we show GuidedStepFragment on top of 201 * other content. The default behavior of this style: 202 * <ul> 203 * <li>Enter transition slides in from two sides, exit transition slide out to START(left). 204 * Background will be faded in. Note: Changing exit transition by UI style is not working 205 * because fragment transition asks for exit transition before UI style is restored in Fragment 206 * .onCreate().</li> 207 * </ul> 208 * When popping multiple GuidedStepFragment, {@link #finishGuidedStepFragments()} also changes 209 * the top GuidedStepFragment to UI_STYLE_ENTRANCE in order to run the return transition 210 * (reverse of enter transition) of UI_STYLE_ENTRANCE. 211 */ 212 public static final int UI_STYLE_ENTRANCE = 1; 213 214 /** 215 * One possible value of argument {@link #EXTRA_UI_STYLE}. This is the case that we show first 216 * GuidedStepFragment in a separate activity. The default behavior of this style: 217 * <ul> 218 * <li>Enter transition is assigned null (will rely on activity transition), exit transition is 219 * same as {@link #UI_STYLE_ENTRANCE}. Note: Changing exit transition by UI style is not working 220 * because fragment transition asks for exit transition before UI style is restored in 221 * Fragment.onCreate().</li> 222 * </ul> 223 */ 224 public static final int UI_STYLE_ACTIVITY_ROOT = 2; 225 226 /** 227 * Animation to slide the contents from the side (left/right). 228 * @hide 229 */ 230 @RestrictTo(LIBRARY_GROUP) 231 public static final int SLIDE_FROM_SIDE = 0; 232 233 /** 234 * Animation to slide the contents from the bottom. 235 * @hide 236 */ 237 @RestrictTo(LIBRARY_GROUP) 238 public static final int SLIDE_FROM_BOTTOM = 1; 239 240 private static final String TAG = "GuidedStepF"; 241 private static final boolean DEBUG = false; 242 243 /** 244 * @hide 245 */ 246 @RestrictTo(LIBRARY_GROUP) 247 public static class DummyFragment extends Fragment { 248 @Override 249 public View onCreateView(LayoutInflater inflater, ViewGroup container, 250 Bundle savedInstanceState) { 251 final View v = new View(inflater.getContext()); 252 v.setVisibility(View.GONE); 253 return v; 254 } 255 } 256 257 private ContextThemeWrapper mThemeWrapper; 258 private GuidanceStylist mGuidanceStylist; 259 GuidedActionsStylist mActionsStylist; 260 private GuidedActionsStylist mButtonActionsStylist; 261 private GuidedActionAdapter mAdapter; 262 private GuidedActionAdapter mSubAdapter; 263 private GuidedActionAdapter mButtonAdapter; 264 private GuidedActionAdapterGroup mAdapterGroup; 265 private List<GuidedAction> mActions = new ArrayList<GuidedAction>(); 266 private List<GuidedAction> mButtonActions = new ArrayList<GuidedAction>(); 267 private int entranceTransitionType = SLIDE_FROM_SIDE; 268 269 public GuidedStepFragment() { 270 mGuidanceStylist = onCreateGuidanceStylist(); 271 mActionsStylist = onCreateActionsStylist(); 272 mButtonActionsStylist = onCreateButtonActionsStylist(); 273 onProvideFragmentTransitions(); 274 } 275 276 /** 277 * Creates the presenter used to style the guidance panel. The default implementation returns 278 * a basic GuidanceStylist. 279 * @return The GuidanceStylist used in this fragment. 280 */ 281 public GuidanceStylist onCreateGuidanceStylist() { 282 return new GuidanceStylist(); 283 } 284 285 /** 286 * Creates the presenter used to style the guided actions panel. The default implementation 287 * returns a basic GuidedActionsStylist. 288 * @return The GuidedActionsStylist used in this fragment. 289 */ 290 public GuidedActionsStylist onCreateActionsStylist() { 291 return new GuidedActionsStylist(); 292 } 293 294 /** 295 * Creates the presenter used to style a sided actions panel for button only. 296 * The default implementation returns a basic GuidedActionsStylist. 297 * @return The GuidedActionsStylist used in this fragment. 298 */ 299 public GuidedActionsStylist onCreateButtonActionsStylist() { 300 GuidedActionsStylist stylist = new GuidedActionsStylist(); 301 stylist.setAsButtonActions(); 302 return stylist; 303 } 304 305 /** 306 * Returns the theme used for styling the fragment. The default returns -1, indicating that the 307 * host Activity's theme should be used. 308 * @return The theme resource ID of the theme to use in this fragment, or -1 to use the 309 * host Activity's theme. 310 */ 311 public int onProvideTheme() { 312 return -1; 313 } 314 315 /** 316 * Returns the information required to provide guidance to the user. This hook is called during 317 * {@link #onCreateView}. May be overridden to return a custom subclass of {@link 318 * GuidanceStylist.Guidance} for use in a subclass of {@link GuidanceStylist}. The default 319 * returns a Guidance object with empty fields; subclasses should override. 320 * @param savedInstanceState The saved instance state from onCreateView. 321 * @return The Guidance object representing the information used to guide the user. 322 */ 323 public @NonNull Guidance onCreateGuidance(Bundle savedInstanceState) { 324 return new Guidance("", "", "", null); 325 } 326 327 /** 328 * Fills out the set of actions available to the user. This hook is called during {@link 329 * #onCreate}. The default leaves the list of actions empty; subclasses should override. 330 * @param actions A non-null, empty list ready to be populated. 331 * @param savedInstanceState The saved instance state from onCreate. 332 */ 333 public void onCreateActions(@NonNull List<GuidedAction> actions, Bundle savedInstanceState) { 334 } 335 336 /** 337 * Fills out the set of actions shown at right available to the user. This hook is called during 338 * {@link #onCreate}. The default leaves the list of actions empty; subclasses may override. 339 * @param actions A non-null, empty list ready to be populated. 340 * @param savedInstanceState The saved instance state from onCreate. 341 */ 342 public void onCreateButtonActions(@NonNull List<GuidedAction> actions, 343 Bundle savedInstanceState) { 344 } 345 346 /** 347 * Callback invoked when an action is taken by the user. Subclasses should override in 348 * order to act on the user's decisions. 349 * @param action The chosen action. 350 */ 351 public void onGuidedActionClicked(GuidedAction action) { 352 } 353 354 /** 355 * Callback invoked when an action in sub actions is taken by the user. Subclasses should 356 * override in order to act on the user's decisions. Default return value is true to close 357 * the sub actions list. 358 * @param action The chosen action. 359 * @return true to collapse the sub actions list, false to keep it expanded. 360 */ 361 public boolean onSubGuidedActionClicked(GuidedAction action) { 362 return true; 363 } 364 365 /** 366 * @return True if is current expanded including subactions list or 367 * action with {@link GuidedAction#hasEditableActivatorView()} is true. 368 */ 369 public boolean isExpanded() { 370 return mActionsStylist.isExpanded(); 371 } 372 373 /** 374 * @return True if the sub actions list is expanded, false otherwise. 375 */ 376 public boolean isSubActionsExpanded() { 377 return mActionsStylist.isSubActionsExpanded(); 378 } 379 380 /** 381 * Expand a given action's sub actions list. 382 * @param action GuidedAction to expand. 383 * @see #expandAction(GuidedAction, boolean) 384 */ 385 public void expandSubActions(GuidedAction action) { 386 if (!action.hasSubActions()) { 387 return; 388 } 389 expandAction(action, true); 390 } 391 392 /** 393 * Expand a given action with sub actions list or 394 * {@link GuidedAction#hasEditableActivatorView()} is true. The method must be called after 395 * {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)} creates fragment view. 396 * 397 * @param action GuidedAction to expand. 398 * @param withTransition True to run transition animation, false otherwise. 399 */ 400 public void expandAction(GuidedAction action, boolean withTransition) { 401 mActionsStylist.expandAction(action, withTransition); 402 } 403 404 /** 405 * Collapse sub actions list. 406 * @see GuidedAction#getSubActions() 407 */ 408 public void collapseSubActions() { 409 collapseAction(true); 410 } 411 412 /** 413 * Collapse action which either has a sub actions list or action with 414 * {@link GuidedAction#hasEditableActivatorView()} is true. 415 * 416 * @param withTransition True to run transition animation, false otherwise. 417 */ 418 public void collapseAction(boolean withTransition) { 419 if (mActionsStylist != null && mActionsStylist.getActionsGridView() != null) { 420 mActionsStylist.collapseAction(withTransition); 421 } 422 } 423 424 /** 425 * Callback invoked when an action is focused (made to be the current selection) by the user. 426 */ 427 @Override 428 public void onGuidedActionFocused(GuidedAction action) { 429 } 430 431 /** 432 * Callback invoked when an action's title or description has been edited, this happens either 433 * when user clicks confirm button in IME or user closes IME window by BACK key. 434 * @deprecated Override {@link #onGuidedActionEditedAndProceed(GuidedAction)} and/or 435 * {@link #onGuidedActionEditCanceled(GuidedAction)}. 436 */ 437 @Deprecated 438 public void onGuidedActionEdited(GuidedAction action) { 439 } 440 441 /** 442 * Callback invoked when an action has been canceled editing, for example when user closes 443 * IME window by BACK key. Default implementation calls deprecated method 444 * {@link #onGuidedActionEdited(GuidedAction)}. 445 * @param action The action which has been canceled editing. 446 */ 447 public void onGuidedActionEditCanceled(GuidedAction action) { 448 onGuidedActionEdited(action); 449 } 450 451 /** 452 * Callback invoked when an action has been edited, for example when user clicks confirm button 453 * in IME window. Default implementation calls deprecated method 454 * {@link #onGuidedActionEdited(GuidedAction)} and returns {@link GuidedAction#ACTION_ID_NEXT}. 455 * 456 * @param action The action that has been edited. 457 * @return ID of the action will be focused or {@link GuidedAction#ACTION_ID_NEXT}, 458 * {@link GuidedAction#ACTION_ID_CURRENT}. 459 */ 460 public long onGuidedActionEditedAndProceed(GuidedAction action) { 461 onGuidedActionEdited(action); 462 return GuidedAction.ACTION_ID_NEXT; 463 } 464 465 /** 466 * Adds the specified GuidedStepFragment to the fragment stack, replacing any existing 467 * GuidedStepFragments in the stack, and configuring the fragment-to-fragment custom 468 * transitions. A backstack entry is added, so the fragment will be dismissed when BACK key 469 * is pressed. 470 * <li>If current fragment on stack is GuidedStepFragment: assign {@link #UI_STYLE_REPLACE} 471 * <li>If current fragment on stack is not GuidedStepFragment: assign {@link #UI_STYLE_ENTRANCE} 472 * <p> 473 * Note: currently fragments added using this method must be created programmatically rather 474 * than via XML. 475 * @param fragmentManager The FragmentManager to be used in the transaction. 476 * @param fragment The GuidedStepFragment to be inserted into the fragment stack. 477 * @return The ID returned by the call FragmentTransaction.commit. 478 */ 479 public static int add(FragmentManager fragmentManager, GuidedStepFragment fragment) { 480 return add(fragmentManager, fragment, android.R.id.content); 481 } 482 483 /** 484 * Adds the specified GuidedStepFragment to the fragment stack, replacing any existing 485 * GuidedStepFragments in the stack, and configuring the fragment-to-fragment custom 486 * transitions. A backstack entry is added, so the fragment will be dismissed when BACK key 487 * is pressed. 488 * <li>If current fragment on stack is GuidedStepFragment: assign {@link #UI_STYLE_REPLACE} and 489 * {@link #onAddSharedElementTransition(FragmentTransaction, GuidedStepFragment)} will be called 490 * to perform shared element transition between GuidedStepFragments. 491 * <li>If current fragment on stack is not GuidedStepFragment: assign {@link #UI_STYLE_ENTRANCE} 492 * <p> 493 * Note: currently fragments added using this method must be created programmatically rather 494 * than via XML. 495 * @param fragmentManager The FragmentManager to be used in the transaction. 496 * @param fragment The GuidedStepFragment to be inserted into the fragment stack. 497 * @param id The id of container to add GuidedStepFragment, can be android.R.id.content. 498 * @return The ID returned by the call FragmentTransaction.commit. 499 */ 500 public static int add(FragmentManager fragmentManager, GuidedStepFragment fragment, int id) { 501 GuidedStepFragment current = getCurrentGuidedStepFragment(fragmentManager); 502 boolean inGuidedStep = current != null; 503 if (IS_FRAMEWORK_FRAGMENT && Build.VERSION.SDK_INT >= 21 && Build.VERSION.SDK_INT < 23 504 && !inGuidedStep) { 505 // workaround b/22631964 for framework fragment 506 fragmentManager.beginTransaction() 507 .replace(id, new DummyFragment(), TAG_LEAN_BACK_ACTIONS_FRAGMENT) 508 .commit(); 509 } 510 FragmentTransaction ft = fragmentManager.beginTransaction(); 511 512 fragment.setUiStyle(inGuidedStep ? UI_STYLE_REPLACE : UI_STYLE_ENTRANCE); 513 ft.addToBackStack(fragment.generateStackEntryName()); 514 if (current != null) { 515 fragment.onAddSharedElementTransition(ft, current); 516 } 517 return ft.replace(id, fragment, TAG_LEAN_BACK_ACTIONS_FRAGMENT).commit(); 518 } 519 520 /** 521 * Called when this fragment is added to FragmentTransaction with {@link #UI_STYLE_REPLACE} (aka 522 * when the GuidedStepFragment replacing an existing GuidedStepFragment). Default implementation 523 * establishes connections between action background views to morph action background bounds 524 * change from disappearing GuidedStepFragment into this GuidedStepFragment. The default 525 * implementation heavily relies on {@link GuidedActionsStylist}'s layout, app may override this 526 * method when modifying the default layout of {@link GuidedActionsStylist}. 527 * 528 * @see GuidedActionsStylist 529 * @see #onProvideFragmentTransitions() 530 * @param ft The FragmentTransaction to add shared element. 531 * @param disappearing The disappearing fragment. 532 */ 533 protected void onAddSharedElementTransition(FragmentTransaction ft, GuidedStepFragment 534 disappearing) { 535 View fragmentView = disappearing.getView(); 536 addNonNullSharedElementTransition(ft, fragmentView.findViewById( 537 R.id.action_fragment_root), "action_fragment_root"); 538 addNonNullSharedElementTransition(ft, fragmentView.findViewById( 539 R.id.action_fragment_background), "action_fragment_background"); 540 addNonNullSharedElementTransition(ft, fragmentView.findViewById( 541 R.id.action_fragment), "action_fragment"); 542 addNonNullSharedElementTransition(ft, fragmentView.findViewById( 543 R.id.guidedactions_root), "guidedactions_root"); 544 addNonNullSharedElementTransition(ft, fragmentView.findViewById( 545 R.id.guidedactions_content), "guidedactions_content"); 546 addNonNullSharedElementTransition(ft, fragmentView.findViewById( 547 R.id.guidedactions_list_background), "guidedactions_list_background"); 548 addNonNullSharedElementTransition(ft, fragmentView.findViewById( 549 R.id.guidedactions_root2), "guidedactions_root2"); 550 addNonNullSharedElementTransition(ft, fragmentView.findViewById( 551 R.id.guidedactions_content2), "guidedactions_content2"); 552 addNonNullSharedElementTransition(ft, fragmentView.findViewById( 553 R.id.guidedactions_list_background2), "guidedactions_list_background2"); 554 } 555 556 private static void addNonNullSharedElementTransition (FragmentTransaction ft, View subView, 557 String transitionName) 558 { 559 if (subView != null) 560 TransitionHelper.addSharedElement(ft, subView, transitionName); 561 } 562 563 /** 564 * Returns BackStackEntry name for the GuidedStepFragment or empty String if no entry is 565 * associated. Note {@link #UI_STYLE_ACTIVITY_ROOT} will return empty String. The method 566 * returns undefined value if the fragment is not in FragmentManager. 567 * @return BackStackEntry name for the GuidedStepFragment or empty String if no entry is 568 * associated. 569 */ 570 final String generateStackEntryName() { 571 return generateStackEntryName(getUiStyle(), getClass()); 572 } 573 574 /** 575 * Generates BackStackEntry name for GuidedStepFragment class or empty String if no entry is 576 * associated. Note {@link #UI_STYLE_ACTIVITY_ROOT} is not allowed and returns empty String. 577 * @param uiStyle {@link #UI_STYLE_REPLACE} or {@link #UI_STYLE_ENTRANCE} 578 * @return BackStackEntry name for the GuidedStepFragment or empty String if no entry is 579 * associated. 580 */ 581 static String generateStackEntryName(int uiStyle, Class guidedStepFragmentClass) { 582 switch (uiStyle) { 583 case UI_STYLE_REPLACE: 584 return ENTRY_NAME_REPLACE + guidedStepFragmentClass.getName(); 585 case UI_STYLE_ENTRANCE: 586 return ENTRY_NAME_ENTRANCE + guidedStepFragmentClass.getName(); 587 case UI_STYLE_ACTIVITY_ROOT: 588 default: 589 return ""; 590 } 591 } 592 593 /** 594 * Returns true if the backstack entry represents GuidedStepFragment with 595 * {@link #UI_STYLE_ENTRANCE}, i.e. this is the first GuidedStepFragment pushed to stack; false 596 * otherwise. 597 * @see #generateStackEntryName(int, Class) 598 * @param backStackEntryName Name of BackStackEntry. 599 * @return True if the backstack represents GuidedStepFragment with {@link #UI_STYLE_ENTRANCE}; 600 * false otherwise. 601 */ 602 static boolean isStackEntryUiStyleEntrance(String backStackEntryName) { 603 return backStackEntryName != null && backStackEntryName.startsWith(ENTRY_NAME_ENTRANCE); 604 } 605 606 /** 607 * Extract Class name from BackStackEntry name. 608 * @param backStackEntryName Name of BackStackEntry. 609 * @return Class name of GuidedStepFragment. 610 */ 611 static String getGuidedStepFragmentClassName(String backStackEntryName) { 612 if (backStackEntryName.startsWith(ENTRY_NAME_REPLACE)) { 613 return backStackEntryName.substring(ENTRY_NAME_REPLACE.length()); 614 } else if (backStackEntryName.startsWith(ENTRY_NAME_ENTRANCE)) { 615 return backStackEntryName.substring(ENTRY_NAME_ENTRANCE.length()); 616 } else { 617 return ""; 618 } 619 } 620 621 /** 622 * Adds the specified GuidedStepFragment as content of Activity; no backstack entry is added so 623 * the activity will be dismissed when BACK key is pressed. The method is typically called in 624 * Activity.onCreate() when savedInstanceState is null. When savedInstanceState is not null, 625 * the Activity is being restored, do not call addAsRoot() to duplicate the Fragment restored 626 * by FragmentManager. 627 * {@link #UI_STYLE_ACTIVITY_ROOT} is assigned. 628 * 629 * Note: currently fragments added using this method must be created programmatically rather 630 * than via XML. 631 * @param activity The Activity to be used to insert GuidedstepFragment. 632 * @param fragment The GuidedStepFragment to be inserted into the fragment stack. 633 * @param id The id of container to add GuidedStepFragment, can be android.R.id.content. 634 * @return The ID returned by the call FragmentTransaction.commit, or -1 there is already 635 * GuidedStepFragment. 636 */ 637 public static int addAsRoot(Activity activity, GuidedStepFragment fragment, int id) { 638 // Workaround b/23764120: call getDecorView() to force requestFeature of ActivityTransition. 639 activity.getWindow().getDecorView(); 640 FragmentManager fragmentManager = activity.getFragmentManager(); 641 if (fragmentManager.findFragmentByTag(TAG_LEAN_BACK_ACTIONS_FRAGMENT) != null) { 642 Log.w(TAG, "Fragment is already exists, likely calling " 643 + "addAsRoot() when savedInstanceState is not null in Activity.onCreate()."); 644 return -1; 645 } 646 FragmentTransaction ft = fragmentManager.beginTransaction(); 647 fragment.setUiStyle(UI_STYLE_ACTIVITY_ROOT); 648 return ft.replace(id, fragment, TAG_LEAN_BACK_ACTIONS_FRAGMENT).commit(); 649 } 650 651 /** 652 * Returns the current GuidedStepFragment on the fragment transaction stack. 653 * @return The current GuidedStepFragment, if any, on the fragment transaction stack. 654 */ 655 public static GuidedStepFragment getCurrentGuidedStepFragment(FragmentManager fm) { 656 Fragment f = fm.findFragmentByTag(TAG_LEAN_BACK_ACTIONS_FRAGMENT); 657 if (f instanceof GuidedStepFragment) { 658 return (GuidedStepFragment) f; 659 } 660 return null; 661 } 662 663 /** 664 * Returns the GuidanceStylist that displays guidance information for the user. 665 * @return The GuidanceStylist for this fragment. 666 */ 667 public GuidanceStylist getGuidanceStylist() { 668 return mGuidanceStylist; 669 } 670 671 /** 672 * Returns the GuidedActionsStylist that displays the actions the user may take. 673 * @return The GuidedActionsStylist for this fragment. 674 */ 675 public GuidedActionsStylist getGuidedActionsStylist() { 676 return mActionsStylist; 677 } 678 679 /** 680 * Returns the list of button GuidedActions that the user may take in this fragment. 681 * @return The list of button GuidedActions for this fragment. 682 */ 683 public List<GuidedAction> getButtonActions() { 684 return mButtonActions; 685 } 686 687 /** 688 * Find button GuidedAction by Id. 689 * @param id Id of the button action to search. 690 * @return GuidedAction object or null if not found. 691 */ 692 public GuidedAction findButtonActionById(long id) { 693 int index = findButtonActionPositionById(id); 694 return index >= 0 ? mButtonActions.get(index) : null; 695 } 696 697 /** 698 * Find button GuidedAction position in array by Id. 699 * @param id Id of the button action to search. 700 * @return position of GuidedAction object in array or -1 if not found. 701 */ 702 public int findButtonActionPositionById(long id) { 703 if (mButtonActions != null) { 704 for (int i = 0; i < mButtonActions.size(); i++) { 705 GuidedAction action = mButtonActions.get(i); 706 if (mButtonActions.get(i).getId() == id) { 707 return i; 708 } 709 } 710 } 711 return -1; 712 } 713 714 /** 715 * Returns the GuidedActionsStylist that displays the button actions the user may take. 716 * @return The GuidedActionsStylist for this fragment. 717 */ 718 public GuidedActionsStylist getGuidedButtonActionsStylist() { 719 return mButtonActionsStylist; 720 } 721 722 /** 723 * Sets the list of button GuidedActions that the user may take in this fragment. 724 * @param actions The list of button GuidedActions for this fragment. 725 */ 726 public void setButtonActions(List<GuidedAction> actions) { 727 mButtonActions = actions; 728 if (mButtonAdapter != null) { 729 mButtonAdapter.setActions(mButtonActions); 730 } 731 } 732 733 /** 734 * Notify an button action has changed and update its UI. 735 * @param position Position of the button GuidedAction in array. 736 */ 737 public void notifyButtonActionChanged(int position) { 738 if (mButtonAdapter != null) { 739 mButtonAdapter.notifyItemChanged(position); 740 } 741 } 742 743 /** 744 * Returns the view corresponding to the button action at the indicated position in the list of 745 * actions for this fragment. 746 * @param position The integer position of the button action of interest. 747 * @return The View corresponding to the button action at the indicated position, or null if 748 * that action is not currently onscreen. 749 */ 750 public View getButtonActionItemView(int position) { 751 final RecyclerView.ViewHolder holder = mButtonActionsStylist.getActionsGridView() 752 .findViewHolderForPosition(position); 753 return holder == null ? null : holder.itemView; 754 } 755 756 /** 757 * Scrolls the action list to the position indicated, selecting that button action's view. 758 * @param position The integer position of the button action of interest. 759 */ 760 public void setSelectedButtonActionPosition(int position) { 761 mButtonActionsStylist.getActionsGridView().setSelectedPosition(position); 762 } 763 764 /** 765 * Returns the position if the currently selected button GuidedAction. 766 * @return position The integer position of the currently selected button action. 767 */ 768 public int getSelectedButtonActionPosition() { 769 return mButtonActionsStylist.getActionsGridView().getSelectedPosition(); 770 } 771 772 /** 773 * Returns the list of GuidedActions that the user may take in this fragment. 774 * @return The list of GuidedActions for this fragment. 775 */ 776 public List<GuidedAction> getActions() { 777 return mActions; 778 } 779 780 /** 781 * Find GuidedAction by Id. 782 * @param id Id of the action to search. 783 * @return GuidedAction object or null if not found. 784 */ 785 public GuidedAction findActionById(long id) { 786 int index = findActionPositionById(id); 787 return index >= 0 ? mActions.get(index) : null; 788 } 789 790 /** 791 * Find GuidedAction position in array by Id. 792 * @param id Id of the action to search. 793 * @return position of GuidedAction object in array or -1 if not found. 794 */ 795 public int findActionPositionById(long id) { 796 if (mActions != null) { 797 for (int i = 0; i < mActions.size(); i++) { 798 GuidedAction action = mActions.get(i); 799 if (mActions.get(i).getId() == id) { 800 return i; 801 } 802 } 803 } 804 return -1; 805 } 806 807 /** 808 * Sets the list of GuidedActions that the user may take in this fragment. 809 * @param actions The list of GuidedActions for this fragment. 810 */ 811 public void setActions(List<GuidedAction> actions) { 812 mActions = actions; 813 if (mAdapter != null) { 814 mAdapter.setActions(mActions); 815 } 816 } 817 818 /** 819 * Notify an action has changed and update its UI. 820 * @param position Position of the GuidedAction in array. 821 */ 822 public void notifyActionChanged(int position) { 823 if (mAdapter != null) { 824 mAdapter.notifyItemChanged(position); 825 } 826 } 827 828 /** 829 * Returns the view corresponding to the action at the indicated position in the list of 830 * actions for this fragment. 831 * @param position The integer position of the action of interest. 832 * @return The View corresponding to the action at the indicated position, or null if that 833 * action is not currently onscreen. 834 */ 835 public View getActionItemView(int position) { 836 final RecyclerView.ViewHolder holder = mActionsStylist.getActionsGridView() 837 .findViewHolderForPosition(position); 838 return holder == null ? null : holder.itemView; 839 } 840 841 /** 842 * Scrolls the action list to the position indicated, selecting that action's view. 843 * @param position The integer position of the action of interest. 844 */ 845 public void setSelectedActionPosition(int position) { 846 mActionsStylist.getActionsGridView().setSelectedPosition(position); 847 } 848 849 /** 850 * Returns the position if the currently selected GuidedAction. 851 * @return position The integer position of the currently selected action. 852 */ 853 public int getSelectedActionPosition() { 854 return mActionsStylist.getActionsGridView().getSelectedPosition(); 855 } 856 857 /** 858 * Called by Constructor to provide fragment transitions. The default implementation assigns 859 * transitions based on {@link #getUiStyle()}: 860 * <ul> 861 * <li> {@link #UI_STYLE_REPLACE} Slide from/to end(right) for enter transition, slide from/to 862 * start(left) for exit transition, shared element enter transition is set to ChangeBounds. 863 * <li> {@link #UI_STYLE_ENTRANCE} Enter transition is set to slide from both sides, exit 864 * transition is same as {@link #UI_STYLE_REPLACE}, no shared element enter transition. 865 * <li> {@link #UI_STYLE_ACTIVITY_ROOT} Enter transition is set to null and app should rely on 866 * activity transition, exit transition is same as {@link #UI_STYLE_REPLACE}, no shared element 867 * enter transition. 868 * </ul> 869 * <p> 870 * The default implementation heavily relies on {@link GuidedActionsStylist} and 871 * {@link GuidanceStylist} layout, app may override this method when modifying the default 872 * layout of {@link GuidedActionsStylist} or {@link GuidanceStylist}. 873 * <p> 874 * TIP: because the fragment view is removed during fragment transition, in general app cannot 875 * use two Visibility transition together. Workaround is to create your own Visibility 876 * transition that controls multiple animators (e.g. slide and fade animation in one Transition 877 * class). 878 */ 879 protected void onProvideFragmentTransitions() { 880 if (Build.VERSION.SDK_INT >= 21) { 881 final int uiStyle = getUiStyle(); 882 if (uiStyle == UI_STYLE_REPLACE) { 883 Object enterTransition = TransitionHelper.createFadeAndShortSlide(Gravity.END); 884 TransitionHelper.exclude(enterTransition, R.id.guidedstep_background, true); 885 TransitionHelper.exclude(enterTransition, R.id.guidedactions_sub_list_background, 886 true); 887 TransitionHelper.setEnterTransition(this, enterTransition); 888 889 Object fade = TransitionHelper.createFadeTransition( 890 TransitionHelper.FADE_IN | TransitionHelper.FADE_OUT); 891 TransitionHelper.include(fade, R.id.guidedactions_sub_list_background); 892 Object changeBounds = TransitionHelper.createChangeBounds(false); 893 Object sharedElementTransition = TransitionHelper.createTransitionSet(false); 894 TransitionHelper.addTransition(sharedElementTransition, fade); 895 TransitionHelper.addTransition(sharedElementTransition, changeBounds); 896 TransitionHelper.setSharedElementEnterTransition(this, sharedElementTransition); 897 } else if (uiStyle == UI_STYLE_ENTRANCE) { 898 if (entranceTransitionType == SLIDE_FROM_SIDE) { 899 Object fade = TransitionHelper.createFadeTransition( 900 TransitionHelper.FADE_IN | TransitionHelper.FADE_OUT); 901 TransitionHelper.include(fade, R.id.guidedstep_background); 902 Object slideFromSide = TransitionHelper.createFadeAndShortSlide( 903 Gravity.END | Gravity.START); 904 TransitionHelper.include(slideFromSide, R.id.content_fragment); 905 TransitionHelper.include(slideFromSide, R.id.action_fragment_root); 906 Object enterTransition = TransitionHelper.createTransitionSet(false); 907 TransitionHelper.addTransition(enterTransition, fade); 908 TransitionHelper.addTransition(enterTransition, slideFromSide); 909 TransitionHelper.setEnterTransition(this, enterTransition); 910 } else { 911 Object slideFromBottom = TransitionHelper.createFadeAndShortSlide( 912 Gravity.BOTTOM); 913 TransitionHelper.include(slideFromBottom, R.id.guidedstep_background_view_root); 914 Object enterTransition = TransitionHelper.createTransitionSet(false); 915 TransitionHelper.addTransition(enterTransition, slideFromBottom); 916 TransitionHelper.setEnterTransition(this, enterTransition); 917 } 918 // No shared element transition 919 TransitionHelper.setSharedElementEnterTransition(this, null); 920 } else if (uiStyle == UI_STYLE_ACTIVITY_ROOT) { 921 // for Activity root, we don't need enter transition, use activity transition 922 TransitionHelper.setEnterTransition(this, null); 923 // No shared element transition 924 TransitionHelper.setSharedElementEnterTransition(this, null); 925 } 926 // exitTransition is same for all style 927 Object exitTransition = TransitionHelper.createFadeAndShortSlide(Gravity.START); 928 TransitionHelper.exclude(exitTransition, R.id.guidedstep_background, true); 929 TransitionHelper.exclude(exitTransition, R.id.guidedactions_sub_list_background, 930 true); 931 TransitionHelper.setExitTransition(this, exitTransition); 932 } 933 } 934 935 /** 936 * Called by onCreateView to inflate background view. Default implementation loads view 937 * from {@link R.layout#lb_guidedstep_background} which holds a reference to 938 * guidedStepBackground. 939 * @param inflater LayoutInflater to load background view. 940 * @param container Parent view of background view. 941 * @param savedInstanceState 942 * @return Created background view or null if no background. 943 */ 944 public View onCreateBackgroundView(LayoutInflater inflater, ViewGroup container, 945 Bundle savedInstanceState) { 946 return inflater.inflate(R.layout.lb_guidedstep_background, container, false); 947 } 948 949 /** 950 * Set UI style to fragment arguments. Default value is {@link #UI_STYLE_ENTRANCE} when fragment 951 * is first initialized. UI style is used to choose different fragment transition animations and 952 * determine if this is the first GuidedStepFragment on backstack. In most cases app does not 953 * directly call this method, app calls helper function 954 * {@link #add(FragmentManager, GuidedStepFragment, int)}. However if the app creates Fragment 955 * transaction and controls backstack by itself, it would need call setUiStyle() to select the 956 * fragment transition to use. 957 * 958 * @param style {@link #UI_STYLE_ACTIVITY_ROOT} {@link #UI_STYLE_REPLACE} or 959 * {@link #UI_STYLE_ENTRANCE}. 960 */ 961 public void setUiStyle(int style) { 962 int oldStyle = getUiStyle(); 963 Bundle arguments = getArguments(); 964 boolean isNew = false; 965 if (arguments == null) { 966 arguments = new Bundle(); 967 isNew = true; 968 } 969 arguments.putInt(EXTRA_UI_STYLE, style); 970 // call setArgument() will validate if the fragment is already added. 971 if (isNew) { 972 setArguments(arguments); 973 } 974 if (style != oldStyle) { 975 onProvideFragmentTransitions(); 976 } 977 } 978 979 /** 980 * Read UI style from fragment arguments. Default value is {@link #UI_STYLE_ENTRANCE} when 981 * fragment is first initialized. UI style is used to choose different fragment transition 982 * animations and determine if this is the first GuidedStepFragment on backstack. 983 * 984 * @return {@link #UI_STYLE_ACTIVITY_ROOT} {@link #UI_STYLE_REPLACE} or 985 * {@link #UI_STYLE_ENTRANCE}. 986 * @see #onProvideFragmentTransitions() 987 */ 988 public int getUiStyle() { 989 Bundle b = getArguments(); 990 if (b == null) return UI_STYLE_ENTRANCE; 991 return b.getInt(EXTRA_UI_STYLE, UI_STYLE_ENTRANCE); 992 } 993 994 /** 995 * {@inheritDoc} 996 */ 997 @Override 998 public void onCreate(Bundle savedInstanceState) { 999 super.onCreate(savedInstanceState); 1000 if (DEBUG) Log.v(TAG, "onCreate"); 1001 // Set correct transition from saved arguments. 1002 onProvideFragmentTransitions(); 1003 1004 ArrayList<GuidedAction> actions = new ArrayList<GuidedAction>(); 1005 onCreateActions(actions, savedInstanceState); 1006 if (savedInstanceState != null) { 1007 onRestoreActions(actions, savedInstanceState); 1008 } 1009 setActions(actions); 1010 ArrayList<GuidedAction> buttonActions = new ArrayList<GuidedAction>(); 1011 onCreateButtonActions(buttonActions, savedInstanceState); 1012 if (savedInstanceState != null) { 1013 onRestoreButtonActions(buttonActions, savedInstanceState); 1014 } 1015 setButtonActions(buttonActions); 1016 } 1017 1018 /** 1019 * {@inheritDoc} 1020 */ 1021 @Override 1022 public void onDestroyView() { 1023 mGuidanceStylist.onDestroyView(); 1024 mActionsStylist.onDestroyView(); 1025 mButtonActionsStylist.onDestroyView(); 1026 mAdapter = null; 1027 mSubAdapter = null; 1028 mButtonAdapter = null; 1029 mAdapterGroup = null; 1030 super.onDestroyView(); 1031 } 1032 1033 /** 1034 * {@inheritDoc} 1035 */ 1036 @Override 1037 public View onCreateView(LayoutInflater inflater, ViewGroup container, 1038 Bundle savedInstanceState) { 1039 if (DEBUG) Log.v(TAG, "onCreateView"); 1040 1041 resolveTheme(); 1042 inflater = getThemeInflater(inflater); 1043 1044 GuidedStepRootLayout root = (GuidedStepRootLayout) inflater.inflate( 1045 R.layout.lb_guidedstep_fragment, container, false); 1046 1047 root.setFocusOutStart(isFocusOutStartAllowed()); 1048 root.setFocusOutEnd(isFocusOutEndAllowed()); 1049 1050 ViewGroup guidanceContainer = (ViewGroup) root.findViewById(R.id.content_fragment); 1051 ViewGroup actionContainer = (ViewGroup) root.findViewById(R.id.action_fragment); 1052 ((NonOverlappingLinearLayout) actionContainer).setFocusableViewAvailableFixEnabled(true); 1053 1054 Guidance guidance = onCreateGuidance(savedInstanceState); 1055 View guidanceView = mGuidanceStylist.onCreateView(inflater, guidanceContainer, guidance); 1056 guidanceContainer.addView(guidanceView); 1057 1058 View actionsView = mActionsStylist.onCreateView(inflater, actionContainer); 1059 actionContainer.addView(actionsView); 1060 1061 View buttonActionsView = mButtonActionsStylist.onCreateView(inflater, actionContainer); 1062 actionContainer.addView(buttonActionsView); 1063 1064 GuidedActionAdapter.EditListener editListener = new GuidedActionAdapter.EditListener() { 1065 1066 @Override 1067 public void onImeOpen() { 1068 runImeAnimations(true); 1069 } 1070 1071 @Override 1072 public void onImeClose() { 1073 runImeAnimations(false); 1074 } 1075 1076 @Override 1077 public long onGuidedActionEditedAndProceed(GuidedAction action) { 1078 return GuidedStepFragment.this.onGuidedActionEditedAndProceed(action); 1079 } 1080 1081 @Override 1082 public void onGuidedActionEditCanceled(GuidedAction action) { 1083 GuidedStepFragment.this.onGuidedActionEditCanceled(action); 1084 } 1085 }; 1086 1087 mAdapter = new GuidedActionAdapter(mActions, new GuidedActionAdapter.ClickListener() { 1088 @Override 1089 public void onGuidedActionClicked(GuidedAction action) { 1090 GuidedStepFragment.this.onGuidedActionClicked(action); 1091 if (isExpanded()) { 1092 collapseAction(true); 1093 } else if (action.hasSubActions() || action.hasEditableActivatorView()) { 1094 expandAction(action, true); 1095 } 1096 } 1097 }, this, mActionsStylist, false); 1098 mButtonAdapter = 1099 new GuidedActionAdapter(mButtonActions, new GuidedActionAdapter.ClickListener() { 1100 @Override 1101 public void onGuidedActionClicked(GuidedAction action) { 1102 GuidedStepFragment.this.onGuidedActionClicked(action); 1103 } 1104 }, this, mButtonActionsStylist, false); 1105 mSubAdapter = new GuidedActionAdapter(null, new GuidedActionAdapter.ClickListener() { 1106 @Override 1107 public void onGuidedActionClicked(GuidedAction action) { 1108 if (mActionsStylist.isInExpandTransition()) { 1109 return; 1110 } 1111 if (GuidedStepFragment.this.onSubGuidedActionClicked(action)) { 1112 collapseSubActions(); 1113 } 1114 } 1115 }, this, mActionsStylist, true); 1116 mAdapterGroup = new GuidedActionAdapterGroup(); 1117 mAdapterGroup.addAdpter(mAdapter, mButtonAdapter); 1118 mAdapterGroup.addAdpter(mSubAdapter, null); 1119 mAdapterGroup.setEditListener(editListener); 1120 mActionsStylist.setEditListener(editListener); 1121 1122 mActionsStylist.getActionsGridView().setAdapter(mAdapter); 1123 if (mActionsStylist.getSubActionsGridView() != null) { 1124 mActionsStylist.getSubActionsGridView().setAdapter(mSubAdapter); 1125 } 1126 mButtonActionsStylist.getActionsGridView().setAdapter(mButtonAdapter); 1127 if (mButtonActions.size() == 0) { 1128 // when there is no button actions, we don't need show the second panel, but keep 1129 // the width zero to run ChangeBounds transition. 1130 LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) 1131 buttonActionsView.getLayoutParams(); 1132 lp.weight = 0; 1133 buttonActionsView.setLayoutParams(lp); 1134 } else { 1135 // when there are two actions panel, we need adjust the weight of action to 1136 // guidedActionContentWidthWeightTwoPanels. 1137 Context ctx = mThemeWrapper != null ? mThemeWrapper : FragmentUtil.getContext(GuidedStepFragment.this); 1138 TypedValue typedValue = new TypedValue(); 1139 if (ctx.getTheme().resolveAttribute(R.attr.guidedActionContentWidthWeightTwoPanels, 1140 typedValue, true)) { 1141 View actionsRoot = root.findViewById(R.id.action_fragment_root); 1142 float weight = typedValue.getFloat(); 1143 LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) actionsRoot 1144 .getLayoutParams(); 1145 lp.weight = weight; 1146 actionsRoot.setLayoutParams(lp); 1147 } 1148 } 1149 1150 // Add the background view. 1151 View backgroundView = onCreateBackgroundView(inflater, root, savedInstanceState); 1152 if (backgroundView != null) { 1153 FrameLayout backgroundViewRoot = (FrameLayout)root.findViewById( 1154 R.id.guidedstep_background_view_root); 1155 backgroundViewRoot.addView(backgroundView, 0); 1156 } 1157 1158 return root; 1159 } 1160 1161 @Override 1162 public void onResume() { 1163 super.onResume(); 1164 getView().findViewById(R.id.action_fragment).requestFocus(); 1165 } 1166 1167 /** 1168 * Get the key will be used to save GuidedAction with Fragment. 1169 * @param action GuidedAction to get key. 1170 * @return Key to save the GuidedAction. 1171 */ 1172 final String getAutoRestoreKey(GuidedAction action) { 1173 return EXTRA_ACTION_PREFIX + action.getId(); 1174 } 1175 1176 /** 1177 * Get the key will be used to save GuidedAction with Fragment. 1178 * @param action GuidedAction to get key. 1179 * @return Key to save the GuidedAction. 1180 */ 1181 final String getButtonAutoRestoreKey(GuidedAction action) { 1182 return EXTRA_BUTTON_ACTION_PREFIX + action.getId(); 1183 } 1184 1185 final static boolean isSaveEnabled(GuidedAction action) { 1186 return action.isAutoSaveRestoreEnabled() && action.getId() != GuidedAction.NO_ID; 1187 } 1188 1189 final void onRestoreActions(List<GuidedAction> actions, Bundle savedInstanceState) { 1190 for (int i = 0, size = actions.size(); i < size; i++) { 1191 GuidedAction action = actions.get(i); 1192 if (isSaveEnabled(action)) { 1193 action.onRestoreInstanceState(savedInstanceState, getAutoRestoreKey(action)); 1194 } 1195 } 1196 } 1197 1198 final void onRestoreButtonActions(List<GuidedAction> actions, Bundle savedInstanceState) { 1199 for (int i = 0, size = actions.size(); i < size; i++) { 1200 GuidedAction action = actions.get(i); 1201 if (isSaveEnabled(action)) { 1202 action.onRestoreInstanceState(savedInstanceState, getButtonAutoRestoreKey(action)); 1203 } 1204 } 1205 } 1206 1207 final void onSaveActions(List<GuidedAction> actions, Bundle outState) { 1208 for (int i = 0, size = actions.size(); i < size; i++) { 1209 GuidedAction action = actions.get(i); 1210 if (isSaveEnabled(action)) { 1211 action.onSaveInstanceState(outState, getAutoRestoreKey(action)); 1212 } 1213 } 1214 } 1215 1216 final void onSaveButtonActions(List<GuidedAction> actions, Bundle outState) { 1217 for (int i = 0, size = actions.size(); i < size; i++) { 1218 GuidedAction action = actions.get(i); 1219 if (isSaveEnabled(action)) { 1220 action.onSaveInstanceState(outState, getButtonAutoRestoreKey(action)); 1221 } 1222 } 1223 } 1224 1225 /** 1226 * {@inheritDoc} 1227 */ 1228 @Override 1229 public void onSaveInstanceState(Bundle outState) { 1230 super.onSaveInstanceState(outState); 1231 onSaveActions(mActions, outState); 1232 onSaveButtonActions(mButtonActions, outState); 1233 } 1234 1235 private static boolean isGuidedStepTheme(Context context) { 1236 int resId = R.attr.guidedStepThemeFlag; 1237 TypedValue typedValue = new TypedValue(); 1238 boolean found = context.getTheme().resolveAttribute(resId, typedValue, true); 1239 if (DEBUG) Log.v(TAG, "Found guided step theme flag? " + found); 1240 return found && typedValue.type == TypedValue.TYPE_INT_BOOLEAN && typedValue.data != 0; 1241 } 1242 1243 /** 1244 * Convenient method to close GuidedStepFragments on top of other content or finish Activity if 1245 * GuidedStepFragments were started in a separate activity. Pops all stack entries including 1246 * {@link #UI_STYLE_ENTRANCE}; if {@link #UI_STYLE_ENTRANCE} is not found, finish the activity. 1247 * Note that this method must be paired with {@link #add(FragmentManager, GuidedStepFragment, 1248 * int)} which sets up the stack entry name for finding which fragment we need to pop back to. 1249 */ 1250 public void finishGuidedStepFragments() { 1251 final FragmentManager fragmentManager = getFragmentManager(); 1252 final int entryCount = fragmentManager.getBackStackEntryCount(); 1253 if (entryCount > 0) { 1254 for (int i = entryCount - 1; i >= 0; i--) { 1255 BackStackEntry entry = fragmentManager.getBackStackEntryAt(i); 1256 if (isStackEntryUiStyleEntrance(entry.getName())) { 1257 GuidedStepFragment top = getCurrentGuidedStepFragment(fragmentManager); 1258 if (top != null) { 1259 top.setUiStyle(UI_STYLE_ENTRANCE); 1260 } 1261 fragmentManager.popBackStackImmediate(entry.getId(), 1262 FragmentManager.POP_BACK_STACK_INCLUSIVE); 1263 return; 1264 } 1265 } 1266 } 1267 ActivityCompat.finishAfterTransition(getActivity()); 1268 } 1269 1270 /** 1271 * Convenient method to pop to fragment with Given class. 1272 * @param guidedStepFragmentClass Name of the Class of GuidedStepFragment to pop to. 1273 * @param flags Either 0 or {@link FragmentManager#POP_BACK_STACK_INCLUSIVE}. 1274 */ 1275 public void popBackStackToGuidedStepFragment(Class guidedStepFragmentClass, int flags) { 1276 if (!GuidedStepFragment.class.isAssignableFrom(guidedStepFragmentClass)) { 1277 return; 1278 } 1279 final FragmentManager fragmentManager = getFragmentManager(); 1280 final int entryCount = fragmentManager.getBackStackEntryCount(); 1281 String className = guidedStepFragmentClass.getName(); 1282 if (entryCount > 0) { 1283 for (int i = entryCount - 1; i >= 0; i--) { 1284 BackStackEntry entry = fragmentManager.getBackStackEntryAt(i); 1285 String entryClassName = getGuidedStepFragmentClassName(entry.getName()); 1286 if (className.equals(entryClassName)) { 1287 fragmentManager.popBackStackImmediate(entry.getId(), flags); 1288 return; 1289 } 1290 } 1291 } 1292 } 1293 1294 /** 1295 * Returns true if allows focus out of start edge of GuidedStepFragment, false otherwise. 1296 * Default value is false, the reason is to disable FocusFinder to find focusable views 1297 * beneath content of GuidedStepFragment. Subclass may override. 1298 * @return True if allows focus out of start edge of GuidedStepFragment. 1299 */ 1300 public boolean isFocusOutStartAllowed() { 1301 return false; 1302 } 1303 1304 /** 1305 * Returns true if allows focus out of end edge of GuidedStepFragment, false otherwise. 1306 * Default value is false, the reason is to disable FocusFinder to find focusable views 1307 * beneath content of GuidedStepFragment. Subclass may override. 1308 * @return True if allows focus out of end edge of GuidedStepFragment. 1309 */ 1310 public boolean isFocusOutEndAllowed() { 1311 return false; 1312 } 1313 1314 /** 1315 * Sets the transition type to be used for {@link #UI_STYLE_ENTRANCE} animation. 1316 * Currently we provide 2 different variations for animation - slide in from 1317 * side (default) or bottom. 1318 * 1319 * Ideally we can retrieve the screen mode settings from the theme attribute 1320 * {@code Theme.Leanback.GuidedStep#guidedStepHeightWeight} and use that to 1321 * determine the transition. But the fragment context to retrieve the theme 1322 * isn't available on platform v23 or earlier. 1323 * 1324 * For now clients(subclasses) can call this method inside the constructor. 1325 * @hide 1326 */ 1327 @RestrictTo(LIBRARY_GROUP) 1328 public void setEntranceTransitionType(int transitionType) { 1329 this.entranceTransitionType = transitionType; 1330 } 1331 1332 /** 1333 * Opens the provided action in edit mode and raises ime. This can be 1334 * used to programmatically skip the extra click required to go into edit mode. This method 1335 * can be invoked in {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}. 1336 */ 1337 public void openInEditMode(GuidedAction action) { 1338 mActionsStylist.openInEditMode(action); 1339 } 1340 1341 private void resolveTheme() { 1342 // Look up the guidedStepTheme in the currently specified theme. If it exists, 1343 // replace the theme with its value. 1344 Context context = FragmentUtil.getContext(GuidedStepFragment.this); 1345 int theme = onProvideTheme(); 1346 if (theme == -1 && !isGuidedStepTheme(context)) { 1347 // Look up the guidedStepTheme in the activity's currently specified theme. If it 1348 // exists, replace the theme with its value. 1349 int resId = R.attr.guidedStepTheme; 1350 TypedValue typedValue = new TypedValue(); 1351 boolean found = context.getTheme().resolveAttribute(resId, typedValue, true); 1352 if (DEBUG) Log.v(TAG, "Found guided step theme reference? " + found); 1353 if (found) { 1354 ContextThemeWrapper themeWrapper = 1355 new ContextThemeWrapper(context, typedValue.resourceId); 1356 if (isGuidedStepTheme(themeWrapper)) { 1357 mThemeWrapper = themeWrapper; 1358 } else { 1359 found = false; 1360 mThemeWrapper = null; 1361 } 1362 } 1363 if (!found) { 1364 Log.e(TAG, "GuidedStepFragment does not have an appropriate theme set."); 1365 } 1366 } else if (theme != -1) { 1367 mThemeWrapper = new ContextThemeWrapper(context, theme); 1368 } 1369 } 1370 1371 private LayoutInflater getThemeInflater(LayoutInflater inflater) { 1372 if (mThemeWrapper == null) { 1373 return inflater; 1374 } else { 1375 return inflater.cloneInContext(mThemeWrapper); 1376 } 1377 } 1378 1379 private int getFirstCheckedAction() { 1380 for (int i = 0, size = mActions.size(); i < size; i++) { 1381 if (mActions.get(i).isChecked()) { 1382 return i; 1383 } 1384 } 1385 return 0; 1386 } 1387 1388 void runImeAnimations(boolean entering) { 1389 ArrayList<Animator> animators = new ArrayList<Animator>(); 1390 if (entering) { 1391 mGuidanceStylist.onImeAppearing(animators); 1392 mActionsStylist.onImeAppearing(animators); 1393 mButtonActionsStylist.onImeAppearing(animators); 1394 } else { 1395 mGuidanceStylist.onImeDisappearing(animators); 1396 mActionsStylist.onImeDisappearing(animators); 1397 mButtonActionsStylist.onImeDisappearing(animators); 1398 } 1399 AnimatorSet set = new AnimatorSet(); 1400 set.playTogether(animators); 1401 set.start(); 1402 } 1403} 1404