├── 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 |
--------------------------------------------------------------------------------
/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 | []()
3 | [](https://jitpack.io/#Yalantis/OfficialFoldingTabBar.Android)
4 | [](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 |
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 | }
--------------------------------------------------------------------------------