├── foldingtabbar ├── .gitignore ├── src │ └── main │ │ ├── res │ │ ├── values │ │ │ ├── strings.xml │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ └── attrs.xml │ │ ├── drawable-hdpi │ │ │ └── ic_action_plus.png │ │ ├── drawable-mdpi │ │ │ └── ic_action_plus.png │ │ ├── drawable-xhdpi │ │ │ └── ic_action_plus.png │ │ ├── drawable-xxhdpi │ │ │ └── ic_action_plus.png │ │ ├── drawable-xxxhdpi │ │ │ └── ic_action_plus.png │ │ └── drawable │ │ │ └── background_tabbar.xml │ │ ├── java │ │ └── client │ │ │ └── yalantis │ │ │ └── com │ │ │ └── foldingtabbar │ │ │ ├── OddMenuItemsException.kt │ │ │ ├── CustomBounceInterpolator.kt │ │ │ ├── SelectedMenuItem.kt │ │ │ └── FoldingTabBar.kt │ │ └── AndroidManifest.xml ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── content_tab_bar_animation_fin-02.gif ├── app ├── src │ └── main │ │ ├── assets │ │ ├── code_pro_title.otf │ │ ├── сode_pro_bold.otf │ │ └── proxima_nova_regular.otf │ │ ├── res │ │ ├── drawable │ │ │ ├── man_on_bike.png │ │ │ └── background_additional_buttons.xml │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ ├── drawable-hdpi │ │ │ ├── ic_img_chat_one.png │ │ │ ├── ic_nearby_icon.png │ │ │ ├── ic_new_chat_icon.png │ │ │ ├── ic_profile_icon.png │ │ │ ├── ic_search_icon.png │ │ │ └── ic_settings_icon.png │ │ ├── drawable-mdpi │ │ │ ├── ic_img_chat_one.png │ │ │ ├── ic_nearby_icon.png │ │ │ ├── ic_new_chat_icon.png │ │ │ ├── ic_profile_icon.png │ │ │ ├── ic_search_icon.png │ │ │ └── ic_settings_icon.png │ │ ├── drawable-xhdpi │ │ │ ├── ic_img_chat_one.png │ │ │ ├── ic_nearby_icon.png │ │ │ ├── ic_profile_icon.png │ │ │ ├── ic_search_icon.png │ │ │ ├── ic_new_chat_icon.png │ │ │ └── ic_settings_icon.png │ │ ├── drawable-xxhdpi │ │ │ ├── ic_nearby_icon.png │ │ │ ├── ic_search_icon.png │ │ │ ├── ic_img_chat_one.png │ │ │ ├── ic_new_chat_icon.png │ │ │ ├── ic_profile_icon.png │ │ │ └── ic_settings_icon.png │ │ ├── drawable-xxxhdpi │ │ │ ├── ic_img_chat_one.png │ │ │ ├── ic_nearby_icon.png │ │ │ ├── ic_profile_icon.png │ │ │ ├── ic_search_icon.png │ │ │ ├── ic_new_chat_icon.png │ │ │ └── ic_settings_icon.png │ │ ├── values │ │ │ ├── attrs.xml │ │ │ ├── strings.xml │ │ │ ├── colors.xml │ │ │ ├── styles.xml │ │ │ └── dimens.xml │ │ ├── values-w820dp │ │ │ └── dimens.xml │ │ ├── menu │ │ │ └── menu_tab_bar.xml │ │ └── layout │ │ │ ├── fragment_chats.xml │ │ │ ├── activity_main.xml │ │ │ ├── fragment_profile.xml │ │ │ └── item_chat.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── client │ │ └── yalantis │ │ └── com │ │ └── foldingtabbarandroid │ │ ├── DataManager.java │ │ ├── ChatsFragment.java │ │ ├── CustomFontTextView.java │ │ ├── ChatModel.java │ │ ├── MainActivity.java │ │ ├── ChatsAdapter.java │ │ └── ProfileFragment.java ├── .gitignore ├── proguard-rules.pro └── build.gradle ├── .gitignore ├── gradle.properties ├── gradlew.bat ├── gradlew └── README.md /foldingtabbar/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':foldingtabbar' 2 | -------------------------------------------------------------------------------- /foldingtabbar/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | FoldingTabBar 3 | 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /content_tab_bar_animation_fin-02.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/content_tab_bar_animation_fin-02.gif -------------------------------------------------------------------------------- /app/src/main/assets/code_pro_title.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/app/src/main/assets/code_pro_title.otf -------------------------------------------------------------------------------- /app/src/main/assets/сode_pro_bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/app/src/main/assets/сode_pro_bold.otf -------------------------------------------------------------------------------- /app/src/main/res/drawable/man_on_bike.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/app/src/main/res/drawable/man_on_bike.png -------------------------------------------------------------------------------- /app/src/main/assets/proxima_nova_regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/app/src/main/assets/proxima_nova_regular.otf -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_img_chat_one.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/app/src/main/res/drawable-hdpi/ic_img_chat_one.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_nearby_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/app/src/main/res/drawable-hdpi/ic_nearby_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_new_chat_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/app/src/main/res/drawable-hdpi/ic_new_chat_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_profile_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/app/src/main/res/drawable-hdpi/ic_profile_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_search_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/app/src/main/res/drawable-hdpi/ic_search_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_settings_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/app/src/main/res/drawable-hdpi/ic_settings_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_img_chat_one.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/app/src/main/res/drawable-mdpi/ic_img_chat_one.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_nearby_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/app/src/main/res/drawable-mdpi/ic_nearby_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_new_chat_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/app/src/main/res/drawable-mdpi/ic_new_chat_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_profile_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/app/src/main/res/drawable-mdpi/ic_profile_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_search_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/app/src/main/res/drawable-mdpi/ic_search_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_settings_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/app/src/main/res/drawable-mdpi/ic_settings_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_img_chat_one.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/app/src/main/res/drawable-xhdpi/ic_img_chat_one.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_nearby_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/app/src/main/res/drawable-xhdpi/ic_nearby_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_profile_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/app/src/main/res/drawable-xhdpi/ic_profile_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_search_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/app/src/main/res/drawable-xhdpi/ic_search_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_nearby_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/app/src/main/res/drawable-xxhdpi/ic_nearby_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_search_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/app/src/main/res/drawable-xxhdpi/ic_search_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_new_chat_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/app/src/main/res/drawable-xhdpi/ic_new_chat_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_settings_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/app/src/main/res/drawable-xhdpi/ic_settings_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_img_chat_one.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/app/src/main/res/drawable-xxhdpi/ic_img_chat_one.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_new_chat_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/app/src/main/res/drawable-xxhdpi/ic_new_chat_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_profile_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/app/src/main/res/drawable-xxhdpi/ic_profile_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_settings_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/app/src/main/res/drawable-xxhdpi/ic_settings_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_img_chat_one.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/app/src/main/res/drawable-xxxhdpi/ic_img_chat_one.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_nearby_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/app/src/main/res/drawable-xxxhdpi/ic_nearby_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_profile_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/app/src/main/res/drawable-xxxhdpi/ic_profile_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_search_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/app/src/main/res/drawable-xxxhdpi/ic_search_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_new_chat_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/app/src/main/res/drawable-xxxhdpi/ic_new_chat_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_settings_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/app/src/main/res/drawable-xxxhdpi/ic_settings_icon.png -------------------------------------------------------------------------------- /foldingtabbar/src/main/res/drawable-hdpi/ic_action_plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/foldingtabbar/src/main/res/drawable-hdpi/ic_action_plus.png -------------------------------------------------------------------------------- /foldingtabbar/src/main/res/drawable-mdpi/ic_action_plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/foldingtabbar/src/main/res/drawable-mdpi/ic_action_plus.png -------------------------------------------------------------------------------- /foldingtabbar/src/main/res/drawable-xhdpi/ic_action_plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/foldingtabbar/src/main/res/drawable-xhdpi/ic_action_plus.png -------------------------------------------------------------------------------- /foldingtabbar/src/main/res/drawable-xxhdpi/ic_action_plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/foldingtabbar/src/main/res/drawable-xxhdpi/ic_action_plus.png -------------------------------------------------------------------------------- /foldingtabbar/src/main/res/drawable-xxxhdpi/ic_action_plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/OfficialFoldingTabBar.Android/develop/foldingtabbar/src/main/res/drawable-xxxhdpi/ic_action_plus.png -------------------------------------------------------------------------------- /app/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /foldingtabbar/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #5E5B95 5 | #4BD7BF 6 | 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Dec 28 10:00:20 PST 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_additional_buttons.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /foldingtabbar/src/main/res/drawable/background_tabbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /foldingtabbar/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 50dp 4 | 70dp 5 | 17dp 6 | 60dp 7 | -------------------------------------------------------------------------------- /foldingtabbar/src/main/java/client/yalantis/com/foldingtabbar/OddMenuItemsException.kt: -------------------------------------------------------------------------------- 1 | package client.yalantis.com.foldingtabbar 2 | 3 | /** 4 | * Created by andrewkhristyan on 11/22/16. 5 | */ 6 | class OddMenuItemsException : Exception() { 7 | override val message: String? = """Your menu should have non-odd size ¯\_(ツ)_/¯""" 8 | } -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | FoldingTabBar.Android 3 | Brian Forza 4 | 21, London 5 | Photographer, biker, father and overall \nsuper cool dude. Cheers! 6 | 7 | -------------------------------------------------------------------------------- /foldingtabbar/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /foldingtabbar/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #5E5C96 4 | #5E5C96 5 | #5E5C96 6 | #66639E 7 | #6BCEF7 8 | #958CC8 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 90dp 6 | 10dp 7 | 50dp 8 | 18sp 9 | 250dp 10 | 80dp 11 | 12 | -------------------------------------------------------------------------------- /foldingtabbar/src/main/java/client/yalantis/com/foldingtabbar/CustomBounceInterpolator.kt: -------------------------------------------------------------------------------- 1 | package client.yalantis.com.foldingtabbar 2 | 3 | import android.view.animation.Interpolator 4 | 5 | /** 6 | * Created by andrewkhristyan on 11/15/16. 7 | */ 8 | internal class CustomBounceInterpolator(val amplitude: Double = 0.1, 9 | val frequency: Double = 0.8) : Interpolator { 10 | 11 | override fun getInterpolation(time: Float): Float { 12 | return (-1.0 * Math.exp(-time / amplitude) * 13 | Math.cos(frequency * time) + 1).toFloat() 14 | } 15 | } -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | # svn 2 | *.svn* 3 | 4 | # built application files 5 | *.apk 6 | *.ap_ 7 | 8 | # files for the dex VM 9 | *.dex 10 | 11 | # Java class files 12 | *.class 13 | 14 | # generated GUI files 15 | */R.java 16 | 17 | # generated folder 18 | bin 19 | gen 20 | 21 | # local 22 | local.properties 23 | 24 | proguard_logs/ 25 | 26 | # log files 27 | log*.txt 28 | 29 | # archives 30 | *.gz 31 | *.tar 32 | *.zip 33 | 34 | #idea 35 | *.idea 36 | *.iml 37 | out/ 38 | 39 | # eclipse 40 | *.metadata 41 | *.settings 42 | *.prefs 43 | 44 | #idea 45 | *.idea 46 | *.iml 47 | out/ 48 | 49 | build/ 50 | .gradle/ 51 | .DS_Store -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/andrewkhristyan/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /foldingtabbar/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/andrewkhristyan/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_tab_bar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 12 | 13 | 17 | 18 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /app/src/main/java/client/yalantis/com/foldingtabbarandroid/DataManager.java: -------------------------------------------------------------------------------- 1 | package client.yalantis.com.foldingtabbarandroid; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | /** 7 | * Created by andrewkhristyan on 12/9/16. 8 | */ 9 | 10 | public class DataManager { 11 | 12 | private static List sChatModels = new ArrayList<>(); 13 | 14 | static { 15 | sChatModels.add(new ChatModel("Ann Drewer", "Hey, why didn't you call me?", R.drawable.ic_img_chat_one, "5 min ago")); 16 | sChatModels.add(new ChatModel("Ann Drewer", "Hey, why didn't you call me?", R.drawable.ic_img_chat_one, "5 min ago")); 17 | sChatModels.add(new ChatModel("Ann Drewer", "Hey, why didn't you call me?", R.drawable.ic_img_chat_one, "5 min ago")); 18 | } 19 | 20 | public static List getChatModels() { 21 | return sChatModels; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /foldingtabbar/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | 4 | android { 5 | compileSdkVersion 24 6 | buildToolsVersion "24.0.3" 7 | 8 | defaultConfig { 9 | minSdkVersion 19 10 | targetSdkVersion 24 11 | versionCode 1 12 | versionName "1.0" 13 | 14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 15 | 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | } 24 | 25 | dependencies { 26 | compile fileTree(dir: 'libs', include: ['*.jar']) 27 | testCompile 'junit:junit:4.12' 28 | compile "com.android.support:appcompat-v7:$support_version" 29 | compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 30 | } 31 | repositories { 32 | mavenCentral() 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_chats.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 17 | 18 | 19 | 23 | 24 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 24 5 | buildToolsVersion "24.0.3" 6 | defaultConfig { 7 | applicationId "client.yalantis.com.foldingtabbarandroid" 8 | minSdkVersion 19 9 | targetSdkVersion 24 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 25 | exclude group: 'com.android.support', module: 'support-annotations' 26 | }) 27 | compile 'com.android.support:appcompat-v7:24.2.1' 28 | compile 'com.android.support:design:24.2.1' 29 | compile project(':foldingtabbar') 30 | testCompile 'junit:junit:4.12' 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/client/yalantis/com/foldingtabbarandroid/ChatsFragment.java: -------------------------------------------------------------------------------- 1 | package client.yalantis.com.foldingtabbarandroid; 2 | 3 | import android.app.Fragment; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.support.v7.widget.LinearLayoutManager; 7 | import android.support.v7.widget.RecyclerView; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | 12 | /** 13 | * Created by andrewkhristyan on 12/9/16. 14 | */ 15 | 16 | public class ChatsFragment extends Fragment { 17 | 18 | @Nullable 19 | @Override 20 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 21 | View view = inflater.inflate(R.layout.fragment_chats, container, false); 22 | createUI(view); 23 | return view; 24 | } 25 | 26 | private void createUI(View view) { 27 | RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.recycler_view_chats); 28 | recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); 29 | recyclerView.setAdapter(new ChatsAdapter()); 30 | 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/client/yalantis/com/foldingtabbarandroid/CustomFontTextView.java: -------------------------------------------------------------------------------- 1 | package client.yalantis.com.foldingtabbarandroid; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Typeface; 6 | import android.util.AttributeSet; 7 | import android.widget.TextView; 8 | 9 | /** 10 | * Created by andrewkhristyan on 12/9/16. 11 | */ 12 | 13 | public class CustomFontTextView extends TextView { 14 | 15 | public CustomFontTextView(Context context) { 16 | this(context, null, 0); 17 | } 18 | 19 | public CustomFontTextView(Context context, AttributeSet attrs) { 20 | this(context, attrs, 0); 21 | } 22 | 23 | public CustomFontTextView(Context context, AttributeSet attrs, int defStyleAttr) { 24 | super(context, attrs, defStyleAttr); 25 | 26 | TypedArray attributeArray = context.obtainStyledAttributes(attrs, R.styleable.CustomFontTextView); 27 | 28 | String textType = attributeArray.getString(R.styleable.CustomFontTextView_font); 29 | if (textType != null) { 30 | Typeface typeface = Typeface.createFromAsset(getContext().getAssets(), textType); 31 | setTypeface(typeface); 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/client/yalantis/com/foldingtabbarandroid/ChatModel.java: -------------------------------------------------------------------------------- 1 | package client.yalantis.com.foldingtabbarandroid; 2 | 3 | import android.support.annotation.DrawableRes; 4 | 5 | /** 6 | * Created by andrewkhristyan on 12/9/16. 7 | */ 8 | 9 | public class ChatModel { 10 | private String userName; 11 | private String lastMessage; 12 | private int chatImageRes; 13 | private String time; 14 | 15 | public String getUserName() { 16 | return userName; 17 | } 18 | 19 | public void setUserName(String userName) { 20 | this.userName = userName; 21 | } 22 | 23 | public int getChatImageRes() { 24 | return chatImageRes; 25 | } 26 | 27 | public void setChatImageRes(int chatImageRes) { 28 | this.chatImageRes = chatImageRes; 29 | } 30 | 31 | public String getLastMessage() { 32 | return lastMessage; 33 | } 34 | 35 | public void setLastMessage(String lastMessage) { 36 | this.lastMessage = lastMessage; 37 | } 38 | 39 | public String getTime() { 40 | return time; 41 | } 42 | 43 | public void setTime(String time) { 44 | this.time = time; 45 | } 46 | 47 | public ChatModel(String userName, String lastMessage, @DrawableRes int chatImageRes, String time) { 48 | this.userName = userName; 49 | this.lastMessage = lastMessage; 50 | this.chatImageRes = chatImageRes; 51 | this.time = time; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 16 | 17 | 25 | 26 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /foldingtabbar/src/main/java/client/yalantis/com/foldingtabbar/SelectedMenuItem.kt: -------------------------------------------------------------------------------- 1 | package client.yalantis.com.foldingtabbar 2 | 3 | import android.content.Context 4 | import android.graphics.Canvas 5 | import android.graphics.Paint 6 | import android.graphics.Paint.ANTI_ALIAS_FLAG 7 | import android.support.annotation.ColorRes 8 | import android.support.v4.content.res.ResourcesCompat 9 | import android.util.AttributeSet 10 | import android.widget.ImageView 11 | 12 | 13 | /** 14 | * Created by andrewkhristyan on 11/21/16. 15 | */ 16 | class SelectedMenuItem : ImageView { 17 | 18 | private var mCirclePaint: Paint 19 | private var radius: Float = 0f 20 | 21 | constructor(context: Context, @ColorRes color: Int) : this(context, null, color) 22 | 23 | constructor(context: Context, attrs: AttributeSet?, @ColorRes color: Int) : this(context, attrs, 0, color) 24 | 25 | constructor(context: Context, attrs: AttributeSet?, defStyleRes: Int, @ColorRes color: Int) 26 | : super(context, attrs, defStyleRes) { 27 | mCirclePaint = Paint(ANTI_ALIAS_FLAG).apply { 28 | this.color = ResourcesCompat.getColor(resources, color, null) 29 | } 30 | } 31 | 32 | override fun onDraw(canvas: Canvas) { 33 | super.onDraw(canvas) 34 | if (isActivated) { 35 | drawCircleIcon(canvas) 36 | } 37 | } 38 | 39 | /** 40 | * Here we are making scale drawing of selection 41 | * */ 42 | private fun drawCircleIcon(canvas: Canvas) { 43 | canvas.drawCircle(canvas.width / 2.0f, canvas.height - paddingBottom / 1.5f, radius, mCirclePaint) 44 | if (radius <= canvas.width / 20.0f) { 45 | radius++ 46 | invalidate() 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /app/src/main/java/client/yalantis/com/foldingtabbarandroid/MainActivity.java: -------------------------------------------------------------------------------- 1 | package client.yalantis.com.foldingtabbarandroid; 2 | 3 | import android.app.Fragment; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.view.MenuItem; 7 | 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | import client.yalantis.com.foldingtabbar.FoldingTabBar; 11 | 12 | public class MainActivity extends AppCompatActivity { 13 | 14 | @Override 15 | protected void onCreate(Bundle savedInstanceState) { 16 | super.onCreate(savedInstanceState); 17 | setContentView(R.layout.activity_main); 18 | FoldingTabBar tabBar = (FoldingTabBar) findViewById(R.id.folding_tab_bar); 19 | changeFragment(new ProfileFragment()); 20 | 21 | tabBar.setOnFoldingItemClickListener(new FoldingTabBar.OnFoldingItemSelectedListener() { 22 | @Override 23 | public boolean onFoldingItemSelected(@NotNull MenuItem item) { 24 | switch (item.getItemId()) { 25 | case R.id.ftb_menu_nearby: 26 | break; 27 | case R.id.ftb_menu_new_chat: 28 | changeFragment(new ChatsFragment()); 29 | break; 30 | case R.id.ftb_menu_profile: 31 | changeFragment(new ProfileFragment()); 32 | break; 33 | case R.id.ftb_menu_settings: 34 | break; 35 | } 36 | return false; 37 | } 38 | }); 39 | } 40 | 41 | private void changeFragment(Fragment fragment) { 42 | getFragmentManager().beginTransaction().replace(R.id.container, fragment).commit(); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_profile.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | 17 | 26 | 27 | 35 | 36 | 44 | 45 | -------------------------------------------------------------------------------- /app/src/main/java/client/yalantis/com/foldingtabbarandroid/ChatsAdapter.java: -------------------------------------------------------------------------------- 1 | package client.yalantis.com.foldingtabbarandroid; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.ImageView; 8 | import android.widget.TextView; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * Created by andrewkhristyan on 12/9/16. 14 | */ 15 | 16 | public class ChatsAdapter extends RecyclerView.Adapter { 17 | 18 | private List mChatModels = DataManager.getChatModels(); 19 | 20 | @Override 21 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 22 | View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_chat, parent, false); 23 | return new ViewHolder(view); 24 | } 25 | 26 | @Override 27 | public void onBindViewHolder(ViewHolder holder, int position) { 28 | holder.chatName.setText(mChatModels.get(position).getUserName()); 29 | holder.lastMessage.setText(mChatModels.get(position).getLastMessage()); 30 | holder.time.setText(mChatModels.get(position).getTime()); 31 | holder.userImage.setImageResource(mChatModels.get(position).getChatImageRes()); 32 | } 33 | 34 | @Override 35 | public int getItemCount() { 36 | return mChatModels.size(); 37 | } 38 | 39 | class ViewHolder extends RecyclerView.ViewHolder { 40 | private TextView chatName; 41 | private TextView lastMessage; 42 | private ImageView userImage; 43 | private TextView time; 44 | 45 | public ViewHolder(View itemView) { 46 | super(itemView); 47 | chatName = (TextView) itemView.findViewById(R.id.text_view_chat_name); 48 | lastMessage = (TextView) itemView.findViewById(R.id.text_view_last_message); 49 | time = (TextView) itemView.findViewById(R.id.text_view_time); 50 | userImage = (ImageView) itemView.findViewById(R.id.image_view_chat); 51 | } 52 | 53 | 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/client/yalantis/com/foldingtabbarandroid/ProfileFragment.java: -------------------------------------------------------------------------------- 1 | package client.yalantis.com.foldingtabbarandroid; 2 | 3 | import android.animation.Animator; 4 | import android.annotation.TargetApi; 5 | import android.app.Fragment; 6 | import android.os.Build; 7 | import android.os.Bundle; 8 | import android.support.annotation.Nullable; 9 | import android.support.annotation.RequiresApi; 10 | import android.view.LayoutInflater; 11 | import android.view.View; 12 | import android.view.ViewAnimationUtils; 13 | import android.view.ViewGroup; 14 | import android.view.animation.AccelerateDecelerateInterpolator; 15 | 16 | /** 17 | * Created by andrewkhristyan on 12/9/16. 18 | */ 19 | 20 | public class ProfileFragment extends Fragment { 21 | 22 | @Nullable 23 | @Override 24 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 25 | View view = inflater.inflate(R.layout.fragment_profile, container, false); 26 | return view; 27 | } 28 | 29 | 30 | @Override 31 | public void onViewCreated(final View view, Bundle savedInstanceState) { 32 | super.onViewCreated(view, savedInstanceState); 33 | 34 | view.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { 35 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 36 | @Override 37 | public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { 38 | v.removeOnLayoutChangeListener(this); 39 | int currentApiVersion = android.os.Build.VERSION.SDK_INT; 40 | if (currentApiVersion >= android.os.Build.VERSION_CODES.LOLLIPOP) { 41 | animateRevealColorFromCoordinates((ViewGroup) view, right / 2, bottom); 42 | } 43 | } 44 | }); 45 | } 46 | 47 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) 48 | private Animator animateRevealColorFromCoordinates(ViewGroup viewRoot, int x, int y) { 49 | float finalRadius = (float) Math.hypot(viewRoot.getWidth(), viewRoot.getHeight()); 50 | 51 | Animator anim = ViewAnimationUtils.createCircularReveal(viewRoot, x, y, 0, finalRadius); 52 | anim.setDuration(1000); 53 | anim.setInterpolator(new AccelerateDecelerateInterpolator()); 54 | anim.start(); 55 | return anim; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_chat.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 18 | 19 | 28 | 29 | 38 | 39 | 47 | 48 | 53 | 54 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OfficialFoldingTabBar.Android 2 | [![License](http://img.shields.io/badge/license-MIT-green.svg?style=flat)]() 3 | [![](https://jitpack.io/v/Yalantis/OfficialFoldingTabBar.Android.svg)](https://jitpack.io/#Yalantis/OfficialFoldingTabBar.Android) 4 | [![Yalantis](https://raw.githubusercontent.com/Yalantis/PullToRefresh/develop/PullToRefreshDemo/Resources/badge_dark.png)](https://yalantis.com/?utm_source=github) 5 | 6 | Folding Tab Bar and Tab Bar Menu 7 | 8 | 9 | ## Requirements 10 | - Android SDK 19+ 11 | 12 | ## Usage 13 | 14 | Add to your root build.gradle: 15 | ```Groovy 16 | allprojects { 17 | repositories { 18 | ... 19 | maven { url "https://jitpack.io" } 20 | } 21 | } 22 | ``` 23 | 24 | Add the dependency: 25 | ```Groovy 26 | dependencies { 27 | compile 'com.github.Yalantis:OfficialFoldingTabBar.Android:v0.9' 28 | } 29 | ``` 30 | ## How to use this library 31 | 32 | It’s very simple. On Android we have the @menu resource type. We can think of FoldingTabBar as a solution that provides folding menu effect. Let’s create our menu file: 33 | ```xml 34 | 35 | 36 | 40 | 41 | 45 | 46 | 50 | 51 | 55 | 56 | 57 | ``` 58 | Looks good. This is the menu that you’re using for dialogs, toolbar menus, or the navigation drawer. The coolest thing is that you can easily switch from the navigation drawer to our FoldingTabBar. Or you can use both menus simultaneously on the same screen; not all Android action bars have that flexibility. 59 | 60 | Here’s an example of how you can implement our FoldingTabBar into your layout file: 61 | ```xml 62 | 71 | 72 | ... 73 | 74 | 82 | 83 | ... 84 | 85 | 86 | ``` 87 | Initialize it in your java/kotlin code 88 | ```java 89 | FoldingTabBar tabBar = (FoldingTabBar) findViewById(R.id.folding_tab_bar); 90 | ``` 91 | 92 | As you can see, we have some custom attributes. The main and required attribute is app:menu – here you can link your menu file to our component. 93 | There are also some additional attributes: 94 | 95 | *app:itemPadding* - sets padding for your menu items. Default item padding is 17dp. 96 | *app:mainImage* - here you can link your image resource for the main image. 97 | *app:selectionColor* - our menu supports color selection. You can change the menu’s color here. 98 | 99 | Our menu is flexible, so you can use wrap_content or hard-coded sizes. When you’re using wrap_content, the size will equal 70dp. 100 | 101 | ## How to use our FoldingTabBar in Java code 102 | 103 | Here we have two interfaces. The first works when your menu item is pressed: 104 | ```java 105 | tabBar.setOnFoldingItemClickListener(new FoldingTabBar.OnFoldingItemSelectedListener() { 106 | @Override 107 | public boolean onFoldingItemSelected(@NotNull MenuItem item) { 108 | switch (item.getItemId()) { 109 | case R.id.ftb_menu_nearby: 110 | mViewPager.setCurrentItem(0); 111 | break; 112 | case R.id.ftb_menu_new_chat: 113 | mViewPager.setCurrentItem(1); 114 | break; 115 | case R.id.ftb_menu_profile: 116 | mViewPager.setCurrentItem(2); 117 | break; 118 | case R.id.ftb_menu_settings: 119 | mViewPager.setCurrentItem(3); 120 | break; 121 | } 122 | return false; 123 | } 124 | }); 125 | ``` 126 | The second works when a user presses the home button: 127 | ```java 128 | tabBar.setOnMainButtonClickListener(new FoldingTabBar.OnMainButtonClickedListener() { 129 | @Override 130 | public void onMainButtonClicked() { 131 | 132 | } 133 | }); 134 | ``` 135 | We created two interfaces instead of one because this corresponds with the Interface Segregation principle (from SOLID). 136 | 137 | ## Technology stack 138 | 139 | We chose Kotlin as the language for our library. We already have a few components in Kotlin; we love Kotlin because it’s a powerful language that makes it much more fun to develop apps than Java does. It was a good fit for Android action bar development. 140 | 141 | ## Collections 142 | 143 | Here we’re using some functional “magic” of Kotlin collections: 144 | ```kotlin 145 | mData = mMenu.visibleItems.map { 146 | initAndAddMenuItem(it) 147 | } 148 | ``` 149 | Also, note that we’ve used forEach instead of for loops. 150 | 151 | ## Null Safety 152 | 153 | This fantastic feature of Kotlin is used everywhere across our library (where needed, of course). Now our code is much cleaner and more understandable. 154 | 155 | ## Lambda functions 156 | 157 | We’ve only had this feature in Java since version 8. But Java 8 is not yet ready for Android. In Kotlin we have this feature by default. 158 | ```kotlin 159 | rotationAnimator.addUpdateListener { 160 | valueAnimator -> 161 | val value = valueAnimator.animatedValue as Float 162 | mainImageView.rotation = value 163 | } 164 | ``` 165 | Really beautiful, isn’t it? 166 | 167 | ## Apply function 168 | 169 | The apply function defines an extension function for all types. When you invoke the apply function, it calls the closure passed as a parameter and then returns the receiver object that the closure ran on. This is an amazing feature! 170 | ```kotlin 171 | val scalingAnimator = ValueAnimator.ofInt(mSize, destWidth).apply { 172 | addUpdateListener(scaleAnimator) 173 | addListener(rollUpListener) 174 | } 175 | ``` 176 | 177 | ## Animations 178 | 179 | We’re using different ValueAnimators and, of course, AnimationSets for playing our animators together. Obviously, we made our own interpolator: 180 | 181 | ```kotlin 182 | internal class CustomBounceInterpolator(val amplitude: Double = 0.1, 183 | val frequency: Double = 0.8) : Interpolator { 184 | 185 | override fun getInterpolation(time: Float): Float { 186 | return (-1.0 * Math.exp(-time / amplitude) * 187 | Math.cos(frequency * time) + 1).toFloat() 188 | } 189 | } 190 | ``` 191 | Enjoy! :) 192 | 193 | Also check out: 194 | 195 | Kotlin vs Java: [basic syntax differences](https://yalantis.com/blog/kotlin-vs-java-syntax/) 196 | 197 | We constantly work on various open-source elements of navigation bar or styling tabs, and focus on providing high-quality open-source libraries for various purposes. 198 | 199 | ## Let us know! 200 | 201 | We’d be really happy if you sent us links to your projects where you use our component. Just send an email to github@yalantis.com And do let us know if you have any questions or suggestion regarding the animation. 202 | 203 | P.S. We’re going to publish more awesomeness wrapped in code and a tutorial on how to make UI for iOS (Android) better than better. Stay tuned! 204 | 205 | ##License 206 | 207 | The MIT License (MIT) 208 | 209 | Copyright © 2016 Yalantis 210 | 211 | Permission is hereby granted, free of charge, to any person obtaining a copy 212 | of this software and associated documentation files (the "Software"), to deal 213 | in the Software without restriction, including without limitation the rights 214 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 215 | copies of the Software, and to permit persons to whom the Software is 216 | furnished to do so, subject to the following conditions: 217 | 218 | The above copyright notice and this permission notice shall be included in 219 | all copies or substantial portions of the Software. 220 | 221 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 222 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 223 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 224 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 225 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 226 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 227 | THE SOFTWARE. 228 | 229 | -------------------------------------------------------------------------------- /foldingtabbar/src/main/java/client/yalantis/com/foldingtabbar/FoldingTabBar.kt: -------------------------------------------------------------------------------- 1 | package client.yalantis.com.foldingtabbar 2 | 3 | import android.animation.Animator 4 | import android.animation.AnimatorSet 5 | import android.animation.ValueAnimator 6 | import android.content.Context 7 | import android.content.res.TypedArray 8 | import android.os.Parcel 9 | import android.os.Parcelable 10 | import android.support.annotation.MenuRes 11 | import android.support.v7.view.SupportMenuInflater 12 | import android.support.v7.view.menu.MenuBuilder 13 | import android.support.v7.view.menu.MenuItemImpl 14 | import android.util.AttributeSet 15 | import android.util.Log 16 | import android.view.* 17 | import android.view.animation.BounceInterpolator 18 | import android.widget.ImageView 19 | import android.widget.LinearLayout 20 | 21 | 22 | /** 23 | * Created by andrewkhristyan on 11/8/16. 24 | */ 25 | class FoldingTabBar : LinearLayout { 26 | 27 | private val ANIMATION_DURATION = 500L 28 | private val START_DELAY = 150L 29 | private val MAIN_ROTATION_START = 0f 30 | private val MAIN_ROTATION_END = 405f 31 | private val ITEM_ROTATION_START = 180f 32 | private val ITEM_ROTATION_END = 360f 33 | private val ROLL_UP_ROTATION_START = -45f 34 | private val ROLL_UP_ROTATION_END = 360f 35 | 36 | var onFoldingItemClickListener: OnFoldingItemSelectedListener? = null 37 | var onMainButtonClickListener: OnMainButtonClickedListener? = null 38 | 39 | private lateinit var mData: List 40 | 41 | private var mExpandingSet: AnimatorSet = AnimatorSet() 42 | private var mRollupSet: AnimatorSet = AnimatorSet() 43 | private var isAnimating: Boolean = false 44 | 45 | private var mMenu: MenuBuilder 46 | 47 | private var mSize: Int = 0 48 | private var indexCounter = 0 49 | private var mainImageView: ImageView = ImageView(context) 50 | private var selectedImageView: ImageView? = null 51 | private var selectedIndex: Int = 0 52 | 53 | private var itemsPadding: Int = 0 54 | private var drawableResource: Int = 0 55 | private var selectionColor: Int = 0 56 | 57 | constructor(context: Context) : this(context, null) 58 | 59 | constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) 60 | 61 | constructor(context: Context, attrs: AttributeSet?, defStyleRes: Int) 62 | : super(context, attrs, defStyleRes) { 63 | mMenu = MenuBuilder(context) 64 | gravity = Gravity.CENTER 65 | 66 | if (background == null) { 67 | setBackgroundResource(R.drawable.background_tabbar) 68 | } 69 | val a: TypedArray = initAttrs(attrs, defStyleRes) 70 | 71 | mSize = getSizeDimension() 72 | initViewTreeObserver(a) 73 | } 74 | 75 | /** 76 | * Initializing attributes 77 | */ 78 | private fun initAttrs(attrs: AttributeSet?, defStyleRes: Int) = 79 | context.obtainStyledAttributes(attrs, 80 | R.styleable.FoldingTabBar, 0, 81 | defStyleRes) 82 | 83 | /** 84 | * Here is size of our FoldingTabBar. Simple 85 | */ 86 | private fun getSizeDimension(): Int = resources.getDimensionPixelSize(R.dimen.ftb_size_normal) 87 | 88 | /** 89 | * This is the padding for menu items 90 | */ 91 | private fun getItemsPadding(): Int = resources.getDimensionPixelSize(R.dimen.ftb_item_padding) 92 | 93 | /** 94 | * When folding tab bar pre-draws we should initialize 95 | * inflate our menu, and also add menu items, into the 96 | * FoldingTabBar, also here we are initializing animators 97 | * and animation sets 98 | */ 99 | private fun initViewTreeObserver(a: TypedArray) { 100 | viewTreeObserver.addOnPreDrawListener(object: ViewTreeObserver.OnPreDrawListener { 101 | override fun onPreDraw(): Boolean { 102 | viewTreeObserver.removeOnPreDrawListener(this) 103 | isAnimating = true 104 | initAttributesValues(a) 105 | initExpandAnimators() 106 | initRollUpAnimators() 107 | select(selectedIndex) 108 | return true 109 | } 110 | }) 111 | } 112 | 113 | /** 114 | * Here we are initializing default values 115 | * Also here we are binding new attributes into this values 116 | * 117 | * @param a - incoming typed array with attributes values 118 | */ 119 | private fun initAttributesValues(a: TypedArray) { 120 | drawableResource = R.drawable.ic_action_plus 121 | itemsPadding = getItemsPadding() 122 | selectionColor = R.color.ftb_selected_dot_color 123 | if (a.hasValue(R.styleable.FoldingTabBar_mainImage)) { 124 | drawableResource = a.getResourceId(R.styleable.FoldingTabBar_mainImage, 0) 125 | } 126 | if (a.hasValue(R.styleable.FoldingTabBar_itemPadding)) { 127 | itemsPadding = a.getDimensionPixelSize(R.styleable.FoldingTabBar_itemPadding, 0) 128 | } 129 | if (a.hasValue(R.styleable.FoldingTabBar_selectionColor)) { 130 | selectionColor = a.getResourceId(R.styleable.FoldingTabBar_selectionColor, 0) 131 | } 132 | if (a.hasValue(R.styleable.FoldingTabBar_menu)) { 133 | inflateMenu(a.getResourceId(R.styleable.FoldingTabBar_menu, 0)) 134 | } 135 | } 136 | 137 | /** 138 | * Expand animation. Whole animators 139 | */ 140 | private fun initExpandAnimators() { 141 | mExpandingSet.duration = ANIMATION_DURATION 142 | 143 | val destWidth = childCount.times(mSize) 144 | 145 | val rotationSet = AnimatorSet() 146 | val scalingSet = AnimatorSet() 147 | 148 | val scalingAnimator = ValueAnimator.ofInt(mSize, destWidth).apply { 149 | addUpdateListener(scaleAnimator) 150 | addListener(rollUpListener) 151 | } 152 | 153 | val rotationAnimator = ValueAnimator.ofFloat(MAIN_ROTATION_START, MAIN_ROTATION_END).apply { 154 | addUpdateListener { valueAnimator -> 155 | val value = valueAnimator.animatedValue as Float 156 | mainImageView.rotation = value 157 | } 158 | } 159 | 160 | mData.forEach { item -> 161 | ValueAnimator.ofFloat(ITEM_ROTATION_START, ITEM_ROTATION_END).apply { 162 | addUpdateListener { 163 | val fraction = it.animatedFraction 164 | item.scaleX = fraction 165 | item.scaleY = fraction 166 | item.rotation = it.animatedValue as Float 167 | } 168 | addListener(expandingListener) 169 | rotationSet.playTogether(this) 170 | } 171 | } 172 | 173 | scalingSet.playTogether(scalingAnimator, rotationAnimator) 174 | scalingSet.interpolator = CustomBounceInterpolator() 175 | rotationSet.interpolator = BounceInterpolator() 176 | 177 | rotationSet.startDelay = START_DELAY 178 | mExpandingSet.playTogether(scalingSet, rotationSet) 179 | } 180 | 181 | /** 182 | * Roll-up animators. Whole roll-up animation 183 | */ 184 | private fun initRollUpAnimators() { 185 | mRollupSet.duration = ANIMATION_DURATION 186 | 187 | val destWidth = mMenu.size().times(mSize) 188 | 189 | val rotationSet = AnimatorSet() 190 | 191 | val scalingAnimator = ValueAnimator.ofInt(destWidth, mSize) 192 | val rotationAnimator = ValueAnimator.ofFloat(ROLL_UP_ROTATION_START, ROLL_UP_ROTATION_END) 193 | 194 | scalingAnimator.addUpdateListener(scaleAnimator) 195 | mRollupSet.addListener(rollUpListener) 196 | 197 | rotationAnimator.addUpdateListener { valueAnimator -> 198 | val value = valueAnimator.animatedValue as Float 199 | mainImageView.rotation = value 200 | } 201 | 202 | val scalingSet = AnimatorSet().apply { 203 | playTogether(scalingAnimator, rotationAnimator) 204 | interpolator = CustomBounceInterpolator() 205 | } 206 | rotationSet.interpolator = BounceInterpolator() 207 | 208 | 209 | mRollupSet.playTogether(scalingSet, rotationSet) 210 | } 211 | 212 | /** 213 | * Menu inflating, we are getting list of visible items, 214 | * and use them in method @link initAndAddMenuItem 215 | * Be careful, don't use non-odd number of menu items 216 | * FTB works not good for such menus. Anyway you will have an exception 217 | * 218 | * @param resId your menu resource id 219 | */ 220 | private fun inflateMenu(@MenuRes resId: Int) { 221 | getMenuInflater().inflate(resId, mMenu) 222 | if (mMenu.visibleItems.size % 2 != 0) { 223 | throw OddMenuItemsException() 224 | } 225 | mData = mMenu.visibleItems.map { 226 | initAndAddMenuItem(it) 227 | } 228 | initMainButton(mMenu.visibleItems.size / 2) 229 | } 230 | 231 | /** 232 | * Here we are resolving sizes of your Folding tab bar. 233 | * Depending on 234 | * @param measureSpec we can understand what kind of parameters 235 | * do you using in your layout file 236 | * In case if you are using wrap_content, we are using @dimen/ftb_size_normal 237 | * by default 238 | * 239 | * In case if you need some custom sizes, please use them) 240 | */ 241 | private fun resolveAdjustedSize(desiredSize: Int, measureSpec: Int): Int { 242 | val specMode = View.MeasureSpec.getMode(measureSpec) 243 | val specSize = View.MeasureSpec.getSize(measureSpec) 244 | 245 | return when (specMode) { 246 | View.MeasureSpec.UNSPECIFIED -> 247 | desiredSize 248 | View.MeasureSpec.AT_MOST -> 249 | Math.min(desiredSize, specSize) 250 | View.MeasureSpec.EXACTLY -> 251 | specSize 252 | else -> 253 | desiredSize 254 | } 255 | } 256 | 257 | /** 258 | * Here we are overriding onMeasure and here we are making our control 259 | * squared 260 | */ 261 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 262 | super.onMeasure(widthMeasureSpec, heightMeasureSpec) 263 | if (!isAnimating) { 264 | val preferredSize = getSizeDimension() 265 | mSize = resolveAdjustedSize(preferredSize, widthMeasureSpec) 266 | setMeasuredDimension(mSize, mSize) 267 | } 268 | } 269 | 270 | /** 271 | * Here we are saving view state 272 | */ 273 | override fun onSaveInstanceState(): Parcelable { 274 | val superState = super.onSaveInstanceState() 275 | return SavedState(superState).apply { 276 | selection = selectedIndex 277 | } 278 | } 279 | 280 | /** 281 | * Here we are restoring view state (state, selection) 282 | */ 283 | override fun onRestoreInstanceState(state: Parcelable?) { 284 | (state as SavedState).let { 285 | super.onRestoreInstanceState(it.superState) 286 | selectedIndex = it.selection 287 | } 288 | } 289 | 290 | val scaleAnimator = ValueAnimator.AnimatorUpdateListener { valueAnimator -> 291 | layoutParams = layoutParams.apply { 292 | width = valueAnimator.animatedValue as Int 293 | } 294 | } 295 | 296 | /** 297 | * Main button (+/x) initialization 298 | * Adding listener to the main button click 299 | */ 300 | private fun initMainButton(mainButtonIndex: Int) { 301 | mainImageView.setImageResource(drawableResource) 302 | mainImageView.layoutParams = ViewGroup.LayoutParams(mSize, mSize) 303 | mainImageView.setOnClickListener { 304 | onMainButtonClickListener?.onMainButtonClicked() 305 | animateMenu() 306 | } 307 | addView(mainImageView, mainButtonIndex) 308 | mainImageView.setPadding(itemsPadding, itemsPadding, itemsPadding, itemsPadding) 309 | } 310 | 311 | /** 312 | * @param menuItem object from Android Sdk. This is same menu item 313 | * that you are using e.g in NavigationView or any kind of native menus 314 | */ 315 | private fun initAndAddMenuItem(menuItem: MenuItemImpl): SelectedMenuItem { 316 | return SelectedMenuItem(context, selectionColor).apply { 317 | setImageDrawable(menuItem.icon) 318 | layoutParams = ViewGroup.LayoutParams(mSize, mSize) 319 | setPadding(itemsPadding, itemsPadding, itemsPadding, itemsPadding) 320 | visibility = View.GONE 321 | isActivated = menuItem.isChecked 322 | addView(this, indexCounter) 323 | 324 | setOnClickListener { 325 | onFoldingItemClickListener?.onFoldingItemSelected(menuItem) ?: 326 | Log.e("FoldingTabBar", "FoldingItemClickListener is null") 327 | menuItem.isChecked = true 328 | 329 | selectedImageView?.isActivated = false 330 | selectedImageView = this 331 | selectedIndex = indexOfChild(this) 332 | animateMenu() 333 | } 334 | 335 | indexCounter++ 336 | } 337 | } 338 | 339 | fun select(position: Int) { 340 | selectedImageView = (getChildAt(position) as SelectedMenuItem).apply { 341 | isActivated = true 342 | } 343 | } 344 | 345 | 346 | /** 347 | * measuredWidth - mSize = 0 we can understand that our menu is closed 348 | * But on some devices I've found a case when we don't have exactly 0. So 349 | * now we defined some range to understand what is the state of our menu 350 | */ 351 | private fun animateMenu() { 352 | if ((measuredWidth - mSize) in -2..2) { 353 | expand() 354 | } else { 355 | rollUp() 356 | } 357 | } 358 | 359 | /** 360 | * These two public functions can be used to open our menu 361 | * externally 362 | */ 363 | fun expand() { 364 | mExpandingSet.start() 365 | } 366 | 367 | fun rollUp() { 368 | mRollupSet.start() 369 | } 370 | 371 | /** 372 | * Getting SupportMenuInflater to get all visible items from 373 | * menu object 374 | */ 375 | private fun getMenuInflater(): MenuInflater = SupportMenuInflater(context) 376 | 377 | /** 378 | * Here we should hide all items, and deactivate menu item 379 | */ 380 | private val rollUpListener = object : Animator.AnimatorListener { 381 | override fun onAnimationStart(animator: Animator) { 382 | mData.forEach { 383 | it.visibility = View.GONE 384 | } 385 | selectedImageView?.isActivated = false 386 | } 387 | 388 | override fun onAnimationEnd(animator: Animator) { 389 | } 390 | 391 | override fun onAnimationCancel(animator: Animator) { 392 | } 393 | 394 | override fun onAnimationRepeat(animator: Animator) { 395 | } 396 | } 397 | 398 | /** 399 | * This listener we need to show our Menu items 400 | * And also after animation was finished we should activate 401 | * our SelectableImageView 402 | */ 403 | private val expandingListener = object : Animator.AnimatorListener { 404 | 405 | override fun onAnimationStart(animator: Animator) { 406 | mData.forEach { 407 | it.visibility = View.VISIBLE 408 | } 409 | } 410 | 411 | override fun onAnimationEnd(animator: Animator) { 412 | selectedImageView?.isActivated = true 413 | } 414 | 415 | override fun onAnimationCancel(animator: Animator) { 416 | } 417 | 418 | override fun onAnimationRepeat(animator: Animator) { 419 | } 420 | } 421 | 422 | /** 423 | * Listener for handling events on folding items. 424 | */ 425 | interface OnFoldingItemSelectedListener { 426 | /** 427 | * Called when an item in the folding tab bar menu is selected. 428 | 429 | * @param item The selected item 430 | * * 431 | * * 432 | * @return true to display the item as the selected item 433 | */ 434 | fun onFoldingItemSelected(item: MenuItem): Boolean 435 | } 436 | 437 | /** 438 | * Listener for handling events on folding main button 439 | */ 440 | interface OnMainButtonClickedListener { 441 | /** 442 | * Called when the main button was pressed 443 | */ 444 | fun onMainButtonClicked() 445 | } 446 | 447 | /** 448 | * We have to save state and selection of our View 449 | */ 450 | internal class SavedState : View.BaseSavedState { 451 | var selection: Int = 0 452 | 453 | internal constructor(superState: Parcelable) : super(superState) 454 | 455 | private constructor(inp: Parcel) : super(inp) { 456 | selection = inp.readInt() 457 | } 458 | 459 | override fun writeToParcel(out: Parcel, flags: Int) { 460 | super.writeToParcel(out, flags) 461 | out.writeInt(selection) 462 | } 463 | 464 | companion object { 465 | @JvmField val CREATOR: Parcelable.Creator = object : Parcelable.Creator { 466 | override fun createFromParcel(source: Parcel): SavedState { 467 | return SavedState(source) 468 | } 469 | 470 | override fun newArray(size: Int): Array { 471 | return arrayOfNulls(size) 472 | } 473 | } 474 | } 475 | } 476 | 477 | } --------------------------------------------------------------------------------