Initial implementation for BottomNavigationView widget.

Bug: 27675079
Change-Id: Ic24dfa979557f5af06294c61da5d74821254a583
diff --git a/api/current.txt b/api/current.txt
index 4b9459e..1fe440a 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -307,6 +307,24 @@
     method public boolean onDependentViewChanged(android.support.design.widget.CoordinatorLayout, android.view.View, android.view.View);
   }
 
+  public class BottomNavigationView extends android.widget.FrameLayout {
+    ctor public BottomNavigationView(android.content.Context);
+    ctor public BottomNavigationView(android.content.Context, android.util.AttributeSet);
+    ctor public BottomNavigationView(android.content.Context, android.util.AttributeSet, int);
+    method public android.content.res.ColorStateList getItemIconTintList();
+    method public android.content.res.ColorStateList getItemTextColor();
+    method public android.view.Menu getMenu();
+    method public void inflateMenu(int);
+    method public void setItemBackgroundResource(int);
+    method public void setItemIconTintList(android.content.res.ColorStateList);
+    method public void setItemTextColor(android.content.res.ColorStateList);
+    method public void setOnNavigationItemSelectedListener(android.support.design.widget.BottomNavigationView.OnNavigationItemSelectedListener);
+  }
+
+  public static abstract interface BottomNavigationView.OnNavigationItemSelectedListener {
+    method public abstract boolean onNavigationItemSelected(android.view.MenuItem);
+  }
+
   public class BottomSheetBehavior extends android.support.design.widget.CoordinatorLayout.Behavior {
     ctor public BottomSheetBehavior();
     ctor public BottomSheetBehavior(android.content.Context, android.util.AttributeSet);
diff --git a/design/res-public/values/public_styles.xml b/design/res-public/values/public_styles.xml
index a7c0af6..0dcde45 100644
--- a/design/res-public/values/public_styles.xml
+++ b/design/res-public/values/public_styles.xml
@@ -24,6 +24,7 @@
     <public type="style" name="TextAppearance.Design.Snackbar.Message"/>
     <public type="style" name="TextAppearance.Design.Tab"/>
     <public type="style" name="Theme.Design"/>
+    <public type="style" name="Theme.Design.BottomNavigationView"/>
     <public type="style" name="Theme.Design.BottomSheetDialog"/>
     <public type="style" name="Theme.Design.Light"/>
     <public type="style" name="Theme.Design.Light.BottomSheetDialog"/>
diff --git a/design/res/drawable-v21/design_bottom_navigation_item_background.xml b/design/res/drawable-v21/design_bottom_navigation_item_background.xml
new file mode 100644
index 0000000..f30f08b
--- /dev/null
+++ b/design/res/drawable-v21/design_bottom_navigation_item_background.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2016 The Android Open Source Project
+  ~
+  ~ 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.
+  -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        android:color="?attr/colorPrimary" />
\ No newline at end of file
diff --git a/design/res/drawable/design_bottom_navigation_item_background.xml b/design/res/drawable/design_bottom_navigation_item_background.xml
new file mode 100644
index 0000000..7674f42
--- /dev/null
+++ b/design/res/drawable/design_bottom_navigation_item_background.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2016 The Android Open Source Project
+  ~
+  ~ 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.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true">
+        <shape android:shape="rectangle">
+            <solid android:color="#ff0000"/>
+        </shape>
+    </item>
+    <item>
+        <shape android:shape="rectangle">
+            <solid android:color="#ffffff"/>
+        </shape>
+    </item>
+</selector>
\ No newline at end of file
diff --git a/design/res/layout/design_bottom_navigation_item.xml b/design/res/layout/design_bottom_navigation_item.xml
new file mode 100644
index 0000000..7f93eba
--- /dev/null
+++ b/design/res/layout/design_bottom_navigation_item.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2016 The Android Open Source Project
+  ~
+  ~ 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.
+  -->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+    <ImageView
+        android:id="@+id/icon"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:duplicateParentState="true" />
+    <TextView
+        android:id="@+id/label"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textSize="@dimen/design_bottom_navigation_text_size"
+        android:gravity="center"
+        android:duplicateParentState="true" />
+</merge>
\ No newline at end of file
diff --git a/design/res/values/attrs.xml b/design/res/values/attrs.xml
index fc233da..d56cd88 100644
--- a/design/res/values/attrs.xml
+++ b/design/res/values/attrs.xml
@@ -397,5 +397,12 @@
         <attr name="textColorError" format="color" />
     </declare-styleable>
 
-</resources>
+    <declare-styleable name="BottomNavigationView">
+        <!-- The menu resource to inflate and populate items from. -->
+        <attr name="menu"/>
+        <attr name="itemIconTint"/>
+        <attr name="itemTextColor"/>
+        <attr name="itemBackground"/>
+    </declare-styleable>
 
+</resources>
diff --git a/design/res/values/dimens.xml b/design/res/values/dimens.xml
index 604ac52..6439b57e3 100644
--- a/design/res/values/dimens.xml
+++ b/design/res/values/dimens.xml
@@ -58,4 +58,14 @@
     <dimen name="design_bottom_sheet_modal_elevation">16dp</dimen>
     <dimen name="design_bottom_sheet_modal_peek_height">256dp</dimen>
 
+    <dimen name="design_bottom_navigation_height">56dp</dimen>
+    <dimen name="design_bottom_navigation_text_size">12sp</dimen>
+    <dimen name="design_bottom_navigation_active_text_size">14sp</dimen>
+    <dimen name="design_bottom_navigation_top_padding">8dp</dimen>
+    <dimen name="design_bottom_navigation_active_top_padding">6dp</dimen>
+    <dimen name="design_bottom_navigation_horizontal_padding">12dp</dimen>
+    <dimen name="design_bottom_navigation_bottom_padding">8dp</dimen>
+    <dimen name="design_bottom_navigation_item_max_width">96dp</dimen>
+    <dimen name="design_bottom_navigation_active_item_max_width">168dp</dimen>
+
 </resources>
diff --git a/design/res/values/styles.xml b/design/res/values/styles.xml
index b25646c..ccc310e 100644
--- a/design/res/values/styles.xml
+++ b/design/res/values/styles.xml
@@ -42,6 +42,10 @@
         <item name="tabMode">fixed</item>
     </style>
 
+    <style name="Widget.Design.BottomNavigationView" parent="">
+        <item name="itemBackground">?attr/selectableItemBackgroundBorderless</item>
+    </style>
+
     <style name="Base.Widget.Design.TabLayout" parent="android:Widget">
         <item name="tabMaxWidth">@dimen/design_tab_max_width</item>
         <item name="tabIndicatorColor">?attr/colorAccent</item>
diff --git a/design/src/android/support/design/internal/BottomNavigationItemView.java b/design/src/android/support/design/internal/BottomNavigationItemView.java
new file mode 100644
index 0000000..0d7df1a
--- /dev/null
+++ b/design/src/android/support/design/internal/BottomNavigationItemView.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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 android.support.design.internal;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.support.annotation.NonNull;
+import android.support.design.R;
+import android.support.v4.content.ContextCompat;
+import android.support.v4.graphics.drawable.DrawableCompat;
+import android.support.v4.view.animation.LinearOutSlowInInterpolator;
+import android.support.v7.view.menu.MenuItemImpl;
+import android.support.v7.view.menu.MenuView;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+/**
+ * @hide
+ */
+public class BottomNavigationItemView extends ForegroundLinearLayout implements MenuView.ItemView {
+    public static final int INVALID_ITEM_POSTION = -1;
+
+    private static final int[] CHECKED_STATE_SET = { android.R.attr.state_checked };
+    private static final long ACTIVE_ANIMATION_DURATION_MS = 115L;
+
+    private final int mHorizontalPadding;
+    private final int mBottomPadding;
+    private final int mTopPadding;
+    private final int mActiveTopPadding;
+    private final float mInactiveLabelSize;
+    private final float mActiveLabelSize;
+
+    private ImageView mIcon;
+    private TextView mLabel;
+    private int mItemPosition = INVALID_ITEM_POSTION;
+
+    private MenuItemImpl mItemData;
+
+    private ColorStateList mIconTint;
+    private ColorStateList mTextColor;
+
+    public BottomNavigationItemView(@NonNull Context context) {
+        this(context, null);
+    }
+
+    public BottomNavigationItemView(@NonNull Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public BottomNavigationItemView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        mHorizontalPadding = getResources().getDimensionPixelSize(
+                R.dimen.design_bottom_navigation_horizontal_padding);
+        mBottomPadding = getResources().getDimensionPixelSize(
+                R.dimen.design_bottom_navigation_bottom_padding);
+        mTopPadding = getResources().getDimensionPixelSize(
+                R.dimen.design_bottom_navigation_top_padding);
+        mActiveTopPadding = getResources().getDimensionPixelSize(
+                R.dimen.design_bottom_navigation_active_top_padding);
+        mInactiveLabelSize =
+                getResources().getDimension(R.dimen.design_bottom_navigation_text_size);
+        mActiveLabelSize =
+                getResources().getDimension(R.dimen.design_bottom_navigation_active_text_size);
+
+        setOrientation(VERTICAL);
+        setGravity(Gravity.CENTER);
+        LayoutInflater.from(context).inflate(R.layout.design_bottom_navigation_item, this, true);
+        setBackgroundResource(R.drawable.design_bottom_navigation_item_background);
+        mIcon = (ImageView) findViewById(R.id.icon);
+        mLabel = (TextView) findViewById(R.id.label);
+    }
+
+    @Override
+    public void initialize(MenuItemImpl itemData, int menuType) {
+        mItemData = itemData;
+        setCheckable(itemData.isCheckable());
+        setChecked(itemData.isChecked());
+        setEnabled(itemData.isEnabled());
+        setIcon(itemData.getIcon());
+        setTitle(itemData.getTitle());
+    }
+
+    public void setItemPosition(int position) {
+        mItemPosition = position;
+    }
+
+    public int getItemPosition() {
+        return mItemPosition;
+    }
+
+    @Override
+    public MenuItemImpl getItemData() {
+        return null;
+    }
+
+    @Override
+    public void setTitle(CharSequence title) {
+        mLabel.setText(title);
+    }
+
+    @Override
+    public void setCheckable(boolean checkable) {
+        refreshDrawableState();
+    }
+
+    @Override
+    public void setChecked(boolean checked) {
+        mItemData.setChecked(checked);
+        if (checked) {
+            mLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, mActiveLabelSize);
+            setPadding(mHorizontalPadding, mActiveTopPadding, mHorizontalPadding, mBottomPadding);
+        } else {
+            mLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, mInactiveLabelSize);
+            setPadding(mHorizontalPadding, mTopPadding, mHorizontalPadding, mBottomPadding);
+        }
+        refreshDrawableState();
+    }
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        super.setEnabled(enabled);
+        mLabel.setEnabled(enabled);
+        mIcon.setEnabled(enabled);
+    }
+
+    @Override
+    public int[] onCreateDrawableState(final int extraSpace) {
+        final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
+        if (mItemData != null && mItemData.isCheckable() && mItemData.isChecked()) {
+            mergeDrawableStates(drawableState, CHECKED_STATE_SET);
+        }
+        return drawableState;
+    }
+
+    @Override
+    public void setShortcut(boolean showShortcut, char shortcutKey) {
+    }
+
+    @Override
+    public void setIcon(Drawable icon) {
+        if (icon != null) {
+            Drawable.ConstantState state = icon.getConstantState();
+            icon = DrawableCompat.wrap(state == null ? icon : state.newDrawable()).mutate();
+            DrawableCompat.setTintList(icon, mIconTint);
+        }
+        mIcon.setImageDrawable(icon);
+    }
+
+    @Override
+    public boolean prefersCondensedTitle() {
+        return false;
+    }
+
+    @Override
+    public boolean showsIcon() {
+        return true;
+    }
+
+    public void setIconTintList(ColorStateList tint) {
+        mIconTint = tint;
+        if (mItemData != null) {
+            // Update the icon so that the tint takes effect
+            setIcon(mItemData.getIcon());
+        }
+        mLabel.setTextColor(mIconTint);
+    }
+
+    public void setTextColor(ColorStateList color) {
+        mTextColor = color;
+        mLabel.setTextColor(color);
+    }
+
+    public void setItemBackground(int background) {
+        Drawable backgroundDrawable = background == 0
+                ? null : ContextCompat.getDrawable(getContext(), background);
+        setBackgroundDrawable(backgroundDrawable);
+    }
+
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+    public Animator getAnimator(boolean active) {
+        final float currentTextSize = mLabel.getTextSize();
+        final int currentTopPadding = getPaddingTop();
+
+        final float finalTextSize = active ? mActiveLabelSize : mInactiveLabelSize;
+        final int finalTopPadding = active ? mActiveTopPadding : mTopPadding;
+
+        if (currentTextSize == finalTextSize && currentTopPadding == finalTopPadding) {
+            return null;
+        }
+
+        // Grow or shrink the text of the tab.
+        ValueAnimator textAnimator = ValueAnimator.ofFloat(currentTextSize, finalTextSize);
+        textAnimator.setDuration(ACTIVE_ANIMATION_DURATION_MS);
+        textAnimator.setInterpolator(new LinearOutSlowInInterpolator());
+        textAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator valueAnimator) {
+                float animatedValue = (float) valueAnimator.getAnimatedValue();
+                mLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, animatedValue);
+            }
+        });
+
+        // Reduce or increase the padding top of the tab.
+        ValueAnimator paddingTopAnimator = ValueAnimator.ofInt(currentTopPadding, finalTopPadding);
+        paddingTopAnimator.setDuration(ACTIVE_ANIMATION_DURATION_MS);
+        paddingTopAnimator.setInterpolator(new LinearOutSlowInInterpolator());
+        paddingTopAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator valueAnimator) {
+                int animatedValue = (int) valueAnimator.getAnimatedValue();
+                setPadding(mHorizontalPadding, animatedValue,
+                        mHorizontalPadding, mBottomPadding);
+            }
+        });
+
+        AnimatorSet set = new AnimatorSet();
+        set.playTogether(textAnimator, paddingTopAnimator);
+        return set;
+    }
+}
diff --git a/design/src/android/support/design/internal/BottomNavigationMenuView.java b/design/src/android/support/design/internal/BottomNavigationMenuView.java
new file mode 100644
index 0000000..a4df28f
--- /dev/null
+++ b/design/src/android/support/design/internal/BottomNavigationMenuView.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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 android.support.design.internal;
+
+import android.animation.AnimatorSet;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.os.Build;
+import android.support.annotation.Nullable;
+import android.support.design.R;
+import android.support.v4.util.Pools;
+import android.support.v7.view.menu.MenuBuilder;
+import android.support.v7.view.menu.MenuItemImpl;
+import android.support.v7.view.menu.MenuView;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+/**
+ * @hide
+ */
+public class BottomNavigationMenuView extends LinearLayout implements MenuView {
+    private final int mInactiveItemMaxWidth;
+    private final int mActiveItemMaxWidth;
+    private final OnClickListener mOnClickListener;
+    private static final Pools.Pool<BottomNavigationItemView> sItemPool =
+            new Pools.SynchronizedPool<>(5);
+
+    private BottomNavigationItemView[] mButtons;
+    private int mActiveButton = 0;
+    private ColorStateList mItemIconTint;
+    private ColorStateList mItemTextColor;
+    private int mItemBackgroundRes;
+
+    private BottomNavigationPresenter mPresenter;
+    private MenuBuilder mMenu;
+
+    public BottomNavigationMenuView(Context context) {
+        this(context, null);
+    }
+
+    public BottomNavigationMenuView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public BottomNavigationMenuView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        setGravity(Gravity.CENTER);
+        setOrientation(HORIZONTAL);
+
+        mInactiveItemMaxWidth = getResources().getDimensionPixelSize(
+                R.dimen.design_bottom_navigation_item_max_width);
+        mActiveItemMaxWidth = getResources()
+                .getDimensionPixelSize(R.dimen.design_bottom_navigation_active_item_max_width);
+
+        mOnClickListener = new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                final int itemPosition = ((BottomNavigationItemView) v).getItemPosition();
+                activateNewButton(itemPosition);
+            }
+        };
+    }
+
+    @Override
+    public void initialize(MenuBuilder menu) {
+        mMenu = menu;
+        if (mMenu == null) return;
+        if (mMenu.size() > mActiveButton) {
+            mMenu.getItem(mActiveButton).setChecked(true);
+        }
+    }
+
+    @Override
+    public int getWindowAnimations() {
+        return 0;
+    }
+
+    public void setIconTintList(ColorStateList color) {
+        mItemIconTint = color;
+        if (mButtons == null) return;
+        for (BottomNavigationItemView item : mButtons) {
+            item.setIconTintList(color);
+        }
+    }
+
+    @Nullable
+    public ColorStateList getIconTintList() {
+        return mItemIconTint;
+    }
+
+    public void setItemTextColor(ColorStateList color) {
+        mItemTextColor = color;
+        if (mButtons == null) return;
+        for (BottomNavigationItemView item : mButtons) {
+            item.setTextColor(color);
+        }
+    }
+
+    public ColorStateList getItemTextColor() {
+        return mItemTextColor;
+    }
+
+    public void setItemBackgroundRes(int background) {
+        mItemBackgroundRes = background;
+        if (mButtons == null) return;
+        for (BottomNavigationItemView item : mButtons) {
+            item.setItemBackground(background);
+        }
+    }
+
+    public int getItemBackgroundRes() {
+        return mItemBackgroundRes;
+    }
+
+    public void setPresenter(BottomNavigationPresenter presenter) {
+        mPresenter = presenter;
+    }
+
+    public void buildMenuView() {
+        if (mButtons != null) {
+            for (BottomNavigationItemView item : mButtons) {
+                sItemPool.release(item);
+            }
+        }
+        removeAllViews();
+        mButtons = new BottomNavigationItemView[mMenu.size()];
+        for (int i = 0; i < mMenu.size(); i++) {
+            mPresenter.setUpdateSuspended(true);
+            mMenu.getItem(i).setCheckable(true);
+            mPresenter.setUpdateSuspended(false);
+            BottomNavigationItemView child = getNewItem();
+            mButtons[i] = child;
+            child.setIconTintList(mItemIconTint);
+            child.setTextColor(mItemTextColor);
+            child.setItemBackground(mItemBackgroundRes);
+            child.initialize((MenuItemImpl) mMenu.getItem(i), 0);
+            child.setItemPosition(i);
+            child.setOnClickListener(mOnClickListener);
+            addView(child);
+        }
+    }
+
+    public void updateMenuView() {
+        final int menuSize = mMenu.size();
+        if (menuSize != mButtons.length) {
+            // The size has changed. Rebuild menu view from scratch.
+            buildMenuView();
+            return;
+        }
+        for (int i = 0; i < menuSize; i++) {
+            mPresenter.setUpdateSuspended(true);
+            mButtons[i].initialize((MenuItemImpl) mMenu.getItem(i), 0);
+            mPresenter.setUpdateSuspended(false);
+        }
+    }
+
+    private void activateNewButton(int newButton) {
+        if (mActiveButton == newButton) return;
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+            AnimatorSet animatorSet = new AnimatorSet();
+            animatorSet.playTogether(
+                    mButtons[mActiveButton].getAnimator(false),
+                    mButtons[newButton].getAnimator(true));
+            animatorSet.start();
+        }
+        mPresenter.setUpdateSuspended(true);
+        mButtons[mActiveButton].setChecked(false);
+        mButtons[newButton].setChecked(true);
+        mPresenter.setUpdateSuspended(false);
+
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+            // Manually force UI update since we cannot use animations.
+            mPresenter.updateMenuView(true);
+        }
+        mActiveButton = newButton;
+    }
+
+    public void updateOnSizeChange(int width) {
+        if (getChildCount() == 0) return;
+        int available = width / getChildCount();
+        int itemWidth = Math.min(available, mActiveItemMaxWidth);
+
+        for (int i = 0; i < mButtons.length; i++) {
+            ViewGroup.LayoutParams params = mButtons[i].getLayoutParams();
+            if (params.width == itemWidth) {
+                continue;
+            }
+            params.width = itemWidth;
+            params.height = ViewGroup.LayoutParams.MATCH_PARENT;
+            mButtons[i].setLayoutParams(params);
+        }
+    }
+
+    private BottomNavigationItemView getNewItem() {
+        BottomNavigationItemView item = sItemPool.acquire();
+        if (item == null) {
+            item = new BottomNavigationItemView(getContext());
+        }
+        return item;
+    }
+}
diff --git a/design/src/android/support/design/internal/BottomNavigationPresenter.java b/design/src/android/support/design/internal/BottomNavigationPresenter.java
new file mode 100644
index 0000000..8dc0549
--- /dev/null
+++ b/design/src/android/support/design/internal/BottomNavigationPresenter.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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 android.support.design.internal;
+
+import android.content.Context;
+import android.os.Parcelable;
+import android.support.v7.view.menu.MenuBuilder;
+import android.support.v7.view.menu.MenuItemImpl;
+import android.support.v7.view.menu.MenuPresenter;
+import android.support.v7.view.menu.MenuView;
+import android.support.v7.view.menu.SubMenuBuilder;
+import android.view.ViewGroup;
+
+/**
+ * @hide
+ */
+public class BottomNavigationPresenter implements MenuPresenter {
+    private MenuBuilder mMenu;
+    private BottomNavigationMenuView mMenuView;
+    private boolean mUpdateSuspended = false;
+
+    public void setBottomNavigationMenuView(BottomNavigationMenuView menuView) {
+        mMenuView = menuView;
+    }
+
+    @Override
+    public void initForMenu(Context context, MenuBuilder menu) {
+        mMenuView.initialize(mMenu);
+        mMenu = menu;
+    }
+
+    @Override
+    public MenuView getMenuView(ViewGroup root) {
+        return mMenuView;
+    }
+
+    @Override
+    public void updateMenuView(boolean cleared) {
+        if (mUpdateSuspended) return;
+        if (cleared) {
+            mMenuView.buildMenuView();
+        } else {
+            mMenuView.updateMenuView();
+        }
+    }
+
+    @Override
+    public void setCallback(Callback cb) {}
+
+    @Override
+    public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+        return false;
+    }
+
+    @Override
+    public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {}
+
+    @Override
+    public boolean flagActionItems() {
+        return false;
+    }
+
+    @Override
+    public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
+        return false;
+    }
+
+    @Override
+    public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
+        return false;
+    }
+
+    @Override
+    public int getId() {
+        return -1;
+    }
+
+    @Override
+    public Parcelable onSaveInstanceState() {
+        return null;
+    }
+
+    @Override
+    public void onRestoreInstanceState(Parcelable state) {}
+
+    public void setUpdateSuspended(boolean updateSuspended) {
+        mUpdateSuspended = updateSuspended;
+    }
+}
diff --git a/design/src/android/support/design/widget/BottomNavigationView.java b/design/src/android/support/design/widget/BottomNavigationView.java
new file mode 100644
index 0000000..1ccb6ff
--- /dev/null
+++ b/design/src/android/support/design/widget/BottomNavigationView.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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 android.support.design.widget;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.design.R;
+import android.support.design.internal.BottomNavigationMenuView;
+import android.support.design.internal.BottomNavigationPresenter;
+import android.support.v7.content.res.AppCompatResources;
+import android.support.v7.view.SupportMenuInflater;
+import android.support.v7.view.menu.MenuBuilder;
+import android.support.v7.widget.TintTypedArray;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+
+/**
+ * Represents a standard bottom navigation bar for application. It is an implementation of material
+ * design bottom navigation. See https://material.google.com/components/bottom-navigation.html
+ *
+ * Bottom navigation bars make it easy for users to explore and switch between top-level views in
+ * a single tap. It should be used when application has three to five top-level destinations.
+ *
+ * The bar contents can be populated by specifying a menu resource file. Each menu item title, icon
+ * and enabled state will be used for displaying bottom navigation bar items.
+ *
+ * &lt;android.support.design.widget.BottomNavigationView
+ *     xmlns:android="http://schemas.android.com/apk/res/android"
+ *     xmlns:app="http://schemas.android.com/apk/res-auto"
+ *     android:id="@+id/navigation"
+ *     android:layout_width="wrap_content"
+ *     android:layout_height="match_parent"
+ *     android:layout_gravity="start"
+ *     app:menu="@menu/my_navigation_items" /&gt;
+ */
+public class BottomNavigationView extends FrameLayout {
+
+    private static final int[] CHECKED_STATE_SET = {android.R.attr.state_checked};
+    private static final int[] DISABLED_STATE_SET = {-android.R.attr.state_enabled};
+
+    private final MenuBuilder mMenu;
+    private final BottomNavigationMenuView mMenuView;
+    private final BottomNavigationPresenter mPresenter = new BottomNavigationPresenter();
+    private MenuInflater mMenuInflater;
+
+    private OnNavigationItemSelectedListener mListener;
+
+    public BottomNavigationView(Context context) {
+        this(context, null);
+    }
+
+    public BottomNavigationView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public BottomNavigationView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+
+        ThemeUtils.checkAppCompatTheme(context);
+
+        // Create the menu
+        mMenu = new MenuBuilder(context);
+
+        mMenuView = new BottomNavigationMenuView(context, attrs);
+        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
+        mMenuView.setLayoutParams(params);
+
+        mPresenter.setBottomNavigationMenuView(mMenuView);
+        mMenuView.setPresenter(mPresenter);
+        mMenu.addMenuPresenter(mPresenter);
+
+
+        // Custom attributes
+        TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs,
+                R.styleable.BottomNavigationView, defStyleAttr,
+                R.style.Widget_Design_BottomNavigationView);
+
+        if (a.hasValue(R.styleable.BottomNavigationView_itemIconTint)) {
+            mMenuView.setIconTintList(
+                    a.getColorStateList(R.styleable.BottomNavigationView_itemIconTint));
+        } else {
+            mMenuView.setIconTintList(
+                    createDefaultColorStateList(android.R.attr.textColorSecondary));
+        }
+        if (a.hasValue(R.styleable.BottomNavigationView_itemTextColor)) {
+            mMenuView.setItemTextColor(
+                    a.getColorStateList(R.styleable.BottomNavigationView_itemTextColor));
+        } else {
+            mMenuView.setItemTextColor(
+                    createDefaultColorStateList(android.R.attr.textColorSecondary));
+        }
+
+        int itemBackground = a.getResourceId(R.styleable.BottomNavigationView_itemBackground, 0);
+        mMenuView.setItemBackgroundRes(itemBackground);
+
+        if (a.hasValue(R.styleable.BottomNavigationView_menu)) {
+            inflateMenu(a.getResourceId(R.styleable.BottomNavigationView_menu, 0));
+        }
+        a.recycle();
+
+        addView(mMenuView);
+
+        mMenu.setCallback(new MenuBuilder.Callback() {
+            @Override
+            public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
+                return mListener != null && mListener.onNavigationItemSelected(item);
+            }
+
+            @Override
+            public void onMenuModeChange(MenuBuilder menu) {}
+        });
+    }
+
+    /**
+     * Set a listener that will be notified when a bottom navigation item is selected.
+     *
+     * @param listener The listener to notify
+     */
+    public void setOnNavigationItemSelectedListener(
+            @Nullable OnNavigationItemSelectedListener listener) {
+        mListener = listener;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        // TODO(aurimas): move updateOnSizeChange to a different location that is less expensive.
+        mMenuView.updateOnSizeChange(MeasureSpec.getSize(widthMeasureSpec));
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+        mMenuView.updateOnSizeChange(w);
+    }
+
+    /**
+     * Returns the {@link Menu} instance associated with this bottom navigation bar.
+     */
+    @NonNull
+    public Menu getMenu() {
+        return mMenu;
+    }
+
+    /**
+     * Inflate a menu resource into this navigation view.
+     *
+     * <p>Existing items in the menu will not be modified or removed.</p>
+     *
+     * @param resId ID of a menu resource to inflate
+     */
+    public void inflateMenu(int resId) {
+        mPresenter.setUpdateSuspended(true);
+        getMenuInflater().inflate(resId, mMenu);
+        mPresenter.initForMenu(getContext(), mMenu);
+        mPresenter.setUpdateSuspended(false);
+        mPresenter.updateMenuView(true);
+    }
+
+    /**
+     * Returns the tint which is applied to our menu items' icons.
+     *
+     * @see #setItemIconTintList(ColorStateList)
+     *
+     * @attr ref R.styleable#BottomNavigationView_itemIconTint
+     */
+    @Nullable
+    public ColorStateList getItemIconTintList() {
+        return mMenuView.getIconTintList();
+    }
+
+    /**
+     * Set the tint which is applied to our menu items' icons.
+     *
+     * @param tint the tint to apply.
+     *
+     * @attr ref R.styleable#BottomNavigationView_itemIconTint
+     */
+    public void setItemIconTintList(@Nullable ColorStateList tint) {
+        mMenuView.setIconTintList(tint);
+    }
+
+
+    /**
+     * Returns the tint which is applied to menu items' icons.
+     *
+     * @see #setItemTextColor(ColorStateList)
+     *
+     * @attr ref R.styleable#BottomNavigationView_itemTextColor
+     */
+    @Nullable
+    public ColorStateList getItemTextColor() {
+        return mMenuView.getItemTextColor();
+    }
+
+    /**
+     * Set the text color to be used on menu items.
+     *
+     * @see #getItemTextColor()
+     *
+     * @attr ref R.styleable#BottomNavigationView_itemTextColor
+     */
+    public void setItemTextColor(@Nullable ColorStateList textColor) {
+        mMenuView.setItemTextColor(textColor);
+    }
+
+    /**
+     * Set the background of our menu items to the given resource.
+     *
+     * @param resId The identifier of the resource.
+     *
+     * @attr ref R.styleable#BottomNavigationView_itemBackground
+     */
+    public void setItemBackgroundResource(@DrawableRes int resId) {
+        mMenuView.setItemBackgroundRes(resId);
+    }
+
+    /**
+     * Listener for handling events on bottom navigation items.
+     */
+    public interface OnNavigationItemSelectedListener {
+
+        /**
+         * Called when an item in the bottom navigation menu is selected.
+         *
+         * @param item The selected item
+         *
+         * @return true to display the item as the selected item
+         */
+        public boolean onNavigationItemSelected(@NonNull MenuItem item);
+    }
+
+    private MenuInflater getMenuInflater() {
+        if (mMenuInflater == null) {
+            mMenuInflater = new SupportMenuInflater(getContext());
+        }
+        return mMenuInflater;
+    }
+
+    private ColorStateList createDefaultColorStateList(int baseColorThemeAttr) {
+        final TypedValue value = new TypedValue();
+        if (!getContext().getTheme().resolveAttribute(baseColorThemeAttr, value, true)) {
+            return null;
+        }
+        ColorStateList baseColor = AppCompatResources.getColorStateList(
+                getContext(), value.resourceId);
+        if (!getContext().getTheme().resolveAttribute(
+                android.support.v7.appcompat.R.attr.colorPrimary, value, true)) {
+            return null;
+        }
+        int colorPrimary = value.data;
+        int defaultColor = baseColor.getDefaultColor();
+        return new ColorStateList(new int[][]{
+                DISABLED_STATE_SET,
+                CHECKED_STATE_SET,
+                EMPTY_STATE_SET
+        }, new int[]{
+                baseColor.getColorForState(DISABLED_STATE_SET, defaultColor),
+                colorPrimary,
+                defaultColor
+        });
+    }
+}
diff --git a/samples/SupportDesignDemos/AndroidManifest.xml b/samples/SupportDesignDemos/AndroidManifest.xml
index 4d05bc6..ec97037 100644
--- a/samples/SupportDesignDemos/AndroidManifest.xml
+++ b/samples/SupportDesignDemos/AndroidManifest.xml
@@ -307,5 +307,14 @@
             </intent-filter>
         </activity>
 
+        <activity android:name=".widget.BottomNavigationViewUsage"
+                  android:label="@string/design_bottom_navigation_view"
+                  android:theme="@style/Theme.Design">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="com.example.android.support.design.SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
+
     </application>
 </manifest>
diff --git a/samples/SupportDesignDemos/res/layout/design_bottom_navigation_view.xml b/samples/SupportDesignDemos/res/layout/design_bottom_navigation_view.xml
new file mode 100644
index 0000000..7e66c79
--- /dev/null
+++ b/samples/SupportDesignDemos/res/layout/design_bottom_navigation_view.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2016 The Android Open Source Project
+
+     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.
+-->
+<FrameLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:app="http://schemas.android.com/apk/res-auto"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+    <Button
+            android:id="@+id/button_disable"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/bottomnavigation_disable"/>
+
+    <Button
+        android:id="@+id/button_add"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="50dp"
+        android:text="@string/bottomnavigation_add"/>
+
+
+    <android.support.design.widget.BottomNavigationView
+            android:id="@+id/bottom_navigation"
+            android:layout_width="match_parent"
+            android:layout_height="56dp"
+            android:layout_gravity="bottom"
+            android:background="#eee"
+            app:menu="@menu/sample_bottom_menu"/>
+
+</FrameLayout>
diff --git a/samples/SupportDesignDemos/res/menu/sample_bottom_menu.xml b/samples/SupportDesignDemos/res/menu/sample_bottom_menu.xml
new file mode 100644
index 0000000..4294f80
--- /dev/null
+++ b/samples/SupportDesignDemos/res/menu/sample_bottom_menu.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 Google Inc.
+
+     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.
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/action_search"
+          android:title="@string/menu_search"
+          android:icon="@drawable/ic_search"/>
+    <item android:id="@+id/action_settings"
+          android:title="@string/menu_settings"
+          android:icon="@drawable/ic_add"/>
+    <item android:id="@+id/action_navigation"
+          android:title="@string/tab_text"
+          android:icon="@drawable/ic_action_navigation_menu"/>
+</menu>
\ No newline at end of file
diff --git a/samples/SupportDesignDemos/res/values/strings.xml b/samples/SupportDesignDemos/res/values/strings.xml
index 8f21310..1040e0e 100644
--- a/samples/SupportDesignDemos/res/values/strings.xml
+++ b/samples/SupportDesignDemos/res/values/strings.xml
@@ -106,4 +106,8 @@
     <string name="bottomsheet_show">Show</string>
     <string name="item_n">Item %d</string>
 
+    <string name="design_bottom_navigation_view">Bottom navigation view</string>
+
+    <string name="bottomnavigation_disable">Disable item</string>
+    <string name="bottomnavigation_add">Add item</string>
 </resources>
diff --git a/samples/SupportDesignDemos/src/com/example/android/support/design/widget/BottomNavigationViewUsage.java b/samples/SupportDesignDemos/src/com/example/android/support/design/widget/BottomNavigationViewUsage.java
new file mode 100644
index 0000000..e19554d
--- /dev/null
+++ b/samples/SupportDesignDemos/src/com/example/android/support/design/widget/BottomNavigationViewUsage.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.example.android.support.design.widget;
+
+import android.os.Bundle;
+import android.support.design.R;
+import android.support.design.widget.BottomNavigationView;
+import android.support.v7.app.AppCompatActivity;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.Button;
+
+/**
+ * This demonstrates idiomatic usage of the bottom navigation widget.
+ */
+public class BottomNavigationViewUsage extends AppCompatActivity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.design_bottom_navigation_view);
+        Button buttonDisable = (Button) findViewById(R.id.button_disable);
+        final BottomNavigationView bottom =
+                (BottomNavigationView) findViewById(R.id.bottom_navigation);
+        buttonDisable.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                bottom.getMenu().getItem(0).setEnabled(!bottom.getMenu().getItem(0).isEnabled());
+            }
+        });
+        Button buttonAdd = (Button) findViewById(R.id.button_add);
+        buttonAdd.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                MenuItem item = bottom.getMenu().add("Bananas");
+                item.setIcon(android.R.drawable.ic_lock_power_off);
+            }
+        });
+    }
+}