despotoski.nikola.github.com.bottomnavigationlayout.BottomTabLayout.java Source code

Java tutorial

Introduction

Here is the source code for despotoski.nikola.github.com.bottomnavigationlayout.BottomTabLayout.java

Source

/*
 * BottomNavigationLayout library for Android
 * Copyright (c) 2016. Nikola Despotoski (http://github.com/NikolaDespotoski).
 * 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 despotoski.nikola.github.com.bottomnavigationlayout;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
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.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.ColorRes;
import android.support.annotation.MenuRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.CoordinatorLayout;
import android.support.v4.view.GravityCompat;
import android.support.v4.view.ViewCompat;
import android.support.v7.view.menu.MenuBuilder;
import android.support.v7.widget.LinearLayoutCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by Nikola on 3/15/2016.
 */
@CoordinatorLayout.DefaultBehavior(BottomNavigationBehavior.class)
public class BottomTabLayout extends DrawShadowFrameLayout {
    static final float MAX_ITEM_WIDTH = 168f;
    private static final int MAX_BOTTOM_NAVIGATION_ITEMS = 5;
    private static final int MIN_BOTTOM_NAVIGATION_ITEMS = 3;
    private final List<View> mBottomTabViews = new ArrayList<>(MAX_BOTTOM_NAVIGATION_ITEMS);
    private int[] mParentBackgroundColors;
    private View mRevealOverlayView;
    private LinearLayoutCompat mContainer;
    private int mActiveColorFilter;
    private int mSelectedItemPosition = View.NO_ID;
    private View mCurrentNavigationItem;
    private boolean mAlwaysShowText = false;
    private int mMinBottomItemWidth;
    private boolean mShiftingMode;
    private int mMaxItemWidth;
    private int mInactiveTextColor;
    private boolean isTablet;
    private int mMaxContainerHeight;
    private BottomNavigationItem mPreviouslySelectedItem;
    private List<OnNavigationItemSelectionListener> mNavigationItemSelectionListeners;
    private final OnClickListener mBottomTabSelectionClickListener = new OnClickListener() {
        @Override
        public void onClick(View v) {
            if (!v.isSelected()) {
                v.setSelected(true);
                if (mCurrentNavigationItem != null) {
                    mCurrentNavigationItem.setSelected(false);
                    mPreviouslySelectedItem = (BottomNavigationItem) mCurrentNavigationItem.getTag();
                    dispatchItemUnselected(mPreviouslySelectedItem);
                }
                if (mNavigationItemSelectionListeners != null) {
                    dispatchItemSelected((BottomNavigationItem) v.getTag());

                }
            }
            mCurrentNavigationItem = v;
        }
    };

    public BottomTabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initalize(context, attrs);

    }

    public BottomTabLayout(Context context) {
        super(context);
        removeAllViews();

    }

    public BottomTabLayout(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        isTablet = getResources().getBoolean(R.bool.isTablet);
        initalize(context, attrs);
    }

    private static void checkBottomItemGuidelines(int count) {
        if (count < MIN_BOTTOM_NAVIGATION_ITEMS || count > MAX_BOTTOM_NAVIGATION_ITEMS) {
            throw new IllegalArgumentException(
                    "Number of bottom navigation items should between 3 and 5, count: " + count);
        }
    }

    private void initalize(Context context, AttributeSet attrs) {
        bringToFront();
        mMaxContainerHeight = (int) getResources().getDimension(R.dimen.bottom_navigation_height);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.BottomNavigationTabLayout);
        if (isTablet) {
            isTablet = !a.getBoolean(R.styleable.BottomNavigationTabLayout_disable_tablet_behaviour, false);
        }
        if (isTablet) {
            setShadowVisible(false);
            Util.runOnAttachedToLayout(this, new Runnable() {
                @Override
                public void run() {
                    ViewGroup.LayoutParams params = getLayoutParams();
                    if (params instanceof FrameLayout.LayoutParams) {
                        ((LayoutParams) params).gravity = GravityCompat.START;
                    } else if (params instanceof CoordinatorLayout.LayoutParams) {
                        ((CoordinatorLayout.LayoutParams) params).gravity = GravityCompat.START;
                    }
                    params.width = mMaxContainerHeight;
                    params.height = ViewGroup.LayoutParams.MATCH_PARENT;
                    requestLayout();
                    if (mContainer != null)
                        mContainer.requestLayout();
                    disableBehavior();
                }
            });
        }
        removeAllViews();
        ViewCompat.setElevation(this, getResources().getDimension(R.dimen.bottom_navigation_elevation));
        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
        if (!isTablet) {
            setupOverlayView();
        }
        setupContainer();

        boolean shadow = a.getBoolean(R.styleable.BottomNavigationTabLayout_bottom_tabs_shadow, true);
        mAlwaysShowText = a.getBoolean(R.styleable.BottomNavigationTabLayout_always_show_text, false);
        mShiftingMode = a.getBoolean(R.styleable.BottomNavigationTabLayout_shift_mode, false);
        mInactiveTextColor = a.getColor(R.styleable.BottomNavigationTabLayout_inactive_item_text_color,
                Color.WHITE);
        mActiveColorFilter = a.getResourceId(R.styleable.BottomNavigationTabLayout_active_item_color_filter,
                View.NO_ID);
        int bottomTabMenuResId = a.getResourceId(R.styleable.BottomNavigationTabLayout_bottom_tabs_menu,
                View.NO_ID);
        if (bottomTabMenuResId != View.NO_ID) {
            int parentBackgroundColorsResId = a.getResourceId(
                    R.styleable.BottomNavigationTabLayout_bottom_tabs_menu_parent_background_colors, View.NO_ID);
            if (!isInEditMode() && parentBackgroundColorsResId != View.NO_ID) {
                mParentBackgroundColors = getResources().getIntArray(parentBackgroundColorsResId);
            }
        }
        mMaxItemWidth = (int) getResources().getDimension(R.dimen.bottom_navigation_max_width);
        mMinBottomItemWidth = (int) getResources().getDimension(R.dimen.bottom_navigation_min_width);

        setBottomTabs(bottomTabMenuResId);
        Util.runOnAttachedToLayout(this, new Runnable() {
            @Override
            public void run() {
                updateBottomNavViews();
            }
        });
        a.recycle();
        if (!shadow) {
            setShadowVisible(false);
            ViewCompat.setElevation(this, 0);
        }
    }

    public int getBackgroundColor() {
        Drawable drawable = getBackground();
        if (drawable instanceof ColorDrawable) {
            ColorDrawable colorDrawable = (ColorDrawable) drawable;
            if (Build.VERSION.SDK_INT >= 11) {
                return colorDrawable.getColor();
            }
            try {
                Field field = colorDrawable.getClass().getDeclaredField("mState");
                field.setAccessible(true);
                Object object = field.get(colorDrawable);
                field = object.getClass().getDeclaredField("mUseColor");
                field.setAccessible(true);
                return field.getInt(object);
            } catch (Exception ignore) {
            }
        }
        return Color.TRANSPARENT;
    }

    @Override
    protected LayoutParams generateDefaultLayoutParams() {

        LayoutParams layoutParams = super.generateDefaultLayoutParams();
        if (!isTablet)
            layoutParams.gravity = Gravity.BOTTOM;
        else {
            layoutParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                    ViewGroup.LayoutParams.MATCH_PARENT);
            layoutParams.gravity = GravityCompat.START;
        }
        return layoutParams;
    }

    private void setupContainer() {
        mContainer = new LinearLayoutCompat(getContext());
        mContainer.setFocusable(false);
        LayoutParams layoutParams;
        if (isTablet) {
            layoutParams = new LayoutParams(mMaxContainerHeight, LayoutParams.MATCH_PARENT);
            layoutParams.gravity = Gravity.CENTER_VERTICAL;
            disableBehavior();
            mContainer.setOrientation(LinearLayoutCompat.VERTICAL);
            mContainer.setGravity(Gravity.TOP | Gravity.CENTER_VERTICAL);
            addView(mContainer, layoutParams);
        } else {
            mContainer.setOrientation(LinearLayoutCompat.HORIZONTAL);
            mContainer.setPadding(0, 0,
                    (int) getResources().getDimension(R.dimen.bottom_navigation_item_padding_bottom), 0);
            layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT,
                    (int) getResources().getDimension(R.dimen.bottom_navigation_height));
            layoutParams.gravity = Gravity.TOP;
            layoutParams.bottomMargin = !isInEditMode() && Util.isNavigationBarTranslucent(getContext())
                    && !isLandscape() ? Util.getNavigationBarHeight(getContext()) : 0;
            mContainer.setOrientation(LinearLayoutCompat.HORIZONTAL);
            mContainer.setGravity(Gravity.CENTER | Gravity.CENTER_HORIZONTAL);
            addView(mContainer, layoutParams);
        }

    }

    private void disableBehavior() {
        ViewGroup.LayoutParams params = super.getLayoutParams();
        if (params instanceof CoordinatorLayout.LayoutParams) {
            ((CoordinatorLayout.LayoutParams) params).setBehavior(null);
        }
    }

    private void setupOverlayView() {
        int height = (int) getResources().getDimension(R.dimen.bottom_navigation_height);
        height += !isInEditMode() && Util.isNavigationBarTranslucent(getContext()) && !isLandscape()
                ? Util.getNavigationBarHeight(getContext())
                : 0;
        LayoutParams layoutParams;
        if (!isTablet) {
            layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, height);
            layoutParams.topMargin = getShadowElevation();
        } else {
            layoutParams = new LayoutParams(height, LayoutParams.MATCH_PARENT);
        }
        mRevealOverlayView = new View(getContext());
        mRevealOverlayView.setFocusable(false);
        mRevealOverlayView.setFocusableInTouchMode(false);
        mRevealOverlayView.setClickable(false);
        addView(mRevealOverlayView, layoutParams);
    }

    private void updateBottomNavViews() {
        if (mContainer.getChildCount() == 0)
            return;
        mMinBottomItemWidth = findMinItemWidth();
        for (int i = mContainer.getChildCount() - 1; i >= 0; i--) {
            View childAt = mContainer.getChildAt(i);
            childAt.setLayoutParams(generateBottomItemLayoutParams());
            childAt.setOnClickListener(mBottomTabSelectionClickListener);
            if (childAt instanceof BottomNavigation) {
                BottomNavigation bottomView = (BottomNavigation) childAt;
                bottomView.setShiftingModeEnabled(mShiftingMode);
                bottomView.setActiveColorResource(mActiveColorFilter);
                bottomView.setInactiveTextColor(mInactiveTextColor);
                bottomView.setAlwaysShowText(mAlwaysShowText);
            }
        }
        selectTabView();
    }

    private LinearLayoutCompat.LayoutParams generateBottomItemLayoutParams() {
        if (isLandscape()) {
            mMinBottomItemWidth = Math.min(mMinBottomItemWidth, mMaxItemWidth);
        }
        if (isTablet) {
            return new LinearLayoutCompat.LayoutParams(mMaxContainerHeight, ViewGroup.LayoutParams.WRAP_CONTENT);
        }
        return new LinearLayoutCompat.LayoutParams(mMinBottomItemWidth, mMaxContainerHeight);
    }

    private boolean isLandscape() {
        return getContext().getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
    }

    /**
     * @return Returns true when shifting mode is enabled, false when disabled
     */
    public boolean isShiftingMode() {
        return mShiftingMode;
    }

    /**
     * @param mShiftingMode boolean value to disable or enable shifting mode. Shifting value set to true is overriden if the number of items is 3.
     */
    public void setShiftingMode(boolean mShiftingMode) {
        this.mShiftingMode = mShiftingMode;
        updateBottomNavViews();
    }

    private int findMinItemWidth() {
        int longest = isTablet ? mContainer.getMeasuredHeight() : mContainer.getMeasuredWidth();
        return longest / mContainer.getChildCount();
    }

    /**
     * @param menuResId              Menu resource from which bottom navigation items will be generated
     */
    public void setBottomTabs(@MenuRes int menuResId) {
        if (!isInEditMode()) {
            if (menuResId != View.NO_ID) {
                MenuBuilder menuBuilder = new MenuBuilder(getContext());
                ((Activity) getContext()).getMenuInflater().inflate(menuResId, menuBuilder);
                //if (menuBuilder.size() != parentBackgroundColors.length) {
                //   throw new IllegalArgumentException("The number of menu items should be equal to the number of parent backgrounds. Make sure you are using both attributes.");
                //}
                checkBottomItemGuidelines(menuBuilder.size());
                populateFromMenuResource(menuBuilder);
            }
        }
    }

    private void populateFromMenuResource(@NonNull MenuBuilder menuBuilder) {
        removeAllTabs();
        int size = menuBuilder.size();
        checkBottomItemGuidelines(size);
        for (int i = 0; i < size; i++) {
            MenuItem item = menuBuilder.getItem(i);
            BottomNavigationItem bottomNavigationItem = BottomNavigationItemBuilder.create(item.getIcon(),
                    String.valueOf(item.getTitle()), getParentBackgroundColor(i));
            bottomNavigationItem.setPosition(i);
            addBottomNavigationItem(bottomNavigationItem);
        }
        updateBottomNavViews();
    }

    private int getParentBackgroundColor(int index) {
        if (mParentBackgroundColors != null && mParentBackgroundColors.length < index) {
            return mParentBackgroundColors[index];
        }
        return getBackgroundColor();
    }

    private void addBottomNavigationItem(BottomNavigationItem item) {
        View tabView;
        if (!isTablet) {
            BottomNavigationTextView bottomNavigationTextView = new BottomNavigationTextView(getContext(), item);
            bottomNavigationTextView.setActiveColor(mActiveColorFilter);
            bottomNavigationTextView.setInactiveTextColor(mInactiveTextColor);
            bottomNavigationTextView.setTag(item);
            tabView = bottomNavigationTextView;
        } else {
            BottomTabletNavigationView tabletNavigationView = new BottomTabletNavigationView(getContext(), item);
            tabletNavigationView.setActiveColor(mActiveColorFilter);
            tabletNavigationView.setInactiveTextColor(mInactiveTextColor);
            tabletNavigationView.setTag(item);
            tabView = tabletNavigationView;
        }
        mContainer.addView(tabView, generateBottomItemLayoutParams());
        mBottomTabViews.add(tabView);
    }

    /**
     * Method for manually selecting navigation item index (zero  based)
     *
     * @param selectedItemPosition Zero based index for item position
     */
    public void setSelectedItemPosition(int selectedItemPosition) {
        mSelectedItemPosition = selectedItemPosition;
        selectTabView();
    }

    /**
     * Removes all items in container
     */
    public void removeAllTabs() {
        for (int i = mContainer.getChildCount() - 1; i >= 0; i--) {
            mContainer.removeViewAt(i);
        }
        mCurrentNavigationItem = null;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        updateBottomNavViews();
    }

    /**
     * Convinient way to populate bottom navigation layout using BottomTabsBuilder
     *
     * @param builder Must not be null, will throw IllegalArgumentException, if number of items is less than 3 and greater than 5
     */

    public void populateBottomTabItems(@NonNull BottomTabsBuilder builder) {
        if (mContainer.getChildCount() >= MIN_BOTTOM_NAVIGATION_ITEMS) {
            checkBottomItemGuidelines(mContainer.getChildCount());
        }
        List<BottomNavigationItem> build = builder.build();
        for (int i = 0; i < build.size(); i++) {
            BottomNavigationItem item = build.get(i);
            item.setPosition(i);
            addBottomNavigationItem(item);
        }
        updateBottomNavViews();
        selectTabView();
    }

    /**
     *  Resets bottom navigation component to translation 0 in case there is no scrollable content to restore its translation from coordinator layout behavior
     */
    public void resetBottomTabLayout() {
        BottomNavigationBehavior<BottomTabLayout> from = BottomNavigationBehavior.from(this);
        if (from != null) {
            from.setHidden(this, false);
        }
    }

    @Override
    protected Parcelable onSaveInstanceState() {
        Parcelable parcelable = super.onSaveInstanceState();
        if (Build.VERSION.SDK_INT > 10) {
            SavedState savedState = new SavedState(parcelable);
            savedState.selectedPosition = mSelectedItemPosition;
            return savedState;
        }
        return parcelable;
    }

    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        super.onRestoreInstanceState(state);
        if (state instanceof SavedState) {
            mSelectedItemPosition = ((SavedState) state).selectedPosition;
            selectTabView();
        }
    }

    private void dispatchItemSelected(BottomNavigationItem item) {
        if (mNavigationItemSelectionListeners != null && mNavigationItemSelectionListeners.size() > 0) {
            int count = mNavigationItemSelectionListeners.size();
            for (int i = 0; i < count; i++) {
                mNavigationItemSelectionListeners.get(i).onBottomNavigationItemSelected(item);
            }
        }
        mSelectedItemPosition = item.getPosition();
    }

    private void dispatchItemUnselected(BottomNavigationItem item) {
        if (mNavigationItemSelectionListeners != null && mNavigationItemSelectionListeners.size() > 0) {
            int count = mNavigationItemSelectionListeners.size();
            for (int i = 0; i < count; i++) {
                mNavigationItemSelectionListeners.get(i).onBottomNavigationItemUnselected(item);
            }
        }
    }

    private void clearSelection() {
        for (int i = mContainer.getChildCount() - 1; i >= 0; i--) {
            mContainer.getChildAt(i).setSelected(false);
        }
    }

    private void selectTabView() {
        boolean callListener = false;
        if (mSelectedItemPosition == View.NO_ID) {
            return;
        }
        View bottomNavigationTextView = mBottomTabViews.get(mSelectedItemPosition);
        bottomNavigationTextView.requestFocus();
        bottomNavigationTextView.setSelected(true);
        mCurrentNavigationItem = bottomNavigationTextView;
        if (callListener) {
            mBottomTabSelectionClickListener.onClick(mCurrentNavigationItem);
        }

    }

    /**
     * @param position Position of the bottom tab to be removed (0 based)
     */
    public void removeTabAt(int position) {
        ensureListenersList();
        View removedTab = mBottomTabViews.remove(position);
        if (removedTab != null) {
            removeTabInternal(removedTab, position);
        }
    }

    /**
     * @param position        Position of the bottom tab to be removed (0 based)
     * @param removeAnimation Animation to be perfomed before tab being removed from its container
     */
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public void removeTabAt(final int position, @NonNull Animator removeAnimation) {
        ensureListenersList();
        final View removedTab = mBottomTabViews.remove(position);
        if (removedTab != null) {
            removeAnimation.setTarget(removedTab);
            removeAnimation.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    removeTabInternal(removedTab, position);
                }
            });
            removeAnimation.start();
        }
    }

    private void removeTabInternal(View removedTab, int position) {
        mContainer.removeView(removedTab);
        mBottomTabViews.clear();
        int newCount = mContainer.getChildCount();
        for (int i = 0; i < newCount; i++) {
            mBottomTabViews.add(i, mContainer.getChildAt(position));
        }
        if (mSelectedItemPosition == position) {
            mSelectedItemPosition = Math.max(0, mSelectedItemPosition - 1);
            if (mContainer.getChildCount() > 0) {
                selectTabView();
            }
        }
    }

    /**
     * @param enable Disables translation of layout when scrolled. This has no effect if the BottomNavigationBehavior is removed from this Layout
     */
    public void setEnableScrollingBehavior(boolean enable) {
        BottomNavigationBehavior<BottomTabLayout> bottomTabLayoutBottomNavigationBehavior = BottomNavigationBehavior
                .from(this);
        if (bottomTabLayoutBottomNavigationBehavior != null) {
            bottomTabLayoutBottomNavigationBehavior.setScrollingEnabled(enable);
        }
    }

    final View getRevealOverlayView() {
        return mRevealOverlayView;
    }

    /**
     * @param onNavigationItemSelectionListener Callback of bottom navigation item selection
     */
    public void addOnNavigationItemSelectionListener(
            @NonNull OnNavigationItemSelectionListener onNavigationItemSelectionListener) {
        ensureListenersList();
        mNavigationItemSelectionListeners.add(onNavigationItemSelectionListener);
    }

    public boolean removeOnNavigationItemSelectionListener(
            @NonNull OnNavigationItemSelectionListener onNavigationItemSelectionListener) {
        ensureListenersList();
        return mNavigationItemSelectionListeners.remove(onNavigationItemSelectionListener);
    }

    private void ensureListenersList() {
        if (mNavigationItemSelectionListeners == null)
            mNavigationItemSelectionListeners = new ArrayList<>();
    }

    /**
     * @param activeColor Color resource for active item color filter
     */
    public void setActiveItemColorResource(@ColorRes int activeColor) {
        mActiveColorFilter = activeColor;
    }

    final BottomNavigationItem getPreviouslySelectedItem() {
        return mPreviouslySelectedItem;
    }

    public interface OnNavigationItemSelectionListener {
        void onBottomNavigationItemSelected(BottomNavigationItem item);

        void onBottomNavigationItemUnselected(BottomNavigationItem item);
    }

    public static class BottomTabsBuilder {

        private ArrayList<BottomNavigationItem> mNavItems;

        public BottomTabsBuilder addBottomNavigationItem(@NonNull BottomNavigationItem bottomNavigationItem) {
            ensureList();
            mNavItems.add(bottomNavigationItem);
            return this;
        }

        private void ensureList() {
            if (mNavItems == null) {
                mNavItems = new ArrayList<>();
            }
        }

        public void validate() {
            checkBottomItemGuidelines(mNavItems != null ? mNavItems.size() : 0);
        }

        List<BottomNavigationItem> build() {
            validate();
            return mNavItems;
        }
    }

    static class SavedState extends BaseSavedState {
        public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
            public SavedState createFromParcel(Parcel in) {
                return new SavedState(in);
            }

            public SavedState[] newArray(int size) {
                return new SavedState[size];
            }
        };
        int selectedPosition;

        SavedState(Parcelable superState) {
            super(superState);
        }

        private SavedState(Parcel in) {
            super(in);
            this.selectedPosition = in.readInt();
        }

        @Override
        public void writeToParcel(Parcel out, int flags) {
            super.writeToParcel(out, flags);
            out.writeInt(this.selectedPosition);
        }
    }
}