├── .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 |
--------------------------------------------------------------------------------
/demo/src/main/res/menu/bottom_navigation_menu_5.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------