├── settings.gradle ├── images ├── banner.png ├── expand_adapt.gif ├── radial_adapt.gif ├── expand_open_close.gif └── radial_open_close.gif ├── floatingmenu ├── src │ └── main │ │ ├── res │ │ ├── values │ │ │ ├── strings.xml │ │ │ └── attrs.xml │ │ ├── drawable-hdpi │ │ │ └── defaultimage.png │ │ ├── drawable-mdpi │ │ │ └── defaultimage.png │ │ ├── drawable-xhdpi │ │ │ └── defaultimage.png │ │ ├── drawable-xxhdpi │ │ │ └── defaultimage.png │ │ └── drawable-xxxhdpi │ │ │ └── defaultimage.png │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── rjsv │ │ └── floatingmenu │ │ ├── animation │ │ ├── enumerators │ │ │ ├── MenuState.java │ │ │ └── AnimationType.java │ │ ├── listeners │ │ │ └── FloatingMenuButtonAnimationListener.java │ │ └── handlers │ │ │ ├── AnimationHandler.java │ │ │ └── FloatingMenuAnimationHandler.java │ │ └── floatingmenubutton │ │ ├── MovementStyle.java │ │ ├── subbutton │ │ ├── FloatingSubButton.java │ │ └── SubButton.java │ │ ├── listeners │ │ ├── FloatingMenuStateChangeListener.java │ │ ├── FloatingMenuButtonClickListener.java │ │ └── SubButtonViewQueueListener.java │ │ ├── general │ │ └── Utils.java │ │ └── FloatingMenuButton.java └── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── sample ├── src │ └── main │ │ ├── res │ │ ├── values │ │ │ ├── strings.xml │ │ │ ├── colors.xml │ │ │ └── values.xml │ │ ├── drawable-hdpi │ │ │ ├── five.png │ │ │ ├── four.png │ │ │ ├── one.png │ │ │ ├── two.png │ │ │ ├── center.png │ │ │ └── three.png │ │ ├── drawable-mdpi │ │ │ ├── five.png │ │ │ ├── four.png │ │ │ ├── one.png │ │ │ ├── two.png │ │ │ ├── center.png │ │ │ └── three.png │ │ ├── drawable-xhdpi │ │ │ ├── one.png │ │ │ ├── two.png │ │ │ ├── center.png │ │ │ ├── five.png │ │ │ ├── four.png │ │ │ └── three.png │ │ ├── drawable-xxhdpi │ │ │ ├── five.png │ │ │ ├── four.png │ │ │ ├── one.png │ │ │ ├── three.png │ │ │ ├── two.png │ │ │ └── center.png │ │ ├── drawable-xxxhdpi │ │ │ ├── five.png │ │ │ ├── four.png │ │ │ ├── one.png │ │ │ ├── two.png │ │ │ ├── center.png │ │ │ └── three.png │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ └── layout │ │ │ └── activity_main.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── rjsvieira │ │ └── floatingmenu │ │ └── sample │ │ └── MainActivity.java └── build.gradle ├── .gitignore ├── gradlew.bat ├── gradlew ├── README.md └── LICENSE /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':floatingmenu' 2 | include ':sample' 3 | -------------------------------------------------------------------------------- /images/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjsvieira/floatingMenu/HEAD/images/banner.png -------------------------------------------------------------------------------- /images/expand_adapt.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjsvieira/floatingMenu/HEAD/images/expand_adapt.gif -------------------------------------------------------------------------------- /images/radial_adapt.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjsvieira/floatingMenu/HEAD/images/radial_adapt.gif -------------------------------------------------------------------------------- /images/expand_open_close.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjsvieira/floatingMenu/HEAD/images/expand_open_close.gif -------------------------------------------------------------------------------- /images/radial_open_close.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjsvieira/floatingMenu/HEAD/images/radial_open_close.gif -------------------------------------------------------------------------------- /floatingmenu/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | FloatingMenu 3 | 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjsvieira/floatingMenu/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /sample/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | FloatingMenuSample 3 | 4 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/five.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjsvieira/floatingMenu/HEAD/sample/src/main/res/drawable-hdpi/five.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/four.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjsvieira/floatingMenu/HEAD/sample/src/main/res/drawable-hdpi/four.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/one.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjsvieira/floatingMenu/HEAD/sample/src/main/res/drawable-hdpi/one.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/two.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjsvieira/floatingMenu/HEAD/sample/src/main/res/drawable-hdpi/two.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-mdpi/five.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjsvieira/floatingMenu/HEAD/sample/src/main/res/drawable-mdpi/five.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-mdpi/four.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjsvieira/floatingMenu/HEAD/sample/src/main/res/drawable-mdpi/four.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-mdpi/one.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjsvieira/floatingMenu/HEAD/sample/src/main/res/drawable-mdpi/one.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-mdpi/two.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjsvieira/floatingMenu/HEAD/sample/src/main/res/drawable-mdpi/two.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/one.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjsvieira/floatingMenu/HEAD/sample/src/main/res/drawable-xhdpi/one.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/two.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjsvieira/floatingMenu/HEAD/sample/src/main/res/drawable-xhdpi/two.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/center.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjsvieira/floatingMenu/HEAD/sample/src/main/res/drawable-hdpi/center.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/three.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjsvieira/floatingMenu/HEAD/sample/src/main/res/drawable-hdpi/three.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-mdpi/center.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjsvieira/floatingMenu/HEAD/sample/src/main/res/drawable-mdpi/center.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-mdpi/three.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjsvieira/floatingMenu/HEAD/sample/src/main/res/drawable-mdpi/three.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/center.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjsvieira/floatingMenu/HEAD/sample/src/main/res/drawable-xhdpi/center.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/five.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjsvieira/floatingMenu/HEAD/sample/src/main/res/drawable-xhdpi/five.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/four.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjsvieira/floatingMenu/HEAD/sample/src/main/res/drawable-xhdpi/four.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/three.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjsvieira/floatingMenu/HEAD/sample/src/main/res/drawable-xhdpi/three.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/five.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjsvieira/floatingMenu/HEAD/sample/src/main/res/drawable-xxhdpi/five.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/four.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjsvieira/floatingMenu/HEAD/sample/src/main/res/drawable-xxhdpi/four.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/one.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjsvieira/floatingMenu/HEAD/sample/src/main/res/drawable-xxhdpi/one.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/three.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjsvieira/floatingMenu/HEAD/sample/src/main/res/drawable-xxhdpi/three.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/two.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjsvieira/floatingMenu/HEAD/sample/src/main/res/drawable-xxhdpi/two.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/five.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjsvieira/floatingMenu/HEAD/sample/src/main/res/drawable-xxxhdpi/five.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/four.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjsvieira/floatingMenu/HEAD/sample/src/main/res/drawable-xxxhdpi/four.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/one.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjsvieira/floatingMenu/HEAD/sample/src/main/res/drawable-xxxhdpi/one.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/two.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjsvieira/floatingMenu/HEAD/sample/src/main/res/drawable-xxxhdpi/two.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/center.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjsvieira/floatingMenu/HEAD/sample/src/main/res/drawable-xxhdpi/center.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/center.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjsvieira/floatingMenu/HEAD/sample/src/main/res/drawable-xxxhdpi/center.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/three.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjsvieira/floatingMenu/HEAD/sample/src/main/res/drawable-xxxhdpi/three.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjsvieira/floatingMenu/HEAD/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjsvieira/floatingMenu/HEAD/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /floatingmenu/src/main/res/drawable-hdpi/defaultimage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjsvieira/floatingMenu/HEAD/floatingmenu/src/main/res/drawable-hdpi/defaultimage.png -------------------------------------------------------------------------------- /floatingmenu/src/main/res/drawable-mdpi/defaultimage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjsvieira/floatingMenu/HEAD/floatingmenu/src/main/res/drawable-mdpi/defaultimage.png -------------------------------------------------------------------------------- /floatingmenu/src/main/res/drawable-xhdpi/defaultimage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjsvieira/floatingMenu/HEAD/floatingmenu/src/main/res/drawable-xhdpi/defaultimage.png -------------------------------------------------------------------------------- /floatingmenu/src/main/res/drawable-xxhdpi/defaultimage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjsvieira/floatingMenu/HEAD/floatingmenu/src/main/res/drawable-xxhdpi/defaultimage.png -------------------------------------------------------------------------------- /floatingmenu/src/main/res/drawable-xxxhdpi/defaultimage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjsvieira/floatingMenu/HEAD/floatingmenu/src/main/res/drawable-xxxhdpi/defaultimage.png -------------------------------------------------------------------------------- /sample/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu May 04 09:46:29 BST 2017 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-3.3-all.zip 7 | -------------------------------------------------------------------------------- /floatingmenu/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /floatingmenu/src/main/java/rjsv/floatingmenu/animation/enumerators/MenuState.java: -------------------------------------------------------------------------------- 1 | package rjsv.floatingmenu.animation.enumerators; 2 | 3 | /** 4 | * Description 5 | * 6 | * @author RJSV 7 | * @version $Revision : 1 $ 8 | */ 9 | 10 | public enum MenuState { 11 | IDLE, OPENING, OPENING_RADIAL, CLOSING, CLOSING_RADIAL, REOPENING 12 | } -------------------------------------------------------------------------------- /sample/src/main/res/values/values.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /floatingmenu/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Local configuration file (sdk path, etc) 2 | local.properties 3 | 4 | # Gradle generated files 5 | .gradle/* 6 | 7 | build/* 8 | .idea/* 9 | *.iml 10 | floatingmenu/build/* 11 | floatingmenu/floatingmenu.iml 12 | 13 | # Local configuration file (sdk path, etc) 14 | local.properties 15 | gradle.properties 16 | 17 | # Signing files 18 | .signing/ 19 | 20 | # OS-specific files 21 | .DS_Store 22 | .DS_Store? 23 | ._* 24 | .Spotlight-V100 25 | .Trashes 26 | ehthumbs.db 27 | Thumbs.db -------------------------------------------------------------------------------- /floatingmenu/src/main/java/rjsv/floatingmenu/floatingmenubutton/MovementStyle.java: -------------------------------------------------------------------------------- 1 | package rjsv.floatingmenu.floatingmenubutton; 2 | 3 | /** 4 | * Created by Junior Damacena on 01/03/18. 5 | */ 6 | public enum MovementStyle { 7 | /** 8 | * The button cannot be dragged 9 | */ 10 | ANCHORED, 11 | /** 12 | * The button can only be placed at the sides of the screen 13 | */ 14 | STICKED_TO_SIDES, 15 | /** 16 | * The button can be placed anywhere 17 | */ 18 | FREE 19 | } 20 | -------------------------------------------------------------------------------- /floatingmenu/src/main/java/rjsv/floatingmenu/floatingmenubutton/subbutton/FloatingSubButton.java: -------------------------------------------------------------------------------- 1 | package rjsv.floatingmenu.floatingmenubutton.subbutton; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.widget.FrameLayout; 6 | 7 | public class FloatingSubButton extends FrameLayout { 8 | 9 | public FloatingSubButton(Context context) { 10 | this(context, null); 11 | } 12 | 13 | public FloatingSubButton(Context context, AttributeSet attr) { 14 | super(context, attr); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /floatingmenu/src/main/java/rjsv/floatingmenu/floatingmenubutton/listeners/FloatingMenuStateChangeListener.java: -------------------------------------------------------------------------------- 1 | package rjsv.floatingmenu.floatingmenubutton.listeners; 2 | 3 | import rjsv.floatingmenu.floatingmenubutton.FloatingMenuButton; 4 | 5 | /** 6 | * Description 7 | * 8 | * @author RJSV 9 | * @version $Revision : 1 $ 10 | */ 11 | 12 | public interface FloatingMenuStateChangeListener { 13 | 14 | void onMenuOpened(FloatingMenuButton menu); 15 | 16 | void onMenuClosed(FloatingMenuButton menu); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /floatingmenu/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'com.github.dcendents.android-maven' 3 | 4 | android { 5 | compileSdkVersion 23 6 | buildToolsVersion "23.0.2" 7 | 8 | defaultConfig { 9 | minSdkVersion 15 10 | targetSdkVersion 21 11 | versionCode 1 12 | versionName "1.0.0" 13 | 14 | } 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | } 22 | 23 | dependencies { 24 | compile fileTree(dir: 'libs', include: ['*.jar']) 25 | } 26 | -------------------------------------------------------------------------------- /sample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /sample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.3" 6 | 7 | defaultConfig { 8 | minSdkVersion 15 9 | targetSdkVersion 21 10 | versionCode 1 11 | versionName "1.0.0" 12 | 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 | compile 'com.android.support:appcompat-v7:25.3.1' 25 | compile 'com.android.support.constraint:constraint-layout:1.0.0-beta4' 26 | compile project(':floatingmenu') 27 | } 28 | -------------------------------------------------------------------------------- /floatingmenu/src/main/java/rjsv/floatingmenu/animation/enumerators/AnimationType.java: -------------------------------------------------------------------------------- 1 | package rjsv.floatingmenu.animation.enumerators; 2 | 3 | /** 4 | * Description 5 | * 6 | * @author RJSV 7 | * @version $Revision : 1 $ 8 | */ 9 | 10 | public enum AnimationType { 11 | 12 | EXPAND("expand"), 13 | RADIAL("radial"); 14 | 15 | private final String type; 16 | 17 | AnimationType(String type) { 18 | this.type = type; 19 | } 20 | 21 | public static AnimationType match(String type) { 22 | if (type != null) { 23 | switch (type) { 24 | case "radial": 25 | return RADIAL; 26 | case "expand": 27 | default: 28 | return EXPAND; 29 | } 30 | } 31 | return EXPAND; 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /floatingmenu/src/main/java/rjsv/floatingmenu/floatingmenubutton/listeners/FloatingMenuButtonClickListener.java: -------------------------------------------------------------------------------- 1 | package rjsv.floatingmenu.floatingmenubutton.listeners; 2 | 3 | import android.view.View; 4 | 5 | import java.util.ArrayList; 6 | 7 | /** 8 | * Description 9 | * 10 | * @author RJSV 11 | * @version $Revision : 1 $ 12 | */ 13 | 14 | public class FloatingMenuButtonClickListener implements View.OnClickListener { 15 | 16 | private ArrayList listeners; 17 | 18 | public FloatingMenuButtonClickListener() { 19 | this.listeners = new ArrayList<>(); 20 | } 21 | 22 | public void addClickListener(View.OnClickListener listener) { 23 | if (listener != null) { 24 | this.listeners.add(listener); 25 | } else { 26 | this.listeners.clear(); 27 | } 28 | } 29 | 30 | @Override 31 | public void onClick(View v) { 32 | if (this.listeners != null) { 33 | for (View.OnClickListener listener : listeners) { 34 | listener.onClick(v); 35 | } 36 | } 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /floatingmenu/src/main/java/rjsv/floatingmenu/animation/listeners/FloatingMenuButtonAnimationListener.java: -------------------------------------------------------------------------------- 1 | package rjsv.floatingmenu.animation.listeners; 2 | 3 | import android.animation.Animator; 4 | 5 | import rjsv.floatingmenu.floatingmenubutton.subbutton.SubButton; 6 | import rjsv.floatingmenu.animation.handlers.AnimationHandler; 7 | import rjsv.floatingmenu.animation.enumerators.MenuState; 8 | 9 | /** 10 | * Description 11 | * 12 | * @author RJSV 13 | * @version $Revision : 1 $ 14 | */ 15 | 16 | public class FloatingMenuButtonAnimationListener implements Animator.AnimatorListener { 17 | 18 | private SubButton subActionItem; 19 | private MenuState actionType; 20 | private AnimationHandler animationHandler; 21 | 22 | public FloatingMenuButtonAnimationListener(AnimationHandler animationHandler, SubButton subActionItem, MenuState actionType) { 23 | this.subActionItem = subActionItem; 24 | this.actionType = actionType; 25 | this.animationHandler = animationHandler; 26 | } 27 | 28 | @Override 29 | public void onAnimationStart(Animator animation) { 30 | 31 | } 32 | 33 | @Override 34 | public void onAnimationEnd(Animator animation) { 35 | animationHandler.restoreSubActionViewAfterAnimation(subActionItem, actionType); 36 | } 37 | 38 | @Override 39 | public void onAnimationCancel(Animator animation) { 40 | animationHandler.restoreSubActionViewAfterAnimation(subActionItem, actionType); 41 | } 42 | 43 | @Override 44 | public void onAnimationRepeat(Animator animation) { 45 | } 46 | } -------------------------------------------------------------------------------- /floatingmenu/src/main/java/rjsv/floatingmenu/floatingmenubutton/listeners/SubButtonViewQueueListener.java: -------------------------------------------------------------------------------- 1 | package rjsv.floatingmenu.floatingmenubutton.listeners; 2 | 3 | import rjsv.floatingmenu.floatingmenubutton.FloatingMenuButton; 4 | import rjsv.floatingmenu.floatingmenubutton.subbutton.SubButton; 5 | 6 | /** 7 | * Description 8 | * 9 | * @author RJSV 10 | * @version $Revision : 1 $ 11 | */ 12 | 13 | public class SubButtonViewQueueListener implements Runnable { 14 | 15 | private static final int MAX_TRIES = 10; 16 | private int tries; 17 | private SubButton button; 18 | private FloatingMenuButton floatingActionMenu; 19 | 20 | public SubButtonViewQueueListener(FloatingMenuButton floatingActionMenu, SubButton button) { 21 | this.floatingActionMenu = floatingActionMenu; 22 | this.button = button; 23 | this.tries = 0; 24 | } 25 | 26 | @Override 27 | public void run() { 28 | // Wait until the the view can be measured but do not push too hard. 29 | if (button.getView().getMeasuredWidth() == 0 && tries < MAX_TRIES) { 30 | button.getView().post(this); 31 | return; 32 | } 33 | // Measure the size of the button view 34 | button.setWidth(button.getView().getMeasuredWidth()); 35 | button.setHeight(button.getView().getMeasuredHeight()); 36 | // Revert everything back to normal 37 | button.setAlpha(button.getAlpha()); 38 | // Remove the button view from view hierarchy 39 | floatingActionMenu.removeViewFromCurrentContainer(button.getView()); 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /floatingmenu/src/main/java/rjsv/floatingmenu/floatingmenubutton/subbutton/SubButton.java: -------------------------------------------------------------------------------- 1 | package rjsv.floatingmenu.floatingmenubutton.subbutton; 2 | 3 | import android.view.View; 4 | 5 | /** 6 | * Description 7 | * 8 | * @author RJSV 9 | * @version $Revision : 1 $ 10 | */ 11 | 12 | public class SubButton { 13 | 14 | private int x; 15 | private int y; 16 | private int width; 17 | private int height; 18 | private float alpha; 19 | private View view; 20 | 21 | public SubButton(View view, int width, int height) { 22 | this.view = view; 23 | this.width = width; 24 | this.height = height; 25 | this.alpha = view.getAlpha(); 26 | this.x = 0; 27 | this.y = 0; 28 | } 29 | 30 | public float getAlpha() { 31 | return alpha; 32 | } 33 | 34 | public void setAlpha(float alpha) { 35 | this.alpha = alpha; 36 | this.view.setAlpha(alpha); 37 | } 38 | 39 | public int getHeight() { 40 | return height; 41 | } 42 | 43 | public void setHeight(int height) { 44 | this.height = height; 45 | } 46 | 47 | public View getView() { 48 | return view; 49 | } 50 | 51 | public void setView(View view) { 52 | this.view = view; 53 | } 54 | 55 | public int getWidth() { 56 | return width; 57 | } 58 | 59 | public void setWidth(int width) { 60 | this.width = width; 61 | } 62 | 63 | public int getX() { 64 | return x; 65 | } 66 | 67 | public void setX(int x) { 68 | this.x = x; 69 | } 70 | 71 | public int getY() { 72 | return y; 73 | } 74 | 75 | public void setY(int y) { 76 | this.y = y; 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /sample/src/main/java/com/rjsvieira/floatingmenu/sample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.rjsvieira.floatingmenu.sample; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.support.v4.view.animation.FastOutLinearInInterpolator; 6 | import android.support.v4.view.animation.FastOutSlowInInterpolator; 7 | 8 | import rjsv.floatingmenu.animation.enumerators.AnimationType; 9 | import rjsv.floatingmenu.floatingmenubutton.FloatingMenuButton; 10 | import rjsv.floatingmenu.floatingmenubutton.MovementStyle; 11 | 12 | public class MainActivity extends Activity { 13 | 14 | FloatingMenuButton fab_1; 15 | FloatingMenuButton fab_2; 16 | 17 | @Override 18 | protected void onCreate(Bundle savedInstanceState) { 19 | super.onCreate(savedInstanceState); 20 | setContentView(R.layout.activity_main); 21 | initUi(); 22 | } 23 | 24 | private void initUi() { 25 | fab_1 = (FloatingMenuButton) findViewById(R.id.fab_1); 26 | fab_1.setStartAngle(0) 27 | .setEndAngle(360) 28 | .setRadius(200) 29 | .setAnimationType(AnimationType.EXPAND) 30 | .setMovementStyle(MovementStyle.STICKED_TO_SIDES); 31 | 32 | fab_1.getAnimationHandler() 33 | .setOpeningAnimationDuration(500) 34 | .setClosingAnimationDuration(200) 35 | .setLagBetweenItems(0) 36 | .setOpeningInterpolator(new FastOutSlowInInterpolator()) 37 | .setClosingInterpolator(new FastOutLinearInInterpolator()) 38 | .shouldFade(true) 39 | .shouldScale(true) 40 | .shouldRotate(false); 41 | 42 | fab_2 = (FloatingMenuButton) findViewById(R.id.fab_2); 43 | fab_2.setStartAngle(0) 44 | .setEndAngle(360) 45 | .setTransparentAfterMilliseconds(0) 46 | .setAnimationType(AnimationType.RADIAL) 47 | .setMovementStyle(MovementStyle.FREE); 48 | 49 | fab_2.getAnimationHandler() 50 | .setOpeningAnimationDuration(500) 51 | .setClosingAnimationDuration(200) 52 | .setLagBetweenItems(0) 53 | .setOpeningInterpolator(new FastOutSlowInInterpolator()) 54 | .setClosingInterpolator(new FastOutLinearInInterpolator()) 55 | .shouldFade(true) 56 | .shouldScale(true) 57 | .shouldRotate(false); 58 | 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /floatingmenu/src/main/java/rjsv/floatingmenu/animation/handlers/AnimationHandler.java: -------------------------------------------------------------------------------- 1 | package rjsv.floatingmenu.animation.handlers; 2 | 3 | import android.graphics.Point; 4 | import android.view.View; 5 | import android.view.ViewGroup; 6 | import android.widget.FrameLayout; 7 | 8 | import rjsv.floatingmenu.animation.enumerators.AnimationType; 9 | import rjsv.floatingmenu.animation.enumerators.MenuState; 10 | import rjsv.floatingmenu.floatingmenubutton.FloatingMenuButton; 11 | import rjsv.floatingmenu.floatingmenubutton.subbutton.SubButton; 12 | 13 | /** 14 | * Description 15 | * 16 | * @author RJSV 17 | * @version $Revision : 1 $ 18 | */ 19 | 20 | public abstract class AnimationHandler { 21 | 22 | protected FloatingMenuButton menu; 23 | 24 | public void setMenu(FloatingMenuButton menu) { 25 | this.menu = menu; 26 | } 27 | 28 | public void animateMenuOpening(Point center, AnimationType animationType) { 29 | } 30 | 31 | public void animateMenuClosing(Point center, AnimationType animationType) { 32 | } 33 | 34 | public void animateMenuReOpening(Point center) { 35 | } 36 | 37 | public void restoreSubActionViewAfterAnimation(SubButton subActionItem, MenuState actionType) { 38 | ViewGroup.LayoutParams params = subActionItem.getView().getLayoutParams(); 39 | View subActionItemView = subActionItem.getView(); 40 | int openFactor = (actionType == MenuState.OPENING || actionType == MenuState.OPENING_RADIAL) ? 1 : (actionType == MenuState.CLOSING || actionType == MenuState.CLOSING_RADIAL ? 0 : -1); 41 | subActionItemView.setTranslationX(0); 42 | subActionItemView.setTranslationY(0); 43 | subActionItemView.setRotation(0); 44 | subActionItemView.setScaleX(openFactor); 45 | subActionItemView.setScaleY(openFactor); 46 | subActionItemView.setAlpha(openFactor); 47 | if (openFactor > 0) { // open 48 | FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) params; 49 | lp.setMargins(subActionItem.getX(), subActionItem.getY(), 0, 0); 50 | subActionItemView.setLayoutParams(lp); 51 | } else { // close 52 | Point center = menu.getActionViewCenter(); 53 | FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) params; 54 | int x = center.x - subActionItem.getWidth() / 2; 55 | int y = center.y - subActionItem.getHeight() / 2; 56 | lp.setMargins(x, y, 0, 0); 57 | subActionItemView.setLayoutParams(lp); 58 | menu.removeViewFromCurrentContainer(subActionItemView); 59 | } 60 | } 61 | 62 | public abstract boolean isAnimating(); 63 | 64 | public abstract void setAnimating(boolean animating); 65 | 66 | } 67 | -------------------------------------------------------------------------------- /floatingmenu/src/main/java/rjsv/floatingmenu/floatingmenubutton/general/Utils.java: -------------------------------------------------------------------------------- 1 | package rjsv.floatingmenu.floatingmenubutton.general; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.res.Resources; 6 | import android.graphics.Point; 7 | import android.util.DisplayMetrics; 8 | import android.util.Log; 9 | import android.view.Display; 10 | import android.view.View; 11 | import android.view.WindowManager; 12 | 13 | import rjsv.floatingmenu.floatingmenubutton.FloatingMenuButton; 14 | 15 | /** 16 | * Description 17 | * 18 | * @author RJSV 19 | * @version $Revision : 1 $ 20 | */ 21 | 22 | public class Utils { 23 | 24 | // Click Utility 25 | public static boolean isAClick(int clickThreshold, float startX, float endX, float startY, float endY) { 26 | float differenceX = Math.abs(startX - endX); 27 | float differenceY = Math.abs(startY - endY); 28 | return !(differenceX > clickThreshold || differenceY > clickThreshold); 29 | } 30 | 31 | // Display Utilities 32 | public static Point getDisplayDimensions(Context context) { 33 | WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 34 | Display display = wm.getDefaultDisplay(); 35 | 36 | DisplayMetrics metrics = new DisplayMetrics(); 37 | display.getMetrics(metrics); 38 | int screenWidth = metrics.widthPixels; 39 | int screenHeight = metrics.heightPixels; 40 | 41 | // find out if status bar has already been subtracted from screenHeight 42 | display.getMetrics(metrics); 43 | int physicalHeight = metrics.heightPixels; 44 | int statusBarHeight = getStatusBarHeight(context); 45 | int navigationBarHeight = getNavigationBarHeight(context); 46 | int heightDelta = physicalHeight - screenHeight; 47 | if (heightDelta == 0 || heightDelta == navigationBarHeight) { 48 | screenHeight -= statusBarHeight; 49 | } 50 | 51 | return new Point(screenWidth, screenHeight); 52 | } 53 | 54 | public static int getStatusBarHeight(Context context) { 55 | Resources resources = context.getResources(); 56 | int resourceId = resources.getIdentifier("status_bar_height", "dimen", "android"); 57 | return (resourceId > 0) ? resources.getDimensionPixelSize(resourceId) : 0; 58 | } 59 | 60 | public static int getNavigationBarHeight(Context context) { 61 | Resources resources = context.getResources(); 62 | int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android"); 63 | return (resourceId > 0) ? resources.getDimensionPixelSize(resourceId) : 0; 64 | } 65 | 66 | public static WindowManager getWindowManager(Context context) { 67 | return (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 68 | } 69 | 70 | public static Point getScreenSize(Context context) { 71 | Point size = new Point(); 72 | getWindowManager(context).getDefaultDisplay().getSize(size); 73 | return size; 74 | } 75 | 76 | public static View getActivityContentView(Context context) { 77 | try { 78 | return ((Activity) context).getWindow().getDecorView().findViewById(android.R.id.content); 79 | } catch (ClassCastException e) { 80 | Log.e(FloatingMenuButton.class.getName(), e.getMessage()); 81 | return null; 82 | } 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 17 | 18 | 24 | 25 | 37 | 38 | 43 | 44 | 49 | 50 | 55 | 56 | 61 | 62 | 67 | 68 | 69 | 70 | 86 | 87 | 92 | 93 | 98 | 99 | 104 | 105 | 110 | 111 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FloatingMenu 2 | 3 | [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-FloatingMenu-brightgreen.svg?style=flat)](https://android-arsenal.com/details/1/5697) 4 | ![Current Version](https://img.shields.io/badge/Current%20Version-1.3.0-brightgreen.svg) 5 | [![](https://jitpack.io/v/rjsvieira/floatingMenu.svg)](https://jitpack.io/#rjsvieira/floatingMenu) 6 | ![Minimum SDK](https://img.shields.io/badge/minSdkVersion%20-15-blue.svg) 7 | 8 | 9 | 10 | 11 | 12 | 13 |

Include in your project

14 | 15 |

In your root/build.gradle

16 | 17 | ```groovy 18 | allprojects { 19 | repositories { 20 | ... 21 | maven { url 'https://jitpack.io' } 22 | } 23 | } 24 | ``` 25 | 26 |

In your app/build.gradle

27 | 28 | ```groovy 29 | dependencies { 30 | compile 'com.github.rjsvieira:floatingMenu:1.3.0' 31 | } 32 | ``` 33 | 34 | 35 |

Customization

36 | 37 | The FloatingMenuButton central image allows the user to give it any appearance he desires.
38 | The user can also specify a ClickListener and add it to it. Thanks to the FloatingMenuButton's composite clickListener, it will not override the animation toggling.

39 | The FloatingMenuButton only accepts FloatingSubButton children.
40 | Like the FloatingMenuButton, the FloatingSubButton can be configured to have a specific background and a ClickListener for interaction. 41 | The following XML file specifies the example on the radial gifs : 42 |
43 |
44 | 45 | ```xml 46 | 59 | 60 | 65 | 66 | 71 | 72 | 77 | 78 | 83 | 84 | 89 | 90 | 91 | ``` 92 | 93 | 94 |

Adding a FloatingSubButton programmatically

95 | 96 | You can add a FloatingSubButton programmatically using the following lines of code. 97 | 98 | ```java 99 | FloatingSubButton floatingSubButton = new FloatingSubButton(this); // create the button 100 | floatingSubButton.setBackground(getDrawable(R.drawable.four)); // specify a custom background 101 | ``` 102 | And add it to the parent FloatingButton by either specifying layout parameters or assuming default parameters (as default, the layoutParameters for the first button on the list will be considered) 103 | ```java 104 | floatingButton.addFloatingSubButton(floatingSubButton, customLayoutParameters); 105 | ``` 106 | or 107 | ```java 108 | floatingButton.addFloatingSubButton(floatingSubButton); 109 | ``` 110 | 111 | 112 |

Attribute configuration list

113 | 114 | | Attribute | Type | Default | Default | 115 | | ------------- |:-------------:| :-----| :------ | 116 | | startAngle | int | The starting angle for button disposition | 0 | 117 | | endAngle | int | The ending angle for button disposition | 180 | 118 | | radius | int | The distance between the central button and its children | 100(dp) | 119 | | movementStyle | MovementStyle (Enumerator) | Configures whether the user can or not drag the FloatingMenu around | MovementStyle.FREE | 120 | | animationType | AnimationType (Enumerator) | The open/close animation for FloatingMenuButton | AnimationType.EXPAND | 121 | | openingDuration | int | The opening duration, in milliseconds, of the animation | 500 | 122 | | closingDuration | int | The closing duration, in milliseconds, of the animation | 500 | 123 | | lagBetweenItems | int | The lag duration between animating items. Affects only AnimationType.EXPAND | 100 | 124 | | openingInterpolator | Interpolator | The opening interpolator. Allows different rythms on the animations| OvershootInterpolator | 125 | | closingInterpolator | Interpolator | The closing interpolator. Allows different rythms on the animations | AccelerateDecelerateInterpolator | 126 | | shouldRotate | boolean | Specify whether the items should rotate while animating. Available only in AnimationType.EXPAND | true | 127 | | shouldFade | boolean | Specify whether the items should fade while animating. Available only in AnimationType.EXPAND | true | 128 | | shouldScale | boolean | Specify whether the items should scale while animating. Available only in AnimationType.EXPAND | true | 129 | 130 |

FloatingMenuButton State Change Listener

131 | 132 | You can track whether the FloatingMenuButton is in open or closed status 133 | 134 | ```java 135 | public interface FloatingMenuButtonStateChangeListener { 136 | 137 | void onMenuOpened(FloatingMenuButton menu); 138 | 139 | void onMenuClosed(FloatingMenuButton menu); 140 | 141 | } 142 | ``` 143 | 144 |

FloatingMenuButton Animation Handler (Wrapper)

145 | 146 | You can configure the FloatingMenuButton programmatically rather than xml, specifying and settings its variables. 147 | Consider the following example : 148 | 149 | ```java 150 | floatingButton = (FloatingMenuButton) findViewById(R.id.my_floating_button); 151 | floatingButton.setStartAngle(0) 152 | .setEndAngle(360) 153 | .setAnimationType(AnimationType.EXPAND) 154 | .setMovementStyle(MovementStyle.STICKED_TO_SIDES); 155 | floatingButton.getAnimationHandler() 156 | .setOpeningAnimationDuration(500) 157 | .setClosingAnimationDuration(200) 158 | .setLagBetweenItems(0) 159 | .setOpeningInterpolator(new FastOutSlowInInterpolator()) 160 | .setClosingInterpolator(new FastOutLinearInInterpolator()) 161 | .shouldFade(true) 162 | .shouldScale(true) 163 | .shouldRotate(false); 164 | ``` 165 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /floatingmenu/src/main/java/rjsv/floatingmenu/animation/handlers/FloatingMenuAnimationHandler.java: -------------------------------------------------------------------------------- 1 | package rjsv.floatingmenu.animation.handlers; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorSet; 5 | import android.animation.ObjectAnimator; 6 | import android.animation.PropertyValuesHolder; 7 | import android.animation.ValueAnimator; 8 | import android.graphics.Point; 9 | import android.view.View; 10 | import android.view.animation.AccelerateDecelerateInterpolator; 11 | import android.view.animation.Interpolator; 12 | import android.view.animation.OvershootInterpolator; 13 | 14 | import java.util.ArrayList; 15 | import java.util.Collections; 16 | import java.util.List; 17 | 18 | import rjsv.floatingmenu.animation.enumerators.AnimationType; 19 | import rjsv.floatingmenu.animation.enumerators.MenuState; 20 | import rjsv.floatingmenu.animation.listeners.FloatingMenuButtonAnimationListener; 21 | import rjsv.floatingmenu.floatingmenubutton.FloatingMenuButton; 22 | import rjsv.floatingmenu.floatingmenubutton.subbutton.SubButton; 23 | 24 | /** 25 | * Description 26 | * 27 | * @author RJSV 28 | * @version $Revision : 1 $ 29 | */ 30 | 31 | /** 32 | * An example animation handler 33 | * Animates translation, rotation, scale and alpha at the same time using Property Animation APIs. 34 | */ 35 | public class FloatingMenuAnimationHandler extends AnimationHandler implements Animator.AnimatorListener { 36 | 37 | private int openingDuration = 500; 38 | private int closingDuration = 500; 39 | private int lagBetweenItems = 100; 40 | private boolean animating; 41 | private FloatingMenuButtonAnimationListener menuButtonAnimationListener; 42 | private Interpolator openingInterpolator; 43 | private Interpolator closingInterpolator; 44 | private boolean shouldRotate = true; 45 | private boolean shouldFade = true; 46 | private boolean shouldScale = true; 47 | private FloatingMenuButton floatingMenuButton; 48 | private MenuState currentState; 49 | private AnimatorSet openingAnimation; 50 | private AnimatorSet closingAnimation; 51 | 52 | // Constructors 53 | public FloatingMenuAnimationHandler(FloatingMenuButton floatingMenuButton) { 54 | setAnimating(false); 55 | this.floatingMenuButton = floatingMenuButton; 56 | this.openingInterpolator = new OvershootInterpolator(0.9f); 57 | this.closingInterpolator = new AccelerateDecelerateInterpolator(); 58 | } 59 | 60 | // Overridden Methods 61 | @Override 62 | public void animateMenuOpening(Point center, AnimationType animationType) { 63 | super.animateMenuOpening(center, animationType); 64 | currentState = animationType == AnimationType.RADIAL ? MenuState.OPENING_RADIAL : MenuState.OPENING; 65 | openingAnimation = currentState == MenuState.OPENING_RADIAL ? openRadialMenuAnimation(center) : openMenuAnimation(center); 66 | openingAnimation.start(); 67 | } 68 | 69 | @Override 70 | public void animateMenuClosing(Point center, AnimationType animationType) { 71 | super.animateMenuClosing(center, animationType); 72 | currentState = animationType == AnimationType.RADIAL ? MenuState.CLOSING_RADIAL : MenuState.CLOSING; 73 | closingAnimation = currentState == MenuState.CLOSING_RADIAL ? closeRadialMenuAnimation(center) : closeMenuAnimation(center); 74 | closingAnimation.start(); 75 | } 76 | 77 | @Override 78 | public void animateMenuReOpening(Point center) { 79 | super.animateMenuReOpening(center); 80 | animateMenuClosing(center, AnimationType.EXPAND); 81 | currentState = MenuState.REOPENING; 82 | } 83 | 84 | @Override 85 | public boolean isAnimating() { 86 | return animating; 87 | } 88 | 89 | @Override 90 | public void setAnimating(boolean animating) { 91 | this.animating = animating; 92 | if (!this.animating && currentState != MenuState.REOPENING) { 93 | currentState = MenuState.IDLE; 94 | } 95 | } 96 | 97 | // Expand animation 98 | private AnimatorSet openMenuAnimation(Point center) { 99 | setAnimating(true); 100 | Animator lastAnimation = null; 101 | List subActionItems = menu.getSubMenuButtons(); 102 | ArrayList animatorArrayList = new ArrayList<>(); 103 | for (int i = 0; i < subActionItems.size(); i++) { 104 | SubButton currentSubButton = subActionItems.get(i); 105 | ArrayList properties = new ArrayList<>(); 106 | properties.add(PropertyValuesHolder.ofFloat(View.TRANSLATION_X, currentSubButton.getX() - center.x + currentSubButton.getWidth() / 2)); 107 | properties.add(PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, currentSubButton.getY() - center.y + currentSubButton.getHeight() / 2)); 108 | if (shouldRotate) { 109 | properties.add(PropertyValuesHolder.ofFloat(View.ROTATION, 720)); 110 | } 111 | if (shouldScale) { 112 | properties.add(PropertyValuesHolder.ofFloat(View.SCALE_X, 1)); 113 | properties.add(PropertyValuesHolder.ofFloat(View.SCALE_Y, 1)); 114 | } 115 | if (shouldFade) { 116 | properties.add(PropertyValuesHolder.ofFloat(View.ALPHA, 1)); 117 | } 118 | PropertyValuesHolder[] parameters = new PropertyValuesHolder[properties.size() - 1]; 119 | parameters = properties.toArray(parameters); 120 | final ObjectAnimator animation = ObjectAnimator.ofPropertyValuesHolder(currentSubButton.getView(), parameters); 121 | animation.setDuration(openingDuration); 122 | animation.setInterpolator(openingInterpolator); 123 | menuButtonAnimationListener = new FloatingMenuButtonAnimationListener(FloatingMenuAnimationHandler.this, currentSubButton, MenuState.OPENING); 124 | animation.addListener(menuButtonAnimationListener); 125 | if (i == 0) { 126 | lastAnimation = animation; 127 | } 128 | animation.setStartDelay((subActionItems.size() - i) * lagBetweenItems); 129 | animatorArrayList.add(animation); 130 | } 131 | if (lastAnimation != null) { 132 | lastAnimation.addListener(this); 133 | } 134 | AnimatorSet openAnimatorSet = new AnimatorSet(); 135 | openAnimatorSet.playTogether(animatorArrayList); 136 | return openAnimatorSet; 137 | } 138 | 139 | private AnimatorSet closeMenuAnimation(Point center) { 140 | setAnimating(true); 141 | Animator lastAnimation = null; 142 | ArrayList animatorArrayList = new ArrayList<>(); 143 | // We reverse the list to have the animation was intended 144 | ArrayList reverseSubActionItems = new ArrayList<>(menu.getSubMenuButtons()); 145 | Collections.reverse(reverseSubActionItems); 146 | for (int i = 0; i < reverseSubActionItems.size(); i++) { 147 | SubButton currentSubButton = reverseSubActionItems.get(i); 148 | ArrayList properties = new ArrayList<>(); 149 | properties.add(PropertyValuesHolder.ofFloat(View.TRANSLATION_X, -(currentSubButton.getX() - center.x + currentSubButton.getWidth() / 2))); 150 | properties.add(PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, -(currentSubButton.getY() - center.y + currentSubButton.getHeight() / 2))); 151 | if (shouldRotate) { 152 | properties.add(PropertyValuesHolder.ofFloat(View.ROTATION, -720)); 153 | } 154 | if (shouldScale) { 155 | properties.add(PropertyValuesHolder.ofFloat(View.SCALE_X, 0)); 156 | properties.add(PropertyValuesHolder.ofFloat(View.SCALE_Y, 0)); 157 | } 158 | if (shouldFade) { 159 | properties.add(PropertyValuesHolder.ofFloat(View.ALPHA, 0)); 160 | } 161 | PropertyValuesHolder[] parameters = new PropertyValuesHolder[properties.size() - 1]; 162 | parameters = properties.toArray(parameters); 163 | ObjectAnimator animation = ObjectAnimator.ofPropertyValuesHolder(currentSubButton.getView(), parameters); 164 | animation.setDuration(closingDuration); 165 | animation.setInterpolator(closingInterpolator); 166 | menuButtonAnimationListener = new FloatingMenuButtonAnimationListener(FloatingMenuAnimationHandler.this, currentSubButton, MenuState.CLOSING); 167 | animation.addListener(menuButtonAnimationListener); 168 | if (i == 0) { 169 | lastAnimation = animation; 170 | } 171 | animation.setStartDelay((reverseSubActionItems.size() - i) * lagBetweenItems); 172 | animatorArrayList.add(animation); 173 | } 174 | if (lastAnimation != null) { 175 | lastAnimation.addListener(this); 176 | } 177 | AnimatorSet closeAnimatorSet = new AnimatorSet(); 178 | closeAnimatorSet.playTogether(animatorArrayList); 179 | return closeAnimatorSet; 180 | } 181 | 182 | // Radial Animation 183 | private AnimatorSet openRadialMenuAnimation(Point center) { 184 | setAnimating(true); 185 | Animator lastAnimation = null; 186 | int startAngle = floatingMenuButton.getStartAngle(); 187 | int endAngle = floatingMenuButton.getEndAngle(); 188 | int radius = floatingMenuButton.getRadius(); 189 | List subActionItems = menu.getSubMenuButtons(); 190 | ArrayList animatorArrayList = new ArrayList<>(); 191 | for (int i = 0; i < subActionItems.size(); i++) { 192 | final SubButton currentSubButton = subActionItems.get(i); 193 | final View currentSubButtonView = currentSubButton.getView(); 194 | // reset the sub button's properties 195 | resetSubButton(center, currentSubButton, currentSubButtonView); 196 | // Button Properties along the animation 197 | ArrayList properties = new ArrayList<>(); 198 | properties.add(PropertyValuesHolder.ofFloat(View.ROTATION, 720)); 199 | properties.add(PropertyValuesHolder.ofFloat(View.SCALE_X, 1)); 200 | properties.add(PropertyValuesHolder.ofFloat(View.SCALE_Y, 1)); 201 | properties.add(PropertyValuesHolder.ofFloat(View.ALPHA, 1)); 202 | PropertyValuesHolder[] parameters = new PropertyValuesHolder[properties.size() - 1]; 203 | parameters = properties.toArray(parameters); 204 | ObjectAnimator propertiesAnimation = ObjectAnimator.ofPropertyValuesHolder(currentSubButtonView, parameters); 205 | // Button Position along the animation 206 | final ArrayList radialPointsPorCurrentPosition = getArcPointsForButton(center, startAngle, endAngle, subActionItems.size(), i, radius); 207 | ValueAnimator positionAnimation = ValueAnimator.ofFloat(0, radialPointsPorCurrentPosition.size()); 208 | positionAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 209 | @Override 210 | public void onAnimationUpdate(ValueAnimator animation) { 211 | Float value = ((Float) animation.getAnimatedValue()); 212 | if (value.intValue() < radialPointsPorCurrentPosition.size()) { 213 | Point p = radialPointsPorCurrentPosition.get(value.intValue()); 214 | currentSubButtonView.setX(p.x); 215 | currentSubButtonView.setY(p.y); 216 | currentSubButton.setX(p.x); 217 | currentSubButton.setY(p.y); 218 | } 219 | } 220 | }); 221 | positionAnimation.setDuration(openingDuration); 222 | positionAnimation.setInterpolator(openingInterpolator); 223 | menuButtonAnimationListener = new FloatingMenuButtonAnimationListener(FloatingMenuAnimationHandler.this, currentSubButton, MenuState.OPENING_RADIAL); 224 | positionAnimation.addListener(menuButtonAnimationListener); 225 | if (i == 0) { 226 | lastAnimation = positionAnimation; 227 | } 228 | animatorArrayList.add(propertiesAnimation); 229 | animatorArrayList.add(positionAnimation); 230 | } 231 | if (lastAnimation != null) { 232 | lastAnimation.addListener(this); 233 | } 234 | AnimatorSet openRadialAnimatorSet = new AnimatorSet(); 235 | openRadialAnimatorSet.playTogether(animatorArrayList); 236 | return openRadialAnimatorSet; 237 | } 238 | 239 | private AnimatorSet closeRadialMenuAnimation(Point center) { 240 | setAnimating(true); 241 | Animator lastAnimation = null; 242 | int startAngle = floatingMenuButton.getStartAngle(); 243 | int endAngle = floatingMenuButton.getEndAngle(); 244 | int radius = floatingMenuButton.getRadius(); 245 | List subActionItems = menu.getSubMenuButtons(); 246 | ArrayList animatorArrayList = new ArrayList<>(); 247 | for (int i = 0; i < subActionItems.size(); i++) { 248 | final SubButton currentSubButton = subActionItems.get(i); 249 | final View currentSubButtonView = currentSubButton.getView(); 250 | // Button Properties along the animation 251 | ArrayList properties = new ArrayList<>(); 252 | properties.add(PropertyValuesHolder.ofFloat(View.SCALE_X, (float) 0.75)); 253 | properties.add(PropertyValuesHolder.ofFloat(View.SCALE_Y, (float) 0.75)); 254 | PropertyValuesHolder[] parameters = new PropertyValuesHolder[properties.size() - 1]; 255 | parameters = properties.toArray(parameters); 256 | ObjectAnimator propertiesAnimation = ObjectAnimator.ofPropertyValuesHolder(currentSubButtonView, parameters); 257 | // Button Position along the animation 258 | final ArrayList radialPointsPorCurrentPosition = getArcPointsForButton(center, startAngle, endAngle, subActionItems.size(), i, radius); 259 | Collections.reverse(radialPointsPorCurrentPosition); 260 | ValueAnimator positionAnimation = ValueAnimator.ofFloat(0, radialPointsPorCurrentPosition.size()); 261 | positionAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 262 | @Override 263 | public void onAnimationUpdate(ValueAnimator animation) { 264 | Float value = ((Float) animation.getAnimatedValue()); 265 | if (value.intValue() < radialPointsPorCurrentPosition.size()) { 266 | Point p = radialPointsPorCurrentPosition.get(value.intValue()); 267 | currentSubButtonView.setX(p.x); 268 | currentSubButtonView.setY(p.y); 269 | currentSubButton.setX(p.x); 270 | currentSubButton.setY(p.y); 271 | } 272 | } 273 | }); 274 | positionAnimation.setDuration(closingDuration); 275 | positionAnimation.setInterpolator(closingInterpolator); 276 | menuButtonAnimationListener = new FloatingMenuButtonAnimationListener(FloatingMenuAnimationHandler.this, currentSubButton, MenuState.CLOSING_RADIAL); 277 | positionAnimation.addListener(menuButtonAnimationListener); 278 | if (i == 0) { 279 | lastAnimation = positionAnimation; 280 | } 281 | animatorArrayList.add(propertiesAnimation); 282 | animatorArrayList.add(positionAnimation); 283 | } 284 | if (lastAnimation != null) { 285 | lastAnimation.addListener(this); 286 | } 287 | AnimatorSet closeRadialAnimatorSet = new AnimatorSet(); 288 | closeRadialAnimatorSet.playTogether(animatorArrayList); 289 | return closeRadialAnimatorSet; 290 | } 291 | 292 | public void cancelMenuAnimations() { 293 | if (openingAnimation != null) { 294 | openingAnimation.cancel(); 295 | openingAnimation = null; 296 | } 297 | if (closingAnimation != null) { 298 | closingAnimation.cancel(); 299 | closingAnimation = null; 300 | } 301 | } 302 | 303 | 304 | // Configuration Methods 305 | public FloatingMenuAnimationHandler setOpeningInterpolator(Interpolator interpolator) { 306 | this.openingInterpolator = interpolator; 307 | return this; 308 | } 309 | 310 | public FloatingMenuAnimationHandler setClosingInterpolator(Interpolator interpolator) { 311 | this.closingInterpolator = interpolator; 312 | return this; 313 | } 314 | 315 | public FloatingMenuAnimationHandler setOpeningAnimationDuration(int openingDuration) { 316 | this.openingDuration = openingDuration; 317 | return this; 318 | } 319 | 320 | public FloatingMenuAnimationHandler setClosingAnimationDuration(int closingDuration) { 321 | this.closingDuration = closingDuration; 322 | return this; 323 | } 324 | 325 | public FloatingMenuAnimationHandler setLagBetweenItems(int duration) { 326 | this.lagBetweenItems = duration; 327 | return this; 328 | } 329 | 330 | public FloatingMenuAnimationHandler shouldRotate(boolean value) { 331 | this.shouldRotate = value; 332 | return this; 333 | } 334 | 335 | public FloatingMenuAnimationHandler shouldFade(boolean value) { 336 | this.shouldFade = value; 337 | return this; 338 | } 339 | 340 | public FloatingMenuAnimationHandler shouldScale(boolean value) { 341 | this.shouldScale = value; 342 | return this; 343 | } 344 | 345 | 346 | // Animator Interface Listener 347 | @Override 348 | public void onAnimationStart(Animator animation) { 349 | setAnimating(true); 350 | } 351 | 352 | @Override 353 | public void onAnimationEnd(Animator animation) { 354 | setAnimating(false); 355 | if (MenuState.REOPENING == currentState) { 356 | cancelMenuAnimations(); 357 | floatingMenuButton.openMenu(); 358 | } 359 | } 360 | 361 | @Override 362 | public void onAnimationCancel(Animator animation) { 363 | setAnimating(false); 364 | } 365 | 366 | @Override 367 | public void onAnimationRepeat(Animator animation) { 368 | setAnimating(true); 369 | } 370 | 371 | // General Methods 372 | private void resetSubButton(Point center, SubButton subButton, View subButtonView) { 373 | if (subButton != null) { 374 | int floatingMenuButtonWidth = floatingMenuButton.getWidth(); 375 | int floatingMenuButtonHeight = floatingMenuButton.getHeight(); 376 | int xResetPos = center.x + floatingMenuButtonWidth / 2; 377 | int yResetPos = center.y - floatingMenuButtonHeight / 2; 378 | subButtonView.setX(xResetPos); 379 | subButtonView.setY(yResetPos); 380 | subButtonView.setScaleX(0); 381 | subButtonView.setScaleY(0); 382 | subButton.setX(xResetPos); 383 | subButton.setY(yResetPos); 384 | subButton.setAlpha(0); 385 | } 386 | } 387 | 388 | private ArrayList getArcPointsForButton(Point center, int minAngle, int maxAngle, int numberOfButtons, int buttonIndex, int radius) { 389 | ArrayList points = new ArrayList<>(); 390 | // Counter the clockwise default for the angles 391 | int startAngle = 360 - minAngle; 392 | int endAngle = 360 - maxAngle; 393 | double radDegree = Math.toRadians(startAngle); 394 | if (!(Math.abs(endAngle - startAngle) >= 360 || numberOfButtons <= 1)) { 395 | numberOfButtons = numberOfButtons - 1; 396 | } 397 | SubButton currentButton = menu.getSubMenuButtons().get(buttonIndex); 398 | int currentButtonCenterX = currentButton.getView().getWidth() / 2; 399 | int currentButtonCenterY = currentButton.getView().getHeight() / 2; 400 | int angleDistributionValue = Math.abs(endAngle - startAngle) / numberOfButtons; 401 | for (int j = 0; j < radius; j++) { 402 | Point p = new Point(); 403 | p.x = center.x + (int) (j * Math.cos(radDegree)) - currentButtonCenterX; 404 | p.y = center.y - (int) (j * Math.sin(radDegree)) - currentButtonCenterY; 405 | points.add(p); 406 | } 407 | for (int i = startAngle; i <= startAngle + buttonIndex * angleDistributionValue; i++) { 408 | Point p = new Point(); 409 | radDegree = Math.toRadians(i); 410 | p.x = center.x + (int) (radius * Math.cos(radDegree)) - currentButtonCenterX; 411 | p.y = center.y - (int) (radius * Math.sin(radDegree)) - currentButtonCenterY; 412 | points.add(p); 413 | } 414 | return points; 415 | } 416 | 417 | } 418 | 419 | 420 | -------------------------------------------------------------------------------- /floatingmenu/src/main/java/rjsv/floatingmenu/floatingmenubutton/FloatingMenuButton.java: -------------------------------------------------------------------------------- 1 | package rjsv.floatingmenu.floatingmenubutton; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Path; 6 | import android.graphics.PathMeasure; 7 | import android.graphics.Point; 8 | import android.graphics.Rect; 9 | import android.graphics.RectF; 10 | import android.os.Handler; 11 | import android.util.AttributeSet; 12 | import android.util.Log; 13 | import android.util.Pair; 14 | import android.view.Gravity; 15 | import android.view.MotionEvent; 16 | import android.view.View; 17 | import android.view.ViewGroup; 18 | import android.view.animation.AccelerateInterpolator; 19 | import android.view.animation.AlphaAnimation; 20 | import android.view.animation.Animation; 21 | import android.view.animation.AnimationSet; 22 | import android.widget.FrameLayout; 23 | 24 | import java.util.ArrayList; 25 | import java.util.List; 26 | 27 | import rjsv.floatingmenu.R; 28 | import rjsv.floatingmenu.animation.enumerators.AnimationType; 29 | import rjsv.floatingmenu.animation.handlers.FloatingMenuAnimationHandler; 30 | import rjsv.floatingmenu.floatingmenubutton.general.Utils; 31 | import rjsv.floatingmenu.floatingmenubutton.listeners.FloatingMenuButtonClickListener; 32 | import rjsv.floatingmenu.floatingmenubutton.listeners.FloatingMenuStateChangeListener; 33 | import rjsv.floatingmenu.floatingmenubutton.listeners.SubButtonViewQueueListener; 34 | import rjsv.floatingmenu.floatingmenubutton.subbutton.FloatingSubButton; 35 | import rjsv.floatingmenu.floatingmenubutton.subbutton.SubButton; 36 | 37 | 38 | /** 39 | * Description 40 | * 41 | * @author RJSV 42 | * @version $Revision : 1 $ 43 | */ 44 | 45 | public class FloatingMenuButton extends FrameLayout implements View.OnTouchListener { 46 | 47 | // General Variables 48 | private static final String TAG = FloatingMenuButton.class.getName(); 49 | private static final int clickThreshold = 15; 50 | private static final int INVALID_POINTER_ID = -1; 51 | private int startAngle = 0, endAngle = 180; 52 | private int preservedStartAngle = 0, preservedEndAngle = 180; 53 | private int radius; 54 | private int transparentAfterMilliseconds = 2000; 55 | private MovementStyle movementStyle = MovementStyle.FREE; 56 | private boolean isMenuOpened = false; 57 | private Context context; 58 | private List subMenuButtons; 59 | private FloatingMenuAnimationHandler menuAnimationHandler; 60 | private FloatingMenuStateChangeListener stateChangeListener; 61 | private FloatingMenuButtonClickListener floatingMenuActionButtonClickListener; 62 | private AnimationType animationType; 63 | // private touch related 64 | private float startPositionX, startPositionY; 65 | private float currentPositionX, currentPositionY; 66 | private float aLastTouchX, aLastTouchY; 67 | private float screenWidth, screenHeight; 68 | private float viewWidth, viewHeight; 69 | private int mActivePointerId = INVALID_POINTER_ID; 70 | private Handler transparancyHandler; 71 | private FloatingMenuButton floatingMenuButton; 72 | 73 | private Runnable transparencyRunnable = new Runnable() { 74 | @Override 75 | public void run() { 76 | Animation fadeOut = new AlphaAnimation(1, 0.6f); 77 | fadeOut.setInterpolator(new AccelerateInterpolator()); //and this 78 | fadeOut.setDuration(300); 79 | fadeOut.setAnimationListener(new Animation.AnimationListener() { 80 | @Override 81 | public void onAnimationStart(Animation animation) { 82 | 83 | } 84 | 85 | @Override 86 | public void onAnimationEnd(Animation animation) { 87 | floatingMenuButton.setAlpha(0.6f); 88 | } 89 | 90 | @Override 91 | public void onAnimationRepeat(Animation animation) { 92 | 93 | } 94 | }); 95 | 96 | 97 | AnimationSet animation = new AnimationSet(false); //change to false 98 | animation.addAnimation(fadeOut); 99 | if (!floatingMenuButton.isMenuOpen()) 100 | floatingMenuButton.startAnimation(animation); 101 | } 102 | }; 103 | 104 | // Constructors 105 | public FloatingMenuButton(Context context) { 106 | this(context, null); 107 | } 108 | 109 | public FloatingMenuButton(Context context, AttributeSet attr) { 110 | this(context, attr, 0); 111 | } 112 | 113 | public FloatingMenuButton(Context context, AttributeSet attrs, int defStyleAttr) { 114 | super(context, attrs, defStyleAttr); 115 | this.context = context; 116 | subMenuButtons = new ArrayList<>(); 117 | menuAnimationHandler = new FloatingMenuAnimationHandler(this); 118 | floatingMenuActionButtonClickListener = new FloatingMenuButtonClickListener(); 119 | transparancyHandler = new Handler(); 120 | 121 | beginGoTransparentProcess(this); 122 | 123 | if (attrs != null) { 124 | final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FloatingMenuButton, 0, 0); 125 | this.animationType = AnimationType.match(a.getString(R.styleable.FloatingMenuButton_animationType)); 126 | this.radius = a.getInt(R.styleable.FloatingMenuButton_subActionButtonRadius, 100); 127 | this.movementStyle = a.getBoolean(R.styleable.FloatingMenuButton_anchored, false) ? MovementStyle.ANCHORED : MovementStyle.FREE; 128 | this.preservedStartAngle = a.getInt(R.styleable.FloatingMenuButton_dispositionStartAngle, startAngle); 129 | this.preservedEndAngle = a.getInt(R.styleable.FloatingMenuButton_dispositionEndAngle, endAngle); 130 | setDefaultImage(this); 131 | setStartAngle(this.preservedStartAngle, false); 132 | setEndAngle(this.preservedEndAngle, false); 133 | a.recycle(); 134 | } 135 | setOnClickListener(new OnClickListener() { 136 | @Override 137 | public void onClick(View v) { 138 | toggleMenu(); 139 | } 140 | }); 141 | setOnTouchListener(this); 142 | if (menuAnimationHandler != null) { 143 | menuAnimationHandler.setMenu(FloatingMenuButton.this); 144 | } 145 | } 146 | 147 | 148 | // Overridden Methods 149 | @Override 150 | public boolean onTouch(View view, MotionEvent event) { 151 | try { 152 | // Return the alpha to normal 153 | restoreTransparency(this); 154 | 155 | switch (event.getAction() & MotionEvent.ACTION_MASK) { 156 | case MotionEvent.ACTION_DOWN: 157 | mActivePointerId = event.getPointerId(0); 158 | aLastTouchX = event.getX(mActivePointerId); 159 | aLastTouchY = event.getY(mActivePointerId); 160 | startPositionX = getX(); 161 | currentPositionX = startPositionX; 162 | startPositionY = getY(); 163 | currentPositionY = startPositionY; 164 | break; 165 | 166 | case MotionEvent.ACTION_UP: 167 | 168 | if (Utils.isAClick(clickThreshold, startPositionX, getX(), startPositionY, getY())) { 169 | // If the state is Open, the it will close after this click 170 | if (isMenuOpen()) beginGoTransparentProcess(this); 171 | floatingMenuActionButtonClickListener.onClick(FloatingMenuButton.this); 172 | } else { 173 | if (!isMenuOpen()) beginGoTransparentProcess(this); 174 | 175 | if (movementStyle == MovementStyle.STICKED_TO_SIDES) { 176 | int padding = 10; 177 | 178 | boolean[] boundaries = isGlobalViewOutsideBoundaries(padding); 179 | boolean top = boundaries[1]; 180 | boolean bottom = boundaries[3]; 181 | 182 | if (top) { 183 | currentPositionY = radius; // top 184 | } else if (bottom) { 185 | currentPositionY = screenHeight - (radius + viewHeight); 186 | } 187 | 188 | // Force the button to stick either to right or left of the screen 189 | if (currentPositionX >= screenWidth / 2) { 190 | currentPositionX = screenWidth - viewWidth; 191 | } else { 192 | currentPositionX = 0; 193 | } 194 | 195 | // set the coordinates 196 | this.setY(currentPositionY); 197 | this.setX(currentPositionX); 198 | } 199 | if (isMenuOpened) { 200 | Point p = new Point(); 201 | p.x += (viewWidth / 2) + currentPositionX; 202 | p.y += (viewHeight / 2) + currentPositionY; 203 | reOpenMenu(p); 204 | } 205 | } 206 | break; 207 | 208 | case MotionEvent.ACTION_MOVE: 209 | if (movementStyle != MovementStyle.ANCHORED) { 210 | final int pointerIndexMove = event.findPointerIndex(mActivePointerId); 211 | // get the old coordinates 212 | float oldPositionX = getX(); 213 | float oldPositionY = getY(); 214 | // calculate the new coordinates based on the finger's movement 215 | currentPositionX = oldPositionX + event.getX(pointerIndexMove) - aLastTouchX; 216 | currentPositionY = oldPositionY + event.getY(pointerIndexMove) - aLastTouchY; 217 | // check if the difference between old and new coordinates violates the boundaries 218 | boolean[] boundaries = isCentralViewOutsideBoundaries(oldPositionX, oldPositionY, currentPositionX, currentPositionY); 219 | currentPositionX = boundaries[0] ? 0 : (boundaries[2] ? (screenWidth - viewWidth) : currentPositionX); 220 | currentPositionY = boundaries[1] ? 0 : (boundaries[3] ? (screenHeight - viewHeight) : currentPositionY); 221 | 222 | if (movementStyle == MovementStyle.STICKED_TO_SIDES) { 223 | boolean[] viewOutsideBoundaries = isGlobalViewOutsideBoundaries(10); 224 | boolean top = viewOutsideBoundaries[1]; 225 | boolean bottom = viewOutsideBoundaries[3]; 226 | 227 | if (top) { 228 | if (currentPositionY > oldPositionY) { 229 | this.setY(currentPositionY); 230 | } 231 | } else if (bottom) { 232 | if (currentPositionY < oldPositionY) { 233 | this.setY(currentPositionY); 234 | } 235 | } else { 236 | this.setY(currentPositionY); 237 | } 238 | } else { 239 | // set the coordinates 240 | this.setY(currentPositionY); 241 | } 242 | 243 | this.setX(currentPositionX); 244 | } 245 | break; 246 | 247 | case MotionEvent.ACTION_CANCEL: { 248 | mActivePointerId = INVALID_POINTER_ID; 249 | break; 250 | } 251 | } 252 | } catch (Exception e) { 253 | Log.e(TAG, e.getMessage()); 254 | } 255 | invalidate(); 256 | return true; 257 | } 258 | 259 | private void beginGoTransparentProcess(final FloatingMenuButton button) { 260 | transparancyHandler.removeCallbacks(transparencyRunnable); 261 | floatingMenuButton = button; 262 | 263 | if (transparentAfterMilliseconds >= 0) { 264 | transparancyHandler.postDelayed(transparencyRunnable, transparentAfterMilliseconds); 265 | } 266 | } 267 | 268 | private void restoreTransparency(FloatingMenuButton button) { 269 | button.setAlpha(1); 270 | transparancyHandler.removeCallbacks(transparencyRunnable); 271 | } 272 | 273 | @Override 274 | public void addView(View child, ViewGroup.LayoutParams params) { 275 | if (child instanceof FloatingSubButton) { 276 | if (params == null) { 277 | params = subMenuButtons.get(0).getView().getLayoutParams(); 278 | } 279 | child.setLayoutParams(params); 280 | SubButton button = new SubButton(child, 0, 0); 281 | setDefaultImage(button.getView()); 282 | subMenuButtons.add(button); 283 | if (button.getWidth() == 0 || button.getHeight() == 0) { 284 | addViewToCurrentContainer(button.getView()); 285 | button.setAlpha(0); 286 | button.getView().post(new SubButtonViewQueueListener(FloatingMenuButton.this, button)); 287 | } 288 | } 289 | } 290 | 291 | @Override 292 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 293 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 294 | Point screen = Utils.getDisplayDimensions(getContext()); 295 | screenWidth = screen.x; 296 | screenHeight = screen.y; 297 | viewWidth = getMeasuredWidth(); 298 | viewHeight = getMeasuredHeight(); 299 | } 300 | 301 | 302 | // General Methods 303 | @Override 304 | public void setOnClickListener(OnClickListener l) { 305 | floatingMenuActionButtonClickListener.addClickListener(l); 306 | } 307 | 308 | public void openMenu() { 309 | transparancyHandler.removeCallbacks(transparencyRunnable); 310 | 311 | if (menuAnimationHandler != null && !menuAnimationHandler.isAnimating()) { 312 | Pair angles = calculateDispositionAngles(); 313 | Point center = calculateItemPositions(angles.first, angles.second); 314 | for (int i = 0; i < subMenuButtons.size(); i++) { 315 | SubButton currentSubButton = subMenuButtons.get(i); 316 | if (subMenuButtons.get(i).getView().getParent() == null) { 317 | FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(currentSubButton.getWidth(), currentSubButton.getHeight(), Gravity.TOP | Gravity.START); 318 | params.setMargins(center.x - currentSubButton.getWidth() / 2, center.y - currentSubButton.getHeight() / 2, 0, 0); 319 | addViewToCurrentContainer(currentSubButton.getView(), params); 320 | } 321 | } 322 | menuAnimationHandler.animateMenuOpening(center, animationType); 323 | isMenuOpened = true; 324 | if (stateChangeListener != null) { 325 | stateChangeListener.onMenuOpened(this); 326 | } 327 | } 328 | } 329 | 330 | public void closeMenu() { 331 | if (menuAnimationHandler != null && !menuAnimationHandler.isAnimating()) { 332 | menuAnimationHandler.animateMenuClosing(getActionViewCenter(), animationType); 333 | isMenuOpened = false; 334 | if (stateChangeListener != null) { 335 | stateChangeListener.onMenuClosed(this); 336 | } 337 | } 338 | } 339 | 340 | private void reOpenMenu(Point p) { 341 | if (menuAnimationHandler != null) { 342 | if (menuAnimationHandler.isAnimating()) { 343 | menuAnimationHandler.cancelMenuAnimations(); 344 | } 345 | menuAnimationHandler.animateMenuReOpening(p); 346 | } 347 | isMenuOpened = false; 348 | } 349 | 350 | public void toggleMenu() { 351 | if (isMenuOpened) { 352 | closeMenu(); 353 | } else { 354 | openMenu(); 355 | } 356 | } 357 | 358 | public boolean isMenuOpen() { 359 | return isMenuOpened; 360 | } 361 | 362 | private void setDefaultImage(View v) { 363 | if (v != null && v.getBackground() == null) { 364 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { 365 | v.setBackground(context.getResources().getDrawable(R.drawable.defaultimage, context.getTheme())); 366 | } else { 367 | v.setBackgroundDrawable(context.getResources().getDrawable(R.drawable.defaultimage)); 368 | } 369 | } 370 | } 371 | 372 | public void addFloatingSubButton(FloatingSubButton floatingSubButton) { 373 | addFloatingSubButton(floatingSubButton, null); 374 | } 375 | 376 | public void addFloatingSubButton(FloatingSubButton floatingSubButton, ViewGroup.LayoutParams layoutParams) { 377 | addView(floatingSubButton, layoutParams); 378 | } 379 | 380 | private void addViewToCurrentContainer(View view) { 381 | addViewToCurrentContainer(view, null); 382 | } 383 | 384 | private void addViewToCurrentContainer(View view, ViewGroup.LayoutParams layoutParams) { 385 | try { 386 | if (layoutParams != null) { 387 | FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) layoutParams; 388 | ViewGroup vg = (ViewGroup) Utils.getActivityContentView(context); 389 | removeViewFromCurrentContainer(view); 390 | if (vg != null) { 391 | vg.addView(view, lp); 392 | } 393 | } else { 394 | ViewGroup vg = (ViewGroup) Utils.getActivityContentView(context); 395 | if (vg != null) { 396 | vg.addView(view); 397 | } 398 | } 399 | } catch (ClassCastException e) { 400 | Log.e(FloatingMenuButton.class.getName(), e.getMessage()); 401 | } 402 | } 403 | 404 | public void removeViewFromCurrentContainer(View view) { 405 | ViewGroup vg = ((ViewGroup) Utils.getActivityContentView(context)); 406 | if (vg != null) { 407 | vg.removeView(view); 408 | } 409 | } 410 | 411 | 412 | // Utils 413 | public Point getActionViewCenter() { 414 | Point point = getActionViewCoordinates(); 415 | point.x += viewWidth / 2; 416 | point.y += viewHeight / 2; 417 | return point; 418 | } 419 | 420 | private Point getActionViewCoordinates() { 421 | int[] coordinates = new int[2]; 422 | // This method returns a x and y values that can be larger than the dimensions of the device screen. 423 | getLocationOnScreen(coordinates); 424 | // We then need to deduce the offsets 425 | Rect activityFrame = new Rect(); 426 | View v = Utils.getActivityContentView(context); 427 | if (v != null) { 428 | v.getWindowVisibleDisplayFrame(activityFrame); 429 | coordinates[0] -= (Utils.getScreenSize(context).x - v.getMeasuredWidth()); 430 | coordinates[1] -= (activityFrame.height() + activityFrame.top - v.getMeasuredHeight()); 431 | } 432 | return new Point(coordinates[0], coordinates[1]); 433 | } 434 | 435 | private Point calculateItemPositions(Integer startAngle, Integer endAngle) { 436 | final Point center = getActionViewCenter(); 437 | RectF area = new RectF(center.x - radius, center.y - radius, center.x + radius, center.y + radius); 438 | Path orbit = new Path(); 439 | orbit.addArc(area, startAngle, endAngle - startAngle); 440 | PathMeasure measure = new PathMeasure(orbit, false); 441 | // Prevent overlapping when it is a full circle 442 | int divisor; 443 | if (Math.abs(endAngle - startAngle) >= 360 || subMenuButtons.size() <= 1) { 444 | divisor = subMenuButtons.size(); 445 | } else { 446 | divisor = subMenuButtons.size() - 1; 447 | } 448 | // Measure the path in order to find points that have the same distance between each other 449 | for (int i = 0; i < subMenuButtons.size(); i++) { 450 | SubButton currentSubButton = subMenuButtons.get(i); 451 | float[] coordinates = new float[]{0f, 0f}; 452 | int factor = animationType == AnimationType.RADIAL ? 0 : i; 453 | measure.getPosTan(factor * measure.getLength() / divisor, coordinates, null); 454 | currentSubButton.setX((int) coordinates[0] - currentSubButton.getWidth() / 2); 455 | currentSubButton.setY((int) coordinates[1] - currentSubButton.getHeight() / 2); 456 | } 457 | return center; 458 | } 459 | 460 | /** 461 | * Returns a boolean array (left, top, right, bottom) that indicates whether the closed central view touches its sides 462 | */ 463 | private boolean[] isCentralViewOutsideBoundaries(float oldPositionX, float oldPositionY, float newPositionX, float newPositionY) { 464 | boolean[] results = new boolean[4]; 465 | results[0] = newPositionX <= 0 && newPositionX <= oldPositionX; // left 466 | results[2] = (newPositionX + getMeasuredWidth()) >= screenWidth && newPositionX >= oldPositionX; // right 467 | results[1] = newPositionY <= 0 && newPositionY <= oldPositionY; // top 468 | results[3] = (newPositionY + getMeasuredHeight()) >= screenHeight && newPositionY >= oldPositionY; // bottom 469 | return results; 470 | } 471 | 472 | /** 473 | * Returns a boolean array (left, top, right, bottom) that indicates whether the opened menu view touches its sides 474 | */ 475 | private boolean[] isGlobalViewOutsideBoundaries() { 476 | return isGlobalViewOutsideBoundaries(0); 477 | } 478 | 479 | /** 480 | * Returns a boolean array (left, top, right, bottom) that indicates whether the opened menu view touches its sides 481 | * 482 | * @param padding An extra space added to the calculus of the radius 483 | */ 484 | private boolean[] isGlobalViewOutsideBoundaries(int padding) { 485 | boolean[] results = new boolean[4]; 486 | float realRadius = getRealRadius(padding); 487 | 488 | // get the center 489 | Point center = getActionViewCenter(); 490 | results[0] = center.x - realRadius <= 0; // left 491 | results[2] = center.x + realRadius >= screenWidth; // right 492 | results[1] = center.y - realRadius <= 0; // top 493 | results[3] = center.y + realRadius >= screenHeight; // bottom 494 | return results; 495 | } 496 | 497 | private float getRealRadius(int padding) { 498 | // 1 - get the largest width/height of the children 499 | int largestWidth = 0, largestHeight = 0; 500 | for (SubButton button : subMenuButtons) { 501 | if (button.getWidth() >= largestWidth) { 502 | largestWidth = button.getWidth(); 503 | } 504 | if (button.getHeight() >= largestHeight) { 505 | largestHeight = button.getHeight(); 506 | } 507 | } 508 | // 2 - add radius to the width to calculate the wides 509 | return radius + padding + (largestWidth >= largestHeight ? largestWidth : largestHeight) / 2; 510 | } 511 | 512 | private Pair calculateDispositionAngles() { 513 | int defaultStartAngle = this.preservedStartAngle, defaultEndAngle = this.preservedEndAngle; 514 | boolean[] boundariesTouched = isGlobalViewOutsideBoundaries(); 515 | boolean topLeftCorner = boundariesTouched[0] && boundariesTouched[1]; 516 | boolean topRightCorner = boundariesTouched[1] && boundariesTouched[2]; 517 | boolean bottomRightCorner = boundariesTouched[2] && boundariesTouched[3]; 518 | boolean bottomLeftCorner = boundariesTouched[3] && boundariesTouched[0]; 519 | boolean touchesAnyCorner = topLeftCorner || topRightCorner || bottomRightCorner || bottomLeftCorner; 520 | // 1 - else, check if touches any wall/boundary 521 | if (!touchesAnyCorner) { 522 | if (boundariesTouched[0]) { 523 | defaultStartAngle = 270; 524 | defaultEndAngle = 450; 525 | } else if (boundariesTouched[1]) { 526 | defaultStartAngle = 180; 527 | defaultEndAngle = 360; 528 | } else if (boundariesTouched[2]) { 529 | defaultStartAngle = 90; 530 | defaultEndAngle = 270; 531 | } else if (boundariesTouched[3]) { 532 | defaultStartAngle = 0; 533 | defaultEndAngle = 180; 534 | } 535 | } else { 536 | // 2 - check if it touched any corners 537 | if (topLeftCorner) { 538 | defaultStartAngle = 270; 539 | defaultEndAngle = 360; 540 | } else if (topRightCorner) { 541 | defaultStartAngle = 180; 542 | defaultEndAngle = 270; 543 | } else if (bottomRightCorner) { 544 | defaultStartAngle = 90; 545 | defaultEndAngle = 180; 546 | } else /*if (bottomLeftCorner) */ { 547 | defaultStartAngle = 0; 548 | defaultEndAngle = 90; 549 | } 550 | } 551 | setStartAngle(defaultStartAngle, false); 552 | setEndAngle(defaultEndAngle, false); 553 | return new Pair<>(this.startAngle, this.endAngle); 554 | } 555 | 556 | 557 | // FloatingMenuStateChangeListener interface 558 | public void setStateChangeListener(FloatingMenuStateChangeListener stateChangeListener) { 559 | this.stateChangeListener = stateChangeListener; 560 | } 561 | 562 | 563 | // Setters and Getters 564 | public int getStartAngle() { 565 | return startAngle; 566 | } 567 | 568 | public FloatingMenuButton setStartAngle(int startAngle) { 569 | setStartAngle(startAngle, true); 570 | return this; 571 | } 572 | 573 | private void setStartAngle(int startAngle, boolean overridePreservedAngles) { 574 | if (overridePreservedAngles) { 575 | this.preservedStartAngle = startAngle; 576 | } 577 | this.startAngle = 360 - startAngle; 578 | } 579 | 580 | public int getEndAngle() { 581 | return endAngle; 582 | } 583 | 584 | public FloatingMenuButton setEndAngle(int endAngle) { 585 | setEndAngle(endAngle, true); 586 | return this; 587 | } 588 | 589 | private void setEndAngle(int endAngle, boolean overridePreservedAngles) { 590 | if (overridePreservedAngles) { 591 | this.preservedEndAngle = endAngle; 592 | } 593 | this.endAngle = 360 - endAngle; 594 | } 595 | 596 | public int getRadius() { 597 | return radius; 598 | } 599 | 600 | public FloatingMenuButton setRadius(int radius) { 601 | this.radius = radius; 602 | return this; 603 | } 604 | 605 | /** 606 | * @deprecated Use movementStyle instead 607 | */ 608 | @Deprecated 609 | public FloatingMenuButton setAnchored(boolean isAnchored) { 610 | movementStyle = isAnchored ? MovementStyle.ANCHORED : MovementStyle.FREE; 611 | return this; 612 | } 613 | 614 | /** 615 | * @deprecated Use movementStyle instead 616 | */ 617 | @Deprecated 618 | public boolean isAnchored() { 619 | return movementStyle == MovementStyle.ANCHORED; 620 | } 621 | 622 | public FloatingMenuButton setMovementStyle(MovementStyle movementStyle) { 623 | this.movementStyle = movementStyle; 624 | return this; 625 | } 626 | 627 | public MovementStyle getMovementStyle() { 628 | return movementStyle; 629 | } 630 | 631 | public AnimationType getAnimationType() { 632 | return this.animationType; 633 | } 634 | 635 | public FloatingMenuButton setAnimationType(AnimationType animationType) { 636 | if (animationType != null) { 637 | this.animationType = animationType; 638 | } 639 | return this; 640 | } 641 | 642 | public FloatingMenuAnimationHandler getAnimationHandler() { 643 | return this.menuAnimationHandler; 644 | } 645 | 646 | public List getSubMenuButtons() { 647 | return subMenuButtons; 648 | } 649 | 650 | /** 651 | * Indicates after how many milliseconds the button should get transparent 652 | * after it was moved or clicked. 653 | * 654 | * @param transparentAfterMilliseconds the time in milliseconds, 0 to always be transparent or < 0 to never be transparent. 655 | */ 656 | public FloatingMenuButton setTransparentAfterMilliseconds(int transparentAfterMilliseconds) { 657 | this.transparentAfterMilliseconds = transparentAfterMilliseconds; 658 | return this; 659 | } 660 | 661 | public int getTransparentAfterMilliseconds() { 662 | return transparentAfterMilliseconds; 663 | } 664 | } --------------------------------------------------------------------------------