| // CHECKSTYLE:OFF Generated code |
| /* This file is auto-generated from GuidedStepFragment.java. DO NOT MODIFY. */ |
| |
| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except |
| * in compliance with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software distributed under the License |
| * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
| * or implied. See the License for the specific language governing permissions and limitations under |
| * the License. |
| */ |
| package android.support.v17.leanback.app; |
| |
| import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; |
| |
| import android.animation.Animator; |
| import android.animation.AnimatorSet; |
| import android.support.v4.app.FragmentActivity; |
| import android.support.v4.app.Fragment; |
| import android.support.v4.app.FragmentManager; |
| import android.support.v4.app.FragmentManager.BackStackEntry; |
| import android.support.v4.app.FragmentTransaction; |
| import android.content.Context; |
| import android.os.Build; |
| import android.os.Bundle; |
| import android.support.annotation.NonNull; |
| import android.support.annotation.RestrictTo; |
| import android.support.v17.leanback.R; |
| import android.support.v17.leanback.transition.TransitionHelper; |
| import android.support.v17.leanback.widget.GuidanceStylist; |
| import android.support.v17.leanback.widget.GuidanceStylist.Guidance; |
| import android.support.v17.leanback.widget.GuidedAction; |
| import android.support.v17.leanback.widget.GuidedActionAdapter; |
| import android.support.v17.leanback.widget.GuidedActionAdapterGroup; |
| import android.support.v17.leanback.widget.GuidedActionsStylist; |
| import android.support.v17.leanback.widget.NonOverlappingLinearLayout; |
| import android.support.v4.app.ActivityCompat; |
| import android.support.v7.widget.RecyclerView; |
| import android.util.Log; |
| import android.util.TypedValue; |
| import android.view.ContextThemeWrapper; |
| import android.view.Gravity; |
| import android.view.LayoutInflater; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.widget.FrameLayout; |
| import android.widget.LinearLayout; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** |
| * A GuidedStepSupportFragment is used to guide the user through a decision or series of decisions. |
| * It is composed of a guidance view on the left and a view on the right containing a list of |
| * possible actions. |
| * <p> |
| * <h3>Basic Usage</h3> |
| * <p> |
| * Clients of GuidedStepSupportFragment must create a custom subclass to attach to their Activities. |
| * This custom subclass provides the information necessary to construct the user interface and |
| * respond to user actions. At a minimum, subclasses should override: |
| * <ul> |
| * <li>{@link #onCreateGuidance}, to provide instructions to the user</li> |
| * <li>{@link #onCreateActions}, to provide a set of {@link GuidedAction}s the user can take</li> |
| * <li>{@link #onGuidedActionClicked}, to respond to those actions</li> |
| * </ul> |
| * <p> |
| * Clients use following helper functions to add GuidedStepSupportFragment to Activity or FragmentManager: |
| * <ul> |
| * <li>{@link #addAsRoot(FragmentActivity, GuidedStepSupportFragment, int)}, to be called during Activity onCreate, |
| * adds GuidedStepSupportFragment as the first Fragment in activity.</li> |
| * <li>{@link #add(FragmentManager, GuidedStepSupportFragment)} or {@link #add(FragmentManager, |
| * GuidedStepSupportFragment, int)}, to add GuidedStepSupportFragment on top of existing Fragments or |
| * replacing existing GuidedStepSupportFragment when moving forward to next step.</li> |
| * <li>{@link #finishGuidedStepSupportFragments()} can either finish the activity or pop all |
| * GuidedStepSupportFragment from stack. |
| * <li>If app chooses not to use the helper function, it is the app's responsibility to call |
| * {@link #setUiStyle(int)} to select fragment transition and remember the stack entry where it |
| * need pops to. |
| * </ul> |
| * <h3>Theming and Stylists</h3> |
| * <p> |
| * GuidedStepSupportFragment delegates its visual styling to classes called stylists. The {@link |
| * GuidanceStylist} is responsible for the left guidance view, while the {@link |
| * GuidedActionsStylist} is responsible for the right actions view. The stylists use theme |
| * attributes to derive values associated with the presentation, such as colors, animations, etc. |
| * Most simple visual aspects of GuidanceStylist and GuidedActionsStylist can be customized |
| * via theming; see their documentation for more information. |
| * <p> |
| * GuidedStepSupportFragments must have access to an appropriate theme in order for the stylists to |
| * function properly. Specifically, the fragment must receive {@link |
| * android.support.v17.leanback.R.style#Theme_Leanback_GuidedStep}, or a theme whose parent is |
| * is set to that theme. Themes can be provided in one of three ways: |
| * <ul> |
| * <li>The simplest way is to set the theme for the host Activity to the GuidedStep theme or a |
| * theme that derives from it.</li> |
| * <li>If the Activity already has a theme and setting its parent theme is inconvenient, the |
| * existing Activity theme can have an entry added for the attribute {@link |
| * android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepTheme}. If present, |
| * this theme will be used by GuidedStepSupportFragment as an overlay to the Activity's theme.</li> |
| * <li>Finally, custom subclasses of GuidedStepSupportFragment may provide a theme through the {@link |
| * #onProvideTheme} method. This can be useful if a subclass is used across multiple |
| * Activities.</li> |
| * </ul> |
| * <p> |
| * If the theme is provided in multiple ways, the onProvideTheme override has priority, followed by |
| * the Activity's theme. (Themes whose parent theme is already set to the guided step theme do not |
| * need to set the guidedStepTheme attribute; if set, it will be ignored.) |
| * <p> |
| * If themes do not provide enough customizability, the stylists themselves may be subclassed and |
| * provided to the GuidedStepSupportFragment through the {@link #onCreateGuidanceStylist} and {@link |
| * #onCreateActionsStylist} methods. The stylists have simple hooks so that subclasses |
| * may override layout files; subclasses may also have more complex logic to determine styling. |
| * <p> |
| * <h3>Guided sequences</h3> |
| * <p> |
| * GuidedStepSupportFragments can be grouped together to provide a guided sequence. GuidedStepSupportFragments |
| * grouped as a sequence use custom animations provided by {@link GuidanceStylist} and |
| * {@link GuidedActionsStylist} (or subclasses) during transitions between steps. Clients |
| * should use {@link #add} to place subsequent GuidedFragments onto the fragment stack so that |
| * custom animations are properly configured. (Custom animations are triggered automatically when |
| * the fragment stack is subsequently popped by any normal mechanism.) |
| * <p> |
| * <i>Note: Currently GuidedStepSupportFragments grouped in this way must all be defined programmatically, |
| * rather than in XML. This restriction may be removed in the future.</i> |
| * |
| * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepTheme |
| * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepBackground |
| * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionContentWidthWeight |
| * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionContentWidthWeightTwoPanels |
| * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsBackground |
| * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsBackgroundDark |
| * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsElevation |
| * @see GuidanceStylist |
| * @see GuidanceStylist.Guidance |
| * @see GuidedAction |
| * @see GuidedActionsStylist |
| */ |
| public class GuidedStepSupportFragment extends Fragment implements GuidedActionAdapter.FocusListener { |
| |
| private static final String TAG_LEAN_BACK_ACTIONS_FRAGMENT = "leanBackGuidedStepSupportFragment"; |
| private static final String EXTRA_ACTION_PREFIX = "action_"; |
| private static final String EXTRA_BUTTON_ACTION_PREFIX = "buttonaction_"; |
| |
| private static final String ENTRY_NAME_REPLACE = "GuidedStepDefault"; |
| |
| private static final String ENTRY_NAME_ENTRANCE = "GuidedStepEntrance"; |
| |
| private static final boolean IS_FRAMEWORK_FRAGMENT = false; |
| |
| /** |
| * Fragment argument name for UI style. The argument value is persisted in fragment state and |
| * used to select fragment transition. The value is initially {@link #UI_STYLE_ENTRANCE} and |
| * might be changed in one of the three helper functions: |
| * <ul> |
| * <li>{@link #addAsRoot(FragmentActivity, GuidedStepSupportFragment, int)} sets to |
| * {@link #UI_STYLE_ACTIVITY_ROOT}</li> |
| * <li>{@link #add(FragmentManager, GuidedStepSupportFragment)} or {@link #add(FragmentManager, |
| * GuidedStepSupportFragment, int)} sets it to {@link #UI_STYLE_REPLACE} if there is already a |
| * GuidedStepSupportFragment on stack.</li> |
| * <li>{@link #finishGuidedStepSupportFragments()} changes current GuidedStepSupportFragment to |
| * {@link #UI_STYLE_ENTRANCE} for the non activity case. This is a special case that changes |
| * the transition settings after fragment has been created, in order to force current |
| * GuidedStepSupportFragment run a return transition of {@link #UI_STYLE_ENTRANCE}</li> |
| * </ul> |
| * <p> |
| * Argument value can be either: |
| * <ul> |
| * <li>{@link #UI_STYLE_REPLACE}</li> |
| * <li>{@link #UI_STYLE_ENTRANCE}</li> |
| * <li>{@link #UI_STYLE_ACTIVITY_ROOT}</li> |
| * </ul> |
| */ |
| public static final String EXTRA_UI_STYLE = "uiStyle"; |
| |
| /** |
| * This is the case that we use GuidedStepSupportFragment to replace another existing |
| * GuidedStepSupportFragment when moving forward to next step. Default behavior of this style is: |
| * <ul> |
| * <li>Enter transition slides in from END(right), exit transition same as |
| * {@link #UI_STYLE_ENTRANCE}. |
| * </li> |
| * </ul> |
| */ |
| public static final int UI_STYLE_REPLACE = 0; |
| |
| /** |
| * @deprecated Same value as {@link #UI_STYLE_REPLACE}. |
| */ |
| @Deprecated |
| public static final int UI_STYLE_DEFAULT = 0; |
| |
| /** |
| * Default value for argument {@link #EXTRA_UI_STYLE}. The default value is assigned in |
| * GuidedStepSupportFragment constructor. This is the case that we show GuidedStepSupportFragment on top of |
| * other content. The default behavior of this style: |
| * <ul> |
| * <li>Enter transition slides in from two sides, exit transition slide out to START(left). |
| * Background will be faded in. Note: Changing exit transition by UI style is not working |
| * because fragment transition asks for exit transition before UI style is restored in Fragment |
| * .onCreate().</li> |
| * </ul> |
| * When popping multiple GuidedStepSupportFragment, {@link #finishGuidedStepSupportFragments()} also changes |
| * the top GuidedStepSupportFragment to UI_STYLE_ENTRANCE in order to run the return transition |
| * (reverse of enter transition) of UI_STYLE_ENTRANCE. |
| */ |
| public static final int UI_STYLE_ENTRANCE = 1; |
| |
| /** |
| * One possible value of argument {@link #EXTRA_UI_STYLE}. This is the case that we show first |
| * GuidedStepSupportFragment in a separate activity. The default behavior of this style: |
| * <ul> |
| * <li>Enter transition is assigned null (will rely on activity transition), exit transition is |
| * same as {@link #UI_STYLE_ENTRANCE}. Note: Changing exit transition by UI style is not working |
| * because fragment transition asks for exit transition before UI style is restored in |
| * Fragment.onCreate().</li> |
| * </ul> |
| */ |
| public static final int UI_STYLE_ACTIVITY_ROOT = 2; |
| |
| /** |
| * Animation to slide the contents from the side (left/right). |
| * @hide |
| */ |
| @RestrictTo(LIBRARY_GROUP) |
| public static final int SLIDE_FROM_SIDE = 0; |
| |
| /** |
| * Animation to slide the contents from the bottom. |
| * @hide |
| */ |
| @RestrictTo(LIBRARY_GROUP) |
| public static final int SLIDE_FROM_BOTTOM = 1; |
| |
| private static final String TAG = "GuidedStepF"; |
| private static final boolean DEBUG = false; |
| |
| /** |
| * @hide |
| */ |
| @RestrictTo(LIBRARY_GROUP) |
| public static class DummyFragment extends Fragment { |
| @Override |
| public View onCreateView(LayoutInflater inflater, ViewGroup container, |
| Bundle savedInstanceState) { |
| final View v = new View(inflater.getContext()); |
| v.setVisibility(View.GONE); |
| return v; |
| } |
| } |
| |
| private ContextThemeWrapper mThemeWrapper; |
| private GuidanceStylist mGuidanceStylist; |
| GuidedActionsStylist mActionsStylist; |
| private GuidedActionsStylist mButtonActionsStylist; |
| private GuidedActionAdapter mAdapter; |
| private GuidedActionAdapter mSubAdapter; |
| private GuidedActionAdapter mButtonAdapter; |
| private GuidedActionAdapterGroup mAdapterGroup; |
| private List<GuidedAction> mActions = new ArrayList<GuidedAction>(); |
| private List<GuidedAction> mButtonActions = new ArrayList<GuidedAction>(); |
| private int entranceTransitionType = SLIDE_FROM_SIDE; |
| |
| public GuidedStepSupportFragment() { |
| mGuidanceStylist = onCreateGuidanceStylist(); |
| mActionsStylist = onCreateActionsStylist(); |
| mButtonActionsStylist = onCreateButtonActionsStylist(); |
| onProvideFragmentTransitions(); |
| } |
| |
| /** |
| * Creates the presenter used to style the guidance panel. The default implementation returns |
| * a basic GuidanceStylist. |
| * @return The GuidanceStylist used in this fragment. |
| */ |
| public GuidanceStylist onCreateGuidanceStylist() { |
| return new GuidanceStylist(); |
| } |
| |
| /** |
| * Creates the presenter used to style the guided actions panel. The default implementation |
| * returns a basic GuidedActionsStylist. |
| * @return The GuidedActionsStylist used in this fragment. |
| */ |
| public GuidedActionsStylist onCreateActionsStylist() { |
| return new GuidedActionsStylist(); |
| } |
| |
| /** |
| * Creates the presenter used to style a sided actions panel for button only. |
| * The default implementation returns a basic GuidedActionsStylist. |
| * @return The GuidedActionsStylist used in this fragment. |
| */ |
| public GuidedActionsStylist onCreateButtonActionsStylist() { |
| GuidedActionsStylist stylist = new GuidedActionsStylist(); |
| stylist.setAsButtonActions(); |
| return stylist; |
| } |
| |
| /** |
| * Returns the theme used for styling the fragment. The default returns -1, indicating that the |
| * host Activity's theme should be used. |
| * @return The theme resource ID of the theme to use in this fragment, or -1 to use the |
| * host Activity's theme. |
| */ |
| public int onProvideTheme() { |
| return -1; |
| } |
| |
| /** |
| * Returns the information required to provide guidance to the user. This hook is called during |
| * {@link #onCreateView}. May be overridden to return a custom subclass of {@link |
| * GuidanceStylist.Guidance} for use in a subclass of {@link GuidanceStylist}. The default |
| * returns a Guidance object with empty fields; subclasses should override. |
| * @param savedInstanceState The saved instance state from onCreateView. |
| * @return The Guidance object representing the information used to guide the user. |
| */ |
| public @NonNull Guidance onCreateGuidance(Bundle savedInstanceState) { |
| return new Guidance("", "", "", null); |
| } |
| |
| /** |
| * Fills out the set of actions available to the user. This hook is called during {@link |
| * #onCreate}. The default leaves the list of actions empty; subclasses should override. |
| * @param actions A non-null, empty list ready to be populated. |
| * @param savedInstanceState The saved instance state from onCreate. |
| */ |
| public void onCreateActions(@NonNull List<GuidedAction> actions, Bundle savedInstanceState) { |
| } |
| |
| /** |
| * Fills out the set of actions shown at right available to the user. This hook is called during |
| * {@link #onCreate}. The default leaves the list of actions empty; subclasses may override. |
| * @param actions A non-null, empty list ready to be populated. |
| * @param savedInstanceState The saved instance state from onCreate. |
| */ |
| public void onCreateButtonActions(@NonNull List<GuidedAction> actions, |
| Bundle savedInstanceState) { |
| } |
| |
| /** |
| * Callback invoked when an action is taken by the user. Subclasses should override in |
| * order to act on the user's decisions. |
| * @param action The chosen action. |
| */ |
| public void onGuidedActionClicked(GuidedAction action) { |
| } |
| |
| /** |
| * Callback invoked when an action in sub actions is taken by the user. Subclasses should |
| * override in order to act on the user's decisions. Default return value is true to close |
| * the sub actions list. |
| * @param action The chosen action. |
| * @return true to collapse the sub actions list, false to keep it expanded. |
| */ |
| public boolean onSubGuidedActionClicked(GuidedAction action) { |
| return true; |
| } |
| |
| /** |
| * @return True if is current expanded including subactions list or |
| * action with {@link GuidedAction#hasEditableActivatorView()} is true. |
| */ |
| public boolean isExpanded() { |
| return mActionsStylist.isExpanded(); |
| } |
| |
| /** |
| * @return True if the sub actions list is expanded, false otherwise. |
| */ |
| public boolean isSubActionsExpanded() { |
| return mActionsStylist.isSubActionsExpanded(); |
| } |
| |
| /** |
| * Expand a given action's sub actions list. |
| * @param action GuidedAction to expand. |
| * @see #expandAction(GuidedAction, boolean) |
| */ |
| public void expandSubActions(GuidedAction action) { |
| if (!action.hasSubActions()) { |
| return; |
| } |
| expandAction(action, true); |
| } |
| |
| /** |
| * Expand a given action with sub actions list or |
| * {@link GuidedAction#hasEditableActivatorView()} is true. The method must be called after |
| * {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)} creates fragment view. |
| * |
| * @param action GuidedAction to expand. |
| * @param withTransition True to run transition animation, false otherwise. |
| */ |
| public void expandAction(GuidedAction action, boolean withTransition) { |
| mActionsStylist.expandAction(action, withTransition); |
| } |
| |
| /** |
| * Collapse sub actions list. |
| * @see GuidedAction#getSubActions() |
| */ |
| public void collapseSubActions() { |
| collapseAction(true); |
| } |
| |
| /** |
| * Collapse action which either has a sub actions list or action with |
| * {@link GuidedAction#hasEditableActivatorView()} is true. |
| * |
| * @param withTransition True to run transition animation, false otherwise. |
| */ |
| public void collapseAction(boolean withTransition) { |
| if (mActionsStylist != null && mActionsStylist.getActionsGridView() != null) { |
| mActionsStylist.collapseAction(withTransition); |
| } |
| } |
| |
| /** |
| * Callback invoked when an action is focused (made to be the current selection) by the user. |
| */ |
| @Override |
| public void onGuidedActionFocused(GuidedAction action) { |
| } |
| |
| /** |
| * Callback invoked when an action's title or description has been edited, this happens either |
| * when user clicks confirm button in IME or user closes IME window by BACK key. |
| * @deprecated Override {@link #onGuidedActionEditedAndProceed(GuidedAction)} and/or |
| * {@link #onGuidedActionEditCanceled(GuidedAction)}. |
| */ |
| @Deprecated |
| public void onGuidedActionEdited(GuidedAction action) { |
| } |
| |
| /** |
| * Callback invoked when an action has been canceled editing, for example when user closes |
| * IME window by BACK key. Default implementation calls deprecated method |
| * {@link #onGuidedActionEdited(GuidedAction)}. |
| * @param action The action which has been canceled editing. |
| */ |
| public void onGuidedActionEditCanceled(GuidedAction action) { |
| onGuidedActionEdited(action); |
| } |
| |
| /** |
| * Callback invoked when an action has been edited, for example when user clicks confirm button |
| * in IME window. Default implementation calls deprecated method |
| * {@link #onGuidedActionEdited(GuidedAction)} and returns {@link GuidedAction#ACTION_ID_NEXT}. |
| * |
| * @param action The action that has been edited. |
| * @return ID of the action will be focused or {@link GuidedAction#ACTION_ID_NEXT}, |
| * {@link GuidedAction#ACTION_ID_CURRENT}. |
| */ |
| public long onGuidedActionEditedAndProceed(GuidedAction action) { |
| onGuidedActionEdited(action); |
| return GuidedAction.ACTION_ID_NEXT; |
| } |
| |
| /** |
| * Adds the specified GuidedStepSupportFragment to the fragment stack, replacing any existing |
| * GuidedStepSupportFragments in the stack, and configuring the fragment-to-fragment custom |
| * transitions. A backstack entry is added, so the fragment will be dismissed when BACK key |
| * is pressed. |
| * <li>If current fragment on stack is GuidedStepSupportFragment: assign {@link #UI_STYLE_REPLACE} |
| * <li>If current fragment on stack is not GuidedStepSupportFragment: assign {@link #UI_STYLE_ENTRANCE} |
| * <p> |
| * Note: currently fragments added using this method must be created programmatically rather |
| * than via XML. |
| * @param fragmentManager The FragmentManager to be used in the transaction. |
| * @param fragment The GuidedStepSupportFragment to be inserted into the fragment stack. |
| * @return The ID returned by the call FragmentTransaction.commit. |
| */ |
| public static int add(FragmentManager fragmentManager, GuidedStepSupportFragment fragment) { |
| return add(fragmentManager, fragment, android.R.id.content); |
| } |
| |
| /** |
| * Adds the specified GuidedStepSupportFragment to the fragment stack, replacing any existing |
| * GuidedStepSupportFragments in the stack, and configuring the fragment-to-fragment custom |
| * transitions. A backstack entry is added, so the fragment will be dismissed when BACK key |
| * is pressed. |
| * <li>If current fragment on stack is GuidedStepSupportFragment: assign {@link #UI_STYLE_REPLACE} and |
| * {@link #onAddSharedElementTransition(FragmentTransaction, GuidedStepSupportFragment)} will be called |
| * to perform shared element transition between GuidedStepSupportFragments. |
| * <li>If current fragment on stack is not GuidedStepSupportFragment: assign {@link #UI_STYLE_ENTRANCE} |
| * <p> |
| * Note: currently fragments added using this method must be created programmatically rather |
| * than via XML. |
| * @param fragmentManager The FragmentManager to be used in the transaction. |
| * @param fragment The GuidedStepSupportFragment to be inserted into the fragment stack. |
| * @param id The id of container to add GuidedStepSupportFragment, can be android.R.id.content. |
| * @return The ID returned by the call FragmentTransaction.commit. |
| */ |
| public static int add(FragmentManager fragmentManager, GuidedStepSupportFragment fragment, int id) { |
| GuidedStepSupportFragment current = getCurrentGuidedStepSupportFragment(fragmentManager); |
| boolean inGuidedStep = current != null; |
| if (IS_FRAMEWORK_FRAGMENT && Build.VERSION.SDK_INT >= 21 && Build.VERSION.SDK_INT < 23 |
| && !inGuidedStep) { |
| // workaround b/22631964 for framework fragment |
| fragmentManager.beginTransaction() |
| .replace(id, new DummyFragment(), TAG_LEAN_BACK_ACTIONS_FRAGMENT) |
| .commit(); |
| } |
| FragmentTransaction ft = fragmentManager.beginTransaction(); |
| |
| fragment.setUiStyle(inGuidedStep ? UI_STYLE_REPLACE : UI_STYLE_ENTRANCE); |
| ft.addToBackStack(fragment.generateStackEntryName()); |
| if (current != null) { |
| fragment.onAddSharedElementTransition(ft, current); |
| } |
| return ft.replace(id, fragment, TAG_LEAN_BACK_ACTIONS_FRAGMENT).commit(); |
| } |
| |
| /** |
| * Called when this fragment is added to FragmentTransaction with {@link #UI_STYLE_REPLACE} (aka |
| * when the GuidedStepSupportFragment replacing an existing GuidedStepSupportFragment). Default implementation |
| * establishes connections between action background views to morph action background bounds |
| * change from disappearing GuidedStepSupportFragment into this GuidedStepSupportFragment. The default |
| * implementation heavily relies on {@link GuidedActionsStylist}'s layout, app may override this |
| * method when modifying the default layout of {@link GuidedActionsStylist}. |
| * |
| * @see GuidedActionsStylist |
| * @see #onProvideFragmentTransitions() |
| * @param ft The FragmentTransaction to add shared element. |
| * @param disappearing The disappearing fragment. |
| */ |
| protected void onAddSharedElementTransition(FragmentTransaction ft, GuidedStepSupportFragment |
| disappearing) { |
| View fragmentView = disappearing.getView(); |
| addNonNullSharedElementTransition(ft, fragmentView.findViewById( |
| R.id.action_fragment_root), "action_fragment_root"); |
| addNonNullSharedElementTransition(ft, fragmentView.findViewById( |
| R.id.action_fragment_background), "action_fragment_background"); |
| addNonNullSharedElementTransition(ft, fragmentView.findViewById( |
| R.id.action_fragment), "action_fragment"); |
| addNonNullSharedElementTransition(ft, fragmentView.findViewById( |
| R.id.guidedactions_root), "guidedactions_root"); |
| addNonNullSharedElementTransition(ft, fragmentView.findViewById( |
| R.id.guidedactions_content), "guidedactions_content"); |
| addNonNullSharedElementTransition(ft, fragmentView.findViewById( |
| R.id.guidedactions_list_background), "guidedactions_list_background"); |
| addNonNullSharedElementTransition(ft, fragmentView.findViewById( |
| R.id.guidedactions_root2), "guidedactions_root2"); |
| addNonNullSharedElementTransition(ft, fragmentView.findViewById( |
| R.id.guidedactions_content2), "guidedactions_content2"); |
| addNonNullSharedElementTransition(ft, fragmentView.findViewById( |
| R.id.guidedactions_list_background2), "guidedactions_list_background2"); |
| } |
| |
| private static void addNonNullSharedElementTransition (FragmentTransaction ft, View subView, |
| String transitionName) |
| { |
| if (subView != null) |
| TransitionHelper.addSharedElement(ft, subView, transitionName); |
| } |
| |
| /** |
| * Returns BackStackEntry name for the GuidedStepSupportFragment or empty String if no entry is |
| * associated. Note {@link #UI_STYLE_ACTIVITY_ROOT} will return empty String. The method |
| * returns undefined value if the fragment is not in FragmentManager. |
| * @return BackStackEntry name for the GuidedStepSupportFragment or empty String if no entry is |
| * associated. |
| */ |
| final String generateStackEntryName() { |
| return generateStackEntryName(getUiStyle(), getClass()); |
| } |
| |
| /** |
| * Generates BackStackEntry name for GuidedStepSupportFragment class or empty String if no entry is |
| * associated. Note {@link #UI_STYLE_ACTIVITY_ROOT} is not allowed and returns empty String. |
| * @param uiStyle {@link #UI_STYLE_REPLACE} or {@link #UI_STYLE_ENTRANCE} |
| * @return BackStackEntry name for the GuidedStepSupportFragment or empty String if no entry is |
| * associated. |
| */ |
| static String generateStackEntryName(int uiStyle, Class guidedStepFragmentClass) { |
| switch (uiStyle) { |
| case UI_STYLE_REPLACE: |
| return ENTRY_NAME_REPLACE + guidedStepFragmentClass.getName(); |
| case UI_STYLE_ENTRANCE: |
| return ENTRY_NAME_ENTRANCE + guidedStepFragmentClass.getName(); |
| case UI_STYLE_ACTIVITY_ROOT: |
| default: |
| return ""; |
| } |
| } |
| |
| /** |
| * Returns true if the backstack entry represents GuidedStepSupportFragment with |
| * {@link #UI_STYLE_ENTRANCE}, i.e. this is the first GuidedStepSupportFragment pushed to stack; false |
| * otherwise. |
| * @see #generateStackEntryName(int, Class) |
| * @param backStackEntryName Name of BackStackEntry. |
| * @return True if the backstack represents GuidedStepSupportFragment with {@link #UI_STYLE_ENTRANCE}; |
| * false otherwise. |
| */ |
| static boolean isStackEntryUiStyleEntrance(String backStackEntryName) { |
| return backStackEntryName != null && backStackEntryName.startsWith(ENTRY_NAME_ENTRANCE); |
| } |
| |
| /** |
| * Extract Class name from BackStackEntry name. |
| * @param backStackEntryName Name of BackStackEntry. |
| * @return Class name of GuidedStepSupportFragment. |
| */ |
| static String getGuidedStepSupportFragmentClassName(String backStackEntryName) { |
| if (backStackEntryName.startsWith(ENTRY_NAME_REPLACE)) { |
| return backStackEntryName.substring(ENTRY_NAME_REPLACE.length()); |
| } else if (backStackEntryName.startsWith(ENTRY_NAME_ENTRANCE)) { |
| return backStackEntryName.substring(ENTRY_NAME_ENTRANCE.length()); |
| } else { |
| return ""; |
| } |
| } |
| |
| /** |
| * Adds the specified GuidedStepSupportFragment as content of Activity; no backstack entry is added so |
| * the activity will be dismissed when BACK key is pressed. The method is typically called in |
| * Activity.onCreate() when savedInstanceState is null. When savedInstanceState is not null, |
| * the Activity is being restored, do not call addAsRoot() to duplicate the Fragment restored |
| * by FragmentManager. |
| * {@link #UI_STYLE_ACTIVITY_ROOT} is assigned. |
| * |
| * Note: currently fragments added using this method must be created programmatically rather |
| * than via XML. |
| * @param activity The Activity to be used to insert GuidedstepFragment. |
| * @param fragment The GuidedStepSupportFragment to be inserted into the fragment stack. |
| * @param id The id of container to add GuidedStepSupportFragment, can be android.R.id.content. |
| * @return The ID returned by the call FragmentTransaction.commit, or -1 there is already |
| * GuidedStepSupportFragment. |
| */ |
| public static int addAsRoot(FragmentActivity activity, GuidedStepSupportFragment fragment, int id) { |
| // Workaround b/23764120: call getDecorView() to force requestFeature of ActivityTransition. |
| activity.getWindow().getDecorView(); |
| FragmentManager fragmentManager = activity.getSupportFragmentManager(); |
| if (fragmentManager.findFragmentByTag(TAG_LEAN_BACK_ACTIONS_FRAGMENT) != null) { |
| Log.w(TAG, "Fragment is already exists, likely calling " |
| + "addAsRoot() when savedInstanceState is not null in Activity.onCreate()."); |
| return -1; |
| } |
| FragmentTransaction ft = fragmentManager.beginTransaction(); |
| fragment.setUiStyle(UI_STYLE_ACTIVITY_ROOT); |
| return ft.replace(id, fragment, TAG_LEAN_BACK_ACTIONS_FRAGMENT).commit(); |
| } |
| |
| /** |
| * Returns the current GuidedStepSupportFragment on the fragment transaction stack. |
| * @return The current GuidedStepSupportFragment, if any, on the fragment transaction stack. |
| */ |
| public static GuidedStepSupportFragment getCurrentGuidedStepSupportFragment(FragmentManager fm) { |
| Fragment f = fm.findFragmentByTag(TAG_LEAN_BACK_ACTIONS_FRAGMENT); |
| if (f instanceof GuidedStepSupportFragment) { |
| return (GuidedStepSupportFragment) f; |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the GuidanceStylist that displays guidance information for the user. |
| * @return The GuidanceStylist for this fragment. |
| */ |
| public GuidanceStylist getGuidanceStylist() { |
| return mGuidanceStylist; |
| } |
| |
| /** |
| * Returns the GuidedActionsStylist that displays the actions the user may take. |
| * @return The GuidedActionsStylist for this fragment. |
| */ |
| public GuidedActionsStylist getGuidedActionsStylist() { |
| return mActionsStylist; |
| } |
| |
| /** |
| * Returns the list of button GuidedActions that the user may take in this fragment. |
| * @return The list of button GuidedActions for this fragment. |
| */ |
| public List<GuidedAction> getButtonActions() { |
| return mButtonActions; |
| } |
| |
| /** |
| * Find button GuidedAction by Id. |
| * @param id Id of the button action to search. |
| * @return GuidedAction object or null if not found. |
| */ |
| public GuidedAction findButtonActionById(long id) { |
| int index = findButtonActionPositionById(id); |
| return index >= 0 ? mButtonActions.get(index) : null; |
| } |
| |
| /** |
| * Find button GuidedAction position in array by Id. |
| * @param id Id of the button action to search. |
| * @return position of GuidedAction object in array or -1 if not found. |
| */ |
| public int findButtonActionPositionById(long id) { |
| if (mButtonActions != null) { |
| for (int i = 0; i < mButtonActions.size(); i++) { |
| GuidedAction action = mButtonActions.get(i); |
| if (mButtonActions.get(i).getId() == id) { |
| return i; |
| } |
| } |
| } |
| return -1; |
| } |
| |
| /** |
| * Returns the GuidedActionsStylist that displays the button actions the user may take. |
| * @return The GuidedActionsStylist for this fragment. |
| */ |
| public GuidedActionsStylist getGuidedButtonActionsStylist() { |
| return mButtonActionsStylist; |
| } |
| |
| /** |
| * Sets the list of button GuidedActions that the user may take in this fragment. |
| * @param actions The list of button GuidedActions for this fragment. |
| */ |
| public void setButtonActions(List<GuidedAction> actions) { |
| mButtonActions = actions; |
| if (mButtonAdapter != null) { |
| mButtonAdapter.setActions(mButtonActions); |
| } |
| } |
| |
| /** |
| * Notify an button action has changed and update its UI. |
| * @param position Position of the button GuidedAction in array. |
| */ |
| public void notifyButtonActionChanged(int position) { |
| if (mButtonAdapter != null) { |
| mButtonAdapter.notifyItemChanged(position); |
| } |
| } |
| |
| /** |
| * Returns the view corresponding to the button action at the indicated position in the list of |
| * actions for this fragment. |
| * @param position The integer position of the button action of interest. |
| * @return The View corresponding to the button action at the indicated position, or null if |
| * that action is not currently onscreen. |
| */ |
| public View getButtonActionItemView(int position) { |
| final RecyclerView.ViewHolder holder = mButtonActionsStylist.getActionsGridView() |
| .findViewHolderForPosition(position); |
| return holder == null ? null : holder.itemView; |
| } |
| |
| /** |
| * Scrolls the action list to the position indicated, selecting that button action's view. |
| * @param position The integer position of the button action of interest. |
| */ |
| public void setSelectedButtonActionPosition(int position) { |
| mButtonActionsStylist.getActionsGridView().setSelectedPosition(position); |
| } |
| |
| /** |
| * Returns the position if the currently selected button GuidedAction. |
| * @return position The integer position of the currently selected button action. |
| */ |
| public int getSelectedButtonActionPosition() { |
| return mButtonActionsStylist.getActionsGridView().getSelectedPosition(); |
| } |
| |
| /** |
| * Returns the list of GuidedActions that the user may take in this fragment. |
| * @return The list of GuidedActions for this fragment. |
| */ |
| public List<GuidedAction> getActions() { |
| return mActions; |
| } |
| |
| /** |
| * Find GuidedAction by Id. |
| * @param id Id of the action to search. |
| * @return GuidedAction object or null if not found. |
| */ |
| public GuidedAction findActionById(long id) { |
| int index = findActionPositionById(id); |
| return index >= 0 ? mActions.get(index) : null; |
| } |
| |
| /** |
| * Find GuidedAction position in array by Id. |
| * @param id Id of the action to search. |
| * @return position of GuidedAction object in array or -1 if not found. |
| */ |
| public int findActionPositionById(long id) { |
| if (mActions != null) { |
| for (int i = 0; i < mActions.size(); i++) { |
| GuidedAction action = mActions.get(i); |
| if (mActions.get(i).getId() == id) { |
| return i; |
| } |
| } |
| } |
| return -1; |
| } |
| |
| /** |
| * Sets the list of GuidedActions that the user may take in this fragment. |
| * @param actions The list of GuidedActions for this fragment. |
| */ |
| public void setActions(List<GuidedAction> actions) { |
| mActions = actions; |
| if (mAdapter != null) { |
| mAdapter.setActions(mActions); |
| } |
| } |
| |
| /** |
| * Notify an action has changed and update its UI. |
| * @param position Position of the GuidedAction in array. |
| */ |
| public void notifyActionChanged(int position) { |
| if (mAdapter != null) { |
| mAdapter.notifyItemChanged(position); |
| } |
| } |
| |
| /** |
| * Returns the view corresponding to the action at the indicated position in the list of |
| * actions for this fragment. |
| * @param position The integer position of the action of interest. |
| * @return The View corresponding to the action at the indicated position, or null if that |
| * action is not currently onscreen. |
| */ |
| public View getActionItemView(int position) { |
| final RecyclerView.ViewHolder holder = mActionsStylist.getActionsGridView() |
| .findViewHolderForPosition(position); |
| return holder == null ? null : holder.itemView; |
| } |
| |
| /** |
| * Scrolls the action list to the position indicated, selecting that action's view. |
| * @param position The integer position of the action of interest. |
| */ |
| public void setSelectedActionPosition(int position) { |
| mActionsStylist.getActionsGridView().setSelectedPosition(position); |
| } |
| |
| /** |
| * Returns the position if the currently selected GuidedAction. |
| * @return position The integer position of the currently selected action. |
| */ |
| public int getSelectedActionPosition() { |
| return mActionsStylist.getActionsGridView().getSelectedPosition(); |
| } |
| |
| /** |
| * Called by Constructor to provide fragment transitions. The default implementation assigns |
| * transitions based on {@link #getUiStyle()}: |
| * <ul> |
| * <li> {@link #UI_STYLE_REPLACE} Slide from/to end(right) for enter transition, slide from/to |
| * start(left) for exit transition, shared element enter transition is set to ChangeBounds. |
| * <li> {@link #UI_STYLE_ENTRANCE} Enter transition is set to slide from both sides, exit |
| * transition is same as {@link #UI_STYLE_REPLACE}, no shared element enter transition. |
| * <li> {@link #UI_STYLE_ACTIVITY_ROOT} Enter transition is set to null and app should rely on |
| * activity transition, exit transition is same as {@link #UI_STYLE_REPLACE}, no shared element |
| * enter transition. |
| * </ul> |
| * <p> |
| * The default implementation heavily relies on {@link GuidedActionsStylist} and |
| * {@link GuidanceStylist} layout, app may override this method when modifying the default |
| * layout of {@link GuidedActionsStylist} or {@link GuidanceStylist}. |
| * <p> |
| * TIP: because the fragment view is removed during fragment transition, in general app cannot |
| * use two Visibility transition together. Workaround is to create your own Visibility |
| * transition that controls multiple animators (e.g. slide and fade animation in one Transition |
| * class). |
| */ |
| protected void onProvideFragmentTransitions() { |
| if (Build.VERSION.SDK_INT >= 21) { |
| final int uiStyle = getUiStyle(); |
| if (uiStyle == UI_STYLE_REPLACE) { |
| Object enterTransition = TransitionHelper.createFadeAndShortSlide(Gravity.END); |
| TransitionHelper.exclude(enterTransition, R.id.guidedstep_background, true); |
| TransitionHelper.exclude(enterTransition, R.id.guidedactions_sub_list_background, |
| true); |
| TransitionHelper.setEnterTransition(this, enterTransition); |
| |
| Object fade = TransitionHelper.createFadeTransition( |
| TransitionHelper.FADE_IN | TransitionHelper.FADE_OUT); |
| TransitionHelper.include(fade, R.id.guidedactions_sub_list_background); |
| Object changeBounds = TransitionHelper.createChangeBounds(false); |
| Object sharedElementTransition = TransitionHelper.createTransitionSet(false); |
| TransitionHelper.addTransition(sharedElementTransition, fade); |
| TransitionHelper.addTransition(sharedElementTransition, changeBounds); |
| TransitionHelper.setSharedElementEnterTransition(this, sharedElementTransition); |
| } else if (uiStyle == UI_STYLE_ENTRANCE) { |
| if (entranceTransitionType == SLIDE_FROM_SIDE) { |
| Object fade = TransitionHelper.createFadeTransition( |
| TransitionHelper.FADE_IN | TransitionHelper.FADE_OUT); |
| TransitionHelper.include(fade, R.id.guidedstep_background); |
| Object slideFromSide = TransitionHelper.createFadeAndShortSlide( |
| Gravity.END | Gravity.START); |
| TransitionHelper.include(slideFromSide, R.id.content_fragment); |
| TransitionHelper.include(slideFromSide, R.id.action_fragment_root); |
| Object enterTransition = TransitionHelper.createTransitionSet(false); |
| TransitionHelper.addTransition(enterTransition, fade); |
| TransitionHelper.addTransition(enterTransition, slideFromSide); |
| TransitionHelper.setEnterTransition(this, enterTransition); |
| } else { |
| Object slideFromBottom = TransitionHelper.createFadeAndShortSlide( |
| Gravity.BOTTOM); |
| TransitionHelper.include(slideFromBottom, R.id.guidedstep_background_view_root); |
| Object enterTransition = TransitionHelper.createTransitionSet(false); |
| TransitionHelper.addTransition(enterTransition, slideFromBottom); |
| TransitionHelper.setEnterTransition(this, enterTransition); |
| } |
| // No shared element transition |
| TransitionHelper.setSharedElementEnterTransition(this, null); |
| } else if (uiStyle == UI_STYLE_ACTIVITY_ROOT) { |
| // for Activity root, we don't need enter transition, use activity transition |
| TransitionHelper.setEnterTransition(this, null); |
| // No shared element transition |
| TransitionHelper.setSharedElementEnterTransition(this, null); |
| } |
| // exitTransition is same for all style |
| Object exitTransition = TransitionHelper.createFadeAndShortSlide(Gravity.START); |
| TransitionHelper.exclude(exitTransition, R.id.guidedstep_background, true); |
| TransitionHelper.exclude(exitTransition, R.id.guidedactions_sub_list_background, |
| true); |
| TransitionHelper.setExitTransition(this, exitTransition); |
| } |
| } |
| |
| /** |
| * Called by onCreateView to inflate background view. Default implementation loads view |
| * from {@link R.layout#lb_guidedstep_background} which holds a reference to |
| * guidedStepBackground. |
| * @param inflater LayoutInflater to load background view. |
| * @param container Parent view of background view. |
| * @param savedInstanceState |
| * @return Created background view or null if no background. |
| */ |
| public View onCreateBackgroundView(LayoutInflater inflater, ViewGroup container, |
| Bundle savedInstanceState) { |
| return inflater.inflate(R.layout.lb_guidedstep_background, container, false); |
| } |
| |
| /** |
| * Set UI style to fragment arguments. Default value is {@link #UI_STYLE_ENTRANCE} when fragment |
| * is first initialized. UI style is used to choose different fragment transition animations and |
| * determine if this is the first GuidedStepSupportFragment on backstack. In most cases app does not |
| * directly call this method, app calls helper function |
| * {@link #add(FragmentManager, GuidedStepSupportFragment, int)}. However if the app creates Fragment |
| * transaction and controls backstack by itself, it would need call setUiStyle() to select the |
| * fragment transition to use. |
| * |
| * @param style {@link #UI_STYLE_ACTIVITY_ROOT} {@link #UI_STYLE_REPLACE} or |
| * {@link #UI_STYLE_ENTRANCE}. |
| */ |
| public void setUiStyle(int style) { |
| int oldStyle = getUiStyle(); |
| Bundle arguments = getArguments(); |
| boolean isNew = false; |
| if (arguments == null) { |
| arguments = new Bundle(); |
| isNew = true; |
| } |
| arguments.putInt(EXTRA_UI_STYLE, style); |
| // call setArgument() will validate if the fragment is already added. |
| if (isNew) { |
| setArguments(arguments); |
| } |
| if (style != oldStyle) { |
| onProvideFragmentTransitions(); |
| } |
| } |
| |
| /** |
| * Read UI style from fragment arguments. Default value is {@link #UI_STYLE_ENTRANCE} when |
| * fragment is first initialized. UI style is used to choose different fragment transition |
| * animations and determine if this is the first GuidedStepSupportFragment on backstack. |
| * |
| * @return {@link #UI_STYLE_ACTIVITY_ROOT} {@link #UI_STYLE_REPLACE} or |
| * {@link #UI_STYLE_ENTRANCE}. |
| * @see #onProvideFragmentTransitions() |
| */ |
| public int getUiStyle() { |
| Bundle b = getArguments(); |
| if (b == null) return UI_STYLE_ENTRANCE; |
| return b.getInt(EXTRA_UI_STYLE, UI_STYLE_ENTRANCE); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void onCreate(Bundle savedInstanceState) { |
| super.onCreate(savedInstanceState); |
| if (DEBUG) Log.v(TAG, "onCreate"); |
| // Set correct transition from saved arguments. |
| onProvideFragmentTransitions(); |
| |
| ArrayList<GuidedAction> actions = new ArrayList<GuidedAction>(); |
| onCreateActions(actions, savedInstanceState); |
| if (savedInstanceState != null) { |
| onRestoreActions(actions, savedInstanceState); |
| } |
| setActions(actions); |
| ArrayList<GuidedAction> buttonActions = new ArrayList<GuidedAction>(); |
| onCreateButtonActions(buttonActions, savedInstanceState); |
| if (savedInstanceState != null) { |
| onRestoreButtonActions(buttonActions, savedInstanceState); |
| } |
| setButtonActions(buttonActions); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void onDestroyView() { |
| mGuidanceStylist.onDestroyView(); |
| mActionsStylist.onDestroyView(); |
| mButtonActionsStylist.onDestroyView(); |
| mAdapter = null; |
| mSubAdapter = null; |
| mButtonAdapter = null; |
| mAdapterGroup = null; |
| super.onDestroyView(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public View onCreateView(LayoutInflater inflater, ViewGroup container, |
| Bundle savedInstanceState) { |
| if (DEBUG) Log.v(TAG, "onCreateView"); |
| |
| resolveTheme(); |
| inflater = getThemeInflater(inflater); |
| |
| GuidedStepRootLayout root = (GuidedStepRootLayout) inflater.inflate( |
| R.layout.lb_guidedstep_fragment, container, false); |
| |
| root.setFocusOutStart(isFocusOutStartAllowed()); |
| root.setFocusOutEnd(isFocusOutEndAllowed()); |
| |
| ViewGroup guidanceContainer = (ViewGroup) root.findViewById(R.id.content_fragment); |
| ViewGroup actionContainer = (ViewGroup) root.findViewById(R.id.action_fragment); |
| ((NonOverlappingLinearLayout) actionContainer).setFocusableViewAvailableFixEnabled(true); |
| |
| Guidance guidance = onCreateGuidance(savedInstanceState); |
| View guidanceView = mGuidanceStylist.onCreateView(inflater, guidanceContainer, guidance); |
| guidanceContainer.addView(guidanceView); |
| |
| View actionsView = mActionsStylist.onCreateView(inflater, actionContainer); |
| actionContainer.addView(actionsView); |
| |
| View buttonActionsView = mButtonActionsStylist.onCreateView(inflater, actionContainer); |
| actionContainer.addView(buttonActionsView); |
| |
| GuidedActionAdapter.EditListener editListener = new GuidedActionAdapter.EditListener() { |
| |
| @Override |
| public void onImeOpen() { |
| runImeAnimations(true); |
| } |
| |
| @Override |
| public void onImeClose() { |
| runImeAnimations(false); |
| } |
| |
| @Override |
| public long onGuidedActionEditedAndProceed(GuidedAction action) { |
| return GuidedStepSupportFragment.this.onGuidedActionEditedAndProceed(action); |
| } |
| |
| @Override |
| public void onGuidedActionEditCanceled(GuidedAction action) { |
| GuidedStepSupportFragment.this.onGuidedActionEditCanceled(action); |
| } |
| }; |
| |
| mAdapter = new GuidedActionAdapter(mActions, new GuidedActionAdapter.ClickListener() { |
| @Override |
| public void onGuidedActionClicked(GuidedAction action) { |
| GuidedStepSupportFragment.this.onGuidedActionClicked(action); |
| if (isExpanded()) { |
| collapseAction(true); |
| } else if (action.hasSubActions() || action.hasEditableActivatorView()) { |
| expandAction(action, true); |
| } |
| } |
| }, this, mActionsStylist, false); |
| mButtonAdapter = |
| new GuidedActionAdapter(mButtonActions, new GuidedActionAdapter.ClickListener() { |
| @Override |
| public void onGuidedActionClicked(GuidedAction action) { |
| GuidedStepSupportFragment.this.onGuidedActionClicked(action); |
| } |
| }, this, mButtonActionsStylist, false); |
| mSubAdapter = new GuidedActionAdapter(null, new GuidedActionAdapter.ClickListener() { |
| @Override |
| public void onGuidedActionClicked(GuidedAction action) { |
| if (mActionsStylist.isInExpandTransition()) { |
| return; |
| } |
| if (GuidedStepSupportFragment.this.onSubGuidedActionClicked(action)) { |
| collapseSubActions(); |
| } |
| } |
| }, this, mActionsStylist, true); |
| mAdapterGroup = new GuidedActionAdapterGroup(); |
| mAdapterGroup.addAdpter(mAdapter, mButtonAdapter); |
| mAdapterGroup.addAdpter(mSubAdapter, null); |
| mAdapterGroup.setEditListener(editListener); |
| mActionsStylist.setEditListener(editListener); |
| |
| mActionsStylist.getActionsGridView().setAdapter(mAdapter); |
| if (mActionsStylist.getSubActionsGridView() != null) { |
| mActionsStylist.getSubActionsGridView().setAdapter(mSubAdapter); |
| } |
| mButtonActionsStylist.getActionsGridView().setAdapter(mButtonAdapter); |
| if (mButtonActions.size() == 0) { |
| // when there is no button actions, we don't need show the second panel, but keep |
| // the width zero to run ChangeBounds transition. |
| LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) |
| buttonActionsView.getLayoutParams(); |
| lp.weight = 0; |
| buttonActionsView.setLayoutParams(lp); |
| } else { |
| // when there are two actions panel, we need adjust the weight of action to |
| // guidedActionContentWidthWeightTwoPanels. |
| Context ctx = mThemeWrapper != null ? mThemeWrapper : getContext(); |
| TypedValue typedValue = new TypedValue(); |
| if (ctx.getTheme().resolveAttribute(R.attr.guidedActionContentWidthWeightTwoPanels, |
| typedValue, true)) { |
| View actionsRoot = root.findViewById(R.id.action_fragment_root); |
| float weight = typedValue.getFloat(); |
| LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) actionsRoot |
| .getLayoutParams(); |
| lp.weight = weight; |
| actionsRoot.setLayoutParams(lp); |
| } |
| } |
| |
| // Add the background view. |
| View backgroundView = onCreateBackgroundView(inflater, root, savedInstanceState); |
| if (backgroundView != null) { |
| FrameLayout backgroundViewRoot = (FrameLayout)root.findViewById( |
| R.id.guidedstep_background_view_root); |
| backgroundViewRoot.addView(backgroundView, 0); |
| } |
| |
| return root; |
| } |
| |
| @Override |
| public void onResume() { |
| super.onResume(); |
| getView().findViewById(R.id.action_fragment).requestFocus(); |
| } |
| |
| /** |
| * Get the key will be used to save GuidedAction with Fragment. |
| * @param action GuidedAction to get key. |
| * @return Key to save the GuidedAction. |
| */ |
| final String getAutoRestoreKey(GuidedAction action) { |
| return EXTRA_ACTION_PREFIX + action.getId(); |
| } |
| |
| /** |
| * Get the key will be used to save GuidedAction with Fragment. |
| * @param action GuidedAction to get key. |
| * @return Key to save the GuidedAction. |
| */ |
| final String getButtonAutoRestoreKey(GuidedAction action) { |
| return EXTRA_BUTTON_ACTION_PREFIX + action.getId(); |
| } |
| |
| final static boolean isSaveEnabled(GuidedAction action) { |
| return action.isAutoSaveRestoreEnabled() && action.getId() != GuidedAction.NO_ID; |
| } |
| |
| final void onRestoreActions(List<GuidedAction> actions, Bundle savedInstanceState) { |
| for (int i = 0, size = actions.size(); i < size; i++) { |
| GuidedAction action = actions.get(i); |
| if (isSaveEnabled(action)) { |
| action.onRestoreInstanceState(savedInstanceState, getAutoRestoreKey(action)); |
| } |
| } |
| } |
| |
| final void onRestoreButtonActions(List<GuidedAction> actions, Bundle savedInstanceState) { |
| for (int i = 0, size = actions.size(); i < size; i++) { |
| GuidedAction action = actions.get(i); |
| if (isSaveEnabled(action)) { |
| action.onRestoreInstanceState(savedInstanceState, getButtonAutoRestoreKey(action)); |
| } |
| } |
| } |
| |
| final void onSaveActions(List<GuidedAction> actions, Bundle outState) { |
| for (int i = 0, size = actions.size(); i < size; i++) { |
| GuidedAction action = actions.get(i); |
| if (isSaveEnabled(action)) { |
| action.onSaveInstanceState(outState, getAutoRestoreKey(action)); |
| } |
| } |
| } |
| |
| final void onSaveButtonActions(List<GuidedAction> actions, Bundle outState) { |
| for (int i = 0, size = actions.size(); i < size; i++) { |
| GuidedAction action = actions.get(i); |
| if (isSaveEnabled(action)) { |
| action.onSaveInstanceState(outState, getButtonAutoRestoreKey(action)); |
| } |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void onSaveInstanceState(Bundle outState) { |
| super.onSaveInstanceState(outState); |
| onSaveActions(mActions, outState); |
| onSaveButtonActions(mButtonActions, outState); |
| } |
| |
| private static boolean isGuidedStepTheme(Context context) { |
| int resId = R.attr.guidedStepThemeFlag; |
| TypedValue typedValue = new TypedValue(); |
| boolean found = context.getTheme().resolveAttribute(resId, typedValue, true); |
| if (DEBUG) Log.v(TAG, "Found guided step theme flag? " + found); |
| return found && typedValue.type == TypedValue.TYPE_INT_BOOLEAN && typedValue.data != 0; |
| } |
| |
| /** |
| * Convenient method to close GuidedStepSupportFragments on top of other content or finish Activity if |
| * GuidedStepSupportFragments were started in a separate activity. Pops all stack entries including |
| * {@link #UI_STYLE_ENTRANCE}; if {@link #UI_STYLE_ENTRANCE} is not found, finish the activity. |
| * Note that this method must be paired with {@link #add(FragmentManager, GuidedStepSupportFragment, |
| * int)} which sets up the stack entry name for finding which fragment we need to pop back to. |
| */ |
| public void finishGuidedStepSupportFragments() { |
| final FragmentManager fragmentManager = getFragmentManager(); |
| final int entryCount = fragmentManager.getBackStackEntryCount(); |
| if (entryCount > 0) { |
| for (int i = entryCount - 1; i >= 0; i--) { |
| BackStackEntry entry = fragmentManager.getBackStackEntryAt(i); |
| if (isStackEntryUiStyleEntrance(entry.getName())) { |
| GuidedStepSupportFragment top = getCurrentGuidedStepSupportFragment(fragmentManager); |
| if (top != null) { |
| top.setUiStyle(UI_STYLE_ENTRANCE); |
| } |
| fragmentManager.popBackStackImmediate(entry.getId(), |
| FragmentManager.POP_BACK_STACK_INCLUSIVE); |
| return; |
| } |
| } |
| } |
| ActivityCompat.finishAfterTransition(getActivity()); |
| } |
| |
| /** |
| * Convenient method to pop to fragment with Given class. |
| * @param guidedStepFragmentClass Name of the Class of GuidedStepSupportFragment to pop to. |
| * @param flags Either 0 or {@link FragmentManager#POP_BACK_STACK_INCLUSIVE}. |
| */ |
| public void popBackStackToGuidedStepSupportFragment(Class guidedStepFragmentClass, int flags) { |
| if (!GuidedStepSupportFragment.class.isAssignableFrom(guidedStepFragmentClass)) { |
| return; |
| } |
| final FragmentManager fragmentManager = getFragmentManager(); |
| final int entryCount = fragmentManager.getBackStackEntryCount(); |
| String className = guidedStepFragmentClass.getName(); |
| if (entryCount > 0) { |
| for (int i = entryCount - 1; i >= 0; i--) { |
| BackStackEntry entry = fragmentManager.getBackStackEntryAt(i); |
| String entryClassName = getGuidedStepSupportFragmentClassName(entry.getName()); |
| if (className.equals(entryClassName)) { |
| fragmentManager.popBackStackImmediate(entry.getId(), flags); |
| return; |
| } |
| } |
| } |
| } |
| |
| /** |
| * Returns true if allows focus out of start edge of GuidedStepSupportFragment, false otherwise. |
| * Default value is false, the reason is to disable FocusFinder to find focusable views |
| * beneath content of GuidedStepSupportFragment. Subclass may override. |
| * @return True if allows focus out of start edge of GuidedStepSupportFragment. |
| */ |
| public boolean isFocusOutStartAllowed() { |
| return false; |
| } |
| |
| /** |
| * Returns true if allows focus out of end edge of GuidedStepSupportFragment, false otherwise. |
| * Default value is false, the reason is to disable FocusFinder to find focusable views |
| * beneath content of GuidedStepSupportFragment. Subclass may override. |
| * @return True if allows focus out of end edge of GuidedStepSupportFragment. |
| */ |
| public boolean isFocusOutEndAllowed() { |
| return false; |
| } |
| |
| /** |
| * Sets the transition type to be used for {@link #UI_STYLE_ENTRANCE} animation. |
| * Currently we provide 2 different variations for animation - slide in from |
| * side (default) or bottom. |
| * |
| * Ideally we can retrieve the screen mode settings from the theme attribute |
| * {@code Theme.Leanback.GuidedStep#guidedStepHeightWeight} and use that to |
| * determine the transition. But the fragment context to retrieve the theme |
| * isn't available on platform v23 or earlier. |
| * |
| * For now clients(subclasses) can call this method inside the constructor. |
| * @hide |
| */ |
| @RestrictTo(LIBRARY_GROUP) |
| public void setEntranceTransitionType(int transitionType) { |
| this.entranceTransitionType = transitionType; |
| } |
| |
| /** |
| * Opens the provided action in edit mode and raises ime. This can be |
| * used to programmatically skip the extra click required to go into edit mode. This method |
| * can be invoked in {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}. |
| */ |
| public void openInEditMode(GuidedAction action) { |
| mActionsStylist.openInEditMode(action); |
| } |
| |
| private void resolveTheme() { |
| // Look up the guidedStepTheme in the currently specified theme. If it exists, |
| // replace the theme with its value. |
| Context context = getContext(); |
| int theme = onProvideTheme(); |
| if (theme == -1 && !isGuidedStepTheme(context)) { |
| // Look up the guidedStepTheme in the activity's currently specified theme. If it |
| // exists, replace the theme with its value. |
| int resId = R.attr.guidedStepTheme; |
| TypedValue typedValue = new TypedValue(); |
| boolean found = context.getTheme().resolveAttribute(resId, typedValue, true); |
| if (DEBUG) Log.v(TAG, "Found guided step theme reference? " + found); |
| if (found) { |
| ContextThemeWrapper themeWrapper = |
| new ContextThemeWrapper(context, typedValue.resourceId); |
| if (isGuidedStepTheme(themeWrapper)) { |
| mThemeWrapper = themeWrapper; |
| } else { |
| found = false; |
| mThemeWrapper = null; |
| } |
| } |
| if (!found) { |
| Log.e(TAG, "GuidedStepSupportFragment does not have an appropriate theme set."); |
| } |
| } else if (theme != -1) { |
| mThemeWrapper = new ContextThemeWrapper(context, theme); |
| } |
| } |
| |
| private LayoutInflater getThemeInflater(LayoutInflater inflater) { |
| if (mThemeWrapper == null) { |
| return inflater; |
| } else { |
| return inflater.cloneInContext(mThemeWrapper); |
| } |
| } |
| |
| private int getFirstCheckedAction() { |
| for (int i = 0, size = mActions.size(); i < size; i++) { |
| if (mActions.get(i).isChecked()) { |
| return i; |
| } |
| } |
| return 0; |
| } |
| |
| void runImeAnimations(boolean entering) { |
| ArrayList<Animator> animators = new ArrayList<Animator>(); |
| if (entering) { |
| mGuidanceStylist.onImeAppearing(animators); |
| mActionsStylist.onImeAppearing(animators); |
| mButtonActionsStylist.onImeAppearing(animators); |
| } else { |
| mGuidanceStylist.onImeDisappearing(animators); |
| mActionsStylist.onImeDisappearing(animators); |
| mButtonActionsStylist.onImeDisappearing(animators); |
| } |
| AnimatorSet set = new AnimatorSet(); |
| set.playTogether(animators); |
| set.start(); |
| } |
| } |