com.albedinsky.android.ui.navigation.NavigationLayout.java Source code

Java tutorial

Introduction

Here is the source code for com.albedinsky.android.ui.navigation.NavigationLayout.java

Source

/*
 * =================================================================================================
 *                             Copyright (C) 2015 Martin Albedinsky
 * =================================================================================================
 *         Licensed under the Apache License, Version 2.0 or later (further "License" only).
 * -------------------------------------------------------------------------------------------------
 * You may use this file only in compliance with the License. More details and copy of this License 
 * you may obtain at
 * 
 *       http://www.apache.org/licenses/LICENSE-2.0
 * 
 * You can redistribute, modify or publish any part of the code written within this file but as it 
 * is described in the License, the software distributed under the License is distributed on an 
 * "AS IS" BASIS, WITHOUT WARRANTIES or CONDITIONS OF ANY KIND.
 * 
 * See the License for the specific language governing permissions and limitations under the License.
 * =================================================================================================
 */
package com.albedinsky.android.ui.navigation;

import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ListAdapter;
import android.widget.ListView;

import com.albedinsky.android.ui.R;

import java.util.LinkedList;

/**
 * A {@link DrawerLayout} implementation designed to present set of {@link NavigationItem NavigationItems}
 * within a simple navigation list. The navigation list is contained within navigation drawer at the
 * start (left) position of NavigationLayout.
 * <p>
 * Set of navigation items can be provided through {@link BaseNavigationAdapter} implementation that
 * can be set via {@link #setNavigationAdapter(ListAdapter)}. The navigation layout will automatically
 * handle all clicks on item views provided by such adapter and also theirs proper selection. The
 * navigation item click events will be dispatched through {@link OnNavigationItemClickListener}
 * listener that can be registered via {@link #setOnNavigationItemClickListener(OnNavigationItemClickListener)}.
 * <p>
 * The NavigationLayout also by default creates an instance of {@link NavigationHeaderView} and inserts
 * it into the navigation list as its header view. The current navigation header view can be obtained
 * via {@link #getNavigationHeaderView()}. Custom header view can be specified via {@link #setNavigationHeaderView(View)}
 * and removed via {@link #removeNavigationHeaderView()}. See {@link NavigationHeaderView} for provided
 * API allowing to bind such view with desired information.
 * <p>
 * If needed opened navigation drawer can be closed via {@link #closeNavigation()} or opened via
 * {@link #openNavigation()}.
 *
 * <h3>Navigation item click handling</h3>
 * As described above, the navigation layout will dispatch all navigation item click events to registered
 * OnNavigationItemClickListener via
 * {@link OnNavigationItemClickListener#onNavigationItemClick(NavigationLayout, View, NavigationItem, int) onNavigationItemClick(NavigationLayout, View, NavigationItem, int)}.
 * It is a good practice to schedule execution of action associated with the clicked navigation item
 * until the navigation drawer is closed. You can do so via {@link #scheduleNavigationAction(Runnable)}
 * where such Runnable action will be executed after the navigation drawer is closed.
 *
 * <h3>Styling</h3>
 * <b>Directly</b>
 * <ul>
 * <li>{@link R.attr#uiNavigationDrawerShadow uiNavigationDrawerShadow}</li>
 * <li>{@link R.attr#uiColorScrim uiColorScrim}</li>
 * </ul>
 * <p>
 * <b>Via Theme</b>
 * <ul>
 * <li>{@link R.attr#uiNavigationLayoutStyle uiNavigationLayoutStyle}</li>
 * <li>{@link R.attr#uiNavigationWidth uiNavigationWidth}</li>
 * <li>{@link R.attr#uiNavigationBackground uiNavigationBackground}</li>
 * <li>{@link R.attr#uiNavigationDivider uiNavigationDivider}</li>
 * <li>{@link R.attr#uiNavigationListSelector uiNavigationListSelector}</li>
 * <li>{@link R.attr#uiNavigationListViewStyle uiNavigationListViewStyle}</li>
 * <li>{@link R.attr#uiNavigationSubheaderStyle uiNavigationSubheaderStyle}</li>
 * <li>{@link R.attr#uiNavigationToolbarLayout uiNavigationToolbarLayout}</li>
 * </ul>
 *
 * @author Martin Albedinsky
 */
public class NavigationLayout extends DrawerLayout implements AdapterView.OnItemClickListener {

    /**
     * Interface ===================================================================================
     */

    /**
     * Listener that can receive callbacks about <b>opened</b> and <b>closed</b> navigation.
     *
     * @author Martin Albedinsky
     */
    public interface OnNavigationListener {

        /**
         * Invoked whenever the navigation drawer has been opened.
         *
         * @param navigationLayout Navigation layout in which has been the drawer opened.
         */
        void onNavigationOpened(@NonNull NavigationLayout navigationLayout);

        /**
         * Invoked whenever the navigation drawer has been closed.
         *
         * @param navigationLayout Navigation layout in which has been the drawer closed.
         */
        void onNavigationClosed(@NonNull NavigationLayout navigationLayout);
    }

    /**
     * Listener that can receive callbacks about clicked navigation item or clicked navigation header.
     *
     * @author Martin Albedinsky
     */
    public interface OnNavigationItemClickListener {

        /**
         * Invoked whenever the specified <var>headerView</var> has been clicked within the given
         * <var>navigationLayout</var>.
         *
         * @param navigationLayout The navigation layout containing the clicked header view.
         * @param headerView       The clicked header view.
         */
        void onNavigationHeaderClick(@NonNull NavigationLayout navigationLayout, @NonNull View headerView);

        /**
         * Invoked whenever the specified <var>itemView</var> has been clicked within the given
         * <var>navigationLayout</var>.
         *
         * @param navigationLayout The navigation layout containing the clicked item view.
         * @param itemView         The clicked item view.
         * @param item             Navigation item that is associated with the clicked view.
         * @param position         Position of the navigation item within the current data set of
         *                         attached navigation adapter.
         */
        void onNavigationItemClick(@NonNull NavigationLayout navigationLayout, @NonNull View itemView,
                @NonNull NavigationItem item, int position);
    }

    /**
     * Constants ===================================================================================
     */

    /**
     * Log TAG.
     */
    // private static final String TAG = "NavigationLayout";

    /**
     * Gravity of the drawer containing navigation items.
     */
    public static final int NAVIGATION_DRAWER_GRAVITY = Gravity.LEFT | Gravity.START;

    /**
     * Static members ==============================================================================
     */

    /**
     * Members =====================================================================================
     */

    /**
     * Queue of navigation actions waiting to run.
     */
    private LinkedList<Runnable> mWaitingActions;

    /**
     * Registered navigation listener (if any).
     */
    private OnNavigationListener mListener;

    /**
     * List view used to present data set of attached navigation adapter.
     */
    private ListView mNavigationListView;

    /**
     * Adapter with data set containing navigation items to be presented within {@link #mNavigationListView}.
     */
    private BaseNavigationAdapter mNavigationAdapter;

    /**
     * Registered navigation item click listener (if any).
     */
    private OnNavigationItemClickListener mItemClickListener;

    /**
     * Action bar toggle used to handle toggling between navigation drawer's opened and closed state.
     */
    private ActionBarDrawerToggle mActionBarToggle;

    /**
     * Current header view presented within navigation list view (if any).
     */
    private View mHeaderView;

    /**
     * Current spacer presented within navigation list view (if any).
     */
    private View mHeaderViewSpacer;

    /**
     * Inflater used to inflate custom views for this navigation layout.
     */
    private LayoutInflater mLayoutInflater;

    /**
     * Constructors ================================================================================
     */

    /**
     * Same as {@link #NavigationLayout(android.content.Context, android.util.AttributeSet)} without
     * attributes.
     */
    public NavigationLayout(Context context) {
        this(context, null);
    }

    /**
     * Same as {@link #NavigationLayout(android.content.Context, android.util.AttributeSet, int)}
     * with {@link R.attr#uiNavigationLayoutStyle} as attribute for default style.
     */
    public NavigationLayout(Context context, AttributeSet attrs) {
        this(context, attrs, R.attr.uiNavigationLayoutStyle);
    }

    /**
     * Same as {@link #NavigationLayout(android.content.Context, android.util.AttributeSet, int, int)}
     * with {@code 0} as default style.
     */
    public NavigationLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.init(context, attrs, defStyleAttr, 0);
    }

    /**
     * Creates a new instance of NavigationLayout within the given <var>context</var>.
     *
     * @param context      Context in which will be the new view presented.
     * @param attrs        Set of Xml attributes used to configure the new instance of this view.
     * @param defStyleAttr An attribute which contains a reference to a default style resource for
     *                     this view within a theme of the given context.
     * @param defStyleRes  Resource id of the default style for the new view.
     */
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public NavigationLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr);
        this.init(context, attrs, defStyleAttr, defStyleRes);
    }

    /**
     * Methods =====================================================================================
     */

    /**
     * Called from one of constructors of this view to perform its initialization.
     * <p>
     * Initialization is done via parsing of the specified <var>attrs</var> set and obtaining for
     * this view specific data from it that can be used to configure this new view instance. The
     * specified <var>defStyleAttr</var> and <var>defStyleRes</var> are used to obtain default data
     * from the current theme provided by the specified <var>context</var>.
     */
    @SuppressWarnings("ResourceType")
    private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        mLayoutInflater = LayoutInflater.from(context);
        int initialLayout = 0;

        /**
         * Process attributes.
         */
        final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.Ui_NavigationLayout,
                defStyleAttr, defStyleRes);
        if (typedArray != null) {
            final int n = typedArray.getIndexCount();
            for (int i = 0; i < n; i++) {
                int index = typedArray.getIndex(i);
                if (index == R.styleable.Ui_NavigationLayout_android_initialLayout) {
                    initialLayout = typedArray.getResourceId(index, initialLayout);
                } else if (index == R.styleable.Ui_NavigationLayout_uiNavigationDrawerShadow) {
                    final int resId = typedArray.getResourceId(index, -1);
                    if (resId != -1)
                        setDrawerShadow(resId, NAVIGATION_DRAWER_GRAVITY);
                } else if (index == R.styleable.Ui_NavigationLayout_uiColorScrim) {
                    setScrimColor(typedArray.getColor(index, Color.TRANSPARENT));
                }
            }
            typedArray.recycle();
        }

        if (initialLayout != 0) {
            mLayoutInflater.inflate(initialLayout, this);
            this.handleInflationFinished();
        }
    }

    /**
     */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        this.handleInflationFinished();
    }

    /**
     * Called after inflation of view hierarchy of this layout has been finished.
     */
    private void handleInflationFinished() {
        final ListView listView = (ListView) findViewById(R.id.ui_navigation_list_view);
        if (listView != mNavigationListView) {
            this.mNavigationListView = listView;
            this.ensureHeaderViewWithinNavigationList();
            if (mNavigationListView != null)
                mNavigationListView.setOnItemClickListener(this);
        }
        this.ensureToolbarWithinLayout();
    }

    /**
     * Ensures that a header view is presented within the current navigation list. If not the default
     * {@link NavigationHeaderView} will be inflated and inserted.
     */
    private void ensureHeaderViewWithinNavigationList() {
        if (mNavigationListView != null) {
            final View headerView = mLayoutInflater.inflate(R.layout.ui_header_navigation, mNavigationListView,
                    false);
            setNavigationHeaderView(headerView);
        }
    }

    /**
     * Ensures that a toolbar widget is contained within the current view hierarchy of this navigation
     * layout. If not it will be inflated from a layout resource presented within the current theme
     * and inserted into this layout at the proper place.
     */
    private void ensureToolbarWithinLayout() {
        final View toolbar = findViewById(R.id.ui_toolbar);
        if (toolbar == null) {
            final ViewGroup contentLayout = (ViewGroup) findViewById(R.id.ui_navigation_layout_content);
            if (contentLayout != null) {
                final Context context = getContext();
                final TypedValue typedValue = new TypedValue();
                if (context.getTheme().resolveAttribute(R.attr.uiNavigationToolbarLayout, typedValue, true)) {
                    // Insert the Toolbar above the content container.
                    contentLayout.addView(mLayoutInflater.inflate(typedValue.resourceId, contentLayout, false), 0);
                }
            }
        }
    }

    /**
     * Like {@link #createActionBarToggle(Activity, int, int)} where the created toggle will be attached
     * to this navigation layout if it is not attached yet.
     */
    public void createAndAttachActionBarToggle(@NonNull Activity activity, @StringRes int openDrawerContentDescRes,
            @StringRes int closeDrawerContentDescRes) {
        if (mActionBarToggle == null) {
            this.mActionBarToggle = createActionBarToggle(activity, openDrawerContentDescRes,
                    closeDrawerContentDescRes);
            setDrawerListener(mActionBarToggle);
        }
    }

    /**
     * Creates a new instance of ActionBarDrawerToggle for the specified <var>activity</var>. The
     * created toggle is also used by this navigation layout to properly close/open its navigation
     * drawer.
     *
     * @param activity                  The activity for which to create the toggle.
     * @param openDrawerContentDescRes  Resource id of content description text for the open navigation
     *                                  drawer  action.
     * @param closeDrawerContentDescRes Resource id of content description text for the close navigation
     *                                  drawer  action.
     * @return New instance of ActionBarDrawerToggle.
     */
    @NonNull
    public ActionBarDrawerToggle createActionBarToggle(@NonNull Activity activity,
            @StringRes int openDrawerContentDescRes, @StringRes int closeDrawerContentDescRes) {
        this.attachActivityAsListener(activity);
        return new ActionBarToggle(activity, this, openDrawerContentDescRes, closeDrawerContentDescRes);
    }

    /**
     * Attaches the specified <var>activity</var> as one of {@link OnNavigationListener} or {@link OnNavigationItemClickListener}
     * if they are implemented by the activity.
     *
     * @param activity The activity to be attached as listener to this navigation layout.
     */
    private void attachActivityAsListener(Activity activity) {
        if (activity instanceof OnNavigationListener) {
            setOnNavigationListener((OnNavigationListener) activity);
        }
        if (activity instanceof OnNavigationItemClickListener) {
            setOnNavigationItemClickListener((OnNavigationItemClickListener) activity);
        }
    }

    /**
     * If attached to action bar toggle via {@link #createAndAttachActionBarToggle(Activity, int, int)}
     * this will return that toggle.
     *
     * @return Current attached toggle or {@code null} if no toggle has been created and attached yet.
     */
    @Nullable
    public ActionBarDrawerToggle getActionBarToggle() {
        return mActionBarToggle;
    }

    /**
     * If attached to action bar toggle via {@link #createAndAttachActionBarToggle(Activity, int, int)}
     * this will delegate {@link ActionBarDrawerToggle#onConfigurationChanged(Configuration)} to that
     * toggle.
     * <p>
     * This should be called whenever {@link Activity#onConfigurationChanged(Configuration)} is invoked.
     *
     * @param newConfig The new configuration to be delegated to the attached action bar toggle.
     */
    public void dispatchConfigurationChanged(@NonNull Configuration newConfig) {
        if (mActionBarToggle != null)
            mActionBarToggle.onConfigurationChanged(newConfig);
    }

    /**
     * If attached to action bar toggle via {@link #createAndAttachActionBarToggle(Activity, int, int)}
     * this will delegate {@link ActionBarDrawerToggle#onOptionsItemSelected(MenuItem)} to that toggle.
     * <p>
     * This should be called whenever {@link Activity#onOptionsItemSelected(MenuItem)} is invoked.
     *
     * @param item The selected menu item to be delegated to the attached action bar toggle.
     * @return {@code True} if the toggle has processed the specified item's selection, {@code false}
     * otherwise.
     */
    public boolean dispatchOptionsItemSelected(@NonNull MenuItem item) {
        return mActionBarToggle != null && mActionBarToggle.onOptionsItemSelected(item);
    }

    /**
     * If attached to action bar toggle via {@link #createAndAttachActionBarToggle(Activity, int, int)}
     * this will delegate {@link ActionBarDrawerToggle#syncState()} to that toggle.
     * <p>
     * This should be called whenever {@link Activity#onPostCreate(Bundle)} is invoked.
     */
    public void syncState() {
        if (mActionBarToggle != null)
            mActionBarToggle.syncState();
    }

    /**
     * Specifies a view that should be inserted into navigation list as header view.
     * <p>
     * The current header view (if any) will be removed.
     *
     * @param headerView The desired header view.
     * @see #getNavigationHeaderView()
     */
    public void setNavigationHeaderView(@NonNull View headerView) {
        if (mNavigationListView != null) {
            removeNavigationHeaderView();
            this.mHeaderView = headerView;
            mNavigationListView.addHeaderView(headerView, null, true);
            this.mHeaderViewSpacer = mLayoutInflater.inflate(R.layout.ui_spacer_navigation, mNavigationListView,
                    false);
            mNavigationListView.addHeaderView(mHeaderViewSpacer, null, false);
        }
    }

    /**
     * Returns the current header view presented within the navigation list.
     * <p>
     * This navigation layout creates by default an instance of {@link NavigationHeaderView} that is
     * inserted into navigation list so if it has not been removed it will be returned here.
     *
     * @return Current header view or {@code null} if header view has been removed or not specified.
     */
    @Nullable
    public View getNavigationHeaderView() {
        return mHeaderView;
    }

    /**
     * Removes the current header view from the navigation list.
     *
     * @return {@code True} if header view has been removed, {@code false} if there was no header to
     * be removed or not navigation list from to which to remove the header.
     */
    public boolean removeNavigationHeaderView() {
        if (mNavigationListView != null) {
            if (mHeaderView != null) {
                mNavigationListView.removeHeaderView(mHeaderView);
                mNavigationListView.removeHeaderView(mHeaderViewSpacer);
                this.mHeaderViewSpacer = null;
                this.mHeaderView = null;
                return true;
            }
        }
        return false;
    }

    /**
     * Sets an adapter that provides data set of navigation items for the navigation list that is
     * presented within navigation drawer.
     *
     * @param adapter The desired adapter.
     * @see #getNavigationAdapter()
     */
    public void setNavigationAdapter(@NonNull ListAdapter adapter) {
        if (adapter instanceof BaseNavigationAdapter) {
            this.mNavigationAdapter = (BaseNavigationAdapter) adapter;
        } else {
            this.mNavigationAdapter = null;
        }
        if (mNavigationListView != null) {
            mNavigationListView.setAdapter(adapter);
        }
    }

    /**
     * Returns the current attached navigation adapter.
     *
     * @see #setNavigationAdapter(ListAdapter)
     */
    @Nullable
    public ListAdapter getNavigationAdapter() {
        return mNavigationListView != null ? mNavigationListView.getAdapter() : null;
    }

    /**
     * Registers a callback to be invoked whenever a navigation drawer is opened or closed.
     *
     * @param listener Listener callback.
     * @see #removeOnNavigationListener()
     */
    public void setOnNavigationListener(@NonNull OnNavigationListener listener) {
        this.mListener = listener;
    }

    /**
     * Removes the current OnNavigationListener callback (if any).
     */
    public void removeOnNavigationListener() {
        this.mListener = null;
    }

    /**
     * Registers a callback to be invoked whenever a navigation item or navigation header are clicked.
     *
     * @param listener Listener callback.
     * @see #removeOnNavigationItemClickListener()
     */
    public void setOnNavigationItemClickListener(@NonNull OnNavigationItemClickListener listener) {
        this.mItemClickListener = listener;
    }

    /**
     * Removes the current OnNavigationItemClickListener callback (if any).
     */
    public void removeOnNavigationItemClickListener() {
        this.mItemClickListener = null;
    }

    /**
     */
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        // There are two headers in navigation list view.
        final int itemPosition = position - 2;
        if (position == 0) {
            this.notifyNavigationHeaderClick(mHeaderView);
        } else if (itemPosition != getSelectedNavigationItemPosition()) {
            this.setNavigationItemSelected(itemPosition);
            this.notifyNavigationItemClick(view, itemPosition);
        } else {
            closeNavigation();
        }
    }

    /**
     * Returns a position of the navigation item that is at this time selected.
     *
     * @return Selected navigation item position.
     * @see BaseNavigationAdapter#getSelectedItemPosition()
     */
    public int getSelectedNavigationItemPosition() {
        return mNavigationAdapter != null ? mNavigationAdapter.getSelectedItemPosition() : -1;
    }

    /**
     * Sets selected navigation item at the specified <var>position</var>.
     *
     * @param position Position of the desired navigation item to be selected.
     * @return {@code True} if current selected item has been changed, {@code false} otherwise.
     * @see BaseNavigationAdapter#setItemSelected(int)
     */
    public boolean setNavigationItemSelected(int position) {
        return mNavigationAdapter != null && mNavigationAdapter.setItemSelected(position);
    }

    /**
     * Notifies current OnNavigationItemClickListener (if any) that the specified <var>headerView</var>
     * has been clicked.
     *
     * @param headerView The clicked header view.
     */
    @SuppressWarnings("ConstantConditions")
    private void notifyNavigationHeaderClick(View headerView) {
        if (mItemClickListener != null)
            mItemClickListener.onNavigationHeaderClick(this, headerView);
    }

    /**
     * Notifies current OnNavigationItemClickListener (if any) that the specified <var>itemView</var>
     * at the given position has been clicked.
     *
     * @param itemView The clicked item view.
     * @param position Position of navigation item associated with the clicked item view within data
     *                 set of attached navigation adapter.
     */
    @SuppressWarnings("ConstantConditions")
    private void notifyNavigationItemClick(View itemView, int position) {
        if (mNavigationAdapter != null && mItemClickListener != null) {
            mItemClickListener.onNavigationItemClick(this, itemView, mNavigationAdapter.getItem(position),
                    position);
        }
    }

    /**
     * Toggles the current state (open/closed) of the navigation. If the navigation is open this will
     * close it, otherwise it will open it.
     *
     * @return {@code True} if the navigation has been opened, {@code false} otherwise.
     */
    public boolean toggleNavigation() {
        if (isNavigationOpen()) {
            closeNavigation();
            return false;
        }
        openNavigation();
        return true;
    }

    /**
     * Returns a flag indicating whether the navigation is open or not.
     *
     * @return {@code True} if the navigation is open so it is visible to a user, {@code false} otherwise.
     */
    @SuppressWarnings("ResourceType")
    public boolean isNavigationOpen() {
        return isDrawerOpen(NAVIGATION_DRAWER_GRAVITY);
    }

    /**
     * Closes the navigation so a user can no longer interact with its items, if it is currently open.
     *
     * @return {@code True} if the navigation has been closed, {@code false} if it hasn't because it
     * was already closed.
     */
    @SuppressWarnings("ResourceType")
    public boolean closeNavigation() {
        if (isNavigationOpen()) {
            closeDrawer(NAVIGATION_DRAWER_GRAVITY);
            return true;
        }
        return false;
    }

    /**
     * Opens the navigation so it will be visible to a user, if it is currently closed.
     *
     * @return {@code True} if the navigation has been opened, {@code false} if it hasn't because it
     * was already open.
     */
    @SuppressWarnings("ResourceType")
    public boolean openNavigation() {
        if (!isNavigationOpen()) {
            openDrawer(NAVIGATION_DRAWER_GRAVITY);
            return true;
        }
        return false;
    }

    /**
     * Invoked whenever the navigation has been opened within this navigation layout so it is
     * currently visible to a user.
     */
    protected void onNavigationOpened() {
        this.notifyNavigationOpened();
    }

    /**
     * Notifies the current OnNavigationListener (if any) that the navigation has been opened.
     */
    private void notifyNavigationOpened() {
        if (mListener != null)
            mListener.onNavigationOpened(this);
    }

    /**
     * Invoked whenever the navigation has been closed within this navigation layout so a user
     * cannot to interact with it.
     * <p>
     * This implementation will run all waiting navigation actions via {@link #runAllWaitingNavigationActions()}
     * (if any).
     */
    protected void onNavigationClosed() {
        if (hasWaitingNavigationActions()) {
            runAllWaitingNavigationActions();
        }
        this.notifyNavigationClosed();
    }

    /**
     * Notifies the current OnNavigationListener (if any) that the navigation has been closed.
     */
    private void notifyNavigationClosed() {
        if (mListener != null)
            mListener.onNavigationClosed(this);
    }

    /**
     * Like {@link #registerNavigationAction(Runnable)}, but this will register the specified action
     * if the navigation drawer is open and will close the navigation drawer to run the specified action,
     * otherwise will run the specified action immediately.
     *
     * @see #isNavigationOpen()
     */
    public final void scheduleNavigationAction(@NonNull Runnable action) {
        if (isNavigationOpen()) {
            registerNavigationAction(action);
            closeNavigation();
        } else {
            action.run();
        }
    }

    /**
     * Inserts the given action into the currently waiting navigation actions.
     *
     * @param action Action to be registered.
     * @see #runTopWaitingNavigationAction()
     * @see #runAllWaitingNavigationActions()
     */
    public final void registerNavigationAction(@NonNull Runnable action) {
        if (mWaitingActions == null) {
            this.mWaitingActions = new LinkedList<>();
        }
        mWaitingActions.add(action);
    }

    /**
     * Returns a flag indicating whether there are some waiting navigation actions to be run at this
     * time or not.
     *
     * @return {@code True} if there are some waiting actions, {@code false} otherwise.
     * @see #runTopWaitingNavigationAction()
     * @see #runAllWaitingNavigationActions()
     */
    public final boolean hasWaitingNavigationActions() {
        return mWaitingActions != null && !mWaitingActions.isEmpty();
    }

    /**
     * Runs the top waiting navigation action (if any).
     *
     * @see #registerNavigationAction(Runnable)
     */
    public final void runTopWaitingNavigationAction() {
        if (hasWaitingNavigationActions())
            mWaitingActions.poll().run();
    }

    /**
     * Runs all waiting navigation actions (if any).
     *
     * @see #registerNavigationAction(Runnable)
     */
    public final void runAllWaitingNavigationActions() {
        if (hasWaitingNavigationActions()) {
            while (!mWaitingActions.isEmpty()) {
                mWaitingActions.poll().run();
            }
        }
    }

    /**
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return isEnabled() && super.onInterceptTouchEvent(ev);
    }

    /**
     */
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        return isEnabled() && super.onTouchEvent(ev);
    }

    /**
     * Inner classes ===============================================================================
     */

    /**
     * Helper implementation of {@link android.support.v7.app.ActionBarDrawerToggle} that can be used
     * to toggle visibility (open, closed) of the navigation slider of this navigation layout.
     */
    private final class ActionBarToggle extends ActionBarDrawerToggle {

        /**
         * Creates a new instance of ActionBarToggle helper for the specified <var>activity</var> and
         * <var>drawerLayout</var>.
         *
         * @param activity                  The activity that has the drawer layout presented within
         *                                  its view hierarchy.
         * @param drawerLayout              Drawer layout that should be managed by the new toolbar
         *                                  toggle helper.
         * @param openDrawerContentDescRes  Resource id of description text for open drawer action.
         * @param closeDrawerContentDescRes Resource if of description text for close drawer action.
         */
        ActionBarToggle(Activity activity, DrawerLayout drawerLayout, int openDrawerContentDescRes,
                int closeDrawerContentDescRes) {
            super(activity, drawerLayout, openDrawerContentDescRes, closeDrawerContentDescRes);
        }

        /**
         */
        @Override
        public void onDrawerClosed(View drawerView) {
            onNavigationClosed();
        }

        /**
         */
        @Override
        public void onDrawerOpened(View drawerView) {
            onNavigationOpened();
        }
    }
}