├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── gradle-mvn-push.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── library ├── .gitignore ├── build.gradle ├── gradle.properties ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── github │ │ └── clans │ │ └── fab │ │ ├── FloatingActionButton.java │ │ ├── FloatingActionMenu.java │ │ ├── Label.java │ │ └── Util.java │ └── res │ ├── anim │ ├── fab_scale_down.xml │ ├── fab_scale_up.xml │ ├── fab_slide_in_from_left.xml │ ├── fab_slide_in_from_right.xml │ ├── fab_slide_out_to_left.xml │ └── fab_slide_out_to_right.xml │ ├── drawable-hdpi │ └── fab_add.png │ ├── drawable-mdpi │ └── fab_add.png │ ├── drawable-xhdpi │ └── fab_add.png │ ├── drawable-xxhdpi │ └── fab_add.png │ └── values │ ├── attrs.xml │ ├── dimens.xml │ └── ids.xml ├── sample ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── dmytrotarianyk │ │ └── fab │ │ └── ApplicationTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── github │ │ └── clans │ │ └── fab │ │ └── sample │ │ ├── HomeFragment.java │ │ ├── MainActivity.java │ │ ├── MenusFragment.java │ │ └── ProgressFragment.java │ └── res │ ├── anim │ ├── hide_to_bottom.xml │ ├── jump_from_down.xml │ ├── jump_to_down.xml │ ├── scale_down.xml │ ├── scale_up.xml │ └── show_from_bottom.xml │ ├── drawable-hdpi │ ├── ic_close.png │ ├── ic_edit.png │ ├── ic_menu.png │ ├── ic_nav_item.png │ ├── ic_progress.png │ └── ic_star.png │ ├── drawable-mdpi │ ├── ic_close.png │ ├── ic_edit.png │ ├── ic_menu.png │ ├── ic_nav_item.png │ ├── ic_progress.png │ └── ic_star.png │ ├── drawable-v21 │ └── fab_label_background.xml │ ├── drawable-xhdpi │ ├── ic_close.png │ ├── ic_edit.png │ ├── ic_menu.png │ ├── ic_nav_item.png │ ├── ic_progress.png │ └── ic_star.png │ ├── drawable-xxhdpi │ ├── ic_close.png │ ├── ic_edit.png │ ├── ic_menu.png │ ├── ic_nav_item.png │ ├── ic_progress.png │ └── ic_star.png │ ├── drawable-xxxhdpi │ ├── ic_nav_item.png │ └── ic_progress.png │ ├── drawable │ └── fab_label_background.xml │ ├── layout │ ├── drawer_header.xml │ ├── home_fragment.xml │ ├── main_activity.xml │ ├── menus_fragment.xml │ ├── progress_fragment.xml │ └── toolbar.xml │ ├── menu │ ├── main_menu.xml │ └── menu_drawer.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── values-v21 │ └── styles.xml │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── screenshots ├── main_screen.png ├── menu_closed.png ├── menu_custom_opened.png ├── menu_default_opened.png ├── menu_down_opened.png ├── menu_mini_opened.png ├── menu_right_opened.png ├── progress_background.png └── progress_no_background.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | 15 | # Gradle files 16 | .gradle/ 17 | build/ 18 | 19 | # Local configuration file (sdk path, etc) 20 | local.properties 21 | 22 | # Proguard folder generated by Eclipse 23 | proguard/ 24 | 25 | # Log Files 26 | *.log 27 | 28 | .idea/ 29 | .DS_Store 30 | *.iml -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WARNING! FURTHER DEVELOPMENT AND SUPPORT IS DISCONTINUED. 2 | # FloatingActionButton 3 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.clans/fab/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.github.clans/fab) [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-Clans%2FFloatingActionButton-blue.svg?style=flat)](http://android-arsenal.com/details/1/1684) 4 | 5 | Yet another implementation of [Floating Action Button](http://www.google.com/design/spec/components/buttons.html#buttons-floating-action-button) for Android with lots of features. 6 | 7 | # Requirements 8 | The library requires Android **API Level 14+**. 9 | 10 | # Demo 11 | Watch a short **[Demo Video](https://youtu.be/XngUY3PN1IQ)** on YouTube or try it using **[Android simulator in the browser](https://appetize.io/app/ffreudbwmyedw5trzhyjknd2jg)** on Appetize.io. 12 | Sample APK can be found in **[Releases](https://github.com/Clans/FloatingActionButton/releases)** section. 13 | 14 | # Screenshots 15 | ![Main screen](/screenshots/main_screen.png) ![Menu closed](/screenshots/menu_closed.png) ![Menu default opened](/screenshots/menu_default_opened.png) ![Menu custom opened](/screenshots/menu_custom_opened.png) ![Menu mini opened](/screenshots/menu_mini_opened.png) ![Menu right opened](/screenshots/menu_right_opened.png) ![Menu down opened](/screenshots/menu_down_opened.png) ![Progress background](/screenshots/progress_background.png) ![Progress no background](/screenshots/progress_no_background.png) 16 | 17 | # Features 18 | - Ripple effect on Android Lollipop devices 19 | - Option to set custom **normal**/**pressed**/**ripple** colors 20 | - Option to set custom shadow color and offsets 21 | - Option to disable shadow for buttons and (or) labels 22 | - Option to set custom animations 23 | - Option to set custom icon drawable 24 | - Support for **normal** `56dp` and **mini** `40dp` button sizes 25 | - Custom FloatingActionMenu icon animations 26 | - Option to expand menu up and down 27 | - Option to show labels to the left and to the right of the menu 28 | - Option to show circle progress on `FloactinActionButton` 29 | - Option to add button to the `FloatingActionMenu` programmatically 30 | - Option to dim the `FloatinActionMenu`'s background 31 | - *Option to remove all buttons from the `FloatingActionMenu`* 32 | - *Option to set a label for the `FloatingActionMenu`'s button* 33 | 34 | # Usage 35 | Add a dependency to your `build.gradle`: 36 | ``` 37 | dependencies { 38 | compile 'com.github.clans:fab:1.6.4' 39 | } 40 | ``` 41 | Add the `com.github.clans.fab.FloatingActionButton` to your layout XML file. 42 | ```XML 43 | 47 | 48 | 52 | 53 | 64 | 65 | 66 | ``` 67 | You can set an icon for the **FloatingActionButton** using `android:src` xml attribute. Use drawables of size `24dp` as specified by [guidlines](http://www.google.com/design/spec/components/buttons.html#buttons-floating-action-button). Icons of desired size can be generated with [Android Asset Studio](http://romannurik.github.io/AndroidAssetStudio/icons-generic.html). 68 | 69 | ### Floating action button 70 | Here are all the **FloatingActionButton**'s xml attributes with their **default values** which means that you don't have to set all of them: 71 | ```XML 72 | 98 | ``` 99 | All of these **FloatingActionButton**'s attributes has their corresponding getters and setters. So you can set them **programmatically**. 100 | 101 | ### Floating action menu 102 | Here are all the **FloatingActionMenu**'s xml attributes with their **default values** which means that you don't have to set all of them: 103 | ```XML 104 | 150 | 151 | 158 | 159 | 160 | ``` 161 | 162 | If you're using custom style for labels - other labels attributes will be ignored. 163 | 164 | Labels shadow preferences depends on their corresponding **FloatingActionButtons**' shadow preferences. 165 | 166 | For more usage examples check the **sample** project. 167 | 168 | # Changelog 169 | Please see the [Changelog](https://github.com/Clans/FloatingActionButton/wiki/Changelog) page to see what's recently changed. 170 | 171 | # Credits 172 | I used [android-floating-action-button](https://github.com/futuresimple/android-floating-action-button) library by Jerzy Chalupski as a base for development. 173 | 174 | # License 175 | ``` 176 | Copyright 2015 Dmytro Tarianyk 177 | 178 | Licensed under the Apache License, Version 2.0 (the "License"); 179 | you may not use this file except in compliance with the License. 180 | You may obtain a copy of the License at 181 | 182 | http://www.apache.org/licenses/LICENSE-2.0 183 | 184 | Unless required by applicable law or agreed to in writing, software 185 | distributed under the License is distributed on an "AS IS" BASIS, 186 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 187 | See the License for the specific language governing permissions and 188 | limitations under the License. 189 | ``` 190 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.1.0' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /gradle-mvn-push.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Chris Banes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | apply plugin: 'maven' 18 | apply plugin: 'signing' 19 | 20 | def isReleaseBuild() { 21 | return VERSION_NAME.contains("SNAPSHOT") == false 22 | } 23 | 24 | def getReleaseRepositoryUrl() { 25 | return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL 26 | : "https://oss.sonatype.org/service/local/staging/deploy/maven2/" 27 | } 28 | 29 | def getSnapshotRepositoryUrl() { 30 | return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL 31 | : "https://oss.sonatype.org/content/repositories/snapshots/" 32 | } 33 | 34 | def getRepositoryUsername() { 35 | return hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : "" 36 | } 37 | 38 | def getRepositoryPassword() { 39 | return hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : "" 40 | } 41 | 42 | afterEvaluate { project -> 43 | uploadArchives { 44 | repositories { 45 | mavenDeployer { 46 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } 47 | 48 | pom.groupId = GROUP 49 | pom.artifactId = POM_ARTIFACT_ID 50 | pom.version = VERSION_NAME 51 | 52 | repository(url: getReleaseRepositoryUrl()) { 53 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) 54 | } 55 | snapshotRepository(url: getSnapshotRepositoryUrl()) { 56 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) 57 | } 58 | 59 | pom.project { 60 | name POM_NAME 61 | packaging POM_PACKAGING 62 | description POM_DESCRIPTION 63 | url POM_URL 64 | 65 | scm { 66 | url POM_SCM_URL 67 | connection POM_SCM_CONNECTION 68 | developerConnection POM_SCM_DEV_CONNECTION 69 | } 70 | 71 | licenses { 72 | license { 73 | name POM_LICENCE_NAME 74 | url POM_LICENCE_URL 75 | distribution POM_LICENCE_DIST 76 | } 77 | } 78 | 79 | developers { 80 | developer { 81 | id POM_DEVELOPER_ID 82 | name POM_DEVELOPER_NAME 83 | } 84 | } 85 | } 86 | } 87 | } 88 | } 89 | 90 | signing { 91 | required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") } 92 | sign configurations.archives 93 | } 94 | 95 | task androidJavadocs(type: Javadoc) { 96 | source = android.sourceSets.main.java.srcDirs 97 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 98 | } 99 | 100 | task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) { 101 | classifier = 'javadoc' 102 | from androidJavadocs.destinationDir 103 | } 104 | 105 | task androidSourcesJar(type: Jar) { 106 | classifier = 'sources' 107 | from android.sourceSets.main.java.sourceFiles 108 | } 109 | 110 | artifacts { 111 | archives androidSourcesJar 112 | archives androidJavadocsJar 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /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 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | VERSION_NAME=1.6.4 21 | VERSION_CODE=10604 22 | GROUP=com.github.clans 23 | 24 | POM_DESCRIPTION=Floating Action Button implementation for Android 25 | POM_URL=https://github.com/Clans/FloatingActionButton 26 | POM_SCM_URL=https://github.com/Clans/FloatingActionButton 27 | POM_SCM_CONNECTION=scm:git@github.com:Clans/FloatingActionButton.git 28 | POM_SCM_DEV_CONNECTION=scm:git@github.com:Clans/FloatingActionButton.git 29 | POM_LICENCE_NAME=The Apache Software License, Version 2.0 30 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt 31 | POM_LICENCE_DIST=repo 32 | POM_DEVELOPER_ID=clans 33 | POM_DEVELOPER_NAME=Dmytro Tarianyk -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Apr 11 11:06:38 EEST 2016 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.10-all.zip 7 | -------------------------------------------------------------------------------- /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 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.3" 6 | 7 | defaultConfig { 8 | minSdkVersion 14 9 | targetSdkVersion 23 10 | versionCode Integer.parseInt(project.VERSION_CODE) 11 | versionName project.VERSION_NAME 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | debuggable false 17 | } 18 | 19 | debug { 20 | minifyEnabled false 21 | debuggable true 22 | } 23 | } 24 | } 25 | 26 | dependencies { 27 | compile fileTree(dir: 'libs', include: ['*.jar']) 28 | } 29 | 30 | apply from: '../gradle-mvn-push.gradle' 31 | -------------------------------------------------------------------------------- /library/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Floating Action Button Library 2 | POM_ARTIFACT_ID=fab 3 | POM_PACKAGING=aar -------------------------------------------------------------------------------- /library/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 D:/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 | -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /library/src/main/java/com/github/clans/fab/FloatingActionButton.java: -------------------------------------------------------------------------------- 1 | package com.github.clans.fab; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.content.res.ColorStateList; 6 | import android.content.res.TypedArray; 7 | import android.graphics.Canvas; 8 | import android.graphics.Color; 9 | import android.graphics.ColorFilter; 10 | import android.graphics.Outline; 11 | import android.graphics.Paint; 12 | import android.graphics.PorterDuff; 13 | import android.graphics.PorterDuffXfermode; 14 | import android.graphics.RectF; 15 | import android.graphics.Xfermode; 16 | import android.graphics.drawable.ColorDrawable; 17 | import android.graphics.drawable.Drawable; 18 | import android.graphics.drawable.LayerDrawable; 19 | import android.graphics.drawable.RippleDrawable; 20 | import android.graphics.drawable.ShapeDrawable; 21 | import android.graphics.drawable.StateListDrawable; 22 | import android.graphics.drawable.shapes.OvalShape; 23 | import android.graphics.drawable.shapes.Shape; 24 | import android.os.Build; 25 | import android.os.Parcel; 26 | import android.os.Parcelable; 27 | import android.os.SystemClock; 28 | import android.util.AttributeSet; 29 | import android.view.GestureDetector; 30 | import android.view.MotionEvent; 31 | import android.view.View; 32 | import android.view.ViewGroup; 33 | import android.view.ViewOutlineProvider; 34 | import android.view.animation.Animation; 35 | import android.view.animation.AnimationUtils; 36 | import android.widget.ImageButton; 37 | import android.widget.TextView; 38 | 39 | public class FloatingActionButton extends ImageButton { 40 | 41 | public static final int SIZE_NORMAL = 0; 42 | public static final int SIZE_MINI = 1; 43 | 44 | int mFabSize; 45 | boolean mShowShadow; 46 | int mShadowColor; 47 | int mShadowRadius = Util.dpToPx(getContext(), 4f); 48 | int mShadowXOffset = Util.dpToPx(getContext(), 1f); 49 | int mShadowYOffset = Util.dpToPx(getContext(), 3f); 50 | 51 | private static final Xfermode PORTER_DUFF_CLEAR = new PorterDuffXfermode(PorterDuff.Mode.CLEAR); 52 | private static final long PAUSE_GROWING_TIME = 200; 53 | private static final double BAR_SPIN_CYCLE_TIME = 500; 54 | private static final int BAR_MAX_LENGTH = 270; 55 | 56 | private int mColorNormal; 57 | private int mColorPressed; 58 | private int mColorDisabled; 59 | private int mColorRipple; 60 | private Drawable mIcon; 61 | private int mIconSize = Util.dpToPx(getContext(), 24f); 62 | private Animation mShowAnimation; 63 | private Animation mHideAnimation; 64 | private String mLabelText; 65 | private OnClickListener mClickListener; 66 | private Drawable mBackgroundDrawable; 67 | private boolean mUsingElevation; 68 | private boolean mUsingElevationCompat; 69 | 70 | // Progress 71 | private boolean mProgressBarEnabled; 72 | private int mProgressWidth = Util.dpToPx(getContext(), 6f); 73 | private int mProgressColor; 74 | private int mProgressBackgroundColor; 75 | private boolean mShouldUpdateButtonPosition; 76 | private float mOriginalX = -1; 77 | private float mOriginalY = -1; 78 | private boolean mButtonPositionSaved; 79 | private RectF mProgressCircleBounds = new RectF(); 80 | private Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 81 | private Paint mProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 82 | private boolean mProgressIndeterminate; 83 | private long mLastTimeAnimated; 84 | private float mSpinSpeed = 195.0f; //The amount of degrees per second 85 | private long mPausedTimeWithoutGrowing = 0; 86 | private double mTimeStartGrowing; 87 | private boolean mBarGrowingFromFront = true; 88 | private int mBarLength = 16; 89 | private float mBarExtraLength; 90 | private float mCurrentProgress; 91 | private float mTargetProgress; 92 | private int mProgress; 93 | private boolean mAnimateProgress; 94 | private boolean mShouldProgressIndeterminate; 95 | private boolean mShouldSetProgress; 96 | private int mProgressMax = 100; 97 | private boolean mShowProgressBackground; 98 | 99 | public FloatingActionButton(Context context) { 100 | this(context, null); 101 | } 102 | 103 | public FloatingActionButton(Context context, AttributeSet attrs) { 104 | this(context, attrs, 0); 105 | } 106 | 107 | public FloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr) { 108 | super(context, attrs, defStyleAttr); 109 | init(context, attrs, defStyleAttr); 110 | } 111 | 112 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 113 | public FloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 114 | super(context, attrs, defStyleAttr, defStyleRes); 115 | init(context, attrs, defStyleAttr); 116 | } 117 | 118 | private void init(Context context, AttributeSet attrs, int defStyleAttr) { 119 | TypedArray attr = context.obtainStyledAttributes(attrs, R.styleable.FloatingActionButton, defStyleAttr, 0); 120 | mColorNormal = attr.getColor(R.styleable.FloatingActionButton_fab_colorNormal, 0xFFDA4336); 121 | mColorPressed = attr.getColor(R.styleable.FloatingActionButton_fab_colorPressed, 0xFFE75043); 122 | mColorDisabled = attr.getColor(R.styleable.FloatingActionButton_fab_colorDisabled, 0xFFAAAAAA); 123 | mColorRipple = attr.getColor(R.styleable.FloatingActionButton_fab_colorRipple, 0x99FFFFFF); 124 | mShowShadow = attr.getBoolean(R.styleable.FloatingActionButton_fab_showShadow, true); 125 | mShadowColor = attr.getColor(R.styleable.FloatingActionButton_fab_shadowColor, 0x66000000); 126 | mShadowRadius = attr.getDimensionPixelSize(R.styleable.FloatingActionButton_fab_shadowRadius, mShadowRadius); 127 | mShadowXOffset = attr.getDimensionPixelSize(R.styleable.FloatingActionButton_fab_shadowXOffset, mShadowXOffset); 128 | mShadowYOffset = attr.getDimensionPixelSize(R.styleable.FloatingActionButton_fab_shadowYOffset, mShadowYOffset); 129 | mFabSize = attr.getInt(R.styleable.FloatingActionButton_fab_size, SIZE_NORMAL); 130 | mLabelText = attr.getString(R.styleable.FloatingActionButton_fab_label); 131 | mShouldProgressIndeterminate = attr.getBoolean(R.styleable.FloatingActionButton_fab_progress_indeterminate, false); 132 | mProgressColor = attr.getColor(R.styleable.FloatingActionButton_fab_progress_color, 0xFF009688); 133 | mProgressBackgroundColor = attr.getColor(R.styleable.FloatingActionButton_fab_progress_backgroundColor, 0x4D000000); 134 | mProgressMax = attr.getInt(R.styleable.FloatingActionButton_fab_progress_max, mProgressMax); 135 | mShowProgressBackground = attr.getBoolean(R.styleable.FloatingActionButton_fab_progress_showBackground, true); 136 | 137 | if (attr.hasValue(R.styleable.FloatingActionButton_fab_progress)) { 138 | mProgress = attr.getInt(R.styleable.FloatingActionButton_fab_progress, 0); 139 | mShouldSetProgress = true; 140 | } 141 | 142 | if (attr.hasValue(R.styleable.FloatingActionButton_fab_elevationCompat)) { 143 | float elevation = attr.getDimensionPixelOffset(R.styleable.FloatingActionButton_fab_elevationCompat, 0); 144 | if (isInEditMode()) { 145 | setElevation(elevation); 146 | } else { 147 | setElevationCompat(elevation); 148 | } 149 | } 150 | 151 | initShowAnimation(attr); 152 | initHideAnimation(attr); 153 | attr.recycle(); 154 | 155 | if (isInEditMode()) { 156 | if (mShouldProgressIndeterminate) { 157 | setIndeterminate(true); 158 | } else if (mShouldSetProgress) { 159 | saveButtonOriginalPosition(); 160 | setProgress(mProgress, false); 161 | } 162 | } 163 | 164 | // updateBackground(); 165 | setClickable(true); 166 | } 167 | 168 | private void initShowAnimation(TypedArray attr) { 169 | int resourceId = attr.getResourceId(R.styleable.FloatingActionButton_fab_showAnimation, R.anim.fab_scale_up); 170 | mShowAnimation = AnimationUtils.loadAnimation(getContext(), resourceId); 171 | } 172 | 173 | private void initHideAnimation(TypedArray attr) { 174 | int resourceId = attr.getResourceId(R.styleable.FloatingActionButton_fab_hideAnimation, R.anim.fab_scale_down); 175 | mHideAnimation = AnimationUtils.loadAnimation(getContext(), resourceId); 176 | } 177 | 178 | private int getCircleSize() { 179 | return getResources().getDimensionPixelSize(mFabSize == SIZE_NORMAL 180 | ? R.dimen.fab_size_normal : R.dimen.fab_size_mini); 181 | } 182 | 183 | private int calculateMeasuredWidth() { 184 | int width = getCircleSize() + calculateShadowWidth(); 185 | if (mProgressBarEnabled) { 186 | width += mProgressWidth * 2; 187 | } 188 | return width; 189 | } 190 | 191 | private int calculateMeasuredHeight() { 192 | int height = getCircleSize() + calculateShadowHeight(); 193 | if (mProgressBarEnabled) { 194 | height += mProgressWidth * 2; 195 | } 196 | return height; 197 | } 198 | 199 | int calculateShadowWidth() { 200 | return hasShadow() ? getShadowX() * 2 : 0; 201 | } 202 | 203 | int calculateShadowHeight() { 204 | return hasShadow() ? getShadowY() * 2 : 0; 205 | } 206 | 207 | private int getShadowX() { 208 | return mShadowRadius + Math.abs(mShadowXOffset); 209 | } 210 | 211 | private int getShadowY() { 212 | return mShadowRadius + Math.abs(mShadowYOffset); 213 | } 214 | 215 | private float calculateCenterX() { 216 | return (float) (getMeasuredWidth() / 2); 217 | } 218 | 219 | private float calculateCenterY() { 220 | return (float) (getMeasuredHeight() / 2); 221 | } 222 | 223 | @Override 224 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 225 | // super.onMeasure(widthMeasureSpec, heightMeasureSpec); 226 | setMeasuredDimension(calculateMeasuredWidth(), calculateMeasuredHeight()); 227 | } 228 | 229 | @Override 230 | protected void onDraw(Canvas canvas) { 231 | super.onDraw(canvas); 232 | 233 | if (mProgressBarEnabled) { 234 | if (mShowProgressBackground) { 235 | canvas.drawArc(mProgressCircleBounds, 360, 360, false, mBackgroundPaint); 236 | } 237 | 238 | boolean shouldInvalidate = false; 239 | 240 | if (mProgressIndeterminate) { 241 | shouldInvalidate = true; 242 | 243 | long deltaTime = SystemClock.uptimeMillis() - mLastTimeAnimated; 244 | float deltaNormalized = deltaTime * mSpinSpeed / 1000.0f; 245 | 246 | updateProgressLength(deltaTime); 247 | 248 | mCurrentProgress += deltaNormalized; 249 | if (mCurrentProgress > 360f) { 250 | mCurrentProgress -= 360f; 251 | } 252 | 253 | mLastTimeAnimated = SystemClock.uptimeMillis(); 254 | float from = mCurrentProgress - 90; 255 | float to = mBarLength + mBarExtraLength; 256 | 257 | if (isInEditMode()) { 258 | from = 0; 259 | to = 135; 260 | } 261 | 262 | canvas.drawArc(mProgressCircleBounds, from, to, false, mProgressPaint); 263 | } else { 264 | if (mCurrentProgress != mTargetProgress) { 265 | shouldInvalidate = true; 266 | float deltaTime = (float) (SystemClock.uptimeMillis() - mLastTimeAnimated) / 1000; 267 | float deltaNormalized = deltaTime * mSpinSpeed; 268 | 269 | if (mCurrentProgress > mTargetProgress) { 270 | mCurrentProgress = Math.max(mCurrentProgress - deltaNormalized, mTargetProgress); 271 | } else { 272 | mCurrentProgress = Math.min(mCurrentProgress + deltaNormalized, mTargetProgress); 273 | } 274 | mLastTimeAnimated = SystemClock.uptimeMillis(); 275 | } 276 | 277 | canvas.drawArc(mProgressCircleBounds, -90, mCurrentProgress, false, mProgressPaint); 278 | } 279 | 280 | if (shouldInvalidate) { 281 | invalidate(); 282 | } 283 | } 284 | } 285 | 286 | private void updateProgressLength(long deltaTimeInMillis) { 287 | if (mPausedTimeWithoutGrowing >= PAUSE_GROWING_TIME) { 288 | mTimeStartGrowing += deltaTimeInMillis; 289 | 290 | if (mTimeStartGrowing > BAR_SPIN_CYCLE_TIME) { 291 | mTimeStartGrowing -= BAR_SPIN_CYCLE_TIME; 292 | mPausedTimeWithoutGrowing = 0; 293 | mBarGrowingFromFront = !mBarGrowingFromFront; 294 | } 295 | 296 | float distance = (float) Math.cos((mTimeStartGrowing / BAR_SPIN_CYCLE_TIME + 1) * Math.PI) / 2 + 0.5f; 297 | float length = BAR_MAX_LENGTH - mBarLength; 298 | 299 | if (mBarGrowingFromFront) { 300 | mBarExtraLength = distance * length; 301 | } else { 302 | float newLength = length * (1 - distance); 303 | mCurrentProgress += (mBarExtraLength - newLength); 304 | mBarExtraLength = newLength; 305 | } 306 | } else { 307 | mPausedTimeWithoutGrowing += deltaTimeInMillis; 308 | } 309 | } 310 | 311 | @Override 312 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 313 | saveButtonOriginalPosition(); 314 | 315 | if (mShouldProgressIndeterminate) { 316 | setIndeterminate(true); 317 | mShouldProgressIndeterminate = false; 318 | } else if (mShouldSetProgress) { 319 | setProgress(mProgress, mAnimateProgress); 320 | mShouldSetProgress = false; 321 | } else if (mShouldUpdateButtonPosition) { 322 | updateButtonPosition(); 323 | mShouldUpdateButtonPosition = false; 324 | } 325 | super.onSizeChanged(w, h, oldw, oldh); 326 | 327 | setupProgressBounds(); 328 | setupProgressBarPaints(); 329 | updateBackground(); 330 | } 331 | 332 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 333 | @Override 334 | public void setLayoutParams(ViewGroup.LayoutParams params) { 335 | if (params instanceof ViewGroup.MarginLayoutParams && mUsingElevationCompat) { 336 | ((ViewGroup.MarginLayoutParams) params).leftMargin += getShadowX(); 337 | ((ViewGroup.MarginLayoutParams) params).topMargin += getShadowY(); 338 | ((ViewGroup.MarginLayoutParams) params).rightMargin += getShadowX(); 339 | ((ViewGroup.MarginLayoutParams) params).bottomMargin += getShadowY(); 340 | } 341 | super.setLayoutParams(params); 342 | } 343 | 344 | void updateBackground() { 345 | LayerDrawable layerDrawable; 346 | if (hasShadow()) { 347 | layerDrawable = new LayerDrawable(new Drawable[]{ 348 | new Shadow(), 349 | createFillDrawable(), 350 | getIconDrawable() 351 | }); 352 | } else { 353 | layerDrawable = new LayerDrawable(new Drawable[]{ 354 | createFillDrawable(), 355 | getIconDrawable() 356 | }); 357 | } 358 | 359 | int iconSize = -1; 360 | if (getIconDrawable() != null) { 361 | iconSize = Math.max(getIconDrawable().getIntrinsicWidth(), getIconDrawable().getIntrinsicHeight()); 362 | } 363 | int iconOffset = (getCircleSize() - (iconSize > 0 ? iconSize : mIconSize)) / 2; 364 | int circleInsetHorizontal = hasShadow() ? mShadowRadius + Math.abs(mShadowXOffset) : 0; 365 | int circleInsetVertical = hasShadow() ? mShadowRadius + Math.abs(mShadowYOffset) : 0; 366 | 367 | if (mProgressBarEnabled) { 368 | circleInsetHorizontal += mProgressWidth; 369 | circleInsetVertical += mProgressWidth; 370 | } 371 | 372 | /*layerDrawable.setLayerInset( 373 | mShowShadow ? 1 : 0, 374 | circleInsetHorizontal, 375 | circleInsetVertical, 376 | circleInsetHorizontal, 377 | circleInsetVertical 378 | );*/ 379 | layerDrawable.setLayerInset( 380 | hasShadow() ? 2 : 1, 381 | circleInsetHorizontal + iconOffset, 382 | circleInsetVertical + iconOffset, 383 | circleInsetHorizontal + iconOffset, 384 | circleInsetVertical + iconOffset 385 | ); 386 | 387 | setBackgroundCompat(layerDrawable); 388 | } 389 | 390 | protected Drawable getIconDrawable() { 391 | if (mIcon != null) { 392 | return mIcon; 393 | } else { 394 | return new ColorDrawable(Color.TRANSPARENT); 395 | } 396 | } 397 | 398 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 399 | private Drawable createFillDrawable() { 400 | StateListDrawable drawable = new StateListDrawable(); 401 | drawable.addState(new int[]{-android.R.attr.state_enabled}, createCircleDrawable(mColorDisabled)); 402 | drawable.addState(new int[]{android.R.attr.state_pressed}, createCircleDrawable(mColorPressed)); 403 | drawable.addState(new int[]{}, createCircleDrawable(mColorNormal)); 404 | 405 | if (Util.hasLollipop()) { 406 | RippleDrawable ripple = new RippleDrawable(new ColorStateList(new int[][]{{}}, 407 | new int[]{mColorRipple}), drawable, null); 408 | setOutlineProvider(new ViewOutlineProvider() { 409 | @Override 410 | public void getOutline(View view, Outline outline) { 411 | outline.setOval(0, 0, view.getWidth(), view.getHeight()); 412 | } 413 | }); 414 | setClipToOutline(true); 415 | mBackgroundDrawable = ripple; 416 | return ripple; 417 | } 418 | 419 | mBackgroundDrawable = drawable; 420 | return drawable; 421 | } 422 | 423 | private Drawable createCircleDrawable(int color) { 424 | CircleDrawable shapeDrawable = new CircleDrawable(new OvalShape()); 425 | shapeDrawable.getPaint().setColor(color); 426 | return shapeDrawable; 427 | } 428 | 429 | @SuppressWarnings("deprecation") 430 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN) 431 | private void setBackgroundCompat(Drawable drawable) { 432 | if (Util.hasJellyBean()) { 433 | setBackground(drawable); 434 | } else { 435 | setBackgroundDrawable(drawable); 436 | } 437 | } 438 | 439 | private void saveButtonOriginalPosition() { 440 | if (!mButtonPositionSaved) { 441 | if (mOriginalX == -1) { 442 | mOriginalX = getX(); 443 | } 444 | 445 | if (mOriginalY == -1) { 446 | mOriginalY = getY(); 447 | } 448 | 449 | mButtonPositionSaved = true; 450 | } 451 | } 452 | 453 | private void updateButtonPosition() { 454 | float x; 455 | float y; 456 | if (mProgressBarEnabled) { 457 | x = mOriginalX > getX() ? getX() + mProgressWidth : getX() - mProgressWidth; 458 | y = mOriginalY > getY() ? getY() + mProgressWidth : getY() - mProgressWidth; 459 | } else { 460 | x = mOriginalX; 461 | y = mOriginalY; 462 | } 463 | setX(x); 464 | setY(y); 465 | } 466 | 467 | private void setupProgressBarPaints() { 468 | mBackgroundPaint.setColor(mProgressBackgroundColor); 469 | mBackgroundPaint.setStyle(Paint.Style.STROKE); 470 | mBackgroundPaint.setStrokeWidth(mProgressWidth); 471 | 472 | mProgressPaint.setColor(mProgressColor); 473 | mProgressPaint.setStyle(Paint.Style.STROKE); 474 | mProgressPaint.setStrokeWidth(mProgressWidth); 475 | } 476 | 477 | private void setupProgressBounds() { 478 | int circleInsetHorizontal = hasShadow() ? getShadowX() : 0; 479 | int circleInsetVertical = hasShadow() ? getShadowY() : 0; 480 | mProgressCircleBounds = new RectF( 481 | circleInsetHorizontal + mProgressWidth / 2, 482 | circleInsetVertical + mProgressWidth / 2, 483 | calculateMeasuredWidth() - circleInsetHorizontal - mProgressWidth / 2, 484 | calculateMeasuredHeight() - circleInsetVertical - mProgressWidth / 2 485 | ); 486 | } 487 | 488 | Animation getShowAnimation() { 489 | return mShowAnimation; 490 | } 491 | 492 | Animation getHideAnimation() { 493 | return mHideAnimation; 494 | } 495 | 496 | void playShowAnimation() { 497 | mHideAnimation.cancel(); 498 | startAnimation(mShowAnimation); 499 | } 500 | 501 | void playHideAnimation() { 502 | mShowAnimation.cancel(); 503 | startAnimation(mHideAnimation); 504 | } 505 | 506 | OnClickListener getOnClickListener() { 507 | return mClickListener; 508 | } 509 | 510 | Label getLabelView() { 511 | return (Label) getTag(R.id.fab_label); 512 | } 513 | 514 | void setColors(int colorNormal, int colorPressed, int colorRipple) { 515 | mColorNormal = colorNormal; 516 | mColorPressed = colorPressed; 517 | mColorRipple = colorRipple; 518 | } 519 | 520 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 521 | void onActionDown() { 522 | if (mBackgroundDrawable instanceof StateListDrawable) { 523 | StateListDrawable drawable = (StateListDrawable) mBackgroundDrawable; 524 | drawable.setState(new int[]{android.R.attr.state_enabled, android.R.attr.state_pressed}); 525 | } else if (Util.hasLollipop()) { 526 | RippleDrawable ripple = (RippleDrawable) mBackgroundDrawable; 527 | ripple.setState(new int[]{android.R.attr.state_enabled, android.R.attr.state_pressed}); 528 | ripple.setHotspot(calculateCenterX(), calculateCenterY()); 529 | ripple.setVisible(true, true); 530 | } 531 | } 532 | 533 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 534 | void onActionUp() { 535 | if (mBackgroundDrawable instanceof StateListDrawable) { 536 | StateListDrawable drawable = (StateListDrawable) mBackgroundDrawable; 537 | drawable.setState(new int[]{android.R.attr.state_enabled}); 538 | } else if (Util.hasLollipop()) { 539 | RippleDrawable ripple = (RippleDrawable) mBackgroundDrawable; 540 | ripple.setState(new int[]{android.R.attr.state_enabled}); 541 | ripple.setHotspot(calculateCenterX(), calculateCenterY()); 542 | ripple.setVisible(true, true); 543 | } 544 | } 545 | 546 | @Override 547 | public boolean onTouchEvent(MotionEvent event) { 548 | if (mClickListener != null && isEnabled()) { 549 | Label label = (Label) getTag(R.id.fab_label); 550 | if (label == null) return super.onTouchEvent(event); 551 | 552 | int action = event.getAction(); 553 | switch (action) { 554 | case MotionEvent.ACTION_UP: 555 | if (label != null) { 556 | label.onActionUp(); 557 | } 558 | onActionUp(); 559 | break; 560 | 561 | case MotionEvent.ACTION_CANCEL: 562 | if (label != null) { 563 | label.onActionUp(); 564 | } 565 | onActionUp(); 566 | break; 567 | } 568 | mGestureDetector.onTouchEvent(event); 569 | } 570 | return super.onTouchEvent(event); 571 | } 572 | 573 | GestureDetector mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() { 574 | 575 | @Override 576 | public boolean onDown(MotionEvent e) { 577 | Label label = (Label) getTag(R.id.fab_label); 578 | if (label != null) { 579 | label.onActionDown(); 580 | } 581 | onActionDown(); 582 | return super.onDown(e); 583 | } 584 | 585 | @Override 586 | public boolean onSingleTapUp(MotionEvent e) { 587 | Label label = (Label) getTag(R.id.fab_label); 588 | if (label != null) { 589 | label.onActionUp(); 590 | } 591 | onActionUp(); 592 | return super.onSingleTapUp(e); 593 | } 594 | }); 595 | 596 | @Override 597 | public Parcelable onSaveInstanceState() { 598 | Parcelable superState = super.onSaveInstanceState(); 599 | 600 | ProgressSavedState ss = new ProgressSavedState(superState); 601 | 602 | ss.mCurrentProgress = this.mCurrentProgress; 603 | ss.mTargetProgress = this.mTargetProgress; 604 | ss.mSpinSpeed = this.mSpinSpeed; 605 | ss.mProgressWidth = this.mProgressWidth; 606 | ss.mProgressColor = this.mProgressColor; 607 | ss.mProgressBackgroundColor = this.mProgressBackgroundColor; 608 | ss.mShouldProgressIndeterminate = this.mProgressIndeterminate; 609 | ss.mShouldSetProgress = this.mProgressBarEnabled && mProgress > 0 && !this.mProgressIndeterminate; 610 | ss.mProgress = this.mProgress; 611 | ss.mAnimateProgress = this.mAnimateProgress; 612 | ss.mShowProgressBackground = this.mShowProgressBackground; 613 | 614 | return ss; 615 | } 616 | 617 | @Override 618 | public void onRestoreInstanceState(Parcelable state) { 619 | if (!(state instanceof ProgressSavedState)) { 620 | super.onRestoreInstanceState(state); 621 | return; 622 | } 623 | 624 | ProgressSavedState ss = (ProgressSavedState) state; 625 | super.onRestoreInstanceState(ss.getSuperState()); 626 | 627 | this.mCurrentProgress = ss.mCurrentProgress; 628 | this.mTargetProgress = ss.mTargetProgress; 629 | this.mSpinSpeed = ss.mSpinSpeed; 630 | this.mProgressWidth = ss.mProgressWidth; 631 | this.mProgressColor = ss.mProgressColor; 632 | this.mProgressBackgroundColor = ss.mProgressBackgroundColor; 633 | this.mShouldProgressIndeterminate = ss.mShouldProgressIndeterminate; 634 | this.mShouldSetProgress = ss.mShouldSetProgress; 635 | this.mProgress = ss.mProgress; 636 | this.mAnimateProgress = ss.mAnimateProgress; 637 | this.mShowProgressBackground = ss.mShowProgressBackground; 638 | 639 | this.mLastTimeAnimated = SystemClock.uptimeMillis(); 640 | } 641 | 642 | private class CircleDrawable extends ShapeDrawable { 643 | 644 | private int circleInsetHorizontal; 645 | private int circleInsetVertical; 646 | 647 | private CircleDrawable() { 648 | } 649 | 650 | private CircleDrawable(Shape s) { 651 | super(s); 652 | circleInsetHorizontal = hasShadow() ? mShadowRadius + Math.abs(mShadowXOffset) : 0; 653 | circleInsetVertical = hasShadow() ? mShadowRadius + Math.abs(mShadowYOffset) : 0; 654 | 655 | if (mProgressBarEnabled) { 656 | circleInsetHorizontal += mProgressWidth; 657 | circleInsetVertical += mProgressWidth; 658 | } 659 | } 660 | 661 | @Override 662 | public void draw(Canvas canvas) { 663 | setBounds(circleInsetHorizontal, circleInsetVertical, calculateMeasuredWidth() 664 | - circleInsetHorizontal, calculateMeasuredHeight() - circleInsetVertical); 665 | super.draw(canvas); 666 | } 667 | } 668 | 669 | private class Shadow extends Drawable { 670 | 671 | private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 672 | private Paint mErase = new Paint(Paint.ANTI_ALIAS_FLAG); 673 | private float mRadius; 674 | 675 | private Shadow() { 676 | this.init(); 677 | } 678 | 679 | private void init() { 680 | setLayerType(LAYER_TYPE_SOFTWARE, null); 681 | mPaint.setStyle(Paint.Style.FILL); 682 | mPaint.setColor(mColorNormal); 683 | 684 | mErase.setXfermode(PORTER_DUFF_CLEAR); 685 | 686 | if (!isInEditMode()) { 687 | mPaint.setShadowLayer(mShadowRadius, mShadowXOffset, mShadowYOffset, mShadowColor); 688 | } 689 | 690 | mRadius = getCircleSize() / 2; 691 | 692 | if (mProgressBarEnabled && mShowProgressBackground) { 693 | mRadius += mProgressWidth; 694 | } 695 | } 696 | 697 | @Override 698 | public void draw(Canvas canvas) { 699 | canvas.drawCircle(calculateCenterX(), calculateCenterY(), mRadius, mPaint); 700 | canvas.drawCircle(calculateCenterX(), calculateCenterY(), mRadius, mErase); 701 | } 702 | 703 | @Override 704 | public void setAlpha(int alpha) { 705 | 706 | } 707 | 708 | @Override 709 | public void setColorFilter(ColorFilter cf) { 710 | 711 | } 712 | 713 | @Override 714 | public int getOpacity() { 715 | return 0; 716 | } 717 | } 718 | 719 | static class ProgressSavedState extends BaseSavedState { 720 | 721 | float mCurrentProgress; 722 | float mTargetProgress; 723 | float mSpinSpeed; 724 | int mProgress; 725 | int mProgressWidth; 726 | int mProgressColor; 727 | int mProgressBackgroundColor; 728 | boolean mProgressBarEnabled; 729 | boolean mProgressBarVisibilityChanged; 730 | boolean mProgressIndeterminate; 731 | boolean mShouldProgressIndeterminate; 732 | boolean mShouldSetProgress; 733 | boolean mAnimateProgress; 734 | boolean mShowProgressBackground; 735 | 736 | ProgressSavedState(Parcelable superState) { 737 | super(superState); 738 | } 739 | 740 | private ProgressSavedState(Parcel in) { 741 | super(in); 742 | this.mCurrentProgress = in.readFloat(); 743 | this.mTargetProgress = in.readFloat(); 744 | this.mProgressBarEnabled = in.readInt() != 0; 745 | this.mSpinSpeed = in.readFloat(); 746 | this.mProgress = in.readInt(); 747 | this.mProgressWidth = in.readInt(); 748 | this.mProgressColor = in.readInt(); 749 | this.mProgressBackgroundColor = in.readInt(); 750 | this.mProgressBarVisibilityChanged = in.readInt() != 0; 751 | this.mProgressIndeterminate = in.readInt() != 0; 752 | this.mShouldProgressIndeterminate = in.readInt() != 0; 753 | this.mShouldSetProgress = in.readInt() != 0; 754 | this.mAnimateProgress = in.readInt() != 0; 755 | this.mShowProgressBackground = in.readInt() != 0; 756 | } 757 | 758 | @Override 759 | public void writeToParcel(Parcel out, int flags) { 760 | super.writeToParcel(out, flags); 761 | out.writeFloat(this.mCurrentProgress); 762 | out.writeFloat(this.mTargetProgress); 763 | out.writeInt((mProgressBarEnabled ? 1 : 0)); 764 | out.writeFloat(this.mSpinSpeed); 765 | out.writeInt(this.mProgress); 766 | out.writeInt(this.mProgressWidth); 767 | out.writeInt(this.mProgressColor); 768 | out.writeInt(this.mProgressBackgroundColor); 769 | out.writeInt(this.mProgressBarVisibilityChanged ? 1 : 0); 770 | out.writeInt(this.mProgressIndeterminate ? 1 : 0); 771 | out.writeInt(this.mShouldProgressIndeterminate ? 1 : 0); 772 | out.writeInt(this.mShouldSetProgress ? 1 : 0); 773 | out.writeInt(this.mAnimateProgress ? 1 : 0); 774 | out.writeInt(this.mShowProgressBackground ? 1 : 0); 775 | } 776 | 777 | public static final Parcelable.Creator CREATOR = 778 | new Parcelable.Creator() { 779 | public ProgressSavedState createFromParcel(Parcel in) { 780 | return new ProgressSavedState(in); 781 | } 782 | 783 | public ProgressSavedState[] newArray(int size) { 784 | return new ProgressSavedState[size]; 785 | } 786 | }; 787 | } 788 | 789 | /* ===== API methods ===== */ 790 | 791 | @Override 792 | public void setImageDrawable(Drawable drawable) { 793 | if (mIcon != drawable) { 794 | mIcon = drawable; 795 | updateBackground(); 796 | } 797 | } 798 | 799 | @Override 800 | public void setImageResource(int resId) { 801 | Drawable drawable = getResources().getDrawable(resId); 802 | if (mIcon != drawable) { 803 | mIcon = drawable; 804 | updateBackground(); 805 | } 806 | } 807 | 808 | @Override 809 | public void setOnClickListener(final OnClickListener l) { 810 | super.setOnClickListener(l); 811 | mClickListener = l; 812 | View label = (View) getTag(R.id.fab_label); 813 | if (label != null) { 814 | label.setOnClickListener(new OnClickListener() { 815 | @Override 816 | public void onClick(View v) { 817 | if (mClickListener != null) { 818 | mClickListener.onClick(FloatingActionButton.this); 819 | } 820 | } 821 | }); 822 | } 823 | } 824 | 825 | /** 826 | * Sets the size of the FloatingActionButton and invalidates its layout. 827 | * 828 | * @param size size of the FloatingActionButton. Accepted values: SIZE_NORMAL, SIZE_MINI. 829 | */ 830 | public void setButtonSize(int size) { 831 | if (size != SIZE_NORMAL && size != SIZE_MINI) { 832 | throw new IllegalArgumentException("Use @FabSize constants only!"); 833 | } 834 | 835 | if (mFabSize != size) { 836 | mFabSize = size; 837 | updateBackground(); 838 | } 839 | } 840 | 841 | public int getButtonSize() { 842 | return mFabSize; 843 | } 844 | 845 | public void setColorNormal(int color) { 846 | if (mColorNormal != color) { 847 | mColorNormal = color; 848 | updateBackground(); 849 | } 850 | } 851 | 852 | public void setColorNormalResId(int colorResId) { 853 | setColorNormal(getResources().getColor(colorResId)); 854 | } 855 | 856 | public int getColorNormal() { 857 | return mColorNormal; 858 | } 859 | 860 | public void setColorPressed(int color) { 861 | if (color != mColorPressed) { 862 | mColorPressed = color; 863 | updateBackground(); 864 | } 865 | } 866 | 867 | public void setColorPressedResId(int colorResId) { 868 | setColorPressed(getResources().getColor(colorResId)); 869 | } 870 | 871 | public int getColorPressed() { 872 | return mColorPressed; 873 | } 874 | 875 | public void setColorRipple(int color) { 876 | if (color != mColorRipple) { 877 | mColorRipple = color; 878 | updateBackground(); 879 | } 880 | } 881 | 882 | public void setColorRippleResId(int colorResId) { 883 | setColorRipple(getResources().getColor(colorResId)); 884 | } 885 | 886 | public int getColorRipple() { 887 | return mColorRipple; 888 | } 889 | 890 | public void setColorDisabled(int color) { 891 | if (color != mColorDisabled) { 892 | mColorDisabled = color; 893 | updateBackground(); 894 | } 895 | } 896 | 897 | public void setColorDisabledResId(int colorResId) { 898 | setColorDisabled(getResources().getColor(colorResId)); 899 | } 900 | 901 | public int getColorDisabled() { 902 | return mColorDisabled; 903 | } 904 | 905 | public void setShowShadow(boolean show) { 906 | if (mShowShadow != show) { 907 | mShowShadow = show; 908 | updateBackground(); 909 | } 910 | } 911 | 912 | public boolean hasShadow() { 913 | return !mUsingElevation && mShowShadow; 914 | } 915 | 916 | /** 917 | * Sets the shadow radius of the FloatingActionButton and invalidates its layout. 918 | * 919 | * @param dimenResId the resource identifier of the dimension 920 | */ 921 | public void setShadowRadius(int dimenResId) { 922 | int shadowRadius = getResources().getDimensionPixelSize(dimenResId); 923 | if (mShadowRadius != shadowRadius) { 924 | mShadowRadius = shadowRadius; 925 | requestLayout(); 926 | updateBackground(); 927 | } 928 | } 929 | 930 | /** 931 | * Sets the shadow radius of the FloatingActionButton and invalidates its layout. 932 | *

933 | * Must be specified in density-independent (dp) pixels, which are then converted into actual 934 | * pixels (px). 935 | * 936 | * @param shadowRadiusDp shadow radius specified in density-independent (dp) pixels 937 | */ 938 | public void setShadowRadius(float shadowRadiusDp) { 939 | mShadowRadius = Util.dpToPx(getContext(), shadowRadiusDp); 940 | requestLayout(); 941 | updateBackground(); 942 | } 943 | 944 | public int getShadowRadius() { 945 | return mShadowRadius; 946 | } 947 | 948 | /** 949 | * Sets the shadow x offset of the FloatingActionButton and invalidates its layout. 950 | * 951 | * @param dimenResId the resource identifier of the dimension 952 | */ 953 | public void setShadowXOffset(int dimenResId) { 954 | int shadowXOffset = getResources().getDimensionPixelSize(dimenResId); 955 | if (mShadowXOffset != shadowXOffset) { 956 | mShadowXOffset = shadowXOffset; 957 | requestLayout(); 958 | updateBackground(); 959 | } 960 | } 961 | 962 | /** 963 | * Sets the shadow x offset of the FloatingActionButton and invalidates its layout. 964 | *

965 | * Must be specified in density-independent (dp) pixels, which are then converted into actual 966 | * pixels (px). 967 | * 968 | * @param shadowXOffsetDp shadow radius specified in density-independent (dp) pixels 969 | */ 970 | public void setShadowXOffset(float shadowXOffsetDp) { 971 | mShadowXOffset = Util.dpToPx(getContext(), shadowXOffsetDp); 972 | requestLayout(); 973 | updateBackground(); 974 | } 975 | 976 | public int getShadowXOffset() { 977 | return mShadowXOffset; 978 | } 979 | 980 | /** 981 | * Sets the shadow y offset of the FloatingActionButton and invalidates its layout. 982 | * 983 | * @param dimenResId the resource identifier of the dimension 984 | */ 985 | public void setShadowYOffset(int dimenResId) { 986 | int shadowYOffset = getResources().getDimensionPixelSize(dimenResId); 987 | if (mShadowYOffset != shadowYOffset) { 988 | mShadowYOffset = shadowYOffset; 989 | requestLayout(); 990 | updateBackground(); 991 | } 992 | } 993 | 994 | /** 995 | * Sets the shadow y offset of the FloatingActionButton and invalidates its layout. 996 | *

997 | * Must be specified in density-independent (dp) pixels, which are then converted into actual 998 | * pixels (px). 999 | * 1000 | * @param shadowYOffsetDp shadow radius specified in density-independent (dp) pixels 1001 | */ 1002 | public void setShadowYOffset(float shadowYOffsetDp) { 1003 | mShadowYOffset = Util.dpToPx(getContext(), shadowYOffsetDp); 1004 | requestLayout(); 1005 | updateBackground(); 1006 | } 1007 | 1008 | public int getShadowYOffset() { 1009 | return mShadowYOffset; 1010 | } 1011 | 1012 | public void setShadowColorResource(int colorResId) { 1013 | int shadowColor = getResources().getColor(colorResId); 1014 | if (mShadowColor != shadowColor) { 1015 | mShadowColor = shadowColor; 1016 | updateBackground(); 1017 | } 1018 | } 1019 | 1020 | public void setShadowColor(int color) { 1021 | if (mShadowColor != color) { 1022 | mShadowColor = color; 1023 | updateBackground(); 1024 | } 1025 | } 1026 | 1027 | public int getShadowColor() { 1028 | return mShadowColor; 1029 | } 1030 | 1031 | /** 1032 | * Checks whether FloatingActionButton is hidden 1033 | * 1034 | * @return true if FloatingActionButton is hidden, false otherwise 1035 | */ 1036 | public boolean isHidden() { 1037 | return getVisibility() == INVISIBLE; 1038 | } 1039 | 1040 | /** 1041 | * Makes the FloatingActionButton to appear and sets its visibility to {@link #VISIBLE} 1042 | * 1043 | * @param animate if true - plays "show animation" 1044 | */ 1045 | public void show(boolean animate) { 1046 | if (isHidden()) { 1047 | if (animate) { 1048 | playShowAnimation(); 1049 | } 1050 | super.setVisibility(VISIBLE); 1051 | } 1052 | } 1053 | 1054 | /** 1055 | * Makes the FloatingActionButton to disappear and sets its visibility to {@link #INVISIBLE} 1056 | * 1057 | * @param animate if true - plays "hide animation" 1058 | */ 1059 | public void hide(boolean animate) { 1060 | if (!isHidden()) { 1061 | if (animate) { 1062 | playHideAnimation(); 1063 | } 1064 | super.setVisibility(INVISIBLE); 1065 | } 1066 | } 1067 | 1068 | public void toggle(boolean animate) { 1069 | if (isHidden()) { 1070 | show(animate); 1071 | } else { 1072 | hide(animate); 1073 | } 1074 | } 1075 | 1076 | public void setLabelText(String text) { 1077 | mLabelText = text; 1078 | TextView labelView = getLabelView(); 1079 | if (labelView != null) { 1080 | labelView.setText(text); 1081 | } 1082 | } 1083 | 1084 | public String getLabelText() { 1085 | return mLabelText; 1086 | } 1087 | 1088 | public void setShowAnimation(Animation showAnimation) { 1089 | mShowAnimation = showAnimation; 1090 | } 1091 | 1092 | public void setHideAnimation(Animation hideAnimation) { 1093 | mHideAnimation = hideAnimation; 1094 | } 1095 | 1096 | public void setLabelVisibility(int visibility) { 1097 | Label labelView = getLabelView(); 1098 | if (labelView != null) { 1099 | labelView.setVisibility(visibility); 1100 | labelView.setHandleVisibilityChanges(visibility == VISIBLE); 1101 | } 1102 | } 1103 | 1104 | public int getLabelVisibility() { 1105 | TextView labelView = getLabelView(); 1106 | if (labelView != null) { 1107 | return labelView.getVisibility(); 1108 | } 1109 | 1110 | return -1; 1111 | } 1112 | 1113 | @Override 1114 | public void setElevation(float elevation) { 1115 | if (Util.hasLollipop() && elevation > 0) { 1116 | super.setElevation(elevation); 1117 | if (!isInEditMode()) { 1118 | mUsingElevation = true; 1119 | mShowShadow = false; 1120 | } 1121 | updateBackground(); 1122 | } 1123 | } 1124 | 1125 | /** 1126 | * Sets the shadow color and radius to mimic the native elevation. 1127 | * 1128 | *

API 21+: Sets the native elevation of this view, in pixels. Updates margins to 1129 | * make the view hold its position in layout across different platform versions.

1130 | */ 1131 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 1132 | public void setElevationCompat(float elevation) { 1133 | mShadowColor = 0x26000000; 1134 | mShadowRadius = Math.round(elevation / 2); 1135 | mShadowXOffset = 0; 1136 | mShadowYOffset = Math.round(mFabSize == SIZE_NORMAL ? elevation : elevation / 2); 1137 | 1138 | if (Util.hasLollipop()) { 1139 | super.setElevation(elevation); 1140 | mUsingElevationCompat = true; 1141 | mShowShadow = false; 1142 | updateBackground(); 1143 | 1144 | ViewGroup.LayoutParams layoutParams = getLayoutParams(); 1145 | if (layoutParams != null) { 1146 | setLayoutParams(layoutParams); 1147 | } 1148 | } else { 1149 | mShowShadow = true; 1150 | updateBackground(); 1151 | } 1152 | } 1153 | 1154 | /** 1155 | *

Change the indeterminate mode for the progress bar. In indeterminate 1156 | * mode, the progress is ignored and the progress bar shows an infinite 1157 | * animation instead.

1158 | * 1159 | * @param indeterminate true to enable the indeterminate mode 1160 | */ 1161 | public synchronized void setIndeterminate(boolean indeterminate) { 1162 | if (!indeterminate) { 1163 | mCurrentProgress = 0.0f; 1164 | } 1165 | 1166 | mProgressBarEnabled = indeterminate; 1167 | mShouldUpdateButtonPosition = true; 1168 | mProgressIndeterminate = indeterminate; 1169 | mLastTimeAnimated = SystemClock.uptimeMillis(); 1170 | setupProgressBounds(); 1171 | // saveButtonOriginalPosition(); 1172 | updateBackground(); 1173 | } 1174 | 1175 | public synchronized void setMax(int max) { 1176 | mProgressMax = max; 1177 | } 1178 | 1179 | public synchronized int getMax() { 1180 | return mProgressMax; 1181 | } 1182 | 1183 | public synchronized void setProgress(int progress, boolean animate) { 1184 | if (mProgressIndeterminate) return; 1185 | 1186 | mProgress = progress; 1187 | mAnimateProgress = animate; 1188 | 1189 | if (!mButtonPositionSaved) { 1190 | mShouldSetProgress = true; 1191 | return; 1192 | } 1193 | 1194 | mProgressBarEnabled = true; 1195 | mShouldUpdateButtonPosition = true; 1196 | setupProgressBounds(); 1197 | saveButtonOriginalPosition(); 1198 | updateBackground(); 1199 | 1200 | if (progress < 0) { 1201 | progress = 0; 1202 | } else if (progress > mProgressMax) { 1203 | progress = mProgressMax; 1204 | } 1205 | 1206 | if (progress == mTargetProgress) { 1207 | return; 1208 | } 1209 | 1210 | mTargetProgress = mProgressMax > 0 ? (progress / (float) mProgressMax) * 360 : 0; 1211 | mLastTimeAnimated = SystemClock.uptimeMillis(); 1212 | 1213 | if (!animate) { 1214 | mCurrentProgress = mTargetProgress; 1215 | } 1216 | 1217 | invalidate(); 1218 | } 1219 | 1220 | public synchronized int getProgress() { 1221 | return mProgressIndeterminate ? 0 : mProgress; 1222 | } 1223 | 1224 | public synchronized void hideProgress() { 1225 | mProgressBarEnabled = false; 1226 | mShouldUpdateButtonPosition = true; 1227 | updateBackground(); 1228 | } 1229 | 1230 | public synchronized void setShowProgressBackground(boolean show) { 1231 | mShowProgressBackground = show; 1232 | } 1233 | 1234 | public synchronized boolean isProgressBackgroundShown() { 1235 | return mShowProgressBackground; 1236 | } 1237 | 1238 | @Override 1239 | public void setEnabled(boolean enabled) { 1240 | super.setEnabled(enabled); 1241 | Label label = (Label) getTag(R.id.fab_label); 1242 | if (label != null) { 1243 | label.setEnabled(enabled); 1244 | } 1245 | } 1246 | 1247 | @Override 1248 | public void setVisibility(int visibility) { 1249 | super.setVisibility(visibility); 1250 | Label label = (Label) getTag(R.id.fab_label); 1251 | if (label != null) { 1252 | label.setVisibility(visibility); 1253 | } 1254 | } 1255 | 1256 | /** 1257 | * This will clear all AnimationListeners. 1258 | */ 1259 | public void hideButtonInMenu(boolean animate) { 1260 | if (!isHidden() && getVisibility() != GONE) { 1261 | hide(animate); 1262 | 1263 | Label label = getLabelView(); 1264 | if (label != null) { 1265 | label.hide(animate); 1266 | } 1267 | 1268 | getHideAnimation().setAnimationListener(new Animation.AnimationListener() { 1269 | @Override 1270 | public void onAnimationStart(Animation animation) { 1271 | } 1272 | 1273 | @Override 1274 | public void onAnimationEnd(Animation animation) { 1275 | setVisibility(GONE); 1276 | getHideAnimation().setAnimationListener(null); 1277 | } 1278 | 1279 | @Override 1280 | public void onAnimationRepeat(Animation animation) { 1281 | } 1282 | }); 1283 | } 1284 | } 1285 | 1286 | public void showButtonInMenu(boolean animate) { 1287 | if (getVisibility() == VISIBLE) return; 1288 | 1289 | setVisibility(INVISIBLE); 1290 | show(animate); 1291 | Label label = getLabelView(); 1292 | if (label != null) { 1293 | label.show(animate); 1294 | } 1295 | } 1296 | 1297 | /** 1298 | * Set the label's background colors 1299 | */ 1300 | public void setLabelColors(int colorNormal, int colorPressed, int colorRipple) { 1301 | Label label = getLabelView(); 1302 | 1303 | int left = label.getPaddingLeft(); 1304 | int top = label.getPaddingTop(); 1305 | int right = label.getPaddingRight(); 1306 | int bottom = label.getPaddingBottom(); 1307 | 1308 | label.setColors(colorNormal, colorPressed, colorRipple); 1309 | label.updateBackground(); 1310 | label.setPadding(left, top, right, bottom); 1311 | } 1312 | 1313 | public void setLabelTextColor(int color) { 1314 | getLabelView().setTextColor(color); 1315 | } 1316 | 1317 | public void setLabelTextColor(ColorStateList colors) { 1318 | getLabelView().setTextColor(colors); 1319 | } 1320 | } 1321 | -------------------------------------------------------------------------------- /library/src/main/java/com/github/clans/fab/FloatingActionMenu.java: -------------------------------------------------------------------------------- 1 | package com.github.clans.fab; 2 | 3 | import android.animation.AnimatorSet; 4 | import android.animation.ObjectAnimator; 5 | import android.animation.ValueAnimator; 6 | import android.content.Context; 7 | import android.content.res.ColorStateList; 8 | import android.content.res.TypedArray; 9 | import android.graphics.Color; 10 | import android.graphics.Typeface; 11 | import android.graphics.drawable.Drawable; 12 | import android.os.Handler; 13 | import android.text.TextUtils; 14 | import android.util.AttributeSet; 15 | import android.util.TypedValue; 16 | import android.view.ContextThemeWrapper; 17 | import android.view.GestureDetector; 18 | import android.view.MotionEvent; 19 | import android.view.View; 20 | import android.view.ViewGroup; 21 | import android.view.animation.Animation; 22 | import android.view.animation.AnimationUtils; 23 | import android.view.animation.AnticipateInterpolator; 24 | import android.view.animation.Interpolator; 25 | import android.view.animation.OvershootInterpolator; 26 | import android.widget.ImageView; 27 | 28 | import java.util.ArrayList; 29 | import java.util.List; 30 | 31 | public class FloatingActionMenu extends ViewGroup { 32 | 33 | private static final int ANIMATION_DURATION = 300; 34 | private static final float CLOSED_PLUS_ROTATION = 0f; 35 | private static final float OPENED_PLUS_ROTATION_LEFT = -90f - 45f; 36 | private static final float OPENED_PLUS_ROTATION_RIGHT = 90f + 45f; 37 | 38 | private static final int OPEN_UP = 0; 39 | private static final int OPEN_DOWN = 1; 40 | 41 | private static final int LABELS_POSITION_LEFT = 0; 42 | private static final int LABELS_POSITION_RIGHT = 1; 43 | 44 | private AnimatorSet mOpenAnimatorSet = new AnimatorSet(); 45 | private AnimatorSet mCloseAnimatorSet = new AnimatorSet(); 46 | private AnimatorSet mIconToggleSet; 47 | 48 | private int mButtonSpacing = Util.dpToPx(getContext(), 0f); 49 | private FloatingActionButton mMenuButton; 50 | private int mMaxButtonWidth; 51 | private int mLabelsMargin = Util.dpToPx(getContext(), 0f); 52 | private int mLabelsVerticalOffset = Util.dpToPx(getContext(), 0f); 53 | private int mButtonsCount; 54 | private boolean mMenuOpened; 55 | private boolean mIsMenuOpening; 56 | private Handler mUiHandler = new Handler(); 57 | private int mLabelsShowAnimation; 58 | private int mLabelsHideAnimation; 59 | private int mLabelsPaddingTop = Util.dpToPx(getContext(), 4f); 60 | private int mLabelsPaddingRight = Util.dpToPx(getContext(), 8f); 61 | private int mLabelsPaddingBottom = Util.dpToPx(getContext(), 4f); 62 | private int mLabelsPaddingLeft = Util.dpToPx(getContext(), 8f); 63 | private ColorStateList mLabelsTextColor; 64 | private float mLabelsTextSize; 65 | private int mLabelsCornerRadius = Util.dpToPx(getContext(), 3f); 66 | private boolean mLabelsShowShadow; 67 | private int mLabelsColorNormal; 68 | private int mLabelsColorPressed; 69 | private int mLabelsColorRipple; 70 | private boolean mMenuShowShadow; 71 | private int mMenuShadowColor; 72 | private float mMenuShadowRadius = 4f; 73 | private float mMenuShadowXOffset = 1f; 74 | private float mMenuShadowYOffset = 3f; 75 | private int mMenuColorNormal; 76 | private int mMenuColorPressed; 77 | private int mMenuColorRipple; 78 | private Drawable mIcon; 79 | private int mAnimationDelayPerItem; 80 | private Interpolator mOpenInterpolator; 81 | private Interpolator mCloseInterpolator; 82 | private boolean mIsAnimated = true; 83 | private boolean mLabelsSingleLine; 84 | private int mLabelsEllipsize; 85 | private int mLabelsMaxLines; 86 | private int mMenuFabSize; 87 | private int mLabelsStyle; 88 | private Typeface mCustomTypefaceFromFont; 89 | private boolean mIconAnimated = true; 90 | private ImageView mImageToggle; 91 | private Animation mMenuButtonShowAnimation; 92 | private Animation mMenuButtonHideAnimation; 93 | private Animation mImageToggleShowAnimation; 94 | private Animation mImageToggleHideAnimation; 95 | private boolean mIsMenuButtonAnimationRunning; 96 | private boolean mIsSetClosedOnTouchOutside; 97 | private int mOpenDirection; 98 | private OnMenuToggleListener mToggleListener; 99 | 100 | private ValueAnimator mShowBackgroundAnimator; 101 | private ValueAnimator mHideBackgroundAnimator; 102 | private int mBackgroundColor; 103 | 104 | private int mLabelsPosition; 105 | private Context mLabelsContext; 106 | private String mMenuLabelText; 107 | private boolean mUsingMenuLabel; 108 | 109 | public interface OnMenuToggleListener { 110 | void onMenuToggle(boolean opened); 111 | } 112 | 113 | public FloatingActionMenu(Context context) { 114 | this(context, null); 115 | } 116 | 117 | public FloatingActionMenu(Context context, AttributeSet attrs) { 118 | this(context, attrs, 0); 119 | } 120 | 121 | public FloatingActionMenu(Context context, AttributeSet attrs, int defStyleAttr) { 122 | super(context, attrs, defStyleAttr); 123 | init(context, attrs); 124 | } 125 | 126 | private void init(Context context, AttributeSet attrs) { 127 | TypedArray attr = context.obtainStyledAttributes(attrs, R.styleable.FloatingActionMenu, 0, 0); 128 | mButtonSpacing = attr.getDimensionPixelSize(R.styleable.FloatingActionMenu_menu_buttonSpacing, mButtonSpacing); 129 | mLabelsMargin = attr.getDimensionPixelSize(R.styleable.FloatingActionMenu_menu_labels_margin, mLabelsMargin); 130 | mLabelsPosition = attr.getInt(R.styleable.FloatingActionMenu_menu_labels_position, LABELS_POSITION_LEFT); 131 | mLabelsShowAnimation = attr.getResourceId(R.styleable.FloatingActionMenu_menu_labels_showAnimation, 132 | mLabelsPosition == LABELS_POSITION_LEFT ? R.anim.fab_slide_in_from_right : R.anim.fab_slide_in_from_left); 133 | mLabelsHideAnimation = attr.getResourceId(R.styleable.FloatingActionMenu_menu_labels_hideAnimation, 134 | mLabelsPosition == LABELS_POSITION_LEFT ? R.anim.fab_slide_out_to_right : R.anim.fab_slide_out_to_left); 135 | mLabelsPaddingTop = attr.getDimensionPixelSize(R.styleable.FloatingActionMenu_menu_labels_paddingTop, mLabelsPaddingTop); 136 | mLabelsPaddingRight = attr.getDimensionPixelSize(R.styleable.FloatingActionMenu_menu_labels_paddingRight, mLabelsPaddingRight); 137 | mLabelsPaddingBottom = attr.getDimensionPixelSize(R.styleable.FloatingActionMenu_menu_labels_paddingBottom, mLabelsPaddingBottom); 138 | mLabelsPaddingLeft = attr.getDimensionPixelSize(R.styleable.FloatingActionMenu_menu_labels_paddingLeft, mLabelsPaddingLeft); 139 | mLabelsTextColor = attr.getColorStateList(R.styleable.FloatingActionMenu_menu_labels_textColor); 140 | // set default value if null same as for textview 141 | if (mLabelsTextColor == null) { 142 | mLabelsTextColor = ColorStateList.valueOf(Color.WHITE); 143 | } 144 | mLabelsTextSize = attr.getDimension(R.styleable.FloatingActionMenu_menu_labels_textSize, getResources().getDimension(R.dimen.labels_text_size)); 145 | mLabelsCornerRadius = attr.getDimensionPixelSize(R.styleable.FloatingActionMenu_menu_labels_cornerRadius, mLabelsCornerRadius); 146 | mLabelsShowShadow = attr.getBoolean(R.styleable.FloatingActionMenu_menu_labels_showShadow, true); 147 | mLabelsColorNormal = attr.getColor(R.styleable.FloatingActionMenu_menu_labels_colorNormal, 0xFF333333); 148 | mLabelsColorPressed = attr.getColor(R.styleable.FloatingActionMenu_menu_labels_colorPressed, 0xFF444444); 149 | mLabelsColorRipple = attr.getColor(R.styleable.FloatingActionMenu_menu_labels_colorRipple, 0x66FFFFFF); 150 | mMenuShowShadow = attr.getBoolean(R.styleable.FloatingActionMenu_menu_showShadow, true); 151 | mMenuShadowColor = attr.getColor(R.styleable.FloatingActionMenu_menu_shadowColor, 0x66000000); 152 | mMenuShadowRadius = attr.getDimension(R.styleable.FloatingActionMenu_menu_shadowRadius, mMenuShadowRadius); 153 | mMenuShadowXOffset = attr.getDimension(R.styleable.FloatingActionMenu_menu_shadowXOffset, mMenuShadowXOffset); 154 | mMenuShadowYOffset = attr.getDimension(R.styleable.FloatingActionMenu_menu_shadowYOffset, mMenuShadowYOffset); 155 | mMenuColorNormal = attr.getColor(R.styleable.FloatingActionMenu_menu_colorNormal, 0xFFDA4336); 156 | mMenuColorPressed = attr.getColor(R.styleable.FloatingActionMenu_menu_colorPressed, 0xFFE75043); 157 | mMenuColorRipple = attr.getColor(R.styleable.FloatingActionMenu_menu_colorRipple, 0x99FFFFFF); 158 | mAnimationDelayPerItem = attr.getInt(R.styleable.FloatingActionMenu_menu_animationDelayPerItem, 50); 159 | mIcon = attr.getDrawable(R.styleable.FloatingActionMenu_menu_icon); 160 | if (mIcon == null) { 161 | mIcon = getResources().getDrawable(R.drawable.fab_add); 162 | } 163 | mLabelsSingleLine = attr.getBoolean(R.styleable.FloatingActionMenu_menu_labels_singleLine, false); 164 | mLabelsEllipsize = attr.getInt(R.styleable.FloatingActionMenu_menu_labels_ellipsize, 0); 165 | mLabelsMaxLines = attr.getInt(R.styleable.FloatingActionMenu_menu_labels_maxLines, -1); 166 | mMenuFabSize = attr.getInt(R.styleable.FloatingActionMenu_menu_fab_size, FloatingActionButton.SIZE_NORMAL); 167 | mLabelsStyle = attr.getResourceId(R.styleable.FloatingActionMenu_menu_labels_style, 0); 168 | String customFont = attr.getString(R.styleable.FloatingActionMenu_menu_labels_customFont); 169 | try { 170 | if (!TextUtils.isEmpty(customFont)) { 171 | mCustomTypefaceFromFont = Typeface.createFromAsset(getContext().getAssets(), customFont); 172 | } 173 | } catch (RuntimeException ex) { 174 | throw new IllegalArgumentException("Unable to load specified custom font: " + customFont, ex); 175 | } 176 | mOpenDirection = attr.getInt(R.styleable.FloatingActionMenu_menu_openDirection, OPEN_UP); 177 | mBackgroundColor = attr.getColor(R.styleable.FloatingActionMenu_menu_backgroundColor, Color.TRANSPARENT); 178 | 179 | if (attr.hasValue(R.styleable.FloatingActionMenu_menu_fab_label)) { 180 | mUsingMenuLabel = true; 181 | mMenuLabelText = attr.getString(R.styleable.FloatingActionMenu_menu_fab_label); 182 | } 183 | 184 | if (attr.hasValue(R.styleable.FloatingActionMenu_menu_labels_padding)) { 185 | int padding = attr.getDimensionPixelSize(R.styleable.FloatingActionMenu_menu_labels_padding, 0); 186 | initPadding(padding); 187 | } 188 | 189 | mOpenInterpolator = new OvershootInterpolator(); 190 | mCloseInterpolator = new AnticipateInterpolator(); 191 | mLabelsContext = new ContextThemeWrapper(getContext(), mLabelsStyle); 192 | 193 | initBackgroundDimAnimation(); 194 | createMenuButton(); 195 | initMenuButtonAnimations(attr); 196 | 197 | attr.recycle(); 198 | } 199 | 200 | private void initMenuButtonAnimations(TypedArray attr) { 201 | int showResId = attr.getResourceId(R.styleable.FloatingActionMenu_menu_fab_show_animation, R.anim.fab_scale_up); 202 | setMenuButtonShowAnimation(AnimationUtils.loadAnimation(getContext(), showResId)); 203 | mImageToggleShowAnimation = AnimationUtils.loadAnimation(getContext(), showResId); 204 | 205 | int hideResId = attr.getResourceId(R.styleable.FloatingActionMenu_menu_fab_hide_animation, R.anim.fab_scale_down); 206 | setMenuButtonHideAnimation(AnimationUtils.loadAnimation(getContext(), hideResId)); 207 | mImageToggleHideAnimation = AnimationUtils.loadAnimation(getContext(), hideResId); 208 | } 209 | 210 | private void initBackgroundDimAnimation() { 211 | final int maxAlpha = Color.alpha(mBackgroundColor); 212 | final int red = Color.red(mBackgroundColor); 213 | final int green = Color.green(mBackgroundColor); 214 | final int blue = Color.blue(mBackgroundColor); 215 | 216 | mShowBackgroundAnimator = ValueAnimator.ofInt(0, maxAlpha); 217 | mShowBackgroundAnimator.setDuration(ANIMATION_DURATION); 218 | mShowBackgroundAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 219 | @Override 220 | public void onAnimationUpdate(ValueAnimator animation) { 221 | Integer alpha = (Integer) animation.getAnimatedValue(); 222 | setBackgroundColor(Color.argb(alpha, red, green, blue)); 223 | } 224 | }); 225 | 226 | mHideBackgroundAnimator = ValueAnimator.ofInt(maxAlpha, 0); 227 | mHideBackgroundAnimator.setDuration(ANIMATION_DURATION); 228 | mHideBackgroundAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 229 | @Override 230 | public void onAnimationUpdate(ValueAnimator animation) { 231 | Integer alpha = (Integer) animation.getAnimatedValue(); 232 | setBackgroundColor(Color.argb(alpha, red, green, blue)); 233 | } 234 | }); 235 | } 236 | 237 | private boolean isBackgroundEnabled() { 238 | return mBackgroundColor != Color.TRANSPARENT; 239 | } 240 | 241 | private void initPadding(int padding) { 242 | mLabelsPaddingTop = padding; 243 | mLabelsPaddingRight = padding; 244 | mLabelsPaddingBottom = padding; 245 | mLabelsPaddingLeft = padding; 246 | } 247 | 248 | private void createMenuButton() { 249 | mMenuButton = new FloatingActionButton(getContext()); 250 | 251 | mMenuButton.mShowShadow = mMenuShowShadow; 252 | if (mMenuShowShadow) { 253 | mMenuButton.mShadowRadius = Util.dpToPx(getContext(), mMenuShadowRadius); 254 | mMenuButton.mShadowXOffset = Util.dpToPx(getContext(), mMenuShadowXOffset); 255 | mMenuButton.mShadowYOffset = Util.dpToPx(getContext(), mMenuShadowYOffset); 256 | } 257 | mMenuButton.setColors(mMenuColorNormal, mMenuColorPressed, mMenuColorRipple); 258 | mMenuButton.mShadowColor = mMenuShadowColor; 259 | mMenuButton.mFabSize = mMenuFabSize; 260 | mMenuButton.updateBackground(); 261 | mMenuButton.setLabelText(mMenuLabelText); 262 | 263 | mImageToggle = new ImageView(getContext()); 264 | mImageToggle.setImageDrawable(mIcon); 265 | 266 | addView(mMenuButton, super.generateDefaultLayoutParams()); 267 | addView(mImageToggle); 268 | 269 | createDefaultIconAnimation(); 270 | } 271 | 272 | private void createDefaultIconAnimation() { 273 | float collapseAngle; 274 | float expandAngle; 275 | if (mOpenDirection == OPEN_UP) { 276 | collapseAngle = mLabelsPosition == LABELS_POSITION_LEFT ? OPENED_PLUS_ROTATION_LEFT : OPENED_PLUS_ROTATION_RIGHT; 277 | expandAngle = mLabelsPosition == LABELS_POSITION_LEFT ? OPENED_PLUS_ROTATION_LEFT : OPENED_PLUS_ROTATION_RIGHT; 278 | } else { 279 | collapseAngle = mLabelsPosition == LABELS_POSITION_LEFT ? OPENED_PLUS_ROTATION_RIGHT : OPENED_PLUS_ROTATION_LEFT; 280 | expandAngle = mLabelsPosition == LABELS_POSITION_LEFT ? OPENED_PLUS_ROTATION_RIGHT : OPENED_PLUS_ROTATION_LEFT; 281 | } 282 | 283 | ObjectAnimator collapseAnimator = ObjectAnimator.ofFloat( 284 | mImageToggle, 285 | "rotation", 286 | collapseAngle, 287 | CLOSED_PLUS_ROTATION 288 | ); 289 | 290 | ObjectAnimator expandAnimator = ObjectAnimator.ofFloat( 291 | mImageToggle, 292 | "rotation", 293 | CLOSED_PLUS_ROTATION, 294 | expandAngle 295 | ); 296 | 297 | mOpenAnimatorSet.play(expandAnimator); 298 | mCloseAnimatorSet.play(collapseAnimator); 299 | 300 | mOpenAnimatorSet.setInterpolator(mOpenInterpolator); 301 | mCloseAnimatorSet.setInterpolator(mCloseInterpolator); 302 | 303 | mOpenAnimatorSet.setDuration(ANIMATION_DURATION); 304 | mCloseAnimatorSet.setDuration(ANIMATION_DURATION); 305 | } 306 | 307 | @Override 308 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 309 | int width = 0; 310 | int height = 0; 311 | mMaxButtonWidth = 0; 312 | int maxLabelWidth = 0; 313 | 314 | measureChildWithMargins(mImageToggle, widthMeasureSpec, 0, heightMeasureSpec, 0); 315 | 316 | for (int i = 0; i < mButtonsCount; i++) { 317 | View child = getChildAt(i); 318 | 319 | if (child.getVisibility() == GONE || child == mImageToggle) continue; 320 | 321 | measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); 322 | mMaxButtonWidth = Math.max(mMaxButtonWidth, child.getMeasuredWidth()); 323 | } 324 | 325 | for (int i = 0; i < mButtonsCount; i++) { 326 | int usedWidth = 0; 327 | View child = getChildAt(i); 328 | 329 | if (child.getVisibility() == GONE || child == mImageToggle) continue; 330 | 331 | usedWidth += child.getMeasuredWidth(); 332 | height += child.getMeasuredHeight(); 333 | 334 | Label label = (Label) child.getTag(R.id.fab_label); 335 | if (label != null) { 336 | int labelOffset = (mMaxButtonWidth - child.getMeasuredWidth()) / (mUsingMenuLabel ? 1 : 2); 337 | int labelUsedWidth = child.getMeasuredWidth() + label.calculateShadowWidth() + mLabelsMargin + labelOffset; 338 | measureChildWithMargins(label, widthMeasureSpec, labelUsedWidth, heightMeasureSpec, 0); 339 | usedWidth += label.getMeasuredWidth(); 340 | maxLabelWidth = Math.max(maxLabelWidth, usedWidth + labelOffset); 341 | } 342 | } 343 | 344 | width = Math.max(mMaxButtonWidth, maxLabelWidth + mLabelsMargin) + getPaddingLeft() + getPaddingRight(); 345 | 346 | height += mButtonSpacing * (mButtonsCount - 1) + getPaddingTop() + getPaddingBottom(); 347 | height = adjustForOvershoot(height); 348 | 349 | if (getLayoutParams().width == LayoutParams.MATCH_PARENT) { 350 | width = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec); 351 | } 352 | 353 | if (getLayoutParams().height == LayoutParams.MATCH_PARENT) { 354 | height = getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec); 355 | } 356 | 357 | setMeasuredDimension(width, height); 358 | } 359 | 360 | @Override 361 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 362 | int buttonsHorizontalCenter = mLabelsPosition == LABELS_POSITION_LEFT 363 | ? r - l - mMaxButtonWidth / 2 - getPaddingRight() 364 | : mMaxButtonWidth / 2 + getPaddingLeft(); 365 | boolean openUp = mOpenDirection == OPEN_UP; 366 | 367 | int menuButtonTop = openUp 368 | ? b - t - mMenuButton.getMeasuredHeight() - getPaddingBottom() 369 | : getPaddingTop(); 370 | int menuButtonLeft = buttonsHorizontalCenter - mMenuButton.getMeasuredWidth() / 2; 371 | 372 | mMenuButton.layout(menuButtonLeft, menuButtonTop, menuButtonLeft + mMenuButton.getMeasuredWidth(), 373 | menuButtonTop + mMenuButton.getMeasuredHeight()); 374 | 375 | int imageLeft = buttonsHorizontalCenter - mImageToggle.getMeasuredWidth() / 2; 376 | int imageTop = menuButtonTop + mMenuButton.getMeasuredHeight() / 2 - mImageToggle.getMeasuredHeight() / 2; 377 | 378 | mImageToggle.layout(imageLeft, imageTop, imageLeft + mImageToggle.getMeasuredWidth(), 379 | imageTop + mImageToggle.getMeasuredHeight()); 380 | 381 | int nextY = openUp 382 | ? menuButtonTop + mMenuButton.getMeasuredHeight() + mButtonSpacing 383 | : menuButtonTop; 384 | 385 | for (int i = mButtonsCount - 1; i >= 0; i--) { 386 | View child = getChildAt(i); 387 | 388 | if (child == mImageToggle) continue; 389 | 390 | FloatingActionButton fab = (FloatingActionButton) child; 391 | 392 | if (fab.getVisibility() == GONE) continue; 393 | 394 | int childX = buttonsHorizontalCenter - fab.getMeasuredWidth() / 2; 395 | int childY = openUp ? nextY - fab.getMeasuredHeight() - mButtonSpacing : nextY; 396 | 397 | if (fab != mMenuButton) { 398 | fab.layout(childX, childY, childX + fab.getMeasuredWidth(), 399 | childY + fab.getMeasuredHeight()); 400 | 401 | if (!mIsMenuOpening) { 402 | fab.hide(false); 403 | } 404 | } 405 | 406 | View label = (View) fab.getTag(R.id.fab_label); 407 | if (label != null) { 408 | int labelsOffset = (mUsingMenuLabel ? mMaxButtonWidth / 2 : fab.getMeasuredWidth() / 2) + mLabelsMargin; 409 | int labelXNearButton = mLabelsPosition == LABELS_POSITION_LEFT 410 | ? buttonsHorizontalCenter - labelsOffset 411 | : buttonsHorizontalCenter + labelsOffset; 412 | 413 | int labelXAwayFromButton = mLabelsPosition == LABELS_POSITION_LEFT 414 | ? labelXNearButton - label.getMeasuredWidth() 415 | : labelXNearButton + label.getMeasuredWidth(); 416 | 417 | int labelLeft = mLabelsPosition == LABELS_POSITION_LEFT 418 | ? labelXAwayFromButton 419 | : labelXNearButton; 420 | 421 | int labelRight = mLabelsPosition == LABELS_POSITION_LEFT 422 | ? labelXNearButton 423 | : labelXAwayFromButton; 424 | 425 | int labelTop = childY - mLabelsVerticalOffset + (fab.getMeasuredHeight() 426 | - label.getMeasuredHeight()) / 2; 427 | 428 | label.layout(labelLeft, labelTop, labelRight, labelTop + label.getMeasuredHeight()); 429 | 430 | if (!mIsMenuOpening) { 431 | label.setVisibility(INVISIBLE); 432 | } 433 | } 434 | 435 | nextY = openUp 436 | ? childY - mButtonSpacing 437 | : childY + child.getMeasuredHeight() + mButtonSpacing; 438 | } 439 | } 440 | 441 | private int adjustForOvershoot(int dimension) { 442 | return (int) (dimension * 0.03 + dimension); 443 | } 444 | 445 | @Override 446 | protected void onFinishInflate() { 447 | super.onFinishInflate(); 448 | bringChildToFront(mMenuButton); 449 | bringChildToFront(mImageToggle); 450 | mButtonsCount = getChildCount(); 451 | createLabels(); 452 | } 453 | 454 | private void createLabels() { 455 | for (int i = 0; i < mButtonsCount; i++) { 456 | 457 | if (getChildAt(i) == mImageToggle) continue; 458 | 459 | final FloatingActionButton fab = (FloatingActionButton) getChildAt(i); 460 | 461 | if (fab.getTag(R.id.fab_label) != null) continue; 462 | 463 | addLabel(fab); 464 | 465 | if (fab == mMenuButton) { 466 | mMenuButton.setOnClickListener(new OnClickListener() { 467 | @Override 468 | public void onClick(View v) { 469 | toggle(mIsAnimated); 470 | } 471 | }); 472 | } 473 | } 474 | } 475 | 476 | private void addLabel(FloatingActionButton fab) { 477 | String text = fab.getLabelText(); 478 | 479 | if (TextUtils.isEmpty(text)) return; 480 | 481 | final Label label = new Label(mLabelsContext); 482 | label.setClickable(true); 483 | label.setFab(fab); 484 | label.setShowAnimation(AnimationUtils.loadAnimation(getContext(), mLabelsShowAnimation)); 485 | label.setHideAnimation(AnimationUtils.loadAnimation(getContext(), mLabelsHideAnimation)); 486 | 487 | if (mLabelsStyle > 0) { 488 | label.setTextAppearance(getContext(), mLabelsStyle); 489 | label.setShowShadow(false); 490 | label.setUsingStyle(true); 491 | } else { 492 | label.setColors(mLabelsColorNormal, mLabelsColorPressed, mLabelsColorRipple); 493 | label.setShowShadow(mLabelsShowShadow); 494 | label.setCornerRadius(mLabelsCornerRadius); 495 | if (mLabelsEllipsize > 0) { 496 | setLabelEllipsize(label); 497 | } 498 | label.setMaxLines(mLabelsMaxLines); 499 | label.updateBackground(); 500 | 501 | label.setTextSize(TypedValue.COMPLEX_UNIT_PX, mLabelsTextSize); 502 | label.setTextColor(mLabelsTextColor); 503 | 504 | int left = mLabelsPaddingLeft; 505 | int top = mLabelsPaddingTop; 506 | if (mLabelsShowShadow) { 507 | left += fab.getShadowRadius() + Math.abs(fab.getShadowXOffset()); 508 | top += fab.getShadowRadius() + Math.abs(fab.getShadowYOffset()); 509 | } 510 | 511 | label.setPadding( 512 | left, 513 | top, 514 | mLabelsPaddingLeft, 515 | mLabelsPaddingTop 516 | ); 517 | 518 | if (mLabelsMaxLines < 0 || mLabelsSingleLine) { 519 | label.setSingleLine(mLabelsSingleLine); 520 | } 521 | } 522 | 523 | if (mCustomTypefaceFromFont != null) { 524 | label.setTypeface(mCustomTypefaceFromFont); 525 | } 526 | label.setText(text); 527 | label.setOnClickListener(fab.getOnClickListener()); 528 | 529 | addView(label); 530 | fab.setTag(R.id.fab_label, label); 531 | } 532 | 533 | private void setLabelEllipsize(Label label) { 534 | switch (mLabelsEllipsize) { 535 | case 1: 536 | label.setEllipsize(TextUtils.TruncateAt.START); 537 | break; 538 | case 2: 539 | label.setEllipsize(TextUtils.TruncateAt.MIDDLE); 540 | break; 541 | case 3: 542 | label.setEllipsize(TextUtils.TruncateAt.END); 543 | break; 544 | case 4: 545 | label.setEllipsize(TextUtils.TruncateAt.MARQUEE); 546 | break; 547 | } 548 | } 549 | 550 | @Override 551 | public MarginLayoutParams generateLayoutParams(AttributeSet attrs) { 552 | return new MarginLayoutParams(getContext(), attrs); 553 | } 554 | 555 | @Override 556 | protected MarginLayoutParams generateLayoutParams(LayoutParams p) { 557 | return new MarginLayoutParams(p); 558 | } 559 | 560 | @Override 561 | protected MarginLayoutParams generateDefaultLayoutParams() { 562 | return new MarginLayoutParams(MarginLayoutParams.WRAP_CONTENT, 563 | MarginLayoutParams.WRAP_CONTENT); 564 | } 565 | 566 | @Override 567 | protected boolean checkLayoutParams(LayoutParams p) { 568 | return p instanceof MarginLayoutParams; 569 | } 570 | 571 | private void hideMenuButtonWithImage(boolean animate) { 572 | if (!isMenuButtonHidden()) { 573 | mMenuButton.hide(animate); 574 | if (animate) { 575 | mImageToggle.startAnimation(mImageToggleHideAnimation); 576 | } 577 | mImageToggle.setVisibility(INVISIBLE); 578 | mIsMenuButtonAnimationRunning = false; 579 | } 580 | } 581 | 582 | private void showMenuButtonWithImage(boolean animate) { 583 | if (isMenuButtonHidden()) { 584 | mMenuButton.show(animate); 585 | if (animate) { 586 | mImageToggle.startAnimation(mImageToggleShowAnimation); 587 | } 588 | mImageToggle.setVisibility(VISIBLE); 589 | } 590 | } 591 | 592 | @Override 593 | public boolean onTouchEvent(MotionEvent event) { 594 | if (mIsSetClosedOnTouchOutside) { 595 | boolean handled = false; 596 | switch (event.getAction()) { 597 | case MotionEvent.ACTION_DOWN: 598 | handled = isOpened(); 599 | break; 600 | case MotionEvent.ACTION_UP: 601 | close(mIsAnimated); 602 | handled = true; 603 | } 604 | 605 | return handled; 606 | } 607 | 608 | return super.onTouchEvent(event); 609 | } 610 | 611 | /* ===== API methods ===== */ 612 | 613 | public boolean isOpened() { 614 | return mMenuOpened; 615 | } 616 | 617 | public void toggle(boolean animate) { 618 | if (isOpened()) { 619 | close(animate); 620 | } else { 621 | open(animate); 622 | } 623 | } 624 | 625 | public void open(final boolean animate) { 626 | if (!isOpened()) { 627 | if (isBackgroundEnabled()) { 628 | mShowBackgroundAnimator.start(); 629 | } 630 | 631 | if (mIconAnimated) { 632 | if (mIconToggleSet != null) { 633 | mIconToggleSet.start(); 634 | } else { 635 | mCloseAnimatorSet.cancel(); 636 | mOpenAnimatorSet.start(); 637 | } 638 | } 639 | 640 | int delay = 0; 641 | int counter = 0; 642 | mIsMenuOpening = true; 643 | for (int i = getChildCount() - 1; i >= 0; i--) { 644 | View child = getChildAt(i); 645 | if (child instanceof FloatingActionButton && child.getVisibility() != GONE) { 646 | counter++; 647 | 648 | final FloatingActionButton fab = (FloatingActionButton) child; 649 | mUiHandler.postDelayed(new Runnable() { 650 | @Override 651 | public void run() { 652 | if (isOpened()) return; 653 | 654 | if (fab != mMenuButton) { 655 | fab.show(animate); 656 | } 657 | 658 | Label label = (Label) fab.getTag(R.id.fab_label); 659 | if (label != null && label.isHandleVisibilityChanges()) { 660 | label.show(animate); 661 | } 662 | } 663 | }, delay); 664 | delay += mAnimationDelayPerItem; 665 | } 666 | } 667 | 668 | mUiHandler.postDelayed(new Runnable() { 669 | @Override 670 | public void run() { 671 | mMenuOpened = true; 672 | 673 | if (mToggleListener != null) { 674 | mToggleListener.onMenuToggle(true); 675 | } 676 | } 677 | }, ++counter * mAnimationDelayPerItem); 678 | } 679 | } 680 | 681 | public void close(final boolean animate) { 682 | if (isOpened()) { 683 | if (isBackgroundEnabled()) { 684 | mHideBackgroundAnimator.start(); 685 | } 686 | 687 | if (mIconAnimated) { 688 | if (mIconToggleSet != null) { 689 | mIconToggleSet.start(); 690 | } else { 691 | mCloseAnimatorSet.start(); 692 | mOpenAnimatorSet.cancel(); 693 | } 694 | } 695 | 696 | int delay = 0; 697 | int counter = 0; 698 | mIsMenuOpening = false; 699 | for (int i = 0; i < getChildCount(); i++) { 700 | View child = getChildAt(i); 701 | if (child instanceof FloatingActionButton && child.getVisibility() != GONE) { 702 | counter++; 703 | 704 | final FloatingActionButton fab = (FloatingActionButton) child; 705 | mUiHandler.postDelayed(new Runnable() { 706 | @Override 707 | public void run() { 708 | if (!isOpened()) return; 709 | 710 | if (fab != mMenuButton) { 711 | fab.hide(animate); 712 | } 713 | 714 | Label label = (Label) fab.getTag(R.id.fab_label); 715 | if (label != null && label.isHandleVisibilityChanges()) { 716 | label.hide(animate); 717 | } 718 | } 719 | }, delay); 720 | delay += mAnimationDelayPerItem; 721 | } 722 | } 723 | 724 | mUiHandler.postDelayed(new Runnable() { 725 | @Override 726 | public void run() { 727 | mMenuOpened = false; 728 | 729 | if (mToggleListener != null) { 730 | mToggleListener.onMenuToggle(false); 731 | } 732 | } 733 | }, ++counter * mAnimationDelayPerItem); 734 | } 735 | } 736 | 737 | /** 738 | * Sets the {@link android.view.animation.Interpolator} for FloatingActionButton's icon animation. 739 | * 740 | * @param interpolator the Interpolator to be used in animation 741 | */ 742 | public void setIconAnimationInterpolator(Interpolator interpolator) { 743 | mOpenAnimatorSet.setInterpolator(interpolator); 744 | mCloseAnimatorSet.setInterpolator(interpolator); 745 | } 746 | 747 | public void setIconAnimationOpenInterpolator(Interpolator openInterpolator) { 748 | mOpenAnimatorSet.setInterpolator(openInterpolator); 749 | } 750 | 751 | public void setIconAnimationCloseInterpolator(Interpolator closeInterpolator) { 752 | mCloseAnimatorSet.setInterpolator(closeInterpolator); 753 | } 754 | 755 | /** 756 | * Sets whether open and close actions should be animated 757 | * 758 | * @param animated if false - menu items will appear/disappear instantly without any animation 759 | */ 760 | public void setAnimated(boolean animated) { 761 | mIsAnimated = animated; 762 | mOpenAnimatorSet.setDuration(animated ? ANIMATION_DURATION : 0); 763 | mCloseAnimatorSet.setDuration(animated ? ANIMATION_DURATION : 0); 764 | } 765 | 766 | public boolean isAnimated() { 767 | return mIsAnimated; 768 | } 769 | 770 | public void setAnimationDelayPerItem(int animationDelayPerItem) { 771 | mAnimationDelayPerItem = animationDelayPerItem; 772 | } 773 | 774 | public int getAnimationDelayPerItem() { 775 | return mAnimationDelayPerItem; 776 | } 777 | 778 | public void setOnMenuToggleListener(OnMenuToggleListener listener) { 779 | mToggleListener = listener; 780 | } 781 | 782 | public void setIconAnimated(boolean animated) { 783 | mIconAnimated = animated; 784 | } 785 | 786 | public boolean isIconAnimated() { 787 | return mIconAnimated; 788 | } 789 | 790 | public ImageView getMenuIconView() { 791 | return mImageToggle; 792 | } 793 | 794 | public void setIconToggleAnimatorSet(AnimatorSet toggleAnimatorSet) { 795 | mIconToggleSet = toggleAnimatorSet; 796 | } 797 | 798 | public AnimatorSet getIconToggleAnimatorSet() { 799 | return mIconToggleSet; 800 | } 801 | 802 | public void setMenuButtonShowAnimation(Animation showAnimation) { 803 | mMenuButtonShowAnimation = showAnimation; 804 | mMenuButton.setShowAnimation(showAnimation); 805 | } 806 | 807 | public void setMenuButtonHideAnimation(Animation hideAnimation) { 808 | mMenuButtonHideAnimation = hideAnimation; 809 | mMenuButton.setHideAnimation(hideAnimation); 810 | } 811 | 812 | public boolean isMenuHidden() { 813 | return getVisibility() == INVISIBLE; 814 | } 815 | 816 | public boolean isMenuButtonHidden() { 817 | return mMenuButton.isHidden(); 818 | } 819 | 820 | /** 821 | * Makes the whole {@link #FloatingActionMenu} to appear and sets its visibility to {@link #VISIBLE} 822 | * 823 | * @param animate if true - plays "show animation" 824 | */ 825 | public void showMenu(boolean animate) { 826 | if (isMenuHidden()) { 827 | if (animate) { 828 | startAnimation(mMenuButtonShowAnimation); 829 | } 830 | setVisibility(VISIBLE); 831 | } 832 | } 833 | 834 | /** 835 | * Makes the {@link #FloatingActionMenu} to disappear and sets its visibility to {@link #INVISIBLE} 836 | * 837 | * @param animate if true - plays "hide animation" 838 | */ 839 | public void hideMenu(final boolean animate) { 840 | if (!isMenuHidden() && !mIsMenuButtonAnimationRunning) { 841 | mIsMenuButtonAnimationRunning = true; 842 | if (isOpened()) { 843 | close(animate); 844 | mUiHandler.postDelayed(new Runnable() { 845 | @Override 846 | public void run() { 847 | if (animate) { 848 | startAnimation(mMenuButtonHideAnimation); 849 | } 850 | setVisibility(INVISIBLE); 851 | mIsMenuButtonAnimationRunning = false; 852 | } 853 | }, mAnimationDelayPerItem * mButtonsCount); 854 | } else { 855 | if (animate) { 856 | startAnimation(mMenuButtonHideAnimation); 857 | } 858 | setVisibility(INVISIBLE); 859 | mIsMenuButtonAnimationRunning = false; 860 | } 861 | } 862 | } 863 | 864 | public void toggleMenu(boolean animate) { 865 | if (isMenuHidden()) { 866 | showMenu(animate); 867 | } else { 868 | hideMenu(animate); 869 | } 870 | } 871 | 872 | /** 873 | * Makes the {@link FloatingActionButton} to appear inside the {@link #FloatingActionMenu} and 874 | * sets its visibility to {@link #VISIBLE} 875 | * 876 | * @param animate if true - plays "show animation" 877 | */ 878 | public void showMenuButton(boolean animate) { 879 | if (isMenuButtonHidden()) { 880 | showMenuButtonWithImage(animate); 881 | } 882 | } 883 | 884 | /** 885 | * Makes the {@link FloatingActionButton} to disappear inside the {@link #FloatingActionMenu} and 886 | * sets its visibility to {@link #INVISIBLE} 887 | * 888 | * @param animate if true - plays "hide animation" 889 | */ 890 | public void hideMenuButton(final boolean animate) { 891 | if (!isMenuButtonHidden() && !mIsMenuButtonAnimationRunning) { 892 | mIsMenuButtonAnimationRunning = true; 893 | if (isOpened()) { 894 | close(animate); 895 | mUiHandler.postDelayed(new Runnable() { 896 | @Override 897 | public void run() { 898 | hideMenuButtonWithImage(animate); 899 | } 900 | }, mAnimationDelayPerItem * mButtonsCount); 901 | } else { 902 | hideMenuButtonWithImage(animate); 903 | } 904 | } 905 | } 906 | 907 | public void toggleMenuButton(boolean animate) { 908 | if (isMenuButtonHidden()) { 909 | showMenuButton(animate); 910 | } else { 911 | hideMenuButton(animate); 912 | } 913 | } 914 | 915 | public void setClosedOnTouchOutside(boolean close) { 916 | mIsSetClosedOnTouchOutside = close; 917 | } 918 | 919 | public void setMenuButtonColorNormal(int color) { 920 | mMenuColorNormal = color; 921 | mMenuButton.setColorNormal(color); 922 | } 923 | 924 | public void setMenuButtonColorNormalResId(int colorResId) { 925 | mMenuColorNormal = getResources().getColor(colorResId); 926 | mMenuButton.setColorNormalResId(colorResId); 927 | } 928 | 929 | public int getMenuButtonColorNormal() { 930 | return mMenuColorNormal; 931 | } 932 | 933 | public void setMenuButtonColorPressed(int color) { 934 | mMenuColorPressed = color; 935 | mMenuButton.setColorPressed(color); 936 | } 937 | 938 | public void setMenuButtonColorPressedResId(int colorResId) { 939 | mMenuColorPressed = getResources().getColor(colorResId); 940 | mMenuButton.setColorPressedResId(colorResId); 941 | } 942 | 943 | public int getMenuButtonColorPressed() { 944 | return mMenuColorPressed; 945 | } 946 | 947 | public void setMenuButtonColorRipple(int color) { 948 | mMenuColorRipple = color; 949 | mMenuButton.setColorRipple(color); 950 | } 951 | 952 | public void setMenuButtonColorRippleResId(int colorResId) { 953 | mMenuColorRipple = getResources().getColor(colorResId); 954 | mMenuButton.setColorRippleResId(colorResId); 955 | } 956 | 957 | public int getMenuButtonColorRipple() { 958 | return mMenuColorRipple; 959 | } 960 | 961 | public void addMenuButton(FloatingActionButton fab) { 962 | addView(fab, mButtonsCount - 2); 963 | mButtonsCount++; 964 | addLabel(fab); 965 | } 966 | 967 | public void removeMenuButton(FloatingActionButton fab) { 968 | removeView(fab.getLabelView()); 969 | removeView(fab); 970 | mButtonsCount--; 971 | } 972 | 973 | public void addMenuButton(FloatingActionButton fab, int index) { 974 | int size = mButtonsCount - 2; 975 | if (index < 0) { 976 | index = 0; 977 | } else if (index > size) { 978 | index = size; 979 | } 980 | 981 | addView(fab, index); 982 | mButtonsCount++; 983 | addLabel(fab); 984 | } 985 | 986 | public void removeAllMenuButtons() { 987 | close(true); 988 | 989 | List viewsToRemove = new ArrayList<>(); 990 | for (int i = 0; i < getChildCount(); i++) { 991 | View v = getChildAt(i); 992 | if (v != mMenuButton && v != mImageToggle && v instanceof FloatingActionButton) { 993 | viewsToRemove.add((FloatingActionButton) v); 994 | } 995 | } 996 | for (FloatingActionButton v : viewsToRemove) { 997 | removeMenuButton(v); 998 | } 999 | } 1000 | 1001 | public void setMenuButtonLabelText(String text) { 1002 | mMenuButton.setLabelText(text); 1003 | } 1004 | 1005 | public String getMenuButtonLabelText() { 1006 | return mMenuLabelText; 1007 | } 1008 | 1009 | public void setOnMenuButtonClickListener(OnClickListener clickListener) { 1010 | mMenuButton.setOnClickListener(clickListener); 1011 | } 1012 | 1013 | public void setOnMenuButtonLongClickListener(OnLongClickListener longClickListener) { 1014 | mMenuButton.setOnLongClickListener(longClickListener); 1015 | } 1016 | } 1017 | -------------------------------------------------------------------------------- /library/src/main/java/com/github/clans/fab/Label.java: -------------------------------------------------------------------------------- 1 | package com.github.clans.fab; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.content.res.ColorStateList; 6 | import android.graphics.Canvas; 7 | import android.graphics.ColorFilter; 8 | import android.graphics.Outline; 9 | import android.graphics.Paint; 10 | import android.graphics.PorterDuff; 11 | import android.graphics.PorterDuffXfermode; 12 | import android.graphics.RectF; 13 | import android.graphics.Xfermode; 14 | import android.graphics.drawable.Drawable; 15 | import android.graphics.drawable.LayerDrawable; 16 | import android.graphics.drawable.RippleDrawable; 17 | import android.graphics.drawable.ShapeDrawable; 18 | import android.graphics.drawable.StateListDrawable; 19 | import android.graphics.drawable.shapes.RoundRectShape; 20 | import android.os.Build; 21 | import android.util.AttributeSet; 22 | import android.view.GestureDetector; 23 | import android.view.MotionEvent; 24 | import android.view.View; 25 | import android.view.ViewOutlineProvider; 26 | import android.view.animation.Animation; 27 | import android.widget.TextView; 28 | 29 | public class Label extends TextView { 30 | 31 | private static final Xfermode PORTER_DUFF_CLEAR = new PorterDuffXfermode(PorterDuff.Mode.CLEAR); 32 | 33 | private int mShadowRadius; 34 | private int mShadowXOffset; 35 | private int mShadowYOffset; 36 | private int mShadowColor; 37 | private Drawable mBackgroundDrawable; 38 | private boolean mShowShadow = true; 39 | private int mRawWidth; 40 | private int mRawHeight; 41 | private int mColorNormal; 42 | private int mColorPressed; 43 | private int mColorRipple; 44 | private int mCornerRadius; 45 | private FloatingActionButton mFab; 46 | private Animation mShowAnimation; 47 | private Animation mHideAnimation; 48 | private boolean mUsingStyle; 49 | private boolean mHandleVisibilityChanges = true; 50 | 51 | public Label(Context context) { 52 | super(context); 53 | } 54 | 55 | public Label(Context context, AttributeSet attrs) { 56 | super(context, attrs); 57 | } 58 | 59 | public Label(Context context, AttributeSet attrs, int defStyleAttr) { 60 | super(context, attrs, defStyleAttr); 61 | } 62 | 63 | @Override 64 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 65 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 66 | setMeasuredDimension(calculateMeasuredWidth(), calculateMeasuredHeight()); 67 | } 68 | 69 | private int calculateMeasuredWidth() { 70 | if (mRawWidth == 0) { 71 | mRawWidth = getMeasuredWidth(); 72 | } 73 | return getMeasuredWidth() + calculateShadowWidth(); 74 | } 75 | 76 | private int calculateMeasuredHeight() { 77 | if (mRawHeight == 0) { 78 | mRawHeight = getMeasuredHeight(); 79 | } 80 | return getMeasuredHeight() + calculateShadowHeight(); 81 | } 82 | 83 | int calculateShadowWidth() { 84 | return mShowShadow ? (mShadowRadius + Math.abs(mShadowXOffset)) : 0; 85 | } 86 | 87 | int calculateShadowHeight() { 88 | return mShowShadow ? (mShadowRadius + Math.abs(mShadowYOffset)) : 0; 89 | } 90 | 91 | void updateBackground() { 92 | LayerDrawable layerDrawable; 93 | if (mShowShadow) { 94 | layerDrawable = new LayerDrawable(new Drawable[]{ 95 | new Shadow(), 96 | createFillDrawable() 97 | }); 98 | 99 | int leftInset = mShadowRadius + Math.abs(mShadowXOffset); 100 | int topInset = mShadowRadius + Math.abs(mShadowYOffset); 101 | int rightInset = (mShadowRadius + Math.abs(mShadowXOffset)); 102 | int bottomInset = (mShadowRadius + Math.abs(mShadowYOffset)); 103 | 104 | layerDrawable.setLayerInset( 105 | 1, 106 | leftInset, 107 | topInset, 108 | rightInset, 109 | bottomInset 110 | ); 111 | } else { 112 | layerDrawable = new LayerDrawable(new Drawable[]{ 113 | createFillDrawable() 114 | }); 115 | } 116 | 117 | setBackgroundCompat(layerDrawable); 118 | } 119 | 120 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 121 | private Drawable createFillDrawable() { 122 | StateListDrawable drawable = new StateListDrawable(); 123 | drawable.addState(new int[]{android.R.attr.state_pressed}, createRectDrawable(mColorPressed)); 124 | drawable.addState(new int[]{}, createRectDrawable(mColorNormal)); 125 | 126 | if (Util.hasLollipop()) { 127 | RippleDrawable ripple = new RippleDrawable(new ColorStateList(new int[][]{{}}, 128 | new int[]{mColorRipple}), drawable, null); 129 | setOutlineProvider(new ViewOutlineProvider() { 130 | @Override 131 | public void getOutline(View view, Outline outline) { 132 | outline.setOval(0, 0, view.getWidth(), view.getHeight()); 133 | } 134 | }); 135 | setClipToOutline(true); 136 | mBackgroundDrawable = ripple; 137 | return ripple; 138 | } 139 | 140 | mBackgroundDrawable = drawable; 141 | return drawable; 142 | } 143 | 144 | private Drawable createRectDrawable(int color) { 145 | RoundRectShape shape = new RoundRectShape( 146 | new float[]{ 147 | mCornerRadius, 148 | mCornerRadius, 149 | mCornerRadius, 150 | mCornerRadius, 151 | mCornerRadius, 152 | mCornerRadius, 153 | mCornerRadius, 154 | mCornerRadius 155 | }, 156 | null, 157 | null); 158 | ShapeDrawable shapeDrawable = new ShapeDrawable(shape); 159 | shapeDrawable.getPaint().setColor(color); 160 | return shapeDrawable; 161 | } 162 | 163 | private void setShadow(FloatingActionButton fab) { 164 | mShadowColor = fab.getShadowColor(); 165 | mShadowRadius = fab.getShadowRadius(); 166 | mShadowXOffset = fab.getShadowXOffset(); 167 | mShadowYOffset = fab.getShadowYOffset(); 168 | mShowShadow = fab.hasShadow(); 169 | } 170 | 171 | @SuppressWarnings("deprecation") 172 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 173 | private void setBackgroundCompat(Drawable drawable) { 174 | if (Util.hasJellyBean()) { 175 | setBackground(drawable); 176 | } else { 177 | setBackgroundDrawable(drawable); 178 | } 179 | } 180 | 181 | private void playShowAnimation() { 182 | if (mShowAnimation != null) { 183 | mHideAnimation.cancel(); 184 | startAnimation(mShowAnimation); 185 | } 186 | } 187 | 188 | private void playHideAnimation() { 189 | if (mHideAnimation != null) { 190 | mShowAnimation.cancel(); 191 | startAnimation(mHideAnimation); 192 | } 193 | } 194 | 195 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 196 | void onActionDown() { 197 | if (mUsingStyle) { 198 | mBackgroundDrawable = getBackground(); 199 | } 200 | 201 | if (mBackgroundDrawable instanceof StateListDrawable) { 202 | StateListDrawable drawable = (StateListDrawable) mBackgroundDrawable; 203 | drawable.setState(new int[]{android.R.attr.state_pressed}); 204 | } else if (Util.hasLollipop() && mBackgroundDrawable instanceof RippleDrawable) { 205 | RippleDrawable ripple = (RippleDrawable) mBackgroundDrawable; 206 | ripple.setState(new int[]{android.R.attr.state_enabled, android.R.attr.state_pressed}); 207 | ripple.setHotspot(getMeasuredWidth() / 2, getMeasuredHeight() / 2); 208 | ripple.setVisible(true, true); 209 | } 210 | // setPressed(true); 211 | } 212 | 213 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 214 | void onActionUp() { 215 | if (mUsingStyle) { 216 | mBackgroundDrawable = getBackground(); 217 | } 218 | 219 | if (mBackgroundDrawable instanceof StateListDrawable) { 220 | StateListDrawable drawable = (StateListDrawable) mBackgroundDrawable; 221 | drawable.setState(new int[]{}); 222 | } else if (Util.hasLollipop() && mBackgroundDrawable instanceof RippleDrawable) { 223 | RippleDrawable ripple = (RippleDrawable) mBackgroundDrawable; 224 | ripple.setState(new int[]{}); 225 | ripple.setHotspot(getMeasuredWidth() / 2, getMeasuredHeight() / 2); 226 | ripple.setVisible(true, true); 227 | } 228 | // setPressed(false); 229 | } 230 | 231 | void setFab(FloatingActionButton fab) { 232 | mFab = fab; 233 | setShadow(fab); 234 | } 235 | 236 | void setShowShadow(boolean show) { 237 | mShowShadow = show; 238 | } 239 | 240 | void setCornerRadius(int cornerRadius) { 241 | mCornerRadius = cornerRadius; 242 | } 243 | 244 | void setColors(int colorNormal, int colorPressed, int colorRipple) { 245 | mColorNormal = colorNormal; 246 | mColorPressed = colorPressed; 247 | mColorRipple = colorRipple; 248 | } 249 | 250 | void show(boolean animate) { 251 | if (animate) { 252 | playShowAnimation(); 253 | } 254 | setVisibility(VISIBLE); 255 | } 256 | 257 | void hide(boolean animate) { 258 | if (animate) { 259 | playHideAnimation(); 260 | } 261 | setVisibility(INVISIBLE); 262 | } 263 | 264 | void setShowAnimation(Animation showAnimation) { 265 | mShowAnimation = showAnimation; 266 | } 267 | 268 | void setHideAnimation(Animation hideAnimation) { 269 | mHideAnimation = hideAnimation; 270 | } 271 | 272 | void setUsingStyle(boolean usingStyle) { 273 | mUsingStyle = usingStyle; 274 | } 275 | 276 | void setHandleVisibilityChanges(boolean handle) { 277 | mHandleVisibilityChanges = handle; 278 | } 279 | 280 | boolean isHandleVisibilityChanges() { 281 | return mHandleVisibilityChanges; 282 | } 283 | 284 | @Override 285 | public boolean onTouchEvent(MotionEvent event) { 286 | if (mFab == null || mFab.getOnClickListener() == null || !mFab.isEnabled()) { 287 | return super.onTouchEvent(event); 288 | } 289 | 290 | int action = event.getAction(); 291 | switch (action) { 292 | case MotionEvent.ACTION_UP: 293 | onActionUp(); 294 | mFab.onActionUp(); 295 | break; 296 | 297 | case MotionEvent.ACTION_CANCEL: 298 | onActionUp(); 299 | mFab.onActionUp(); 300 | break; 301 | } 302 | 303 | mGestureDetector.onTouchEvent(event); 304 | return super.onTouchEvent(event); 305 | } 306 | 307 | GestureDetector mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() { 308 | 309 | @Override 310 | public boolean onDown(MotionEvent e) { 311 | onActionDown(); 312 | if (mFab != null) { 313 | mFab.onActionDown(); 314 | } 315 | return super.onDown(e); 316 | } 317 | 318 | @Override 319 | public boolean onSingleTapUp(MotionEvent e) { 320 | onActionUp(); 321 | if (mFab != null) { 322 | mFab.onActionUp(); 323 | } 324 | return super.onSingleTapUp(e); 325 | } 326 | }); 327 | 328 | private class Shadow extends Drawable { 329 | 330 | private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 331 | private Paint mErase = new Paint(Paint.ANTI_ALIAS_FLAG); 332 | 333 | private Shadow() { 334 | this.init(); 335 | } 336 | 337 | private void init() { 338 | setLayerType(LAYER_TYPE_SOFTWARE, null); 339 | mPaint.setStyle(Paint.Style.FILL); 340 | mPaint.setColor(mColorNormal); 341 | 342 | mErase.setXfermode(PORTER_DUFF_CLEAR); 343 | 344 | if (!isInEditMode()) { 345 | mPaint.setShadowLayer(mShadowRadius, mShadowXOffset, mShadowYOffset, mShadowColor); 346 | } 347 | } 348 | 349 | @Override 350 | public void draw(Canvas canvas) { 351 | RectF shadowRect = new RectF( 352 | mShadowRadius + Math.abs(mShadowXOffset), 353 | mShadowRadius + Math.abs(mShadowYOffset), 354 | mRawWidth, 355 | mRawHeight 356 | ); 357 | 358 | canvas.drawRoundRect(shadowRect, mCornerRadius, mCornerRadius, mPaint); 359 | canvas.drawRoundRect(shadowRect, mCornerRadius, mCornerRadius, mErase); 360 | } 361 | 362 | @Override 363 | public void setAlpha(int alpha) { 364 | 365 | } 366 | 367 | @Override 368 | public void setColorFilter(ColorFilter cf) { 369 | 370 | } 371 | 372 | @Override 373 | public int getOpacity() { 374 | return 0; 375 | } 376 | } 377 | } 378 | -------------------------------------------------------------------------------- /library/src/main/java/com/github/clans/fab/Util.java: -------------------------------------------------------------------------------- 1 | package com.github.clans.fab; 2 | 3 | import android.content.Context; 4 | import android.os.Build; 5 | 6 | final class Util { 7 | 8 | private Util() { 9 | } 10 | 11 | static int dpToPx(Context context, float dp) { 12 | final float scale = context.getResources().getDisplayMetrics().density; 13 | return Math.round(dp * scale); 14 | } 15 | 16 | static boolean hasJellyBean() { 17 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN; 18 | } 19 | 20 | static boolean hasLollipop() { 21 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /library/src/main/res/anim/fab_scale_down.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /library/src/main/res/anim/fab_scale_up.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /library/src/main/res/anim/fab_slide_in_from_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | 12 | -------------------------------------------------------------------------------- /library/src/main/res/anim/fab_slide_in_from_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | 12 | -------------------------------------------------------------------------------- /library/src/main/res/anim/fab_slide_out_to_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | 12 | -------------------------------------------------------------------------------- /library/src/main/res/anim/fab_slide_out_to_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | 12 | -------------------------------------------------------------------------------- /library/src/main/res/drawable-hdpi/fab_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/library/src/main/res/drawable-hdpi/fab_add.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-mdpi/fab_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/library/src/main/res/drawable-mdpi/fab_add.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-xhdpi/fab_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/library/src/main/res/drawable-xhdpi/fab_add.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-xxhdpi/fab_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/library/src/main/res/drawable-xxhdpi/fab_add.png -------------------------------------------------------------------------------- /library/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /library/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 56dp 5 | 40dp 6 | 14sp 7 | 8 | -------------------------------------------------------------------------------- /library/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /sample/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /sample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.3" 6 | 7 | defaultConfig { 8 | applicationId "com.github.fab.sample" 9 | minSdkVersion 14 10 | targetSdkVersion 23 11 | versionCode Integer.parseInt(project.VERSION_CODE) 12 | versionName project.VERSION_NAME 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | debuggable false 18 | proguardFiles 'proguard-rules.pro' 19 | } 20 | 21 | debug { 22 | minifyEnabled false 23 | debuggable true 24 | proguardFiles 'proguard-rules.pro' 25 | } 26 | } 27 | } 28 | 29 | dependencies { 30 | compile fileTree(dir: 'libs', include: ['*.jar']) 31 | compile 'com.android.support:appcompat-v7:23.3.0' 32 | compile 'com.android.support:recyclerview-v7:23.3.0' 33 | compile 'com.android.support:design:23.3.0' 34 | compile project(':library') 35 | } 36 | -------------------------------------------------------------------------------- /sample/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 D:\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 | #} -------------------------------------------------------------------------------- /sample/src/androidTest/java/com/dmytrotarianyk/fab/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.dmytrotarianyk.fab; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /sample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /sample/src/main/java/com/github/clans/fab/sample/HomeFragment.java: -------------------------------------------------------------------------------- 1 | package com.github.clans.fab.sample; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.os.Handler; 6 | import android.support.annotation.Nullable; 7 | import android.support.v4.app.Fragment; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.view.animation.AnimationUtils; 12 | import android.widget.AbsListView; 13 | import android.widget.ArrayAdapter; 14 | import android.widget.ListView; 15 | 16 | import com.github.clans.fab.FloatingActionButton; 17 | import com.github.fab.sample.R; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | import java.util.Locale; 22 | 23 | public class HomeFragment extends Fragment { 24 | 25 | private ListView mListView; 26 | private FloatingActionButton mFab; 27 | private int mPreviousVisibleItem; 28 | 29 | @Nullable 30 | @Override 31 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 32 | return inflater.inflate(R.layout.home_fragment, container, false); 33 | } 34 | 35 | @Override 36 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 37 | super.onViewCreated(view, savedInstanceState); 38 | mListView = (ListView) view.findViewById(R.id.list); 39 | mFab = (FloatingActionButton) view.findViewById(R.id.fab); 40 | } 41 | 42 | @Override 43 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 44 | super.onActivityCreated(savedInstanceState); 45 | 46 | Locale[] availableLocales = Locale.getAvailableLocales(); 47 | List locales = new ArrayList<>(); 48 | for (Locale locale : availableLocales) { 49 | locales.add(locale.getDisplayName()); 50 | } 51 | 52 | mListView.setAdapter(new ArrayAdapter<>(getActivity(), android.R.layout.simple_list_item_1, 53 | android.R.id.text1, locales)); 54 | 55 | mFab.hide(false); 56 | new Handler().postDelayed(new Runnable() { 57 | @Override 58 | public void run() { 59 | mFab.show(true); 60 | mFab.setShowAnimation(AnimationUtils.loadAnimation(getActivity(), R.anim.show_from_bottom)); 61 | mFab.setHideAnimation(AnimationUtils.loadAnimation(getActivity(), R.anim.hide_to_bottom)); 62 | } 63 | }, 300); 64 | 65 | mListView.setOnScrollListener(new AbsListView.OnScrollListener() { 66 | @Override 67 | public void onScrollStateChanged(AbsListView view, int scrollState) { 68 | } 69 | 70 | @Override 71 | public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { 72 | if (firstVisibleItem > mPreviousVisibleItem) { 73 | mFab.hide(true); 74 | } else if (firstVisibleItem < mPreviousVisibleItem) { 75 | mFab.show(true); 76 | } 77 | mPreviousVisibleItem = firstVisibleItem; 78 | } 79 | }); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /sample/src/main/java/com/github/clans/fab/sample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.github.clans.fab.sample; 2 | 3 | import android.os.Bundle; 4 | import android.support.design.widget.NavigationView; 5 | import android.support.v4.app.Fragment; 6 | import android.support.v4.app.FragmentTransaction; 7 | import android.support.v4.view.GravityCompat; 8 | import android.support.v4.widget.DrawerLayout; 9 | import android.support.v7.app.ActionBarDrawerToggle; 10 | import android.support.v7.app.AppCompatActivity; 11 | import android.support.v7.widget.Toolbar; 12 | import android.view.MenuItem; 13 | 14 | import com.github.fab.sample.R; 15 | 16 | public class MainActivity extends AppCompatActivity { 17 | 18 | private DrawerLayout mDrawerLayout; 19 | 20 | @Override 21 | protected void onCreate(Bundle savedInstanceState) { 22 | super.onCreate(savedInstanceState); 23 | setContentView(R.layout.main_activity); 24 | 25 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 26 | setSupportActionBar(toolbar); 27 | 28 | mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); 29 | ActionBarDrawerToggle toggle = new ActionBarDrawerToggle( 30 | this, mDrawerLayout, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close); 31 | mDrawerLayout.addDrawerListener(toggle); 32 | toggle.syncState(); 33 | 34 | NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view); 35 | navigationView.setNavigationItemSelectedListener(navigationItemSelectedListener); 36 | 37 | if (savedInstanceState == null) { 38 | getSupportFragmentManager().beginTransaction() 39 | .add(R.id.fragment, new HomeFragment()).commit(); 40 | } 41 | 42 | navigationView.setCheckedItem(R.id.home); 43 | } 44 | 45 | NavigationView.OnNavigationItemSelectedListener navigationItemSelectedListener = new NavigationView.OnNavigationItemSelectedListener() { 46 | @Override 47 | public boolean onNavigationItemSelected(MenuItem item) { 48 | mDrawerLayout.closeDrawer(GravityCompat.START); 49 | 50 | Fragment fragment = null; 51 | final FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); 52 | switch (item.getItemId()) { 53 | case R.id.home: 54 | fragment = new HomeFragment(); 55 | break; 56 | case R.id.menus: 57 | fragment = new MenusFragment(); 58 | break; 59 | case R.id.progress: 60 | fragment = new ProgressFragment(); 61 | break; 62 | } 63 | 64 | ft.replace(R.id.fragment, fragment).commit(); 65 | return true; 66 | } 67 | }; 68 | 69 | @Override 70 | public void onBackPressed() { 71 | if (mDrawerLayout != null && mDrawerLayout.isDrawerOpen(GravityCompat.START)) { 72 | mDrawerLayout.closeDrawer(GravityCompat.START); 73 | } else { 74 | super.onBackPressed(); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /sample/src/main/java/com/github/clans/fab/sample/MenusFragment.java: -------------------------------------------------------------------------------- 1 | package com.github.clans.fab.sample; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.animation.AnimatorSet; 6 | import android.animation.ObjectAnimator; 7 | import android.os.Bundle; 8 | import android.os.Handler; 9 | import android.support.annotation.Nullable; 10 | import android.support.v4.app.Fragment; 11 | import android.support.v4.content.ContextCompat; 12 | import android.view.ContextThemeWrapper; 13 | import android.view.LayoutInflater; 14 | import android.view.View; 15 | import android.view.ViewGroup; 16 | import android.view.animation.AnimationUtils; 17 | import android.view.animation.OvershootInterpolator; 18 | import android.widget.Toast; 19 | 20 | import com.github.clans.fab.FloatingActionButton; 21 | import com.github.clans.fab.FloatingActionMenu; 22 | import com.github.fab.sample.R; 23 | 24 | import java.util.ArrayList; 25 | import java.util.List; 26 | 27 | public class MenusFragment extends Fragment { 28 | 29 | private FloatingActionMenu menuRed; 30 | private FloatingActionMenu menuYellow; 31 | private FloatingActionMenu menuGreen; 32 | private FloatingActionMenu menuBlue; 33 | private FloatingActionMenu menuDown; 34 | private FloatingActionMenu menuLabelsRight; 35 | 36 | private FloatingActionButton fab1; 37 | private FloatingActionButton fab2; 38 | private FloatingActionButton fab3; 39 | 40 | private FloatingActionButton fabEdit; 41 | 42 | private List menus = new ArrayList<>(); 43 | private Handler mUiHandler = new Handler(); 44 | 45 | @Nullable 46 | @Override 47 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 48 | return inflater.inflate(R.layout.menus_fragment, container, false); 49 | } 50 | 51 | @Override 52 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 53 | super.onViewCreated(view, savedInstanceState); 54 | 55 | menuRed = (FloatingActionMenu) view.findViewById(R.id.menu_red); 56 | menuYellow = (FloatingActionMenu) view.findViewById(R.id.menu_yellow); 57 | menuGreen = (FloatingActionMenu) view.findViewById(R.id.menu_green); 58 | menuBlue = (FloatingActionMenu) view.findViewById(R.id.menu_blue); 59 | menuDown = (FloatingActionMenu) view.findViewById(R.id.menu_down); 60 | menuLabelsRight = (FloatingActionMenu) view.findViewById(R.id.menu_labels_right); 61 | 62 | fab1 = (FloatingActionButton) view.findViewById(R.id.fab1); 63 | fab2 = (FloatingActionButton) view.findViewById(R.id.fab2); 64 | fab3 = (FloatingActionButton) view.findViewById(R.id.fab3); 65 | 66 | final FloatingActionButton programFab1 = new FloatingActionButton(getActivity()); 67 | programFab1.setButtonSize(FloatingActionButton.SIZE_MINI); 68 | programFab1.setLabelText(getString(R.string.lorem_ipsum)); 69 | programFab1.setImageResource(R.drawable.ic_edit); 70 | menuRed.addMenuButton(programFab1); 71 | programFab1.setOnClickListener(new View.OnClickListener() { 72 | @Override 73 | public void onClick(View v) { 74 | programFab1.setLabelColors(ContextCompat.getColor(getActivity(), R.color.grey), 75 | ContextCompat.getColor(getActivity(), R.color.light_grey), 76 | ContextCompat.getColor(getActivity(), R.color.white_transparent)); 77 | programFab1.setLabelTextColor(ContextCompat.getColor(getActivity(), R.color.black)); 78 | } 79 | }); 80 | 81 | ContextThemeWrapper context = new ContextThemeWrapper(getActivity(), R.style.MenuButtonsStyle); 82 | FloatingActionButton programFab2 = new FloatingActionButton(context); 83 | programFab2.setLabelText("Programmatically added button"); 84 | programFab2.setImageResource(R.drawable.ic_edit); 85 | menuYellow.addMenuButton(programFab2); 86 | 87 | fab1.setEnabled(false); 88 | menuRed.setClosedOnTouchOutside(true); 89 | menuBlue.setIconAnimated(false); 90 | 91 | menuDown.hideMenuButton(false); 92 | menuRed.hideMenuButton(false); 93 | menuYellow.hideMenuButton(false); 94 | menuGreen.hideMenuButton(false); 95 | menuBlue.hideMenuButton(false); 96 | menuLabelsRight.hideMenuButton(false); 97 | 98 | fabEdit = (FloatingActionButton) view.findViewById(R.id.fab_edit); 99 | fabEdit.setShowAnimation(AnimationUtils.loadAnimation(getActivity(), R.anim.scale_up)); 100 | fabEdit.setHideAnimation(AnimationUtils.loadAnimation(getActivity(), R.anim.scale_down)); 101 | } 102 | 103 | @Override 104 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 105 | super.onActivityCreated(savedInstanceState); 106 | 107 | menus.add(menuDown); 108 | menus.add(menuRed); 109 | menus.add(menuYellow); 110 | menus.add(menuGreen); 111 | menus.add(menuBlue); 112 | menus.add(menuLabelsRight); 113 | 114 | menuYellow.setOnMenuToggleListener(new FloatingActionMenu.OnMenuToggleListener() { 115 | @Override 116 | public void onMenuToggle(boolean opened) { 117 | String text; 118 | if (opened) { 119 | text = "Menu opened"; 120 | } else { 121 | text = "Menu closed"; 122 | } 123 | Toast.makeText(getActivity(), text, Toast.LENGTH_SHORT).show(); 124 | } 125 | }); 126 | 127 | fab1.setOnClickListener(clickListener); 128 | fab2.setOnClickListener(clickListener); 129 | fab3.setOnClickListener(clickListener); 130 | 131 | int delay = 400; 132 | for (final FloatingActionMenu menu : menus) { 133 | mUiHandler.postDelayed(new Runnable() { 134 | @Override 135 | public void run() { 136 | menu.showMenuButton(true); 137 | } 138 | }, delay); 139 | delay += 150; 140 | } 141 | 142 | new Handler().postDelayed(new Runnable() { 143 | @Override 144 | public void run() { 145 | fabEdit.show(true); 146 | } 147 | }, delay + 150); 148 | 149 | menuRed.setOnMenuButtonClickListener(new View.OnClickListener() { 150 | @Override 151 | public void onClick(View v) { 152 | if (menuRed.isOpened()) { 153 | Toast.makeText(getActivity(), menuRed.getMenuButtonLabelText(), Toast.LENGTH_SHORT).show(); 154 | } 155 | 156 | menuRed.toggle(true); 157 | } 158 | }); 159 | 160 | createCustomAnimation(); 161 | } 162 | 163 | private void createCustomAnimation() { 164 | AnimatorSet set = new AnimatorSet(); 165 | 166 | ObjectAnimator scaleOutX = ObjectAnimator.ofFloat(menuGreen.getMenuIconView(), "scaleX", 1.0f, 0.2f); 167 | ObjectAnimator scaleOutY = ObjectAnimator.ofFloat(menuGreen.getMenuIconView(), "scaleY", 1.0f, 0.2f); 168 | 169 | ObjectAnimator scaleInX = ObjectAnimator.ofFloat(menuGreen.getMenuIconView(), "scaleX", 0.2f, 1.0f); 170 | ObjectAnimator scaleInY = ObjectAnimator.ofFloat(menuGreen.getMenuIconView(), "scaleY", 0.2f, 1.0f); 171 | 172 | scaleOutX.setDuration(50); 173 | scaleOutY.setDuration(50); 174 | 175 | scaleInX.setDuration(150); 176 | scaleInY.setDuration(150); 177 | 178 | scaleInX.addListener(new AnimatorListenerAdapter() { 179 | @Override 180 | public void onAnimationStart(Animator animation) { 181 | menuGreen.getMenuIconView().setImageResource(menuGreen.isOpened() 182 | ? R.drawable.ic_close : R.drawable.ic_star); 183 | } 184 | }); 185 | 186 | set.play(scaleOutX).with(scaleOutY); 187 | set.play(scaleInX).with(scaleInY).after(scaleOutX); 188 | set.setInterpolator(new OvershootInterpolator(2)); 189 | 190 | menuGreen.setIconToggleAnimatorSet(set); 191 | } 192 | 193 | private View.OnClickListener clickListener = new View.OnClickListener() { 194 | @Override 195 | public void onClick(View v) { 196 | switch (v.getId()) { 197 | case R.id.fab1: 198 | break; 199 | case R.id.fab2: 200 | fab2.setVisibility(View.GONE); 201 | break; 202 | case R.id.fab3: 203 | fab2.setVisibility(View.VISIBLE); 204 | break; 205 | } 206 | } 207 | }; 208 | } 209 | -------------------------------------------------------------------------------- /sample/src/main/java/com/github/clans/fab/sample/ProgressFragment.java: -------------------------------------------------------------------------------- 1 | package com.github.clans.fab.sample; 2 | 3 | import android.os.Bundle; 4 | import android.os.Handler; 5 | import android.support.annotation.Nullable; 6 | import android.support.v4.app.Fragment; 7 | import android.support.v7.widget.LinearLayoutManager; 8 | import android.support.v7.widget.RecyclerView; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | import android.widget.TextView; 13 | 14 | import com.github.clans.fab.FloatingActionButton; 15 | import com.github.fab.sample.R; 16 | 17 | import java.util.LinkedList; 18 | import java.util.Locale; 19 | 20 | public class ProgressFragment extends Fragment { 21 | 22 | private int mScrollOffset = 4; 23 | private int mMaxProgress = 100; 24 | private LinkedList mProgressTypes; 25 | private Handler mUiHandler = new Handler(); 26 | 27 | @Nullable 28 | @Override 29 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 30 | return inflater.inflate(R.layout.progress_fragment, container, false); 31 | } 32 | 33 | @Override 34 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 35 | super.onViewCreated(view, savedInstanceState); 36 | 37 | Locale[] availableLocales = Locale.getAvailableLocales(); 38 | mProgressTypes = new LinkedList<>(); 39 | for (ProgressType type : ProgressType.values()) { 40 | mProgressTypes.offer(type); 41 | } 42 | 43 | final FloatingActionButton fab = (FloatingActionButton) view.findViewById(R.id.fab); 44 | fab.setMax(mMaxProgress); 45 | 46 | RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.my_recycler_view); 47 | recyclerView.setHasFixedSize(true); 48 | recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); 49 | recyclerView.setAdapter(new LanguageAdapter(availableLocales)); 50 | fab.setOnClickListener(new View.OnClickListener() { 51 | @Override 52 | public void onClick(View v) { 53 | ProgressType type = mProgressTypes.poll(); 54 | switch (type) { 55 | case INDETERMINATE: 56 | fab.setShowProgressBackground(true); 57 | fab.setIndeterminate(true); 58 | mProgressTypes.offer(ProgressType.INDETERMINATE); 59 | break; 60 | case PROGRESS_POSITIVE: 61 | fab.setIndeterminate(false); 62 | fab.setProgress(70, true); 63 | mProgressTypes.offer(ProgressType.PROGRESS_POSITIVE); 64 | break; 65 | case PROGRESS_NEGATIVE: 66 | fab.setProgress(30, true); 67 | mProgressTypes.offer(ProgressType.PROGRESS_NEGATIVE); 68 | break; 69 | case HIDDEN: 70 | fab.hideProgress(); 71 | mProgressTypes.offer(ProgressType.HIDDEN); 72 | break; 73 | case PROGRESS_NO_ANIMATION: 74 | increaseProgress(fab, 0); 75 | break; 76 | case PROGRESS_NO_BACKGROUND: 77 | fab.setShowProgressBackground(false); 78 | fab.setIndeterminate(true); 79 | mProgressTypes.offer(ProgressType.PROGRESS_NO_BACKGROUND); 80 | break; 81 | } 82 | } 83 | }); 84 | 85 | recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { 86 | @Override 87 | public void onScrolled(RecyclerView recyclerView, int dx, int dy) { 88 | super.onScrolled(recyclerView, dx, dy); 89 | if (Math.abs(dy) > mScrollOffset) { 90 | if (dy > 0) { 91 | fab.hide(true); 92 | } else { 93 | fab.show(true); 94 | } 95 | } 96 | } 97 | }); 98 | } 99 | 100 | private void increaseProgress(final FloatingActionButton fab, int i) { 101 | if (i <= mMaxProgress) { 102 | fab.setProgress(i, false); 103 | final int progress = ++i; 104 | mUiHandler.postDelayed(new Runnable() { 105 | @Override 106 | public void run() { 107 | increaseProgress(fab, progress); 108 | } 109 | }, 30); 110 | } else { 111 | mUiHandler.postDelayed(new Runnable() { 112 | @Override 113 | public void run() { 114 | fab.hideProgress(); 115 | } 116 | }, 200); 117 | mProgressTypes.offer(ProgressType.PROGRESS_NO_ANIMATION); 118 | } 119 | } 120 | 121 | private class LanguageAdapter extends RecyclerView.Adapter { 122 | 123 | private Locale[] mLocales; 124 | 125 | private LanguageAdapter(Locale[] mLocales) { 126 | this.mLocales = mLocales; 127 | } 128 | 129 | @Override 130 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 131 | TextView tv = (TextView) LayoutInflater.from(parent.getContext()) 132 | .inflate(android.R.layout.simple_list_item_1, parent, false); 133 | 134 | return new ViewHolder(tv); 135 | } 136 | 137 | @Override 138 | public void onBindViewHolder(ViewHolder holder, int position) { 139 | holder.mTextView.setText(mLocales[position].getDisplayName()); 140 | } 141 | 142 | @Override 143 | public int getItemCount() { 144 | return mLocales.length; 145 | } 146 | } 147 | 148 | private static class ViewHolder extends RecyclerView.ViewHolder { 149 | 150 | public TextView mTextView; 151 | 152 | public ViewHolder(TextView v) { 153 | super(v); 154 | mTextView = v; 155 | } 156 | } 157 | 158 | private enum ProgressType { 159 | INDETERMINATE, PROGRESS_POSITIVE, PROGRESS_NEGATIVE, HIDDEN, PROGRESS_NO_ANIMATION, PROGRESS_NO_BACKGROUND 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /sample/src/main/res/anim/hide_to_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /sample/src/main/res/anim/jump_from_down.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | 12 | -------------------------------------------------------------------------------- /sample/src/main/res/anim/jump_to_down.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /sample/src/main/res/anim/scale_down.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /sample/src/main/res/anim/scale_up.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /sample/src/main/res/anim/show_from_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/ic_close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-hdpi/ic_close.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/ic_edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-hdpi/ic_edit.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/ic_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-hdpi/ic_menu.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/ic_nav_item.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-hdpi/ic_nav_item.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/ic_progress.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-hdpi/ic_progress.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/ic_star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-hdpi/ic_star.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-mdpi/ic_close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-mdpi/ic_close.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-mdpi/ic_edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-mdpi/ic_edit.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-mdpi/ic_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-mdpi/ic_menu.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-mdpi/ic_nav_item.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-mdpi/ic_nav_item.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-mdpi/ic_progress.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-mdpi/ic_progress.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-mdpi/ic_star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-mdpi/ic_star.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-v21/fab_label_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/ic_close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-xhdpi/ic_close.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/ic_edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-xhdpi/ic_edit.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/ic_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-xhdpi/ic_menu.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/ic_nav_item.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-xhdpi/ic_nav_item.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/ic_progress.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-xhdpi/ic_progress.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/ic_star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-xhdpi/ic_star.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/ic_close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-xxhdpi/ic_close.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/ic_edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-xxhdpi/ic_edit.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/ic_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-xxhdpi/ic_menu.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/ic_nav_item.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-xxhdpi/ic_nav_item.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/ic_progress.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-xxhdpi/ic_progress.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/ic_star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-xxhdpi/ic_star.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/ic_nav_item.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-xxxhdpi/ic_nav_item.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/ic_progress.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/drawable-xxxhdpi/ic_progress.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable/fab_label_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/drawer_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 16 | 17 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/home_fragment.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 21 | 22 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/main_activity.xml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 14 | 15 | 16 | 17 | 21 | 22 | 23 | 24 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/menus_fragment.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 24 | 25 | 32 | 33 | 51 | 52 | 59 | 60 | 67 | 68 | 75 | 76 | 77 | 78 | 95 | 96 | 102 | 103 | 109 | 110 | 116 | 117 | 118 | 119 | 133 | 134 | 140 | 141 | 147 | 148 | 154 | 155 | 156 | 157 | 172 | 173 | 188 | 189 | 195 | 196 | 202 | 203 | 209 | 210 | 211 | 212 | 225 | 226 | 233 | 234 | 241 | 242 | 249 | 250 | 251 | 252 | 264 | 265 | 271 | 272 | 278 | 279 | 285 | 286 | 287 | 288 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/progress_fragment.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 23 | 24 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/toolbar.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /sample/src/main/res/menu/main_menu.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /sample/src/main/res/menu/menu_drawer.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 11 | 15 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /sample/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /sample/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #1976D2 5 | #1565C0 6 | #DDDDDD 7 | #EEEEEE 8 | #99FFFFFF 9 | #000 10 | 11 | -------------------------------------------------------------------------------- /sample/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /sample/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Floating Action Button 3 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur magna tortor, semper eu feugiat eu, vehicula vitae lacus. 4 | RecyclerView FAB example 5 | Floating Action Menu 6 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis sed ultricies risus. Integer nisi orci, lacinia sit amet mi in, mollis scelerisque purus. Nunc risus ligula, maximus eu orci a, facilisis dictum velit. Proin nec laoreet magna. Nulla ut sagittis lorem. Morbi id enim fermentum, semper diam et, tempus leo. Aliquam vel congue orci. Suspendisse potenti. Curabitur finibus diam augue, vel bibendum sapien ultrices non. 7 | Navigation open 8 | Navigation close 9 | 10 | -------------------------------------------------------------------------------- /sample/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 12 | 13 | 16 | 17 | 24 | 25 | 34 | 35 | 41 | 42 | 47 | 48 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /screenshots/main_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/screenshots/main_screen.png -------------------------------------------------------------------------------- /screenshots/menu_closed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/screenshots/menu_closed.png -------------------------------------------------------------------------------- /screenshots/menu_custom_opened.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/screenshots/menu_custom_opened.png -------------------------------------------------------------------------------- /screenshots/menu_default_opened.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/screenshots/menu_default_opened.png -------------------------------------------------------------------------------- /screenshots/menu_down_opened.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/screenshots/menu_down_opened.png -------------------------------------------------------------------------------- /screenshots/menu_mini_opened.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/screenshots/menu_mini_opened.png -------------------------------------------------------------------------------- /screenshots/menu_right_opened.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/screenshots/menu_right_opened.png -------------------------------------------------------------------------------- /screenshots/progress_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/screenshots/progress_background.png -------------------------------------------------------------------------------- /screenshots/progress_no_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clans/FloatingActionButton/d3feaadabb8e4780e8bca330f4f67b94f21e1796/screenshots/progress_no_background.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':sample', ':library' 2 | --------------------------------------------------------------------------------