├── .gitignore ├── CHANGELOG.md ├── README.md ├── ahbottomnavigation ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── aurelhubert │ │ └── ahbottomnavigation │ │ ├── AHBottomNavigation.java │ │ ├── AHBottomNavigationAdapter.java │ │ ├── AHBottomNavigationBehavior.java │ │ ├── AHBottomNavigationFABBehavior.java │ │ ├── AHBottomNavigationItem.java │ │ ├── AHBottomNavigationViewPager.java │ │ ├── AHHelper.java │ │ ├── VerticalScrollingBehavior.java │ │ └── notification │ │ ├── AHNotification.java │ │ └── AHNotificationHelper.java │ └── res │ ├── drawable-v21 │ └── item_background.xml │ ├── drawable │ ├── item_background.xml │ └── notification_background.xml │ ├── layout-v21 │ ├── bottom_navigation_item.xml │ └── bottom_navigation_small_item.xml │ ├── layout │ ├── bottom_navigation_item.xml │ └── bottom_navigation_small_item.xml │ └── values │ ├── attrs.xml │ ├── colors.xml │ └── dimens.xml ├── build.gradle ├── demo ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── aurelhubert │ │ └── ahbottomnavigation │ │ └── demo │ │ ├── DemoActivity.java │ │ ├── DemoAdapter.java │ │ ├── DemoFragment.java │ │ └── DemoViewPagerAdapter.java │ └── res │ ├── anim │ ├── fade_in.xml │ └── fade_out.xml │ ├── drawable-hdpi │ ├── ic_content_add.png │ ├── ic_maps_local_attraction.png │ ├── ic_maps_local_bar.png │ ├── ic_maps_local_restaurant.png │ └── ic_maps_place.png │ ├── drawable-mdpi │ ├── ic_content_add.png │ ├── ic_maps_local_attraction.png │ ├── ic_maps_local_bar.png │ ├── ic_maps_local_restaurant.png │ └── ic_maps_place.png │ ├── drawable-xhdpi │ ├── ic_content_add.png │ ├── ic_maps_local_attraction.png │ ├── ic_maps_local_bar.png │ ├── ic_maps_local_restaurant.png │ └── ic_maps_place.png │ ├── drawable-xxhdpi │ ├── ic_content_add.png │ ├── ic_maps_local_attraction.png │ ├── ic_maps_local_bar.png │ ├── ic_maps_local_restaurant.png │ └── ic_maps_place.png │ ├── drawable-xxxhdpi │ ├── ic_content_add.png │ ├── ic_maps_local_attraction.png │ ├── ic_maps_local_bar.png │ ├── ic_maps_local_restaurant.png │ └── ic_maps_place.png │ ├── drawable │ ├── bottom_navigation_background.xml │ └── ic_apps_black_24dp.xml │ ├── layout │ ├── activity_home.xml │ ├── fragment_demo_list.xml │ ├── fragment_demo_settings.xml │ └── layout_item_demo.xml │ ├── menu │ ├── bottom_navigation_menu_3.xml │ └── bottom_navigation_menu_5.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ ├── values-v19 │ └── styles.xml │ ├── values-v21 │ └── styles.xml │ ├── values-w820dp │ └── dimens.xml │ ├── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml │ └── xml │ └── bottom_fav.xml ├── demo1.gif ├── demo2.gif ├── demo3.gif ├── demo4.gif ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Gradle files 2 | .gradle/ 3 | build/ 4 | 5 | # Local configuration file (sdk path, etc) 6 | local.properties 7 | 8 | # Android Studio generated folders 9 | .navigation/ 10 | captures/ 11 | .externalNativeBuild 12 | 13 | # IntelliJ project files 14 | *.iml 15 | .idea/ 16 | 17 | # Misc 18 | .DS_Store 19 | 20 | # Keystore files 21 | *.jks 22 | 23 | # Google Services (e.g. APIs or Firebase) 24 | google-services.json 25 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Changelog 2 | 3 | ### Newest version: 2.3.4 4 | * Fix a bug with the disappearing animation for the notification 5 | 6 | ### 2.3.3 7 | * Re-add old notification animation behavior to avoid a bug (from 2.3.0: Badge removal no longer clears text prior to animation) 8 | 9 | ### 2.3.2 10 | * Remove notification animation listener to avoid weird behaviors 11 | 12 | ### 2.3.1 13 | * Fix animation duration for notification 14 | 15 | ### 2.3.0 16 | * Migrate project to AndroidX 17 | * Update libraries versions 18 | * Incorporate padding into item width calculation 19 | * Badge removal no longer clears text prior to animation 20 | * Fix NPE crash when currentItem was switched programmatically with titleState = TitleState.SHOW_WHEN_ACTIVE_FORCE 21 | * Update isClassic() in AHBottomNavigation 22 | * Navigation item layouts for >= SDK 21 now use item_background drawable for background. 23 | * Only change drawable colour if forceTint is true (default value) 24 | * Add method `addItemAtIndex(int index, AHBottomNavigationItem item)` (with warning when index is out of bounds) 25 | 26 | ### 2.2.0 27 | * Update libraries versions 28 | * Add another state for titles: `SHOW_WHEN_ACTIVE_FORCE` (PR #313) 29 | 30 | ### 2.1.0 31 | 32 | * Update libraries versions 33 | * Add enable/disable tab state (with custom color) 34 | * Add new xml attributes (`colored`, `accentColor`, `inactiveColor`, `disableColor`, `coloredActive`, `coloredInactive`) 35 | * Add param `notificationAnimationDuration` 36 | * Update getDrawable method with `AppCompatResources.getDrawable(context, drawableRes);` 37 | If you use drawable selector and target API < 21, don't forget to add this: 38 | `AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
` 39 | 40 | ### 2.0.6 41 | 42 | * Fix selected item background for API >= 21 43 | * Fix `isHidden()` method 44 | * Update design support library version 45 | 46 | ### 2.0.5 47 | 48 | * Add `setTitleTextSizeInSp(float activeSize, float inactiveSize)` 49 | * Update selected item background for API >= 21 50 | * Allow for the disabling of sound effects 51 | 52 | ### 2.0.4 53 | 54 | * Fixed the icon alpha update for API >= 25 55 | 56 | ### 2.0.3 57 | 58 | * Added method `getViewAtPosition(int position)` 59 | 60 | ### 2.0.2 61 | 62 | * Fix a bug when titles are always shown 63 | 64 | ### 2.0.1 65 | 66 | * Fix a crash with `setCurrentItem(int position, boolean useCallback)` 67 | 68 | ### 2.0.0 69 | 70 | * **BREAKING!** 3 states for titles: `SHOW_WHEN_ACTIVE`, `ALWAYS_SHOW` & `ALWAYS_HIDE` (PR #140) 71 | * Color under the navigation bar (PR #166) 72 | * Fix CoordinatorLayout with FloatingActionButton: use `manageFloatingActionButtonBehavior` 73 | 74 | ### 1.5.1 75 | 76 | * Fixed ripple effect bug (API 21+) 77 | 78 | ### 1.5.0 79 | 80 | * Added AHNotification class to manage easily the style of each notification (PR #156) (**old method still works**) 81 | * Added `setForceTitlesHide(boolean forceTitlesHide)` to force the titles to be hidden (when 3 or less items are displayed) 82 | * Updated `buildToolsVersion` to version `24.0.2` 83 | * Updated `'com.android.support:design:24.2.1'` 84 | 85 | ### 1.4.0 86 | 87 | * Added `isHidden()` method. 88 | * Added `setDefaultBackgroundResource(@DrawableRes int defaultBackgroundResource)` 89 | * Added optional selected item background (PR #132) 90 | * Displayed classic items for less than 3 items (PR #152) 91 | 92 | ### 1.3.3 93 | 94 | * Added a setup method with colors for `AHBottomNavigationAdapter` 95 | 96 | ### 1.3.2 97 | 98 | * Added a new class `AHBottomNavigationAdapter` to inflate menu from resources. 99 | * Updated example to show how to implement `AHBottomNavigationAdapter`. 100 | 101 | ### 1.3.1 102 | 103 | * Added `setColoredModeColors(@ColorInt int colorActive, @ColorInt int colorInactive)` to set the item color for the colored mode. 104 | * Added `OnNavigationPositionListener` to follow the Y translation changes of the bottom navigation. 105 | * Improved vector support. 106 | 107 | ### 1.3.0 108 | 109 | * **BREAKING!** Updated listener, now return a boolean => `boolean onTabSelected(int position, boolean wasSelected);` 110 | * Improved notification management for small items 111 | * Added notification elevation 112 | * Managed complex drawable (selector with states) 113 | * Added constructor `public AHBottomNavigationItem(String title, Drawable drawable)` 114 | 115 | ### 1.2.3 116 | 117 | * Added `setUseElevation(boolean useElevation, float elevation)` 118 | * Fixed a bug with `behaviorTranslationEnabled` & `restoreBottomNavigation` 119 | * Improved translation behavior when the Scroll View is not long enough. 120 | 121 | ### 1.2.2 122 | 123 | * Fixed bug when switching between normal and colored mode 124 | 125 | ### 1.2.1 126 | 127 | * Fixed method typo `setNotificationMarginLef` => `setNotificationMarginLeft` 128 | * Avoid multiple call for showing/hiding AHBottomNavigation 129 | 130 | ### 1.2.0 131 | 132 | * Updated Notification: now accept String (empty String to remove the notification) 133 | * Deprecated integer for Notification 134 | * Removed deprecated methods & interface for `AHBottomNavigationListener` 135 | * Fixed touch ripples when the bottom navigation is colored 136 | * Cleaned colors.xml to avoid conflicts 137 | * Removed constructor AHBottomNavigationItem() 138 | * Added `setTitleTextSize(float activeSize, float inactiveSize)` 139 | * Added `setNotificationMarginLeft(int activeMargin, int inactiveMargin)` 140 | 141 | ### 1.1.8 142 | 143 | * Added `hideBottomNavigation(boolean withAnimation)` 144 | * Added `restoreBottomNavigation(boolean withAnimation)` 145 | 146 | ### 1.1.7 147 | 148 | * Added `public AHBottomNavigationItem getItem(int position)` to get a specific item 149 | * Added `public void refresh()` to force a UI refresh 150 | 151 | ### 1.1.6 152 | 153 | * Improved `hideBottomNavigation()` and `restoreBottomNavigation()` 154 | * Added `setTitleTypeface` 155 | * Changed method name `setNotificationBackgroundColorResource` by `setNotificationTypeface` 156 | * Started working on `onSaveInstanceState` and `onRestoreInstanceState` (currentItem & notifications for now) 157 | 158 | ### 1.1.5 159 | 160 | * Added hideBottomNavigation() 161 | * Added CURRENT_ITEM_NONE to unselect all items 162 | * Improved Notifications (animation, size) 163 | 164 | ### 1.1.4 165 | 166 | * Updated lib dependencies 167 | 168 | ### 1.1.3 169 | 170 | * Fixed Snackbar when setBehaviorTranslationEnabled(false) 171 | 172 | ### 1.1.2 173 | 174 | * Fixed animations on pre Kit Kat 175 | * Added an example with Vector Drawable 176 | 177 | ### 1.1.1 178 | 179 | * Fixed layout rendering with fragments 180 | 181 | ### 1.1.0 182 | 183 | * Compatible with Snackbar 184 | * Compatible with Floating Action Button 185 | 186 | ### 1.0.5 187 | 188 | * Snackbar is now compatible 189 | 190 | ### 1.0.4 191 | 192 | * Added: setCurrentItem(int position, boolean useCallback) 193 | * Added: setUseElevation(boolean useElevation) 194 | * Added: restoreBottomNavigation() 195 | 196 | ### 1.0.3 197 | 198 | * Fixed setForceTint() 199 | 200 | ### 1.0.2 201 | 202 | * Fixed crash when setForceTitlesDisplay(true) 203 | * Improved UI 204 | 205 | ### 1.0.1 206 | 207 | * Bug fixes 208 | * Notifications 209 | * Minimum SDK version: 14 210 | 211 | ### Before 212 | 213 | * AHBottomNavigation was under development. 214 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # AHBottomNavigation 3 | Library to implement the Bottom Navigation component from Material Design guidelines (minSdkVersion=14). 4 | 5 | **Warning: For >= 2.3.0, you need to use AndroidX in your project** 6 | 7 | ## Demo 8 | 9 | 10 | ## What's new (2.3.4) - [Changelog](https://github.com/aurelhubert/ahbottomnavigation/blob/master/CHANGELOG.md) 11 | * Fix a bug with the disappearing animation for the notification 12 | 13 | ## Features 14 | * Follow the bottom navigation guidelines (https://www.google.com/design/spec/components/bottom-navigation.html) 15 | * Add 3 to 5 items (with title, icon & color) 16 | * Choose your style: Classic or colored navigation 17 | * Add a OnTabSelectedListener to detect tab selection 18 | * Support icon font color with "setForceTint(true)" 19 | * Manage notififcations for each item 20 | * Enable/disable tab state 21 | 22 | ## How to? 23 | 24 | ### Gradle 25 | ```groovy 26 | dependencies { 27 | compile 'com.aurelhubert:ahbottomnavigation:2.3.4' 28 | } 29 | ``` 30 | ### XML 31 | ```xml 32 | 36 | ``` 37 | OR 38 | ```xml 39 | 43 | 44 | ... 45 | 46 | 51 | 52 | 53 | ``` 54 | 55 | ### Activity/Fragment 56 | ```java 57 | AHBottomNavigation bottomNavigation = (AHBottomNavigation) findViewById(R.id.bottom_navigation); 58 | 59 | // Create items 60 | AHBottomNavigationItem item1 = new AHBottomNavigationItem(R.string.tab_1, R.drawable.ic_maps_place, R.color.color_tab_1); 61 | AHBottomNavigationItem item2 = new AHBottomNavigationItem(R.string.tab_2, R.drawable.ic_maps_local_bar, R.color.color_tab_2); 62 | AHBottomNavigationItem item3 = new AHBottomNavigationItem(R.string.tab_3, R.drawable.ic_maps_local_restaurant, R.color.color_tab_3); 63 | 64 | // Add items 65 | bottomNavigation.addItem(item1); 66 | bottomNavigation.addItem(item2); 67 | bottomNavigation.addItem(item3); 68 | 69 | // Set background color 70 | bottomNavigation.setDefaultBackgroundColor(Color.parseColor("#FEFEFE")); 71 | 72 | // Disable the translation inside the CoordinatorLayout 73 | bottomNavigation.setBehaviorTranslationEnabled(false); 74 | 75 | // Enable the translation of the FloatingActionButton 76 | bottomNavigation.manageFloatingActionButtonBehavior(floatingActionButton); 77 | 78 | // Change colors 79 | bottomNavigation.setAccentColor(Color.parseColor("#F63D2B")); 80 | bottomNavigation.setInactiveColor(Color.parseColor("#747474")); 81 | 82 | // Force to tint the drawable (useful for font with icon for example) 83 | bottomNavigation.setForceTint(true); 84 | 85 | // Display color under navigation bar (API 21+) 86 | // Don't forget these lines in your style-v21 87 | // true 88 | // true 89 | bottomNavigation.setTranslucentNavigationEnabled(true); 90 | 91 | // Manage titles 92 | bottomNavigation.setTitleState(AHBottomNavigation.TitleState.SHOW_WHEN_ACTIVE); 93 | bottomNavigation.setTitleState(AHBottomNavigation.TitleState.ALWAYS_SHOW); 94 | bottomNavigation.setTitleState(AHBottomNavigation.TitleState.ALWAYS_HIDE); 95 | 96 | // Use colored navigation with circle reveal effect 97 | bottomNavigation.setColored(true); 98 | 99 | // Set current item programmatically 100 | bottomNavigation.setCurrentItem(1); 101 | 102 | // Customize notification (title, background, typeface) 103 | bottomNavigation.setNotificationBackgroundColor(Color.parseColor("#F63D2B")); 104 | 105 | // Add or remove notification for each item 106 | bottomNavigation.setNotification("1", 3); 107 | // OR 108 | AHNotification notification = new AHNotification.Builder() 109 | .setText("1") 110 | .setBackgroundColor(ContextCompat.getColor(DemoActivity.this, R.color.color_notification_back)) 111 | .setTextColor(ContextCompat.getColor(DemoActivity.this, R.color.color_notification_text)) 112 | .build(); 113 | bottomNavigation.setNotification(notification, 1); 114 | 115 | // Enable / disable item & set disable color 116 | bottomNavigation.enableItemAtPosition(2); 117 | bottomNavigation.disableItemAtPosition(2); 118 | bottomNavigation.setItemDisableColor(Color.parseColor("#3A000000")); 119 | 120 | // Set listeners 121 | bottomNavigation.setOnTabSelectedListener(new AHBottomNavigation.OnTabSelectedListener() { 122 | @Override 123 | public boolean onTabSelected(int position, boolean wasSelected) { 124 | // Do something cool here... 125 | return true; 126 | } 127 | }); 128 | bottomNavigation.setOnNavigationPositionListener(new AHBottomNavigation.OnNavigationPositionListener() { 129 | @Override public void onPositionChange(int y) { 130 | // Manage the new y position 131 | } 132 | }); 133 | ``` 134 | 135 | ### With XML menu 136 | ```java 137 | int[] tabColors = getApplicationContext().getResources().getIntArray(R.array.tab_colors); 138 | AHBottomNavigation bottomNavigation = (AHBottomNavigation) findViewById(R.id.bottom_navigation); 139 | AHBottomNavigationAdapter navigationAdapter = new AHBottomNavigationAdapter(this, R.menu.bottom_navigation_menu_3); 140 | navigationAdapter.setupWithBottomNavigation(bottomNavigation, tabColors); 141 | ``` 142 | 143 | ## TODO 144 | * Manage tablet 145 | 146 | ## Contributions 147 | Feel free to create issues / pull requests. 148 | 149 | ## License 150 | ``` 151 | AHBottomNavigation library for Android 152 | Copyright (c) 2018 Aurelien Hubert (http://github.com/aurelhubert). 153 | 154 | Licensed under the Apache License, Version 2.0 (the "License"); 155 | you may not use this file except in compliance with the License. 156 | You may obtain a copy of the License at 157 | 158 | http://www.apache.org/licenses/LICENSE-2.0 159 | 160 | Unless required by applicable law or agreed to in writing, software 161 | distributed under the License is distributed on an "AS IS" BASIS, 162 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 163 | See the License for the specific language governing permissions and 164 | limitations under the License. 165 | ``` 166 | -------------------------------------------------------------------------------- /ahbottomnavigation/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | ext { 4 | bintrayRepo = 'maven' 5 | bintrayName = 'ah-bottom-navigation' 6 | 7 | publishedGroupId = 'com.aurelhubert' 8 | libraryName = 'AHBottomNavigation' 9 | artifact = 'ahbottomnavigation' 10 | 11 | libraryDescription = 'A library to reproduce the behavior of the Bottom Navigation guidelines from Material Design.' 12 | 13 | siteUrl = 'https://github.com/aurelhubert/ahbottomnavigation' 14 | gitUrl = 'https://github.com/aurelhubert/ahbottomnavigation.git' 15 | 16 | libraryVersion = '2.3.4' 17 | 18 | developerId = 'aurelhubert' 19 | developerName = 'Aurelien Hubert' 20 | developerEmail = 'aurel.hubert@gmail.com' 21 | 22 | licenseName = 'The Apache Software License, Version 2.0' 23 | licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt' 24 | allLicenses = ["Apache-2.0"] 25 | } 26 | 27 | android { 28 | compileSdkVersion 28 29 | 30 | defaultConfig { 31 | minSdkVersion 14 32 | targetSdkVersion 28 33 | versionCode 46 34 | versionName "2.3.4" 35 | } 36 | buildTypes { 37 | release { 38 | minifyEnabled false 39 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 40 | } 41 | } 42 | } 43 | 44 | dependencies { 45 | implementation 'com.google.android.material:material:1.0.0' 46 | } 47 | 48 | // Place it at the end of the file 49 | if (project.rootProject.file('local.properties').exists()) { 50 | apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/installv1.gradle' 51 | apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/bintrayv1.gradle' 52 | } 53 | -------------------------------------------------------------------------------- /ahbottomnavigation/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /ahbottomnavigation/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/AHBottomNavigation.java: -------------------------------------------------------------------------------- 1 | package com.aurelhubert.ahbottomnavigation; 2 | 3 | import android.animation.Animator; 4 | import android.annotation.SuppressLint; 5 | import android.annotation.TargetApi; 6 | import android.content.Context; 7 | import android.content.res.Resources; 8 | import android.content.res.TypedArray; 9 | import android.graphics.Color; 10 | import android.graphics.Typeface; 11 | import android.graphics.drawable.Drawable; 12 | import android.os.Build; 13 | import android.os.Bundle; 14 | import android.os.Parcelable; 15 | import androidx.annotation.ColorInt; 16 | import androidx.annotation.ColorRes; 17 | import androidx.annotation.DrawableRes; 18 | import androidx.coordinatorlayout.widget.CoordinatorLayout; 19 | import com.google.android.material.floatingactionbutton.FloatingActionButton; 20 | import androidx.core.content.ContextCompat; 21 | import androidx.core.view.ViewCompat; 22 | import androidx.interpolator.view.animation.LinearOutSlowInInterpolator; 23 | import android.util.AttributeSet; 24 | import android.util.DisplayMetrics; 25 | import android.util.Log; 26 | import android.util.TypedValue; 27 | import android.view.Display; 28 | import android.view.Gravity; 29 | import android.view.LayoutInflater; 30 | import android.view.View; 31 | import android.view.ViewAnimationUtils; 32 | import android.view.ViewGroup; 33 | import android.view.WindowManager; 34 | import android.view.animation.AccelerateInterpolator; 35 | import android.view.animation.OvershootInterpolator; 36 | import android.widget.FrameLayout; 37 | import android.widget.ImageView; 38 | import android.widget.LinearLayout; 39 | import android.widget.TextView; 40 | 41 | import com.aurelhubert.ahbottomnavigation.notification.AHNotification; 42 | import com.aurelhubert.ahbottomnavigation.notification.AHNotificationHelper; 43 | 44 | import java.util.ArrayList; 45 | import java.util.List; 46 | import java.util.Locale; 47 | 48 | /** 49 | * AHBottomNavigationLayout 50 | * Material Design guidelines : https://www.google.com/design/spec/components/bottom-navigation.html 51 | */ 52 | public class AHBottomNavigation extends FrameLayout { 53 | 54 | // Constant 55 | public static final int CURRENT_ITEM_NONE = -1; 56 | public static final int UPDATE_ALL_NOTIFICATIONS = -1; 57 | 58 | // Title state 59 | public enum TitleState { 60 | SHOW_WHEN_ACTIVE, 61 | SHOW_WHEN_ACTIVE_FORCE, 62 | ALWAYS_SHOW, 63 | ALWAYS_HIDE 64 | } 65 | 66 | // Static 67 | private static String TAG = "AHBottomNavigation"; 68 | private static final String EXCEPTION_INDEX_OUT_OF_BOUNDS = "The position (%d) is out of bounds of the items (%d elements)"; 69 | private static final int MIN_ITEMS = 3; 70 | private static final int MAX_ITEMS = 5; 71 | 72 | // Listener 73 | private OnTabSelectedListener tabSelectedListener; 74 | private OnNavigationPositionListener navigationPositionListener; 75 | 76 | // Variables 77 | private Context context; 78 | private Resources resources; 79 | private ArrayList items = new ArrayList<>(); 80 | private ArrayList views = new ArrayList<>(); 81 | private AHBottomNavigationBehavior bottomNavigationBehavior; 82 | private LinearLayout linearLayoutContainer; 83 | private View backgroundColorView; 84 | private Animator circleRevealAnim; 85 | private boolean colored = false; 86 | private boolean selectedBackgroundVisible = false; 87 | private boolean translucentNavigationEnabled; 88 | private List notifications = AHNotification.generateEmptyList(MAX_ITEMS); 89 | private Boolean[] itemsEnabledStates = {true, true, true, true, true}; 90 | private boolean isBehaviorTranslationSet = false; 91 | private int currentItem = 0; 92 | private int currentColor = 0; 93 | private boolean behaviorTranslationEnabled = true; 94 | private boolean needHideBottomNavigation = false; 95 | private boolean hideBottomNavigationWithAnimation = false; 96 | private boolean soundEffectsEnabled = true; 97 | 98 | // Variables (Styles) 99 | private Typeface titleTypeface; 100 | private int defaultBackgroundColor = Color.WHITE; 101 | private int defaultBackgroundResource = 0; 102 | private @ColorInt int itemActiveColor; 103 | private @ColorInt int itemInactiveColor; 104 | private @ColorInt int titleColorActive; 105 | private @ColorInt int itemDisableColor; 106 | private @ColorInt int titleColorInactive; 107 | private @ColorInt int coloredTitleColorActive; 108 | private @ColorInt int coloredTitleColorInactive; 109 | private float titleActiveTextSize, titleInactiveTextSize; 110 | private int bottomNavigationHeight, navigationBarHeight = 0; 111 | private float selectedItemWidth, notSelectedItemWidth; 112 | private boolean forceTint = true; 113 | private TitleState titleState = TitleState.SHOW_WHEN_ACTIVE; 114 | 115 | // Notifications 116 | private @ColorInt int notificationTextColor; 117 | private @ColorInt int notificationBackgroundColor; 118 | private Drawable notificationBackgroundDrawable; 119 | private Typeface notificationTypeface; 120 | private int notificationActiveMarginLeft, notificationInactiveMarginLeft; 121 | private int notificationActiveMarginTop, notificationInactiveMarginTop; 122 | private long notificationAnimationDuration; 123 | 124 | /** 125 | * Constructors 126 | */ 127 | public AHBottomNavigation(Context context) { 128 | super(context); 129 | init(context, null); 130 | } 131 | 132 | public AHBottomNavigation(Context context, AttributeSet attrs) { 133 | super(context, attrs); 134 | init(context, attrs); 135 | } 136 | 137 | public AHBottomNavigation(Context context, AttributeSet attrs, int defStyleAttr) { 138 | super(context, attrs, defStyleAttr); 139 | init(context, attrs); 140 | } 141 | 142 | @Override 143 | public void setSoundEffectsEnabled(final boolean soundEffectsEnabled) { 144 | super.setSoundEffectsEnabled(soundEffectsEnabled); 145 | this.soundEffectsEnabled = soundEffectsEnabled; 146 | } 147 | 148 | @Override 149 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 150 | super.onSizeChanged(w, h, oldw, oldh); 151 | createItems(); 152 | } 153 | 154 | @Override 155 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 156 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 157 | if (!isBehaviorTranslationSet) { 158 | //The translation behavior has to be set up after the super.onMeasure has been called. 159 | setBehaviorTranslationEnabled(behaviorTranslationEnabled); 160 | isBehaviorTranslationSet = true; 161 | } 162 | } 163 | 164 | @Override 165 | protected Parcelable onSaveInstanceState() { 166 | Bundle bundle = new Bundle(); 167 | bundle.putParcelable("superState", super.onSaveInstanceState()); 168 | bundle.putInt("current_item", currentItem); 169 | bundle.putParcelableArrayList("notifications", new ArrayList<> (notifications)); 170 | return bundle; 171 | } 172 | 173 | @Override 174 | protected void onRestoreInstanceState(Parcelable state) { 175 | if (state instanceof Bundle) { 176 | Bundle bundle = (Bundle) state; 177 | currentItem = bundle.getInt("current_item"); 178 | notifications = bundle.getParcelableArrayList("notifications"); 179 | state = bundle.getParcelable("superState"); 180 | } 181 | super.onRestoreInstanceState(state); 182 | } 183 | 184 | ///////////// 185 | // PRIVATE // 186 | ///////////// 187 | 188 | /** 189 | * Init 190 | * 191 | * @param context 192 | */ 193 | private void init(Context context, AttributeSet attrs) { 194 | this.context = context; 195 | resources = this.context.getResources(); 196 | 197 | // Item colors 198 | titleColorActive = ContextCompat.getColor(context, R.color.colorBottomNavigationAccent); 199 | titleColorInactive = ContextCompat.getColor(context, R.color.colorBottomNavigationInactive); 200 | itemDisableColor = ContextCompat.getColor(context, R.color.colorBottomNavigationDisable); 201 | 202 | // Colors for colored bottom navigation 203 | coloredTitleColorActive = ContextCompat.getColor(context, R.color.colorBottomNavigationActiveColored); 204 | coloredTitleColorInactive = ContextCompat.getColor(context, R.color.colorBottomNavigationInactiveColored); 205 | 206 | if (attrs != null) { 207 | TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.AHBottomNavigationBehavior_Params, 0, 0); 208 | try { 209 | selectedBackgroundVisible = ta.getBoolean(R.styleable.AHBottomNavigationBehavior_Params_selectedBackgroundVisible, false); 210 | translucentNavigationEnabled = ta.getBoolean(R.styleable.AHBottomNavigationBehavior_Params_translucentNavigationEnabled, false); 211 | 212 | titleColorActive = ta.getColor(R.styleable.AHBottomNavigationBehavior_Params_accentColor, 213 | ContextCompat.getColor(context, R.color.colorBottomNavigationAccent)); 214 | titleColorInactive = ta.getColor(R.styleable.AHBottomNavigationBehavior_Params_inactiveColor, 215 | ContextCompat.getColor(context, R.color.colorBottomNavigationInactive)); 216 | itemDisableColor = ta.getColor(R.styleable.AHBottomNavigationBehavior_Params_disableColor, 217 | ContextCompat.getColor(context, R.color.colorBottomNavigationDisable)); 218 | 219 | coloredTitleColorActive = ta.getColor(R.styleable.AHBottomNavigationBehavior_Params_coloredActive, 220 | ContextCompat.getColor(context, R.color.colorBottomNavigationActiveColored)); 221 | coloredTitleColorInactive = ta.getColor(R.styleable.AHBottomNavigationBehavior_Params_coloredInactive, 222 | ContextCompat.getColor(context, R.color.colorBottomNavigationInactiveColored)); 223 | 224 | colored = ta.getBoolean(R.styleable.AHBottomNavigationBehavior_Params_colored, false); 225 | 226 | } finally { 227 | ta.recycle(); 228 | } 229 | } 230 | 231 | notificationTextColor = ContextCompat.getColor(context, android.R.color.white); 232 | bottomNavigationHeight = (int) resources.getDimension(R.dimen.bottom_navigation_height); 233 | 234 | itemActiveColor = titleColorActive; 235 | itemInactiveColor = titleColorInactive; 236 | 237 | // Notifications 238 | notificationActiveMarginLeft = (int) resources.getDimension(R.dimen.bottom_navigation_notification_margin_left_active); 239 | notificationInactiveMarginLeft = (int) resources.getDimension(R.dimen.bottom_navigation_notification_margin_left); 240 | notificationActiveMarginTop = (int) resources.getDimension(R.dimen.bottom_navigation_notification_margin_top_active); 241 | notificationInactiveMarginTop = (int) resources.getDimension(R.dimen.bottom_navigation_notification_margin_top); 242 | notificationAnimationDuration = 150; 243 | 244 | ViewCompat.setElevation(this, resources.getDimension(R.dimen.bottom_navigation_elevation)); 245 | 246 | ViewGroup.LayoutParams params = new ViewGroup.LayoutParams( 247 | ViewGroup.LayoutParams.MATCH_PARENT, bottomNavigationHeight); 248 | setLayoutParams(params); 249 | } 250 | 251 | /** 252 | * Create the items in the bottom navigation 253 | */ 254 | private void createItems() { 255 | if (items.size() < MIN_ITEMS) { 256 | Log.w(TAG, "The items list should have at least 3 items"); 257 | } else if (items.size() > MAX_ITEMS) { 258 | Log.w(TAG, "The items list should not have more than 5 items"); 259 | } 260 | 261 | int layoutHeight = (int) resources.getDimension(R.dimen.bottom_navigation_height); 262 | 263 | removeAllViews(); 264 | 265 | views.clear(); 266 | backgroundColorView = new View(context); 267 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 268 | LayoutParams backgroundLayoutParams = new LayoutParams( 269 | ViewGroup.LayoutParams.MATCH_PARENT, calculateHeight(layoutHeight)); 270 | addView(backgroundColorView, backgroundLayoutParams); 271 | bottomNavigationHeight = layoutHeight; 272 | } 273 | 274 | linearLayoutContainer = new LinearLayout(context); 275 | linearLayoutContainer.setOrientation(LinearLayout.HORIZONTAL); 276 | linearLayoutContainer.setGravity(Gravity.CENTER); 277 | 278 | LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, layoutHeight); 279 | addView(linearLayoutContainer, layoutParams); 280 | 281 | if (isClassic()) { 282 | createClassicItems(linearLayoutContainer); 283 | } else { 284 | createSmallItems(linearLayoutContainer); 285 | } 286 | 287 | // Force a request layout after all the items have been created 288 | post(new Runnable() { 289 | @Override 290 | public void run() { 291 | requestLayout(); 292 | } 293 | }); 294 | } 295 | 296 | @SuppressLint("NewApi") 297 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 298 | private int calculateHeight(int layoutHeight) { 299 | if(!translucentNavigationEnabled) return layoutHeight; 300 | 301 | int resourceId = getResources().getIdentifier("navigation_bar_height", "dimen", "android"); 302 | if (resourceId > 0) { 303 | navigationBarHeight = resources.getDimensionPixelSize(resourceId); 304 | } 305 | 306 | int[] attrs = {android.R.attr.fitsSystemWindows, android.R.attr.windowTranslucentNavigation}; 307 | TypedArray typedValue = getContext().getTheme().obtainStyledAttributes(attrs); 308 | 309 | @SuppressWarnings("ResourceType") 310 | boolean fitWindow = typedValue.getBoolean(0, false); 311 | 312 | @SuppressWarnings("ResourceType") 313 | boolean translucentNavigation = typedValue.getBoolean(1, true); 314 | 315 | if(hasImmersive() /*&& !fitWindow*/ && translucentNavigation) { 316 | layoutHeight += navigationBarHeight; 317 | } 318 | 319 | typedValue.recycle(); 320 | 321 | return layoutHeight; 322 | } 323 | 324 | @SuppressLint("NewApi") 325 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 326 | public boolean hasImmersive() { 327 | Display d = ((WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); 328 | 329 | DisplayMetrics realDisplayMetrics = new DisplayMetrics(); 330 | d.getRealMetrics(realDisplayMetrics); 331 | 332 | int realHeight = realDisplayMetrics.heightPixels; 333 | int realWidth = realDisplayMetrics.widthPixels; 334 | 335 | DisplayMetrics displayMetrics = new DisplayMetrics(); 336 | d.getMetrics(displayMetrics); 337 | 338 | int displayHeight = displayMetrics.heightPixels; 339 | int displayWidth = displayMetrics.widthPixels; 340 | 341 | return (realWidth > displayWidth) || (realHeight > displayHeight); 342 | } 343 | 344 | // updated 345 | 346 | /** 347 | * Check if items must be classic 348 | * 349 | * @return true if classic (icon + title) 350 | */ 351 | private boolean isClassic() { 352 | return titleState != TitleState.ALWAYS_HIDE && 353 | titleState != TitleState.SHOW_WHEN_ACTIVE_FORCE && 354 | (items.size() == MIN_ITEMS || titleState == TitleState.ALWAYS_SHOW); 355 | } 356 | 357 | /** 358 | * Create classic items (only 3 items in the bottom navigation) 359 | * 360 | * @param linearLayout The layout where the items are added 361 | */ 362 | private void createClassicItems(LinearLayout linearLayout) { 363 | 364 | LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 365 | 366 | float height = resources.getDimension(R.dimen.bottom_navigation_height); 367 | float minWidth = resources.getDimension(R.dimen.bottom_navigation_min_width); 368 | float maxWidth = resources.getDimension(R.dimen.bottom_navigation_max_width); 369 | 370 | if (titleState == TitleState.ALWAYS_SHOW && items.size() > MIN_ITEMS) { 371 | minWidth = resources.getDimension(R.dimen.bottom_navigation_small_inactive_min_width); 372 | maxWidth = resources.getDimension(R.dimen.bottom_navigation_small_inactive_max_width); 373 | } 374 | 375 | int layoutWidth = getWidth() - getPaddingLeft() - getPaddingRight(); 376 | if (layoutWidth == 0 || items.size() == 0) { 377 | return; 378 | } 379 | 380 | float itemWidth = layoutWidth / items.size(); 381 | if (itemWidth < minWidth) { 382 | itemWidth = minWidth; 383 | } else if (itemWidth > maxWidth) { 384 | itemWidth = maxWidth; 385 | } 386 | 387 | float activeSize = resources.getDimension(R.dimen.bottom_navigation_text_size_active); 388 | float inactiveSize = resources.getDimension(R.dimen.bottom_navigation_text_size_inactive); 389 | int activePaddingTop = (int) resources.getDimension(R.dimen.bottom_navigation_margin_top_active); 390 | 391 | if (titleActiveTextSize != 0 && titleInactiveTextSize != 0) { 392 | activeSize = titleActiveTextSize; 393 | inactiveSize = titleInactiveTextSize; 394 | } else if (titleState == TitleState.ALWAYS_SHOW && items.size() > MIN_ITEMS) { 395 | activeSize = resources.getDimension(R.dimen.bottom_navigation_text_size_forced_active); 396 | inactiveSize = resources.getDimension(R.dimen.bottom_navigation_text_size_forced_inactive); 397 | } 398 | 399 | Drawable iconDrawable; 400 | for (int i = 0; i < items.size(); i++) { 401 | final boolean current = currentItem == i; 402 | final int itemIndex = i; 403 | AHBottomNavigationItem item = items.get(itemIndex); 404 | 405 | View view = inflater.inflate(R.layout.bottom_navigation_item, this, false); 406 | FrameLayout container = (FrameLayout) view.findViewById(R.id.bottom_navigation_container); 407 | ImageView icon = (ImageView) view.findViewById(R.id.bottom_navigation_item_icon); 408 | TextView title = (TextView) view.findViewById(R.id.bottom_navigation_item_title); 409 | TextView notification = (TextView) view.findViewById(R.id.bottom_navigation_notification); 410 | 411 | icon.setImageDrawable(item.getDrawable(context)); 412 | title.setText(item.getTitle(context)); 413 | 414 | if (titleTypeface != null) { 415 | title.setTypeface(titleTypeface); 416 | } 417 | 418 | if (titleState == TitleState.ALWAYS_SHOW && items.size() > MIN_ITEMS) { 419 | container.setPadding(0, container.getPaddingTop(), 0, container.getPaddingBottom()); 420 | } 421 | 422 | if (current) { 423 | if (selectedBackgroundVisible) { 424 | view.setSelected(true); 425 | } 426 | icon.setSelected(true); 427 | // Update margins (icon & notification) 428 | if (view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) { 429 | ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) icon.getLayoutParams(); 430 | p.setMargins(p.leftMargin, activePaddingTop, p.rightMargin, p.bottomMargin); 431 | 432 | ViewGroup.MarginLayoutParams paramsNotification = (ViewGroup.MarginLayoutParams) 433 | notification.getLayoutParams(); 434 | paramsNotification.setMargins(notificationActiveMarginLeft, paramsNotification.topMargin, 435 | paramsNotification.rightMargin, paramsNotification.bottomMargin); 436 | 437 | view.requestLayout(); 438 | } 439 | } else { 440 | icon.setSelected(false); 441 | ViewGroup.MarginLayoutParams paramsNotification = (ViewGroup.MarginLayoutParams) 442 | notification.getLayoutParams(); 443 | paramsNotification.setMargins(notificationInactiveMarginLeft, paramsNotification.topMargin, 444 | paramsNotification.rightMargin, paramsNotification.bottomMargin); 445 | } 446 | 447 | if (colored) { 448 | if (current) { 449 | setBackgroundColor(item.getColor(context)); 450 | currentColor = item.getColor(context); 451 | } 452 | } else { 453 | if (defaultBackgroundResource != 0) { 454 | setBackgroundResource(defaultBackgroundResource); 455 | } else { 456 | setBackgroundColor(defaultBackgroundColor); 457 | } 458 | } 459 | 460 | title.setTextSize(TypedValue.COMPLEX_UNIT_PX, current ? activeSize : inactiveSize); 461 | 462 | if (itemsEnabledStates[i]) { 463 | view.setOnClickListener(new OnClickListener() { 464 | @Override 465 | public void onClick(View v) { 466 | updateItems(itemIndex, true); 467 | } 468 | }); 469 | iconDrawable = forceTint ? AHHelper.getTintDrawable(items.get(i).getDrawable(context), 470 | current ? itemActiveColor : itemInactiveColor, forceTint) : items.get(i).getDrawable(context); 471 | icon.setImageDrawable(iconDrawable); 472 | title.setTextColor(current ? itemActiveColor : itemInactiveColor); 473 | view.setSoundEffectsEnabled(soundEffectsEnabled); 474 | view.setEnabled(true); 475 | } else { 476 | iconDrawable = forceTint ? AHHelper.getTintDrawable(items.get(i).getDrawable(context), 477 | itemDisableColor, forceTint) : items.get(i).getDrawable(context); 478 | icon.setImageDrawable(iconDrawable); 479 | title.setTextColor(itemDisableColor); 480 | view.setClickable(true); 481 | view.setEnabled(false); 482 | } 483 | 484 | LayoutParams params = new LayoutParams((int) itemWidth, (int) height); 485 | linearLayout.addView(view, params); 486 | views.add(view); 487 | } 488 | 489 | updateNotifications(true, UPDATE_ALL_NOTIFICATIONS); 490 | } 491 | 492 | /** 493 | * Create small items (more than 3 items in the bottom navigation) 494 | * 495 | * @param linearLayout The layout where the items are added 496 | */ 497 | private void createSmallItems(LinearLayout linearLayout) { 498 | 499 | LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 500 | 501 | float height = resources.getDimension(R.dimen.bottom_navigation_height); 502 | float minWidth = resources.getDimension(R.dimen.bottom_navigation_small_inactive_min_width); 503 | float maxWidth = resources.getDimension(R.dimen.bottom_navigation_small_inactive_max_width); 504 | 505 | int layoutWidth = getWidth() - getPaddingLeft() - getPaddingRight(); 506 | if (layoutWidth == 0 || items.size() == 0) { 507 | return; 508 | } 509 | 510 | float itemWidth = layoutWidth / items.size(); 511 | 512 | if (itemWidth < minWidth) { 513 | itemWidth = minWidth; 514 | } else if (itemWidth > maxWidth) { 515 | itemWidth = maxWidth; 516 | } 517 | 518 | int activeMarginTop = (int) resources.getDimension(R.dimen.bottom_navigation_small_margin_top_active); 519 | float difference = resources.getDimension(R.dimen.bottom_navigation_small_selected_width_difference); 520 | 521 | selectedItemWidth = itemWidth + items.size() * difference; 522 | itemWidth -= difference; 523 | notSelectedItemWidth = itemWidth; 524 | 525 | Drawable iconDrawable; 526 | for (int i = 0; i < items.size(); i++) { 527 | 528 | final int itemIndex = i; 529 | AHBottomNavigationItem item = items.get(itemIndex); 530 | 531 | View view = inflater.inflate(R.layout.bottom_navigation_small_item, this, false); 532 | ImageView icon = (ImageView) view.findViewById(R.id.bottom_navigation_small_item_icon); 533 | TextView title = (TextView) view.findViewById(R.id.bottom_navigation_small_item_title); 534 | TextView notification = (TextView) view.findViewById(R.id.bottom_navigation_notification); 535 | icon.setImageDrawable(item.getDrawable(context)); 536 | 537 | if (titleState != TitleState.ALWAYS_HIDE) { 538 | title.setText(item.getTitle(context)); 539 | } 540 | 541 | if (titleActiveTextSize != 0) { 542 | title.setTextSize(TypedValue.COMPLEX_UNIT_PX, titleActiveTextSize); 543 | } 544 | 545 | if (titleTypeface != null) { 546 | title.setTypeface(titleTypeface); 547 | } 548 | 549 | if (i == currentItem) { 550 | if (selectedBackgroundVisible) { 551 | view.setSelected(true); 552 | } 553 | icon.setSelected(true); 554 | // Update margins (icon & notification) 555 | 556 | if (titleState != TitleState.ALWAYS_HIDE) { 557 | if (view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) { 558 | ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) icon.getLayoutParams(); 559 | p.setMargins(p.leftMargin, activeMarginTop, p.rightMargin, p.bottomMargin); 560 | 561 | ViewGroup.MarginLayoutParams paramsNotification = (ViewGroup.MarginLayoutParams) 562 | notification.getLayoutParams(); 563 | paramsNotification.setMargins(notificationActiveMarginLeft, notificationActiveMarginTop, 564 | paramsNotification.rightMargin, paramsNotification.bottomMargin); 565 | 566 | view.requestLayout(); 567 | } 568 | } 569 | } else { 570 | icon.setSelected(false); 571 | ViewGroup.MarginLayoutParams paramsNotification = (ViewGroup.MarginLayoutParams) 572 | notification.getLayoutParams(); 573 | paramsNotification.setMargins(notificationInactiveMarginLeft, notificationInactiveMarginTop, 574 | paramsNotification.rightMargin, paramsNotification.bottomMargin); 575 | } 576 | 577 | if (colored) { 578 | if (i == currentItem) { 579 | setBackgroundColor(item.getColor(context)); 580 | currentColor = item.getColor(context); 581 | } 582 | } else { 583 | if (defaultBackgroundResource != 0) { 584 | setBackgroundResource(defaultBackgroundResource); 585 | } else { 586 | setBackgroundColor(defaultBackgroundColor); 587 | } 588 | } 589 | 590 | if (itemsEnabledStates[i]) { 591 | iconDrawable = forceTint ? AHHelper.getTintDrawable(items.get(i).getDrawable(context), 592 | currentItem == i ? itemActiveColor : itemInactiveColor, forceTint) : items.get(i).getDrawable(context); 593 | icon.setImageDrawable(iconDrawable); 594 | title.setTextColor(currentItem == i ? itemActiveColor : itemInactiveColor); 595 | title.setAlpha(currentItem == i ? 1 : 0); 596 | view.setOnClickListener(new OnClickListener() { 597 | @Override 598 | public void onClick(View v) { 599 | updateSmallItems(itemIndex, true); 600 | } 601 | }); 602 | view.setSoundEffectsEnabled(soundEffectsEnabled); 603 | view.setEnabled(true); 604 | } else { 605 | iconDrawable = forceTint ? AHHelper.getTintDrawable(items.get(i).getDrawable(context), 606 | itemDisableColor, forceTint) : items.get(i).getDrawable(context); 607 | icon.setImageDrawable(iconDrawable); 608 | title.setTextColor(itemDisableColor); 609 | title.setAlpha(0); 610 | view.setClickable(true); 611 | view.setEnabled(false); 612 | } 613 | 614 | int width = i == currentItem ? (int) selectedItemWidth : 615 | (int) itemWidth; 616 | 617 | if (titleState == TitleState.ALWAYS_HIDE) { 618 | width = (int) (itemWidth * 1.16); 619 | } 620 | 621 | LayoutParams params = new LayoutParams(width, (int) height); 622 | linearLayout.addView(view, params); 623 | views.add(view); 624 | } 625 | 626 | updateNotifications(true, UPDATE_ALL_NOTIFICATIONS); 627 | } 628 | 629 | 630 | /** 631 | * Update Items UI 632 | * 633 | * @param itemIndex int: Selected item position 634 | * @param useCallback boolean: Use or not the callback 635 | */ 636 | private void updateItems(final int itemIndex, boolean useCallback) { 637 | 638 | if (currentItem == itemIndex) { 639 | if (tabSelectedListener != null && useCallback) { 640 | tabSelectedListener.onTabSelected(itemIndex, true); 641 | } 642 | return; 643 | } 644 | 645 | if (tabSelectedListener != null && useCallback) { 646 | boolean selectionAllowed = tabSelectedListener.onTabSelected(itemIndex, false); 647 | if (!selectionAllowed) return; 648 | } 649 | 650 | int activeMarginTop = (int) resources.getDimension(R.dimen.bottom_navigation_margin_top_active); 651 | int inactiveMarginTop = (int) resources.getDimension(R.dimen.bottom_navigation_margin_top_inactive); 652 | float activeSize = resources.getDimension(R.dimen.bottom_navigation_text_size_active); 653 | float inactiveSize = resources.getDimension(R.dimen.bottom_navigation_text_size_inactive); 654 | 655 | if (titleActiveTextSize != 0 && titleInactiveTextSize != 0) { 656 | activeSize = titleActiveTextSize; 657 | inactiveSize = titleInactiveTextSize; 658 | } else if (titleState == TitleState.ALWAYS_SHOW && items.size() > MIN_ITEMS) { 659 | activeSize = resources.getDimension(R.dimen.bottom_navigation_text_size_forced_active); 660 | inactiveSize = resources.getDimension(R.dimen.bottom_navigation_text_size_forced_inactive); 661 | } 662 | 663 | for (int i = 0; i < views.size(); i++) { 664 | 665 | final View view = views.get(i); 666 | if (selectedBackgroundVisible) { 667 | view.setSelected(i == itemIndex); 668 | } 669 | 670 | if (i == itemIndex) { 671 | 672 | final TextView title = (TextView) view.findViewById(R.id.bottom_navigation_item_title); 673 | final ImageView icon = (ImageView) view.findViewById(R.id.bottom_navigation_item_icon); 674 | final TextView notification = (TextView) view.findViewById(R.id.bottom_navigation_notification); 675 | 676 | icon.setSelected(true); 677 | AHHelper.updateTopMargin(icon, inactiveMarginTop, activeMarginTop); 678 | AHHelper.updateLeftMargin(notification, notificationInactiveMarginLeft, notificationActiveMarginLeft); 679 | AHHelper.updateTextColor(title, itemInactiveColor, itemActiveColor); 680 | AHHelper.updateTextSize(title, inactiveSize, activeSize); 681 | if (forceTint) { 682 | AHHelper.updateDrawableColor(context, items.get(itemIndex).getDrawable(context), icon, 683 | itemInactiveColor, itemActiveColor, forceTint); 684 | } 685 | 686 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && colored) { 687 | 688 | int finalRadius = Math.max(getWidth(), getHeight()); 689 | int cx = (int) view.getX() + view.getWidth() / 2; 690 | int cy = view.getHeight() / 2; 691 | 692 | if (circleRevealAnim != null && circleRevealAnim.isRunning()) { 693 | circleRevealAnim.cancel(); 694 | setBackgroundColor(items.get(itemIndex).getColor(context)); 695 | backgroundColorView.setBackgroundColor(Color.TRANSPARENT); 696 | } 697 | 698 | circleRevealAnim = ViewAnimationUtils.createCircularReveal(backgroundColorView, cx, cy, 0, finalRadius); 699 | circleRevealAnim.setStartDelay(5); 700 | circleRevealAnim.addListener(new Animator.AnimatorListener() { 701 | @Override 702 | public void onAnimationStart(Animator animation) { 703 | backgroundColorView.setBackgroundColor(items.get(itemIndex).getColor(context)); 704 | } 705 | 706 | @Override 707 | public void onAnimationEnd(Animator animation) { 708 | setBackgroundColor(items.get(itemIndex).getColor(context)); 709 | backgroundColorView.setBackgroundColor(Color.TRANSPARENT); 710 | } 711 | 712 | @Override 713 | public void onAnimationCancel(Animator animation) { 714 | } 715 | 716 | @Override 717 | public void onAnimationRepeat(Animator animation) { 718 | } 719 | }); 720 | circleRevealAnim.start(); 721 | } else if (colored) { 722 | AHHelper.updateViewBackgroundColor(this, currentColor, 723 | items.get(itemIndex).getColor(context)); 724 | } else { 725 | if (defaultBackgroundResource != 0) { 726 | setBackgroundResource(defaultBackgroundResource); 727 | } else { 728 | setBackgroundColor(defaultBackgroundColor); 729 | } 730 | backgroundColorView.setBackgroundColor(Color.TRANSPARENT); 731 | } 732 | 733 | } else if (i == currentItem) { 734 | 735 | final TextView title = (TextView) view.findViewById(R.id.bottom_navigation_item_title); 736 | final ImageView icon = (ImageView) view.findViewById(R.id.bottom_navigation_item_icon); 737 | final TextView notification = (TextView) view.findViewById(R.id.bottom_navigation_notification); 738 | 739 | icon.setSelected(false); 740 | AHHelper.updateTopMargin(icon, activeMarginTop, inactiveMarginTop); 741 | AHHelper.updateLeftMargin(notification, notificationActiveMarginLeft, notificationInactiveMarginLeft); 742 | AHHelper.updateTextColor(title, itemActiveColor, itemInactiveColor); 743 | AHHelper.updateTextSize(title, activeSize, inactiveSize); 744 | if (forceTint) { 745 | AHHelper.updateDrawableColor(context, items.get(currentItem).getDrawable(context), icon, 746 | itemActiveColor, itemInactiveColor, forceTint); 747 | } 748 | } 749 | } 750 | 751 | currentItem = itemIndex; 752 | if (currentItem > 0 && currentItem < items.size()) { 753 | currentColor = items.get(currentItem).getColor(context); 754 | } else if (currentItem == CURRENT_ITEM_NONE) { 755 | if (defaultBackgroundResource != 0) { 756 | setBackgroundResource(defaultBackgroundResource); 757 | } else { 758 | setBackgroundColor(defaultBackgroundColor); 759 | } 760 | backgroundColorView.setBackgroundColor(Color.TRANSPARENT); 761 | } 762 | } 763 | 764 | /** 765 | * Update Small items UI 766 | * 767 | * @param itemIndex int: Selected item position 768 | * @param useCallback boolean: Use or not the callback 769 | */ 770 | private void updateSmallItems(final int itemIndex, boolean useCallback) { 771 | 772 | if (currentItem == itemIndex) { 773 | if (tabSelectedListener != null && useCallback) { 774 | tabSelectedListener.onTabSelected(itemIndex, true); 775 | } 776 | return; 777 | } 778 | 779 | if (tabSelectedListener != null && useCallback) { 780 | boolean selectionAllowed = tabSelectedListener.onTabSelected(itemIndex, false); 781 | if (!selectionAllowed) return; 782 | } 783 | 784 | int activeMarginTop = (int) resources.getDimension(R.dimen.bottom_navigation_small_margin_top_active); 785 | int inactiveMargin = (int) resources.getDimension(R.dimen.bottom_navigation_small_margin_top); 786 | 787 | for (int i = 0; i < views.size(); i++) { 788 | 789 | final View view = views.get(i); 790 | if (selectedBackgroundVisible) { 791 | view.setSelected(i == itemIndex); 792 | } 793 | 794 | if (i == itemIndex) { 795 | 796 | final FrameLayout container = (FrameLayout) view.findViewById(R.id.bottom_navigation_small_container); 797 | final TextView title = (TextView) view.findViewById(R.id.bottom_navigation_small_item_title); 798 | final ImageView icon = (ImageView) view.findViewById(R.id.bottom_navigation_small_item_icon); 799 | final TextView notification = (TextView) view.findViewById(R.id.bottom_navigation_notification); 800 | 801 | icon.setSelected(true); 802 | 803 | if (titleState != TitleState.ALWAYS_HIDE) { 804 | AHHelper.updateTopMargin(icon, inactiveMargin, activeMarginTop); 805 | AHHelper.updateLeftMargin(notification, notificationInactiveMarginLeft, notificationActiveMarginLeft); 806 | AHHelper.updateTopMargin(notification, notificationInactiveMarginTop, notificationActiveMarginTop); 807 | AHHelper.updateTextColor(title, itemInactiveColor, itemActiveColor); 808 | AHHelper.updateWidth(container, notSelectedItemWidth, selectedItemWidth); 809 | } 810 | 811 | AHHelper.updateAlpha(title, 0, 1); 812 | if (forceTint) { 813 | AHHelper.updateDrawableColor(context, items.get(itemIndex).getDrawable(context), icon, 814 | itemInactiveColor, itemActiveColor, forceTint); 815 | } 816 | 817 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && colored) { 818 | int finalRadius = Math.max(getWidth(), getHeight()); 819 | int cx = (int) views.get(itemIndex).getX() + views.get(itemIndex).getWidth() / 2; 820 | int cy = views.get(itemIndex).getHeight() / 2; 821 | 822 | if (circleRevealAnim != null && circleRevealAnim.isRunning()) { 823 | circleRevealAnim.cancel(); 824 | setBackgroundColor(items.get(itemIndex).getColor(context)); 825 | backgroundColorView.setBackgroundColor(Color.TRANSPARENT); 826 | } 827 | 828 | circleRevealAnim = ViewAnimationUtils.createCircularReveal(backgroundColorView, cx, cy, 0, finalRadius); 829 | circleRevealAnim.setStartDelay(5); 830 | circleRevealAnim.addListener(new Animator.AnimatorListener() { 831 | @Override 832 | public void onAnimationStart(Animator animation) { 833 | backgroundColorView.setBackgroundColor(items.get(itemIndex).getColor(context)); 834 | } 835 | 836 | @Override 837 | public void onAnimationEnd(Animator animation) { 838 | setBackgroundColor(items.get(itemIndex).getColor(context)); 839 | backgroundColorView.setBackgroundColor(Color.TRANSPARENT); 840 | } 841 | 842 | @Override 843 | public void onAnimationCancel(Animator animation) { 844 | } 845 | 846 | @Override 847 | public void onAnimationRepeat(Animator animation) { 848 | } 849 | }); 850 | circleRevealAnim.start(); 851 | } else if (colored) { 852 | AHHelper.updateViewBackgroundColor(this, currentColor, 853 | items.get(itemIndex).getColor(context)); 854 | } else { 855 | if (defaultBackgroundResource != 0) { 856 | setBackgroundResource(defaultBackgroundResource); 857 | } else { 858 | setBackgroundColor(defaultBackgroundColor); 859 | } 860 | backgroundColorView.setBackgroundColor(Color.TRANSPARENT); 861 | } 862 | 863 | } else if (i == currentItem) { 864 | 865 | final View container = view.findViewById(R.id.bottom_navigation_small_container); 866 | final TextView title = (TextView) view.findViewById(R.id.bottom_navigation_small_item_title); 867 | final ImageView icon = (ImageView) view.findViewById(R.id.bottom_navigation_small_item_icon); 868 | final TextView notification = (TextView) view.findViewById(R.id.bottom_navigation_notification); 869 | 870 | icon.setSelected(false); 871 | 872 | if (titleState != TitleState.ALWAYS_HIDE) { 873 | AHHelper.updateTopMargin(icon, activeMarginTop, inactiveMargin); 874 | AHHelper.updateLeftMargin(notification, notificationActiveMarginLeft, notificationInactiveMarginLeft); 875 | AHHelper.updateTopMargin(notification, notificationActiveMarginTop, notificationInactiveMarginTop); 876 | AHHelper.updateTextColor(title, itemActiveColor, itemInactiveColor); 877 | AHHelper.updateWidth(container, selectedItemWidth, notSelectedItemWidth); 878 | } 879 | 880 | AHHelper.updateAlpha(title, 1, 0); 881 | if (forceTint) { 882 | AHHelper.updateDrawableColor(context, items.get(currentItem).getDrawable(context), icon, 883 | itemActiveColor, itemInactiveColor, forceTint); 884 | } 885 | } 886 | } 887 | 888 | currentItem = itemIndex; 889 | if (currentItem > 0 && currentItem < items.size()) { 890 | currentColor = items.get(currentItem).getColor(context); 891 | } else if (currentItem == CURRENT_ITEM_NONE) { 892 | if (defaultBackgroundResource != 0) { 893 | setBackgroundResource(defaultBackgroundResource); 894 | } else { 895 | setBackgroundColor(defaultBackgroundColor); 896 | } 897 | backgroundColorView.setBackgroundColor(Color.TRANSPARENT); 898 | } 899 | } 900 | 901 | /** 902 | * Update notifications 903 | */ 904 | private void updateNotifications(boolean updateStyle, int itemPosition) { 905 | 906 | for (int i = 0; i < views.size(); i++) { 907 | 908 | if (i >= notifications.size()) { 909 | break; 910 | } 911 | 912 | if (itemPosition != UPDATE_ALL_NOTIFICATIONS && itemPosition != i) { 913 | continue; 914 | } 915 | 916 | final AHNotification notificationItem = notifications.get(i); 917 | final int currentTextColor = AHNotificationHelper.getTextColor(notificationItem, notificationTextColor); 918 | final int currentBackgroundColor = AHNotificationHelper.getBackgroundColor(notificationItem, notificationBackgroundColor); 919 | 920 | final TextView notification = (TextView) views.get(i).findViewById(R.id.bottom_navigation_notification); 921 | 922 | String currentValue = notification.getText().toString(); 923 | boolean animate = !currentValue.equals(String.valueOf(notificationItem.getText())); 924 | 925 | if (updateStyle) { 926 | notification.setTextColor(currentTextColor); 927 | if (notificationTypeface != null) { 928 | notification.setTypeface(notificationTypeface); 929 | } else { 930 | notification.setTypeface(null, Typeface.BOLD); 931 | } 932 | 933 | if (notificationBackgroundDrawable != null) { 934 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 935 | Drawable drawable = notificationBackgroundDrawable.getConstantState().newDrawable(); 936 | notification.setBackground(drawable); 937 | } else { 938 | notification.setBackgroundDrawable(notificationBackgroundDrawable); 939 | } 940 | 941 | } else if (currentBackgroundColor != 0) { 942 | Drawable defautlDrawable = ContextCompat.getDrawable(context, R.drawable.notification_background); 943 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 944 | notification.setBackground(AHHelper.getTintDrawable(defautlDrawable, 945 | currentBackgroundColor, forceTint)); 946 | } else { 947 | notification.setBackgroundDrawable(AHHelper.getTintDrawable(defautlDrawable, 948 | currentBackgroundColor, forceTint)); 949 | } 950 | } 951 | } 952 | 953 | if (notificationItem.isEmpty() && notification.getText().length() > 0) { 954 | notification.setText(""); 955 | if (animate) { 956 | notification.animate() 957 | .scaleX(0) 958 | .scaleY(0) 959 | .alpha(0) 960 | .setInterpolator(new AccelerateInterpolator()) 961 | .setDuration(notificationAnimationDuration) 962 | .start(); 963 | } 964 | } else if (!notificationItem.isEmpty()) { 965 | notification.setText(String.valueOf(notificationItem.getText())); 966 | if (animate) { 967 | notification.setScaleX(0); 968 | notification.setScaleY(0); 969 | notification.animate() 970 | .scaleX(1) 971 | .scaleY(1) 972 | .alpha(1) 973 | .setInterpolator(new OvershootInterpolator()) 974 | .setDuration(notificationAnimationDuration) 975 | .start(); 976 | } 977 | } 978 | } 979 | } 980 | 981 | 982 | //////////// 983 | // PUBLIC // 984 | //////////// 985 | 986 | /** 987 | * Add an item at the given index 988 | */ 989 | public void addItemAtIndex(int index, AHBottomNavigationItem item) { 990 | if (this.items.size() > MAX_ITEMS) { 991 | Log.w(TAG, "The items list should not have more than 5 items"); 992 | } 993 | if (index < items.size()) { 994 | this.items.add(index, item); 995 | } else { 996 | Log.w(TAG, "The index is out of bounds (index: " + index + ", size: " + this.items.size() + ")"); 997 | } 998 | createItems(); 999 | } 1000 | 1001 | /** 1002 | * Add an item 1003 | */ 1004 | public void addItem(AHBottomNavigationItem item) { 1005 | if (this.items.size() > MAX_ITEMS) { 1006 | Log.w(TAG, "The items list should not have more than 5 items"); 1007 | } 1008 | items.add(item); 1009 | createItems(); 1010 | } 1011 | 1012 | /** 1013 | * Add all items 1014 | */ 1015 | public void addItems(List items) { 1016 | if (items.size() > MAX_ITEMS || (this.items.size() + items.size()) > MAX_ITEMS) { 1017 | Log.w(TAG, "The items list should not have more than 5 items"); 1018 | } 1019 | this.items.addAll(items); 1020 | createItems(); 1021 | } 1022 | 1023 | /** 1024 | * Remove an item at the given index 1025 | */ 1026 | public void removeItemAtIndex(int index) { 1027 | if (index < items.size()) { 1028 | this.items.remove(index); 1029 | createItems(); 1030 | } 1031 | } 1032 | 1033 | /** 1034 | * Remove all items 1035 | */ 1036 | public void removeAllItems() { 1037 | this.items.clear(); 1038 | createItems(); 1039 | } 1040 | 1041 | /** 1042 | * Refresh the AHBottomView 1043 | */ 1044 | public void refresh() { 1045 | createItems(); 1046 | } 1047 | 1048 | /** 1049 | * Return the number of items 1050 | * 1051 | * @return int 1052 | */ 1053 | public int getItemsCount() { 1054 | return items.size(); 1055 | } 1056 | 1057 | /** 1058 | * Return if the Bottom Navigation is colored 1059 | */ 1060 | public boolean isColored() { 1061 | return colored; 1062 | } 1063 | 1064 | /** 1065 | * Set if the Bottom Navigation is colored 1066 | */ 1067 | public void setColored(boolean colored) { 1068 | this.colored = colored; 1069 | this.itemActiveColor = colored ? coloredTitleColorActive : titleColorActive; 1070 | this.itemInactiveColor = colored ? coloredTitleColorInactive : titleColorInactive; 1071 | createItems(); 1072 | } 1073 | 1074 | /** 1075 | * Return the bottom navigation background color 1076 | * 1077 | * @return The bottom navigation background color 1078 | */ 1079 | public int getDefaultBackgroundColor() { 1080 | return defaultBackgroundColor; 1081 | } 1082 | 1083 | /** 1084 | * Set the bottom navigation background color 1085 | * 1086 | * @param defaultBackgroundColor The bottom navigation background color 1087 | */ 1088 | public void setDefaultBackgroundColor(@ColorInt int defaultBackgroundColor) { 1089 | this.defaultBackgroundColor = defaultBackgroundColor; 1090 | createItems(); 1091 | } 1092 | 1093 | /** 1094 | * Set the bottom navigation background resource 1095 | * 1096 | * @param defaultBackgroundResource The bottom navigation background resource 1097 | */ 1098 | public void setDefaultBackgroundResource(@DrawableRes int defaultBackgroundResource) { 1099 | this.defaultBackgroundResource = defaultBackgroundResource; 1100 | createItems(); 1101 | } 1102 | 1103 | /** 1104 | * Get the accent color (used when the view contains 3 items) 1105 | * 1106 | * @return The default accent color 1107 | */ 1108 | public int getAccentColor() { 1109 | return itemActiveColor; 1110 | } 1111 | 1112 | /** 1113 | * Set the accent color (used when the view contains 3 items) 1114 | * 1115 | * @param accentColor The new accent color 1116 | */ 1117 | public void setAccentColor(int accentColor) { 1118 | this.titleColorActive = accentColor; 1119 | this.itemActiveColor = accentColor; 1120 | createItems(); 1121 | } 1122 | 1123 | /** 1124 | * Get the inactive color (used when the view contains 3 items) 1125 | * 1126 | * @return The inactive color 1127 | */ 1128 | public int getInactiveColor() { 1129 | return itemInactiveColor; 1130 | } 1131 | 1132 | /** 1133 | * Set the inactive color (used when the view contains 3 items) 1134 | * 1135 | * @param inactiveColor The inactive color 1136 | */ 1137 | public void setInactiveColor(int inactiveColor) { 1138 | this.titleColorInactive = inactiveColor; 1139 | this.itemInactiveColor = inactiveColor; 1140 | createItems(); 1141 | } 1142 | 1143 | /** 1144 | * Set the colors used when the bottom bar uses the colored mode 1145 | * 1146 | * @param colorActive The active color 1147 | * @param colorInactive The inactive color 1148 | */ 1149 | public void setColoredModeColors(@ColorInt int colorActive, @ColorInt int colorInactive) { 1150 | this.coloredTitleColorActive = colorActive; 1151 | this.coloredTitleColorInactive = colorInactive; 1152 | createItems(); 1153 | } 1154 | 1155 | /** 1156 | * Set selected background visibility 1157 | */ 1158 | public void setSelectedBackgroundVisible(boolean visible) { 1159 | this.selectedBackgroundVisible = visible; 1160 | createItems(); 1161 | } 1162 | 1163 | /** 1164 | * Set notification typeface 1165 | * 1166 | * @param typeface Typeface 1167 | */ 1168 | public void setTitleTypeface(Typeface typeface) { 1169 | this.titleTypeface = typeface; 1170 | createItems(); 1171 | } 1172 | 1173 | /** 1174 | * Set title text size in pixels 1175 | * 1176 | * @param activeSize 1177 | * @param inactiveSize 1178 | */ 1179 | public void setTitleTextSize(float activeSize, float inactiveSize) { 1180 | this.titleActiveTextSize = activeSize; 1181 | this.titleInactiveTextSize = inactiveSize; 1182 | createItems(); 1183 | } 1184 | 1185 | /** 1186 | * Set title text size in SP 1187 | * 1188 | + * @param activeSize in sp 1189 | + * @param inactiveSize in sp 1190 | */ 1191 | public void setTitleTextSizeInSp(float activeSize, float inactiveSize) { 1192 | this.titleActiveTextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, activeSize, resources.getDisplayMetrics()); 1193 | this.titleInactiveTextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, inactiveSize, resources.getDisplayMetrics()); 1194 | createItems(); 1195 | } 1196 | 1197 | /** 1198 | * Get item at the given index 1199 | * 1200 | * @param position int: item position 1201 | * @return The item at the given position 1202 | */ 1203 | public AHBottomNavigationItem getItem(int position) { 1204 | if (position < 0 || position > items.size() - 1) { 1205 | Log.w(TAG, "The position is out of bounds of the items (" + items.size() + " elements)"); 1206 | return null; 1207 | } 1208 | return items.get(position); 1209 | } 1210 | 1211 | /** 1212 | * Get the current item 1213 | * 1214 | * @return The current item position 1215 | */ 1216 | public int getCurrentItem() { 1217 | return currentItem; 1218 | } 1219 | 1220 | /** 1221 | * Set the current item 1222 | * 1223 | * @param position int: position 1224 | */ 1225 | public void setCurrentItem(int position) { 1226 | setCurrentItem(position, true); 1227 | } 1228 | 1229 | /** 1230 | * Set the current item 1231 | * 1232 | * @param position int: item position 1233 | * @param useCallback boolean: use or not the callback 1234 | */ 1235 | public void setCurrentItem(int position, boolean useCallback) { 1236 | if (position >= items.size()) { 1237 | Log.w(TAG, "The position is out of bounds of the items (" + items.size() + " elements)"); 1238 | return; 1239 | } 1240 | 1241 | if (titleState != TitleState.ALWAYS_HIDE && 1242 | titleState != TitleState.SHOW_WHEN_ACTIVE_FORCE && 1243 | (items.size() == MIN_ITEMS || titleState == TitleState.ALWAYS_SHOW)) { 1244 | updateItems(position, useCallback); 1245 | } else { 1246 | updateSmallItems(position, useCallback); 1247 | } 1248 | } 1249 | 1250 | /** 1251 | * Return if the behavior translation is enabled 1252 | * 1253 | * @return a boolean value 1254 | */ 1255 | public boolean isBehaviorTranslationEnabled() { 1256 | return behaviorTranslationEnabled; 1257 | } 1258 | 1259 | /** 1260 | * Set the behavior translation value 1261 | * 1262 | * @param behaviorTranslationEnabled boolean for the state 1263 | */ 1264 | public void setBehaviorTranslationEnabled(boolean behaviorTranslationEnabled) { 1265 | this.behaviorTranslationEnabled = behaviorTranslationEnabled; 1266 | if (getParent() instanceof CoordinatorLayout) { 1267 | ViewGroup.LayoutParams params = getLayoutParams(); 1268 | if (bottomNavigationBehavior == null) { 1269 | bottomNavigationBehavior = new AHBottomNavigationBehavior<>(behaviorTranslationEnabled, navigationBarHeight); 1270 | } else { 1271 | bottomNavigationBehavior.setBehaviorTranslationEnabled(behaviorTranslationEnabled, navigationBarHeight); 1272 | } 1273 | if (navigationPositionListener != null) { 1274 | bottomNavigationBehavior.setOnNavigationPositionListener(navigationPositionListener); 1275 | } 1276 | ((CoordinatorLayout.LayoutParams) params).setBehavior(bottomNavigationBehavior); 1277 | if (needHideBottomNavigation) { 1278 | needHideBottomNavigation = false; 1279 | bottomNavigationBehavior.hideView(this, bottomNavigationHeight, hideBottomNavigationWithAnimation); 1280 | } 1281 | } 1282 | } 1283 | 1284 | /** 1285 | * Manage the floating action button behavior with AHBottomNavigation 1286 | * @param fab Floating Action Button 1287 | */ 1288 | public void manageFloatingActionButtonBehavior(FloatingActionButton fab) { 1289 | if (fab.getParent() instanceof CoordinatorLayout) { 1290 | AHBottomNavigationFABBehavior fabBehavior = new AHBottomNavigationFABBehavior(navigationBarHeight); 1291 | ((CoordinatorLayout.LayoutParams) fab.getLayoutParams()) 1292 | .setBehavior(fabBehavior); 1293 | } 1294 | } 1295 | 1296 | /** 1297 | * Hide Bottom Navigation with animation 1298 | */ 1299 | public void hideBottomNavigation() { 1300 | hideBottomNavigation(true); 1301 | } 1302 | 1303 | /** 1304 | * Hide Bottom Navigation with or without animation 1305 | * 1306 | * @param withAnimation Boolean 1307 | */ 1308 | public void hideBottomNavigation(boolean withAnimation) { 1309 | if (bottomNavigationBehavior != null) { 1310 | bottomNavigationBehavior.hideView(this, bottomNavigationHeight, withAnimation); 1311 | } else if (getParent() instanceof CoordinatorLayout) { 1312 | needHideBottomNavigation = true; 1313 | hideBottomNavigationWithAnimation = withAnimation; 1314 | } else { 1315 | // Hide bottom navigation 1316 | ViewCompat.animate(this) 1317 | .translationY(bottomNavigationHeight) 1318 | .setInterpolator(new LinearOutSlowInInterpolator()) 1319 | .setDuration(withAnimation ? 300 : 0) 1320 | .start(); 1321 | } 1322 | } 1323 | 1324 | /** 1325 | * Restore Bottom Navigation with animation 1326 | */ 1327 | public void restoreBottomNavigation() { 1328 | restoreBottomNavigation(true); 1329 | } 1330 | 1331 | /** 1332 | * Restore Bottom Navigation with or without animation 1333 | * 1334 | * @param withAnimation Boolean 1335 | */ 1336 | public void restoreBottomNavigation(boolean withAnimation) { 1337 | if (bottomNavigationBehavior != null) { 1338 | bottomNavigationBehavior.resetOffset(this, withAnimation); 1339 | } else { 1340 | // Show bottom navigation 1341 | ViewCompat.animate(this) 1342 | .translationY(0) 1343 | .setInterpolator(new LinearOutSlowInInterpolator()) 1344 | .setDuration(withAnimation ? 300 : 0) 1345 | .start(); 1346 | } 1347 | } 1348 | 1349 | /** 1350 | * Return if the translucent navigation is enabled 1351 | */ 1352 | public boolean isTranslucentNavigationEnabled() { 1353 | return translucentNavigationEnabled; 1354 | } 1355 | 1356 | /** 1357 | * Set the translucent navigation value 1358 | */ 1359 | public void setTranslucentNavigationEnabled(boolean translucentNavigationEnabled) { 1360 | this.translucentNavigationEnabled = translucentNavigationEnabled; 1361 | } 1362 | 1363 | /** 1364 | * Return if the tint should be forced (with setColorFilter) 1365 | * 1366 | * @return Boolean 1367 | */ 1368 | public boolean isForceTint() { 1369 | return forceTint; 1370 | } 1371 | 1372 | /** 1373 | * Set the force tint value 1374 | * If forceTint = true, the tint is made with drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN); 1375 | * 1376 | * @param forceTint Boolean 1377 | */ 1378 | public void setForceTint(boolean forceTint) { 1379 | this.forceTint = forceTint; 1380 | createItems(); 1381 | } 1382 | 1383 | /** 1384 | * Return the title state for display 1385 | * 1386 | * @return TitleState 1387 | */ 1388 | public TitleState getTitleState() { 1389 | return titleState; 1390 | } 1391 | 1392 | /** 1393 | * Sets the title state for each tab 1394 | * SHOW_WHEN_ACTIVE: when a tab is focused 1395 | * ALWAYS_SHOW: show regardless of which tab is in focus 1396 | * ALWAYS_HIDE: never show tab titles 1397 | * Note: Always showing the title is against Material Design guidelines 1398 | * 1399 | * @param titleState TitleState 1400 | */ 1401 | public void setTitleState(TitleState titleState) { 1402 | this.titleState = titleState; 1403 | createItems(); 1404 | } 1405 | 1406 | /** 1407 | * Set AHOnTabSelectedListener 1408 | */ 1409 | public void setOnTabSelectedListener(OnTabSelectedListener tabSelectedListener) { 1410 | this.tabSelectedListener = tabSelectedListener; 1411 | } 1412 | 1413 | /** 1414 | * Remove AHOnTabSelectedListener 1415 | */ 1416 | public void removeOnTabSelectedListener() { 1417 | this.tabSelectedListener = null; 1418 | } 1419 | 1420 | /** 1421 | * Set OnNavigationPositionListener 1422 | */ 1423 | public void setOnNavigationPositionListener(OnNavigationPositionListener navigationPositionListener) { 1424 | this.navigationPositionListener = navigationPositionListener; 1425 | if (bottomNavigationBehavior != null) { 1426 | bottomNavigationBehavior.setOnNavigationPositionListener(navigationPositionListener); 1427 | } 1428 | } 1429 | 1430 | /** 1431 | * Remove OnNavigationPositionListener() 1432 | */ 1433 | public void removeOnNavigationPositionListener() { 1434 | this.navigationPositionListener = null; 1435 | if (bottomNavigationBehavior != null) { 1436 | bottomNavigationBehavior.removeOnNavigationPositionListener(); 1437 | } 1438 | } 1439 | 1440 | /** 1441 | * Set the notification number 1442 | * 1443 | * @param nbNotification int 1444 | * @param itemPosition int 1445 | */ 1446 | @Deprecated 1447 | public void setNotification(int nbNotification, int itemPosition) { 1448 | if (itemPosition < 0 || itemPosition > items.size() - 1) { 1449 | throw new IndexOutOfBoundsException(String.format(Locale.US, EXCEPTION_INDEX_OUT_OF_BOUNDS, itemPosition, items.size())); 1450 | } 1451 | final String title = nbNotification == 0 ? "" : String.valueOf(nbNotification); 1452 | notifications.set(itemPosition, AHNotification.justText(title)); 1453 | updateNotifications(false, itemPosition); 1454 | } 1455 | 1456 | /** 1457 | * Set notification text 1458 | * 1459 | * @param title String 1460 | * @param itemPosition int 1461 | */ 1462 | public void setNotification(String title, int itemPosition) { 1463 | if (itemPosition < 0 || itemPosition > items.size() - 1) { 1464 | throw new IndexOutOfBoundsException(String.format(Locale.US, EXCEPTION_INDEX_OUT_OF_BOUNDS, itemPosition, items.size())); 1465 | } 1466 | notifications.set(itemPosition, AHNotification.justText(title)); 1467 | updateNotifications(false, itemPosition); 1468 | } 1469 | 1470 | /** 1471 | * Set fully customized Notification 1472 | * 1473 | * @param notification AHNotification 1474 | * @param itemPosition int 1475 | */ 1476 | public void setNotification(AHNotification notification, int itemPosition) { 1477 | if (itemPosition < 0 || itemPosition > items.size() - 1) { 1478 | throw new IndexOutOfBoundsException(String.format(Locale.US, EXCEPTION_INDEX_OUT_OF_BOUNDS, itemPosition, items.size())); 1479 | } 1480 | if (notification == null) { 1481 | notification = new AHNotification(); // instead of null, use empty notification 1482 | } 1483 | notifications.set(itemPosition, notification); 1484 | updateNotifications(true, itemPosition); 1485 | } 1486 | 1487 | /** 1488 | * Set notification text color 1489 | * 1490 | * @param textColor int 1491 | */ 1492 | public void setNotificationTextColor(@ColorInt int textColor) { 1493 | this.notificationTextColor = textColor; 1494 | updateNotifications(true, UPDATE_ALL_NOTIFICATIONS); 1495 | } 1496 | 1497 | /** 1498 | * Set notification text color 1499 | * 1500 | * @param textColor int 1501 | */ 1502 | public void setNotificationTextColorResource(@ColorRes int textColor) { 1503 | this.notificationTextColor = ContextCompat.getColor(context, textColor); 1504 | updateNotifications(true, UPDATE_ALL_NOTIFICATIONS); 1505 | } 1506 | 1507 | /** 1508 | * Set notification background resource 1509 | * 1510 | * @param drawable Drawable 1511 | */ 1512 | public void setNotificationBackground(Drawable drawable) { 1513 | this.notificationBackgroundDrawable = drawable; 1514 | updateNotifications(true, UPDATE_ALL_NOTIFICATIONS); 1515 | } 1516 | 1517 | /** 1518 | * Set notification background color 1519 | * 1520 | * @param color int 1521 | */ 1522 | public void setNotificationBackgroundColor(@ColorInt int color) { 1523 | this.notificationBackgroundColor = color; 1524 | updateNotifications(true, UPDATE_ALL_NOTIFICATIONS); 1525 | } 1526 | 1527 | /** 1528 | * Set notification background color 1529 | * 1530 | * @param color int 1531 | */ 1532 | public void setNotificationBackgroundColorResource(@ColorRes int color) { 1533 | this.notificationBackgroundColor = ContextCompat.getColor(context, color); 1534 | updateNotifications(true, UPDATE_ALL_NOTIFICATIONS); 1535 | } 1536 | 1537 | /** 1538 | * Set notification typeface 1539 | * 1540 | * @param typeface Typeface 1541 | */ 1542 | public void setNotificationTypeface(Typeface typeface) { 1543 | this.notificationTypeface = typeface; 1544 | updateNotifications(true, UPDATE_ALL_NOTIFICATIONS); 1545 | } 1546 | 1547 | public void setNotificationAnimationDuration(long notificationAnimationDuration){ 1548 | this.notificationAnimationDuration = notificationAnimationDuration; 1549 | updateNotifications(true, UPDATE_ALL_NOTIFICATIONS); 1550 | } 1551 | 1552 | /** 1553 | * Set the notification margin left 1554 | * 1555 | * @param activeMargin 1556 | * @param inactiveMargin 1557 | */ 1558 | public void setNotificationMarginLeft(int activeMargin, int inactiveMargin) { 1559 | this.notificationActiveMarginLeft = activeMargin; 1560 | this.notificationInactiveMarginLeft = inactiveMargin; 1561 | createItems(); 1562 | } 1563 | 1564 | /** 1565 | * Activate or not the elevation 1566 | * 1567 | * @param useElevation boolean 1568 | */ 1569 | public void setUseElevation(boolean useElevation) { 1570 | ViewCompat.setElevation(this, useElevation ? 1571 | resources.getDimension(R.dimen.bottom_navigation_elevation) : 0); 1572 | setClipToPadding(false); 1573 | } 1574 | 1575 | /** 1576 | * Activate or not the elevation, and set the value 1577 | * 1578 | * @param useElevation boolean 1579 | * @param elevation float 1580 | */ 1581 | public void setUseElevation(boolean useElevation, float elevation) { 1582 | ViewCompat.setElevation(this, useElevation ? elevation : 0); 1583 | setClipToPadding(false); 1584 | } 1585 | 1586 | /** 1587 | * Return if the Bottom Navigation is hidden or not 1588 | */ 1589 | public boolean isHidden() { 1590 | if (bottomNavigationBehavior != null) { 1591 | return bottomNavigationBehavior.isHidden(); 1592 | } 1593 | return false; 1594 | } 1595 | 1596 | /** 1597 | * Get the view at the given position 1598 | * @param position int 1599 | * @return The view at the position, or null 1600 | */ 1601 | public View getViewAtPosition(int position) { 1602 | if (linearLayoutContainer != null && position >= 0 1603 | && position < linearLayoutContainer.getChildCount()) { 1604 | return linearLayoutContainer.getChildAt(position); 1605 | } 1606 | return null; 1607 | } 1608 | 1609 | /** 1610 | * Enable the tab item at the given position 1611 | * @param position int 1612 | */ 1613 | public void enableItemAtPosition(int position) { 1614 | if (position < 0 || position > items.size() - 1) { 1615 | Log.w(TAG, "The position is out of bounds of the items (" + items.size() + " elements)"); 1616 | return; 1617 | } 1618 | itemsEnabledStates[position] = true; 1619 | createItems(); 1620 | } 1621 | 1622 | /** 1623 | * Disable the tab item at the given position 1624 | * @param position int 1625 | */ 1626 | public void disableItemAtPosition(int position) { 1627 | if (position < 0 || position > items.size() - 1) { 1628 | Log.w(TAG, "The position is out of bounds of the items (" + items.size() + " elements)"); 1629 | return; 1630 | } 1631 | itemsEnabledStates[position] = false; 1632 | createItems(); 1633 | } 1634 | 1635 | /** 1636 | * Set the item disable color 1637 | * @param itemDisableColor int 1638 | */ 1639 | public void setItemDisableColor(@ColorInt int itemDisableColor) { 1640 | this.itemDisableColor = itemDisableColor; 1641 | } 1642 | 1643 | //////////////// 1644 | // INTERFACES // 1645 | //////////////// 1646 | 1647 | /** 1648 | * 1649 | */ 1650 | public interface OnTabSelectedListener { 1651 | /** 1652 | * Called when a tab has been selected (clicked) 1653 | * 1654 | * @param position int: Position of the selected tab 1655 | * @param wasSelected boolean: true if the tab was already selected 1656 | * @return boolean: true for updating the tab UI, false otherwise 1657 | */ 1658 | boolean onTabSelected(int position, boolean wasSelected); 1659 | } 1660 | 1661 | public interface OnNavigationPositionListener { 1662 | /** 1663 | * Called when the bottom navigation position is changed 1664 | * 1665 | * @param y int: y translation of bottom navigation 1666 | */ 1667 | void onPositionChange(int y); 1668 | } 1669 | 1670 | } 1671 | -------------------------------------------------------------------------------- /ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/AHBottomNavigationAdapter.java: -------------------------------------------------------------------------------- 1 | package com.aurelhubert.ahbottomnavigation; 2 | 3 | import android.app.Activity; 4 | import androidx.annotation.ColorInt; 5 | import androidx.annotation.MenuRes; 6 | import androidx.appcompat.widget.PopupMenu; 7 | import android.view.Menu; 8 | import android.view.MenuItem; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | /** 14 | * 15 | */ 16 | public class AHBottomNavigationAdapter { 17 | 18 | private Menu mMenu; 19 | private List navigationItems; 20 | 21 | /** 22 | * Constructor 23 | * 24 | * @param activity 25 | * @param menuRes 26 | */ 27 | public AHBottomNavigationAdapter(Activity activity, @MenuRes int menuRes) { 28 | PopupMenu popupMenu = new PopupMenu(activity, null); 29 | mMenu = popupMenu.getMenu(); 30 | activity.getMenuInflater().inflate(menuRes, mMenu); 31 | } 32 | 33 | /** 34 | * Setup bottom navigation 35 | * 36 | * @param ahBottomNavigation AHBottomNavigation: Bottom navigation 37 | */ 38 | public void setupWithBottomNavigation(AHBottomNavigation ahBottomNavigation) { 39 | setupWithBottomNavigation(ahBottomNavigation, null); 40 | } 41 | 42 | /** 43 | * Setup bottom navigation (with colors) 44 | * 45 | * @param ahBottomNavigation AHBottomNavigation: Bottom navigation 46 | * @param colors int[]: Colors of the item 47 | */ 48 | public void setupWithBottomNavigation(AHBottomNavigation ahBottomNavigation, @ColorInt int[] colors) { 49 | if (navigationItems == null) { 50 | navigationItems = new ArrayList<>(); 51 | } else { 52 | navigationItems.clear(); 53 | } 54 | 55 | if (mMenu != null) { 56 | for (int i = 0; i < mMenu.size(); i++) { 57 | MenuItem item = mMenu.getItem(i); 58 | if (colors != null && colors.length >= mMenu.size() && colors[i] != 0) { 59 | AHBottomNavigationItem navigationItem = new AHBottomNavigationItem(String.valueOf(item.getTitle()), item.getIcon(), colors[i]); 60 | navigationItems.add(navigationItem); 61 | } else { 62 | AHBottomNavigationItem navigationItem = new AHBottomNavigationItem(String.valueOf(item.getTitle()), item.getIcon()); 63 | navigationItems.add(navigationItem); 64 | } 65 | } 66 | ahBottomNavigation.removeAllItems(); 67 | ahBottomNavigation.addItems(navigationItems); 68 | } 69 | } 70 | 71 | /** 72 | * Get Menu Item 73 | * 74 | * @param index 75 | * @return 76 | */ 77 | public MenuItem getMenuItem(int index) { 78 | return mMenu.getItem(index); 79 | } 80 | 81 | /** 82 | * Get Navigation Item 83 | * 84 | * @param index 85 | * @return 86 | */ 87 | public AHBottomNavigationItem getNavigationItem(int index) { 88 | return navigationItems.get(index); 89 | } 90 | 91 | /** 92 | * Get position by menu id 93 | * 94 | * @param menuId 95 | * @return 96 | */ 97 | public Integer getPositionByMenuId(int menuId) { 98 | for (int i = 0; i < mMenu.size(); i++) { 99 | if (mMenu.getItem(i).getItemId() == menuId) 100 | return i; 101 | } 102 | return null; 103 | } 104 | } -------------------------------------------------------------------------------- /ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/AHBottomNavigationBehavior.java: -------------------------------------------------------------------------------- 1 | package com.aurelhubert.ahbottomnavigation; 2 | 3 | import android.animation.ObjectAnimator; 4 | import android.animation.ValueAnimator; 5 | import android.content.Context; 6 | import android.content.res.TypedArray; 7 | import android.os.Build; 8 | import androidx.coordinatorlayout.widget.CoordinatorLayout; 9 | import com.google.android.material.floatingactionbutton.FloatingActionButton; 10 | import com.google.android.material.snackbar.Snackbar; 11 | import com.google.android.material.tabs.TabLayout; 12 | import androidx.core.view.ViewCompat; 13 | import androidx.core.view.ViewPropertyAnimatorCompat; 14 | import androidx.core.view.ViewPropertyAnimatorUpdateListener; 15 | import androidx.interpolator.view.animation.LinearOutSlowInInterpolator; 16 | import android.util.AttributeSet; 17 | import android.view.View; 18 | import android.view.ViewGroup; 19 | import android.view.animation.Interpolator; 20 | 21 | import com.aurelhubert.ahbottomnavigation.AHBottomNavigation.OnNavigationPositionListener; 22 | 23 | /** 24 | * 25 | */ 26 | public class AHBottomNavigationBehavior extends VerticalScrollingBehavior { 27 | 28 | private static final Interpolator INTERPOLATOR = new LinearOutSlowInInterpolator(); 29 | private static final int ANIM_DURATION = 300; 30 | 31 | private int mTabLayoutId; 32 | private boolean hidden = false; 33 | private ViewPropertyAnimatorCompat translationAnimator; 34 | private ObjectAnimator translationObjectAnimator; 35 | private TabLayout mTabLayout; 36 | private Snackbar.SnackbarLayout snackbarLayout; 37 | private FloatingActionButton floatingActionButton; 38 | private int mSnackbarHeight = -1, navigationBarHeight = 0; 39 | private boolean fabBottomMarginInitialized = false; 40 | private float targetOffset = 0, fabTargetOffset = 0, fabDefaultBottomMargin = 0, snackBarY = 0; 41 | private boolean behaviorTranslationEnabled = true; 42 | private OnNavigationPositionListener navigationPositionListener; 43 | 44 | /** 45 | * Constructor 46 | */ 47 | public AHBottomNavigationBehavior() { 48 | super(); 49 | } 50 | 51 | public AHBottomNavigationBehavior(boolean behaviorTranslationEnabled, int navigationBarHeight) { 52 | super(); 53 | this.behaviorTranslationEnabled = behaviorTranslationEnabled; 54 | this.navigationBarHeight = navigationBarHeight; 55 | } 56 | 57 | public AHBottomNavigationBehavior(Context context, AttributeSet attrs) { 58 | super(context, attrs); 59 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AHBottomNavigationBehavior_Params); 60 | mTabLayoutId = a.getResourceId(R.styleable.AHBottomNavigationBehavior_Params_tabLayoutId, View.NO_ID); 61 | a.recycle(); 62 | } 63 | 64 | @Override 65 | public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection) { 66 | boolean layoutChild = super.onLayoutChild(parent, child, layoutDirection); 67 | if (mTabLayout == null && mTabLayoutId != View.NO_ID) { 68 | mTabLayout = findTabLayout(child); 69 | } 70 | return layoutChild; 71 | } 72 | 73 | private TabLayout findTabLayout(View child) { 74 | if (mTabLayoutId == 0) return null; 75 | return (TabLayout) child.findViewById(mTabLayoutId); 76 | } 77 | 78 | @Override 79 | public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) { 80 | return super.onDependentViewChanged(parent, child, dependency); 81 | } 82 | 83 | @Override 84 | public void onDependentViewRemoved(CoordinatorLayout parent, V child, View dependency) { 85 | super.onDependentViewRemoved(parent, child, dependency); 86 | } 87 | 88 | @Override 89 | public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) { 90 | if (dependency != null && dependency instanceof Snackbar.SnackbarLayout) { 91 | updateSnackbar(child, dependency); 92 | return true; 93 | } 94 | return super.layoutDependsOn(parent, child, dependency); 95 | } 96 | 97 | @Override 98 | public void onNestedVerticalOverScroll(CoordinatorLayout coordinatorLayout, V child, @ScrollDirection int direction, int currentOverScroll, int totalOverScroll) { 99 | } 100 | 101 | @Override 102 | public void onDirectionNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dx, int dy, int[] consumed, @ScrollDirection int scrollDirection) { 103 | } 104 | 105 | @Override 106 | protected boolean onNestedDirectionFling(CoordinatorLayout coordinatorLayout, V child, View target, float velocityX, float velocityY, @ScrollDirection int scrollDirection) { 107 | return false; 108 | } 109 | 110 | @Override 111 | public void onNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { 112 | super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed); 113 | if (dyConsumed < 0) { 114 | handleDirection(child, ScrollDirection.SCROLL_DIRECTION_DOWN); 115 | } else if (dyConsumed > 0) { 116 | handleDirection(child, ScrollDirection.SCROLL_DIRECTION_UP); 117 | } 118 | } 119 | 120 | @Override 121 | public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, V child, View directTargetChild, View target, int nestedScrollAxes) { 122 | return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL || super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes); 123 | } 124 | 125 | /** 126 | * Handle scroll direction 127 | * @param child 128 | * @param scrollDirection 129 | */ 130 | private void handleDirection(V child, int scrollDirection) { 131 | if (!behaviorTranslationEnabled) { 132 | return; 133 | } 134 | if (scrollDirection == ScrollDirection.SCROLL_DIRECTION_DOWN && hidden) { 135 | hidden = false; 136 | animateOffset(child, 0, false, true); 137 | } else if (scrollDirection == ScrollDirection.SCROLL_DIRECTION_UP && !hidden) { 138 | hidden = true; 139 | animateOffset(child, child.getHeight(), false, true); 140 | } 141 | } 142 | 143 | /** 144 | * Animate offset 145 | * 146 | * @param child 147 | * @param offset 148 | */ 149 | private void animateOffset(final V child, final int offset, boolean forceAnimation, boolean withAnimation) { 150 | if (!behaviorTranslationEnabled && !forceAnimation) { 151 | return; 152 | } 153 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { 154 | ensureOrCancelObjectAnimation(child, offset, withAnimation); 155 | translationObjectAnimator.start(); 156 | } else { 157 | ensureOrCancelAnimator(child, withAnimation); 158 | translationAnimator.translationY(offset).start(); 159 | } 160 | } 161 | 162 | /** 163 | * Manage animation for Android >= KITKAT 164 | * 165 | * @param child 166 | */ 167 | private void ensureOrCancelAnimator(V child, boolean withAnimation) { 168 | if (translationAnimator == null) { 169 | translationAnimator = ViewCompat.animate(child); 170 | translationAnimator.setDuration(withAnimation ? ANIM_DURATION : 0); 171 | translationAnimator.setUpdateListener(new ViewPropertyAnimatorUpdateListener() { 172 | @Override 173 | public void onAnimationUpdate(View view) { 174 | if (navigationPositionListener != null) { 175 | navigationPositionListener.onPositionChange((int) (view.getMeasuredHeight() - view.getTranslationY() + snackBarY)); 176 | } 177 | } 178 | }); 179 | translationAnimator.setInterpolator(INTERPOLATOR); 180 | } else { 181 | translationAnimator.setDuration(withAnimation ? ANIM_DURATION : 0); 182 | translationAnimator.cancel(); 183 | } 184 | } 185 | 186 | /** 187 | * Manage animation for Android < KITKAT 188 | * 189 | * @param child 190 | */ 191 | private void ensureOrCancelObjectAnimation(final V child, final int offset, boolean withAnimation) { 192 | 193 | if (translationObjectAnimator != null) { 194 | translationObjectAnimator.cancel(); 195 | } 196 | 197 | translationObjectAnimator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Y, offset); 198 | translationObjectAnimator.setDuration(withAnimation ? ANIM_DURATION : 0); 199 | translationObjectAnimator.setInterpolator(INTERPOLATOR); 200 | translationObjectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 201 | @Override 202 | public void onAnimationUpdate(ValueAnimator animation) { 203 | if (snackbarLayout != null && snackbarLayout.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) { 204 | targetOffset = child.getMeasuredHeight() - child.getTranslationY(); 205 | ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) snackbarLayout.getLayoutParams(); 206 | p.setMargins(p.leftMargin, p.topMargin, p.rightMargin, (int) targetOffset); 207 | snackbarLayout.requestLayout(); 208 | } 209 | // Pass navigation height to listener 210 | if (navigationPositionListener != null) { 211 | navigationPositionListener.onPositionChange((int) (child.getMeasuredHeight() - child.getTranslationY() + snackBarY)); 212 | } 213 | } 214 | }); 215 | } 216 | 217 | 218 | public static AHBottomNavigationBehavior from(V view) { 219 | ViewGroup.LayoutParams params = view.getLayoutParams(); 220 | if (!(params instanceof CoordinatorLayout.LayoutParams)) { 221 | throw new IllegalArgumentException("The view is not a child of CoordinatorLayout"); 222 | } 223 | CoordinatorLayout.Behavior behavior = ((CoordinatorLayout.LayoutParams) params) 224 | .getBehavior(); 225 | if (!(behavior instanceof AHBottomNavigationBehavior)) { 226 | throw new IllegalArgumentException( 227 | "The view is not associated with AHBottomNavigationBehavior"); 228 | } 229 | return (AHBottomNavigationBehavior) behavior; 230 | } 231 | 232 | public void setTabLayoutId(int tabId) { 233 | this.mTabLayoutId = tabId; 234 | } 235 | 236 | /** 237 | * Enable or not the behavior translation 238 | * @param behaviorTranslationEnabled 239 | */ 240 | public void setBehaviorTranslationEnabled(boolean behaviorTranslationEnabled, int navigationBarHeight) { 241 | this.behaviorTranslationEnabled = behaviorTranslationEnabled; 242 | this.navigationBarHeight = navigationBarHeight; 243 | } 244 | 245 | /** 246 | * Set OnNavigationPositionListener 247 | */ 248 | public void setOnNavigationPositionListener(OnNavigationPositionListener navigationHeightListener) { 249 | this.navigationPositionListener = navigationHeightListener; 250 | } 251 | 252 | /** 253 | * Remove OnNavigationPositionListener() 254 | */ 255 | public void removeOnNavigationPositionListener() { 256 | this.navigationPositionListener = null; 257 | } 258 | 259 | /** 260 | * Hide AHBottomNavigation with animation 261 | * @param view 262 | * @param offset 263 | */ 264 | public void hideView(V view, int offset, boolean withAnimation) { 265 | if (!hidden) { 266 | hidden = true; 267 | animateOffset(view, offset, true, withAnimation); 268 | } 269 | } 270 | 271 | /** 272 | * Reset AHBottomNavigation position with animation 273 | * @param view 274 | */ 275 | public void resetOffset(V view, boolean withAnimation) { 276 | if (hidden) { 277 | hidden = false; 278 | animateOffset(view, 0, true, withAnimation); 279 | } 280 | } 281 | 282 | /** 283 | * Update Snackbar bottom margin 284 | */ 285 | public void updateSnackbar(final View child, View dependency) { 286 | 287 | if (dependency != null && dependency instanceof Snackbar.SnackbarLayout) { 288 | 289 | snackbarLayout = (Snackbar.SnackbarLayout) dependency; 290 | 291 | if (mSnackbarHeight == -1) { 292 | mSnackbarHeight = dependency.getHeight(); 293 | } 294 | 295 | int targetMargin = (int) (child.getMeasuredHeight() - child.getTranslationY()); 296 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { 297 | child.bringToFront(); 298 | } 299 | 300 | if (dependency.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) { 301 | ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) dependency.getLayoutParams(); 302 | p.setMargins(p.leftMargin, p.topMargin, p.rightMargin, targetMargin); 303 | dependency.requestLayout(); 304 | } 305 | } 306 | } 307 | 308 | /** 309 | * Is hidden 310 | * @return 311 | */ 312 | public boolean isHidden() { 313 | return hidden; 314 | } 315 | } -------------------------------------------------------------------------------- /ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/AHBottomNavigationFABBehavior.java: -------------------------------------------------------------------------------- 1 | package com.aurelhubert.ahbottomnavigation; 2 | 3 | import androidx.coordinatorlayout.widget.CoordinatorLayout; 4 | import com.google.android.material.floatingactionbutton.FloatingActionButton; 5 | import com.google.android.material.snackbar.Snackbar; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | 9 | /** 10 | * 11 | */ 12 | public class AHBottomNavigationFABBehavior extends CoordinatorLayout.Behavior { 13 | 14 | private int navigationBarHeight = 0; 15 | private long lastSnackbarUpdate = 0; 16 | 17 | public AHBottomNavigationFABBehavior(int navigationBarHeight) { 18 | this.navigationBarHeight = navigationBarHeight; 19 | } 20 | 21 | @Override 22 | public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton child, View dependency) { 23 | if (dependency != null && dependency instanceof Snackbar.SnackbarLayout) { 24 | return true; 25 | } else if (dependency != null && dependency instanceof AHBottomNavigation) { 26 | return true; 27 | } 28 | return super.layoutDependsOn(parent, child, dependency); 29 | } 30 | 31 | @Override 32 | public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child, View dependency) { 33 | updateFloatingActionButton(child, dependency); 34 | return super.onDependentViewChanged(parent, child, dependency); 35 | } 36 | 37 | /** 38 | * Update floating action button bottom margin 39 | */ 40 | private void updateFloatingActionButton(FloatingActionButton child, View dependency) { 41 | if (child != null && dependency != null && dependency instanceof Snackbar.SnackbarLayout) { 42 | lastSnackbarUpdate = System.currentTimeMillis(); 43 | ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) child.getLayoutParams(); 44 | int fabDefaultBottomMargin = p.bottomMargin; 45 | child.setY(dependency.getY() - fabDefaultBottomMargin); 46 | } else if (child != null && dependency != null && dependency instanceof AHBottomNavigation) { 47 | // Hack to avoid moving the FAB when the AHBottomNavigation is moving (showing or hiding animation) 48 | if (System.currentTimeMillis() - lastSnackbarUpdate < 30) { 49 | return; 50 | } 51 | ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) child.getLayoutParams(); 52 | int fabDefaultBottomMargin = p.bottomMargin; 53 | child.setY(dependency.getY() - fabDefaultBottomMargin); 54 | } 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/AHBottomNavigationItem.java: -------------------------------------------------------------------------------- 1 | package com.aurelhubert.ahbottomnavigation; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | import android.graphics.Color; 6 | import android.graphics.drawable.Drawable; 7 | import androidx.annotation.ColorInt; 8 | import androidx.annotation.ColorRes; 9 | import androidx.annotation.DrawableRes; 10 | import androidx.annotation.StringRes; 11 | import androidx.core.content.ContextCompat; 12 | import androidx.appcompat.content.res.AppCompatResources; 13 | 14 | /** 15 | * AHBottomNavigationItem 16 | * The item is display in the AHBottomNavigation layout 17 | */ 18 | public class AHBottomNavigationItem { 19 | 20 | private String title = ""; 21 | private Drawable drawable; 22 | private int color = Color.GRAY; 23 | 24 | private 25 | @StringRes 26 | int titleRes = 0; 27 | private 28 | @DrawableRes 29 | int drawableRes = 0; 30 | private 31 | @ColorRes 32 | int colorRes = 0; 33 | 34 | /** 35 | * Constructor 36 | * 37 | * @param title Title 38 | * @param resource Drawable resource 39 | */ 40 | public AHBottomNavigationItem(String title, @DrawableRes int resource) { 41 | this.title = title; 42 | this.drawableRes = resource; 43 | } 44 | 45 | /** 46 | * @param title Title 47 | * @param resource Drawable resource 48 | * @param color Background color 49 | */ 50 | @Deprecated 51 | public AHBottomNavigationItem(String title, @DrawableRes int resource, @ColorRes int color) { 52 | this.title = title; 53 | this.drawableRes = resource; 54 | this.color = color; 55 | } 56 | 57 | /** 58 | * Constructor 59 | * 60 | * @param titleRes String resource 61 | * @param drawableRes Drawable resource 62 | * @param colorRes Color resource 63 | */ 64 | public AHBottomNavigationItem(@StringRes int titleRes, @DrawableRes int drawableRes, @ColorRes int colorRes) { 65 | this.titleRes = titleRes; 66 | this.drawableRes = drawableRes; 67 | this.colorRes = colorRes; 68 | } 69 | 70 | /** 71 | * Constructor 72 | * 73 | * @param title String 74 | * @param drawable Drawable 75 | */ 76 | public AHBottomNavigationItem(String title, Drawable drawable) { 77 | this.title = title; 78 | this.drawable = drawable; 79 | } 80 | 81 | /** 82 | * Constructor 83 | * 84 | * @param title String 85 | * @param drawable Drawable 86 | * @param color Color 87 | */ 88 | public AHBottomNavigationItem(String title, Drawable drawable, @ColorInt int color) { 89 | this.title = title; 90 | this.drawable = drawable; 91 | this.color = color; 92 | } 93 | 94 | public String getTitle(Context context) { 95 | if (titleRes != 0) { 96 | return context.getString(titleRes); 97 | } 98 | return title; 99 | } 100 | 101 | public void setTitle(String title) { 102 | this.title = title; 103 | this.titleRes = 0; 104 | } 105 | 106 | public void setTitle(@StringRes int titleRes) { 107 | this.titleRes = titleRes; 108 | this.title = ""; 109 | } 110 | 111 | public int getColor(Context context) { 112 | if (colorRes != 0) { 113 | return ContextCompat.getColor(context, colorRes); 114 | } 115 | return color; 116 | } 117 | 118 | public void setColor(@ColorInt int color) { 119 | this.color = color; 120 | this.colorRes = 0; 121 | } 122 | 123 | public void setColorRes(@ColorRes int colorRes) { 124 | this.colorRes = colorRes; 125 | this.color = 0; 126 | } 127 | 128 | public Drawable getDrawable(Context context) { 129 | if (drawableRes != 0) { 130 | try { 131 | return AppCompatResources.getDrawable(context, drawableRes); 132 | } catch (Resources.NotFoundException e) { 133 | return ContextCompat.getDrawable(context, drawableRes); 134 | } 135 | } 136 | return drawable; 137 | } 138 | 139 | public void setDrawable(@DrawableRes int drawableRes) { 140 | this.drawableRes = drawableRes; 141 | this.drawable = null; 142 | } 143 | 144 | public void setDrawable(Drawable drawable) { 145 | this.drawable = drawable; 146 | this.drawableRes = 0; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/AHBottomNavigationViewPager.java: -------------------------------------------------------------------------------- 1 | package com.aurelhubert.ahbottomnavigation; 2 | 3 | import android.content.Context; 4 | import androidx.viewpager.widget.ViewPager; 5 | import android.util.AttributeSet; 6 | import android.view.MotionEvent; 7 | 8 | /** 9 | * 10 | */ 11 | public class AHBottomNavigationViewPager extends ViewPager { 12 | 13 | private boolean enabled; 14 | 15 | public AHBottomNavigationViewPager(Context context, AttributeSet attrs) { 16 | super(context, attrs); 17 | this.enabled = false; 18 | } 19 | 20 | @Override 21 | public boolean onTouchEvent(MotionEvent event) { 22 | if (this.enabled) { 23 | return super.onTouchEvent(event); 24 | } 25 | 26 | return false; 27 | } 28 | 29 | @Override 30 | public boolean onInterceptTouchEvent(MotionEvent event) { 31 | if (this.enabled) { 32 | return super.onInterceptTouchEvent(event); 33 | } 34 | 35 | return false; 36 | } 37 | 38 | /** 39 | * Enable or disable the swipe navigation 40 | * @param enabled 41 | */ 42 | public void setPagingEnabled(boolean enabled) { 43 | this.enabled = enabled; 44 | } 45 | } -------------------------------------------------------------------------------- /ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/AHHelper.java: -------------------------------------------------------------------------------- 1 | package com.aurelhubert.ahbottomnavigation; 2 | 3 | import android.animation.ArgbEvaluator; 4 | import android.animation.ValueAnimator; 5 | import android.app.Activity; 6 | import android.content.Context; 7 | import android.content.ContextWrapper; 8 | import android.graphics.PorterDuff; 9 | import android.graphics.drawable.Drawable; 10 | import android.os.Build; 11 | import androidx.annotation.ColorInt; 12 | import androidx.core.graphics.drawable.DrawableCompat; 13 | import android.util.DisplayMetrics; 14 | import android.util.TypedValue; 15 | import android.view.View; 16 | import android.view.ViewGroup; 17 | import android.view.Window; 18 | import android.view.WindowManager; 19 | import android.widget.ImageView; 20 | import android.widget.TextView; 21 | 22 | /** 23 | * 24 | */ 25 | public class AHHelper { 26 | 27 | /** 28 | * Return a tint drawable 29 | * 30 | * @param drawable 31 | * @param color 32 | * @param forceTint 33 | * @return 34 | */ 35 | public static Drawable getTintDrawable(Drawable drawable, @ColorInt int color, boolean forceTint) { 36 | if (forceTint) { 37 | drawable.clearColorFilter(); 38 | drawable.mutate().setColorFilter(color, PorterDuff.Mode.SRC_IN); 39 | drawable.invalidateSelf(); 40 | return drawable; 41 | } 42 | Drawable wrapDrawable = DrawableCompat.wrap(drawable).mutate(); 43 | DrawableCompat.setTint(wrapDrawable, color); 44 | return wrapDrawable; 45 | } 46 | 47 | /** 48 | * Update top margin with animation 49 | */ 50 | public static void updateTopMargin(final View view, int fromMargin, int toMargin) { 51 | ValueAnimator animator = ValueAnimator.ofFloat(fromMargin, toMargin); 52 | animator.setDuration(150); 53 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 54 | @Override 55 | public void onAnimationUpdate(ValueAnimator valueAnimator) { 56 | float animatedValue = (float) valueAnimator.getAnimatedValue(); 57 | if (view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) { 58 | ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); 59 | p.setMargins(p.leftMargin, (int) animatedValue, p.rightMargin, p.bottomMargin); 60 | view.requestLayout(); 61 | } 62 | } 63 | }); 64 | animator.start(); 65 | } 66 | 67 | /** 68 | * Update bottom margin with animation 69 | */ 70 | public static void updateBottomMargin(final View view, int fromMargin, int toMargin, int duration) { 71 | ValueAnimator animator = ValueAnimator.ofFloat(fromMargin, toMargin); 72 | animator.setDuration(duration); 73 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 74 | @Override 75 | public void onAnimationUpdate(ValueAnimator valueAnimator) { 76 | float animatedValue = (float) valueAnimator.getAnimatedValue(); 77 | if (view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) { 78 | ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); 79 | p.setMargins(p.leftMargin, p.topMargin, p.rightMargin, (int) animatedValue); 80 | view.requestLayout(); 81 | } 82 | } 83 | }); 84 | animator.start(); 85 | } 86 | 87 | /** 88 | * Update left margin with animation 89 | */ 90 | public static void updateLeftMargin(final View view, int fromMargin, int toMargin) { 91 | ValueAnimator animator = ValueAnimator.ofFloat(fromMargin, toMargin); 92 | animator.setDuration(150); 93 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 94 | @Override 95 | public void onAnimationUpdate(ValueAnimator valueAnimator) { 96 | float animatedValue = (float) valueAnimator.getAnimatedValue(); 97 | if (view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) { 98 | ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); 99 | p.setMargins((int) animatedValue, p.topMargin, p.rightMargin, p.bottomMargin); 100 | view.requestLayout(); 101 | } 102 | } 103 | }); 104 | animator.start(); 105 | } 106 | 107 | /** 108 | * Update text size with animation 109 | */ 110 | public static void updateTextSize(final TextView textView, float fromSize, float toSize) { 111 | ValueAnimator animator = ValueAnimator.ofFloat(fromSize, toSize); 112 | animator.setDuration(150); 113 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 114 | @Override 115 | public void onAnimationUpdate(ValueAnimator valueAnimator) { 116 | float animatedValue = (float) valueAnimator.getAnimatedValue(); 117 | textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, animatedValue); 118 | } 119 | }); 120 | animator.start(); 121 | } 122 | 123 | /** 124 | * Update alpha 125 | */ 126 | public static void updateAlpha(final View view, float fromValue, float toValue) { 127 | ValueAnimator animator = ValueAnimator.ofFloat(fromValue, toValue); 128 | animator.setDuration(150); 129 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 130 | @Override 131 | public void onAnimationUpdate(ValueAnimator valueAnimator) { 132 | float animatedValue = (float) valueAnimator.getAnimatedValue(); 133 | view.setAlpha(animatedValue); 134 | } 135 | }); 136 | animator.start(); 137 | } 138 | 139 | /** 140 | * Update text color with animation 141 | */ 142 | public static void updateTextColor(final TextView textView, @ColorInt int fromColor, 143 | @ColorInt int toColor) { 144 | ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), fromColor, toColor); 145 | colorAnimation.setDuration(150); 146 | colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 147 | @Override 148 | public void onAnimationUpdate(ValueAnimator animator) { 149 | textView.setTextColor((Integer) animator.getAnimatedValue()); 150 | } 151 | }); 152 | colorAnimation.start(); 153 | } 154 | 155 | /** 156 | * Update text color with animation 157 | */ 158 | public static void updateViewBackgroundColor(final View view, @ColorInt int fromColor, 159 | @ColorInt int toColor) { 160 | ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), fromColor, toColor); 161 | colorAnimation.setDuration(150); 162 | colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 163 | @Override 164 | public void onAnimationUpdate(ValueAnimator animator) { 165 | view.setBackgroundColor((Integer) animator.getAnimatedValue()); 166 | } 167 | }); 168 | colorAnimation.start(); 169 | } 170 | 171 | /** 172 | * Update image view color with animation 173 | */ 174 | public static void updateDrawableColor(final Context context, final Drawable drawable, 175 | final ImageView imageView, @ColorInt int fromColor, 176 | @ColorInt int toColor, final boolean forceTint) { 177 | if (forceTint) { 178 | ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), fromColor, toColor); 179 | colorAnimation.setDuration(150); 180 | colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 181 | @Override 182 | public void onAnimationUpdate(ValueAnimator animator) { 183 | imageView.setImageDrawable(AHHelper.getTintDrawable(drawable, 184 | (Integer) animator.getAnimatedValue(), forceTint)); 185 | imageView.requestLayout(); 186 | } 187 | }); 188 | colorAnimation.start(); 189 | } 190 | } 191 | 192 | /** 193 | * Update width 194 | */ 195 | public static void updateWidth(final View view, float fromWidth, float toWidth) { 196 | ValueAnimator animator = ValueAnimator.ofFloat(fromWidth, toWidth); 197 | animator.setDuration(150); 198 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 199 | @Override 200 | public void onAnimationUpdate(ValueAnimator animator) { 201 | ViewGroup.LayoutParams params = view.getLayoutParams(); 202 | params.width = Math.round((float) animator.getAnimatedValue()); 203 | view.setLayoutParams(params); 204 | } 205 | }); 206 | animator.start(); 207 | } 208 | 209 | /** 210 | * Check if the status bar is translucent 211 | * 212 | * @param context Context 213 | * @return 214 | */ 215 | public static boolean isTranslucentStatusBar(Context context) { 216 | Window w = unwrap(context).getWindow(); 217 | WindowManager.LayoutParams lp = w.getAttributes(); 218 | int flags = lp.flags; 219 | if ((flags & WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION) == WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION) { 220 | return true; 221 | } 222 | 223 | return false; 224 | } 225 | 226 | /** 227 | * Get the height of the buttons bar 228 | * 229 | * @param context Context 230 | * @return 231 | */ 232 | public static int getSoftButtonsBarSizePort(Context context) { 233 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { 234 | DisplayMetrics metrics = new DisplayMetrics(); 235 | Window window = unwrap(context).getWindow(); 236 | window.getWindowManager().getDefaultDisplay().getMetrics(metrics); 237 | int usableHeight = metrics.heightPixels; 238 | window.getWindowManager().getDefaultDisplay().getRealMetrics(metrics); 239 | int realHeight = metrics.heightPixels; 240 | if (realHeight > usableHeight) 241 | return realHeight - usableHeight; 242 | else 243 | return 0; 244 | } 245 | return 0; 246 | } 247 | 248 | /** 249 | * Unwrap wactivity 250 | * 251 | * @param context Context 252 | * @return Activity 253 | */ 254 | public static Activity unwrap(Context context) { 255 | while (!(context instanceof Activity)) { 256 | ContextWrapper wrapper = (ContextWrapper) context; 257 | context = wrapper.getBaseContext(); 258 | } 259 | return (Activity) context; 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/VerticalScrollingBehavior.java: -------------------------------------------------------------------------------- 1 | package com.aurelhubert.ahbottomnavigation; 2 | 3 | 4 | import android.content.Context; 5 | import android.os.Parcelable; 6 | import androidx.annotation.IntDef; 7 | import androidx.coordinatorlayout.widget.CoordinatorLayout; 8 | import androidx.core.view.WindowInsetsCompat; 9 | import android.util.AttributeSet; 10 | import android.view.View; 11 | 12 | import java.lang.annotation.Retention; 13 | import java.lang.annotation.RetentionPolicy; 14 | 15 | /** 16 | * Created by Nikola on 11/22/2015. 17 | */ 18 | public abstract class VerticalScrollingBehavior extends CoordinatorLayout.Behavior { 19 | 20 | private int mTotalDyUnconsumed = 0; 21 | private int mTotalDy = 0; 22 | @ScrollDirection 23 | private int mOverScrollDirection = ScrollDirection.SCROLL_NONE; 24 | @ScrollDirection 25 | private int mScrollDirection = ScrollDirection.SCROLL_NONE; 26 | 27 | public VerticalScrollingBehavior(Context context, AttributeSet attrs) { 28 | super(context, attrs); 29 | } 30 | 31 | public VerticalScrollingBehavior() { 32 | super(); 33 | } 34 | 35 | @Retention(RetentionPolicy.SOURCE) 36 | @IntDef({ScrollDirection.SCROLL_DIRECTION_UP, ScrollDirection.SCROLL_DIRECTION_DOWN}) 37 | public @interface ScrollDirection { 38 | int SCROLL_DIRECTION_UP = 1; 39 | int SCROLL_DIRECTION_DOWN = -1; 40 | int SCROLL_NONE = 0; 41 | } 42 | 43 | 44 | /* 45 | @return Overscroll direction: SCROLL_DIRECTION_UP, CROLL_DIRECTION_DOWN, SCROLL_NONE 46 | */ 47 | @ScrollDirection 48 | public int getOverScrollDirection() { 49 | return mOverScrollDirection; 50 | } 51 | 52 | 53 | /** 54 | * @return Scroll direction: SCROLL_DIRECTION_UP, SCROLL_DIRECTION_DOWN, SCROLL_NONE 55 | */ 56 | 57 | @ScrollDirection 58 | public int getScrollDirection() { 59 | return mScrollDirection; 60 | } 61 | 62 | 63 | /** 64 | * @param coordinatorLayout 65 | * @param child 66 | * @param direction Direction of the overscroll: SCROLL_DIRECTION_UP, SCROLL_DIRECTION_DOWN 67 | * @param currentOverScroll Unconsumed value, negative or positive based on the direction; 68 | * @param totalOverScroll Cumulative value for current direction 69 | */ 70 | public abstract void onNestedVerticalOverScroll(CoordinatorLayout coordinatorLayout, V child, @ScrollDirection int direction, int currentOverScroll, int totalOverScroll); 71 | 72 | /** 73 | * @param scrollDirection Direction of the overscroll: SCROLL_DIRECTION_UP, SCROLL_DIRECTION_DOWN 74 | */ 75 | public abstract void onDirectionNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dx, int dy, int[] consumed, @ScrollDirection int scrollDirection); 76 | 77 | @Override 78 | public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, V child, View directTargetChild, View target, int nestedScrollAxes) { 79 | return (nestedScrollAxes & View.SCROLL_AXIS_VERTICAL) != 0; 80 | } 81 | 82 | @Override 83 | public void onNestedScrollAccepted(CoordinatorLayout coordinatorLayout, V child, View directTargetChild, View target, int nestedScrollAxes) { 84 | super.onNestedScrollAccepted(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes); 85 | } 86 | 87 | @Override 88 | public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target) { 89 | super.onStopNestedScroll(coordinatorLayout, child, target); 90 | } 91 | 92 | @Override 93 | public void onNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { 94 | super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed); 95 | if (dyUnconsumed > 0 && mTotalDyUnconsumed < 0) { 96 | mTotalDyUnconsumed = 0; 97 | mOverScrollDirection = ScrollDirection.SCROLL_DIRECTION_UP; 98 | } else if (dyUnconsumed < 0 && mTotalDyUnconsumed > 0) { 99 | mTotalDyUnconsumed = 0; 100 | mOverScrollDirection = ScrollDirection.SCROLL_DIRECTION_DOWN; 101 | } 102 | mTotalDyUnconsumed += dyUnconsumed; 103 | onNestedVerticalOverScroll(coordinatorLayout, child, mOverScrollDirection, dyConsumed, mTotalDyUnconsumed); 104 | } 105 | 106 | @Override 107 | public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dx, int dy, int[] consumed) { 108 | super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed); 109 | if (dy > 0 && mTotalDy < 0) { 110 | mTotalDy = 0; 111 | mScrollDirection = ScrollDirection.SCROLL_DIRECTION_UP; 112 | } else if (dy < 0 && mTotalDy > 0) { 113 | mTotalDy = 0; 114 | mScrollDirection = ScrollDirection.SCROLL_DIRECTION_DOWN; 115 | } 116 | mTotalDy += dy; 117 | onDirectionNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, mScrollDirection); 118 | } 119 | 120 | 121 | @Override 122 | public boolean onNestedFling(CoordinatorLayout coordinatorLayout, V child, View target, float velocityX, float velocityY, boolean consumed) { 123 | super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed); 124 | mScrollDirection = velocityY > 0 ? ScrollDirection.SCROLL_DIRECTION_UP : ScrollDirection.SCROLL_DIRECTION_DOWN; 125 | return onNestedDirectionFling(coordinatorLayout, child, target, velocityX, velocityY, mScrollDirection); 126 | } 127 | 128 | protected abstract boolean onNestedDirectionFling(CoordinatorLayout coordinatorLayout, V child, View target, float velocityX, float velocityY, @ScrollDirection int scrollDirection); 129 | 130 | @Override 131 | public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, V child, View target, float velocityX, float velocityY) { 132 | return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY); 133 | } 134 | 135 | @Override 136 | public WindowInsetsCompat onApplyWindowInsets(CoordinatorLayout coordinatorLayout, V child, WindowInsetsCompat insets) { 137 | 138 | return super.onApplyWindowInsets(coordinatorLayout, child, insets); 139 | } 140 | 141 | @Override 142 | public Parcelable onSaveInstanceState(CoordinatorLayout parent, V child) { 143 | return super.onSaveInstanceState(parent, child); 144 | } 145 | 146 | } -------------------------------------------------------------------------------- /ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/notification/AHNotification.java: -------------------------------------------------------------------------------- 1 | package com.aurelhubert.ahbottomnavigation.notification; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | import androidx.annotation.ColorInt; 6 | import androidx.annotation.Nullable; 7 | import android.text.TextUtils; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | /** 13 | * @author repitch 14 | */ 15 | public class AHNotification implements Parcelable { 16 | 17 | @Nullable 18 | private String text; // can be null, so notification will not be shown 19 | 20 | @ColorInt 21 | private int textColor; // if 0 then use default value 22 | 23 | @ColorInt 24 | private int backgroundColor; // if 0 then use default value 25 | 26 | public AHNotification() { 27 | // empty 28 | } 29 | 30 | private AHNotification(Parcel in) { 31 | text = in.readString(); 32 | textColor = in.readInt(); 33 | backgroundColor = in.readInt(); 34 | } 35 | 36 | public boolean isEmpty() { 37 | return TextUtils.isEmpty(text); 38 | } 39 | 40 | public String getText() { 41 | return text; 42 | } 43 | 44 | public int getTextColor() { 45 | return textColor; 46 | } 47 | 48 | public int getBackgroundColor() { 49 | return backgroundColor; 50 | } 51 | 52 | public static AHNotification justText(String text) { 53 | return new Builder().setText(text).build(); 54 | } 55 | 56 | public static List generateEmptyList(int size) { 57 | List notificationList = new ArrayList<>(); 58 | for (int i = 0; i < size; i++) { 59 | notificationList.add(new AHNotification()); 60 | } 61 | return notificationList; 62 | } 63 | 64 | @Override 65 | public int describeContents() { 66 | return 0; 67 | } 68 | 69 | @Override 70 | public void writeToParcel(Parcel dest, int flags) { 71 | dest.writeString(text); 72 | dest.writeInt(textColor); 73 | dest.writeInt(backgroundColor); 74 | } 75 | 76 | public static class Builder { 77 | @Nullable 78 | private String text; 79 | @ColorInt 80 | private int textColor; 81 | @ColorInt 82 | private int backgroundColor; 83 | 84 | public Builder setText(String text) { 85 | this.text = text; 86 | return this; 87 | } 88 | 89 | public Builder setTextColor(@ColorInt int textColor) { 90 | this.textColor = textColor; 91 | return this; 92 | } 93 | 94 | public Builder setBackgroundColor(@ColorInt int backgroundColor) { 95 | this.backgroundColor = backgroundColor; 96 | return this; 97 | } 98 | 99 | public AHNotification build() { 100 | AHNotification notification = new AHNotification(); 101 | notification.text = text; 102 | notification.textColor = textColor; 103 | notification.backgroundColor = backgroundColor; 104 | return notification; 105 | } 106 | } 107 | 108 | public static final Creator CREATOR = new Creator() { 109 | @Override 110 | public AHNotification createFromParcel(Parcel in) { 111 | return new AHNotification(in); 112 | } 113 | 114 | @Override 115 | public AHNotification[] newArray(int size) { 116 | return new AHNotification[size]; 117 | } 118 | }; 119 | 120 | } 121 | -------------------------------------------------------------------------------- /ahbottomnavigation/src/main/java/com/aurelhubert/ahbottomnavigation/notification/AHNotificationHelper.java: -------------------------------------------------------------------------------- 1 | package com.aurelhubert.ahbottomnavigation.notification; 2 | 3 | import androidx.annotation.ColorInt; 4 | import androidx.annotation.NonNull; 5 | 6 | /** 7 | * @author repitch 8 | */ 9 | public final class AHNotificationHelper { 10 | 11 | private AHNotificationHelper() { 12 | // empty 13 | } 14 | 15 | /** 16 | * Get text color for given notification. If color is not set (0), returns default value. 17 | * 18 | * @param notification AHNotification, non null 19 | * @param defaultTextColor int default text color for all notifications 20 | * @return 21 | */ 22 | public static int getTextColor(@NonNull AHNotification notification, @ColorInt int defaultTextColor) { 23 | int textColor = notification.getTextColor(); 24 | return textColor == 0 ? defaultTextColor : textColor; 25 | } 26 | 27 | /** 28 | * Get background color for given notification. If color is not set (0), returns default value. 29 | * 30 | * @param notification AHNotification, non null 31 | * @param defaultBackgroundColor int default background color for all notifications 32 | * @return 33 | */ 34 | public static int getBackgroundColor(@NonNull AHNotification notification, @ColorInt int defaultBackgroundColor) { 35 | int backgroundColor = notification.getBackgroundColor(); 36 | return backgroundColor == 0 ? defaultBackgroundColor : backgroundColor; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /ahbottomnavigation/src/main/res/drawable-v21/item_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /ahbottomnavigation/src/main/res/drawable/item_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /ahbottomnavigation/src/main/res/drawable/notification_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ahbottomnavigation/src/main/res/layout-v21/bottom_navigation_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 21 | 22 | 34 | 35 | 52 | 53 | -------------------------------------------------------------------------------- /ahbottomnavigation/src/main/res/layout-v21/bottom_navigation_small_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 18 | 19 | 32 | 33 | 50 | 51 | -------------------------------------------------------------------------------- /ahbottomnavigation/src/main/res/layout/bottom_navigation_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 21 | 22 | 34 | 35 | 52 | 53 | -------------------------------------------------------------------------------- /ahbottomnavigation/src/main/res/layout/bottom_navigation_small_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 18 | 19 | 32 | 33 | 50 | 51 | -------------------------------------------------------------------------------- /ahbottomnavigation/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /ahbottomnavigation/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | #747474 7 | #3A000000 8 | #FFFFFF 9 | #50FFFFFF 10 | #17000000 11 | #F63D2B 12 | 13 | -------------------------------------------------------------------------------- /ahbottomnavigation/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8dp 4 | 5 | 104dp 6 | 168dp 7 | 56dp 8 | 24dp 9 | 10 | 6dp 11 | 8dp 12 | 7dp 13 | 12dp 14 | 12dp 15 | 16 | 14sp 17 | 12sp 18 | 19 | 11sp 20 | 10sp 21 | 22 | 23 | 96dp 24 | 168dp 25 | 64dp 26 | 96dp 27 | 28 | 6dp 29 | 16dp 30 | 16dp 31 | 32 | 10dp 33 | 34 | 35 | 16dp 36 | 16dp 37 | 8dp 38 | 1dp 39 | 3dp 40 | 4dp 41 | 16dp 42 | 16dp 43 | 14dp 44 | 3dp 45 | 9sp 46 | 47 | 48 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | google() 7 | } 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.2.1' 10 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.0' 11 | classpath 'com.github.dcendents:android-maven-gradle-plugin:2.0' 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | maven { 19 | url "https://maven.google.com" 20 | } 21 | google() 22 | } 23 | } 24 | 25 | task clean(type: Delete) { 26 | delete rootProject.buildDir 27 | } 28 | -------------------------------------------------------------------------------- /demo/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 28 5 | 6 | defaultConfig { 7 | applicationId "com.aurelhubert.ahbottomnavigation.demo" 8 | minSdkVersion 16 9 | targetSdkVersion 28 10 | versionCode 1 11 | versionName "1.0" 12 | vectorDrawables.useSupportLibrary = true 13 | } 14 | 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | } 22 | 23 | dependencies { 24 | implementation fileTree(dir: 'libs', include: ['*.jar']) 25 | testImplementation 'junit:junit:4.12' 26 | implementation 'androidx.appcompat:appcompat:1.0.2' 27 | implementation 'androidx.cardview:cardview:1.0.0' 28 | implementation 'com.google.android.material:material:1.0.0' 29 | implementation project(':ahbottomnavigation') 30 | } 31 | -------------------------------------------------------------------------------- /demo/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /demo/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /demo/src/main/java/com/aurelhubert/ahbottomnavigation/demo/DemoActivity.java: -------------------------------------------------------------------------------- 1 | package com.aurelhubert.ahbottomnavigation.demo; 2 | 3 | import android.animation.Animator; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.os.Build; 7 | import android.os.Bundle; 8 | import android.os.Handler; 9 | import com.google.android.material.floatingactionbutton.FloatingActionButton; 10 | import com.google.android.material.snackbar.Snackbar; 11 | import androidx.core.content.ContextCompat; 12 | import androidx.interpolator.view.animation.LinearOutSlowInInterpolator; 13 | import androidx.appcompat.app.AppCompatActivity; 14 | import androidx.appcompat.app.AppCompatDelegate; 15 | import android.view.View; 16 | import android.view.animation.OvershootInterpolator; 17 | 18 | import com.aurelhubert.ahbottomnavigation.AHBottomNavigation; 19 | import com.aurelhubert.ahbottomnavigation.AHBottomNavigationAdapter; 20 | import com.aurelhubert.ahbottomnavigation.AHBottomNavigationItem; 21 | import com.aurelhubert.ahbottomnavigation.AHBottomNavigationViewPager; 22 | import com.aurelhubert.ahbottomnavigation.notification.AHNotification; 23 | 24 | import java.util.ArrayList; 25 | 26 | public class DemoActivity extends AppCompatActivity { 27 | 28 | private DemoFragment currentFragment; 29 | private DemoViewPagerAdapter adapter; 30 | private AHBottomNavigationAdapter navigationAdapter; 31 | private ArrayList bottomNavigationItems = new ArrayList<>(); 32 | private boolean useMenuResource = true; 33 | private int[] tabColors; 34 | private Handler handler = new Handler(); 35 | 36 | // UI 37 | private AHBottomNavigationViewPager viewPager; 38 | private AHBottomNavigation bottomNavigation; 39 | private FloatingActionButton floatingActionButton; 40 | 41 | @Override 42 | protected void onCreate(Bundle savedInstanceState) { 43 | super.onCreate(savedInstanceState); 44 | boolean enabledTranslucentNavigation = getSharedPreferences("shared", Context.MODE_PRIVATE) 45 | .getBoolean("translucentNavigation", false); 46 | setTheme(enabledTranslucentNavigation ? R.style.AppTheme_TranslucentNavigation : R.style.AppTheme); 47 | setContentView(R.layout.activity_home); 48 | initUI(); 49 | } 50 | 51 | @Override 52 | protected void onDestroy() { 53 | super.onDestroy(); 54 | handler.removeCallbacksAndMessages(null); 55 | } 56 | 57 | /** 58 | * Init UI 59 | */ 60 | private void initUI() { 61 | 62 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { 63 | AppCompatDelegate.setCompatVectorFromResourcesEnabled(true); 64 | } 65 | 66 | bottomNavigation = findViewById(R.id.bottom_navigation); 67 | viewPager = findViewById(R.id.view_pager); 68 | floatingActionButton = findViewById(R.id.floating_action_button); 69 | 70 | if (useMenuResource) { 71 | tabColors = getApplicationContext().getResources().getIntArray(R.array.tab_colors); 72 | navigationAdapter = new AHBottomNavigationAdapter(this, R.menu.bottom_navigation_menu_3); 73 | navigationAdapter.setupWithBottomNavigation(bottomNavigation, tabColors); 74 | } else { 75 | AHBottomNavigationItem item1 = new AHBottomNavigationItem(R.string.tab_1, R.drawable.ic_apps_black_24dp, R.color.color_tab_1); 76 | AHBottomNavigationItem item2 = new AHBottomNavigationItem(R.string.tab_2, R.drawable.ic_maps_local_bar, R.color.color_tab_2); 77 | AHBottomNavigationItem item3 = new AHBottomNavigationItem(R.string.tab_3, R.drawable.ic_maps_local_restaurant, R.color.color_tab_3); 78 | 79 | bottomNavigationItems.add(item1); 80 | bottomNavigationItems.add(item2); 81 | bottomNavigationItems.add(item3); 82 | 83 | bottomNavigation.addItems(bottomNavigationItems); 84 | } 85 | 86 | bottomNavigation.manageFloatingActionButtonBehavior(floatingActionButton); 87 | bottomNavigation.setTranslucentNavigationEnabled(true); 88 | 89 | bottomNavigation.setOnTabSelectedListener(new AHBottomNavigation.OnTabSelectedListener() { 90 | @Override 91 | public boolean onTabSelected(int position, boolean wasSelected) { 92 | 93 | if (currentFragment == null) { 94 | currentFragment = adapter.getCurrentFragment(); 95 | } 96 | 97 | if (wasSelected) { 98 | currentFragment.refresh(); 99 | return true; 100 | } 101 | 102 | if (currentFragment != null) { 103 | currentFragment.willBeHidden(); 104 | } 105 | 106 | viewPager.setCurrentItem(position, false); 107 | 108 | if (currentFragment == null) { 109 | return true; 110 | } 111 | 112 | currentFragment = adapter.getCurrentFragment(); 113 | currentFragment.willBeDisplayed(); 114 | 115 | if (position == 1) { 116 | bottomNavigation.setNotification("", 1); 117 | 118 | floatingActionButton.setVisibility(View.VISIBLE); 119 | floatingActionButton.setAlpha(0f); 120 | floatingActionButton.setScaleX(0f); 121 | floatingActionButton.setScaleY(0f); 122 | floatingActionButton.animate() 123 | .alpha(1) 124 | .scaleX(1) 125 | .scaleY(1) 126 | .setDuration(300) 127 | .setInterpolator(new OvershootInterpolator()) 128 | .setListener(new Animator.AnimatorListener() { 129 | @Override 130 | public void onAnimationStart(Animator animation) { 131 | 132 | } 133 | 134 | @Override 135 | public void onAnimationEnd(Animator animation) { 136 | floatingActionButton.animate() 137 | .setInterpolator(new LinearOutSlowInInterpolator()) 138 | .start(); 139 | } 140 | 141 | @Override 142 | public void onAnimationCancel(Animator animation) { 143 | 144 | } 145 | 146 | @Override 147 | public void onAnimationRepeat(Animator animation) { 148 | 149 | } 150 | }) 151 | .start(); 152 | 153 | } else { 154 | if (floatingActionButton.getVisibility() == View.VISIBLE) { 155 | floatingActionButton.animate() 156 | .alpha(0) 157 | .scaleX(0) 158 | .scaleY(0) 159 | .setDuration(300) 160 | .setInterpolator(new LinearOutSlowInInterpolator()) 161 | .setListener(new Animator.AnimatorListener() { 162 | @Override 163 | public void onAnimationStart(Animator animation) { 164 | 165 | } 166 | 167 | @Override 168 | public void onAnimationEnd(Animator animation) { 169 | floatingActionButton.setVisibility(View.GONE); 170 | } 171 | 172 | @Override 173 | public void onAnimationCancel(Animator animation) { 174 | floatingActionButton.setVisibility(View.GONE); 175 | } 176 | 177 | @Override 178 | public void onAnimationRepeat(Animator animation) { 179 | 180 | } 181 | }) 182 | .start(); 183 | } 184 | } 185 | 186 | return true; 187 | } 188 | }); 189 | 190 | /* 191 | bottomNavigation.setOnNavigationPositionListener(new AHBottomNavigation.OnNavigationPositionListener() { 192 | @Override public void onPositionChange(int y) { 193 | Log.d("DemoActivity", "BottomNavigation Position: " + y); 194 | } 195 | }); 196 | */ 197 | 198 | viewPager.setOffscreenPageLimit(4); 199 | adapter = new DemoViewPagerAdapter(getSupportFragmentManager()); 200 | viewPager.setAdapter(adapter); 201 | 202 | currentFragment = adapter.getCurrentFragment(); 203 | 204 | handler.postDelayed(new Runnable() { 205 | @Override 206 | public void run() { 207 | // Setting custom colors for notification 208 | AHNotification notification = new AHNotification.Builder() 209 | .setText(":)") 210 | .setBackgroundColor(ContextCompat.getColor(DemoActivity.this, R.color.color_notification_back)) 211 | .setTextColor(ContextCompat.getColor(DemoActivity.this, R.color.color_notification_text)) 212 | .build(); 213 | bottomNavigation.setNotification(notification, 1); 214 | Snackbar.make(bottomNavigation, "Snackbar with bottom navigation", 215 | Snackbar.LENGTH_SHORT).show(); 216 | 217 | } 218 | }, 3000); 219 | 220 | //bottomNavigation.setDefaultBackgroundResource(R.drawable.bottom_navigation_background); 221 | } 222 | 223 | /** 224 | * Update the bottom navigation colored param 225 | */ 226 | public void updateBottomNavigationColor(boolean isColored) { 227 | bottomNavigation.setColored(isColored); 228 | } 229 | 230 | /** 231 | * Return if the bottom navigation is colored 232 | */ 233 | public boolean isBottomNavigationColored() { 234 | return bottomNavigation.isColored(); 235 | } 236 | 237 | /** 238 | * Add or remove items of the bottom navigation 239 | */ 240 | public void updateBottomNavigationItems(boolean addItems) { 241 | 242 | if (useMenuResource) { 243 | if (addItems) { 244 | navigationAdapter = new AHBottomNavigationAdapter(this, R.menu.bottom_navigation_menu_5); 245 | navigationAdapter.setupWithBottomNavigation(bottomNavigation, tabColors); 246 | bottomNavigation.setNotification("1", 3); 247 | } else { 248 | navigationAdapter = new AHBottomNavigationAdapter(this, R.menu.bottom_navigation_menu_3); 249 | navigationAdapter.setupWithBottomNavigation(bottomNavigation, tabColors); 250 | } 251 | 252 | } else { 253 | if (addItems) { 254 | AHBottomNavigationItem item4 = new AHBottomNavigationItem(getString(R.string.tab_4), 255 | ContextCompat.getDrawable(this, R.drawable.ic_maps_local_bar), 256 | ContextCompat.getColor(this, R.color.color_tab_4)); 257 | AHBottomNavigationItem item5 = new AHBottomNavigationItem(getString(R.string.tab_5), 258 | ContextCompat.getDrawable(this, R.drawable.ic_maps_place), 259 | ContextCompat.getColor(this, R.color.color_tab_5)); 260 | 261 | bottomNavigation.addItem(item4); 262 | bottomNavigation.addItem(item5); 263 | bottomNavigation.setNotification("1", 3); 264 | } else { 265 | bottomNavigation.removeAllItems(); 266 | bottomNavigation.addItems(bottomNavigationItems); 267 | } 268 | } 269 | } 270 | 271 | /** 272 | * Show or hide the bottom navigation with animation 273 | */ 274 | public void showOrHideBottomNavigation(boolean show) { 275 | if (show) { 276 | bottomNavigation.restoreBottomNavigation(true); 277 | } else { 278 | bottomNavigation.hideBottomNavigation(true); 279 | } 280 | } 281 | 282 | /** 283 | * Show or hide selected item background 284 | */ 285 | public void updateSelectedBackgroundVisibility(boolean isVisible) { 286 | bottomNavigation.setSelectedBackgroundVisible(isVisible); 287 | } 288 | 289 | /** 290 | * Set title state for bottomNavigation 291 | */ 292 | public void setTitleState(AHBottomNavigation.TitleState titleState) { 293 | bottomNavigation.setTitleState(titleState); 294 | } 295 | 296 | /** 297 | * Reload activity 298 | */ 299 | public void reload() { 300 | startActivity(new Intent(this, DemoActivity.class)); 301 | finish(); 302 | } 303 | 304 | /** 305 | * Return the number of items in the bottom navigation 306 | */ 307 | public int getBottomNavigationNbItems() { 308 | return bottomNavigation.getItemsCount(); 309 | } 310 | 311 | } 312 | -------------------------------------------------------------------------------- /demo/src/main/java/com/aurelhubert/ahbottomnavigation/demo/DemoAdapter.java: -------------------------------------------------------------------------------- 1 | package com.aurelhubert.ahbottomnavigation.demo; 2 | 3 | import androidx.recyclerview.widget.RecyclerView; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.TextView; 8 | 9 | import java.util.ArrayList; 10 | 11 | /** 12 | * 13 | */ 14 | public class DemoAdapter extends RecyclerView.Adapter { 15 | 16 | private ArrayList mDataset = new ArrayList<>(); 17 | 18 | public static class ViewHolder extends RecyclerView.ViewHolder { 19 | public TextView mTextView; 20 | public ViewHolder(View v) { 21 | super(v); 22 | mTextView = (TextView) v.findViewById(R.id.layout_item_demo_title); 23 | } 24 | } 25 | 26 | public DemoAdapter(ArrayList dataset) { 27 | mDataset.clear(); 28 | mDataset.addAll(dataset); 29 | } 30 | 31 | @Override 32 | public DemoAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 33 | View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_item_demo, parent, false); 34 | ViewHolder vh = new ViewHolder(v); 35 | return vh; 36 | } 37 | 38 | @Override 39 | public void onBindViewHolder(ViewHolder holder, int position) { 40 | holder.mTextView.setText(mDataset.get(position)); 41 | 42 | } 43 | 44 | @Override 45 | public int getItemCount() { 46 | return mDataset.size(); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /demo/src/main/java/com/aurelhubert/ahbottomnavigation/demo/DemoFragment.java: -------------------------------------------------------------------------------- 1 | package com.aurelhubert.ahbottomnavigation.demo; 2 | 3 | import android.content.Context; 4 | import android.os.Build; 5 | import android.os.Bundle; 6 | import androidx.annotation.Nullable; 7 | import androidx.fragment.app.Fragment; 8 | import androidx.recyclerview.widget.LinearLayoutManager; 9 | import androidx.recyclerview.widget.RecyclerView; 10 | import androidx.appcompat.widget.SwitchCompat; 11 | import android.view.LayoutInflater; 12 | import android.view.View; 13 | import android.view.ViewGroup; 14 | import android.view.animation.Animation; 15 | import android.view.animation.AnimationUtils; 16 | import android.widget.AdapterView; 17 | import android.widget.ArrayAdapter; 18 | import android.widget.CompoundButton; 19 | import android.widget.FrameLayout; 20 | import android.widget.Spinner; 21 | 22 | import com.aurelhubert.ahbottomnavigation.AHBottomNavigation; 23 | 24 | import java.util.ArrayList; 25 | import java.util.List; 26 | 27 | /** 28 | * 29 | */ 30 | public class DemoFragment extends Fragment { 31 | 32 | private FrameLayout fragmentContainer; 33 | private RecyclerView recyclerView; 34 | private RecyclerView.LayoutManager layoutManager; 35 | 36 | /** 37 | * Create a new instance of the fragment 38 | */ 39 | public static DemoFragment newInstance(int index) { 40 | DemoFragment fragment = new DemoFragment(); 41 | Bundle b = new Bundle(); 42 | b.putInt("index", index); 43 | fragment.setArguments(b); 44 | return fragment; 45 | } 46 | 47 | @Nullable 48 | @Override 49 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 50 | if (getArguments().getInt("index", 0) == 0) { 51 | View view = inflater.inflate(R.layout.fragment_demo_settings, container, false); 52 | initDemoSettings(view); 53 | return view; 54 | } else { 55 | View view = inflater.inflate(R.layout.fragment_demo_list, container, false); 56 | initDemoList(view); 57 | return view; 58 | } 59 | } 60 | 61 | /** 62 | * Init demo settings 63 | */ 64 | private void initDemoSettings(View view) { 65 | 66 | final DemoActivity demoActivity = (DemoActivity) getActivity(); 67 | final SwitchCompat switchColored = view.findViewById(R.id.fragment_demo_switch_colored); 68 | final SwitchCompat switchFiveItems = view.findViewById(R.id.fragment_demo_switch_five_items); 69 | final SwitchCompat showHideBottomNavigation = view.findViewById(R.id.fragment_demo_show_hide); 70 | final SwitchCompat showSelectedBackground = view.findViewById(R.id.fragment_demo_selected_background); 71 | final Spinner spinnerTitleState = view.findViewById(R.id.fragment_demo_title_state); 72 | final SwitchCompat switchTranslucentNavigation = view.findViewById(R.id.fragment_demo_translucent_navigation); 73 | 74 | switchColored.setChecked(demoActivity.isBottomNavigationColored()); 75 | switchFiveItems.setChecked(demoActivity.getBottomNavigationNbItems() == 5); 76 | switchTranslucentNavigation.setChecked(getActivity() 77 | .getSharedPreferences("shared", Context.MODE_PRIVATE) 78 | .getBoolean("translucentNavigation", false)); 79 | switchTranslucentNavigation.setVisibility( 80 | Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? View.VISIBLE : View.GONE); 81 | 82 | switchTranslucentNavigation.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 83 | @Override 84 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 85 | getActivity() 86 | .getSharedPreferences("shared", Context.MODE_PRIVATE) 87 | .edit() 88 | .putBoolean("translucentNavigation", isChecked) 89 | .apply(); 90 | demoActivity.reload(); 91 | } 92 | }); 93 | switchColored.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 94 | @Override 95 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 96 | demoActivity.updateBottomNavigationColor(isChecked); 97 | } 98 | }); 99 | switchFiveItems.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 100 | @Override 101 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 102 | demoActivity.updateBottomNavigationItems(isChecked); 103 | } 104 | }); 105 | showHideBottomNavigation.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 106 | @Override 107 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 108 | demoActivity.showOrHideBottomNavigation(isChecked); 109 | } 110 | }); 111 | showSelectedBackground.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 112 | @Override 113 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 114 | demoActivity.updateSelectedBackgroundVisibility(isChecked); 115 | } 116 | }); 117 | final List titleStates = new ArrayList<>(); 118 | for (AHBottomNavigation.TitleState titleState : AHBottomNavigation.TitleState.values()) { 119 | titleStates.add(titleState.toString()); 120 | } 121 | ArrayAdapter spinnerAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_item, titleStates); 122 | spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 123 | spinnerTitleState.setAdapter(spinnerAdapter); 124 | spinnerTitleState.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { 125 | @Override 126 | public void onItemSelected(AdapterView parent, View view, int position, long id) { 127 | AHBottomNavigation.TitleState titleState = AHBottomNavigation.TitleState.valueOf(titleStates.get(position)); 128 | demoActivity.setTitleState(titleState); 129 | } 130 | 131 | @Override 132 | public void onNothingSelected(AdapterView parent) { 133 | // do nothing 134 | } 135 | }); 136 | } 137 | 138 | /** 139 | * Init the fragment 140 | */ 141 | private void initDemoList(View view) { 142 | 143 | fragmentContainer = view.findViewById(R.id.fragment_container); 144 | recyclerView = view.findViewById(R.id.fragment_demo_recycler_view); 145 | recyclerView.setHasFixedSize(true); 146 | layoutManager = new LinearLayoutManager(getActivity()); 147 | recyclerView.setLayoutManager(layoutManager); 148 | 149 | ArrayList itemsData = new ArrayList<>(); 150 | for (int i = 0; i < 50; i++) { 151 | itemsData.add("Fragment " + getArguments().getInt("index", -1) + " / Item " + i); 152 | } 153 | 154 | DemoAdapter adapter = new DemoAdapter(itemsData); 155 | recyclerView.setAdapter(adapter); 156 | } 157 | 158 | /** 159 | * Refresh 160 | */ 161 | public void refresh() { 162 | if (getArguments().getInt("index", 0) > 0 && recyclerView != null) { 163 | recyclerView.smoothScrollToPosition(0); 164 | } 165 | } 166 | 167 | /** 168 | * Called when a fragment will be displayed 169 | */ 170 | public void willBeDisplayed() { 171 | // Do what you want here, for example animate the content 172 | if (fragmentContainer != null) { 173 | Animation fadeIn = AnimationUtils.loadAnimation(getActivity(), R.anim.fade_in); 174 | fragmentContainer.startAnimation(fadeIn); 175 | } 176 | } 177 | 178 | /** 179 | * Called when a fragment will be hidden 180 | */ 181 | public void willBeHidden() { 182 | if (fragmentContainer != null) { 183 | Animation fadeOut = AnimationUtils.loadAnimation(getActivity(), R.anim.fade_out); 184 | fragmentContainer.startAnimation(fadeOut); 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /demo/src/main/java/com/aurelhubert/ahbottomnavigation/demo/DemoViewPagerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.aurelhubert.ahbottomnavigation.demo; 2 | 3 | import androidx.fragment.app.FragmentManager; 4 | import androidx.fragment.app.FragmentPagerAdapter; 5 | import android.view.ViewGroup; 6 | 7 | import java.util.ArrayList; 8 | 9 | /** 10 | * 11 | */ 12 | public class DemoViewPagerAdapter extends FragmentPagerAdapter { 13 | 14 | private ArrayList fragments = new ArrayList<>(); 15 | private DemoFragment currentFragment; 16 | 17 | public DemoViewPagerAdapter(FragmentManager fm) { 18 | super(fm); 19 | 20 | fragments.clear(); 21 | fragments.add(DemoFragment.newInstance(0)); 22 | fragments.add(DemoFragment.newInstance(1)); 23 | fragments.add(DemoFragment.newInstance(2)); 24 | fragments.add(DemoFragment.newInstance(3)); 25 | fragments.add(DemoFragment.newInstance(4)); 26 | } 27 | 28 | @Override 29 | public DemoFragment getItem(int position) { 30 | return fragments.get(position); 31 | } 32 | 33 | @Override 34 | public int getCount() { 35 | return fragments.size(); 36 | } 37 | 38 | @Override 39 | public void setPrimaryItem(ViewGroup container, int position, Object object) { 40 | if (getCurrentFragment() != object) { 41 | currentFragment = ((DemoFragment) object); 42 | } 43 | super.setPrimaryItem(container, position, object); 44 | } 45 | 46 | /** 47 | * Get the current fragment 48 | */ 49 | public DemoFragment getCurrentFragment() { 50 | return currentFragment; 51 | } 52 | } -------------------------------------------------------------------------------- /demo/src/main/res/anim/fade_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 16 | 17 | -------------------------------------------------------------------------------- /demo/src/main/res/anim/fade_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /demo/src/main/res/drawable-hdpi/ic_content_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelhubert/ahbottomnavigation/9241f2a1d9f2321d9fcc1f98e2cdcbea5ddd0ba4/demo/src/main/res/drawable-hdpi/ic_content_add.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-hdpi/ic_maps_local_attraction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelhubert/ahbottomnavigation/9241f2a1d9f2321d9fcc1f98e2cdcbea5ddd0ba4/demo/src/main/res/drawable-hdpi/ic_maps_local_attraction.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-hdpi/ic_maps_local_bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelhubert/ahbottomnavigation/9241f2a1d9f2321d9fcc1f98e2cdcbea5ddd0ba4/demo/src/main/res/drawable-hdpi/ic_maps_local_bar.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-hdpi/ic_maps_local_restaurant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelhubert/ahbottomnavigation/9241f2a1d9f2321d9fcc1f98e2cdcbea5ddd0ba4/demo/src/main/res/drawable-hdpi/ic_maps_local_restaurant.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-hdpi/ic_maps_place.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelhubert/ahbottomnavigation/9241f2a1d9f2321d9fcc1f98e2cdcbea5ddd0ba4/demo/src/main/res/drawable-hdpi/ic_maps_place.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-mdpi/ic_content_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelhubert/ahbottomnavigation/9241f2a1d9f2321d9fcc1f98e2cdcbea5ddd0ba4/demo/src/main/res/drawable-mdpi/ic_content_add.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-mdpi/ic_maps_local_attraction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelhubert/ahbottomnavigation/9241f2a1d9f2321d9fcc1f98e2cdcbea5ddd0ba4/demo/src/main/res/drawable-mdpi/ic_maps_local_attraction.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-mdpi/ic_maps_local_bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelhubert/ahbottomnavigation/9241f2a1d9f2321d9fcc1f98e2cdcbea5ddd0ba4/demo/src/main/res/drawable-mdpi/ic_maps_local_bar.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-mdpi/ic_maps_local_restaurant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelhubert/ahbottomnavigation/9241f2a1d9f2321d9fcc1f98e2cdcbea5ddd0ba4/demo/src/main/res/drawable-mdpi/ic_maps_local_restaurant.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-mdpi/ic_maps_place.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelhubert/ahbottomnavigation/9241f2a1d9f2321d9fcc1f98e2cdcbea5ddd0ba4/demo/src/main/res/drawable-mdpi/ic_maps_place.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-xhdpi/ic_content_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelhubert/ahbottomnavigation/9241f2a1d9f2321d9fcc1f98e2cdcbea5ddd0ba4/demo/src/main/res/drawable-xhdpi/ic_content_add.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-xhdpi/ic_maps_local_attraction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelhubert/ahbottomnavigation/9241f2a1d9f2321d9fcc1f98e2cdcbea5ddd0ba4/demo/src/main/res/drawable-xhdpi/ic_maps_local_attraction.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-xhdpi/ic_maps_local_bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelhubert/ahbottomnavigation/9241f2a1d9f2321d9fcc1f98e2cdcbea5ddd0ba4/demo/src/main/res/drawable-xhdpi/ic_maps_local_bar.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-xhdpi/ic_maps_local_restaurant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelhubert/ahbottomnavigation/9241f2a1d9f2321d9fcc1f98e2cdcbea5ddd0ba4/demo/src/main/res/drawable-xhdpi/ic_maps_local_restaurant.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-xhdpi/ic_maps_place.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelhubert/ahbottomnavigation/9241f2a1d9f2321d9fcc1f98e2cdcbea5ddd0ba4/demo/src/main/res/drawable-xhdpi/ic_maps_place.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-xxhdpi/ic_content_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelhubert/ahbottomnavigation/9241f2a1d9f2321d9fcc1f98e2cdcbea5ddd0ba4/demo/src/main/res/drawable-xxhdpi/ic_content_add.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-xxhdpi/ic_maps_local_attraction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelhubert/ahbottomnavigation/9241f2a1d9f2321d9fcc1f98e2cdcbea5ddd0ba4/demo/src/main/res/drawable-xxhdpi/ic_maps_local_attraction.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-xxhdpi/ic_maps_local_bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelhubert/ahbottomnavigation/9241f2a1d9f2321d9fcc1f98e2cdcbea5ddd0ba4/demo/src/main/res/drawable-xxhdpi/ic_maps_local_bar.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-xxhdpi/ic_maps_local_restaurant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelhubert/ahbottomnavigation/9241f2a1d9f2321d9fcc1f98e2cdcbea5ddd0ba4/demo/src/main/res/drawable-xxhdpi/ic_maps_local_restaurant.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-xxhdpi/ic_maps_place.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelhubert/ahbottomnavigation/9241f2a1d9f2321d9fcc1f98e2cdcbea5ddd0ba4/demo/src/main/res/drawable-xxhdpi/ic_maps_place.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-xxxhdpi/ic_content_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelhubert/ahbottomnavigation/9241f2a1d9f2321d9fcc1f98e2cdcbea5ddd0ba4/demo/src/main/res/drawable-xxxhdpi/ic_content_add.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-xxxhdpi/ic_maps_local_attraction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelhubert/ahbottomnavigation/9241f2a1d9f2321d9fcc1f98e2cdcbea5ddd0ba4/demo/src/main/res/drawable-xxxhdpi/ic_maps_local_attraction.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-xxxhdpi/ic_maps_local_bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelhubert/ahbottomnavigation/9241f2a1d9f2321d9fcc1f98e2cdcbea5ddd0ba4/demo/src/main/res/drawable-xxxhdpi/ic_maps_local_bar.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-xxxhdpi/ic_maps_local_restaurant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelhubert/ahbottomnavigation/9241f2a1d9f2321d9fcc1f98e2cdcbea5ddd0ba4/demo/src/main/res/drawable-xxxhdpi/ic_maps_local_restaurant.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-xxxhdpi/ic_maps_place.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelhubert/ahbottomnavigation/9241f2a1d9f2321d9fcc1f98e2cdcbea5ddd0ba4/demo/src/main/res/drawable-xxxhdpi/ic_maps_place.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable/bottom_navigation_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /demo/src/main/res/drawable/ic_apps_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /demo/src/main/res/layout/activity_home.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | 22 | 23 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /demo/src/main/res/layout/fragment_demo_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /demo/src/main/res/layout/fragment_demo_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | 17 | 18 | 23 | 24 | 29 | 30 | 35 | 36 | 44 | 45 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /demo/src/main/res/layout/layout_item_demo.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /demo/src/main/res/menu/bottom_navigation_menu_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 10 | 11 | 14 | 15 | -------------------------------------------------------------------------------- /demo/src/main/res/menu/bottom_navigation_menu_5.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 10 | 11 | 14 | 15 | 18 | 19 | 22 | 23 | -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelhubert/ahbottomnavigation/9241f2a1d9f2321d9fcc1f98e2cdcbea5ddd0ba4/demo/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelhubert/ahbottomnavigation/9241f2a1d9f2321d9fcc1f98e2cdcbea5ddd0ba4/demo/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelhubert/ahbottomnavigation/9241f2a1d9f2321d9fcc1f98e2cdcbea5ddd0ba4/demo/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelhubert/ahbottomnavigation/9241f2a1d9f2321d9fcc1f98e2cdcbea5ddd0ba4/demo/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelhubert/ahbottomnavigation/9241f2a1d9f2321d9fcc1f98e2cdcbea5ddd0ba4/demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /demo/src/main/res/values-v19/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /demo/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /demo/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /demo/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | #747474 7 | #FFFFFF 8 | #50FFFFFF 9 | 10 | #455C65 11 | #00886A 12 | #8B6B62 13 | #6C4A42 14 | #F63D2B 15 | 16 | #ffcd33 17 | #3E4757 18 | 19 | 20 | #455C65 21 | #00886A 22 | #8B6B62 23 | #6C4A42 24 | #F63D2B 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /demo/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 16dp 3 | 16dp 4 | 5 | 8dp 6 | 7 | 104dp 8 | 168dp 9 | 56dp 10 | 24dp 11 | 12 | 6dp 13 | 8dp 14 | 7dp 15 | 12dp 16 | 12dp 17 | 18 | 14sp 19 | 12sp 20 | 21 | 22 | 96dp 23 | 168dp 24 | 64dp 25 | 96dp 26 | 27 | 6dp 28 | 16dp 29 | 16dp 30 | 31 | 10dp 32 | 33 | 34 | -------------------------------------------------------------------------------- /demo/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | AHBottomNavigation 3 | Label 1 4 | Label 2 5 | Label 3 6 | Label 4 7 | Label 5 8 | 9 | Menu 1 10 | Menu 2 11 | Menu 3 12 | Menu 4 13 | Menu 5 14 | 15 | 16 | -------------------------------------------------------------------------------- /demo/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /demo/src/main/res/xml/bottom_fav.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 19 | -------------------------------------------------------------------------------- /demo1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelhubert/ahbottomnavigation/9241f2a1d9f2321d9fcc1f98e2cdcbea5ddd0ba4/demo1.gif -------------------------------------------------------------------------------- /demo2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelhubert/ahbottomnavigation/9241f2a1d9f2321d9fcc1f98e2cdcbea5ddd0ba4/demo2.gif -------------------------------------------------------------------------------- /demo3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelhubert/ahbottomnavigation/9241f2a1d9f2321d9fcc1f98e2cdcbea5ddd0ba4/demo3.gif -------------------------------------------------------------------------------- /demo4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelhubert/ahbottomnavigation/9241f2a1d9f2321d9fcc1f98e2cdcbea5ddd0ba4/demo4.gif -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | android.enableJetifier=true 13 | android.useAndroidX=true 14 | org.gradle.jvmargs=-Xmx1536m 15 | 16 | # When configured, Gradle will run in incubating parallel mode. 17 | # This option should only be used with decoupled projects. More details, visit 18 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 19 | # org.gradle.parallel=true 20 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelhubert/ahbottomnavigation/9241f2a1d9f2321d9fcc1f98e2cdcbea5ddd0ba4/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Nov 14 11:14:30 BRST 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':demo', ':ahbottomnavigation' 2 | --------------------------------------------------------------------------------