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