com.matthewtamlin.sliding_intro_screen_library.core.IntroActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.matthewtamlin.sliding_intro_screen_library.core.IntroActivity.java

Source

/*
 * 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;
    }
}