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