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