com.handsomeyang.mango.thrid.bottombar.BottomBar.java Source code

Java tutorial

Introduction

Here is the source code for com.handsomeyang.mango.thrid.bottombar.BottomBar.java

Source

package com.handsomeyang.mango.thrid.bottombar;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Typeface;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.ColorInt;
import android.support.annotation.IdRes;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.support.annotation.XmlRes;
import android.support.design.widget.CoordinatorLayout;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPropertyAnimatorListenerAdapter;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.handsomeyang.mango.R;
import java.util.List;

/*
 * BottomBar library for Android
 * Copyright (c) 2016 Iiro Krankka (http://github.com/roughike).
 *
 * 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.
 */
public class BottomBar extends LinearLayout implements View.OnClickListener, View.OnLongClickListener {
    private static final String STATE_CURRENT_SELECTED_TAB = "STATE_CURRENT_SELECTED_TAB";

    private static final float DEFAULT_INACTIVE_SHIFTING_TAB_ALPHA = 0.6f;

    // Behaviors
    private static final int BEHAVIOR_NONE = 0;
    private static final int BEHAVIOR_SHIFTING = 1;
    private static final int BEHAVIOR_SHY = 2;
    private static final int BEHAVIOR_DRAW_UNDER_NAV = 4;

    private int primaryColor;
    private int screenWidth;
    private int tenDp;
    private int maxFixedItemWidth;

    // XML Attributes
    private int tabXmlResource;
    private boolean isTabletMode;
    private int behaviors;
    private float inActiveTabAlpha;
    private float activeTabAlpha;
    private int inActiveTabColor;
    private int activeTabColor;
    private int badgeBackgroundColor;
    private int titleTextAppearance;
    private Typeface titleTypeFace;
    private boolean showShadow;

    private View backgroundOverlay;
    private ViewGroup outerContainer;
    private ViewGroup tabContainer;
    private View shadowView;

    private int defaultBackgroundColor = Color.WHITE;
    private int currentBackgroundColor;
    private int currentTabPosition;

    private int inActiveShiftingItemWidth;
    private int activeShiftingItemWidth;

    private OnTabSelectListener onTabSelectListener;
    private OnTabReselectListener onTabReselectListener;

    private boolean isComingFromRestoredState;
    private boolean ignoreTabReselectionListener;

    private boolean shyHeightAlreadyCalculated;
    private boolean navBarAccountedHeightCalculated;

    public BottomBar(Context context) {
        super(context);
        init(context, null);
    }

    public BottomBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
        setItems(tabXmlResource);
    }

    private void init(Context context, AttributeSet attrs) {
        populateAttributes(context, attrs);
        initializeViews();
        determineInitialBackgroundColor();
    }

    private void populateAttributes(Context context, AttributeSet attrs) {
        primaryColor = MiscUtils.getColor(getContext(), R.attr.colorPrimary);
        screenWidth = MiscUtils.getScreenWidth(getContext());
        tenDp = MiscUtils.dpToPixel(getContext(), 10);
        maxFixedItemWidth = MiscUtils.dpToPixel(getContext(), 168);

        TypedArray ta = context.getTheme().obtainStyledAttributes(attrs, R.styleable.BottomBar, 0, 0);

        try {
            tabXmlResource = ta.getResourceId(R.styleable.BottomBar_bb_tabXmlResource, 0);
            isTabletMode = ta.getBoolean(R.styleable.BottomBar_bb_tabletMode, false);
            behaviors = ta.getInteger(R.styleable.BottomBar_bb_behavior, BEHAVIOR_NONE);
            inActiveTabAlpha = ta.getFloat(R.styleable.BottomBar_bb_inActiveTabAlpha,
                    isShiftingMode() ? DEFAULT_INACTIVE_SHIFTING_TAB_ALPHA : 1);
            activeTabAlpha = ta.getFloat(R.styleable.BottomBar_bb_activeTabAlpha, 1);

            @ColorInt
            int defaultInActiveColor = isShiftingMode() ? Color.WHITE
                    : ContextCompat.getColor(context, R.color.bb_inActiveBottomBarItemColor);
            int defaultActiveColor = isShiftingMode() ? Color.WHITE : primaryColor;

            inActiveTabColor = ta.getColor(R.styleable.BottomBar_bb_inActiveTabColor, defaultInActiveColor);
            activeTabColor = ta.getColor(R.styleable.BottomBar_bb_activeTabColor, defaultActiveColor);
            badgeBackgroundColor = ta.getColor(R.styleable.BottomBar_bb_badgeBackgroundColor, Color.RED);
            titleTextAppearance = ta.getResourceId(R.styleable.BottomBar_bb_titleTextAppearance, 0);
            titleTypeFace = getTypeFaceFromAsset(ta.getString(R.styleable.BottomBar_bb_titleTypeFace));
            showShadow = ta.getBoolean(R.styleable.BottomBar_bb_showShadow, true);
        } finally {
            ta.recycle();
        }
    }

    private boolean isShiftingMode() {
        return !isTabletMode && hasBehavior(BEHAVIOR_SHIFTING);
    }

    private boolean drawUnderNav() {
        return !isTabletMode && hasBehavior(BEHAVIOR_DRAW_UNDER_NAV)
                && NavbarUtils.shouldDrawBehindNavbar(getContext());
    }

    private boolean isShy() {
        return !isTabletMode && hasBehavior(BEHAVIOR_SHY);
    }

    private boolean hasBehavior(int behavior) {
        return (behaviors | behavior) == behaviors;
    }

    private Typeface getTypeFaceFromAsset(String fontPath) {
        if (fontPath != null) {
            return Typeface.createFromAsset(getContext().getAssets(), fontPath);
        }

        return null;
    }

    private void initializeViews() {
        int width = isTabletMode ? LayoutParams.WRAP_CONTENT : LayoutParams.MATCH_PARENT;
        int height = isTabletMode ? LayoutParams.MATCH_PARENT : LayoutParams.WRAP_CONTENT;
        LayoutParams params = new LayoutParams(width, height);

        setLayoutParams(params);
        setOrientation(isTabletMode ? HORIZONTAL : VERTICAL);
        ViewCompat.setElevation(this, MiscUtils.dpToPixel(getContext(), 8));

        View rootView = inflate(getContext(),
                isTabletMode ? R.layout.bb_bottom_bar_item_container_tablet : R.layout.bb_bottom_bar_item_container,
                this);
        rootView.setLayoutParams(params);

        backgroundOverlay = rootView.findViewById(R.id.bb_bottom_bar_background_overlay);
        outerContainer = (ViewGroup) rootView.findViewById(R.id.bb_bottom_bar_outer_container);
        tabContainer = (ViewGroup) rootView.findViewById(R.id.bb_bottom_bar_item_container);
        shadowView = rootView.findViewById(R.id.bb_bottom_bar_shadow);

        if (!showShadow) {
            shadowView.setVisibility(GONE);
        }
    }

    private void determineInitialBackgroundColor() {
        if (isShiftingMode()) {
            defaultBackgroundColor = primaryColor;
        }

        Drawable userDefinedBackground = getBackground();

        boolean userHasDefinedBackgroundColor = userDefinedBackground != null
                && userDefinedBackground instanceof ColorDrawable;

        if (userHasDefinedBackgroundColor) {
            defaultBackgroundColor = ((ColorDrawable) userDefinedBackground).getColor();
            setBackgroundColor(Color.TRANSPARENT);
        }
    }

    /**
     * Set the items for the BottomBar from XML Resource.
     */
    public void setItems(@XmlRes int xmlRes) {
        setItems(xmlRes, null);
    }

    /**
     * Set the item for the BottomBar from XML Resource with a default configuration
     * for each tab.
     */
    public void setItems(@XmlRes int xmlRes, BottomBarTab.Config defaultTabConfig) {
        if (xmlRes == 0) {
            throw new RuntimeException("No items specified for the BottomBar!");
        }

        if (defaultTabConfig == null) {
            defaultTabConfig = getTabConfig();
        }

        TabParser parser = new TabParser(getContext(), defaultTabConfig, xmlRes);
        updateItems(parser.getTabs());
    }

    private BottomBarTab.Config getTabConfig() {
        return new BottomBarTab.Config.Builder().inActiveTabAlpha(inActiveTabAlpha).activeTabAlpha(activeTabAlpha)
                .inActiveTabColor(inActiveTabColor).activeTabColor(activeTabColor)
                .barColorWhenSelected(defaultBackgroundColor).badgeBackgroundColor(badgeBackgroundColor)
                .titleTextAppearance(titleTextAppearance).titleTypeFace(titleTypeFace).build();
    }

    private void updateItems(final List<BottomBarTab> bottomBarItems) {
        int index = 0;
        int biggestWidth = 0;

        BottomBarTab[] viewsToAdd = new BottomBarTab[bottomBarItems.size()];

        for (BottomBarTab bottomBarTab : bottomBarItems) {
            BottomBarTab.Type type;

            if (isShiftingMode()) {
                type = BottomBarTab.Type.SHIFTING;
            } else if (isTabletMode) {
                type = BottomBarTab.Type.TABLET;
            } else {
                type = BottomBarTab.Type.FIXED;
            }

            bottomBarTab.setType(type);
            bottomBarTab.prepareLayout();

            if (index == currentTabPosition) {
                bottomBarTab.select(false);

                handleBackgroundColorChange(bottomBarTab, false);
            } else {
                bottomBarTab.deselect(false);
            }

            if (!isTabletMode) {
                if (bottomBarTab.getWidth() > biggestWidth) {
                    biggestWidth = bottomBarTab.getWidth();
                }

                viewsToAdd[index] = bottomBarTab;
            } else {
                tabContainer.addView(bottomBarTab);
            }

            bottomBarTab.setOnClickListener(this);
            bottomBarTab.setOnLongClickListener(this);
            index++;
        }

        if (!isTabletMode) {
            resizeTabsToCorrectSizes(bottomBarItems, viewsToAdd);
        }
    }

    private void resizeTabsToCorrectSizes(List<BottomBarTab> bottomBarItems, BottomBarTab[] viewsToAdd) {
        int proposedItemWidth = Math.min(MiscUtils.dpToPixel(getContext(), screenWidth / bottomBarItems.size()),
                maxFixedItemWidth);

        inActiveShiftingItemWidth = (int) (proposedItemWidth * 0.9);
        activeShiftingItemWidth = (int) (proposedItemWidth + (proposedItemWidth * (bottomBarItems.size() * 0.1)));
        int height = Math.round(getContext().getResources().getDimension(R.dimen.bb_height));

        for (BottomBarTab bottomBarView : viewsToAdd) {
            LayoutParams params;

            if (isShiftingMode()) {
                if (bottomBarView.isActive()) {
                    params = new LayoutParams(activeShiftingItemWidth, height);
                } else {
                    params = new LayoutParams(inActiveShiftingItemWidth, height);
                }
            } else {
                params = new LayoutParams(proposedItemWidth, height);
            }

            bottomBarView.setLayoutParams(params);
            tabContainer.addView(bottomBarView);
        }
    }

    /**
     * Set a listener that gets fired when the selected tab changes.
     *
     * Note: Will be immediately called for the currently selected tab
     * once when set.
     *
     * @param listener a listener for monitoring changes in tab selection.
     */
    public void setOnTabSelectListener(@Nullable OnTabSelectListener listener) {
        onTabSelectListener = listener;

        if (onTabSelectListener != null && getTabCount() > 0) {
            listener.onTabSelected(getCurrentTabId());
        }
    }

    /**
     * Set a listener that gets fired when a currently selected tab is clicked.
     *
     * @param listener a listener for handling tab reselections.
     */
    public void setOnTabReselectListener(@Nullable OnTabReselectListener listener) {
        onTabReselectListener = listener;
    }

    /**
     * Set the default selected to be the tab with the corresponding tab id.
     * By default, the first tab in the container is the default tab.
     */
    public void setDefaultTab(@IdRes int defaultTabId) {
        int defaultTabPosition = findPositionForTabWithId(defaultTabId);
        setDefaultTabPosition(defaultTabPosition);
    }

    /**
     * Sets the default tab for this BottomBar that is shown until the user changes
     * the selection.
     *
     * @param defaultTabPosition the default tab position.
     */
    public void setDefaultTabPosition(int defaultTabPosition) {
        if (isComingFromRestoredState)
            return;

        selectTabAtPosition(defaultTabPosition);
    }

    /**
     * Select the tab with the corresponding id.
     */
    public void selectTabWithId(@IdRes int tabResId) {
        int tabPosition = findPositionForTabWithId(tabResId);
        selectTabAtPosition(tabPosition);
    }

    /**
     * Select a tab at the specified position.
     *
     * @param position the position to select.
     */
    public void selectTabAtPosition(int position) {
        if (position > getTabCount() - 1 || position < 0) {
            throw new IndexOutOfBoundsException(
                    "Can't select tab at position " + position + ". This BottomBar has no items at that position.");
        }

        selectTabAtPosition(position, false);
    }

    public int getTabCount() {
        return tabContainer.getChildCount();
    }

    /**
     * Get the currently selected tab.
     */
    public BottomBarTab getCurrentTab() {
        return getTabAtPosition(getCurrentTabPosition());
    }

    /**
     * Get the tab at the specified position.
     */
    public BottomBarTab getTabAtPosition(int position) {
        View child = tabContainer.getChildAt(position);

        if (child instanceof FrameLayout) {
            return findTabInLayout((FrameLayout) child);
        }

        return (BottomBarTab) child;
    }

    /**
     * Get the resource id for the currently selected tab.
     */
    @IdRes
    public int getCurrentTabId() {
        return getCurrentTab().getId();
    }

    /**
     * Get the currently selected tab position.
     */
    public int getCurrentTabPosition() {
        return currentTabPosition;
    }

    /**
     * Find the tabs' position in the container by id.
     */
    public int findPositionForTabWithId(@IdRes int tabId) {
        return getTabWithId(tabId).getIndexInTabContainer();
    }

    /**
     * Find a BottomBarTab with the corresponding id.
     */
    public BottomBarTab getTabWithId(@IdRes int tabId) {
        return (BottomBarTab) tabContainer.findViewById(tabId);
    }

    /**
     * Set alpha value used for inactive BottomBarTabs.
     */
    public void setInActiveTabAlpha(float alpha) {
        inActiveTabAlpha = alpha;
        refreshTabs();
    }

    /**
     * Set alpha value used for active BottomBarTabs.
     */
    public void setActiveTabAlpha(float alpha) {
        activeTabAlpha = alpha;
        refreshTabs();
    }

    public void setInActiveTabColor(@ColorInt int color) {
        inActiveTabColor = color;
        refreshTabs();
    }

    /**
     * Set active color used for selected BottomBarTabs.
     */
    public void setActiveTabColor(@ColorInt int color) {
        activeTabColor = color;
        refreshTabs();
    }

    public void setBadgeBackgroundColor(@ColorInt int color) {
        badgeBackgroundColor = color;
        refreshTabs();
    }

    /**
     * Set custom text apperance for all BottomBarTabs.
     */
    public void setTabTitleTextAppearance(int textAppearance) {
        titleTextAppearance = textAppearance;
        refreshTabs();
    }

    /**
     * Set a custom typeface for all tab's titles.
     *
     * @param fontPath path for your custom font file, such as fonts/MySuperDuperFont.ttf.
     * In that case your font path would look like src/main/assets/fonts/MySuperDuperFont.ttf,
     * but you only need to provide fonts/MySuperDuperFont.ttf, as the asset folder
     * will be auto-filled for you.
     */
    public void setTabTitleTypeface(String fontPath) {
        Typeface actualTypeface = getTypeFaceFromAsset(fontPath);
        setTabTitleTypeface(actualTypeface);
    }

    /**
     * Set a custom typeface for all tab's titles.
     */
    public void setTabTitleTypeface(Typeface typeface) {
        titleTypeFace = typeface;
        refreshTabs();
    }

    private void refreshTabs() {
        int tabCount = getTabCount();

        if (tabCount > 0) {
            BottomBarTab.Config newConfig = getTabConfig();

            for (int i = 0; i < getTabCount(); i++) {
                BottomBarTab tab = getTabAtPosition(i);
                tab.setConfig(newConfig);
            }
        }
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        if (changed) {
            updateTitleBottomPadding();

            if (isShy()) {
                initializeShyBehavior();
            }

            if (drawUnderNav()) {
                resizeForDrawingUnderNavbar();
            }
        }
    }

    private void updateTitleBottomPadding() {
        if (tabContainer == null) {
            return;
        }

        int childCount = getTabCount();

        for (int i = 0; i < childCount; i++) {
            View tab = tabContainer.getChildAt(i);
            TextView title = (TextView) tab.findViewById(R.id.bb_bottom_bar_title);

            if (title == null) {
                continue;
            }

            int baseline = title.getBaseline();
            int height = title.getHeight();
            int paddingInsideTitle = height - baseline;
            int missingPadding = tenDp - paddingInsideTitle;

            if (missingPadding > 0) {
                title.setPadding(title.getPaddingLeft(), title.getPaddingTop(), title.getPaddingRight(),
                        missingPadding + title.getPaddingBottom());
            }
        }
    }

    private void initializeShyBehavior() {
        ViewParent parent = getParent();

        boolean hasAbusiveParent = parent != null && parent instanceof CoordinatorLayout;

        if (!hasAbusiveParent) {
            throw new RuntimeException("In order to have shy behavior, the "
                    + "BottomBar must be a direct child of a CoordinatorLayout.");
        }

        if (!shyHeightAlreadyCalculated) {
            int height = getHeight();

            if (height != 0) {
                updateShyHeight(height);
                shyHeightAlreadyCalculated = true;
            }
        }
    }

    private void updateShyHeight(int height) {
        ((CoordinatorLayout.LayoutParams) getLayoutParams())
                .setBehavior(new BottomNavigationBehavior(height, 0, false));
    }

    private void resizeForDrawingUnderNavbar() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            int currentHeight = getHeight();

            if (currentHeight != 0 && !navBarAccountedHeightCalculated) {
                navBarAccountedHeightCalculated = true;
                tabContainer.getLayoutParams().height = currentHeight;

                int navbarHeight = NavbarUtils.getNavbarHeight(getContext());
                int finalHeight = currentHeight + navbarHeight;
                getLayoutParams().height = finalHeight;

                if (isShy()) {
                    updateShyHeight(finalHeight);
                }
            }
        }
    }

    @Override
    public Parcelable onSaveInstanceState() {
        Bundle bundle = saveState();
        bundle.putParcelable("superstate", super.onSaveInstanceState());
        return bundle;
    }

    @VisibleForTesting
    Bundle saveState() {
        Bundle outState = new Bundle();
        outState.putInt(STATE_CURRENT_SELECTED_TAB, currentTabPosition);

        return outState;
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        if (state instanceof Bundle) {
            Bundle bundle = (Bundle) state;
            restoreState(bundle);

            state = bundle.getParcelable("superstate");
        }
        super.onRestoreInstanceState(state);
    }

    @VisibleForTesting
    void restoreState(Bundle savedInstanceState) {
        if (savedInstanceState != null) {
            isComingFromRestoredState = true;
            ignoreTabReselectionListener = true;

            int restoredPosition = savedInstanceState.getInt(STATE_CURRENT_SELECTED_TAB, currentTabPosition);
            selectTabAtPosition(restoredPosition, false);
        }
    }

    @Override
    public void onClick(View v) {
        handleClick(v);
    }

    @Override
    public boolean onLongClick(View v) {
        return handleLongClick(v);
    }

    private BottomBarTab findTabInLayout(ViewGroup child) {
        for (int i = 0; i < child.getChildCount(); i++) {
            View candidate = child.getChildAt(i);

            if (candidate instanceof BottomBarTab) {
                return (BottomBarTab) candidate;
            }
        }

        return null;
    }

    private void handleClick(View v) {
        BottomBarTab oldTab = getCurrentTab();
        BottomBarTab newTab = (BottomBarTab) v;

        oldTab.deselect(true);
        newTab.select(true);

        shiftingMagic(oldTab, newTab, true);
        handleBackgroundColorChange(newTab, true);
        updateSelectedTab(newTab.getIndexInTabContainer());
    }

    private boolean handleLongClick(View v) {
        if (v instanceof BottomBarTab) {
            BottomBarTab longClickedTab = (BottomBarTab) v;

            if ((isShiftingMode() || isTabletMode) && !longClickedTab.isActive()) {
                Toast.makeText(getContext(), longClickedTab.getTitle(), Toast.LENGTH_SHORT).show();
            }
        }

        return true;
    }

    private void selectTabAtPosition(int position, boolean animate) {
        BottomBarTab oldTab = getCurrentTab();
        BottomBarTab newTab = getTabAtPosition(position);

        oldTab.deselect(animate);
        newTab.select(animate);

        updateSelectedTab(position);
        shiftingMagic(oldTab, newTab, animate);
        handleBackgroundColorChange(newTab, false);
    }

    private void updateSelectedTab(int newPosition) {
        int newTabId = getTabAtPosition(newPosition).getId();

        if (newPosition != currentTabPosition) {
            if (onTabSelectListener != null) {
                onTabSelectListener.onTabSelected(newTabId);
            }
        } else if (onTabReselectListener != null && !ignoreTabReselectionListener) {
            onTabReselectListener.onTabReSelected(newTabId);
        }

        currentTabPosition = newPosition;

        if (ignoreTabReselectionListener) {
            ignoreTabReselectionListener = false;
        }
    }

    private void shiftingMagic(BottomBarTab oldTab, BottomBarTab newTab, boolean animate) {
        if (isShiftingMode()) {
            oldTab.updateWidth(inActiveShiftingItemWidth, animate);
            newTab.updateWidth(activeShiftingItemWidth, animate);
        }
    }

    private void handleBackgroundColorChange(BottomBarTab tab, boolean animate) {
        int newColor = tab.getBarColorWhenSelected();

        if (currentBackgroundColor == newColor) {
            return;
        }

        if (!animate) {
            outerContainer.setBackgroundColor(newColor);
            return;
        }

        View clickedView = tab;

        if (tab.hasActiveBadge()) {
            clickedView = tab.getOuterView();
        }

        animateBGColorChange(clickedView, newColor);
        currentBackgroundColor = newColor;
    }

    private void animateBGColorChange(View clickedView, final int newColor) {
        prepareForBackgroundColorAnimation(newColor);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            if (!outerContainer.isAttachedToWindow()) {
                return;
            }

            backgroundCircularRevealAnimation(clickedView, newColor);
        } else {
            backgroundCrossfadeAnimation(newColor);
        }
    }

    private void prepareForBackgroundColorAnimation(int newColor) {
        outerContainer.clearAnimation();
        backgroundOverlay.clearAnimation();

        backgroundOverlay.setBackgroundColor(newColor);
        backgroundOverlay.setVisibility(View.VISIBLE);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private void backgroundCircularRevealAnimation(View clickedView, final int newColor) {
        int centerX = (int) (ViewCompat.getX(clickedView) + (clickedView.getMeasuredWidth() / 2));
        int yOffset = isTabletMode ? (int) ViewCompat.getY(clickedView) : 0;
        int centerY = yOffset + clickedView.getMeasuredHeight() / 2;
        int startRadius = 0;
        int finalRadius = isTabletMode ? outerContainer.getHeight() : outerContainer.getWidth();

        Animator animator = ViewAnimationUtils.createCircularReveal(backgroundOverlay, centerX, centerY,
                startRadius, finalRadius);

        if (isTabletMode) {
            animator.setDuration(500);
        }

        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                onEnd();
            }

            @Override
            public void onAnimationCancel(Animator animation) {
                onEnd();
            }

            private void onEnd() {
                outerContainer.setBackgroundColor(newColor);
                backgroundOverlay.setVisibility(View.INVISIBLE);
                ViewCompat.setAlpha(backgroundOverlay, 1);
            }
        });

        animator.start();
    }

    private void backgroundCrossfadeAnimation(final int newColor) {
        ViewCompat.setAlpha(backgroundOverlay, 0);
        ViewCompat.animate(backgroundOverlay).alpha(1).setListener(new ViewPropertyAnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(View view) {
                onEnd();
            }

            @Override
            public void onAnimationCancel(View view) {
                onEnd();
            }

            private void onEnd() {
                outerContainer.setBackgroundColor(newColor);
                backgroundOverlay.setVisibility(View.INVISIBLE);
                ViewCompat.setAlpha(backgroundOverlay, 1);
            }
        }).start();
    }

    /**
     * Toggle translation of BottomBar to hidden and visible in a CoordinatorLayout.
     *
     * @param visible true resets translation to 0, false translates view to hidden
     */
    private void toggleShyVisibility(boolean visible) {
        BottomNavigationBehavior<BottomBar> from = BottomNavigationBehavior.from(this);
        if (from != null) {
            from.setHidden(this, visible);
        }
    }
}