Java tutorial
/* * Copyright 2016 Matthew Tamlin * * 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 com.matthewtamlin.sliding_intro_screen_library.core; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.graphics.Color; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.view.ViewPager; import android.support.v4.view.ViewPager.OnPageChangeListener; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.FrameLayout; import android.widget.RelativeLayout; import com.matthewtamlin.android_utilities_library.helpers.StatusBarHelper; import com.matthewtamlin.android_utilities_library.helpers.ThemeColorHelper; import com.matthewtamlin.sliding_intro_screen_library.R; import com.matthewtamlin.sliding_intro_screen_library.background.BackgroundManager; import com.matthewtamlin.sliding_intro_screen_library.buttons.AnimatorFactory; import com.matthewtamlin.sliding_intro_screen_library.buttons.FadeAnimatorFactory; import com.matthewtamlin.sliding_intro_screen_library.buttons.IntroButton; import com.matthewtamlin.sliding_intro_screen_library.buttons.IntroButton.Appearance; import com.matthewtamlin.sliding_intro_screen_library.buttons.IntroButton.Behaviour; import com.matthewtamlin.sliding_intro_screen_library.buttons.IntroButtonAccessor; import com.matthewtamlin.sliding_intro_screen_library.core.LockableViewPager.LockMode; import com.matthewtamlin.sliding_intro_screen_library.indicators.DotIndicator; import com.matthewtamlin.sliding_intro_screen_library.indicators.SelectionIndicator; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; /** * Displays an introduction screen to the user, consisting of a series of pages and a navigation * bar. The pages display the content of the introduction screen, and the navigation bar displays * the user's progress through the activity. The navigation bar also provides three configurable * buttons for moving through the activity and performing other functions. It is recommended that * the manifest entry declare {@code android:noHistory="true"} to prevent the user from navigating * back to this activity once it is complete. * <p/> * To use this class, subclass it and implement {@link #generatePages(Bundle)} and {@link * #generateFinalButtonBehaviour()}. The former method is called by onCreate method generates the * pages (i.e. Fragments) displayed in the introduction. Pages cannot be added or removed after this * method returns, however they may still be modified. The latter method is called by onCreate to * generate the Behaviour to assign to the button shown on the last page (see {@link IntroButton}). * It is recommended that an instance of the {@link IntroButton.ProgressToNextActivity} class be * used. * <p/> * The navigation bar contains three buttons: a left button, a right button and a final button. By * default the left and right buttons are present on all pages but the last, and the final button is * displayed only on the last page. The left button can be displayed on the last page by calling * {@link #disableLeftButtonOnLastPage(boolean)}. By default, the left button skips ahead to the * last page and the right button moves to the next page. The behaviour of the final button is * generated in {@link #generateFinalButtonBehaviour()}. The behaviour and appearance of each button * can be changed using the methods of this Activity. Whenever buttons appear or disappear, they are * animated using the {@link AnimatorFactory} supplied by {@link #generateButtonAnimatorFactory}. * The default animations cause the buttons to fade in and out, however this behaviour can be * changed by overriding {@code generateButtonAnimatorFactory()} and returning a custom * AnimatorFactory. * <p/> * Unless the individual page Fragments define their own backgrounds, it is highly recommended that * the background of the IntroActivity be changed. The background of an IntroActivity can be changed * in two ways: by manually changing the root View (via {@link #getRootView()}), or by supplying a * BackgroundManager to {@link #setBackgroundManager(BackgroundManager)}. The former approach is * simpler and less error prone, and is ideal when a static background is all that is needed. The * latter approach is ideal when a dynamic background is desired. The {@link * com.matthewtamlin.sliding_intro_screen_library.background.ColorBlender} class is provided to * simplify implementation of a dynamic background. * <p/> * The methods of this activity provide the following additional customisation options: * <ul><li>Hiding/showing the status bar.</li> <li>Programmatically changing the page.</li> * <li>Locking the page.</li> <li>Modifying/replacing the progress indicator.</li> <li>Setting a * page transformer.</li> <li>Obtaining references to the individual pages.</li></ul> */ public abstract class IntroActivity extends AppCompatActivity { // Constants /** * Used to identify this class during debugging. */ @SuppressWarnings("unused") private static final String TAG = "[IntroActivity]"; /** * Key for saving and restoring the current page on configuration changes. */ private static final String STATE_KEY_CURRENT_PAGE_INDEX = "current page index"; /** * The page index to use when there is no state to restore. */ private static final int DEFAULT_CURRENT_PAGE_INDEX = 0; /** * The Appearance to use for the left button until it is explicitly set. */ private static final Appearance DEFAULT_LEFT_BUTTON_APPEARANCE = Appearance.TEXT_ONLY; /** * The Appearance to use for the right button until it is explicitly set. */ private static final Appearance DEFAULT_RIGHT_BUTTON_APPEARANCE = Appearance.ICON_ONLY; /** * The appearance to use for the final button until it is explicitly set. */ private static final Appearance DEFAULT_FINAL_BUTTON_APPEARANCE = Appearance.TEXT_ONLY; /** * The text to display in the final button until it is explicitly set. */ private static final CharSequence DEFAULT_FINAL_BUTTON_TEXT = "DONE"; /** * The length of time to use for button appear/disappear animations, measured in milliseconds. */ private static final int BUTTON_ANIMATION_DURATION_MS = 150; /** * The Behaviour to use for the left button until it is explicitly set. */ private final Behaviour DEFAULT_LEFT_BUTTON_BEHAVIOUR = new IntroButton.GoToLastPage(); /** * The Behaviour to use for the right button until it is explicitly set. */ private final Behaviour DEFAULT_RIGHT_BUTTON_BEHAVIOUR = new IntroButton.GoToNextPage(); // Miscellaneous View handles /** * The root view of the View hierarchy. */ private RelativeLayout rootView; /** * Displays the pages to the user. */ private LockableViewPager viewPager; /** * The thin horizontal divider separating the navigation elements from the rest of the UI. */ private View horizontalDivider; // Progress indicator variables /** * Wrapper for the selection indicator. */ private FrameLayout progressIndicatorWrapper; /** * Displays the user's progress through the intro screen. */ private SelectionIndicator progressIndicator; /** * Whether or not changes in the progress indicator should be animated. */ private boolean progressIndicatorAnimationsEnabled = true; // Button variables /** * The IntroButton displayed in the left end of the navigation bar. This button is hidden on the * last page by default but can be shown using {@link #disableLeftButtonOnLastPage(boolean)}. */ private IntroButton leftButton; /** * The IntroButton displayed in the right end of the navigation bar. This button is not shown on * the last page. */ private IntroButton rightButton; /** * The IntroButton displayed in the right end of the navigation bar when the last page is * shown. */ private IntroButton finalButton; /** * Whether or not the left button should be disabled entirely. */ private boolean leftButtonDisabled = false; /** * Whether or not the right button should be disabled entirely. */ private boolean rightButtonDisabled = false; /** * Whether or not the final button should be disabled entirely. */ private boolean finalButtonDisabled = false; /** * Whether or not {@code leftButton} should be disabled on the last page. */ private boolean disableLeftButtonOnLastPage = true; /** * Supplies the Animators used to make the buttons appear and disappear when being enabled and * disabled. */ private AnimatorFactory buttonAnimatorFactory; /** * Maps each button to the animator currently affecting it. This allows animators to be * cancelled if a newer animator is created. If a button is not currently being animated, then * it should not exist in the keyset. */ private final HashMap<IntroButton, Animator> buttonAnimations = new HashMap<>(); // Dataset related variables /** * The pages to display. */ private final ArrayList<Fragment> pages = new ArrayList<>(); /** * Adapts the pages so that they can be displayed in the UI. */ private final PageAdapter adapter = new PageAdapter(getSupportFragmentManager(), pages); // Background manager related variables /** * Updates the background of the Activity as the pages scroll. */ private BackgroundManager backgroundManager = null; // Listener delegates /** * Page change events from {@code viewPager} are delegated to this receiver. Using a delegate as * the receiver is hides the internal implementation from the class signature. */ private final OnPageChangeListener pageChangeListenerDelegate = new OnPageChangeListener() { @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { if (backgroundManager != null) { backgroundManager.updateBackground(rootView, position, positionOffset); } } @Override public void onPageSelected(int position) { // The active page has changes, so the UI needs to be updated reflectMemberVariablesInAllButtons(); if (progressIndicator != null) { progressIndicator.setSelectedItem(position, progressIndicatorAnimationsEnabled); } } @Override public void onPageScrollStateChanged(int state) { // Forced to implement this method, just do nothing } }; // Overridden methods /** * Initialises the UI and behaviour of this activity. This method's execution calls the abstract * methods. * * @param savedInstanceState * if this activity is being re-initialized after previously being shut down, then this Bundle * contains the data this activity most recently saved in {@link * #onSaveInstanceState(Bundle)}, otherwise null */ @Override protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Initialise the UI and get references to the View elements setContentView(R.layout.activity_intro); bindViews(); // Initialise the buttons initialiseNavigationButtons(); buttonAnimatorFactory = generateButtonAnimatorFactory(); // Generate the pages and create a copy to avoid external changes to the dataset pages.addAll(generatePages(savedInstanceState)); // Initialise the view pager viewPager.addOnPageChangeListener(pageChangeListenerDelegate); initialiseViewPager(savedInstanceState); // Initialise the progress indicator progressIndicator = new DotIndicator(this); regenerateProgressIndicator(); } @Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); // Will apply animations to the left and right buttons as usual reflectMemberVariablesInAllButtons(); // When the activity is displayed (and the final page isn't shown) the final button // disappear animation needs to occur so that appear animation displays properly later final boolean lastPage = getIndexOfCurrentPage() + 1 == pages.size(); if (hasFocus && !lastPage) { final Animator finalButtonAnimator = buttonAnimatorFactory.newFinalButtonDisappearAnimator(finalButton); finalButtonAnimator.start(); } } @Override protected void onSaveInstanceState(final Bundle outState) { super.onSaveInstanceState(outState); outState.putInt(STATE_KEY_CURRENT_PAGE_INDEX, viewPager.getCurrentItem()); } /** * Called when the back button is pressed. If this activity is currently displaying the first * page or the lock mode prevents commands, the default back behaviour occurs. Otherwise, the * previous page is displayed. */ @Override public void onBackPressed() { if (viewPager.getCurrentItem() == 0 || !getPagingLockMode().allowsCommands()) { super.onBackPressed(); } else { viewPager.setCurrentItem(viewPager.getCurrentItem() - 1); } } // Private methods /** * Binds the View elements used in this activity to member variables. */ private void bindViews() { rootView = (RelativeLayout) findViewById(R.id.intro_activity_root); horizontalDivider = findViewById(R.id.intro_activity_horizontalDivider); viewPager = (LockableViewPager) findViewById(R.id.intro_activity_viewPager); progressIndicatorWrapper = (FrameLayout) findViewById(R.id.intro_activity_progressIndicatorHolder); leftButton = (IntroButton) findViewById(R.id.intro_activity_leftButton); rightButton = (IntroButton) findViewById(R.id.intro_activity_rightButton); finalButton = (IntroButton) findViewById(R.id.intro_activity_finalButton); } /** * Initialises the UI elements for displaying the current page. If this activity is being * restored, then the page which was previously displayed will be redisplayed. * * @param savedInstanceState * if this activity is being re-initialized after previously being shut down, then this Bundle * contains the data this activity most recently saved in {@link * #onSaveInstanceState(Bundle)}, otherwise null */ private void initialiseViewPager(final Bundle savedInstanceState) { // Restore the page index from the saved instance state if possible final int pageIndex = (savedInstanceState == null) ? DEFAULT_CURRENT_PAGE_INDEX : savedInstanceState.getInt(STATE_KEY_CURRENT_PAGE_INDEX, DEFAULT_CURRENT_PAGE_INDEX); // Initialise the dataset of the view pager and display the desired page viewPager.setAdapter(adapter); viewPager.setCurrentItem(pageIndex); // Make sure the background for the current page is displayed if (backgroundManager != null) { backgroundManager.updateBackground(rootView, pageIndex, 0); } } /** * Sets the Behaviour and Appearance of the buttons. This Activity is set to the Behaviour * target. */ private void initialiseNavigationButtons() { leftButton.setBehaviour(DEFAULT_LEFT_BUTTON_BEHAVIOUR); leftButton.setAppearance(DEFAULT_LEFT_BUTTON_APPEARANCE); leftButton.setActivity(this); rightButton.setBehaviour(DEFAULT_RIGHT_BUTTON_BEHAVIOUR); rightButton.setAppearance(DEFAULT_RIGHT_BUTTON_APPEARANCE); rightButton.setActivity(this); finalButton.setBehaviour(generateFinalButtonBehaviour()); finalButton.setAppearance(DEFAULT_FINAL_BUTTON_APPEARANCE); finalButton.setText(DEFAULT_FINAL_BUTTON_TEXT, null); finalButton.setActivity(this); } /** * Enables or disables each button separately, so that the buttons match the current member * variables. */ private void reflectMemberVariablesInAllButtons() { reflectMemberVariablesInLeftButton(); reflectMemberVariablesInRightButton(); reflectMemberVariablesInFinalButton(); } /** * Enables or disables the left button, so that it matches the current member variables. */ private void reflectMemberVariablesInLeftButton() { // Determine whether or not changes need to occur final boolean lastPageReached = (viewPager.getCurrentItem() + 1) == pages.size(); final boolean buttonShouldBeInvisible = (lastPageReached && disableLeftButtonOnLastPage) || leftButtonDisabled; final boolean buttonIsCurrentlyInvisible = leftButton.getVisibility() == View.INVISIBLE; final boolean shouldUpdateButton = buttonShouldBeInvisible != buttonIsCurrentlyInvisible; // Apply changes if necessary if (shouldUpdateButton) { final Animator buttonAnimator = buttonShouldBeInvisible ? buttonAnimatorFactory.newLeftButtonDisappearAnimator(leftButton) : buttonAnimatorFactory.newLeftButtonAppearAnimator(leftButton); if (buttonShouldBeInvisible) { disableButton(buttonAnimator, leftButton); } else { enableButton(buttonAnimator, leftButton); } } } /** * Enables or disables the right button, so that it matches the current member variables. */ private void reflectMemberVariablesInRightButton() { // Determine whether or not changes need to occur final boolean lastPageReached = (viewPager.getCurrentItem() + 1) == pages.size(); final boolean buttonShouldBeInvisible = lastPageReached || rightButtonDisabled; final boolean buttonIsCurrentlyInvisible = rightButton.getVisibility() == View.INVISIBLE; final boolean shouldUpdateButton = buttonShouldBeInvisible != buttonIsCurrentlyInvisible; // Apply changes if necessary if (shouldUpdateButton) { final Animator buttonAnimator = buttonShouldBeInvisible ? buttonAnimatorFactory.newRightButtonDisappearAnimator(rightButton) : buttonAnimatorFactory.newRightButtonAppearAnimator(rightButton); if (buttonShouldBeInvisible) { disableButton(buttonAnimator, rightButton); } else { enableButton(buttonAnimator, rightButton); } } } /** * Enables or disables the final button, so that it matches the current member variables. */ private void reflectMemberVariablesInFinalButton() { // Determine whether or not changes need to occur final boolean lastPageReached = (viewPager.getCurrentItem() + 1) == pages.size(); final boolean buttonShouldBeInvisible = !lastPageReached || finalButtonDisabled; final boolean buttonIsCurrentlyInvisible = finalButton.getVisibility() == View.INVISIBLE; final boolean shouldUpdateButton = buttonShouldBeInvisible != buttonIsCurrentlyInvisible; // Apply changes if necessary if (shouldUpdateButton) { final Animator buttonAnimator = buttonShouldBeInvisible ? buttonAnimatorFactory.newFinalButtonDisappearAnimator(finalButton) : buttonAnimatorFactory.newFinalButtonAppearAnimator(finalButton); if (buttonShouldBeInvisible) { disableButton(buttonAnimator, finalButton); } else { enableButton(buttonAnimator, finalButton); } } } /** * Disables the supplied button by making it invisible and un-clickable. This method should only * be called while the supplied button is enabled (i.e. visible and clickable). The supplied * Animator will be used to transition the button. * * @param buttonAnimator * the Animator to use when transitioning the button, null to perform no animation * @param button * the button to disable, not null * @throws IllegalArgumentException * if {@code button} is null */ private void disableButton(final Animator buttonAnimator, final IntroButton button) { if (button == null) { throw new IllegalArgumentException("button cannot be null"); } // Any animations currently affecting the button must be cancelled before new ones start if (buttonAnimations.containsKey(button)) { buttonAnimations.get(button).cancel(); buttonAnimations.remove(button); } if (buttonAnimator != null) { // Register animation so that it may be cancelled later if necessary buttonAnimations.put(button, buttonAnimator); // End/cancel conditions ensure that the UI is not left in a transient state when // animations finish buttonAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(final Animator animation) { button.setVisibility(View.VISIBLE); // Make sure View is visible while animating button.setEnabled(false); // Click events should be ignored immediately } @Override public void onAnimationEnd(Animator animation) { // If the animation doesn't properly hide the button, make sure it's invisible button.setVisibility(View.INVISIBLE); } @Override public void onAnimationCancel(Animator animation) { // Restore the button to enabled mode button.setVisibility(View.VISIBLE); button.setEnabled(true); } }); buttonAnimator.setDuration(BUTTON_ANIMATION_DURATION_MS); buttonAnimator.start(); } else { // If no animator was supplied, just apply the disabled conditions button.setVisibility(View.INVISIBLE); button.setEnabled(false); } } /** * Enables the supplied button by making it visible and clickable. This method should only be * called while the supplied button is disabled (i.e. invisible and un-clickable). The supplied * Animator will be used to transition the button. * * @param buttonAnimator * the Animator to use when transitioning the button, null to perform no animation * @param button * the button to enable, not null * @throws IllegalArgumentException * if {@code button} is null */ private void enableButton(final Animator buttonAnimator, final IntroButton button) { if (button == null) { throw new IllegalArgumentException("button cannot be null"); } // Any animations currently affecting the button must be cancelled before new ones start if (buttonAnimations.containsKey(button)) { buttonAnimations.get(button).cancel(); buttonAnimations.remove(button); } if (buttonAnimator != null) { buttonAnimations.put(button, buttonAnimator); // Give any disable animations time to finish before the enable animation starts buttonAnimator.setStartDelay(BUTTON_ANIMATION_DURATION_MS); // End/cancel conditions ensure that the UI is not left in a transient state when // animations finish buttonAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(final Animator animation) { button.setVisibility(View.VISIBLE); // Make sure View is visible while animating button.setEnabled(true); // Click events should be accepted immediately } @Override public void onAnimationCancel(final Animator animation) { // Restore button to disabled mode button.setVisibility(View.INVISIBLE); button.setEnabled(false); } }); buttonAnimator.setDuration(BUTTON_ANIMATION_DURATION_MS); buttonAnimator.start(); } else { // If no Animator was supplied, just apply the enabled conditions button.setVisibility(View.VISIBLE); button.setEnabled(true); } } /** * Updates the progress indicator to reflect the current member variables. */ private void regenerateProgressIndicator() { // Refresh the View by entirely removing the progress indicator so that it can be re-added progressIndicatorWrapper.removeAllViews(); // Only re-add the indicator if one currently exists if (progressIndicator != null) { progressIndicatorWrapper.addView((View) progressIndicator); // Make sure the number of pages and the displayed page is correct progressIndicator.setNumberOfItems(pages.size()); progressIndicator.setSelectedItem(getIndexOfCurrentPage(), false); } } // Abstract methods /** * Called by {@link #onCreate(Bundle)} to generate the pages displayed in this activity. The * returned Collection is copied, so further changes to the collection will have no effect after * this method returns. The total ordering of the returned collection is maintained in the * display of the pages. * * @param savedInstanceState * if this activity is being re-initialized after previously being shut down, then this Bundle * contains the data this activity most recently saved in {@link * #onSaveInstanceState(Bundle)}, otherwise null * @return the pages to display in the Activity, not null */ protected abstract Collection<? extends Fragment> generatePages(Bundle savedInstanceState); /** * Called by {@link #onCreate(Bundle)} to generate the Behaviour of the final button. The {@link * IntroButton} class contains Behaviours which suit most needs. The Behaviour of the final * button can be changed later using {@link #getFinalButtonAccessor()}. * * @return the Behaviour to use for the final button, not null */ protected abstract Behaviour generateFinalButtonBehaviour(); // Miscellaneous public methods /** * Hides the status bar background but continues to display the system icons. Views and * ViewGroups which declare the {@code android:fitsSystemWindows="false"} attribute will draw to * the top of the screen. The effect of this method varies depending on the current SDK * version. * * @deprecated use the AndroidUtilities library directly (com.matthewtamlin:android-utilities) */ @Deprecated public final void hideStatusBar() { StatusBarHelper.hideStatusBar(getWindow()); } /** * Shows the status bar background and prevents Views from being drawn behind the status bar. * The primary dark color of the current theme will be used for the status bar color (on SDK * version 21 and higher). If the current theme does not specify a primary dark color, the * status bar will be colored black. * * @deprecated use the AndroidUtilities library directly (com.matthewtamlin:android-utilities) */ @Deprecated public final void showStatusBar() { final int statusBarColor = ThemeColorHelper.getPrimaryDarkColor(this, Color.BLACK); StatusBarHelper.showStatusBar(getWindow(), statusBarColor); } /** * Returns the root View in the View hierarchy of this Activity. If this method is called before * {@code onCreate(Bundle)}, then null is returned. * * @return the root View of the View hierarchy */ public final RelativeLayout getRootView() { return rootView; } /** * Allows the visibility of the horizontal divider at the top of the navigation bar to be * changed. * * @param show * true to show the divider, false to hide it */ public final void changeHorizontalDividerVisibility(final boolean show) { horizontalDivider.setVisibility(show ? View.VISIBLE : View.INVISIBLE); } /** * Sets the PageTransformer to use when scrolling. * * @param reverseDrawingOrder * true if the supplied PageTransformer requires page Views to be drawn from last to first * instead of first to last * @param transformer * the transformer to use, null allowed */ public final void setPageTransformer(final boolean reverseDrawingOrder, final ViewPager.PageTransformer transformer) { viewPager.setPageTransformer(reverseDrawingOrder, transformer); } // Methods for listing to page changes /** * Registers a listener to receive a callback whenever the page changes. * * @param listener * the listener to register */ public final void addPageChangeListener(final OnPageChangeListener listener) { viewPager.addOnPageChangeListener(listener); } /** * Unregisters a listener to prevent it from receiving further page change callbacks. * * @param listener * the listener to unregister */ public final void removePageChangeListener(final OnPageChangeListener listener) { viewPager.removeOnPageChangeListener(listener); } // Methods relating to the pages and navigation /** * Returns an unmodifiable Collection containing the pages. * * @return the pages of this activity */ public final Collection<Fragment> getPages() { return Collections.unmodifiableCollection(pages); } /** * Returns the page at the specified index. * * @param pageIndex * the index of the page to return, counting from zero * @return the page at {@code index} * @throws IndexOutOfBoundsException * if the index exceeds the size of the page dataset */ public final Fragment getPage(final int pageIndex) { return pages.get(pageIndex); } /** * @return the page currently being displayed */ public final Fragment getCurrentPage() { return pages.get(viewPager.getCurrentItem()); } /** * @return the first page of this Activity */ public final Fragment getFirstPage() { return pages.get(0); } /** * @return the last page of this Activity */ public final Fragment getLastPage() { return pages.get(pages.size() - 1); } /** * Returns the index of the specified page, or -1 if the page does not exist in the page * Collection. * * @param page * the page to get the index of * @return the index of {@code page}, counting from zero */ public final int getIndexOfPage(final Fragment page) { return pages.indexOf(page); } /** * @return the index of the page currently being displayed, counting from zero */ public final int getIndexOfCurrentPage() { return viewPager.getCurrentItem(); } /** * Navigates to the page at the supplied index. * * @param pageIndex * the index of the page to display, counting from zero * @throws IndexOutOfBoundsException * if the index exceeds the size of the page dataset */ public final void goToPage(final int pageIndex) { viewPager.setCurrentItem(pageIndex); } /** * Navigates to the last page (if not already there). */ public final void goToLastPage() { viewPager.setCurrentItem(pages.size() - 1); } /** * Navigates to the first page (if not already there). */ public final void goToFirstPage() { viewPager.setCurrentItem(0); } /** * Navigates to the next page (if not already there). */ public final void goToNextPage() { final boolean isLastPage = viewPager.getCurrentItem() == (pages.size() - 1); if (!isLastPage) { viewPager.setCurrentItem(viewPager.getCurrentItem() + 1, true); } } /** * Navigates to the previous page (if not already there). */ public final void goToPreviousPage() { final boolean isFirstPage = viewPager.getCurrentItem() == 0; if (!isFirstPage) { viewPager.setCurrentItem(viewPager.getCurrentItem() - 1, true); } } /** * @return the current number of pages */ public final int numberOfPages() { return pages.size(); } /** * Sets the paging lock mode. This can be used to prevent the user from navigating to another * page by swiping, pressing buttons, or both. The lock mode can also disable programmatic * commends. * * @param lockMode * the lock mode to use, not null * @throws IllegalArgumentException * if {@code lockMode} is null */ public final void setPagingLockMode(final LockMode lockMode) { viewPager.setLockMode(lockMode); // throws exception is lockMode is null } /** * @return the current lock mode */ public final LockMode getPagingLockMode() { return viewPager.getLockMode(); } // Methods relating to the background manager /** * Sets the background manager to use when scrolling through pages. The {@link * BackgroundManager#updateBackground(View, int, float)} method of the supplied manager will be * invoked whenever the user scrolls. Note that the BackgroundManager draws behind the pages, * therefore the background will be obscured if the pages do not have transparent backgrounds. * * @param backgroundManager * the backgroundManager to use, null to clear any existing manager */ public final void setBackgroundManager(final BackgroundManager backgroundManager) { this.backgroundManager = backgroundManager; } /** * @return the current BackgroundManager, may be null */ public final BackgroundManager getBackgroundManager() { return backgroundManager; } // Methods relating to the progress indicator /** * Sets the selection indicator to show the user's progress through the Activity. The provided * selection indicator must be a subclass of {@link View}. * * @param selectionIndicator * the selection indicator to use, null to clear any existing indicator * @throws IllegalArgumentException * if {@code selectionIndicator} is not a View subclass */ public void setProgressIndicator(final SelectionIndicator selectionIndicator) { if (!(selectionIndicator instanceof View)) { throw new IllegalArgumentException("selectionIndicator must be a subclass of android.view.View"); } progressIndicator = selectionIndicator; regenerateProgressIndicator(); } /** * @return the current selection indicator, may be null */ public SelectionIndicator getProgressIndicator() { return progressIndicator; } /** * Enables/disables progress indicator page change animations. * * @param enableAnimations * true to enable animations, false to disable them */ public void enableProgressIndicatorAnimations(final boolean enableAnimations) { progressIndicatorAnimationsEnabled = enableAnimations; } /** * @return true if progress indicator page change animations are enabled, false otherwise */ public boolean progressIndicatorAnimationsAreEnabled() { return progressIndicatorAnimationsEnabled; } // Methods relating to the buttons /** * Called in {@code onCreate(Bundle)} to generate an AnimatorFactory for the buttons. The * factory will be used to animate the change whenever a button is enabled or disabled. The * default factory causes the buttons to fade in when enabled and fade out when disabled. * * @return an AnimatorFactory to use when buttons are enabled/disabled, not null */ protected AnimatorFactory generateButtonAnimatorFactory() { return new FadeAnimatorFactory(); } /** * Returns an an IntroButtonAccessor which can be used to modify and inspect the left button. * * @return the left button accessor, not null */ public IntroButtonAccessor getLeftButtonAccessor() { return new IntroButtonAccessor(leftButton); } /** * Returns an an IntroButtonAccessor which can be used to modify and inspect the right button. * * @return the right button accessor, not null */ public IntroButtonAccessor getRightButtonAccessor() { return new IntroButtonAccessor(rightButton); } /** * Returns an an IntroButtonAccessor which can be used to modify and inspect the final button. * * @return the final button accessor, not null */ public IntroButtonAccessor getFinalButtonAccessor() { return new IntroButtonAccessor(finalButton); } /** * Disables the left button on all pages by making it invisible and un-clickable. This method * takes precedence over {@link #disableLeftButtonOnLastPage(boolean)}. * * @param disabled * true to disable the button, false to enable it */ public final void disableLeftButton(final boolean disabled) { leftButtonDisabled = disabled; reflectMemberVariablesInLeftButton(); } /** * Sets whether or not the left button should be automatically disabled on the last page and * re-enabled when returning to a previous page. The {@link #disableLeftButton(boolean)} method * takes precedence over this method when disabling, however values passed to this method are * still stored. This means that if the left button is enabled using the other method after * false was passed to this method, then the left button will still be disabled on the last * page. * * @param disableButton * true to automatically disable the left button on the last page, false to prevent automatic * disabling */ public final void disableLeftButtonOnLastPage(final boolean disableButton) { disableLeftButtonOnLastPage = disableButton; reflectMemberVariablesInLeftButton(); } /** * Returns whether or not the left button is currently disabled on all pages. This method does * not take into account {@link #disableLeftButtonOnLastPage(boolean)} in any way. * * @return true if the button is currently entirely, false otherwise */ public final boolean leftButtonIsEntirelyDisabled() { return leftButtonDisabled; } /** * Returns whether or not the left button will be disabled when the last page is displayed. This * method does not take into account whether or not the button is has been entirely disabled * using {@link #disableLeftButton(boolean)}. * * @return true if the left button will be disabled while the last page is displayed, false * otherwise */ public final boolean leftButtonIsDisabledOnLastPage() { return disableLeftButtonOnLastPage; } /** * Disables the right button on all pages by making it invisible and un-clickable. * * @param disabled * true to disable the button, false to enable it */ public final void disableRightButton(final boolean disabled) { rightButtonDisabled = disabled; reflectMemberVariablesInRightButton(); } /** * Returns whether or not the right button is currently disabled on all pages. * * @return true if the button is currently disabled, false otherwise */ public final boolean rightButtonIsDisabled() { return rightButtonDisabled; } /** * Disables the final button on all pages by making it invisible and un-clickable. * * @param disabled * true to disable the button, false to enable it */ public final void disableFinalButton(final boolean disabled) { finalButtonDisabled = disabled; reflectMemberVariablesInFinalButton(); } /** * Returns whether or not the final button is currently disabled on all pages. * * @return true if the button is currently disabled, false otherwise */ public final boolean finalButtonIsDisabled() { return finalButtonDisabled; } }