Repository: 10clouds/FluidBottomNavigation-android
Branch: master
Commit: 5689408c71e3
Files: 54
Total size: 89.2 KB
Directory structure:
gitextract_gb8nzjgx/
├── .gitignore
├── LICENSE.md
├── README.md
├── app/
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── com/
│ │ └── tenclouds/
│ │ └── fluidbottomnavigationexample/
│ │ └── MainActivity.kt
│ └── res/
│ ├── drawable/
│ │ ├── background.xml
│ │ ├── ic_calendar.xml
│ │ ├── ic_chat.xml
│ │ ├── ic_inbox.xml
│ │ ├── ic_news.xml
│ │ └── ic_profile.xml
│ ├── layout/
│ │ └── activity_main.xml
│ └── values/
│ ├── colors.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── fluidbottomnavigation/
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ ├── script/
│ │ └── version.gradle
│ └── src/
│ ├── main/
│ │ ├── AndroidManifest.xml
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── tenclouds/
│ │ │ └── fluidbottomnavigation/
│ │ │ ├── Consts.kt
│ │ │ ├── FluidBottomNavigation.kt
│ │ │ ├── FluidBottomNavigationAnimations.kt
│ │ │ ├── FluidBottomNavigationItem.kt
│ │ │ ├── extension/
│ │ │ │ ├── AnimatorExtensions.kt
│ │ │ │ ├── InterpolatorExtensions.kt
│ │ │ │ └── ViewExtensions.kt
│ │ │ ├── listener/
│ │ │ │ └── OnTabSelectedListener.kt
│ │ │ └── view/
│ │ │ ├── AnimatedView.kt
│ │ │ ├── CircleView.kt
│ │ │ ├── IconView.kt
│ │ │ ├── RectangleView.kt
│ │ │ ├── TitleView.kt
│ │ │ └── TopContainerView.kt
│ │ └── res/
│ │ ├── drawable/
│ │ │ ├── circle.xml
│ │ │ ├── rectangle.xml
│ │ │ └── top.xml
│ │ ├── layout/
│ │ │ └── item.xml
│ │ └── values/
│ │ ├── attrs.xml
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ └── strings.xml
│ └── test/
│ └── java/
│ └── com/
│ └── tenclouds/
│ └── fluidbottomnavigation/
│ ├── FluidBottomNavigationTest.kt
│ └── util/
│ └── ShadowResourcesCompat.java
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# Built application files
*.apk
*.ap_
# Files for the ART/Dalvik VM
*.dex
# Java class files
*.class
# Generated files
bin/
gen/
out/
# Gradle files
.gradle/
build/
# Local configuration file (sdk path, etc)
local.properties
# Proguard folder generated by Eclipse
proguard/
# Log Files
*.log
# Android Studio Navigation editor temp files
.navigation/
# Android Studio captures folder
captures/
# IntelliJ
*.iml
.idea/
# Keystore files
# Uncomment the following line if you do not want to check your keystore files in.
#*.jks
# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
# Google Services (e.g. APIs or Firebase)
google-services.json
# Freeline
freeline.py
freeline/
freeline_project_description.json
# fastlane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
fastlane/readme.md
# DS Store
*.DS_Store
================================================
FILE: LICENSE.md
================================================
MIT License
Copyright (c) 2018 10Clouds
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Fluid Bottom Navigation [](https://app.bitrise.io/app/339f26db491c854d) [](https://bintray.com/10clouds-android/fluidbottomnavigation/fluid-bottom-navigation)
## Sample
<p align="center">
<img src="static/sample.gif" alt="Sample Fluid Bottom Navigation"/>
</p>
## Installation
Use the JitPack package repository.
Add `jitpack.io` repository to your root `build.gradle` file:
```groovy
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
```
Next add library to your project `build.gradle` file:
**Gradle:**
```groovy
implementation 'com.github.10clouds:FluidBottomNavigation-android:{last_release_version}'
```
## Usage
Place **FluidBottomNavigation** in your layout:
```xml
<com.tenclouds.fluidbottomnavigation.FluidBottomNavigation
android:id="@+id/fluidBottomNavigation"
android:layout_height="wrap_content"
android:layout_width="0dp" />
```
then set navigation items to component:
```kotlin
fluidBottomNavigation.items =
listOf(
FluidBottomNavigationItem(
getString(R.string.news),
ContextCompat.getDrawable(this, R.drawable.ic_news)),
FluidBottomNavigationItem(
getString(R.string.inbox),
ContextCompat.getDrawable(this, R.drawable.ic_inbox)),
FluidBottomNavigationItem(
getString(R.string.calendar),
ContextCompat.getDrawable(this, R.drawable.ic_calendar)),
FluidBottomNavigationItem(
getString(R.string.chat),
ContextCompat.getDrawable(this, R.drawable.ic_chat)),
FluidBottomNavigationItem(
getString(R.string.profile),
ContextCompat.getDrawable(this, R.drawable.ic_profile)))
```
**Application with example is in [app folder](https://github.com/10clouds/FluidBottomNavigation-android/tree/master/app)**
## Customization
You can customize component from XML layout file, using attributes:
```
app:accentColor="@color/accentColor"
app:backColor="@color/backColor"
app:iconColor="@color/iconColor"
app:iconSelectedColor="@color/iconSelectedColor"
app:textColor="@color/textColor"
```
or from Java/Kotlin code:
```kotlin
fluidBottomNavigation.accentColor = ContextCompat.getColor(this, R.color.accentColor)
fluidBottomNavigation.backColor = ContextCompat.getColor(this, R.color.backColor)
fluidBottomNavigation.textColor = ContextCompat.getColor(this, R.color.textColor)
fluidBottomNavigation.iconColor = ContextCompat.getColor(this, R.color.iconColor)
fluidBottomNavigation.iconSelectedColor = ContextCompat.getColor(this, R.color.iconSelectedColor)
```
---
Library made by **[Jakub Jodełka](https://github.com/jakubjodelka)**
================================================
FILE: app/.gitignore
================================================
/build
================================================
FILE: app/build.gradle
================================================
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 29
defaultConfig {
applicationId "com.tenclouds.fluidbottomnavigationexample"
minSdkVersion 15
targetSdkVersion 29
versionCode 1
versionName "1.0"
vectorDrawables.useSupportLibrary = true
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(":fluidbottomnavigation")
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "androidx.core:core:$androidx_version"
implementation "androidx.core:core-ktx:$androidx_version"
implementation "androidx.appcompat:appcompat:$androidx_version"
implementation "androidx.constraintlayout:constraintlayout:$constraint_version"
}
================================================
FILE: app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
================================================
FILE: app/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.tenclouds.fluidbottomnavigationexample">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
================================================
FILE: app/src/main/java/com/tenclouds/fluidbottomnavigationexample/MainActivity.kt
================================================
package com.tenclouds.fluidbottomnavigationexample
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import com.tenclouds.fluidbottomnavigation.FluidBottomNavigationItem
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
fluidBottomNavigation.accentColor = ContextCompat.getColor(this, R.color.colorPrimaryDark)
fluidBottomNavigation.backColor = ContextCompat.getColor(this, R.color.colorPrimaryDark)
fluidBottomNavigation.textColor = ContextCompat.getColor(this, R.color.colorPrimaryDark)
fluidBottomNavigation.iconColor = ContextCompat.getColor(this, R.color.colorPrimary)
fluidBottomNavigation.iconSelectedColor = ContextCompat.getColor(this, R.color.iconSelectedColor)
fluidBottomNavigation.items =
listOf(
FluidBottomNavigationItem(
getString(R.string.news),
ContextCompat.getDrawable(this, R.drawable.ic_news)),
FluidBottomNavigationItem(
getString(R.string.inbox),
ContextCompat.getDrawable(this, R.drawable.ic_inbox)),
FluidBottomNavigationItem(
getString(R.string.calendar),
ContextCompat.getDrawable(this, R.drawable.ic_calendar)),
FluidBottomNavigationItem(
getString(R.string.chat),
ContextCompat.getDrawable(this, R.drawable.ic_chat)),
FluidBottomNavigationItem(
getString(R.string.profile),
ContextCompat.getDrawable(this, R.drawable.ic_profile)))
}
}
================================================
FILE: app/src/main/res/drawable/background.xml
================================================
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
<gradient
android:angle="90"
android:endColor="@color/colorPrimary"
android:startColor="@color/colorPrimaryDark"
android:type="linear" />
</shape>
================================================
FILE: app/src/main/res/drawable/ic_calendar.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<group
android:translateX="-960.000000"
android:translateY="-789.000000">
<path
android:fillColor="#ABAEFF"
android:strokeWidth="1"
android:pathData="M963.333333,809 L976.666667,809 C978.507616,809 980,807.507616 980,805.666667 L980,792.333333 C980,790.492384 978.507616,789 976.666667,789 L963.333333,789 C961.492384,789 960,790.492384 960,792.333333 L960,805.666667 C960,807.507616 961.492384,809 963.333333,809 Z M976.666667,807.333333 L963.333333,807.333333 C962.412859,807.333333 961.666667,806.587141 961.666667,805.666667 L961.666667,795.666667 L978.333333,795.666667 L978.333333,805.666667 C978.333333,806.587141 977.587141,807.333333 976.666667,807.333333 Z M963.333333,790.666667 L976.666667,790.666667 C977.587141,790.666667 978.333333,791.412859 978.333333,792.333333 L978.333333,794 L961.666667,794 L961.666667,792.333333 C961.666667,791.412859 962.412859,790.666667 963.333333,790.666667 Z M965,802.333333 C964.539763,802.333333 964.166667,801.960237 964.166667,801.5 C964.166667,801.039763 964.539763,800.666667 965,800.666667 C965.460237,800.666667 965.833333,801.039763 965.833333,801.5 C965.833333,801.960237 965.460237,802.333333 965,802.333333 Z M965,805.666667 C964.539763,805.666667 964.166667,805.293571 964.166667,804.833333 C964.166667,804.373096 964.539763,804 965,804 C965.460237,804 965.833333,804.373096 965.833333,804.833333 C965.833333,805.293571 965.460237,805.666667 965,805.666667 Z M968.333333,802.333333 C967.873096,802.333333 967.5,801.960237 967.5,801.5 C967.5,801.039763 967.873096,800.666667 968.333333,800.666667 C968.793571,800.666667 969.166667,801.039763 969.166667,801.5 C969.166667,801.960237 968.793571,802.333333 968.333333,802.333333 Z M968.333333,805.666667 C967.873096,805.666667 967.5,805.293571 967.5,804.833333 C967.5,804.373096 967.873096,804 968.333333,804 C968.793571,804 969.166667,804.373096 969.166667,804.833333 C969.166667,805.293571 968.793571,805.666667 968.333333,805.666667 Z M971.666667,802.333333 C971.206429,802.333333 970.833333,801.960237 970.833333,801.5 C970.833333,801.039763 971.206429,800.666667 971.666667,800.666667 C972.126904,800.666667 972.5,801.039763 972.5,801.5 C972.5,801.960237 972.126904,802.333333 971.666667,802.333333 Z M971.666667,805.666667 C971.206429,805.666667 970.833333,805.293571 970.833333,804.833333 C970.833333,804.373096 971.206429,804 971.666667,804 C972.126904,804 972.5,804.373096 972.5,804.833333 C972.5,805.293571 972.126904,805.666667 971.666667,805.666667 Z M975,802.333333 C974.539763,802.333333 974.166667,801.960237 974.166667,801.5 C974.166667,801.039763 974.539763,800.666667 975,800.666667 C975.460237,800.666667 975.833333,801.039763 975.833333,801.5 C975.833333,801.960237 975.460237,802.333333 975,802.333333 Z M968.333333,799 C967.873096,799 967.5,798.626904 967.5,798.166667 C967.5,797.706429 967.873096,797.333333 968.333333,797.333333 C968.793571,797.333333 969.166667,797.706429 969.166667,798.166667 C969.166667,798.626904 968.793571,799 968.333333,799 Z M971.666667,799 C971.206429,799 970.833333,798.626904 970.833333,798.166667 C970.833333,797.706429 971.206429,797.333333 971.666667,797.333333 C972.126904,797.333333 972.5,797.706429 972.5,798.166667 C972.5,798.626904 972.126904,799 971.666667,799 Z M975,799 C974.539763,799 974.166667,798.626904 974.166667,798.166667 C974.166667,797.706429 974.539763,797.333333 975,797.333333 C975.460237,797.333333 975.833333,797.706429 975.833333,798.166667 C975.833333,798.626904 975.460237,799 975,799 Z" />
</group>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_chat.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<group
android:translateX="-1029.000000"
android:translateY="-788.000000">
<path
android:fillColor="#ABAEFF"
android:strokeWidth="1"
android:pathData="M1038.79705,788.000005 C1035.29837,788.002135 1032.06643,789.870068 1030.31801,792.900546 C1028.56959,795.931024 1028.57016,799.663938 1030.3195,802.693883 L1029.04195,806.518373 C1028.94412,806.811733 1029.02045,807.135189 1029.23912,807.353857 C1029.45779,807.572525 1029.78124,807.648858 1030.0746,807.551026 L1033.89909,806.273475 C1037.46539,808.33826 1041.94259,807.950333 1045.1006,805.302919 C1048.25861,802.655506 1049.42209,798.314753 1048.01152,794.44279 C1046.60094,790.570827 1042.91795,787.995671 1038.79705,788.000005 Z M1038.79705,805.959189 C1037.25458,805.956013 1035.74457,805.515889 1034.44195,804.689801 C1034.31067,804.608508 1034.15962,804.564747 1034.00521,804.563271 C1033.91666,804.564539 1033.82872,804.57828 1033.74399,804.604087 L1031.10726,805.48572 L1031.98889,802.848985 C1032.06712,802.61532 1032.03562,802.358818 1031.90317,802.151026 C1029.91565,799.000045 1030.31356,794.903204 1032.87028,792.193579 C1035.42701,789.483954 1039.49386,788.849034 1042.75485,790.650395 C1046.01583,792.451756 1047.64356,796.232353 1046.71108,799.839207 C1045.7786,803.446061 1042.52249,805.964034 1038.79705,805.959189 Z M1035.53175,795.346944 C1035.98259,795.346944 1036.34807,795.712426 1036.34807,796.163271 C1036.34807,796.614115 1036.71355,796.979597 1037.1644,796.979597 C1037.61524,796.979597 1037.98073,796.614115 1037.98073,796.163271 C1037.98073,794.810737 1036.88428,793.714291 1035.53175,793.714291 C1034.17921,793.714291 1033.08277,794.810737 1033.08277,796.163271 C1033.08277,796.614115 1033.44825,796.979597 1033.89909,796.979597 C1034.34994,796.979597 1034.71542,796.614115 1034.71542,796.163271 C1034.71542,795.712426 1035.0809,795.346944 1035.53175,795.346944 Z M1042.06236,793.714291 C1040.70982,793.714291 1039.61338,794.810737 1039.61338,796.163271 C1039.61338,796.614115 1039.97886,796.979597 1040.4297,796.979597 C1040.88055,796.979597 1041.24603,796.614115 1041.24603,796.163271 C1041.24603,795.712426 1041.61151,795.346944 1042.06236,795.346944 C1042.5132,795.346944 1042.87868,795.712426 1042.87868,796.163271 C1042.87868,796.614115 1043.24417,796.979597 1043.69501,796.979597 C1044.14586,796.979597 1044.51134,796.614115 1044.51134,796.163271 C1044.51134,794.810737 1043.41489,793.714291 1042.06236,793.714291 Z M1041.48685,799.669393 L1041.00521,800.146944 C1039.76902,801.325565 1037.82509,801.325565 1036.58889,800.146944 L1036.10726,799.669393 C1035.78337,799.392024 1035.30057,799.410673 1034.99904,799.712199 C1034.69752,800.013725 1034.67887,800.496528 1034.95624,800.820414 L1035.43379,801.302046 C1037.29191,803.157977 1040.3022,803.157977 1042.16032,801.302046 L1042.63787,800.820414 C1042.91524,800.496528 1042.89659,800.013725 1042.59506,799.712199 C1042.29354,799.410673 1041.81073,799.392024 1041.48685,799.669393 Z" />
</group>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_inbox.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<group
android:translateX="-892.000000"
android:translateY="-789.000000">
<path
android:fillColor="#ABAEFF"
android:strokeWidth="1"
android:pathData="M909.5,793.166667 L908.666667,793.166667 L908.666667,791.5 C908.666667,790.119288 907.547379,789 906.166667,789 L897.833333,789 C896.452621,789 895.333333,790.119288 895.333333,791.5 L895.333333,793.166667 L894.5,793.166667 C893.119288,793.166667 892,794.285955 892,795.666667 L892,806.5 C892,807.880712 893.119288,809 894.5,809 L909.5,809 C910.880712,809 912,807.880712 912,806.5 L912,795.666667 C912,794.285955 910.880712,793.166667 909.5,793.166667 Z M909.5,794.833333 C909.960237,794.833333 910.333333,795.206429 910.333333,795.666667 L910.333333,800.666667 L908.666667,800.666667 L908.666667,794.833333 L909.5,794.833333 Z M897,791.5 C897,791.039763 897.373096,790.666667 897.833333,790.666667 L906.166667,790.666667 C906.626904,790.666667 907,791.039763 907,791.5 L907,800.666667 L905.333333,800.666667 C904.973966,800.66634 904.654867,800.896427 904.541667,801.2375 L903.9,803.166667 L900.1,803.166667 L899.458333,801.2375 C899.345133,800.896427 899.026034,800.66634 898.666667,800.666667 L897,800.666667 L897,791.5 Z M894.5,794.833333 L895.333333,794.833333 L895.333333,800.666667 L893.666667,800.666667 L893.666667,795.666667 C893.666667,795.206429 894.039763,794.833333 894.5,794.833333 Z M909.5,807.333333 L894.5,807.333333 C894.039763,807.333333 893.666667,806.960237 893.666667,806.5 L893.666667,802.333333 L898.066667,802.333333 L898.708333,804.2625 C898.821534,804.603573 899.140632,804.83366 899.5,804.833333 L904.5,804.833333 C904.859368,804.83366 905.178466,804.603573 905.291667,804.2625 L905.933333,802.333333 L910.333333,802.333333 L910.333333,806.5 C910.333333,806.960237 909.960237,807.333333 909.5,807.333333 Z M899.5,794.833333 C899.039763,794.833333 898.666667,794.460237 898.666667,794 C898.666667,793.539763 899.039763,793.166667 899.5,793.166667 L904.5,793.166667 C904.960237,793.166667 905.333333,793.539763 905.333333,794 C905.333333,794.460237 904.960237,794.833333 904.5,794.833333 L899.5,794.833333 Z M899.5,798.166667 C899.039763,798.166667 898.666667,797.793571 898.666667,797.333333 C898.666667,796.873096 899.039763,796.5 899.5,796.5 L904.5,796.5 C904.960237,796.5 905.333333,796.873096 905.333333,797.333333 C905.333333,797.793571 904.960237,798.166667 904.5,798.166667 L899.5,798.166667 Z" />
</group>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_news.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="22dp"
android:height="20dp"
android:viewportWidth="22"
android:viewportHeight="20">
<group
android:translateX="-814.000000"
android:translateY="-754.000000">
<path
android:fillColor="#ABAEFF"
android:strokeWidth="1"
android:pathData="M817.181818,774 L832.636364,774 C834.409091,774 835.818182,772.590909 835.818182,770.818182 L835.818182,755.818182 C835.818182,754.818182 835,754 834,754 L820.363636,754 C819.363636,754 818.545455,754.818182 818.545455,755.818182 L818.545455,760.363636 L815.818182,760.363636 C814.818182,760.363636 814,761.181818 814,762.181818 L814,770.818182 C814,772.590909 815.409091,774 817.181818,774 Z M815.818182,770.818182 L815.818182,762.181818 L818.545455,762.181818 L818.545455,770.818182 C818.545455,771.590909 817.954545,772.181818 817.181818,772.181818 C816.409091,772.181818 815.818182,771.590909 815.818182,770.818182 Z M834,770.818182 C834,771.590909 833.409091,772.181818 832.636364,772.181818 L820.045455,772.181818 C820.272727,771.772727 820.363636,771.272727 820.363636,770.818182 L820.363636,755.818182 L834,755.818182 L834,770.818182 Z M823.090909,763.090909 L831.272727,763.090909 C831.772727,763.090909 832.181818,762.681818 832.181818,762.181818 L832.181818,758.545455 C832.181818,758.045455 831.772727,757.636364 831.272727,757.636364 L823.090909,757.636364 C822.590909,757.636364 822.181818,758.045455 822.181818,758.545455 L822.181818,762.181818 C822.181818,762.681818 822.590909,763.090909 823.090909,763.090909 Z M824,759.454545 L830.363636,759.454545 L830.363636,761.272727 L824,761.272727 L824,759.454545 Z M823.090909,766.727273 L831.272727,766.727273 C831.772727,766.727273 832.181818,766.318182 832.181818,765.818182 C832.181818,765.318182 831.772727,764.909091 831.272727,764.909091 L823.090909,764.909091 C822.590909,764.909091 822.181818,765.318182 822.181818,765.818182 C822.181818,766.318182 822.590909,766.727273 823.090909,766.727273 Z M823.090909,770.363636 L828.545455,770.363636 C829.045455,770.363636 829.454545,769.954545 829.454545,769.454545 C829.454545,768.954545 829.045455,768.545455 828.545455,768.545455 L823.090909,768.545455 C822.590909,768.545455 822.181818,768.954545 822.181818,769.454545 C822.181818,769.954545 822.590909,770.363636 823.090909,770.363636 Z" />
</group>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_profile.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<group
android:translateX="-1099.000000"
android:translateY="-788.000000">
<path
android:fillColor="#ABAEFF"
android:strokeWidth="1"
android:pathData="M1109,788 C1103.47715,788 1099,792.477153 1099,798 C1099,803.522847 1103.47715,808 1109,808 C1114.52285,808 1119,803.522847 1119,798 C1119,792.477153 1114.52285,788 1109,788 Z M1104.83333,805.204167 L1104.83333,804.666667 C1104.83333,802.36548 1106.69881,800.5 1109,800.5 C1111.30119,800.5 1113.16667,802.36548 1113.16667,804.666667 L1113.16667,805.204167 C1110.59278,806.709298 1107.40722,806.709298 1104.83333,805.204167 Z M1106.5,796.333333 C1106.5,794.952621 1107.61929,793.833333 1109,793.833333 C1110.38071,793.833333 1111.5,794.952621 1111.5,796.333333 C1111.5,797.714045 1110.38071,798.833333 1109,798.833333 C1107.61929,798.833333 1106.5,797.714045 1106.5,796.333333 Z M1114.7875,803.979167 C1114.55928,802.061761 1113.397,800.381536 1111.68333,799.491667 C1112.62016,798.709781 1113.16318,797.553571 1113.16667,796.333333 C1113.16667,794.032147 1111.30119,792.166667 1109,792.166667 C1106.69881,792.166667 1104.83333,794.032147 1104.83333,796.333333 C1104.83682,797.553571 1105.37984,798.709781 1106.31667,799.491667 C1104.603,800.381536 1103.44072,802.061761 1103.2125,803.979167 C1100.76814,801.619713 1100.0008,798.012624 1101.27312,794.862523 C1102.54544,791.712422 1105.60266,789.650061 1109,789.650061 C1112.39734,789.650061 1115.45456,791.712422 1116.72688,794.862523 C1117.9992,798.012624 1117.23186,801.619713 1114.7875,803.979167 Z" />
</group>
</vector>
================================================
FILE: app/src/main/res/layout/activity_main.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/container"
android:background="@drawable/background"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.tenclouds.fluidbottomnavigation.FluidBottomNavigation
android:id="@+id/fluidBottomNavigation"
android:layout_height="wrap_content"
android:layout_width="0dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:height="54dp"
app:accentColor="@color/colorPrimaryDark"
app:backColor="@color/backColor"
app:iconColor="@color/colorPrimary"
app:iconSelectedColor="@color/iconSelectedColor"
app:textColor="@color/colorPrimaryDark"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
================================================
FILE: app/src/main/res/values/colors.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#6167E6</color>
<color name="colorPrimaryDark">#3C42D5</color>
<color name="colorAccent">#FF4081</color>
</resources>
================================================
FILE: app/src/main/res/values/strings.xml
================================================
<resources>
<string name="app_name">Fluid Bottom Navigation Example</string>
<string name="news">News</string>
<string name="inbox">Inbox</string>
<string name="calendar">Calendar</string>
<string name="chat">Chat</string>
<string name="profile">Profile</string>
</resources>
================================================
FILE: app/src/main/res/values/styles.xml
================================================
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</resources>
================================================
FILE: build.gradle
================================================
buildscript {
ext.kotlin_version = '1.3.61'
ext.androidx_version = "1.1.0"
ext.constraint_version = '2.0.0-beta4'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
jcenter()
mavenCentral()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
================================================
FILE: fluidbottomnavigation/.gitignore
================================================
/build
================================================
FILE: fluidbottomnavigation/build.gradle
================================================
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply from: 'script/version.gradle'
android {
compileSdkVersion 29
defaultConfig {
minSdkVersion 15
targetSdkVersion 29
versionCode getLibraryVersionCode()
versionName getLibraryVersionName()
vectorDrawables.useSupportLibrary = true
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
minifyEnabled false
}
}
testOptions {
unitTests {
includeAndroidResources = true
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "androidx.core:core:$androidx_version"
implementation "androidx.core:core-ktx:$androidx_version"
implementation "androidx.appcompat:appcompat:$androidx_version"
implementation "androidx.constraintlayout:constraintlayout:$constraint_version"
testImplementation 'junit:junit:4.12'
testImplementation 'org.mockito:mockito-core:2.23.4'
testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.1.0"
testImplementation "org.robolectric:robolectric:4.3"
}
================================================
FILE: fluidbottomnavigation/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
================================================
FILE: fluidbottomnavigation/script/version.gradle
================================================
ext {
getLibraryVersionName = { ->
return "1.2"
}
getLibraryVersionCode = { ->
return 120
}
}
================================================
FILE: fluidbottomnavigation/src/main/AndroidManifest.xml
================================================
<manifest package="com.tenclouds.fluidbottomnavigation"/>
================================================
FILE: fluidbottomnavigation/src/main/java/com/tenclouds/fluidbottomnavigation/Consts.kt
================================================
package com.tenclouds.fluidbottomnavigation
internal const val DEFAULT_SELECTED_TAB_POSITION = 0
internal const val EXTRA_SELECTED_TAB_POSITION = "EXTRA_SELECTED_TAB_POSITION"
internal const val EXTRA_SELECTED_SUPER_STATE = "EXTRA_SELECTED_SUPER_STATE"
internal const val KEY_FRAME_IN_MS = ((1f / 24f) * 1000).toLong()
internal const val ITEMS_CLICKS_DEBOUNCE = 250L
================================================
FILE: fluidbottomnavigation/src/main/java/com/tenclouds/fluidbottomnavigation/FluidBottomNavigation.kt
================================================
package com.tenclouds.fluidbottomnavigation
import android.content.Context
import android.graphics.Typeface
import android.os.Build
import android.os.Bundle
import android.os.Parcelable
import android.os.SystemClock
import android.util.AttributeSet
import android.util.TypedValue
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.LinearLayout
import androidx.annotation.VisibleForTesting
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
import com.tenclouds.fluidbottomnavigation.extension.calculateHeight
import com.tenclouds.fluidbottomnavigation.extension.setTintColor
import com.tenclouds.fluidbottomnavigation.listener.OnTabSelectedListener
import kotlinx.android.synthetic.main.item.view.*
import kotlin.math.abs
class FluidBottomNavigation : FrameLayout {
constructor(context: Context) : super(context) {
init(null)
}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
init(attrs)
}
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
init(attrs)
}
var items: List<FluidBottomNavigationItem> = listOf()
set(value) {
check(value.size >= 3) { resources.getString(R.string.exception_not_enough_items) }
check(value.size <= 5) { resources.getString(R.string.exception_too_many_items) }
field = value
drawLayout()
}
var onTabSelectedListener: OnTabSelectedListener? = null
var accentColor: Int = ContextCompat.getColor(context, R.color.accentColor)
var backColor: Int = ContextCompat.getColor(context, R.color.backColor)
var iconColor: Int = ContextCompat.getColor(context, R.color.textColor)
var iconSelectedColor: Int = ContextCompat.getColor(context, R.color.iconColor)
var textColor: Int = ContextCompat.getColor(context, R.color.iconSelectedColor)
var textFont: Typeface = ResourcesCompat.getFont(context, R.font.rubik_regular)
?: Typeface.DEFAULT
val selectedTabItem: FluidBottomNavigationItem? get() = items[selectedTabPosition]
private var bottomBarHeight = resources.getDimension(R.dimen.fluidBottomNavigationHeightWithOpacity).toInt()
private var bottomBarWidth = 0
@VisibleForTesting
internal var isVisible = true
private var selectedTabPosition = DEFAULT_SELECTED_TAB_POSITION
set(value) {
field = value
onTabSelectedListener?.onTabSelected(value)
}
private var backgroundView: View? = null
private val views: MutableList<View> = ArrayList()
private var lastItemClickTimestamp = 0L
private fun init(attrs: AttributeSet?) {
getAttributesOrDefaultValues(attrs)
clipToPadding = false
layoutParams =
ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
bottomBarHeight)
}
fun selectTab(position: Int) {
if (position == selectedTabPosition) return
if (views.size > 0) {
views[selectedTabPosition].animateDeselectItemView()
views[position].animateSelectItemView()
}
this.selectedTabPosition = position
}
fun show() {
if (isVisible.not()) {
animateShow()
isVisible = true
}
}
fun hide() {
if (isVisible) {
animateHide()
isVisible = false
}
}
private fun drawLayout() {
bottomBarHeight = resources.getDimension(R.dimen.fluidBottomNavigationHeightWithOpacity).toInt()
backgroundView = View(context)
removeAllViews()
views.clear()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
calculateHeight(bottomBarHeight)
).let {
addView(backgroundView, it)
}
}
post { requestLayout() }
LinearLayout(context)
.apply {
orientation = LinearLayout.HORIZONTAL
gravity = Gravity.CENTER
}
.let { linearLayoutContainer ->
val layoutParams =
LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
bottomBarHeight,
Gravity.BOTTOM)
addView(linearLayoutContainer, layoutParams)
post {
bottomBarWidth = width
drawItemsViews(linearLayoutContainer)
}
}
}
private fun drawItemsViews(linearLayout: LinearLayout) {
if (bottomBarWidth == 0 || items.isEmpty()) {
return
}
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val itemViewHeight = resources.getDimension(R.dimen.fluidBottomNavigationHeightWithOpacity)
val itemViewWidth = (bottomBarWidth / items.size)
for (itemPosition in items.indices) {
inflater
.inflate(R.layout.item, this, false)
.let {
views.add(it)
linearLayout
.addView(it,
LayoutParams(
itemViewWidth,
itemViewHeight.toInt()))
}
drawItemView(itemPosition)
}
}
private fun drawItemView(position: Int) {
val view = views[position]
val item = items[position]
with(view) {
if (items.size > 3) {
container.setPadding(0, 0, 0, container.paddingBottom)
}
with(icon) {
selectColor = iconSelectedColor
deselectColor = iconColor
setImageDrawable(item.drawable)
if (selectedTabPosition == position)
views[position].animateSelectItemView()
else
setTintColor(deselectColor)
}
with(title) {
typeface = textFont
setTextColor(this@FluidBottomNavigation.textColor)
text = item.title
setTextSize(
TypedValue.COMPLEX_UNIT_PX,
resources.getDimension(R.dimen.fluidBottomNavigationTextSize))
}
with(circle) {
setTintColor(accentColor)
}
with(rectangle) {
setTintColor(accentColor)
}
backgroundContainer.setOnClickListener {
val nowTimestamp = SystemClock.uptimeMillis()
if (abs(lastItemClickTimestamp - nowTimestamp) > ITEMS_CLICKS_DEBOUNCE) {
selectTab(position)
lastItemClickTimestamp = nowTimestamp
}
}
}
}
fun getTabsSize() = items.size
private fun getAttributesOrDefaultValues(attrs: AttributeSet?) {
if (attrs != null) {
with(context
.obtainStyledAttributes(
attrs,
R.styleable.FluidBottomNavigation,
0, 0)) {
selectedTabPosition = getInt(
R.styleable.FluidBottomNavigation_defaultTabPosition,
DEFAULT_SELECTED_TAB_POSITION)
accentColor = getColor(
R.styleable.FluidBottomNavigation_accentColor,
ContextCompat.getColor(context, R.color.accentColor))
backColor = getColor(
R.styleable.FluidBottomNavigation_backColor,
ContextCompat.getColor(context, R.color.backColor))
iconColor = getColor(
R.styleable.FluidBottomNavigation_iconColor,
ContextCompat.getColor(context, R.color.iconColor))
textColor = getColor(
R.styleable.FluidBottomNavigation_textColor,
ContextCompat.getColor(context, R.color.iconSelectedColor))
iconSelectedColor = getColor(
R.styleable.FluidBottomNavigation_iconSelectedColor,
ContextCompat.getColor(context, R.color.iconSelectedColor))
textFont = ResourcesCompat.getFont(
context,
getResourceId(
R.styleable.FluidBottomNavigation_textFont,
R.font.rubik_regular)) ?: Typeface.DEFAULT
recycle()
}
}
}
fun getSelectedTabPosition() = this.selectedTabPosition
override fun onSaveInstanceState() =
Bundle()
.apply {
putInt(EXTRA_SELECTED_TAB_POSITION, selectedTabPosition)
putParcelable(EXTRA_SELECTED_SUPER_STATE, super.onSaveInstanceState())
}
override fun onRestoreInstanceState(state: Parcelable?) =
if (state is Bundle?) {
selectedTabPosition = state
?.getInt(EXTRA_SELECTED_TAB_POSITION) ?: DEFAULT_SELECTED_TAB_POSITION
state?.getParcelable(EXTRA_SELECTED_SUPER_STATE)
} else {
state
}
.let {
super.onRestoreInstanceState(it)
}
}
================================================
FILE: fluidbottomnavigation/src/main/java/com/tenclouds/fluidbottomnavigation/FluidBottomNavigationAnimations.kt
================================================
package com.tenclouds.fluidbottomnavigation
import android.animation.AnimatorSet
import android.view.View
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator
import com.tenclouds.fluidbottomnavigation.extension.translationYAnimator
import kotlinx.android.synthetic.main.item.view.*
internal fun View.animateSelectItemView() =
AnimatorSet()
.apply {
playTogether(
circle.selectAnimator,
icon.selectAnimator,
title.selectAnimator,
rectangle.selectAnimator,
topContainer.selectAnimator)
}
.start()
internal fun View.animateDeselectItemView() =
AnimatorSet()
.apply {
playTogether(
circle.deselectAnimator,
icon.deselectAnimator,
title.deselectAnimator,
rectangle.deselectAnimator,
topContainer.deselectAnimator)
}
.start()
internal fun View.animateShow() =
AnimatorSet()
.apply {
play(translationYAnimator(
height.toFloat(),
0f,
3 * KEY_FRAME_IN_MS,
LinearOutSlowInInterpolator()))
}
.start()
internal fun View.animateHide() =
AnimatorSet()
.apply {
play(translationYAnimator(
0f,
height.toFloat(),
3 * KEY_FRAME_IN_MS,
LinearOutSlowInInterpolator()))
}
.start()
================================================
FILE: fluidbottomnavigation/src/main/java/com/tenclouds/fluidbottomnavigation/FluidBottomNavigationItem.kt
================================================
package com.tenclouds.fluidbottomnavigation
import android.graphics.drawable.Drawable
data class FluidBottomNavigationItem(val title: String,
val drawable: Drawable? = null)
================================================
FILE: fluidbottomnavigation/src/main/java/com/tenclouds/fluidbottomnavigation/extension/AnimatorExtensions.kt
================================================
package com.tenclouds.fluidbottomnavigation.extension
import android.animation.ArgbEvaluator
import android.animation.ValueAnimator
import android.view.View
import android.view.animation.Interpolator
import android.view.animation.LinearInterpolator
import android.widget.ImageView
internal fun View?.scaleAnimator(from: Float = this?.scaleX ?: 0f,
to: Float,
animationDuration: Long,
animationInterpolator: Interpolator = LinearInterpolator()) =
ValueAnimator.ofFloat(from, to)
.apply {
duration = animationDuration
interpolator = animationInterpolator
addUpdateListener {
this@scaleAnimator?.scaleX = animatedValue as Float
this@scaleAnimator?.scaleY = animatedValue as Float
}
}
internal fun View?.scaleYAnimator(from: Float = this?.scaleX ?: 0f,
to: Float,
animationDuration: Long,
animationInterpolator: Interpolator = LinearInterpolator()) =
ValueAnimator.ofFloat(from, to)
.apply {
duration = animationDuration
interpolator = animationInterpolator
addUpdateListener {
this@scaleYAnimator?.scaleY = animatedValue as Float
}
}
internal fun View?.translationYAnimator(from: Float = 0f,
to: Float,
animationDuration: Long,
animationInterpolator: Interpolator = LinearInterpolator()) =
ValueAnimator.ofFloat(from, to)
.apply {
duration = animationDuration
interpolator = animationInterpolator
addUpdateListener {
this@translationYAnimator?.translationY = it.animatedValue as Float
}
}
internal fun View?.alphaAnimator(from: Float = 1f,
to: Float,
animationDuration: Long,
animationInterpolator: Interpolator = LinearInterpolator()) =
ValueAnimator.ofFloat(from, to)
.apply {
duration = animationDuration
interpolator = animationInterpolator
addUpdateListener {
this@alphaAnimator?.alpha = it.animatedValue as Float
}
}
internal fun ImageView?.tintAnimator(from: Int,
to: Int,
animationDuration: Long,
animationInterpolator: Interpolator = LinearInterpolator()) =
ValueAnimator.ofObject(ArgbEvaluator(), from, to)
.apply {
duration = animationDuration
interpolator = animationInterpolator
addUpdateListener {
this@tintAnimator?.setTintColor(it.animatedValue as Int)
}
}
================================================
FILE: fluidbottomnavigation/src/main/java/com/tenclouds/fluidbottomnavigation/extension/InterpolatorExtensions.kt
================================================
package com.tenclouds.fluidbottomnavigation.extension
import androidx.core.view.animation.PathInterpolatorCompat
internal val interpolators = arrayOf(
arrayOf(0.250f, 0.000f, 0.000f, 1.000f).toInterpolator(),
arrayOf(0.200f, 0.000f, 0.800f, 1.000f).toInterpolator(),
arrayOf(0.420f, 0.000f, 0.580f, 1.000f).toInterpolator(),
arrayOf(0.270f, 0.000f, 0.000f, 1.000f).toInterpolator(),
arrayOf(0.500f, 0.000f, 0.500f, 1.000f).toInterpolator())
private fun Array<Float>.toInterpolator() = PathInterpolatorCompat.create(this[0], this[1], this[2], this[3])
================================================
FILE: fluidbottomnavigation/src/main/java/com/tenclouds/fluidbottomnavigation/extension/ViewExtensions.kt
================================================
package com.tenclouds.fluidbottomnavigation.extension
import android.annotation.TargetApi
import android.content.Context
import android.content.res.ColorStateList
import android.os.Build
import android.util.DisplayMetrics
import android.view.Display
import android.view.View
import android.view.WindowManager
import android.widget.ImageView
import androidx.core.widget.ImageViewCompat
import com.tenclouds.fluidbottomnavigation.FluidBottomNavigation
internal fun View.visible() {
this.visibility = View.VISIBLE
}
internal fun View.invisible() {
this.visibility = View.INVISIBLE
}
internal fun View.gone() {
this.visibility = View.GONE
}
internal fun ImageView.setTintColor(color: Int) =
ImageViewCompat.setImageTintList(
this,
ColorStateList.valueOf(color))
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
internal fun FluidBottomNavigation.calculateHeight(layoutHeight: Int): Int {
var navigationLayoutHeight = layoutHeight
var navigationBarHeight = 0
resources.getIdentifier(
"navigation_bar_height",
"dimen",
"android"
)
.let {
if (it > 0)
navigationBarHeight = resources.getDimensionPixelSize(it)
}
intArrayOf(android.R.attr.windowTranslucentNavigation)
.let {
with(context.theme
.obtainStyledAttributes(it)) {
val translucentNavigation = getBoolean(0, true)
if (isInImmersiveMode(context) && !translucentNavigation) {
navigationLayoutHeight += navigationBarHeight
}
recycle()
}
}
return navigationLayoutHeight
}
private fun isInImmersiveMode(context: Context) =
with((context.getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay) {
val realMetrics = getRealMetrics()
val metrics = getMetrics()
realMetrics.widthPixels > metrics.widthPixels
|| realMetrics.heightPixels > metrics.heightPixels
}
private fun Display.getMetrics() =
DisplayMetrics().also { this.getMetrics(it) }
private fun Display.getRealMetrics() =
DisplayMetrics()
.let {
when {
Build.VERSION.SDK_INT >= 17 -> it.also { this.getRealMetrics(it) }
Build.VERSION.SDK_INT >= 15 ->
try {
val getRawHeight = Display::class.java.getMethod("getRawHeight")
val getRawWidth = Display::class.java.getMethod("getRawWidth")
DisplayMetrics()
.apply {
widthPixels = getRawWidth.invoke(this) as Int
heightPixels = getRawHeight.invoke(this) as Int
}
} catch (e: Exception) {
DisplayMetrics()
.apply {
@Suppress("DEPRECATION")
widthPixels = this@getRealMetrics.width
@Suppress("DEPRECATION")
heightPixels = this@getRealMetrics.height
}
}
else -> DisplayMetrics()
.apply {
@Suppress("DEPRECATION")
widthPixels = this@getRealMetrics.width
@Suppress("DEPRECATION")
heightPixels = this@getRealMetrics.height
}
}
}
================================================
FILE: fluidbottomnavigation/src/main/java/com/tenclouds/fluidbottomnavigation/listener/OnTabSelectedListener.kt
================================================
package com.tenclouds.fluidbottomnavigation.listener
interface OnTabSelectedListener {
fun onTabSelected(position: Int)
}
================================================
FILE: fluidbottomnavigation/src/main/java/com/tenclouds/fluidbottomnavigation/view/AnimatedView.kt
================================================
package com.tenclouds.fluidbottomnavigation.view
import android.animation.Animator
import android.content.Context
import com.tenclouds.fluidbottomnavigation.R
internal interface AnimatedView {
val selectAnimator: Animator
val deselectAnimator: Animator
fun getItemTransitionYValue(context: Context) =
-(context.resources?.getDimension(R.dimen.fluidBottomNavigationItemTranslationY) ?: 0f)
fun getItemOvershootTransitionYValue(context: Context) =
getItemTransitionYValue(context) * 11 / 10
}
================================================
FILE: fluidbottomnavigation/src/main/java/com/tenclouds/fluidbottomnavigation/view/CircleView.kt
================================================
package com.tenclouds.fluidbottomnavigation.view
import android.animation.AnimatorSet
import android.content.Context
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatImageView
import com.tenclouds.fluidbottomnavigation.KEY_FRAME_IN_MS
import com.tenclouds.fluidbottomnavigation.extension.interpolators
import com.tenclouds.fluidbottomnavigation.extension.scaleAnimator
import com.tenclouds.fluidbottomnavigation.extension.translationYAnimator
internal class CircleView @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0)
: AppCompatImageView(context, attrs, defStyleAttr), AnimatedView {
init {
scaleY = 0f
scaleX = 0f
}
override val selectAnimator by lazy {
AnimatorSet()
.apply {
playTogether(
selectScaleAnimator,
selectMoveAnimator)
}
}
override val deselectAnimator by lazy {
AnimatorSet()
.apply {
playTogether(
deselectScaleAnimator,
deselectMoveAnimator)
}
}
private val selectScaleAnimator =
AnimatorSet()
.apply {
playSequentially(
scaleAnimator(0.0f, 1.0f, 7 * KEY_FRAME_IN_MS, interpolators[0]),
scaleAnimator(1.0f, 0.33f, 4 * KEY_FRAME_IN_MS, interpolators[2]),
scaleAnimator(0.33f, 1.2f, 7 * KEY_FRAME_IN_MS, interpolators[1]),
scaleAnimator(1.2f, 0.8f, 3 * KEY_FRAME_IN_MS, interpolators[1]),
scaleAnimator(0.8f, 1.0f, 3 * KEY_FRAME_IN_MS, interpolators[1]))
}
private val selectMoveAnimator =
AnimatorSet()
.apply {
playSequentially(
translationYAnimator(
0f,
getItemOvershootTransitionYValue(context),
7 * KEY_FRAME_IN_MS,
interpolators[0]),
translationYAnimator(
getItemOvershootTransitionYValue(context),
getItemTransitionYValue(context),
3 * KEY_FRAME_IN_MS,
interpolators[4]))
startDelay = 11 * KEY_FRAME_IN_MS
}
private val deselectScaleAnimator =
AnimatorSet()
.apply {
playSequentially(
scaleAnimator(1.0f, 0.8f, 3 * KEY_FRAME_IN_MS, interpolators[1]),
scaleAnimator(0.8f, 1.2f, 3 * KEY_FRAME_IN_MS, interpolators[1]),
scaleAnimator(1.2f, 0.33f, 7 * KEY_FRAME_IN_MS, interpolators[1]),
scaleAnimator(0.33f, 1.0f, 6 * KEY_FRAME_IN_MS, interpolators[2]),
scaleAnimator(1.0f, 0.0f, 7 * KEY_FRAME_IN_MS, interpolators[0]))
}
private val deselectMoveAnimator =
AnimatorSet()
.apply {
playSequentially(
translationYAnimator(
getItemTransitionYValue(context),
getItemOvershootTransitionYValue(context),
3 * KEY_FRAME_IN_MS,
interpolators[4]),
translationYAnimator(
getItemOvershootTransitionYValue(context),
0f,
7 * KEY_FRAME_IN_MS,
interpolators[0]))
startDelay = 6 * KEY_FRAME_IN_MS
}
}
================================================
FILE: fluidbottomnavigation/src/main/java/com/tenclouds/fluidbottomnavigation/view/IconView.kt
================================================
package com.tenclouds.fluidbottomnavigation.view
import android.animation.Animator
import android.animation.AnimatorSet
import android.content.Context
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatImageView
import com.tenclouds.fluidbottomnavigation.KEY_FRAME_IN_MS
import com.tenclouds.fluidbottomnavigation.extension.*
internal class IconView @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0)
: AppCompatImageView(context, attrs, defStyleAttr), AnimatedView {
init {
scaleX = 0.9f
scaleY = 0.9f
}
var selectColor = 0
var deselectColor = 0
override val selectAnimator by lazy {
AnimatorSet()
.apply {
playTogether(
selectScaleAnimator,
selectMoveAnimator,
selectTintAnimator)
addListener(object : Animator.AnimatorListener {
override fun onAnimationRepeat(animation: Animator?) = Unit
override fun onAnimationEnd(animation: Animator?) = Unit
override fun onAnimationCancel(animation: Animator?) = Unit
override fun onAnimationStart(animation: Animator?) {
deselectTintAnimator.cancel()
setTintColor(selectColor)
}
})
}
}
override val deselectAnimator by lazy {
AnimatorSet()
.apply {
playTogether(
deselectScaleAnimator,
deselectMoveAnimator,
deselectTintAnimator)
}
}
private val selectScaleAnimator =
AnimatorSet()
.apply {
playSequentially(
scaleAnimator(0.9f, 1.1f, 7 * KEY_FRAME_IN_MS, interpolators[0]),
scaleAnimator(1.1f, 0.84f, 4 * KEY_FRAME_IN_MS, interpolators[0]),
scaleAnimator(0.84f, 0.9f, 4 * KEY_FRAME_IN_MS, interpolators[3]))
}
private val selectMoveAnimator =
AnimatorSet()
.apply {
playSequentially(
translationYAnimator(
0f,
getItemOvershootTransitionYValue(context),
7 * KEY_FRAME_IN_MS,
interpolators[0]),
translationYAnimator(
getItemOvershootTransitionYValue(context),
getItemTransitionYValue(context),
3 * KEY_FRAME_IN_MS,
interpolators[4]))
startDelay = 11 * KEY_FRAME_IN_MS
}
private val selectTintAnimator by lazy {
AnimatorSet()
.apply {
play(tintAnimator(
deselectColor,
selectColor,
3 * KEY_FRAME_IN_MS))
}
}
private val deselectScaleAnimator =
AnimatorSet()
.apply {
playSequentially(
scaleAnimator(0.9f, 0.84f, 4 * KEY_FRAME_IN_MS, interpolators[3]),
scaleAnimator(0.84f, 1.1f, 4 * KEY_FRAME_IN_MS, interpolators[0]),
scaleAnimator(1.1f, 0.9f, 7 * KEY_FRAME_IN_MS, interpolators[0]))
}
private val deselectMoveAnimator =
AnimatorSet()
.apply {
playSequentially(
translationYAnimator(
getItemTransitionYValue(context),
getItemOvershootTransitionYValue(context),
3 * KEY_FRAME_IN_MS,
interpolators[4]),
translationYAnimator(
getItemOvershootTransitionYValue(context),
0f,
7 * KEY_FRAME_IN_MS,
interpolators[0]))
startDelay = 6 * KEY_FRAME_IN_MS
}
private val deselectTintAnimator by lazy {
AnimatorSet()
.apply {
play(tintAnimator(
selectColor,
deselectColor,
3 * KEY_FRAME_IN_MS))
startDelay = 19 * KEY_FRAME_IN_MS
}
}
}
================================================
FILE: fluidbottomnavigation/src/main/java/com/tenclouds/fluidbottomnavigation/view/RectangleView.kt
================================================
package com.tenclouds.fluidbottomnavigation.view
import android.animation.Animator
import android.animation.AnimatorSet
import android.content.Context
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatImageView
import com.tenclouds.fluidbottomnavigation.KEY_FRAME_IN_MS
import com.tenclouds.fluidbottomnavigation.extension.interpolators
import com.tenclouds.fluidbottomnavigation.extension.scaleYAnimator
import com.tenclouds.fluidbottomnavigation.extension.translationYAnimator
internal class RectangleView @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0)
: AppCompatImageView(context, attrs, defStyleAttr), AnimatedView {
init {
scaleY = 0f
}
override val selectAnimator by lazy {
AnimatorSet()
.apply {
playTogether(
selectScaleAnimator,
selectMoveAnimator)
addListener(object : Animator.AnimatorListener {
override fun onAnimationRepeat(animation: Animator?) = Unit
override fun onAnimationEnd(animation: Animator?) = Unit
override fun onAnimationCancel(animation: Animator?) = Unit
override fun onAnimationStart(animation: Animator?) {
deselectMoveAnimator.cancel()
deselectScaleAnimator.cancel()
scaleY = 0f
}
})
}
}
override val deselectAnimator by lazy {
AnimatorSet()
.apply {
playTogether(
deselectScaleAnimator,
deselectMoveAnimator)
addListener(object : Animator.AnimatorListener {
override fun onAnimationRepeat(animation: Animator?) = Unit
override fun onAnimationEnd(animation: Animator?) = Unit
override fun onAnimationCancel(animation: Animator?) = Unit
override fun onAnimationStart(animation: Animator?) {
selectAnimator.cancel()
}
})
}
}
private val selectScaleAnimator =
AnimatorSet()
.apply {
playSequentially(
scaleYAnimator(0.0f, 0.8f, 3 * KEY_FRAME_IN_MS, interpolators[1]),
scaleYAnimator(0.8f, 0.0f, 5 * KEY_FRAME_IN_MS, interpolators[1]))
startDelay = 11 * KEY_FRAME_IN_MS
}
private val selectMoveAnimator =
AnimatorSet()
.apply {
play(
translationYAnimator(
0f,
getItemTransitionYValue(context),
5 * KEY_FRAME_IN_MS,
interpolators[1]))
startDelay = 14 * KEY_FRAME_IN_MS
}
private val deselectScaleAnimator =
AnimatorSet()
.apply {
playSequentially(
scaleYAnimator(0.0f, 0.8f, 5 * KEY_FRAME_IN_MS, interpolators[1]),
scaleYAnimator(0.8f, 0.0f, 3 * KEY_FRAME_IN_MS, interpolators[1]))
startDelay = 4 * KEY_FRAME_IN_MS
}
private val deselectMoveAnimator =
AnimatorSet()
.apply {
play(
translationYAnimator(
getItemDeselectTransitionYValue(context),
0f,
2 * KEY_FRAME_IN_MS,
interpolators[1]))
startDelay = 4 * KEY_FRAME_IN_MS
}
private fun getItemDeselectTransitionYValue(context: Context) =
getItemTransitionYValue(context) * 3 / 5
}
================================================
FILE: fluidbottomnavigation/src/main/java/com/tenclouds/fluidbottomnavigation/view/TitleView.kt
================================================
package com.tenclouds.fluidbottomnavigation.view
import android.animation.AnimatorSet
import android.content.Context
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatTextView
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator
import com.tenclouds.fluidbottomnavigation.KEY_FRAME_IN_MS
import com.tenclouds.fluidbottomnavigation.extension.alphaAnimator
import com.tenclouds.fluidbottomnavigation.extension.interpolators
import com.tenclouds.fluidbottomnavigation.extension.translationYAnimator
internal class TitleView @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0)
: AppCompatTextView(context, attrs, defStyleAttr), AnimatedView {
override val selectAnimator by lazy {
AnimatorSet()
.apply {
playTogether(
selectMoveAnimator,
selectAlphaAnimator)
}
}
override val deselectAnimator by lazy {
AnimatorSet()
.apply {
playTogether(
deselectMoveAnimator,
deselectAlphaAnimator)
}
}
private val selectMoveAnimator =
AnimatorSet()
.apply {
playSequentially(
translationYAnimator(
0f,
getItemOvershootTransitionYValue(context),
7 * KEY_FRAME_IN_MS,
interpolators[0]),
translationYAnimator(
getItemOvershootTransitionYValue(context),
getItemTransitionYValue(context),
3 * KEY_FRAME_IN_MS,
interpolators[4]))
startDelay = 11 * KEY_FRAME_IN_MS
}
private val selectAlphaAnimator =
AnimatorSet()
.apply {
play(alphaAnimator(0f, 1f, 8 * KEY_FRAME_IN_MS, LinearOutSlowInInterpolator()))
startDelay = 14 * KEY_FRAME_IN_MS
}
private val deselectMoveAnimator =
AnimatorSet()
.apply {
playSequentially(
translationYAnimator(
getItemTransitionYValue(context),
getItemOvershootTransitionYValue(context),
3 * KEY_FRAME_IN_MS,
interpolators[4]),
translationYAnimator(
getItemOvershootTransitionYValue(context),
0f,
11 * KEY_FRAME_IN_MS,
interpolators[0]))
startDelay = 4 * KEY_FRAME_IN_MS
}
private val deselectAlphaAnimator =
AnimatorSet()
.apply {
play(alphaAnimator(1f, 0f, 8 * KEY_FRAME_IN_MS, LinearOutSlowInInterpolator()))
startDelay = 7 * KEY_FRAME_IN_MS
}
}
================================================
FILE: fluidbottomnavigation/src/main/java/com/tenclouds/fluidbottomnavigation/view/TopContainerView.kt
================================================
package com.tenclouds.fluidbottomnavigation.view
import android.animation.AnimatorSet
import android.content.Context
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatImageView
import androidx.core.content.ContextCompat
import com.tenclouds.fluidbottomnavigation.KEY_FRAME_IN_MS
import com.tenclouds.fluidbottomnavigation.R
import com.tenclouds.fluidbottomnavigation.extension.interpolators
import com.tenclouds.fluidbottomnavigation.extension.scaleAnimator
import com.tenclouds.fluidbottomnavigation.extension.translationYAnimator
internal class TopContainerView @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0)
: AppCompatImageView(context, attrs, defStyleAttr), AnimatedView {
init {
setImageDrawable(ContextCompat.getDrawable(context, R.drawable.top))
translationY = 100f
}
override val selectAnimator by lazy {
AnimatorSet()
.apply {
playTogether(
selectScaleAnimator,
selectMoveAnimator)
}
}
override val deselectAnimator by lazy {
AnimatorSet()
.apply {
playTogether(
deselectScaleAnimator,
deselectMoveAnimator)
}
}
private val selectScaleAnimator =
AnimatorSet()
.apply {
playSequentially(
scaleAnimator(1.0f, 1.25f, 6 * KEY_FRAME_IN_MS, interpolators[1]),
scaleAnimator(1.25f, 0.85f, 3 * KEY_FRAME_IN_MS, interpolators[1]),
scaleAnimator(0.85f, 1.0f, 3 * KEY_FRAME_IN_MS, interpolators[1]))
startDelay = 11 * KEY_FRAME_IN_MS
}
private val selectMoveAnimator =
AnimatorSet()
.apply {
play(translationYAnimator(
100f,
getItemTransitionYValue(context),
7 * KEY_FRAME_IN_MS,
interpolators[0]))
startDelay = 12 * KEY_FRAME_IN_MS
}
private val deselectScaleAnimator =
AnimatorSet()
.apply {
playSequentially(
scaleAnimator(1.0f, 0.85f, 3 * KEY_FRAME_IN_MS, interpolators[1]),
scaleAnimator(0.85f, 1.25f, 3 * KEY_FRAME_IN_MS, interpolators[1]),
scaleAnimator(1.25f, 1.0f, 7 * KEY_FRAME_IN_MS, interpolators[1]))
}
private val deselectMoveAnimator =
AnimatorSet()
.apply {
play(translationYAnimator(
getItemTransitionYValue(context),
100f,
10 * KEY_FRAME_IN_MS,
interpolators[0]))
startDelay = 8 * KEY_FRAME_IN_MS
}
override fun getItemTransitionYValue(context: Context): Float {
return -super.getItemTransitionYValue(context) * 1 / 6
}
}
================================================
FILE: fluidbottomnavigation/src/main/res/drawable/circle.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid
android:color="#000"/>
<size
android:width="48dp"
android:height="48dp"/>
</shape>
================================================
FILE: fluidbottomnavigation/src/main/res/drawable/rectangle.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners
android:radius="48dp"/>
<solid
android:color="#000"/>
<size
android:width="48dp"
android:height="96dp"/>
</shape>
================================================
FILE: fluidbottomnavigation/src/main/res/drawable/top.xml
================================================
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:name="vector"
android:width="118dp"
android:height="46dp"
android:viewportWidth="118"
android:viewportHeight="46">
<path
android:pathData="M 85 15 C 94 29 95 33 118 33 L 118 46 L 0 46 L 0 33 C 23 33 24 29 33 15 C 36.522 8.881 42.094 4.198 48.727 1.78 C 55.361 -0.637 62.639 -0.637 69.273 1.78 C 75.906 4.198 81.478 8.881 85 15 Z"
android:fillColor="#ffffff"
android:strokeWidth="1"
android:fillType="evenOdd"/>
</vector>
================================================
FILE: fluidbottomnavigation/src/main/res/layout/item.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="@dimen/fluidBottomNavigationHeightWithOpacity"
android:background="@android:color/transparent">
<com.tenclouds.fluidbottomnavigation.view.TopContainerView
android:id="@+id/topContainer"
android:layout_width="@dimen/fluidBottomNavigationTopContainerWidth"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginRight="@dimen/fluidBottomNavigationTopContainerMarginLeftRight"
android:layout_marginEnd="@dimen/fluidBottomNavigationTopContainerMarginLeftRight"
/>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/backgroundContainer"
android:layout_width="match_parent"
android:layout_height="@dimen/fluidBottomNavigationHeight"
android:background="@android:color/white"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginTop="@dimen/fluidBottomNavigationBackgroundMarginTop"
/>
<com.tenclouds.fluidbottomnavigation.view.RectangleView
android:id="@+id/rectangle"
android:layout_width="@dimen/fluidBottomNavigationCircleSize"
android:layout_height="@dimen/fluidBottomNavigationCircleSize"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:src="@drawable/rectangle"
android:layout_marginTop="@dimen/fluidBottomNavigationRectangleMarginTop"
/>
<com.tenclouds.fluidbottomnavigation.view.CircleView
android:id="@+id/circle"
android:layout_width="@dimen/fluidBottomNavigationCircleSize"
android:layout_height="@dimen/fluidBottomNavigationCircleSize"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:src="@drawable/circle"
android:layout_marginTop="@dimen/fluidBottomNavigationCircleMarginTop"
/>
<com.tenclouds.fluidbottomnavigation.view.IconView
android:id="@+id/icon"
android:layout_width="@dimen/fluidBottomNavigationIconSize"
android:layout_height="@dimen/fluidBottomNavigationIconSize"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="@dimen/fluidBottomNavigationIconMarginTop"
tools:src="@android:drawable/btn_star"
/>
<com.tenclouds.fluidbottomnavigation.view.TitleView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/fluidBottomNavigationTitleMarginTop"
android:lines="1"
android:textColor="@color/textColor"
app:layout_constraintTop_toBottomOf="@id/icon"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:textSize="@dimen/fluidBottomNavigationTextSize"
tools:text="Label One"/>
</androidx.constraintlayout.widget.ConstraintLayout>
================================================
FILE: fluidbottomnavigation/src/main/res/values/attrs.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="FluidBottomNavigation">
<attr name="defaultTabPosition" format="integer"/>
<attr name="accentColor" format="color"/>
<attr name="backColor" format="color"/>
<attr name="iconColor" format="color"/>
<attr name="iconSelectedColor" format="color"/>
<attr name="textColor" format="color"/>
<attr name="textFont" format="string"/>
</declare-styleable>
</resources>
================================================
FILE: fluidbottomnavigation/src/main/res/values/colors.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="accentColor">#303F9F</color>
<color name="backColor">#FFFFFF</color>
<color name="textColor">#303F9F</color>
<color name="iconColor">#3F51B5</color>
<color name="iconSelectedColor">#FFFFFF</color>
</resources>
================================================
FILE: fluidbottomnavigation/src/main/res/values/dimens.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="fluidBottomNavigationHeight">56dp</dimen>
<dimen name="fluidBottomNavigationHeightWithOpacity">80dp</dimen>
<dimen name="fluidBottomNavigationTopContainerMarginLeftRight">1dp</dimen>
<dimen name="fluidBottomNavigationTopContainerWidth">88dp</dimen>
<dimen name="fluidBottomNavigationCircleSize">36dp</dimen>
<dimen name="fluidBottomNavigationIconSize">18dp</dimen>
<dimen name="fluidBottomNavigationCircleMarginTop">37dp</dimen>
<dimen name="fluidBottomNavigationIconMarginTop">46dp</dimen>
<dimen name="fluidBottomNavigationBackgroundMarginTop">32dp</dimen>
<dimen name="fluidBottomNavigationRectangleMarginTop">28dp</dimen>
<dimen name="fluidBottomNavigationTitleMarginTop">14dp</dimen>
<dimen name="fluidBottomNavigationTextSize">12sp</dimen>
<dimen name="fluidBottomNavigationItemTranslationY">22dp</dimen>
</resources>
================================================
FILE: fluidbottomnavigation/src/main/res/values/strings.xml
================================================
<resources>
<string name="exception_not_enough_items">Items list should have minimum 3 items</string>
<string name="exception_too_many_items">Items list should have maximum 5 items</string>
</resources>
================================================
FILE: fluidbottomnavigation/src/test/java/com/tenclouds/fluidbottomnavigation/FluidBottomNavigationTest.kt
================================================
package com.tenclouds.fluidbottomnavigation
import android.app.Activity
import com.nhaarman.mockitokotlin2.verify
import com.tenclouds.fluidbottomnavigation.listener.OnTabSelectedListener
import com.tenclouds.fluidbottomnavigation.util.ShadowResourcesCompat
import org.junit.Assert.*
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mock
import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
@RunWith(RobolectricTestRunner::class)
@Config(
packageName = "com.tenclouds.fluidbottomnavigation",
sdk = [21],
shadows = [(ShadowResourcesCompat::class)])
class FluidBottomNavigationTest {
private lateinit var fluidBottomNavigation: FluidBottomNavigation
private val controller = Robolectric.buildActivity(Activity::class.java).create().start()
private val fluidBottomNavigationItems =
listOf(
FluidBottomNavigationItem("Tab1"),
FluidBottomNavigationItem("Tab2"),
FluidBottomNavigationItem("Tab3"))
private val onTabSelectedListener = mock(OnTabSelectedListener::class.java)
@Before
fun setup() {
fluidBottomNavigation =
FluidBottomNavigation(controller.get())
.apply {
items = fluidBottomNavigationItems
onTabSelectedListener = this@FluidBottomNavigationTest.onTabSelectedListener
}
}
@Test
fun `selected tab position and item sets after context recreate`() {
fluidBottomNavigation.selectTab(1)
controller.configurationChange()
assertEquals(1, fluidBottomNavigation.getSelectedTabPosition())
fluidBottomNavigation.selectTab(2)
controller.configurationChange()
assertEquals(2, fluidBottomNavigation.getSelectedTabPosition())
fluidBottomNavigation.selectTab(0)
controller.configurationChange()
assertEquals(0, fluidBottomNavigation.getSelectedTabPosition())
}
@Test
fun `selectTab invokes onTabSelected on OnTabSelectedListener`() {
fluidBottomNavigation.selectTab(1)
verify(onTabSelectedListener).onTabSelected(1)
fluidBottomNavigation.selectTab(2)
verify(onTabSelectedListener).onTabSelected(2)
fluidBottomNavigation.selectTab(0)
verify(onTabSelectedListener).onTabSelected(0)
}
@Test
fun `selectTab changes selected tab position`() {
fluidBottomNavigation.selectTab(1)
assertEquals(1, fluidBottomNavigation.getSelectedTabPosition())
fluidBottomNavigation.selectTab(2)
assertEquals(2, fluidBottomNavigation.getSelectedTabPosition())
fluidBottomNavigation.selectTab(0)
assertEquals(0, fluidBottomNavigation.getSelectedTabPosition())
}
@Test
fun `selectTab changes selected tab item`() {
fluidBottomNavigation.selectTab(1)
assertEquals(fluidBottomNavigationItems[1], fluidBottomNavigation.selectedTabItem)
fluidBottomNavigation.selectTab(2)
assertEquals(fluidBottomNavigationItems[2], fluidBottomNavigation.selectedTabItem)
fluidBottomNavigation.selectTab(0)
assertEquals(fluidBottomNavigationItems[0], fluidBottomNavigation.selectedTabItem)
}
@Test
fun `hide hides navigation`() {
fluidBottomNavigation.isVisible = true
fluidBottomNavigation.hide()
assertFalse(fluidBottomNavigation.isVisible)
}
@Test
fun `show shows navigation`() {
fluidBottomNavigation.isVisible = false
fluidBottomNavigation.show()
assertTrue(fluidBottomNavigation.isVisible)
}
@Test
fun `getTabsSize returns correct items size`() {
assertEquals(fluidBottomNavigationItems.size, fluidBottomNavigation.getTabsSize())
}
}
================================================
FILE: fluidbottomnavigation/src/test/java/com/tenclouds/fluidbottomnavigation/util/ShadowResourcesCompat.java
================================================
package com.tenclouds.fluidbottomnavigation.util;
import android.content.Context;
import android.graphics.Typeface;
import android.support.annotation.FontRes;
import android.support.annotation.NonNull;
import androidx.core.content.res.ResourcesCompat;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
/**
* Mocks out ResourcesCompat so getFont won't actually attempt to look up the FontRes as a real
* resource, because of issues with Robolectric.
* <p>
* See: https://github.com/robolectric/robolectric/issues/3590
*/
@Implements(ResourcesCompat.class)
public class ShadowResourcesCompat {
private static Map<Integer, Typeface> FONT_MAP = new HashMap<>();
@Implementation
public static Typeface getFont(@NonNull Context context,
@FontRes int id) {
return FONT_MAP.computeIfAbsent(id, new Function<Integer, Typeface>() {
@Override
public Typeface apply(Integer integer) {
return ShadowResourcesCompat.buildTypeface(integer);
}
});
}
private static Typeface buildTypeface(@FontRes int id) {
return Typeface.DEFAULT;
}
}
================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
================================================
FILE: gradle.properties
================================================
android.useAndroidX=true
android.enableJetifier=true
org.gradle.jvmargs=-Xmx1536m
================================================
FILE: gradlew
================================================
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"
================================================
FILE: gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: settings.gradle
================================================
include ':app', ':fluidbottomnavigation'
gitextract_gb8nzjgx/ ├── .gitignore ├── LICENSE.md ├── README.md ├── app/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── com/ │ │ └── tenclouds/ │ │ └── fluidbottomnavigationexample/ │ │ └── MainActivity.kt │ └── res/ │ ├── drawable/ │ │ ├── background.xml │ │ ├── ic_calendar.xml │ │ ├── ic_chat.xml │ │ ├── ic_inbox.xml │ │ ├── ic_news.xml │ │ └── ic_profile.xml │ ├── layout/ │ │ └── activity_main.xml │ └── values/ │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── fluidbottomnavigation/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ ├── script/ │ │ └── version.gradle │ └── src/ │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── tenclouds/ │ │ │ └── fluidbottomnavigation/ │ │ │ ├── Consts.kt │ │ │ ├── FluidBottomNavigation.kt │ │ │ ├── FluidBottomNavigationAnimations.kt │ │ │ ├── FluidBottomNavigationItem.kt │ │ │ ├── extension/ │ │ │ │ ├── AnimatorExtensions.kt │ │ │ │ ├── InterpolatorExtensions.kt │ │ │ │ └── ViewExtensions.kt │ │ │ ├── listener/ │ │ │ │ └── OnTabSelectedListener.kt │ │ │ └── view/ │ │ │ ├── AnimatedView.kt │ │ │ ├── CircleView.kt │ │ │ ├── IconView.kt │ │ │ ├── RectangleView.kt │ │ │ ├── TitleView.kt │ │ │ └── TopContainerView.kt │ │ └── res/ │ │ ├── drawable/ │ │ │ ├── circle.xml │ │ │ ├── rectangle.xml │ │ │ └── top.xml │ │ ├── layout/ │ │ │ └── item.xml │ │ └── values/ │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ └── strings.xml │ └── test/ │ └── java/ │ └── com/ │ └── tenclouds/ │ └── fluidbottomnavigation/ │ ├── FluidBottomNavigationTest.kt │ └── util/ │ └── ShadowResourcesCompat.java ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat └── settings.gradle
SYMBOL INDEX (3 symbols across 1 files)
FILE: fluidbottomnavigation/src/test/java/com/tenclouds/fluidbottomnavigation/util/ShadowResourcesCompat.java
class ShadowResourcesCompat (line 23) | @Implements(ResourcesCompat.class)
method getFont (line 27) | @Implementation
method buildTypeface (line 38) | private static Typeface buildTypeface(@FontRes int id) {
Condensed preview — 54 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (99K chars).
[
{
"path": ".gitignore",
"chars": 900,
"preview": "# Built application files\n*.apk\n*.ap_\n\n# Files for the ART/Dalvik VM\n*.dex\n\n# Java class files\n*.class\n\n# Generated file"
},
{
"path": "LICENSE.md",
"chars": 1065,
"preview": "MIT License\n\nCopyright (c) 2018 10Clouds\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\no"
},
{
"path": "README.md",
"chars": 3235,
"preview": "# Fluid Bottom Navigation [
About this extraction
This page contains the full source code of the 10clouds/FluidBottomNavigation-android GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 54 files (89.2 KB), approximately 23.7k tokens, and a symbol index with 3 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.