diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2b75303
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,13 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..30aa626
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 0000000..29fe3ec
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..9fdea7e
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
new file mode 100644
index 0000000..7f68460
--- /dev/null
+++ b/.idea/runConfigurations.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..911480f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,3 @@
+Meow Bottom Navigation Kotlin
+====================================
+version: 2.2.4
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..fa5dd57
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,32 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+
+ ext.compileSdk_version = 28
+ ext.minSdk_version = 15
+ ext.targetSdk_version = 28
+
+ ext.kotlin_version = '1.3.21'
+
+ repositories {
+ google()
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:3.3.1'
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ jcenter()
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..58a1794
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,15 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.parallel=true
+org.gradle.jvmargs=-Xmx4048m
+android.useAndroidX=true
+android.enableJetifier=true
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..f6b961f
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..17d6d62
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Mon Feb 11 12:58:49 IRST 2019
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-all.zip
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..cccdd3d
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/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" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..f955316
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@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
diff --git a/meowbottomnavigation/.gitignore b/meowbottomnavigation/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/meowbottomnavigation/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/meowbottomnavigation/build.gradle b/meowbottomnavigation/build.gradle
new file mode 100644
index 0000000..1281c4f
--- /dev/null
+++ b/meowbottomnavigation/build.gradle
@@ -0,0 +1,41 @@
+apply plugin: 'com.android.library'
+
+apply plugin: 'kotlin-android'
+
+apply plugin: 'kotlin-android-extensions'
+
+def library_groupId = 'com.etebarian'
+def library_artifactId = 'meow-bottom-navigation'
+def library_versionCode = 1_0_00_00_01
+def library_versionName = '0.0.1'
+
+android {
+ compileSdkVersion compileSdk_version
+
+ defaultConfig {
+ minSdkVersion minSdk_version
+ targetSdkVersion targetSdk_version
+ versionCode library_versionCode
+ versionName library_versionName
+
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ vectorDrawables.useSupportLibrary = true
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+
+}
+
+androidExtensions {
+ experimental = true
+}
+
+dependencies {
+ implementation "androidx.appcompat:appcompat:1.0.2"
+ implementation "androidx.core:core-ktx:1.0.1"
+}
diff --git a/meowbottomnavigation/proguard-rules.pro b/meowbottomnavigation/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/meowbottomnavigation/proguard-rules.pro
@@ -0,0 +1,21 @@
+# 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
diff --git a/meowbottomnavigation/src/androidTest/java/com/etebarian/meowbottomnavigation/ExampleInstrumentedTest.java b/meowbottomnavigation/src/androidTest/java/com/etebarian/meowbottomnavigation/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..79fef95
--- /dev/null
+++ b/meowbottomnavigation/src/androidTest/java/com/etebarian/meowbottomnavigation/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package com.etebarian.meowbottomnavigation;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getTargetContext();
+
+ assertEquals("com.etebarian.meowbottomnavigation.test", appContext.getPackageName());
+ }
+}
diff --git a/meowbottomnavigation/src/main/AndroidManifest.xml b/meowbottomnavigation/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..493e5f9
--- /dev/null
+++ b/meowbottomnavigation/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
diff --git a/meowbottomnavigation/src/main/java/com/etebarian/meowbottomnavigation/BezierView.kt b/meowbottomnavigation/src/main/java/com/etebarian/meowbottomnavigation/BezierView.kt
new file mode 100644
index 0000000..30a7817
--- /dev/null
+++ b/meowbottomnavigation/src/main/java/com/etebarian/meowbottomnavigation/BezierView.kt
@@ -0,0 +1,211 @@
+package com.etebarian.meowbottomnavigation
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.graphics.*
+import android.util.AttributeSet
+import android.view.View
+
+/**
+ * Created by 1HE on 2/25/2019.
+ */
+
+class BezierView : View {
+
+ private var mainPaint: Paint? = null
+ private var shadowPaint: Paint? = null
+ private var mainPath: Path? = null
+ private var shadowPath: Path? = null
+ private lateinit var outerArray: Array
+ private lateinit var innerArray: Array
+ private lateinit var progressArray: Array
+
+ private var width = 0f
+ private var height = 0f
+ private var bezierOuterWidth = 0f
+ private var bezierOuterHeight = 0f
+ private var bezierInnerWidth = 0f
+ private var bezierInnerHeight = 0f
+ private val shadowHeight = dipf(context, 8)
+
+ var color = 0
+ set(value) {
+ field = value
+ mainPaint?.color = field
+ }
+
+ var bezierX = 0f
+ set(value) {
+ if (value == field)
+ return
+ field = value
+ invalidate()
+ }
+
+ var progress = 0f
+ set(value) {
+ if (value == field)
+ return
+ field = value
+
+ progressArray[1].x = bezierX - bezierInnerWidth / 2
+ progressArray[2].x = bezierX - bezierInnerWidth / 4
+ progressArray[3].x = bezierX - bezierInnerWidth / 4
+ progressArray[4].x = bezierX
+ progressArray[5].x = bezierX + bezierInnerWidth / 4
+ progressArray[6].x = bezierX + bezierInnerWidth / 4
+ progressArray[7].x = bezierX + bezierInnerWidth / 2
+ for (i in 2..6) {
+ if (progress <= 1f) {//convert to outer
+ progressArray[i].y = calculate(innerArray[i].y, outerArray[i].y)
+ } else {
+ progressArray[i].y = calculate(outerArray[i].y, innerArray[i].y)
+ }
+ }
+ if (field == 2f)
+ field = 0f
+
+ invalidate()
+ }
+
+ @SuppressLint("NewApi")
+ constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) {
+ initializeViews()
+ }
+
+ constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
+ initializeViews()
+ }
+
+ constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
+ initializeViews()
+ }
+
+ constructor(context: Context) : super(context) {
+ initializeViews()
+ }
+
+ private fun initializeViews() {
+ setWillNotDraw(false)
+
+ mainPath = Path()
+ shadowPath = Path()
+ outerArray = Array(11) { PointF() }
+ innerArray = Array(11) { PointF() }
+ progressArray = Array(11) { PointF() }
+
+ mainPaint = Paint(Paint.ANTI_ALIAS_FLAG)
+ mainPaint?.apply {
+ strokeWidth = 0f
+ isAntiAlias = true
+ style = Paint.Style.FILL
+ color = this@BezierView.color
+ }
+
+ color = color
+
+ shadowPaint = Paint(Paint.ANTI_ALIAS_FLAG)
+ shadowPaint?.apply {
+ isAntiAlias = true
+ setShadowLayer(dipf(context, 4), 0f, 0f, -0x454546)
+ }
+
+ setLayerType(View.LAYER_TYPE_SOFTWARE, shadowPaint)
+ }
+
+ @SuppressLint("DrawAllocation")
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+ width = View.MeasureSpec.getSize(widthMeasureSpec).toFloat()
+ height = View.MeasureSpec.getSize(heightMeasureSpec).toFloat()
+ bezierOuterWidth = dipf(context, 72)
+ bezierOuterHeight = dipf(context, 12)
+ bezierInnerWidth = dipf(context, 108)
+ bezierInnerHeight = dipf(context, 16)
+
+ val extra = shadowHeight
+ outerArray[0] = PointF(0f, bezierOuterHeight + extra)
+ outerArray[1] = PointF((bezierX - bezierOuterWidth / 2), bezierOuterHeight + extra)
+ outerArray[2] = PointF(bezierX - bezierOuterWidth / 4, bezierOuterHeight + extra)
+ outerArray[3] = PointF(bezierX - bezierOuterWidth / 4, extra)
+ outerArray[4] = PointF(bezierX, extra)
+ outerArray[5] = PointF(bezierX + bezierOuterWidth / 4, extra)
+ outerArray[6] = PointF(bezierX + bezierOuterWidth / 4, bezierOuterHeight + extra)
+ outerArray[7] = PointF(bezierX + bezierOuterWidth / 2, bezierOuterHeight + extra)
+ outerArray[8] = PointF(width, bezierOuterHeight + extra)
+ outerArray[9] = PointF(width, height)
+ outerArray[10] = PointF(0f, height)
+ }
+
+ override fun onDraw(canvas: Canvas) {
+ super.onDraw(canvas)
+ mainPath!!.reset()
+ shadowPath!!.reset()
+
+ if (progress == 0f) {
+ drawInner(canvas, true)
+ drawInner(canvas, false)
+ } else {
+ drawProgress(canvas, true)
+ drawProgress(canvas, false)
+ }
+ }
+
+ private fun drawInner(canvas: Canvas, isShadow: Boolean) {
+ val paint = if (isShadow) shadowPaint else mainPaint
+ val path = if (isShadow) shadowPath else mainPath
+
+ calculateInner()
+
+ path!!.lineTo(innerArray[0].x, innerArray[0].y)
+ path.lineTo(innerArray[1].x, innerArray[1].y)
+ path.cubicTo(innerArray[2].x, innerArray[2].y, innerArray[3].x, innerArray[3].y, innerArray[4].x, innerArray[4].y)
+ path.cubicTo(innerArray[5].x, innerArray[5].y, innerArray[6].x, innerArray[6].y, innerArray[7].x, innerArray[7].y)
+ path.lineTo(innerArray[8].x, innerArray[8].y)
+ path.lineTo(innerArray[9].x, innerArray[9].y)
+ path.lineTo(innerArray[10].x, innerArray[10].y)
+
+ progressArray = innerArray.clone()
+
+ canvas.drawPath(path, paint!!)
+ }
+
+ private fun calculateInner() {
+ val extra = shadowHeight
+ innerArray[0] = PointF(0f, bezierInnerHeight + extra)
+ innerArray[1] = PointF((bezierX - bezierInnerWidth / 2), bezierInnerHeight + extra)
+ innerArray[2] = PointF(bezierX - bezierInnerWidth / 4, bezierInnerHeight + extra)
+ innerArray[3] = PointF(bezierX - bezierInnerWidth / 4, height - extra)
+ innerArray[4] = PointF(bezierX, height - extra)
+ innerArray[5] = PointF(bezierX + bezierInnerWidth / 4, height - extra)
+ innerArray[6] = PointF(bezierX + bezierInnerWidth / 4, bezierInnerHeight + extra)
+ innerArray[7] = PointF(bezierX + bezierInnerWidth / 2, bezierInnerHeight + extra)
+ innerArray[8] = PointF(width, bezierInnerHeight + extra)
+ innerArray[9] = PointF(width, height)
+ innerArray[10] = PointF(0f, height)
+ }
+
+ private fun drawProgress(canvas: Canvas, isShadow: Boolean) {
+ val paint = if (isShadow) shadowPaint else mainPaint
+ val path = if (isShadow) shadowPath else mainPath
+
+ path!!.lineTo(progressArray[0].x, progressArray[0].y)
+ path.lineTo(progressArray[1].x, progressArray[1].y)
+ path.cubicTo(progressArray[2].x, progressArray[2].y, progressArray[3].x, progressArray[3].y, progressArray[4].x, progressArray[4].y)
+ path.cubicTo(progressArray[5].x, progressArray[5].y, progressArray[6].x, progressArray[6].y, progressArray[7].x, progressArray[7].y)
+ path.lineTo(progressArray[8].x, progressArray[8].y)
+ path.lineTo(progressArray[9].x, progressArray[9].y)
+ path.lineTo(progressArray[10].x, progressArray[10].y)
+
+ canvas.drawPath(path, paint!!)
+ }
+
+ private fun calculate(start: Float, end: Float): Float {
+ var p = progress
+ if (p > 1f)
+ p = progress - 1f
+ if (p in 0.9f..1f)
+ calculateInner()
+ return (p * (end - start)) + start
+ }
+}
diff --git a/meowbottomnavigation/src/main/java/com/etebarian/meowbottomnavigation/CellImageView.kt b/meowbottomnavigation/src/main/java/com/etebarian/meowbottomnavigation/CellImageView.kt
new file mode 100644
index 0000000..5094e17
--- /dev/null
+++ b/meowbottomnavigation/src/main/java/com/etebarian/meowbottomnavigation/CellImageView.kt
@@ -0,0 +1,155 @@
+package com.etebarian.meowbottomnavigation
+
+import android.animation.ValueAnimator
+import android.content.Context
+import android.util.AttributeSet
+import androidx.appcompat.widget.AppCompatImageView
+import androidx.interpolator.view.animation.FastOutSlowInInterpolator
+
+/**
+ * Created by 1HE on 2/23/2019.
+ */
+
+@Suppress("unused", "LeakingThis", "MemberVisibilityCanBePrivate")
+internal class CellImageView : AppCompatImageView {
+
+ var isBitmap = false
+ set(value) {
+ field = value
+ draw()
+ }
+ var useColor = true
+ set(value) {
+ field = value
+ draw()
+ }
+ var resource = 0
+ set(value) {
+ field = value
+ draw()
+ }
+ var color = 0
+ set(value) {
+ field = value
+ draw()
+ }
+ var size = dip(context, 24)
+ set(value) {
+ field = value
+ requestLayout()
+ }
+ private var actionBackgroundAlpha = false
+ private var changeSize = true
+ private var fitImage = false
+ private var colorAnimator: ValueAnimator? = null
+ private var allowDraw = false
+
+ constructor(context: Context) : super(context) {
+ initializeView()
+ }
+
+ constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
+ setAttributeFromXml(context, attrs)
+ initializeView()
+ }
+
+ constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
+ setAttributeFromXml(context, attrs)
+ initializeView()
+ }
+
+ private fun setAttributeFromXml(context: Context, attrs: AttributeSet) {
+ val a = context.theme.obtainStyledAttributes(attrs, R.styleable.CellImageView, 0, 0)
+ try {
+ a?.apply {
+ isBitmap = getBoolean(R.styleable.CellImageView_meow_imageview_isBitmap, isBitmap)
+ useColor = getBoolean(R.styleable.CellImageView_meow_imageview_useColor, useColor)
+ resource = getResourceId(R.styleable.CellImageView_meow_imageview_resource, resource)
+ color = getColor(R.styleable.CellImageView_meow_imageview_color, color)
+ size = getDimensionPixelSize(R.styleable.CellImageView_meow_imageview_size, size)
+ actionBackgroundAlpha = getBoolean(R.styleable.CellImageView_meow_imageview_actionBackgroundAlpha, actionBackgroundAlpha)
+ changeSize = getBoolean(R.styleable.CellImageView_meow_imageview_changeSize, changeSize)
+ fitImage = getBoolean(R.styleable.CellImageView_meow_imageview_fitImage, fitImage)
+ }
+ } finally {
+ a?.recycle()
+ }
+ }
+
+ private fun initializeView() {
+ allowDraw = true
+ draw()
+ }
+
+ private fun draw() {
+ if (!allowDraw)
+ return
+
+ if (resource == 0)
+ return
+
+ if (isBitmap) {
+ try {
+ val drawable = if (color == 0) context.getDrawableCompat(resource) else DrawableHelper.changeColorDrawableRes(context, resource, color)
+ setImageDrawable(drawable)
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ return
+ }
+
+ if (useColor && color == 0)
+ return
+
+ val c = if (useColor) color else -2
+ try {
+ setImageDrawable(DrawableHelper.changeColorDrawableVector(context, resource, c))
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+
+ fun changeColorByAnim(newColor: Int, d: Long = 250L) {
+ if (color == 0) {
+ color = newColor
+ return
+ }
+ val lastColor = color
+
+ colorAnimator?.cancel()
+
+ colorAnimator = ValueAnimator.ofFloat(0f, 1f)
+ colorAnimator?.apply {
+ duration = d
+ interpolator = FastOutSlowInInterpolator()
+ addUpdateListener { animation ->
+ val f = animation.animatedFraction
+ color = ColorHelper.mixTwoColors(newColor, lastColor, f)
+ }
+ start()
+ }
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ if (fitImage) {
+ val d = drawable
+ if (d != null) {
+ val width = MeasureSpec.getSize(widthMeasureSpec)
+ val height = Math.ceil((width.toFloat() * d.intrinsicHeight.toFloat() / d.intrinsicWidth).toDouble()).toInt()
+ setMeasuredDimension(width, height)
+ } else {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+ }
+ return
+ }
+
+ if (isBitmap || !changeSize) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+ return
+ }
+
+ val newSize = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY)
+ super.onMeasure(newSize, newSize)
+ }
+
+}
\ No newline at end of file
diff --git a/meowbottomnavigation/src/main/java/com/etebarian/meowbottomnavigation/MeowBottomNavigation.kt b/meowbottomnavigation/src/main/java/com/etebarian/meowbottomnavigation/MeowBottomNavigation.kt
new file mode 100644
index 0000000..49ca99a
--- /dev/null
+++ b/meowbottomnavigation/src/main/java/com/etebarian/meowbottomnavigation/MeowBottomNavigation.kt
@@ -0,0 +1,235 @@
+@file:Suppress("unused")
+
+package com.etebarian.meowbottomnavigation
+
+import android.animation.ValueAnimator
+import android.content.Context
+import android.graphics.Color
+import android.graphics.Typeface
+import android.util.AttributeSet
+import android.view.Gravity
+import android.widget.FrameLayout
+import android.widget.LinearLayout
+import androidx.interpolator.view.animation.FastOutSlowInInterpolator
+
+/**
+ * Created by 1HE on 10/23/2018.
+ */
+
+internal typealias IBottomNavigationListener = (model: MeowBottomNavigation.Model) -> Unit
+
+@Suppress("MemberVisibilityCanBePrivate")
+class MeowBottomNavigation : FrameLayout {//todo test orientation & slow anim
+
+ private var models = ArrayList()
+ private var cells = ArrayList()
+
+ private var nowShowId = -1
+
+ private var mOnClickedListener: IBottomNavigationListener = {}
+ private var mOnShowListener: IBottomNavigationListener = {}
+
+ var heightCell = 0
+
+ private var defaultIconColor = Color.parseColor("#757575")
+ private var selectedIconColor = Color.parseColor("#2196f3")
+ private var backgroundBottomColor = Color.parseColor("#ffffff")
+ private var countTextColor = Color.parseColor("#ffffff")
+ private var countBackgroundColor = Color.parseColor("#ff0000")
+ private var countTypeface: Typeface? = null
+ private var rippleColor = Color.parseColor("#757575")
+
+ private lateinit var ll_cells: LinearLayout
+ private lateinit var bezierView: BezierView
+
+ init {
+ heightCell = dip(context, 68)
+ }
+
+ constructor(context: Context) : super(context) {
+ initializeViews()
+ }
+
+ constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
+ setAttributeFromXml(context, attrs)
+ initializeViews()
+ }
+
+ constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
+ setAttributeFromXml(context, attrs)
+ initializeViews()
+ }
+
+ private fun setAttributeFromXml(context: Context, attrs: AttributeSet) {
+ val a = context.theme.obtainStyledAttributes(attrs, R.styleable.MeowBottomNavigation, 0, 0)
+ try {
+ a?.apply {
+ defaultIconColor = getColor(R.styleable.MeowBottomNavigation_mbn_defaultIconColor, defaultIconColor)
+ selectedIconColor = getColor(R.styleable.MeowBottomNavigation_mbn_selectedIconColor, selectedIconColor)
+ backgroundBottomColor = getColor(R.styleable.MeowBottomNavigation_mbn_backgroundBottomColor, backgroundBottomColor)
+ countTextColor = getColor(R.styleable.MeowBottomNavigation_mbn_countTextColor, countTextColor)
+ countBackgroundColor = getColor(R.styleable.MeowBottomNavigation_mbn_countBackgroundColor, countBackgroundColor)
+ val typeface = getString(R.styleable.MeowBottomNavigation_mbn_countTypeface)
+ rippleColor = getColor(R.styleable.MeowBottomNavigation_mbn_rippleColor, rippleColor)
+
+ if (typeface != null)
+ countTypeface = Typeface.createFromAsset(context.assets, typeface)
+ }
+ } finally {
+ a?.recycle()
+ }
+ }
+
+ private fun initializeViews() {
+ ll_cells = LinearLayout(context)
+ ll_cells.apply {
+ val params = FrameLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, heightCell)
+ params.gravity = Gravity.BOTTOM
+ layoutParams = params
+ orientation = LinearLayout.HORIZONTAL
+ clipChildren = false
+ clipToPadding = false
+ }
+
+ bezierView = BezierView(context)
+ bezierView.apply {
+ layoutParams = FrameLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, heightCell)
+ color = backgroundBottomColor
+ }
+
+ addView(bezierView)
+ addView(ll_cells)
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+ if (nowShowId == -1 && models.isNotEmpty()) {
+ show(models.first().id, false)
+ }
+ }
+
+ fun add(model: Model) {
+ val cell = MeowBottomNavigationCell(context)
+ cell.apply {
+ val params = LinearLayout.LayoutParams(0, heightCell, 1f)
+ layoutParams = params
+ icon = model.icon
+ count = model.count
+ circleColor = this@MeowBottomNavigation.backgroundBottomColor
+ countTextColor = this@MeowBottomNavigation.countTextColor
+ countBackgroundColor = this@MeowBottomNavigation.countBackgroundColor
+ countTypeface = this@MeowBottomNavigation.countTypeface
+ rippleColor = this@MeowBottomNavigation.rippleColor
+ defaultIconColor = this@MeowBottomNavigation.defaultIconColor
+ selectedIconColor = this@MeowBottomNavigation.selectedIconColor
+ setOnClickListener {
+ show(model.id)
+ mOnClickedListener(model)
+ }
+ disableCell()
+ ll_cells.addView(this)
+ }
+
+ cells.add(cell)
+ models.add(model)
+ }
+
+ fun show(id: Int, enableAnimation: Boolean = true) {
+ for (i in models.indices) {
+ val model = models[i]
+ val cell = cells[i]
+ if (model.id == id) {
+ anim(cell, id, enableAnimation)
+ cell.enableCell()
+ mOnShowListener(model)
+ } else {
+ cell.disableCell()
+ }
+ }
+ nowShowId = id
+ }
+
+ private fun anim(cell: MeowBottomNavigationCell, id: Int, enableAnimation: Boolean = true) {
+ val animDuration = if (enableAnimation) 750L else 1L
+ val animInterpolator = FastOutSlowInInterpolator()
+
+ val anim = ValueAnimator.ofFloat(0f, 1f)
+ anim.apply {
+ duration = animDuration
+ interpolator = animInterpolator
+ val beforeX = bezierView.bezierX
+ addUpdateListener {
+ val f = it.animatedFraction
+ val newX = cell.x + (cell.measuredWidth / 2)
+ if (newX > beforeX)
+ bezierView.bezierX = f * (newX - beforeX) + beforeX
+ else
+ bezierView.bezierX = beforeX - f * (beforeX - newX)
+ }
+ start()
+ }
+
+ val pos = getModelPosition(id)
+ val nowPos = getModelPosition(nowShowId)
+ if (Math.abs(pos - nowPos) > 1) {
+ val progressAnim = ValueAnimator.ofFloat(0f, 1f)
+ progressAnim.apply {
+ duration = animDuration
+ interpolator = animInterpolator
+ addUpdateListener {
+ val f = it.animatedFraction
+ bezierView.progress = f * 2f
+ }
+ start()
+ }
+ }
+
+ cell.isFromLeft = pos > nowPos
+ }
+
+ fun isShowing(id: Int): Boolean {
+ return nowShowId == id
+ }
+
+ fun getModelById(id: Int): Model? {
+ models.forEach {
+ if (it.id == id)
+ return it
+ }
+ return null
+ }
+
+ fun getCellById(id: Int): MeowBottomNavigationCell? {
+ return cells[getModelPosition(id)]
+ }
+
+ fun getModelPosition(id: Int): Int {
+ for (i in models.indices) {
+ val item = models[i]
+ if (item.id == id)
+ return i
+ }
+ return -1
+ }
+
+ fun setCount(id: Int, count: String) {
+ val model = getModelById(id) ?: return
+ val pos = getModelPosition(id)
+ model.count = count
+ cells[pos].count = count
+ }
+
+ fun setOnShowListener(listener: IBottomNavigationListener) {
+ mOnShowListener = listener
+ }
+
+ fun setOnClickMenuListener(listener: IBottomNavigationListener) {
+ mOnClickedListener = listener
+ }
+
+ class Model(var id: Int, var icon: Int) {
+
+ var count: String = MeowBottomNavigationCell.EMPTY_VALUE
+
+ }
+}
\ No newline at end of file
diff --git a/meowbottomnavigation/src/main/java/com/etebarian/meowbottomnavigation/MeowBottomNavigationCell.kt b/meowbottomnavigation/src/main/java/com/etebarian/meowbottomnavigation/MeowBottomNavigationCell.kt
new file mode 100644
index 0000000..99f03ae
--- /dev/null
+++ b/meowbottomnavigation/src/main/java/com/etebarian/meowbottomnavigation/MeowBottomNavigationCell.kt
@@ -0,0 +1,212 @@
+package com.etebarian.meowbottomnavigation
+
+import android.animation.ValueAnimator
+import android.content.Context
+import android.content.res.ColorStateList
+import android.graphics.Color
+import android.graphics.Typeface
+import android.graphics.drawable.GradientDrawable
+import android.graphics.drawable.RippleDrawable
+import android.os.Build
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.RelativeLayout
+import androidx.core.view.ViewCompat
+import androidx.interpolator.view.animation.FastOutSlowInInterpolator
+import kotlinx.android.extensions.LayoutContainer
+import kotlinx.android.synthetic.main.meow_navigation_cell.view.*
+
+/**
+ * Created by 1HE on 2/23/2019.
+ */
+
+@Suppress("unused")
+class MeowBottomNavigationCell : RelativeLayout, LayoutContainer {
+
+ companion object {
+ const val EMPTY_VALUE = "empty"
+ }
+
+ var defaultIconColor = 0
+ var selectedIconColor = 0
+ var circleColor = 0
+
+ var icon = 0
+ set(value) {
+ field = value
+ if (allowDraw)
+ iv.resource = value
+ }
+
+ var count: String? = EMPTY_VALUE
+ set(value) {
+ field = value
+ if (allowDraw) {
+ if (count != null && count == EMPTY_VALUE) {
+ tv_count.text = ""
+ tv_count.visibility = View.INVISIBLE
+ } else {
+ if (count != null && count?.length ?: 0 >= 3) {
+ field = count?.substring(0, 1) + ".."
+ }
+ tv_count.text = count
+ tv_count.visibility = View.VISIBLE
+ val scale = if (count?.isEmpty() == true) 0.5f else 1f
+ tv_count.scaleX = scale
+ tv_count.scaleY = scale
+ }
+ }
+ }
+
+ private var iconSize = dip(context, 24)
+ set(value) {
+ field = value
+ if (allowDraw)
+ iv.size = value
+ }
+
+ var countTextColor = 0
+ set(value) {
+ field = value
+ if (allowDraw)
+ tv_count.setTextColor(field)
+ }
+
+ var countBackgroundColor = 0
+ set(value) {
+ field = value
+ if (allowDraw) {
+ val d = GradientDrawable()
+ d.setColor(field)
+ d.shape = GradientDrawable.OVAL
+ ViewCompat.setBackground(tv_count, d)
+ }
+ }
+
+ var countTypeface : Typeface? = null
+ set(value) {
+ field = value
+ if (allowDraw && field != null)
+ tv_count.typeface = field
+ }
+
+ var rippleColor = 0
+ set(value) {
+ field = value
+ if (allowDraw) {
+
+ }
+ }
+
+ var isFromLeft = false
+ private var progress = 0f
+ set(value) {
+ field = value
+ fl.y = (1f - progress) * dip(context, 18) + dip(context, 8)
+
+ iv.color = if (progress == 1f) selectedIconColor else defaultIconColor
+
+ val d = GradientDrawable()
+ d.setColor(circleColor)
+ d.shape = GradientDrawable.OVAL
+
+ ViewCompat.setBackground(v_circle, d)
+
+ ViewCompat.setElevation(v_circle, if (progress > 0.7f) dipf(context, progress * 4f) else 0f)
+
+ val m = dip(context, 24)
+ v_circle.x = (1f - progress) * (if (isFromLeft) -m else m) + ((measuredWidth - dip(context, 48)) / 2f)
+ v_circle.y = (1f - progress) * measuredHeight + dip(context, 4)
+
+ }
+
+ private var isEnabledCell = false
+ set(value) {
+ field =value
+ val d = GradientDrawable()
+ d.setColor(circleColor)
+ d.shape = GradientDrawable.OVAL
+ if (Build.VERSION.SDK_INT >= 21 && !isEnabledCell) {
+ fl.background = RippleDrawable(ColorStateList.valueOf(rippleColor), null, d)
+ }else{
+ fl.runAfterDelay(200){
+ fl.setBackgroundColor(Color.TRANSPARENT)
+ }
+ }
+ }
+
+ override lateinit var containerView: View
+ private var allowDraw = false
+
+ constructor(context: Context) : super(context) {
+ initializeView()
+ }
+
+ constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
+ setAttributeFromXml(context, attrs)
+ initializeView()
+ }
+
+ constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
+ setAttributeFromXml(context, attrs)
+ initializeView()
+ }
+
+ @Suppress("UNUSED_PARAMETER")
+ private fun setAttributeFromXml(context: Context, attrs: AttributeSet) {
+ }
+
+ private fun initializeView() {
+ allowDraw = true
+ containerView = LayoutInflater.from(context).inflate(R.layout.meow_navigation_cell, this)
+ draw()
+ }
+
+ private fun draw() {
+ if (!allowDraw)
+ return
+
+ icon = icon
+ count = count
+ iconSize = iconSize
+ countTextColor = countTextColor
+ countBackgroundColor = countBackgroundColor
+ countTypeface = countTypeface
+ rippleColor= rippleColor
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+ progress = progress
+ }
+
+ fun disableCell() {
+ if (isEnabledCell)
+ animateProgress(false)
+ isEnabledCell = false
+ }
+
+ fun enableCell() {
+ if (!isEnabledCell)
+ animateProgress(true)
+ isEnabledCell = true
+ }
+
+ private fun animateProgress(enableCell: Boolean) {
+ val anim = ValueAnimator.ofFloat(0f, 1f)
+ anim.apply {
+ startDelay = if (enableCell) 250L else 0L
+ duration = 500L
+ interpolator = FastOutSlowInInterpolator()
+ addUpdateListener {
+ val f = it.animatedFraction
+ progress = if (enableCell)
+ f
+ else
+ 1f - f
+ }
+ start()
+ }
+ }
+}
\ No newline at end of file
diff --git a/meowbottomnavigation/src/main/java/com/etebarian/meowbottomnavigation/Utils.kt b/meowbottomnavigation/src/main/java/com/etebarian/meowbottomnavigation/Utils.kt
new file mode 100644
index 0000000..c29a2b0
--- /dev/null
+++ b/meowbottomnavigation/src/main/java/com/etebarian/meowbottomnavigation/Utils.kt
@@ -0,0 +1,72 @@
+package com.etebarian.meowbottomnavigation
+
+import android.content.Context
+import android.graphics.PorterDuff
+import android.graphics.drawable.Drawable
+import android.view.View
+import androidx.core.content.ContextCompat
+import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat
+import java.lang.Exception
+
+/**
+ * Created by 1HE on 2/23/2019.
+ */
+
+private fun getDP(context: Context) = context.resources.displayMetrics.density
+
+internal fun dipf(context: Context,f: Float) = f * getDP(context)
+
+internal fun dipf(context: Context,i: Int) = i * getDP(context)
+
+internal fun dip(context: Context,i: Int) = (i * getDP(context)).toInt()
+
+internal object DrawableHelper{
+
+ fun changeColorDrawableVector(c: Context?, resDrawable: Int, color: Int): Drawable? {
+ if (c == null)
+ return null
+
+ val d = VectorDrawableCompat.create(c.resources, resDrawable, null) ?: return null
+ d.mutate()
+ if (color != -2)
+ d.setColorFilter(color, PorterDuff.Mode.SRC_IN)
+ return d
+ }
+
+ fun changeColorDrawableRes(c: Context?, resDrawable: Int, color: Int): Drawable? {
+ if (c == null)
+ return null
+
+ val d = ContextCompat.getDrawable(c, resDrawable) ?: return null
+ d.mutate()
+ if (color != -2)
+ d.setColorFilter(color, PorterDuff.Mode.SRC_IN)
+ return d
+ }
+}
+
+internal object ColorHelper{
+
+ fun mixTwoColors(color1: Int, color2: Int, amount: Float): Int {
+ val alphaChannel = 24
+ val redChannel = 16
+ val greenChannel = 8
+
+ val inverseAmount = 1.0f - amount
+
+ val a = ((color1 shr alphaChannel and 0xff).toFloat() * amount + (color2 shr alphaChannel and 0xff).toFloat() * inverseAmount).toInt() and 0xff
+ val r = ((color1 shr redChannel and 0xff).toFloat() * amount + (color2 shr redChannel and 0xff).toFloat() * inverseAmount).toInt() and 0xff
+ val g = ((color1 shr greenChannel and 0xff).toFloat() * amount + (color2 shr greenChannel and 0xff).toFloat() * inverseAmount).toInt() and 0xff
+ val b = ((color1 and 0xff).toFloat() * amount + (color2 and 0xff).toFloat() * inverseAmount).toInt() and 0xff
+
+ return a shl alphaChannel or (r shl redChannel) or (g shl greenChannel) or b
+ }
+}
+
+internal fun Context.getDrawableCompat(res: Int) = ContextCompat.getDrawable(this, res)
+
+internal inline fun T.runAfterDelay(delay: Long, crossinline f: T.() -> Unit) {
+ this?.postDelayed({
+ try { f() }catch (e:Exception){}
+ }, delay)
+}
diff --git a/meowbottomnavigation/src/main/res/layout/meow_navigation_cell.xml b/meowbottomnavigation/src/main/res/layout/meow_navigation_cell.xml
new file mode 100644
index 0000000..44ff3d2
--- /dev/null
+++ b/meowbottomnavigation/src/main/res/layout/meow_navigation_cell.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/meowbottomnavigation/src/main/res/values/attrs.xml b/meowbottomnavigation/src/main/res/values/attrs.xml
new file mode 100644
index 0000000..835e8d3
--- /dev/null
+++ b/meowbottomnavigation/src/main/res/values/attrs.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/meowbottomnavigation/src/main/res/values/strings.xml b/meowbottomnavigation/src/main/res/values/strings.xml
new file mode 100644
index 0000000..e57297d
--- /dev/null
+++ b/meowbottomnavigation/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ MeowBottomNavigation
+
diff --git a/meowbottomnavigation/src/test/java/com/etebarian/meowbottomnavigation/ExampleUnitTest.java b/meowbottomnavigation/src/test/java/com/etebarian/meowbottomnavigation/ExampleUnitTest.java
new file mode 100644
index 0000000..8ec20f6
--- /dev/null
+++ b/meowbottomnavigation/src/test/java/com/etebarian/meowbottomnavigation/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package com.etebarian.meowbottomnavigation;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/sample/.gitignore b/sample/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/sample/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/sample/build.gradle b/sample/build.gradle
new file mode 100644
index 0000000..9f3a975
--- /dev/null
+++ b/sample/build.gradle
@@ -0,0 +1,33 @@
+apply plugin: 'com.android.application'
+
+apply plugin: 'kotlin-android'
+
+apply plugin: 'kotlin-android-extensions'
+
+android {
+ compileSdkVersion 28
+ defaultConfig {
+ applicationId "com.etebarian.meowbottomnavigaion.sample"
+ minSdkVersion 15
+ targetSdkVersion 28
+ versionCode 1
+ versionName "1.0"
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ vectorDrawables.useSupportLibrary = true
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ implementation fileTree(include: ['*.jar'], dir: 'libs')
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ implementation 'androidx.appcompat:appcompat:1.0.2'
+ implementation 'androidx.core:core-ktx:1.0.1'
+
+ implementation project(':meowbottomnavigation')
+}
diff --git a/sample/proguard-rules.pro b/sample/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/sample/proguard-rules.pro
@@ -0,0 +1,21 @@
+# 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
diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..ca577b8
--- /dev/null
+++ b/sample/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sample/src/main/java/com/etebarian/meowbottomnavigaion/MainActivity.kt b/sample/src/main/java/com/etebarian/meowbottomnavigaion/MainActivity.kt
new file mode 100644
index 0000000..64c89ab
--- /dev/null
+++ b/sample/src/main/java/com/etebarian/meowbottomnavigaion/MainActivity.kt
@@ -0,0 +1,39 @@
+package com.etebarian.meowbottomnavigaion
+
+import androidx.appcompat.app.AppCompatActivity
+import android.os.Bundle
+import com.etebarian.meowbottomnavigation.MeowBottomNavigation
+import kotlinx.android.synthetic.main.activity_main.*
+
+class MainActivity : AppCompatActivity() {//todo add ic_launcher
+
+ companion object {
+ private const val ID_HOME = 1
+ private const val ID_EXPLORE = 2
+ private const val ID_MESSAGE = 3
+ private const val ID_NOTIFICATION = 4
+ private const val ID_ACCOUNT = 5
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_main)
+
+ bottomNavigation.add(MeowBottomNavigation.Model(ID_HOME, R.drawable.ic_home))
+ bottomNavigation.add(MeowBottomNavigation.Model(ID_EXPLORE, R.drawable.ic_explore))
+ bottomNavigation.add(MeowBottomNavigation.Model(ID_MESSAGE, R.drawable.ic_message))
+ bottomNavigation.add(MeowBottomNavigation.Model(ID_NOTIFICATION, R.drawable.ic_notification))
+ bottomNavigation.add(MeowBottomNavigation.Model(ID_ACCOUNT, R.drawable.ic_account))
+
+ bt_update.setOnClickListener {
+ // val id = try {
+// et.text.toString().toInt()
+// }catch (e: Exception){
+// e.printStackTrace()
+// 0
+// }
+// bottomNavigation.show(id)
+ bottomNavigation.setCount(ID_NOTIFICATION, "1")
+ }
+ }
+}
diff --git a/sample/src/main/res/drawable-v24/ic_launcher_foreground.xml b/sample/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..c7bd21d
--- /dev/null
+++ b/sample/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sample/src/main/res/drawable/ic_account.xml b/sample/src/main/res/drawable/ic_account.xml
new file mode 100644
index 0000000..35919f9
--- /dev/null
+++ b/sample/src/main/res/drawable/ic_account.xml
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/sample/src/main/res/drawable/ic_explore.xml b/sample/src/main/res/drawable/ic_explore.xml
new file mode 100644
index 0000000..8f004e9
--- /dev/null
+++ b/sample/src/main/res/drawable/ic_explore.xml
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/sample/src/main/res/drawable/ic_home.xml b/sample/src/main/res/drawable/ic_home.xml
new file mode 100644
index 0000000..1dfa87f
--- /dev/null
+++ b/sample/src/main/res/drawable/ic_home.xml
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/sample/src/main/res/drawable/ic_launcher_background.xml b/sample/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..2408e30
--- /dev/null
+++ b/sample/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sample/src/main/res/drawable/ic_message.xml b/sample/src/main/res/drawable/ic_message.xml
new file mode 100644
index 0000000..681e68c
--- /dev/null
+++ b/sample/src/main/res/drawable/ic_message.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
diff --git a/sample/src/main/res/drawable/ic_notification.xml b/sample/src/main/res/drawable/ic_notification.xml
new file mode 100644
index 0000000..a75dabf
--- /dev/null
+++ b/sample/src/main/res/drawable/ic_notification.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/sample/src/main/res/layout/activity_main.xml b/sample/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..e7169d9
--- /dev/null
+++ b/sample/src/main/res/layout/activity_main.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..bbd3e02
--- /dev/null
+++ b/sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/sample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/sample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..bbd3e02
--- /dev/null
+++ b/sample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/sample/src/main/res/mipmap-hdpi/ic_launcher.png b/sample/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..898f3ed
Binary files /dev/null and b/sample/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/sample/src/main/res/mipmap-hdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..dffca36
Binary files /dev/null and b/sample/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/sample/src/main/res/mipmap-mdpi/ic_launcher.png b/sample/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..64ba76f
Binary files /dev/null and b/sample/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/sample/src/main/res/mipmap-mdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..dae5e08
Binary files /dev/null and b/sample/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/sample/src/main/res/mipmap-xhdpi/ic_launcher.png b/sample/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..e5ed465
Binary files /dev/null and b/sample/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..14ed0af
Binary files /dev/null and b/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png b/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..b0907ca
Binary files /dev/null and b/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..d8ae031
Binary files /dev/null and b/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..2c18de9
Binary files /dev/null and b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..beed3cd
Binary files /dev/null and b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/sample/src/main/res/values/colors.xml b/sample/src/main/res/values/colors.xml
new file mode 100644
index 0000000..2f48740
--- /dev/null
+++ b/sample/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #2196f3
+ #2196f3
+ #D81B60
+
diff --git a/sample/src/main/res/values/strings.xml b/sample/src/main/res/values/strings.xml
new file mode 100644
index 0000000..27d67af
--- /dev/null
+++ b/sample/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ MeowBottomNavigaion
+
diff --git a/sample/src/main/res/values/styles.xml b/sample/src/main/res/values/styles.xml
new file mode 100644
index 0000000..47ea7a9
--- /dev/null
+++ b/sample/src/main/res/values/styles.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..1497471
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+include ':sample', ':meowbottomnavigation'