├── settings.gradle ├── example.png ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── samples ├── src │ ├── main │ │ ├── res │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── drawable │ │ │ │ ├── background.xml │ │ │ │ ├── ic_chat_bubble_outline_black_24px.xml │ │ │ │ ├── ic_chat_bubble_outline_white_24px.xml │ │ │ │ ├── ic_mail_outline_black_24px.xml │ │ │ │ ├── ic_mail_outline_white_24px.xml │ │ │ │ ├── ic_call_black_24px.xml │ │ │ │ └── ic_call_white_24px.xml │ │ │ ├── values │ │ │ │ ├── integer_array.xml │ │ │ │ ├── dimens.xml │ │ │ │ ├── colors.xml │ │ │ │ ├── styles.xml │ │ │ │ └── strings.xml │ │ │ ├── values-v21 │ │ │ │ ├── dimens.xml │ │ │ │ └── styles.xml │ │ │ ├── layout │ │ │ │ ├── simple_list_item_2.xml │ │ │ │ ├── activity_events_sample.xml │ │ │ │ ├── activity_home.xml │ │ │ │ ├── activity_group_style_sample.xml │ │ │ │ ├── activity_individual_style_sample.xml │ │ │ │ ├── activity_position_sample.xml │ │ │ │ └── activity_menu_items_sample.xml │ │ │ ├── menu │ │ │ │ └── menu_main.xml │ │ │ └── menu-v21 │ │ │ │ └── menu_main.xml │ │ ├── java │ │ │ └── io │ │ │ │ └── github │ │ │ │ └── yavski │ │ │ │ └── fabspeeddial │ │ │ │ └── samples │ │ │ │ ├── PositionSampleActivity.java │ │ │ │ ├── GroupStyleSampleActivity.java │ │ │ │ ├── BaseSampleActivity.java │ │ │ │ ├── IndividualStyleSampleActivity.java │ │ │ │ ├── EventsSampleActivity.java │ │ │ │ ├── MenuItemsSampleActivity.java │ │ │ │ └── HomeActivity.java │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── io │ │ │ └── yavski │ │ │ └── fabmenu │ │ │ └── samples │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── io │ │ └── yavski │ │ └── fabmenu │ │ └── samples │ │ └── ApplicationTest.java ├── .gitignore ├── proguard-rules.pro └── build.gradle ├── library ├── src │ ├── main │ │ ├── res │ │ │ ├── drawable-xxxhdpi │ │ │ │ ├── ic_add_black_48dp_00.png │ │ │ │ ├── ic_add_black_48dp_01.png │ │ │ │ ├── ic_add_black_48dp_02.png │ │ │ │ ├── ic_add_black_48dp_03.png │ │ │ │ ├── ic_add_black_48dp_04.png │ │ │ │ ├── ic_add_black_48dp_05.png │ │ │ │ ├── ic_add_black_48dp_06.png │ │ │ │ ├── ic_add_black_48dp_07.png │ │ │ │ ├── ic_add_black_48dp_08.png │ │ │ │ └── ic_add_black_48dp_09.png │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── colors.xml │ │ │ │ ├── dimens.xml │ │ │ │ ├── attrs.xml │ │ │ │ └── styles.xml │ │ │ ├── drawable │ │ │ │ └── fab_add_clear_selector.xml │ │ │ ├── values-v21 │ │ │ │ └── dimens.xml │ │ │ ├── layout │ │ │ │ ├── fab_speed_dial_bottom.xml │ │ │ │ ├── fab_speed_dial_top.xml │ │ │ │ ├── fab_menu_item_end.xml │ │ │ │ └── fab_menu_item_start.xml │ │ │ └── drawable-v21 │ │ │ │ └── fab_add_clear_selector.xml │ │ ├── java │ │ │ └── io │ │ │ │ └── github │ │ │ │ └── yavski │ │ │ │ └── fabspeeddial │ │ │ │ ├── SimpleMenuListenerAdapter.java │ │ │ │ ├── ViewGroupUtilsHoneycomb.java │ │ │ │ ├── ViewGroupUtils.java │ │ │ │ ├── FabSpeedDialBehaviour.java │ │ │ │ └── FabSpeedDial.java │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── io │ │ │ └── yavski │ │ │ └── fabmenu │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── io │ │ └── yavski │ │ └── fabmenu │ │ └── ApplicationTest.java ├── .gitignore ├── proguard-rules.pro ├── build.gradle └── maven.gradle ├── .gitignore ├── gradle.properties ├── gradlew.bat ├── gradlew ├── README.md └── LICENSE /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':samples', ':library' 2 | -------------------------------------------------------------------------------- /example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yavski/fab-speed-dial/HEAD/example.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yavski/fab-speed-dial/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /samples/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yavski/fab-speed-dial/HEAD/samples/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yavski/fab-speed-dial/HEAD/samples/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yavski/fab-speed-dial/HEAD/samples/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yavski/fab-speed-dial/HEAD/samples/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yavski/fab-speed-dial/HEAD/samples/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-xxxhdpi/ic_add_black_48dp_00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yavski/fab-speed-dial/HEAD/library/src/main/res/drawable-xxxhdpi/ic_add_black_48dp_00.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-xxxhdpi/ic_add_black_48dp_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yavski/fab-speed-dial/HEAD/library/src/main/res/drawable-xxxhdpi/ic_add_black_48dp_01.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-xxxhdpi/ic_add_black_48dp_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yavski/fab-speed-dial/HEAD/library/src/main/res/drawable-xxxhdpi/ic_add_black_48dp_02.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-xxxhdpi/ic_add_black_48dp_03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yavski/fab-speed-dial/HEAD/library/src/main/res/drawable-xxxhdpi/ic_add_black_48dp_03.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-xxxhdpi/ic_add_black_48dp_04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yavski/fab-speed-dial/HEAD/library/src/main/res/drawable-xxxhdpi/ic_add_black_48dp_04.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-xxxhdpi/ic_add_black_48dp_05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yavski/fab-speed-dial/HEAD/library/src/main/res/drawable-xxxhdpi/ic_add_black_48dp_05.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-xxxhdpi/ic_add_black_48dp_06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yavski/fab-speed-dial/HEAD/library/src/main/res/drawable-xxxhdpi/ic_add_black_48dp_06.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-xxxhdpi/ic_add_black_48dp_07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yavski/fab-speed-dial/HEAD/library/src/main/res/drawable-xxxhdpi/ic_add_black_48dp_07.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-xxxhdpi/ic_add_black_48dp_08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yavski/fab-speed-dial/HEAD/library/src/main/res/drawable-xxxhdpi/ic_add_black_48dp_08.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-xxxhdpi/ic_add_black_48dp_09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yavski/fab-speed-dial/HEAD/library/src/main/res/drawable-xxxhdpi/ic_add_black_48dp_09.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .classpath 2 | .project 3 | .settings 4 | eclipsebin 5 | 6 | bin 7 | gen 8 | build 9 | out 10 | lib 11 | 12 | target 13 | pom.xml.* 14 | release.properties 15 | 16 | .idea 17 | *.iml 18 | classes 19 | 20 | obj 21 | 22 | .DS_Store -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | .classpath 2 | .project 3 | .settings 4 | eclipsebin 5 | 6 | bin 7 | gen 8 | build 9 | out 10 | lib 11 | 12 | target 13 | pom.xml.* 14 | release.properties 15 | 16 | .idea 17 | *.iml 18 | classes 19 | 20 | obj 21 | 22 | .DS_Store -------------------------------------------------------------------------------- /samples/.gitignore: -------------------------------------------------------------------------------- 1 | .classpath 2 | .project 3 | .settings 4 | eclipsebin 5 | 6 | bin 7 | gen 8 | build 9 | out 10 | lib 11 | 12 | target 13 | pom.xml.* 14 | release.properties 15 | 16 | .idea 17 | *.iml 18 | classes 19 | 20 | obj 21 | 22 | .DS_Store -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Oct 16 13:07:40 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.14.1-all.zip 7 | -------------------------------------------------------------------------------- /samples/src/main/res/drawable/background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /samples/src/main/res/values/integer_array.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | @color/holo_red_dark 5 | @color/holo_purple 6 | @color/holo_green_light 7 | 8 | -------------------------------------------------------------------------------- /library/src/test/java/io/yavski/fabmenu/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package io.yavski.fabmenu; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * To work on unit tests, switch the Test Artifact in the Build Variants view. 9 | */ 10 | public class ExampleUnitTest { 11 | @Test 12 | public void addition_isCorrect() throws Exception { 13 | assertEquals(4, 2 + 2); 14 | } 15 | } -------------------------------------------------------------------------------- /samples/src/test/java/io/yavski/fabmenu/samples/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package io.yavski.fabmenu.samples; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * To work on unit tests, switch the Test Artifact in the Build Variants view. 9 | */ 10 | public class ExampleUnitTest { 11 | @Test 12 | public void addition_isCorrect() throws Exception { 13 | assertEquals(4, 2 + 2); 14 | } 15 | } -------------------------------------------------------------------------------- /library/src/androidTest/java/io/yavski/fabmenu/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package io.yavski.fabmenu; 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 | } -------------------------------------------------------------------------------- /samples/src/main/res/drawable/ic_chat_bubble_outline_black_24px.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /samples/src/main/res/drawable/ic_chat_bubble_outline_white_24px.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /samples/src/main/res/drawable/ic_mail_outline_black_24px.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /samples/src/main/res/drawable/ic_mail_outline_white_24px.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /samples/src/main/res/drawable/ic_call_black_24px.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /samples/src/main/res/drawable/ic_call_white_24px.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /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 /Users/yavorivanov/Dev/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 | -------------------------------------------------------------------------------- /samples/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/yavorivanov/Dev/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/java/io/github/yavski/fabspeeddial/SimpleMenuListenerAdapter.java: -------------------------------------------------------------------------------- 1 | package io.github.yavski.fabspeeddial; 2 | 3 | import android.support.design.internal.NavigationMenu; 4 | import android.view.MenuItem; 5 | 6 | /** 7 | * This adapter class provides empty implementations of the methods from 8 | * {@link FabSpeedDial.MenuListener}. 9 | * Created by yavorivanov on 03/01/2016. 10 | */ 11 | public class SimpleMenuListenerAdapter implements FabSpeedDial.MenuListener { 12 | 13 | @Override 14 | public boolean onPrepareMenu(NavigationMenu navigationMenu) { 15 | 16 | return true; 17 | } 18 | 19 | @Override 20 | public boolean onMenuItemSelected(MenuItem menuItem) { 21 | return false; 22 | } 23 | 24 | @Override 25 | public void onMenuClosed() { 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /library/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | FabMenu 20 | 21 | -------------------------------------------------------------------------------- /samples/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.3" 6 | 7 | defaultConfig { 8 | applicationId "io.yavski.fabmenu.samples" 9 | minSdkVersion 8 10 | targetSdkVersion 23 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | testCompile 'junit:junit:4.12' 25 | compile 'com.android.support:appcompat-v7:23.3.0' 26 | compile 'com.android.support:design:23.3.0' 27 | compile project(path: ':library') 28 | } 29 | -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /samples/src/main/res/values-v21/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 16dp 21 | 16dp 22 | 16dp 23 | -------------------------------------------------------------------------------- /samples/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 16dp 21 | 16dp 22 | 12dp 23 | -------------------------------------------------------------------------------- /library/src/main/res/drawable/fab_add_clear_selector.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'bintray-release' 3 | 4 | android { 5 | compileSdkVersion 23 6 | buildToolsVersion "23.0.3" 7 | 8 | defaultConfig { 9 | minSdkVersion 8 10 | targetSdkVersion 23 11 | versionCode 8 12 | versionName "1.0.7" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | testCompile 'junit:junit:4.12' 25 | compile 'com.android.support:support-v4:23.3.0' 26 | compile 'com.android.support:appcompat-v7:23.3.0' 27 | compile 'com.android.support:design:23.3.0' 28 | compile 'com.android.support:cardview-v7:23.3.0' 29 | } 30 | 31 | publish { 32 | userOrg = 'yavski' 33 | groupId = 'io.github.yavski' 34 | artifactId = 'fab-speed-dial' 35 | publishVersion = '1.0.7' 36 | } 37 | 38 | // apply from: './maven.gradle' 39 | -------------------------------------------------------------------------------- /samples/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 25 | 26 | -------------------------------------------------------------------------------- /library/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | #FFEEEEEE 20 | @android:color/white 21 | @android:color/black 22 | @android:color/black 23 | @android:color/white 24 | -------------------------------------------------------------------------------- /samples/src/androidTest/java/io/yavski/fabmenu/samples/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Yavor Ivanov 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 | package io.yavski.fabmenu.samples; 18 | 19 | import android.app.Application; 20 | import android.test.ApplicationTestCase; 21 | 22 | /** 23 | * Testing Fundamentals 24 | */ 25 | public class ApplicationTest extends ApplicationTestCase { 26 | public ApplicationTest() { 27 | super(Application.class); 28 | } 29 | } -------------------------------------------------------------------------------- /library/maven.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'maven-publish' 2 | 3 | group = 'io.github.yavski' 4 | version = android.defaultConfig.versionName 5 | 6 | publishing { 7 | publications { 8 | maven(MavenPublication){ 9 | groupId project.group 10 | artifactId rootProject.name 11 | version project.version 12 | artifact("${buildDir}/outputs/aar/${project.name}-release.aar") 13 | 14 | pom.withXml { 15 | def dependenciesNode = asNode().appendNode('dependencies') 16 | 17 | //Iterate over the compile dependencies (we don't want the test ones), adding a node for each 18 | configurations.compile.allDependencies.each { 19 | if (it.name != 'unspecified') { 20 | def dependencyNode = dependenciesNode.appendNode('dependency') 21 | dependencyNode.appendNode('groupId', it.group) 22 | dependencyNode.appendNode('artifactId', it.name) 23 | dependencyNode.appendNode('version', it.version) 24 | } 25 | } 26 | } 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /library/src/main/res/values-v21/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 0dp 20 | @dimen/keyline_1 21 | 22 | @dimen/keyline_1 23 | @dimen/keyline_1_minus_8dp 24 | @dimen/keyline_1_minus_8dp 25 | @dimen/keyline_1_minus_8dp 26 | @dimen/keyline_1_minus_8dp 27 | 28 | -------------------------------------------------------------------------------- /samples/src/main/java/io/github/yavski/fabspeeddial/samples/PositionSampleActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Yavor Ivanov 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 | package io.github.yavski.fabspeeddial.samples; 18 | 19 | import android.os.Bundle; 20 | import android.support.v7.widget.Toolbar; 21 | 22 | import io.github.yavski.fabmenu.samples.R; 23 | 24 | public class PositionSampleActivity extends BaseSampleActivity { 25 | 26 | @Override 27 | protected void onCreate(Bundle savedInstanceState) { 28 | super.onCreate(savedInstanceState); 29 | setContentView(R.layout.activity_position_sample); 30 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 31 | setSupportActionBar(toolbar); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /samples/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | #673AB7 19 | #512DA8 20 | #FF9800 21 | 22 | 23 | #ff99cc00 24 | 25 | #ffaa66cc 26 | 27 | #ffff8800 28 | 29 | #ffcc0000 30 | 31 | -------------------------------------------------------------------------------- /samples/src/main/java/io/github/yavski/fabspeeddial/samples/GroupStyleSampleActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Yavor Ivanov 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 | package io.github.yavski.fabspeeddial.samples; 18 | 19 | import android.os.Bundle; 20 | import android.support.v7.widget.Toolbar; 21 | 22 | import io.github.yavski.fabmenu.samples.R; 23 | 24 | public class GroupStyleSampleActivity extends BaseSampleActivity { 25 | 26 | @Override 27 | protected void onCreate(Bundle savedInstanceState) { 28 | super.onCreate(savedInstanceState); 29 | setContentView(R.layout.activity_group_style_sample); 30 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 31 | setSupportActionBar(toolbar); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /samples/src/main/java/io/github/yavski/fabspeeddial/samples/BaseSampleActivity.java: -------------------------------------------------------------------------------- 1 | package io.github.yavski.fabspeeddial.samples; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.view.MenuItem; 7 | 8 | import io.github.yavski.fabmenu.samples.R; 9 | 10 | /** 11 | * Created by yavorivanov on 03/01/2016. 12 | */ 13 | public abstract class BaseSampleActivity extends AppCompatActivity { 14 | 15 | public static final String TITLE = "title"; 16 | 17 | private String mTitle; 18 | 19 | @Override 20 | protected void onCreate(@Nullable Bundle savedInstanceState) { 21 | super.onCreate(savedInstanceState); 22 | mTitle = getIntent().getStringExtra(TITLE); 23 | } 24 | 25 | @Override 26 | protected void onResume() { 27 | super.onResume(); 28 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 29 | getSupportActionBar().setTitle(getString(R.string.sample_activity_title, mTitle)); 30 | } 31 | 32 | @Override 33 | public boolean onOptionsItemSelected(MenuItem item) { 34 | if (item.getItemId() == android.R.id.home) { 35 | finish(); 36 | return true; 37 | } 38 | return super.onOptionsItemSelected(item); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /samples/src/main/java/io/github/yavski/fabspeeddial/samples/IndividualStyleSampleActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Yavor Ivanov 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 | package io.github.yavski.fabspeeddial.samples; 18 | 19 | import android.os.Bundle; 20 | import android.support.v7.widget.Toolbar; 21 | 22 | import io.github.yavski.fabmenu.samples.R; 23 | 24 | public class IndividualStyleSampleActivity extends BaseSampleActivity { 25 | 26 | @Override 27 | protected void onCreate(Bundle savedInstanceState) { 28 | super.onCreate(savedInstanceState); 29 | setContentView(R.layout.activity_individual_style_sample); 30 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 31 | setSupportActionBar(toolbar); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /library/src/main/res/layout/fab_speed_dial_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 20 | 21 | 26 | 27 | 32 | 33 | -------------------------------------------------------------------------------- /library/src/main/res/layout/fab_speed_dial_top.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 20 | 21 | 26 | 27 | 32 | 33 | -------------------------------------------------------------------------------- /samples/src/main/res/layout/simple_list_item_2.xml: -------------------------------------------------------------------------------- 1 | 15 | 16 | 22 | 23 | 28 | 29 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /library/src/main/res/layout/fab_menu_item_end.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 21 | 22 | 27 | 28 | 33 | 34 | 35 | 36 | 41 | 42 | -------------------------------------------------------------------------------- /library/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 16dp 20 | 8dp 21 | 22 | 23 | -16dp 24 | 26 | 12dp 27 | 28 | 0dp 29 | 30 | @dimen/keyline_1_minus_8dp 31 | 0dp 32 | @dimen/keyline_1_minus_8dp 33 | 0dp 34 | 35 | -------------------------------------------------------------------------------- /samples/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 21 | 27 | 28 | 34 | 35 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /samples/src/main/res/menu-v21/menu_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 22 | 28 | 29 | 35 | 36 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /library/src/main/res/layout/fab_menu_item_start.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 23 | 24 | 29 | 30 | 35 | 36 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /samples/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 25 | 26 | 30 | 31 | 38 | 39 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /samples/src/main/java/io/github/yavski/fabspeeddial/samples/EventsSampleActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Yavor Ivanov 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 | package io.github.yavski.fabspeeddial.samples; 18 | 19 | import android.os.Bundle; 20 | import android.support.design.widget.Snackbar; 21 | import android.support.v7.widget.Toolbar; 22 | import android.view.MenuItem; 23 | 24 | import io.github.yavski.fabmenu.samples.R; 25 | import io.github.yavski.fabspeeddial.FabSpeedDial; 26 | import io.github.yavski.fabspeeddial.SimpleMenuListenerAdapter; 27 | 28 | public class EventsSampleActivity extends BaseSampleActivity { 29 | 30 | @Override 31 | protected void onCreate(Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | setContentView(R.layout.activity_events_sample); 34 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 35 | setSupportActionBar(toolbar); 36 | 37 | FabSpeedDial fabSpeedDial = (FabSpeedDial) findViewById(R.id.fab_speed_dial); 38 | fabSpeedDial.setMenuListener(new SimpleMenuListenerAdapter() { 39 | @Override 40 | public boolean onMenuItemSelected(MenuItem menuItem) { 41 | Snackbar.make(findViewById(R.id.rootView), getString(R.string.selected_menu_item, 42 | menuItem.getTitle()), Snackbar.LENGTH_SHORT).show(); 43 | return false; 44 | } 45 | }); 46 | 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /samples/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 26 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /library/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 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 | -------------------------------------------------------------------------------- /samples/src/main/res/layout/activity_events_sample.xml: -------------------------------------------------------------------------------- 1 | 16 | 24 | 25 | 29 | 30 | 36 | 37 | 38 | 39 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /library/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 24 | 25 | 35 | 36 | 48 | 49 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /samples/src/main/res/layout/activity_home.xml: -------------------------------------------------------------------------------- 1 | 16 | 25 | 26 | 30 | 31 | 37 | 38 | 39 | 40 | 48 | 49 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /library/src/main/java/io/github/yavski/fabspeeddial/ViewGroupUtilsHoneycomb.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 The Android Open Source Project 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 | package io.github.yavski.fabspeeddial; 18 | 19 | import android.graphics.Matrix; 20 | import android.graphics.Rect; 21 | import android.graphics.RectF; 22 | import android.os.Build; 23 | import android.view.View; 24 | import android.view.ViewGroup; 25 | import android.view.ViewParent; 26 | 27 | class ViewGroupUtilsHoneycomb { 28 | private static final ThreadLocal sMatrix = new ThreadLocal<>(); 29 | private static final ThreadLocal sRectF = new ThreadLocal<>(); 30 | private static final Matrix IDENTITY = new Matrix(); 31 | 32 | public static void offsetDescendantRect(ViewGroup group, View child, Rect rect) { 33 | Matrix m = sMatrix.get(); 34 | if (m == null) { 35 | m = new Matrix(); 36 | sMatrix.set(m); 37 | } else { 38 | m.set(IDENTITY); 39 | } 40 | 41 | offsetDescendantMatrix(group, child, m); 42 | 43 | RectF rectF = sRectF.get(); 44 | if (rectF == null) { 45 | rectF = new RectF(); 46 | } 47 | rectF.set(rect); 48 | m.mapRect(rectF); 49 | rect.set((int) (rectF.left + 0.5f), (int) (rectF.top + 0.5f), 50 | (int) (rectF.right + 0.5f), (int) (rectF.bottom + 0.5f)); 51 | } 52 | 53 | static void offsetDescendantMatrix(ViewParent target, View view, Matrix m) { 54 | final ViewParent parent = view.getParent(); 55 | if (parent instanceof View && parent != target) { 56 | final View vp = (View) parent; 57 | offsetDescendantMatrix(target, vp, m); 58 | m.preTranslate(-vp.getScrollX(), -vp.getScrollY()); 59 | } 60 | 61 | m.preTranslate(view.getLeft(), view.getTop()); 62 | 63 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { 64 | if (!view.getMatrix().isIdentity()) { 65 | m.preConcat(view.getMatrix()); 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /samples/src/main/res/layout/activity_group_style_sample.xml: -------------------------------------------------------------------------------- 1 | 16 | 24 | 25 | 29 | 30 | 36 | 37 | 38 | 39 | 44 | 45 | 50 | 51 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /samples/src/main/res/layout/activity_individual_style_sample.xml: -------------------------------------------------------------------------------- 1 | 16 | 24 | 25 | 29 | 30 | 36 | 37 | 38 | 39 | 44 | 45 | 50 | 51 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /samples/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | FabSpeedDial Samples 19 | Call 20 | Text 21 | Email 22 | 23 | %1$s Sample 24 | Menu item: %1$s 25 | 26 | What is the name of your puppy? 27 | Visibility 28 | 29 | 30 | Position 31 | Style I 32 | Style II 33 | Click Callback 34 | Menu Items 35 | 36 | 37 | 38 | Set fabGravity in order to position the view in your view group 39 | Style up FAB and change the appearance of the menu items as a group 40 | Style up FAB and change the appearance of the menu items individually 41 | Register a callback to receive the click events of the menu items 42 | Update dynamically the title and visibility of your menu items 43 | 44 | 45 | Lorem ipsum dolor sit amet, utamur efficiantur no vim, mei iisque appetere electram ex. Cu eam tamquam commune intellegebat, id pri nibh vidisse. Has at dolore legendos, ei erant quaeque mnesarchum mei. Amet mediocritatem est et. Nam eu nisl dolor dolorum, ei omnium honestatis est. Nullam denique mei no, et vim brute scaevola sententiae, vero commune id cum.\n\nQuod rationibus has cu, eu sed dicta clita oblique, pro cu tibique consectetuer. Vix at everti vivendum scriptorem, ne eam utinam iracundia. Sumo affert regione ea sea, an integre utroque nam. Te duo alia dicam, inani reformidans sed no. Mel et tation nullam.\n\nTota periculis mea ut, cum ad meliore urbanitas. No per suas facer liberavisse. Eam ullum tritani instructior ea, at qui omnium noluisse, ei nec modus commune mediocrem. Quot admodum qui ex, ei tempor atomorum eam, et vel impetus alienum. Has eros nullam tritani eu, cum ea laoreet iudicabit dissentiet, ea est assum voluptatibus comprehensam. Sed graece volumus consulatu id, ei sonet urbanitas nam. 46 | 47 | 48 | -------------------------------------------------------------------------------- /library/src/main/java/io/github/yavski/fabspeeddial/ViewGroupUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 The Android Open Source Project 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 | package io.github.yavski.fabspeeddial; 18 | 19 | import android.graphics.Rect; 20 | import android.os.Build; 21 | import android.view.View; 22 | import android.view.ViewGroup; 23 | 24 | class ViewGroupUtils { 25 | 26 | private interface ViewGroupUtilsImpl { 27 | void offsetDescendantRect(ViewGroup parent, View child, Rect rect); 28 | } 29 | 30 | private static class ViewGroupUtilsImplBase implements ViewGroupUtilsImpl { 31 | @Override 32 | public void offsetDescendantRect(ViewGroup parent, View child, Rect rect) { 33 | parent.offsetDescendantRectToMyCoords(child, rect); 34 | } 35 | } 36 | 37 | private static class ViewGroupUtilsImplHoneycomb implements ViewGroupUtilsImpl { 38 | @Override 39 | public void offsetDescendantRect(ViewGroup parent, View child, Rect rect) { 40 | ViewGroupUtilsHoneycomb.offsetDescendantRect(parent, child, rect); 41 | } 42 | } 43 | 44 | private static final ViewGroupUtilsImpl IMPL; 45 | 46 | static { 47 | final int version = Build.VERSION.SDK_INT; 48 | if (version >= 11) { 49 | IMPL = new ViewGroupUtilsImplHoneycomb(); 50 | } else { 51 | IMPL = new ViewGroupUtilsImplBase(); 52 | } 53 | } 54 | 55 | /** 56 | * This is a port of the common 57 | * {@link ViewGroup#offsetDescendantRectToMyCoords(android.view.View, android.graphics.Rect)} 58 | * from the framework, but adapted to take transformations into account. The result 59 | * will be the bounding rect of the real transformed rect. 60 | * 61 | * @param descendant view defining the original coordinate system of rect 62 | * @param rect (in/out) the rect to offset from descendant to this view's coordinate system 63 | */ 64 | static void offsetDescendantRect(ViewGroup parent, View descendant, Rect rect) { 65 | IMPL.offsetDescendantRect(parent, descendant, rect); 66 | } 67 | 68 | /** 69 | * Retrieve the transformed bounding rect of an arbitrary descendant view. 70 | * This does not need to be a direct child. 71 | * 72 | * @param descendant descendant view to reference 73 | * @param out rect to set to the bounds of the descendant view 74 | */ 75 | static void getDescendantRect(ViewGroup parent, View descendant, Rect out) { 76 | out.set(0, 0, descendant.getWidth(), descendant.getHeight()); 77 | offsetDescendantRect(parent, descendant, out); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /library/src/main/res/drawable-v21/fab_add_clear_selector.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 23 | 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 | -------------------------------------------------------------------------------- /samples/src/main/java/io/github/yavski/fabspeeddial/samples/MenuItemsSampleActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Yavor Ivanov 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 | package io.github.yavski.fabspeeddial.samples; 18 | 19 | import android.os.Bundle; 20 | import android.support.design.internal.NavigationMenu; 21 | import android.support.design.widget.Snackbar; 22 | import android.support.v7.widget.SwitchCompat; 23 | import android.support.v7.widget.Toolbar; 24 | import android.text.TextUtils; 25 | import android.view.MenuItem; 26 | import android.widget.EditText; 27 | 28 | import io.github.yavski.fabmenu.samples.R; 29 | import io.github.yavski.fabspeeddial.FabSpeedDial; 30 | import io.github.yavski.fabspeeddial.SimpleMenuListenerAdapter; 31 | 32 | public class MenuItemsSampleActivity extends BaseSampleActivity { 33 | 34 | @Override 35 | protected void onCreate(Bundle savedInstanceState) { 36 | super.onCreate(savedInstanceState); 37 | setContentView(R.layout.activity_menu_items_sample); 38 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 39 | setSupportActionBar(toolbar); 40 | 41 | final EditText inputView = (EditText) findViewById(R.id.puppy_name); 42 | 43 | FabSpeedDial fabSpeedDial = ((FabSpeedDial) findViewById(R.id.fab_speed_dial)); 44 | fabSpeedDial.setMenuListener(new SimpleMenuListenerAdapter() { 45 | @Override 46 | public boolean onPrepareMenu(NavigationMenu navigationMenu) { 47 | String input = inputView.getText().toString(); 48 | 49 | if (TextUtils.isEmpty(input)) { 50 | Snackbar.make(findViewById(R.id.rootView), "Enter the name of your puppy", 51 | Snackbar.LENGTH_SHORT).show(); 52 | return false; 53 | } 54 | 55 | setMenuItemTitle(navigationMenu, R.id.action_call, input); 56 | setMenuItemTitle(navigationMenu, R.id.action_text, input); 57 | setMenuItemTitle(navigationMenu, R.id.action_email, input); 58 | 59 | setMenuItemVisibility(navigationMenu, R.id.action_call, R.id.menu_item_call_switch); 60 | setMenuItemVisibility(navigationMenu, R.id.action_text, R.id.menu_item_text_switch); 61 | setMenuItemVisibility(navigationMenu, R.id.action_email, R.id.menu_item_email_switch); 62 | 63 | return true; 64 | } 65 | }); 66 | 67 | } 68 | 69 | private void setMenuItemTitle(NavigationMenu menu, int menuItemId, CharSequence input) { 70 | MenuItem menuItem = menu.findItem(menuItemId); 71 | String oldMenuItemTitle = menuItem.getTitle().toString(); 72 | menuItem.setTitle(oldMenuItemTitle + " " + input); 73 | } 74 | 75 | private void setMenuItemVisibility(NavigationMenu menu, int menuItemId, int switchItem) { 76 | SwitchCompat switchView = (SwitchCompat) findViewById(switchItem); 77 | menu.findItem(menuItemId).setVisible(switchView.isChecked()); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /samples/src/main/java/io/github/yavski/fabspeeddial/samples/HomeActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Yavor Ivanov 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 | package io.github.yavski.fabspeeddial.samples; 18 | 19 | import android.content.Intent; 20 | import android.os.Bundle; 21 | import android.support.v7.app.AppCompatActivity; 22 | import android.support.v7.widget.Toolbar; 23 | import android.view.View; 24 | import android.view.ViewGroup; 25 | import android.widget.AdapterView; 26 | import android.widget.ArrayAdapter; 27 | import android.widget.ListView; 28 | import android.widget.TextView; 29 | 30 | import io.github.yavski.fabmenu.samples.R; 31 | 32 | public class HomeActivity extends AppCompatActivity implements AdapterView.OnItemClickListener { 33 | 34 | private String[] titles; 35 | 36 | @Override 37 | protected void onCreate(Bundle savedInstanceState) { 38 | super.onCreate(savedInstanceState); 39 | setContentView(R.layout.activity_home); 40 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 41 | setSupportActionBar(toolbar); 42 | 43 | titles = getResources().getStringArray(R.array.title_array); 44 | final String[] descriptions = getResources().getStringArray(R.array.description_array); 45 | 46 | ArrayAdapter adapter = new ArrayAdapter(this, 47 | R.layout.simple_list_item_2, 48 | R.id.text1, 49 | titles) { 50 | @Override 51 | public View getView(int position, View convertView, ViewGroup parent) { 52 | View view = super.getView(position, convertView, parent); 53 | TextView text1 = (TextView) view.findViewById(R.id.text1); 54 | TextView text2 = (TextView) view.findViewById(R.id.text2); 55 | 56 | text1.setText(titles[position]); 57 | text2.setText(descriptions[position]); 58 | return view; 59 | } 60 | }; 61 | ListView listView = (ListView) findViewById(R.id.list_view); 62 | listView.setAdapter(adapter); 63 | listView.setOnItemClickListener(this); 64 | } 65 | 66 | @Override 67 | public void onItemClick(AdapterView parent, View view, int position, long id) { 68 | final Class newActivity; 69 | switch (position) { 70 | default: 71 | case 0 : 72 | newActivity = PositionSampleActivity.class; 73 | break; 74 | case 1: 75 | newActivity = GroupStyleSampleActivity.class; 76 | break; 77 | case 2: 78 | newActivity = IndividualStyleSampleActivity.class; 79 | break; 80 | case 3: 81 | newActivity = EventsSampleActivity.class; 82 | break; 83 | case 4: 84 | newActivity = MenuItemsSampleActivity.class; 85 | break; 86 | } 87 | Intent i = new Intent(HomeActivity.this, newActivity); 88 | i.putExtra(BaseSampleActivity.TITLE, titles[position]); 89 | startActivity(i); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /samples/src/main/res/layout/activity_position_sample.xml: -------------------------------------------------------------------------------- 1 | 16 | 24 | 25 | 29 | 30 | 36 | 37 | 38 | 39 | 44 | 45 | 53 | 54 | 62 | 63 | 71 | 72 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /samples/src/main/res/layout/activity_menu_items_sample.xml: -------------------------------------------------------------------------------- 1 | 16 | 24 | 25 | 29 | 30 | 36 | 37 | 38 | 39 | 48 | 49 | 53 | 54 | 59 | 60 | 65 | 66 | 71 | 72 | 76 | 77 | 82 | 83 | 84 | 85 | 89 | 90 | 94 | 95 | 100 | 101 | 102 | 103 | 107 | 108 | 112 | 113 | 118 | 119 | 120 | 121 | 122 | 123 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /library/src/main/java/io/github/yavski/fabspeeddial/FabSpeedDialBehaviour.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Yavor Ivanov 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 | package io.github.yavski.fabspeeddial; 18 | 19 | import android.content.Context; 20 | import android.graphics.Rect; 21 | import android.os.Build; 22 | import android.support.design.widget.AppBarLayout; 23 | import android.support.design.widget.CoordinatorLayout; 24 | import android.support.design.widget.Snackbar; 25 | import android.support.v4.view.ViewCompat; 26 | import android.support.v4.view.ViewPropertyAnimatorCompat; 27 | import android.util.AttributeSet; 28 | import android.view.View; 29 | 30 | import java.lang.reflect.InvocationTargetException; 31 | import java.lang.reflect.Method; 32 | import java.util.List; 33 | 34 | 35 | /** 36 | * Copyright (C) 2015 The Android Open Source Project 37 | *

38 | * Licensed under the Apache License, Version 2.0 (the "License"); 39 | * you may not use this file except in compliance with the License. 40 | * You may obtain a copy of the License at 41 | *

42 | * http://www.apache.org/licenses/LICENSE-2.0 43 | *

44 | * Unless required by applicable law or agreed to in writing, software 45 | * distributed under the License is distributed on an "AS IS" BASIS, 46 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 47 | * See the License for the specific language governing permissions and 48 | * limitations under the License. 49 | *

50 | *

51 | * A slightly modified version of the original 52 | * {@link android.support.design.widget.FloatingActionButton.Behavior}. 53 | *

54 | * Created by yavorivanov on 03/01/2016. 55 | */ 56 | public class FabSpeedDialBehaviour extends CoordinatorLayout.Behavior { 57 | 58 | // We only support the FAB <> Snackbar shift movement on Honeycomb and above. This is 59 | // because we can use view translation properties which greatly simplifies the code. 60 | private static final boolean SNACKBAR_BEHAVIOR_ENABLED = Build.VERSION.SDK_INT >= 11; 61 | 62 | private ViewPropertyAnimatorCompat mFabTranslationYAnimator; 63 | private float mFabTranslationY; 64 | private Rect mTmpRect; 65 | 66 | public FabSpeedDialBehaviour() { 67 | 68 | } 69 | 70 | public FabSpeedDialBehaviour(Context context, AttributeSet attrs) { 71 | super(context, attrs); 72 | } 73 | 74 | @Override 75 | public boolean layoutDependsOn(CoordinatorLayout parent, FabSpeedDial child, View dependency) { 76 | // We're dependent on all SnackbarLayouts (if enabled) 77 | return SNACKBAR_BEHAVIOR_ENABLED && dependency instanceof Snackbar.SnackbarLayout; 78 | } 79 | 80 | @Override 81 | public boolean onDependentViewChanged(CoordinatorLayout parent, FabSpeedDial child, 82 | View dependency) { 83 | if (dependency instanceof Snackbar.SnackbarLayout) { 84 | updateFabTranslationForSnackbar(parent, child, dependency); 85 | } else if (dependency instanceof AppBarLayout) { 86 | // If we're depending on an AppBarLayout we will show/hide it automatically 87 | // if the FAB is anchored to the AppBarLayout 88 | updateFabVisibility(parent, (AppBarLayout) dependency, child); 89 | } 90 | return false; 91 | } 92 | 93 | private void updateFabTranslationForSnackbar(CoordinatorLayout parent, 94 | final FabSpeedDial fab, View snackbar) { 95 | if (fab.getVisibility() != View.VISIBLE) { 96 | return; 97 | } 98 | 99 | final float targetTransY = getFabTranslationYForSnackbar(parent, fab); 100 | if (mFabTranslationY == targetTransY) { 101 | // We're already at (or currently animating to) the target value, return... 102 | return; 103 | } 104 | 105 | final float currentTransY = ViewCompat.getTranslationY(fab); 106 | 107 | // Make sure that any current animation is cancelled 108 | if (mFabTranslationYAnimator != null) { 109 | mFabTranslationYAnimator.cancel(); 110 | } 111 | 112 | if (Math.abs(currentTransY - targetTransY) > (fab.getHeight() * 0.667f)) { 113 | mFabTranslationYAnimator = ViewCompat.animate(fab) 114 | .setInterpolator(FabSpeedDial.FAST_OUT_SLOW_IN_INTERPOLATOR) 115 | .translationY(targetTransY); 116 | mFabTranslationYAnimator.start(); 117 | } else { 118 | ViewCompat.setTranslationY(fab, targetTransY); 119 | } 120 | 121 | mFabTranslationY = targetTransY; 122 | } 123 | 124 | private float getFabTranslationYForSnackbar(CoordinatorLayout parent, 125 | FabSpeedDial fab) { 126 | float minOffset = 0; 127 | final List dependencies = parent.getDependencies(fab); 128 | for (int i = 0, z = dependencies.size(); i < z; i++) { 129 | final View view = dependencies.get(i); 130 | if (view instanceof Snackbar.SnackbarLayout && parent.doViewsOverlap(fab, view)) { 131 | minOffset = Math.min(minOffset, 132 | ViewCompat.getTranslationY(view) - view.getHeight()); 133 | } 134 | } 135 | 136 | return minOffset; 137 | } 138 | 139 | @Override 140 | public boolean onLayoutChild(CoordinatorLayout parent, FabSpeedDial child, int layoutDirection) { 141 | // First, lets make sure that the visibility of the FAB is consistent 142 | final List dependencies = parent.getDependencies(child); 143 | for (int i = 0, count = dependencies.size(); i < count; i++) { 144 | final View dependency = dependencies.get(i); 145 | if (dependency instanceof AppBarLayout 146 | && updateFabVisibility(parent, (AppBarLayout) dependency, child)) { 147 | break; 148 | } 149 | } 150 | // Now let the CoordinatorLayout lay out the FAB 151 | parent.onLayoutChild(child, layoutDirection); 152 | return true; 153 | } 154 | 155 | private boolean updateFabVisibility(CoordinatorLayout parent, AppBarLayout appBarLayout, 156 | FabSpeedDial child) { 157 | final CoordinatorLayout.LayoutParams lp = 158 | (CoordinatorLayout.LayoutParams) child.getLayoutParams(); 159 | if (lp.getAnchorId() != appBarLayout.getId()) { 160 | // The anchor ID doesn't match the dependency, so we won't automatically 161 | // show/hide the FAB 162 | return false; 163 | } 164 | 165 | if (mTmpRect == null) { 166 | mTmpRect = new Rect(); 167 | } 168 | 169 | // First, let's get the visible rect of the dependency 170 | final Rect rect = mTmpRect; 171 | ViewGroupUtils.getDescendantRect(parent, appBarLayout, rect); 172 | 173 | /** 174 | * TODO: Remove reflection and replace with 175 | * AppBarLayout#getMinimumHeightForVisibleOverlappingContent() once made publuc 176 | */ 177 | int minimumHeightForVisibleOverlappingContent = 178 | getMinimumHeightForVisibleOverlappingContent(appBarLayout); 179 | if (minimumHeightForVisibleOverlappingContent == -1) { // the api has changed, return 180 | return true; 181 | } 182 | if (rect.bottom <= minimumHeightForVisibleOverlappingContent) { 183 | // If the anchor's bottom is below the seam, we'll animate our FAB out 184 | // child.hide(); 185 | } else { 186 | // Else, we'll animate our FAB back in 187 | // child.show(); 188 | } 189 | return true; 190 | } 191 | 192 | private int getMinimumHeightForVisibleOverlappingContent(AppBarLayout appBarLayout) { 193 | try { 194 | Method method = appBarLayout.getClass().getDeclaredMethod("getMinimumHeightForVisibleOverlappingContent"); 195 | method.setAccessible(true); 196 | Object value = method.invoke(appBarLayout, null); 197 | return (int) value; 198 | } catch (NoSuchMethodException e) { 199 | e.printStackTrace(); 200 | } catch (InvocationTargetException e) { 201 | e.printStackTrace(); 202 | } catch (IllegalAccessException e) { 203 | e.printStackTrace(); 204 | } 205 | return -1; 206 | } 207 | 208 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### fab-speed-dial 2 | 3 | [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-fab--speed--dial-green.svg?style=true)](https://android-arsenal.com/details/1/3062) [![Android Gems](http://www.android-gems.com/badge/yavski/fab-speed-dial.svg?branch=master)](http://www.android-gems.com/lib/yavski/fab-speed-dial) 4 | 5 | A simple library marrying together [FAB] (http://developer.android.com/reference/android/support/design/widget/FloatingActionButton.html) + [menu resources] (http://developer.android.com/guide/topics/resources/menu-resource.html) + [Speed dial metaphor from Material Design] (https://www.google.com/design/spec/components/buttons-floating-action-button.html#buttons-floating-action-button-transitions). 6 | 7 | Similarly tо [NavigationView] (http://developer.android.com/reference/android/support/design/widget/NavigationView.html?utm_campaign=io15&utm_source=dac&utm_medium=blog) and [ActionBar] (http://developer.android.com/reference/android/app/ActionBar.html), [FabSpeedDial] (https://github.com/yavski/fab-speed-dial/blob/master/library/src/main/java/io/github/yavski/fabspeeddial/FabSpeedDial.java) makes use of [menu resources] (http://developer.android.com/guide/topics/resources/menu-resource.html) in order to present a list of actionable buttons. This makes the library somewhat familiar to use and easy to integrate. The library runs on Android 2.2 (API 8) onwards. 8 | 9 | ### Gettting started 10 | 11 | ##### Add the dependency to gradle.build 12 | ``` 13 | dependencies { 14 | compile 'io.github.yavski:fab-speed-dial:1.0.6' 15 | } 16 | ``` 17 | 18 | ##### Define a menu resource 19 | ``` 20 |

22 | 26 | 30 | 34 | 35 | ``` 36 | 37 | ##### Add FabSpeedDial to your layout xml 38 | ``` 39 | 44 | 45 | 54 | 55 | 56 | ``` 57 | ##### Result 58 | 59 | 60 | In order to save the menu open/closed state, **you must define an id to the FabSpeedDial**, otherwise if you rotate the device the state won't be persisted. 61 | 62 | ##### Events 63 | 64 | As with all menus, you have a callback just before the list of actionable items are presented. The callback allows you to update your menu items, or not show the menu altogether. 65 | 66 | ``` 67 | FabSpeedDial fabSpeedDial = (FabSpeedDial) findViewById(R.id.fab_speed_dial); 68 | fabSpeedDial.setMenuListener(new SimpleMenuListenerAdapter() { 69 | @Override 70 | public boolean onPrepareMenu(NavigationMenu navigationMenu) { 71 | // TODO: Do something with yout menu items, or return false if you don't want to show them 72 | return true; 73 | } 74 | }); 75 | ``` 76 | 77 | Similarly, in order to be notified about a selection: 78 | 79 | ``` 80 | FabSpeedDial fabSpeedDial = (FabSpeedDial) findViewById(R.id.fab_speed_dial); 81 | fabSpeedDial.setMenuListener(new SimpleMenuListenerAdapter() { 82 | @Override 83 | public boolean onMenuItemSelected(MenuItem menuItem) { 84 | //TODO: Start some activity 85 | return false; 86 | } 87 | }); 88 | ``` 89 | 90 | 91 | ### Customisation 92 | 93 | ##### Multi-colored fabs 94 | Define an integer-array resource that contains the colors you want to use in the order of the items you want colored: 95 | 96 | ``` 97 | 98 | 99 | @android:color/holo_red_dark 100 | 101 | @android:color/holo_purple 102 | 103 | @android:color/holo_green_light 104 | 105 | ``` 106 | 107 | Make sure you also assign [android:orderInCategory](https://developer.android.com/guide/topics/resources/menu-resource.html) to each menu item in your menu.xml in the same 0-based order. 108 | 109 | To provide the color array to the menu items use the following attributes: ```miniFabTitleTextColorList``` and ```miniFabBackgroundTintList```. Note - if you've used ```miniFabTitleTextColor``` and ```miniFabBackgroundTint``` they will be overriden by the color arrays. 110 | 111 | ##### Result 112 | 113 | 114 | ##### Position 115 | In order to change the position of the view, use the standard android APIs to position *FabSpeedDial* within your *ViewGroup* and be sure to assign ```fabGravity``` a relevant value. 116 | 117 | 118 | 119 | 120 | 121 | ##### Basic styling 122 | As a rule of thumb, attributes prepended with *fab*, i.e. ```fabDrawable```, refer to the normsal-sized FAB view; attribtues prepended with *miniFab* refer to the mini-sized FAB views in the list. 123 | 124 | The following attribtues are supported: 125 | 126 | | FabSpeedDial| Android | Desscription | 127 | | ------------- |-------------|-----| 128 | | app:fabDrawable | [android:src](http://developer.android.com/reference/android/widget/ImageView.html#attr_android:src) | Sets the icon drawable of the main FAB | 129 | | app:fabDrawableTint | [android:tint](http://developer.android.com/reference/android/widget/ImageView.html#attr_android:tint) | Tints the icon drawable of the main FAB | 130 | | app:fabBackgroundTint | [android:backgroundTint](http://developer.android.com/reference/android/view/View.html#attr_android:backgroundTint) | Tints the background colour of the main FAB | 131 | | app:miniFabDrawableTint | [android:tint](http://developer.android.com/reference/android/widget/ImageView.html#attr_android:tint) | Tints the icon drawable of the mini FAB(s) | 132 | | app:miniFabBackgroundTint | [android:backgroundTint](http://developer.android.com/reference/android/view/View.html#attr_android:backgroundTint) | Tints the background colour of the mini FAB(s) | 133 | |app:miniFabBackgroundTintList | | An array containing the background colors for each of the mini FABs. 134 | |app:miniFabTitleTextColor| [android:textColor] (https://developer.android.com/reference/android/widget/TextView.html#attr_android:textColor) | Sets the color of the title of the mini FAB. | 135 | |app:miniFabTitleTextColorList|| An array containing the colors for each of the titles of the mini FABs. 136 | | app:miniFabTitleBackgroundTint | [android:backgroundTint](http://developer.android.com/reference/android/view/View.html#attr_android:backgroundTint) | Tints the background colour of the title(s) of the mini FAB(s) | 137 | | app:miniFabTitlesEnabled | | Convinience for hiding the tilte(s) of the mini FAB(s) | 138 | | app:touchGuard | | Hide FAB when touching out of its bounds | 139 | | app:touchGuardDrawable | [android:background](http://developer.android.com/reference/android/view/View.html#setBackground(android.graphics.drawable.Drawable)) | Sets background to the container of FAB 140 | 141 | ### Caveats 142 | 143 | If you have used FloatingActionButton, CoordinatorLayout, and both combined, you are most probably aware that: 144 | * Internally, FAB has two main implementations: one for SDK >= 21, one for earlier versions; **the one for older versions uses extra padding in order to draw shadows; you don't need to account for the extra padding** as the library takes care of it however do check your layouts/dimensions to avoid mis-positioned views. 145 | * When used in a CoordinatorLayout, FAB is known to have its margin values ignored / misused under certain circumstances; as a workaround the library always adds left or right margin values (depending on gravity), taking into account the SDK version too. 146 | 147 | ### License 148 | ``` 149 | 164 | ``` 165 | 166 | -------------------------------------------------------------------------------- /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 2016 Yavor Ivanov 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. -------------------------------------------------------------------------------- /library/src/main/java/io/github/yavski/fabspeeddial/FabSpeedDial.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Yavor Ivanov 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 | package io.github.yavski.fabspeeddial; 18 | 19 | import android.annotation.TargetApi; 20 | import android.content.Context; 21 | import android.content.res.ColorStateList; 22 | import android.content.res.TypedArray; 23 | import android.graphics.Typeface; 24 | import android.graphics.drawable.Drawable; 25 | import android.os.Build; 26 | import android.os.Parcel; 27 | import android.os.Parcelable; 28 | import android.support.design.internal.NavigationMenu; 29 | import android.support.design.widget.CoordinatorLayout; 30 | import android.support.design.widget.FloatingActionButton; 31 | import android.support.v4.content.ContextCompat; 32 | import android.support.v4.view.ViewCompat; 33 | import android.support.v4.view.ViewPropertyAnimatorListenerAdapter; 34 | import android.support.v4.view.animation.FastOutLinearInInterpolator; 35 | import android.support.v4.view.animation.FastOutSlowInInterpolator; 36 | import android.support.v7.view.SupportMenuInflater; 37 | import android.support.v7.view.menu.MenuBuilder; 38 | import android.support.v7.widget.CardView; 39 | import android.text.TextUtils; 40 | import android.util.AndroidRuntimeException; 41 | import android.util.AttributeSet; 42 | import android.util.Log; 43 | import android.view.Gravity; 44 | import android.view.KeyEvent; 45 | import android.view.LayoutInflater; 46 | import android.view.MenuItem; 47 | import android.view.View; 48 | import android.view.ViewGroup; 49 | import android.view.ViewParent; 50 | import android.widget.FrameLayout; 51 | import android.widget.LinearLayout; 52 | import android.widget.RelativeLayout; 53 | import android.widget.TextView; 54 | 55 | import java.util.HashMap; 56 | import java.util.Map; 57 | 58 | import io.github.yavski.fabmenu.R; 59 | 60 | /** 61 | * Created by yavorivanov on 01/01/2016. 62 | */ 63 | @CoordinatorLayout.DefaultBehavior(FabSpeedDialBehaviour.class) 64 | public class FabSpeedDial extends LinearLayout implements View.OnClickListener { 65 | 66 | /** 67 | * Called to notify of close and selection changes. 68 | */ 69 | public interface MenuListener { 70 | 71 | /** 72 | * Called just before the menu items are about to become visible. 73 | * Don't block as it's called on the main thread. 74 | * 75 | * @param navigationMenu The menu containing all menu items. 76 | * @return You must return true for the menu to be displayed; 77 | * if you return false it will not be shown. 78 | */ 79 | boolean onPrepareMenu(NavigationMenu navigationMenu); 80 | 81 | /** 82 | * Called when a menu item is selected. 83 | * 84 | * @param menuItem The menu item that is selected 85 | * @return whether the menu item selection was handled 86 | */ 87 | boolean onMenuItemSelected(MenuItem menuItem); 88 | 89 | void onMenuClosed(); 90 | } 91 | 92 | private static final String TAG = FabSpeedDial.class.getSimpleName(); 93 | 94 | private static final int VSYNC_RHYTHM = 16; 95 | 96 | public static final FastOutSlowInInterpolator FAST_OUT_SLOW_IN_INTERPOLATOR = 97 | new FastOutSlowInInterpolator(); 98 | 99 | public static final int BOTTOM_END = 0; 100 | public static final int BOTTOM_START = 1; 101 | public static final int TOP_END = 2; 102 | public static final int TOP_START = 3; 103 | private static final int DEFAULT_MENU_POSITION = BOTTOM_END; 104 | 105 | private MenuListener menuListener; 106 | private NavigationMenu navigationMenu; 107 | private Map fabMenuItemMap; 108 | private Map cardViewMenuItemMap; 109 | 110 | private LinearLayout menuItemsLayout; 111 | FloatingActionButton fab; 112 | private View touchGuard = null; 113 | 114 | private int menuId; 115 | private int fabGravity; 116 | private Drawable fabDrawable; 117 | private ColorStateList fabDrawableTint; 118 | private ColorStateList fabBackgroundTint; 119 | private ColorStateList miniFabDrawableTint; 120 | private ColorStateList miniFabBackgroundTint; 121 | private int[] miniFabBackgroundTintArray; 122 | private ColorStateList miniFabTitleBackgroundTint; 123 | private boolean miniFabTitlesEnabled; 124 | private int miniFabTitleTextColor; 125 | private int[] miniFabTitleTextColorArray; 126 | private Drawable touchGuardDrawable; 127 | private boolean useTouchGuard; 128 | 129 | private boolean isAnimating; 130 | 131 | // Variable to hold whether the menu was open or not on config change 132 | private boolean shouldOpenMenu; 133 | 134 | private FabSpeedDial(Context context) { 135 | super(context); 136 | } 137 | 138 | public FabSpeedDial(Context context, AttributeSet attrs) { 139 | super(context, attrs); 140 | init(context, attrs); 141 | } 142 | 143 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) 144 | public FabSpeedDial(Context context, AttributeSet attrs, int defStyleAttr) { 145 | super(context, attrs, defStyleAttr); 146 | init(context, attrs); 147 | } 148 | 149 | private void init(Context context, AttributeSet attrs) { 150 | TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.FabSpeedDial, 0, 0); 151 | resolveCompulsoryAttributes(typedArray); 152 | resolveOptionalAttributes(typedArray); 153 | typedArray.recycle(); 154 | 155 | if (isGravityBottom()) { 156 | LayoutInflater.from(context).inflate(R.layout.fab_speed_dial_bottom, this, true); 157 | } else { 158 | LayoutInflater.from(context).inflate(R.layout.fab_speed_dial_top, this, true); 159 | } 160 | 161 | if (isGravityEnd()) { 162 | setGravity(Gravity.END); 163 | } 164 | 165 | menuItemsLayout = (LinearLayout) findViewById(R.id.menu_items_layout); 166 | 167 | setOrientation(VERTICAL); 168 | 169 | newNavigationMenu(); 170 | 171 | int menuItemCount = navigationMenu.size(); 172 | fabMenuItemMap = new HashMap<>(menuItemCount); 173 | cardViewMenuItemMap = new HashMap<>(menuItemCount); 174 | } 175 | 176 | private void resolveCompulsoryAttributes(TypedArray typedArray) { 177 | if (typedArray.hasValue(R.styleable.FabSpeedDial_fabMenu)) { 178 | menuId = typedArray.getResourceId(R.styleable.FabSpeedDial_fabMenu, 0); 179 | } else { 180 | throw new AndroidRuntimeException("You must provide the id of the menu resource."); 181 | } 182 | 183 | if (typedArray.hasValue(R.styleable.FabSpeedDial_fabGravity)) { 184 | fabGravity = typedArray.getInt(R.styleable.FabSpeedDial_fabGravity, DEFAULT_MENU_POSITION); 185 | } else { 186 | throw new AndroidRuntimeException("You must specify the gravity of the Fab."); 187 | } 188 | } 189 | 190 | private void resolveOptionalAttributes(TypedArray typedArray) { 191 | fabDrawable = typedArray.getDrawable(R.styleable.FabSpeedDial_fabDrawable); 192 | if (fabDrawable == null) { 193 | fabDrawable = ContextCompat.getDrawable(getContext(), R.drawable.fab_add_clear_selector); 194 | } 195 | 196 | fabDrawableTint = typedArray.getColorStateList(R.styleable.FabSpeedDial_fabDrawableTint); 197 | if (fabDrawableTint == null) { 198 | fabDrawableTint = getColorStateList(R.color.fab_drawable_tint); 199 | } 200 | 201 | if (typedArray.hasValue(R.styleable.FabSpeedDial_fabBackgroundTint)) { 202 | fabBackgroundTint = typedArray.getColorStateList(R.styleable.FabSpeedDial_fabBackgroundTint); 203 | } 204 | 205 | miniFabBackgroundTint = typedArray.getColorStateList(R.styleable.FabSpeedDial_miniFabBackgroundTint); 206 | if (miniFabBackgroundTint == null) { 207 | miniFabBackgroundTint = getColorStateList(R.color.fab_background_tint); 208 | } 209 | 210 | if (typedArray.hasValue(R.styleable.FabSpeedDial_miniFabBackgroundTintList)) { 211 | int miniFabBackgroundTintListId = typedArray.getResourceId(R.styleable.FabSpeedDial_miniFabBackgroundTintList, 0); 212 | TypedArray miniFabBackgroundTintRes = getResources().obtainTypedArray(miniFabBackgroundTintListId); 213 | miniFabBackgroundTintArray = new int[miniFabBackgroundTintRes.length()]; 214 | for (int i = 0; i < miniFabBackgroundTintRes.length(); i++) { 215 | miniFabBackgroundTintArray[i] = miniFabBackgroundTintRes.getResourceId(i, 0); 216 | } 217 | miniFabBackgroundTintRes.recycle(); 218 | } 219 | 220 | miniFabDrawableTint = typedArray.getColorStateList(R.styleable.FabSpeedDial_miniFabDrawableTint); 221 | if (miniFabDrawableTint == null) { 222 | miniFabDrawableTint = getColorStateList(R.color.mini_fab_drawable_tint); 223 | } 224 | 225 | miniFabTitleBackgroundTint = typedArray.getColorStateList(R.styleable.FabSpeedDial_miniFabTitleBackgroundTint); 226 | if (miniFabTitleBackgroundTint == null) { 227 | miniFabTitleBackgroundTint = getColorStateList(R.color.mini_fab_title_background_tint); 228 | } 229 | 230 | miniFabTitlesEnabled = typedArray.getBoolean(R.styleable.FabSpeedDial_miniFabTitlesEnabled, true); 231 | 232 | 233 | miniFabTitleTextColor = typedArray.getColor(R.styleable.FabSpeedDial_miniFabTitleTextColor, 234 | ContextCompat.getColor(getContext(), R.color.title_text_color)); 235 | 236 | if (typedArray.hasValue(R.styleable.FabSpeedDial_miniFabTitleTextColorList)) { 237 | int miniFabTitleTextColorListId = typedArray.getResourceId(R.styleable.FabSpeedDial_miniFabTitleTextColorList, 0); 238 | TypedArray miniFabTitleTextColorTa = getResources().obtainTypedArray(miniFabTitleTextColorListId); 239 | miniFabTitleTextColorArray = new int[miniFabTitleTextColorTa.length()]; 240 | for (int i = 0; i < miniFabTitleTextColorTa.length(); i++) { 241 | miniFabTitleTextColorArray[i] = miniFabTitleTextColorTa.getResourceId(i, 0); 242 | } 243 | miniFabTitleTextColorTa.recycle(); 244 | } 245 | 246 | touchGuardDrawable = typedArray.getDrawable(R.styleable.FabSpeedDial_touchGuardDrawable); 247 | 248 | useTouchGuard = typedArray.getBoolean(R.styleable.FabSpeedDial_touchGuard, true); 249 | } 250 | 251 | @Override 252 | protected void onAttachedToWindow() { 253 | super.onAttachedToWindow(); 254 | 255 | LayoutParams layoutParams = 256 | new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 257 | int coordinatorLayoutOffset = getResources().getDimensionPixelSize(R.dimen.coordinator_layout_offset); 258 | if (fabGravity == BOTTOM_END || fabGravity == TOP_END) { 259 | layoutParams.setMargins(0, 0, coordinatorLayoutOffset, 0); 260 | } else { 261 | layoutParams.setMargins(coordinatorLayoutOffset, 0, 0, 0); 262 | } 263 | menuItemsLayout.setLayoutParams(layoutParams); 264 | 265 | // Set up the client's FAB 266 | fab = (FloatingActionButton) findViewById(R.id.fab); 267 | fab.setImageDrawable(fabDrawable); 268 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 269 | fab.setImageTintList(fabDrawableTint); 270 | } 271 | if (fabBackgroundTint != null) { 272 | fab.setBackgroundTintList(fabBackgroundTint); 273 | } 274 | 275 | fab.setOnClickListener(new OnClickListener() { 276 | @Override 277 | public void onClick(View v) { 278 | if (isAnimating) return; 279 | 280 | if (isMenuOpen()) { 281 | closeMenu(); 282 | } else { 283 | openMenu(); 284 | } 285 | } 286 | }); 287 | 288 | // Needed in order to intercept key events 289 | setFocusableInTouchMode(true); 290 | 291 | if (useTouchGuard) { 292 | ViewParent parent = getParent(); 293 | 294 | touchGuard = new View(getContext()); 295 | touchGuard.setOnClickListener(this); 296 | touchGuard.setWillNotDraw(true); 297 | touchGuard.setVisibility(GONE); 298 | 299 | if (touchGuardDrawable != null) { 300 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 301 | touchGuard.setBackground(touchGuardDrawable); 302 | } else { 303 | touchGuard.setBackgroundDrawable(touchGuardDrawable); 304 | } 305 | } 306 | 307 | if (parent instanceof FrameLayout) { 308 | FrameLayout frameLayout = (FrameLayout) parent; 309 | frameLayout.addView(touchGuard); 310 | bringToFront(); 311 | } else if (parent instanceof CoordinatorLayout) { 312 | CoordinatorLayout coordinatorLayout = (CoordinatorLayout) parent; 313 | coordinatorLayout.addView(touchGuard); 314 | bringToFront(); 315 | } else if (parent instanceof RelativeLayout) { 316 | RelativeLayout relativeLayout = (RelativeLayout) parent; 317 | relativeLayout.addView(touchGuard, 318 | new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 319 | ViewGroup.LayoutParams.MATCH_PARENT)); 320 | bringToFront(); 321 | } else { 322 | Log.d(TAG, "touchGuard requires that the parent of this FabSpeedDialer be a FrameLayout or RelativeLayout"); 323 | } 324 | } 325 | 326 | setOnClickListener(this); 327 | 328 | if (shouldOpenMenu) 329 | openMenu(); 330 | } 331 | 332 | private void newNavigationMenu() { 333 | navigationMenu = new NavigationMenu(getContext()); 334 | new SupportMenuInflater(getContext()).inflate(menuId, navigationMenu); 335 | 336 | navigationMenu.setCallback(new MenuBuilder.Callback() { 337 | @Override 338 | public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) { 339 | return menuListener != null && menuListener.onMenuItemSelected(item); 340 | } 341 | 342 | @Override 343 | public void onMenuModeChange(MenuBuilder menu) { 344 | } 345 | }); 346 | } 347 | 348 | @Override 349 | public void onClick(View v) { 350 | fab.setSelected(false); 351 | removeFabMenuItems(); 352 | 353 | if (menuListener != null) { 354 | if (v == this || v == touchGuard) { 355 | menuListener.onMenuClosed(); 356 | } else if (v instanceof FloatingActionButton) { 357 | menuListener.onMenuItemSelected(fabMenuItemMap.get(v)); 358 | } else if (v instanceof CardView) { 359 | menuListener.onMenuItemSelected(cardViewMenuItemMap.get(v)); 360 | } 361 | } else { 362 | Log.d(TAG, "You haven't provided a MenuListener."); 363 | } 364 | } 365 | 366 | @Override 367 | protected Parcelable onSaveInstanceState() { 368 | Parcelable superState = super.onSaveInstanceState(); 369 | SavedState ss = new SavedState(superState); 370 | 371 | ss.isShowingMenu = isMenuOpen(); 372 | 373 | return ss; 374 | } 375 | 376 | @Override 377 | protected void onRestoreInstanceState(Parcelable state) { 378 | if (!(state instanceof SavedState)) { 379 | super.onRestoreInstanceState(state); 380 | return; 381 | } 382 | 383 | SavedState ss = (SavedState) state; 384 | super.onRestoreInstanceState(ss.getSuperState()); 385 | 386 | this.shouldOpenMenu = ss.isShowingMenu; 387 | } 388 | 389 | public void setMenuListener(MenuListener menuListener) { 390 | this.menuListener = menuListener; 391 | } 392 | 393 | public boolean isMenuOpen() { 394 | return menuItemsLayout.getChildCount() > 0; 395 | } 396 | 397 | public void openMenu() { 398 | if (!ViewCompat.isAttachedToWindow(this)) 399 | return; 400 | requestFocus(); 401 | 402 | boolean showMenu = true; 403 | if (menuListener != null) { 404 | newNavigationMenu(); 405 | showMenu = menuListener.onPrepareMenu(navigationMenu); 406 | } 407 | 408 | if (showMenu) { 409 | addMenuItems(); 410 | fab.setSelected(true); 411 | } else { 412 | fab.setSelected(false); 413 | } 414 | } 415 | 416 | public void closeMenu() { 417 | if (!ViewCompat.isAttachedToWindow(this)) 418 | return; 419 | 420 | if (isMenuOpen()) { 421 | fab.setSelected(false); 422 | removeFabMenuItems(); 423 | if (menuListener != null) { 424 | menuListener.onMenuClosed(); 425 | } 426 | } 427 | } 428 | 429 | public void show() { 430 | if (!ViewCompat.isAttachedToWindow(this)) 431 | return; 432 | setVisibility(View.VISIBLE); 433 | fab.show(); 434 | } 435 | 436 | public void hide() { 437 | if (!ViewCompat.isAttachedToWindow(this)) 438 | return; 439 | 440 | if (isMenuOpen()) { 441 | closeMenu(); 442 | } 443 | fab.hide(); 444 | } 445 | 446 | private void addMenuItems() { 447 | ViewCompat.setAlpha(menuItemsLayout, 1f); 448 | for (int i = 0; i < navigationMenu.size(); i++) { 449 | MenuItem menuItem = navigationMenu.getItem(i); 450 | if (menuItem.isVisible()) { 451 | menuItemsLayout.addView(createFabMenuItem(menuItem)); 452 | } 453 | } 454 | animateFabMenuItemsIn(); 455 | } 456 | 457 | private View createFabMenuItem(MenuItem menuItem) { 458 | ViewGroup fabMenuItem = (ViewGroup) LayoutInflater.from(getContext()) 459 | .inflate(getMenuItemLayoutId(), this, false); 460 | 461 | FloatingActionButton miniFab = (FloatingActionButton) fabMenuItem.findViewById(R.id.mini_fab); 462 | CardView cardView = (CardView) fabMenuItem.findViewById(R.id.card_view); 463 | TextView titleView = (TextView) fabMenuItem.findViewById(R.id.title_view); 464 | 465 | fabMenuItemMap.put(miniFab, menuItem); 466 | cardViewMenuItemMap.put(cardView, menuItem); 467 | 468 | miniFab.setImageDrawable(menuItem.getIcon()); 469 | miniFab.setOnClickListener(this); 470 | cardView.setOnClickListener(this); 471 | 472 | ViewCompat.setAlpha(miniFab, 0f); 473 | ViewCompat.setAlpha(cardView, 0f); 474 | 475 | final CharSequence title = menuItem.getTitle(); 476 | if (!TextUtils.isEmpty(title) && miniFabTitlesEnabled) { 477 | cardView.setCardBackgroundColor(miniFabTitleBackgroundTint.getDefaultColor()); 478 | titleView.setText(title); 479 | titleView.setTypeface(null, Typeface.BOLD); 480 | titleView.setTextColor(miniFabTitleTextColor); 481 | 482 | if (miniFabTitleTextColorArray != null) { 483 | titleView.setTextColor(ContextCompat.getColorStateList(getContext(), 484 | miniFabTitleTextColorArray[menuItem.getOrder()])); 485 | } 486 | } else { 487 | fabMenuItem.removeView(cardView); 488 | } 489 | 490 | miniFab.setBackgroundTintList(miniFabBackgroundTint); 491 | 492 | if (miniFabBackgroundTintArray != null) { 493 | miniFab.setBackgroundTintList(ContextCompat.getColorStateList(getContext(), 494 | miniFabBackgroundTintArray[menuItem.getOrder()])); 495 | } 496 | 497 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 498 | miniFab.setImageTintList(miniFabDrawableTint); 499 | } 500 | 501 | return fabMenuItem; 502 | } 503 | 504 | private void removeFabMenuItems() { 505 | if (touchGuard != null) touchGuard.setVisibility(GONE); 506 | 507 | ViewCompat.animate(menuItemsLayout) 508 | .setDuration(getResources().getInteger(android.R.integer.config_shortAnimTime)) 509 | .alpha(0f) 510 | .setInterpolator(new FastOutLinearInInterpolator()) 511 | .setListener(new ViewPropertyAnimatorListenerAdapter() { 512 | @Override 513 | public void onAnimationStart(View view) { 514 | super.onAnimationStart(view); 515 | isAnimating = true; 516 | } 517 | 518 | @Override 519 | public void onAnimationEnd(View view) { 520 | super.onAnimationEnd(view); 521 | menuItemsLayout.removeAllViews(); 522 | isAnimating = false; 523 | } 524 | }) 525 | .start(); 526 | } 527 | 528 | private void animateFabMenuItemsIn() { 529 | if (touchGuard != null) touchGuard.setVisibility(VISIBLE); 530 | 531 | final int count = menuItemsLayout.getChildCount(); 532 | 533 | if (isGravityBottom()) { 534 | for (int i = count - 1; i >= 0; i--) { 535 | final View fabMenuItem = menuItemsLayout.getChildAt(i); 536 | animateViewIn(fabMenuItem.findViewById(R.id.mini_fab), Math.abs(count - 1 - i)); 537 | View cardView = fabMenuItem.findViewById(R.id.card_view); 538 | if (cardView != null) { 539 | animateViewIn(cardView, Math.abs(count - 1 - i)); 540 | } 541 | } 542 | } else { 543 | for (int i = 0; i < count; i++) { 544 | final View fabMenuItem = menuItemsLayout.getChildAt(i); 545 | animateViewIn(fabMenuItem.findViewById(R.id.mini_fab), i); 546 | View cardView = fabMenuItem.findViewById(R.id.card_view); 547 | if (cardView != null) { 548 | animateViewIn(cardView, i); 549 | } 550 | } 551 | } 552 | } 553 | 554 | private void animateViewIn(final View view, int position) { 555 | final float offsetY = getResources().getDimensionPixelSize(R.dimen.keyline_1); 556 | 557 | ViewCompat.setScaleX(view, 0.25f); 558 | ViewCompat.setScaleY(view, 0.25f); 559 | ViewCompat.setY(view, ViewCompat.getY(view) + offsetY); 560 | 561 | ViewCompat.animate(view) 562 | .setDuration(getResources().getInteger(android.R.integer.config_shortAnimTime)) 563 | .scaleX(1f) 564 | .scaleY(1f) 565 | .translationYBy(-offsetY) 566 | .alpha(1f) 567 | .setStartDelay(4 * position * VSYNC_RHYTHM) 568 | .setInterpolator(new FastOutSlowInInterpolator()) 569 | .setListener(new ViewPropertyAnimatorListenerAdapter() { 570 | @Override 571 | public void onAnimationStart(View view) { 572 | super.onAnimationStart(view); 573 | isAnimating = true; 574 | } 575 | 576 | @Override 577 | public void onAnimationEnd(View view) { 578 | super.onAnimationEnd(view); 579 | isAnimating = false; 580 | } 581 | }) 582 | .start(); 583 | } 584 | 585 | private int getMenuItemLayoutId() { 586 | if (isGravityEnd()) { 587 | return R.layout.fab_menu_item_end; 588 | } else { 589 | return R.layout.fab_menu_item_start; 590 | } 591 | } 592 | 593 | private boolean isGravityBottom() { 594 | return fabGravity == BOTTOM_END || fabGravity == BOTTOM_START; 595 | } 596 | 597 | private boolean isGravityEnd() { 598 | return fabGravity == BOTTOM_END || fabGravity == TOP_END; 599 | } 600 | 601 | private ColorStateList getColorStateList(int colorRes) { 602 | int[][] states = new int[][]{ 603 | new int[]{android.R.attr.state_enabled}, // enabled 604 | new int[]{-android.R.attr.state_enabled}, // disabled 605 | new int[]{-android.R.attr.state_checked}, // unchecked 606 | new int[]{android.R.attr.state_pressed} // pressed 607 | }; 608 | 609 | int color = ContextCompat.getColor(getContext(), colorRes); 610 | 611 | int[] colors = new int[]{color, color, color, color}; 612 | return new ColorStateList(states, colors); 613 | } 614 | 615 | @Override 616 | public boolean dispatchKeyEventPreIme(KeyEvent event) { 617 | if (isMenuOpen() 618 | && event.getKeyCode() == KeyEvent.KEYCODE_BACK 619 | && event.getAction() == KeyEvent.ACTION_UP 620 | && event.getRepeatCount() == 0) { 621 | closeMenu(); 622 | return true; 623 | } 624 | 625 | return super.dispatchKeyEventPreIme(event); 626 | } 627 | 628 | static class SavedState extends BaseSavedState { 629 | 630 | boolean isShowingMenu; 631 | 632 | public SavedState(Parcel source) { 633 | super(source); 634 | this.isShowingMenu = source.readInt() == 1; 635 | } 636 | 637 | public SavedState(Parcelable superState) { 638 | super(superState); 639 | } 640 | 641 | @Override 642 | public void writeToParcel(Parcel out, int flags) { 643 | super.writeToParcel(out, flags); 644 | out.writeInt(this.isShowingMenu ? 1 : 0); 645 | } 646 | 647 | public static final Parcelable.Creator CREATOR = new Creator() { 648 | @Override 649 | public SavedState createFromParcel(Parcel parcel) { 650 | return new SavedState(parcel); 651 | } 652 | 653 | @Override 654 | public SavedState[] newArray(int i) { 655 | return new SavedState[i]; 656 | } 657 | }; 658 | 659 | } 660 | 661 | } --------------------------------------------------------------------------------