├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.txt
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── it
│ │ └── sephiroth
│ │ └── android
│ │ └── library
│ │ └── floatingmenu
│ │ └── app
│ │ ├── ListFragment1.java
│ │ ├── MainActivity.java
│ │ └── MainActivity2.java
│ └── res
│ ├── drawable-v21
│ └── custom_item_background_selector.xml
│ ├── drawable-xxhdpi
│ ├── ic_facebook.png
│ ├── ic_googleplus.png
│ ├── ic_instagram.png
│ └── ic_twitter.png
│ ├── drawable
│ ├── custom_item_background_selector.xml
│ ├── pink_oval_shape.xml
│ └── pink_oval_shape_pressed.xml
│ ├── layout
│ ├── activity_main.xml
│ ├── activity_main_activity2.xml
│ └── fragment_main_activity2.xml
│ ├── menu
│ ├── main.xml
│ └── main_activity2.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── values-w820dp
│ └── dimens.xml
│ └── values
│ ├── colors.xml
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── floatingmenu
├── .gitignore
├── build.gradle
├── gradle.properties
├── proguard-rules.txt
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── it
│ │ └── sephiroth
│ │ └── android
│ │ └── library
│ │ └── floatingmenu
│ │ ├── ApiHelper.java
│ │ ├── FloatingActionItem.java
│ │ ├── FloatingActionItemImageView.java
│ │ └── FloatingActionMenu.java
│ └── res
│ ├── anim-v21
│ └── action_item_raise.xml
│ ├── anim
│ └── action_item_raise.xml
│ ├── drawable-v21
│ └── action_item_background_selector.xml
│ ├── drawable-xxhdpi
│ └── action_item_background.png
│ ├── drawable
│ └── action_item_background_selector.xml
│ ├── layout
│ └── floating_action_item_view.xml
│ └── values
│ ├── attrs.xml
│ └── dimen.xml
├── gradle.properties
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.ap_
4 |
5 | # Files for the Dalvik VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # Generated files
12 | bin/
13 | gen/
14 |
15 | # Gradle files
16 | .gradle/
17 | build/
18 |
19 | # Local configuration file (sdk path, etc)
20 | local.properties
21 |
22 | # Proguard folder generated by Eclipse
23 | proguard/
24 |
25 | .idea
26 |
27 | android-floating-action-menu.iml
28 |
29 | app/app.iml
30 |
31 | gradle/*
32 | gradlew
33 |
34 | gradlew.bat
35 |
36 | floatingmenu/library.iml
37 |
38 | floatingmenu/floatingmenu.iml
39 |
40 |
41 | demo.mp4
42 |
43 | graphics/
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | Changelog
2 | ====
3 |
4 | 1.0.0 Initial release
5 | 1.0.1 ScrollDelegate is now public. action items can now have custom background too
--------------------------------------------------------------------------------
/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.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | android-floating-action-menu
2 | ============================
3 |
4 | Floating Action Menu for Android. Inspired by the Google Plus floating menu.
5 |
6 | Demo
7 | ====
8 |
9 | [](https://www.youtube.com/watch?v=mLplXAJ5vug&feature=youtu.be)
10 |
11 |
12 | Setup
13 | =====
14 |
15 | The simplest way to use this library is to add the library as a gradle aar dependency to your build. See the CHANGELOG.md for the latest version number.
16 | ```gradle
17 | repositories {
18 | mavenCentral()
19 | }
20 |
21 | dependencies {
22 | compile 'it.sephiroth.android.library.floatingmenu:floatingmenu:x.x.x' // see changelog
23 | }
24 | ```
25 | Usage
26 | =====
27 |
28 | In your activity create a reference to the `FloatingMenu`:
29 |
30 | ```java
31 | FloatingActionItem item1 = new FloatingActionItem.Builder(0)
32 | .withResId(R.drawable.ic_facebook)
33 | .withDelay(0)
34 | .withPadding(action_item_padding)
35 | .build();
36 |
37 | FloatingActionItem item2 = new FloatingActionItem.Builder(1)
38 | .withResId(R.drawable.ic_googleplus)
39 | .withDelay(50)
40 | .withPadding(action_item_padding)
41 | .build();
42 |
43 | FloatingActionItem item3 = new FloatingActionItem.Builder(2)
44 | .withResId(R.drawable.ic_twitter)
45 | .withDelay(100)
46 | .withPadding(action_item_padding)
47 | .build();
48 |
49 | mFloatingMenu = new FloatingActionMenu
50 | .Builder(this)
51 | .addItem(item1)
52 | .addItem(item2)
53 | .addItem(item3)
54 | .withScrollDelegate(new FloatingActionMenu.AbsListViewScrollDelegate(mListView))
55 | .withThreshold(R.dimen.float_action_threshold)
56 | .withGap(R.dimen.float_action_item_gap)
57 | .withHorizontalPadding(R.dimen.float_action_h_padding)
58 | .withVerticalPadding(R.dimen.float_action_v_padding)
59 | .withGravity(FloatingActionMenu.Gravity.CENTER_HORIZONTAL | FloatingActionMenu.Gravity.BOTTOM)
60 | .withDirection(FloatingActionMenu.Direction.Vertical)
61 | .animationDuration(300)
62 | .animationInterpolator(new OvershootInterpolator())
63 | .visible(visible)
64 | .build();
65 |
66 | mFloatingMenu.setOnItemClickListener(this);
67 | ```
68 |
69 | License
70 | =======
71 |
72 | Copyright (c) 2014 Alessandro Crugnola
73 |
74 | Licensed under the Apache License, Version 2.0 (the "License");
75 | you may not use this file except in compliance with the License.
76 | You may obtain a copy of the License at
77 |
78 | http://www.apache.org/licenses/LICENSE-2.0
79 |
80 | Unless required by applicable law or agreed to in writing, software
81 | distributed under the License is distributed on an "AS IS" BASIS,
82 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
83 | See the License for the specific language governing permissions and
84 | limitations under the License.
85 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 21
5 | buildToolsVersion "21.1.2"
6 |
7 | defaultConfig {
8 | minSdkVersion 9
9 | targetSdkVersion 21
10 | versionCode 1
11 | versionName "1.0"
12 | }
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
17 | }
18 | }
19 | }
20 |
21 | dependencies {
22 | compile fileTree(dir: 'libs', include: ['*.jar'])
23 | compile project(':floatingmenu')
24 | compile 'com.android.support:appcompat-v7:21.0.3'
25 | }
26 |
--------------------------------------------------------------------------------
/app/proguard-rules.txt:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Applications/Android Studio.app/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the ProGuard
5 | # include property in project.properties.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
12 |
15 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/java/it/sephiroth/android/library/floatingmenu/app/ListFragment1.java:
--------------------------------------------------------------------------------
1 | package it.sephiroth.android.library.floatingmenu.app;
2 |
3 | import android.os.Bundle;
4 | import android.support.v4.app.ListFragment;
5 | import android.util.Log;
6 | import android.view.LayoutInflater;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 | import android.view.animation.OvershootInterpolator;
10 | import android.widget.ArrayAdapter;
11 | import android.widget.Toast;
12 |
13 | import java.util.ArrayList;
14 | import java.util.List;
15 |
16 | import it.sephiroth.android.library.floatingmenu.FloatingActionItem;
17 | import it.sephiroth.android.library.floatingmenu.FloatingActionMenu;
18 | import it.sephiroth.android.library.floatingmenu.FloatingActionMenu.Gravity;
19 |
20 | public class ListFragment1 extends ListFragment implements FloatingActionMenu.OnItemClickListener {
21 | private static final String TAG = "ListFragment";
22 | public static final String ARG_SECTION_NUMBER = "section_number";
23 | public static final String ARG_SECTION_VISIBLE = "section_visible";
24 | private FloatingActionMenu mFloatingMenu;
25 | private int sectionNumber;
26 |
27 | public static ListFragment1 newInstance(int sectionNumber, boolean visible) {
28 | Log.i(TAG, "newInstance");
29 | ListFragment1 fragment = new ListFragment1();
30 | Bundle args = new Bundle();
31 | args.putInt(ARG_SECTION_NUMBER, sectionNumber);
32 | args.putBoolean(ARG_SECTION_VISIBLE, visible);
33 | fragment.setArguments(args);
34 | return fragment;
35 | }
36 |
37 | public ListFragment1() {}
38 |
39 | @Override
40 | public View onCreateView(
41 | LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
42 | sectionNumber = getArguments().getInt(ARG_SECTION_NUMBER);
43 | Log.i(TAG, "onCreateView: " + sectionNumber);
44 |
45 | View rootView = inflater.inflate(R.layout.fragment_main_activity2, container, false);
46 | List data = createData();
47 | setListAdapter(new ArrayAdapter<>(getActivity(), android.R.layout.simple_list_item_1, android.R.id.text1, data));
48 | return rootView;
49 | }
50 |
51 | @Override
52 | public void onDestroyView() {
53 | super.onDestroyView();
54 | if (null != mFloatingMenu) {
55 | mFloatingMenu.clear();
56 | }
57 | }
58 |
59 | @Override
60 | public void onViewCreated(final View view, final Bundle savedInstanceState) {
61 | super.onViewCreated(view, savedInstanceState);
62 | Log.i(TAG, "onViewCreated: " + sectionNumber);
63 | initFloatingMenu(sectionNumber, savedInstanceState);
64 |
65 | if (null != mFloatingMenu) {
66 | view.postDelayed(
67 | new Runnable() {
68 | @Override
69 | public void run() {
70 | int currentItem = ((MainActivity2) getActivity()).mViewPager.getCurrentItem();
71 | if (currentItem == sectionNumber) {
72 | mFloatingMenu.show(true, true);
73 | }
74 | }
75 | }, 400);
76 | }
77 | }
78 |
79 | private void initFloatingMenu(final int sectionNumber, final Bundle savedInstanceState) {
80 | int currentItem = ((MainActivity2) getActivity()).mViewPager.getCurrentItem();
81 |
82 | FloatingActionMenu.Builder builder = createMenu(sectionNumber);
83 |
84 | FloatingActionItem item1 = new FloatingActionItem.Builder(getActivity(), 0, R.style.FloatingActionMenuItemStyle)
85 | .withResId(R.drawable.ic_facebook)
86 | .build();
87 |
88 | FloatingActionItem item2 = new FloatingActionItem.Builder(getActivity(), 1, R.style.FloatingActionMenuItemStyle)
89 | .withResId(R.drawable.ic_twitter)
90 | .withDelay(sectionNumber == 3 ? 50 : 0)
91 | .build();
92 |
93 | FloatingActionItem item3 = new FloatingActionItem.Builder(getActivity(), 2, R.style.FloatingActionMenuItemStyle)
94 | .withResId(R.drawable.ic_facebook)
95 | .withDelay(sectionNumber == 3 ? 100 : 0)
96 | .build();
97 |
98 | FloatingActionItem item4 = new FloatingActionItem.Builder(getActivity(), 3, R.style.FloatingActionMenuItemStyle)
99 | .withResId(R.drawable.ic_instagram)
100 | .withDelay(sectionNumber == 3 ? 150 : 0)
101 | .build();
102 |
103 | if (sectionNumber == 0) {
104 | builder.addItem(item1);
105 | } else if (sectionNumber == 1) {
106 | builder.addItem(item1).addItem(item2);
107 | } else if (sectionNumber == 2) {
108 | builder.addItem(item1).addItem(item2).addItem(item3);
109 | } else if (sectionNumber == 3) {
110 | builder.addItem(item1).addItem(item2).addItem(item3).addItem(item4);
111 | }
112 |
113 | mFloatingMenu = builder.build();
114 | mFloatingMenu.setOnItemClickListener(this);
115 | }
116 |
117 | private FloatingActionMenu.Builder createMenu(int sectionNumber) {
118 | FloatingActionMenu.Builder builder = new FloatingActionMenu
119 | .Builder(
120 | getActivity(), sectionNumber < 2 ? R.style.FloatingActionMenuStyle_Horizontal : R.style.FloatingActionMenuStyle)
121 | .withScrollDelegate(new FloatingActionMenu.AbsListViewScrollDelegate(getListView()))
122 | .animationInterpolator(new OvershootInterpolator())
123 | .visible(false);
124 |
125 | if (sectionNumber == 0) {
126 | builder.withGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
127 | } else if (sectionNumber == 1) {
128 | builder.withGravity(Gravity.RIGHT | Gravity.BOTTOM);
129 | } else if (sectionNumber == 2) {
130 | builder.withGravity(Gravity.RIGHT | Gravity.BOTTOM);
131 | } else {
132 | builder.withGravity(Gravity.RIGHT | Gravity.CENTER_VERTICAL);
133 | }
134 |
135 | return builder;
136 | }
137 |
138 | private Boolean visible;
139 |
140 | boolean getVisible() {
141 | if (null != visible) {
142 | return visible.booleanValue();
143 | }
144 | return false;
145 | }
146 |
147 | public void onVisibilityChanged(boolean visible) {
148 | if (null == this.visible || this.visible != visible) {
149 | this.visible = visible;
150 | if (null != mFloatingMenu) {
151 | Log.i(TAG, "onVisibilityChanged: " + sectionNumber + ", visible: " + visible);
152 |
153 | if (visible) {
154 | mFloatingMenu.show(true, true);
155 | } else {
156 | mFloatingMenu.hide(true, true);
157 | }
158 | }
159 | }
160 | }
161 |
162 | @Override
163 | public void onSaveInstanceState(final Bundle outState) {
164 | Log.i(TAG, "onSaveInstanceState: " + sectionNumber);
165 | super.onSaveInstanceState(outState);
166 | }
167 |
168 | private List createData() {
169 | List data = new ArrayList();
170 | for (int i = 0; i < 100; i++) {
171 | data.add(String.format("fragment %d - item %d", sectionNumber, i));
172 | }
173 | return data;
174 | }
175 |
176 | @Override
177 | public void onItemClick(final FloatingActionMenu menu, final int id) {
178 | Log.i(TAG, "onItemClick: " + id);
179 | Toast.makeText(getActivity(), "clicked item " + id, Toast.LENGTH_SHORT).show();
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/app/src/main/java/it/sephiroth/android/library/floatingmenu/app/MainActivity.java:
--------------------------------------------------------------------------------
1 | package it.sephiroth.android.library.floatingmenu.app;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.support.v7.app.ActionBarActivity;
6 | import android.util.Log;
7 | import android.view.Menu;
8 | import android.view.MenuItem;
9 | import android.view.View;
10 | import android.view.animation.OvershootInterpolator;
11 | import android.widget.AdapterView;
12 | import android.widget.ArrayAdapter;
13 | import android.widget.ListView;
14 | import android.widget.Toast;
15 |
16 | import java.util.ArrayList;
17 | import java.util.List;
18 |
19 | import it.sephiroth.android.library.floatingmenu.FloatingActionItem;
20 | import it.sephiroth.android.library.floatingmenu.FloatingActionMenu;
21 |
22 | public class MainActivity extends ActionBarActivity
23 | implements AdapterView.OnItemClickListener, FloatingActionMenu.OnItemClickListener {
24 | private static final String TAG = "MainActivity";
25 | private ListView mListView;
26 | private FloatingActionMenu mFloatingMenu;
27 |
28 | @Override
29 | protected void onCreate(Bundle savedInstanceState) {
30 | Log.i(TAG, "onCreate: " + savedInstanceState);
31 | super.onCreate(savedInstanceState);
32 | setContentView(R.layout.activity_main);
33 |
34 | List objects = new ArrayList<>();
35 | populateData(objects);
36 |
37 | ArrayAdapter adapter =
38 | new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, android.R.id.text1, objects);
39 | mListView.setAdapter(adapter);
40 | mListView.setOnItemClickListener(this);
41 |
42 | boolean visible = true;
43 | if (null != savedInstanceState) {
44 | visible = savedInstanceState.getBoolean("floating-menu-visible", true);
45 | }
46 |
47 | FloatingActionItem item1 = new FloatingActionItem.Builder(this, 0, R.style.FloatingActionMenuItemStyle)
48 | .withResId(R.drawable.ic_facebook)
49 | .withDelay(0)
50 | .build();
51 |
52 | FloatingActionItem item2 = new FloatingActionItem.Builder(this, 1, R.style.FloatingActionMenuItemStyle)
53 | .withResId(R.drawable.ic_googleplus)
54 | .withDelay(50)
55 | .build();
56 |
57 | FloatingActionItem item3 = new FloatingActionItem.Builder(this, 2, R.style.FloatingActionMenuItemStyle)
58 | .withResId(R.drawable.ic_twitter)
59 | .withDelay(100)
60 | .build();
61 |
62 | mFloatingMenu = new FloatingActionMenu
63 | .Builder(this, R.style.FloatingActionMenuStyle)
64 | .addItem(item1)
65 | .addItem(item2)
66 | .addItem(item3)
67 | .withScrollDelegate(new FloatingActionMenu.AbsListViewScrollDelegate(mListView))
68 | .withThreshold(R.dimen.float_action_threshold)
69 | .withGap(R.dimen.float_action_item_gap)
70 | .withVerticalPadding(R.dimen.float_action_v_padding)
71 | .withGravity(FloatingActionMenu.Gravity.CENTER_HORIZONTAL | FloatingActionMenu.Gravity.BOTTOM)
72 | .withDirection(FloatingActionMenu.Direction.Vertical)
73 | .animationDuration(400)
74 | .animationInterpolator(new OvershootInterpolator())
75 | .visible(visible)
76 | .build();
77 |
78 | mFloatingMenu.setOnItemClickListener(this);
79 | }
80 |
81 | @Override
82 | protected void onSaveInstanceState(final Bundle outState) {
83 | Log.i(TAG, "onSaveInstanceState");
84 | super.onSaveInstanceState(outState);
85 |
86 | outState.putBoolean("floating-menu-visible", mFloatingMenu.getVisible());
87 | }
88 |
89 | @Override
90 | protected void onRestoreInstanceState(final Bundle savedInstanceState) {
91 | Log.i(TAG, "onRestoreInstanceState: " + savedInstanceState);
92 | super.onRestoreInstanceState(savedInstanceState);
93 | }
94 |
95 | private void populateData(final List objects) {
96 | for (int i = 0; i < 100; i++) {
97 | objects.add(String.format("Item %d", i));
98 | }
99 | }
100 |
101 | @Override
102 | public void onSupportContentChanged() {
103 | super.onSupportContentChanged();
104 | mListView = (ListView) findViewById(android.R.id.list);
105 | }
106 |
107 | @Override
108 | public boolean onCreateOptionsMenu(Menu menu) {
109 | getMenuInflater().inflate(R.menu.main, menu);
110 | return true;
111 | }
112 |
113 | @Override
114 | public boolean onOptionsItemSelected(MenuItem item) {
115 | int id = item.getItemId();
116 | if (id == R.id.example2) {
117 | startActivity(new Intent(this, MainActivity2.class));
118 | }
119 | return super.onOptionsItemSelected(item);
120 | }
121 |
122 | @Override
123 | public void onItemClick(final AdapterView> parent, final View view, final int position, final long id) {
124 | }
125 |
126 | @Override
127 | public void onItemClick(final FloatingActionMenu menu, final int id) {
128 | Log.i(TAG, "onItemClick: " + id);
129 |
130 | Toast.makeText(this, "item click " + id, Toast.LENGTH_SHORT).show();
131 |
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/app/src/main/java/it/sephiroth/android/library/floatingmenu/app/MainActivity2.java:
--------------------------------------------------------------------------------
1 | package it.sephiroth.android.library.floatingmenu.app;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.support.v4.app.Fragment;
6 | import android.support.v4.app.FragmentManager;
7 | import android.support.v4.app.FragmentStatePagerAdapter;
8 | import android.support.v4.app.FragmentTransaction;
9 | import android.support.v4.view.ViewPager;
10 | import android.support.v7.app.ActionBar;
11 | import android.support.v7.app.ActionBarActivity;
12 | import android.util.Log;
13 | import android.view.Menu;
14 | import android.view.MenuItem;
15 | import android.view.ViewGroup;
16 |
17 | import java.util.HashMap;
18 | import java.util.Locale;
19 |
20 |
21 | public class MainActivity2 extends ActionBarActivity implements ActionBar.TabListener {
22 | private static final String TAG = "MainActivity";
23 | SectionsPagerAdapter mSectionsPagerAdapter;
24 | ViewPager mViewPager;
25 |
26 | @Override
27 | protected void onCreate(Bundle savedInstanceState) {
28 | Log.i(TAG, "onCreate: " + savedInstanceState);
29 | super.onCreate(savedInstanceState);
30 | setContentView(R.layout.activity_main_activity2);
31 |
32 | // Set up the action bar.
33 | final ActionBar actionBar = getSupportActionBar();
34 | actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
35 |
36 | mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
37 |
38 | int viewPagerItem = 0;
39 | if (null != savedInstanceState) {
40 | viewPagerItem = savedInstanceState.getInt("viewpage-current-item", 0);
41 | }
42 |
43 | Log.i(TAG, "viewPagerItem: " + viewPagerItem);
44 |
45 | mViewPager = (ViewPager) findViewById(R.id.pager);
46 | mViewPager.setAdapter(mSectionsPagerAdapter);
47 |
48 | mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
49 | @Override
50 | public void onPageSelected(int position) {
51 | actionBar.setSelectedNavigationItem(position);
52 | }
53 | });
54 |
55 | for (int i = 0; i < mSectionsPagerAdapter.getCount(); i++) {
56 | actionBar.addTab(actionBar.newTab().setText(mSectionsPagerAdapter.getPageTitle(i)).setTabListener(this));
57 | }
58 | }
59 |
60 |
61 | @Override
62 | public boolean onCreateOptionsMenu(Menu menu) {
63 | getMenuInflater().inflate(R.menu.main_activity2, menu);
64 | return true;
65 | }
66 |
67 | @Override
68 | public boolean onOptionsItemSelected(MenuItem item) {
69 | int id = item.getItemId();
70 | if (id == R.id.example1) {
71 | startActivity(new Intent(this, MainActivity.class));
72 | }
73 | return super.onOptionsItemSelected(item);
74 | }
75 |
76 | @Override
77 | public void onTabSelected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
78 | Log.i(TAG, "onTabSelected: " + tab.getPosition());
79 | mViewPager.setCurrentItem(tab.getPosition());
80 |
81 | ListFragment1 fragment = mSectionsPagerAdapter.getFragmentAt(tab.getPosition());
82 | if (null != fragment) {
83 | fragment.onVisibilityChanged(true);
84 | }
85 | }
86 |
87 | @Override
88 | public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
89 | Log.i(TAG, "onTabUnselected: " + tab.getPosition());
90 |
91 | ListFragment1 fragment = mSectionsPagerAdapter.getFragmentAt(tab.getPosition());
92 | if (null != fragment) {
93 | fragment.onVisibilityChanged(false);
94 | }
95 | }
96 |
97 | @Override
98 | public void onTabReselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
99 | }
100 |
101 | @Override
102 | protected void onSaveInstanceState(final Bundle outState) {
103 | super.onSaveInstanceState(outState);
104 |
105 | outState.putInt("viewpage-current-item", mViewPager.getCurrentItem());
106 | }
107 |
108 | @Override
109 | protected void onRestoreInstanceState(final Bundle savedInstanceState) {
110 | Log.i(TAG, "onRestoreInstanceState: " + savedInstanceState);
111 | super.onRestoreInstanceState(savedInstanceState);
112 | }
113 |
114 | public class SectionsPagerAdapter extends FragmentStatePagerAdapter {
115 | final HashMap mFragmentTable;
116 |
117 | public SectionsPagerAdapter(FragmentManager fm) {
118 | super(fm);
119 | mFragmentTable = new HashMap();
120 | }
121 |
122 | @Override
123 | public Fragment getItem(int position) {
124 | ListFragment1 item = ListFragment1.newInstance(position, false);
125 | return item;
126 | }
127 |
128 | public ListFragment1 getFragmentAt(int position) {
129 | return mFragmentTable.get(position);
130 | }
131 |
132 | @Override
133 | public void destroyItem(ViewGroup container, int position, Object object) {
134 | Log.i(TAG, "destroyItem: " + position);
135 | super.destroyItem(container, position, object);
136 | mFragmentTable.remove(position);
137 | }
138 |
139 | @Override
140 | public Object instantiateItem(final ViewGroup container, final int position) {
141 | Log.i(TAG, "instantiateItem: " + position);
142 | Object object = super.instantiateItem(container, position);
143 | mFragmentTable.put(position, (ListFragment1) object);
144 | return object;
145 | }
146 |
147 | @Override
148 | public int getCount() {
149 | return 4;
150 | }
151 |
152 | @Override
153 | public CharSequence getPageTitle(int position) {
154 | Locale l = Locale.getDefault();
155 | switch (position) {
156 | case 0:
157 | return getString(R.string.title_section1).toUpperCase(l);
158 | case 1:
159 | return getString(R.string.title_section2).toUpperCase(l);
160 | case 2:
161 | return getString(R.string.title_section3).toUpperCase(l);
162 | case 3:
163 | return getString(R.string.title_section4).toUpperCase(l);
164 | }
165 | return null;
166 | }
167 | }
168 |
169 | }
170 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v21/custom_item_background_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_facebook.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sephiroth74/android-floating-action-menu/6529698eddfaac1a5e7c69cb3cd11d6f3f76fad4/app/src/main/res/drawable-xxhdpi/ic_facebook.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_googleplus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sephiroth74/android-floating-action-menu/6529698eddfaac1a5e7c69cb3cd11d6f3f76fad4/app/src/main/res/drawable-xxhdpi/ic_googleplus.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_instagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sephiroth74/android-floating-action-menu/6529698eddfaac1a5e7c69cb3cd11d6f3f76fad4/app/src/main/res/drawable-xxhdpi/ic_instagram.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_twitter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sephiroth74/android-floating-action-menu/6529698eddfaac1a5e7c69cb3cd11d6f3f76fad4/app/src/main/res/drawable-xxhdpi/ic_twitter.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/custom_item_background_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/pink_oval_shape.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/pink_oval_shape_pressed.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main_activity2.xml:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_main_activity2.xml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/main.xml:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/main_activity2.xml:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sephiroth74/android-floating-action-menu/6529698eddfaac1a5e7c69cb3cd11d6f3f76fad4/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sephiroth74/android-floating-action-menu/6529698eddfaac1a5e7c69cb3cd11d6f3f76fad4/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sephiroth74/android-floating-action-menu/6529698eddfaac1a5e7c69cb3cd11d6f3f76fad4/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sephiroth74/android-floating-action-menu/6529698eddfaac1a5e7c69cb3cd11d6f3f76fad4/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FF4081
4 | #FF80AB
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 0dp
4 | 0dp
5 |
6 | 16dip
7 | 10dip
8 | 16dip
9 | 16dip
10 | 24dip
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Floating Action
5 | Fam Example 2
6 | Fam Example
7 | Section 1
8 | Section 2
9 | Section 3
10 | Section 4
11 | Advanced Example
12 | Simple Example
13 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
21 |
22 |
26 |
27 |
30 |
31 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:1.+'
9 | }
10 | }
11 |
12 | def isReleaseBuild() {
13 | return version.contains("SNAPSHOT") == false
14 | }
15 |
16 | allprojects {
17 |
18 | version = VERSION_NAME
19 | group = GROUP
20 |
21 | repositories {
22 | jcenter()
23 | }
24 | }
25 |
26 | task wrapper(type: Wrapper) {
27 | gradleVersion = '2.3'
28 | }
29 |
--------------------------------------------------------------------------------
/floatingmenu/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/floatingmenu/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | group GROUP
4 | version VERSION_NAME
5 |
6 | android {
7 | compileSdkVersion Integer.parseInt(ANDROID_BUILD_SDK_VERSION)
8 | buildToolsVersion ANDROID_BUILD_TOOLS_VERSION
9 |
10 | defaultConfig {
11 | minSdkVersion 9
12 | targetSdkVersion Integer.parseInt(ANDROID_BUILD_TARGET_SDK_VERSION)
13 | versionCode 1
14 | versionName VERSION_NAME
15 | }
16 | buildTypes {
17 | release {
18 | minifyEnabled false
19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
20 | }
21 | }
22 | }
23 |
24 | dependencies {
25 | compile fileTree(dir: 'libs', include: ['*.jar'])
26 | compile 'com.nineoldandroids:library:2.4.0'
27 | compile 'com.android.support:appcompat-v7:21.0.3'
28 | }
29 |
30 |
31 | apply from: 'https://raw.githubusercontent.com/sephiroth74/gradle-mvn-push/master/gradle-mvn-push.gradle'
--------------------------------------------------------------------------------
/floatingmenu/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_NAME=floatingmenu
2 | POM_ARTIFACT_ID=floatingmenu
3 | POM_PACKAGING=aar
--------------------------------------------------------------------------------
/floatingmenu/proguard-rules.txt:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Applications/Android Studio.app/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the ProGuard
5 | # include property in project.properties.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
--------------------------------------------------------------------------------
/floatingmenu/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/floatingmenu/src/main/java/it/sephiroth/android/library/floatingmenu/ApiHelper.java:
--------------------------------------------------------------------------------
1 | package it.sephiroth.android.library.floatingmenu;
2 |
3 | import android.os.Build;
4 |
5 | /**
6 | * Created by crugnola on 12/6/14.
7 | */
8 | class ApiHelper {
9 | static final boolean AT_LEAST_21 = Build.VERSION.SDK_INT >= 21;
10 | static final boolean AT_LEAST_11 = Build.VERSION.SDK_INT >= 11;
11 | }
12 |
--------------------------------------------------------------------------------
/floatingmenu/src/main/java/it/sephiroth/android/library/floatingmenu/FloatingActionItem.java:
--------------------------------------------------------------------------------
1 | package it.sephiroth.android.library.floatingmenu;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 |
6 | /**
7 | * Created by alessandro on 24/05/14.
8 | */
9 | public class FloatingActionItem {
10 | int id;
11 | int resId;
12 | int delay = 0;
13 | int paddingTop = 0;
14 | int paddingBottom = 0;
15 | int paddingLeft = 0;
16 | int paddingRight = 0;
17 | int stateListAnimId = 0;
18 | int backgroundResId;
19 |
20 | static public class Builder {
21 | int id;
22 | int resId = -1;
23 | int delay = 0;
24 | int paddingTop = 0;
25 | int paddingBottom = 0;
26 | int paddingLeft = 0;
27 | int paddingRight = 0;
28 | int backgroundResId = 0;
29 | int stateListAnimResId = 0;
30 |
31 | /**
32 | * @param id unique id to be used with the
33 | * {@link it.sephiroth.android.library.floatingmenu.FloatingActionMenu.OnItemClickListener}
34 | */
35 | public Builder(final Context context, int id) {
36 | this(context, id, 0);
37 | }
38 |
39 | public Builder(final Context context, int id, int styleId) {
40 | this.id = id;
41 |
42 | TypedArray array =
43 | context.getTheme().obtainStyledAttributes(styleId, R.styleable.FloatingActionItem);
44 |
45 | this.backgroundResId = array.getResourceId(
46 | R.styleable.FloatingActionItem_fam_item_background,
47 | R.drawable.action_item_background_selector);
48 |
49 | this.resId = array.getResourceId(R.styleable.FloatingActionItem_android_src, 0);
50 | this.delay = array.getInteger(R.styleable.FloatingActionItem_fam_item_delay, 0);
51 | this.stateListAnimResId =
52 | array.getResourceId(R.styleable.FloatingActionItem_fam_item_stateListAnimator, R.anim.action_item_raise);
53 |
54 | int defaultPadding = context.getResources().getDimensionPixelSize(R.dimen.floating_action_item_default_padding);
55 |
56 | int padding = array.getDimensionPixelSize(R.styleable.FloatingActionItem_android_padding, defaultPadding);
57 | array.recycle();
58 |
59 | withPadding(padding);
60 | }
61 |
62 | /**
63 | * Available only for API level 21
64 | *
65 | * @param resId
66 | * @return
67 | */
68 | public Builder withStateListAnimator(int resId) {
69 | this.stateListAnimResId = resId;
70 | return this;
71 | }
72 |
73 | /**
74 | * Assign a custom background resource
75 | */
76 | public Builder withBackgroundResId(int resId) {
77 | this.backgroundResId = resId;
78 | return this;
79 | }
80 |
81 | /**
82 | * Image drawable resource-id
83 | *
84 | * @param resId
85 | * @return
86 | */
87 | public Builder withResId(final int resId) {
88 | this.resId = resId;
89 | return this;
90 | }
91 |
92 | /**
93 | * Show/Hide delay time
94 | *
95 | * @param delay
96 | * @return
97 | */
98 | public Builder withDelay(final int delay) {
99 | this.delay = delay;
100 | return this;
101 | }
102 |
103 | /**
104 | * Image padding
105 | *
106 | * @param left
107 | * @param top
108 | * @param right
109 | * @param bottom
110 | * @return
111 | */
112 | public Builder withPadding(int left, int top, int right, int bottom) {
113 | this.paddingBottom = bottom;
114 | this.paddingLeft = left;
115 | this.paddingRight = right;
116 | this.paddingTop = top;
117 | return this;
118 | }
119 |
120 | /**
121 | * Image padding, applied to all the padding directions
122 | *
123 | * @param padding
124 | * @return
125 | */
126 | public Builder withPadding(int padding) {
127 | return withPadding(padding, padding, padding, padding);
128 | }
129 |
130 | public FloatingActionItem build() {
131 | if (resId == -1) {
132 | throw new IllegalArgumentException("resId missing");
133 | }
134 | FloatingActionItem item = new FloatingActionItem();
135 | item.id = id;
136 | item.resId = resId;
137 | item.delay = delay;
138 | item.paddingBottom = paddingBottom;
139 | item.paddingLeft = paddingLeft;
140 | item.paddingRight = paddingRight;
141 | item.paddingTop = paddingTop;
142 | item.backgroundResId = backgroundResId;
143 | item.stateListAnimId = stateListAnimResId;
144 | return item;
145 | }
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/floatingmenu/src/main/java/it/sephiroth/android/library/floatingmenu/FloatingActionItemImageView.java:
--------------------------------------------------------------------------------
1 | package it.sephiroth.android.library.floatingmenu;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.util.AttributeSet;
6 | import android.util.Log;
7 | import android.view.animation.Interpolator;
8 | import android.widget.FrameLayout;
9 | import android.widget.ImageView;
10 |
11 | import com.nineoldandroids.animation.Animator;
12 | import com.nineoldandroids.animation.ObjectAnimator;
13 | import com.nineoldandroids.view.ViewHelper;
14 |
15 | import static it.sephiroth.android.library.floatingmenu.FloatingActionMenu.LOG_ENABLED;
16 |
17 | final class FloatingActionItemImageView extends ImageView {
18 | final String TAG = "ActionItemImageView";
19 |
20 | static enum Status {
21 | None, Invisible, Visible, Animating
22 | }
23 |
24 | private static final int ANIMATION_SHOW = 0;
25 | private static final int ANIMATION_HIDE = 1;
26 | private Status mStatus;
27 | FloatingActionMenu.Direction direction;
28 | Interpolator interpolator;
29 | Animator.AnimatorListener mShowListener;
30 | Animator.AnimatorListener mHideListener;
31 | ObjectAnimator mShowAnimator;
32 | ObjectAnimator mHideAnimator;
33 | /** final x for for horizontal animation */
34 | int x1;
35 | /** final y for for vertical animation */
36 | int y1;
37 | int x2;
38 | int y2;
39 | int duration;
40 |
41 | public FloatingActionItemImageView(final Context context) {
42 | this(context, null);
43 | }
44 |
45 | public FloatingActionItemImageView(final Context context, AttributeSet attrs) {
46 | this(context, attrs, 0);
47 | }
48 |
49 | public FloatingActionItemImageView(Context context, AttributeSet attrs, int defStyleAttr) {
50 | super(context, attrs, defStyleAttr);
51 |
52 | mShowListener = new Animator.AnimatorListener() {
53 | boolean isCancelled;
54 |
55 | @Override
56 | public void onAnimationStart(final Animator animation) {
57 | isCancelled = false;
58 | FloatingActionItemImageView.this.onAnimationStart(ANIMATION_SHOW);
59 | }
60 |
61 | @Override
62 | public void onAnimationEnd(final Animator animation) {
63 | if (!isCancelled) {
64 | FloatingActionItemImageView.this.onAnimationEnd(ANIMATION_SHOW);
65 | }
66 | }
67 |
68 | @Override
69 | public void onAnimationCancel(final Animator animation) {
70 | isCancelled = true;
71 | }
72 |
73 | @Override
74 | public void onAnimationRepeat(final Animator animation) {}
75 | };
76 |
77 | mHideListener = new Animator.AnimatorListener() {
78 | boolean isCancelled;
79 |
80 | @Override
81 | public void onAnimationStart(final Animator animation) {
82 | isCancelled = false;
83 | FloatingActionItemImageView.this.onAnimationStart(ANIMATION_HIDE);
84 | }
85 |
86 | @Override
87 | public void onAnimationEnd(final Animator animation) {
88 | if (!isCancelled) {
89 | FloatingActionItemImageView.this.onAnimationEnd(ANIMATION_HIDE);
90 | }
91 | }
92 |
93 | @Override
94 | public void onAnimationCancel(final Animator animation) {
95 | isCancelled = true;
96 | }
97 |
98 | @Override
99 | public void onAnimationRepeat(final Animator animation) {}
100 | };
101 |
102 | mShowAnimator = new ObjectAnimator();
103 | mShowAnimator.setTarget(this);
104 | if (null != mShowListener) {
105 | mShowAnimator.addListener(mShowListener);
106 | }
107 |
108 | mHideAnimator = new ObjectAnimator();
109 | mHideAnimator.setTarget(this);
110 | if (null != mHideListener) {
111 | mHideAnimator.addListener(mHideListener);
112 | }
113 | }
114 |
115 | void setStyle(int style) {
116 | TypedArray array =
117 | getContext().getTheme().obtainStyledAttributes(style, R.styleable.FloatingActionItem);
118 |
119 | array.recycle();
120 | }
121 |
122 | void setDirection(final FloatingActionMenu.Direction direction) {
123 | if (LOG_ENABLED) {
124 | Log.v(TAG, "setDirection: " + direction);
125 | }
126 | this.direction = direction;
127 | this.mShowAnimator.setPropertyName(direction == FloatingActionMenu.Direction.Vertical ? "translationY" : "translationX");
128 | this.mHideAnimator.setPropertyName(direction == FloatingActionMenu.Direction.Vertical ? "translationY" : "translationX");
129 | }
130 |
131 | void setAnimationDuration(int ms) {
132 | if (LOG_ENABLED) Log.v(TAG, "setAnimationDuration: " + ms);
133 | this.duration = ms;
134 | this.mHideAnimator.setDuration(duration);
135 | this.mShowAnimator.setDuration(duration);
136 | }
137 |
138 | void setAnimationInterpolator(Interpolator interpolator) {
139 | if (LOG_ENABLED) Log.v(TAG, "setAnimationInterpolator: " + interpolator);
140 | this.interpolator = interpolator;
141 | this.mShowAnimator.setInterpolator(interpolator);
142 | this.mHideAnimator.setInterpolator(interpolator);
143 | }
144 |
145 | void setX1(final int x1, final boolean immediate) {
146 | if (LOG_ENABLED) Log.v(TAG, "setX1: " + x1 + ", immediate: " + immediate);
147 | this.x1 = x1;
148 | if (immediate) {
149 | ViewHelper.setTranslationX(this, x1);
150 | }
151 | }
152 |
153 | void setX2(final int x2) {
154 | if (LOG_ENABLED) Log.v(TAG, "setX2: " + x2);
155 | this.x2 = x2;
156 | }
157 |
158 | void setY1(final int y1, boolean immediate) {
159 | if (LOG_ENABLED) Log.v(TAG, "setY1: " + y1 + ", immediate: " + immediate);
160 | this.y1 = y1;
161 | if (immediate) {
162 | ViewHelper.setTranslationY(this, y1);
163 | }
164 | }
165 |
166 | void setY2(final int y2) {
167 | if (LOG_ENABLED) Log.v(TAG, "setY2: " + y2);
168 | this.y2 = y2;
169 | }
170 |
171 | public void show(boolean animate, int delay) {
172 | if (LOG_ENABLED) {
173 | Log.i(TAG, "show, current status: " + mStatus);
174 | Log.v(TAG, "direction: " + direction);
175 | Log.v(TAG, "animate: " + animate);
176 | }
177 |
178 | if (mStatus == Status.Visible) {
179 | if (LOG_ENABLED) {
180 | Log.w(TAG, "already visible");
181 | }
182 | return;
183 | }
184 |
185 | mStatus = Status.Animating;
186 |
187 | if (mHideAnimator.isStarted()) {
188 | mHideAnimator.cancel();
189 | }
190 |
191 | if (mShowAnimator.isStarted()) {
192 | mShowAnimator.cancel();
193 | }
194 |
195 | if (direction == FloatingActionMenu.Direction.Vertical) {
196 | if (!animate) {
197 | ViewHelper.setTranslationY(this, y1);
198 | onAnimationEnd(ANIMATION_SHOW);
199 | } else {
200 | if (ApiHelper.AT_LEAST_11) {
201 | mShowAnimator.setFloatValues(y1);
202 | } else {
203 | mShowAnimator.setFloatValues(y2, y1);
204 | }
205 | mShowAnimator.setStartDelay(delay);
206 | mShowAnimator.start();
207 | }
208 | } else {
209 | if (!animate) {
210 | ViewHelper.setTranslationX(this, x1);
211 | onAnimationEnd(ANIMATION_SHOW);
212 | } else {
213 | if (ApiHelper.AT_LEAST_11) {
214 | mShowAnimator.setFloatValues(x1);
215 | } else {
216 | mShowAnimator.setFloatValues(x2, x1);
217 | }
218 | mShowAnimator.setStartDelay(delay);
219 | mShowAnimator.start();
220 | }
221 | }
222 | }
223 |
224 | public void hide(boolean animate, int delay) {
225 | if (LOG_ENABLED) {
226 | Log.i(TAG, "hide, current status: " + mStatus);
227 | Log.v(TAG, "direction: " + direction);
228 | Log.v(TAG, "animate: " + animate);
229 | }
230 |
231 | if (mStatus == Status.Invisible) {
232 | if (LOG_ENABLED) {
233 | Log.w(TAG, "already hidden");
234 | }
235 | return;
236 | }
237 |
238 | mStatus = Status.Animating;
239 |
240 | if (mShowAnimator.isStarted()) {
241 | mShowAnimator.cancel();
242 | }
243 |
244 | if (mHideAnimator.isStarted()) {
245 | mHideAnimator.cancel();
246 | }
247 |
248 | if (direction == FloatingActionMenu.Direction.Vertical) {
249 | if (!animate) {
250 | ViewHelper.setTranslationY(this, y2);
251 | onAnimationEnd(ANIMATION_HIDE);
252 | } else {
253 | if (ApiHelper.AT_LEAST_11) {
254 | mHideAnimator.setFloatValues(y2);
255 | } else {
256 | mHideAnimator.setFloatValues(y1, y2);
257 | }
258 | mHideAnimator.setStartDelay(delay);
259 | mHideAnimator.start();
260 | }
261 | } else {
262 | if (!animate) {
263 | ViewHelper.setTranslationX(this, x2);
264 | onAnimationEnd(ANIMATION_HIDE);
265 | } else {
266 | if (ApiHelper.AT_LEAST_11) {
267 | mHideAnimator.setFloatValues(x2);
268 | } else {
269 | mHideAnimator.setFloatValues(x1, x2);
270 | }
271 | mHideAnimator.setStartDelay(delay);
272 | mHideAnimator.start();
273 | }
274 | }
275 | }
276 |
277 | @SuppressWarnings ("unused")
278 | private float getCurrentY() {
279 | if (LOG_ENABLED) Log.i(TAG, "getCurrentY");
280 | if (mShowAnimator.isRunning() || mHideAnimator.isRunning()) {
281 | return ViewHelper.getTranslationY(this);
282 | } else {
283 | FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) getLayoutParams();
284 | return params.topMargin;
285 | }
286 | }
287 |
288 | @SuppressWarnings ("unused")
289 | private float getCurrentX() {
290 | if (LOG_ENABLED) Log.i(TAG, "getCurrentX");
291 | if (mShowAnimator.isRunning() || mHideAnimator.isRunning()) {
292 | return ViewHelper.getTranslationX(this);
293 | } else {
294 | FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) getLayoutParams();
295 | return params.leftMargin;
296 | }
297 | }
298 |
299 | public FloatingActionItem getItem() {
300 | return (FloatingActionItem) getTag();
301 | }
302 |
303 | public void setItem(FloatingActionItem item) {
304 | setTag(item);
305 | }
306 |
307 | protected void onAnimationStart(int type) {
308 | if (!ApiHelper.AT_LEAST_11) {
309 |
310 | if (LOG_ENABLED) {
311 | Log.v(TAG, "onAnimationStart: " + type);
312 | }
313 |
314 | FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) getLayoutParams();
315 | params.setMargins(0, 0, 0, 0);
316 | setLayoutParams(params);
317 |
318 | if (direction == FloatingActionMenu.Direction.Vertical) {
319 | if (LOG_ENABLED) {
320 | Log.v(TAG, "setTranslationX: " + x1);
321 | Log.v(TAG, "setTranslationY: " + (type == ANIMATION_SHOW ? y2 : y1));
322 | }
323 | ViewHelper.setTranslationX(this, x1);
324 | ViewHelper.setTranslationY(this, type == ANIMATION_SHOW ? y2 : y1);
325 | } else {
326 | ViewHelper.setTranslationX(this, type == ANIMATION_SHOW ? x2 : x1);
327 | ViewHelper.setTranslationY(this, y1);
328 | }
329 | }
330 | }
331 |
332 | protected void onAnimationEnd(int type) {
333 | mStatus = type == ANIMATION_SHOW ? Status.Visible : Status.Invisible;
334 |
335 | if (LOG_ENABLED) {
336 | Log.v(TAG, "onAnimationEnd. status: " + mStatus + ", type: " + type);
337 | }
338 |
339 | if (!ApiHelper.AT_LEAST_11) {
340 | FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) getLayoutParams();
341 |
342 | ViewHelper.setTranslationX(this, 0);
343 | ViewHelper.setTranslationY(this, 0);
344 |
345 | if (direction == FloatingActionMenu.Direction.Vertical) {
346 | params.setMargins(x1, type == ANIMATION_SHOW ? y1 : y2, 0, 0);
347 | } else {
348 | params.setMargins(type == ANIMATION_SHOW ? x1 : x2, y1, 0, 0);
349 | }
350 |
351 | setLayoutParams(params);
352 | }
353 | }
354 | }
355 |
--------------------------------------------------------------------------------
/floatingmenu/src/main/java/it/sephiroth/android/library/floatingmenu/FloatingActionMenu.java:
--------------------------------------------------------------------------------
1 | package it.sephiroth.android.library.floatingmenu;
2 |
3 | import android.animation.AnimatorInflater;
4 | import android.annotation.TargetApi;
5 | import android.app.Activity;
6 | import android.content.res.Resources;
7 | import android.content.res.TypedArray;
8 | import android.os.Build;
9 | import android.util.DisplayMetrics;
10 | import android.view.LayoutInflater;
11 | import android.view.View;
12 | import android.view.ViewGroup;
13 | import android.view.animation.AnimationUtils;
14 | import android.view.animation.Interpolator;
15 | import android.view.animation.LinearInterpolator;
16 | import android.widget.AbsListView;
17 | import android.widget.FrameLayout;
18 | import android.widget.RelativeLayout;
19 |
20 | import java.util.ArrayList;
21 | import java.util.Iterator;
22 | import java.util.List;
23 |
24 | public class FloatingActionMenu implements AbsListView.OnScrollListener, View.OnClickListener {
25 | private static final String TAG = "FloatingMenu";
26 | final Activity activity;
27 | final List itemViews;
28 | private final ScrollDelegate scrollDelegate;
29 | private final int verticalPadding;
30 | private final int horizontalPadding;
31 | private final int animationDuration;
32 | private final Interpolator animationInterpolator;
33 | private final int gravity;
34 | private Direction direction;
35 | private OnItemClickListener clickListener;
36 | private int itemGap;
37 | private int threshold;
38 | private boolean visible;
39 | private ViewGroup root;
40 | static final boolean LOG_ENABLED = false;
41 |
42 | public FloatingActionMenu(final Builder builder) {
43 | this.activity = builder.activity;
44 | this.visible = builder.visible;
45 | this.threshold = builder.threshold;
46 | this.itemGap = builder.gap;
47 | this.verticalPadding = builder.verticalPadding;
48 | this.horizontalPadding = builder.horitontalPadding;
49 | this.gravity = builder.gravity;
50 | this.direction = builder.direction;
51 | this.animationDuration = builder.animationDuration;
52 | this.animationInterpolator = builder.interpolator;
53 | this.scrollDelegate = builder.scrollDelegate;
54 |
55 | if (null != scrollDelegate) {
56 | scrollDelegate.setOnScrollListener(this);
57 | }
58 |
59 | this.root = createRoot(this.activity);
60 | this.itemViews = create(builder.items);
61 | layout();
62 | }
63 |
64 | public void setOnItemClickListener(OnItemClickListener listener) {
65 | clickListener = listener;
66 | }
67 |
68 | /**
69 | * Hide all the menu items
70 | *
71 | * @param animate turn on/off the animation
72 | */
73 | public void hide(boolean animate) {
74 | hide(animate, false);
75 | }
76 |
77 | /**
78 | * Show all the menu items
79 | *
80 | * @param animate turn on/off the animation
81 | */
82 | public void show(boolean animate) {
83 | show(animate, false);
84 | }
85 |
86 | /**
87 | * @param animate
88 | * @param play_together play all the items together (override the item's delay value)
89 | * @see #hide(boolean)
90 | */
91 | public void hide(boolean animate, boolean play_together) {
92 | for (FloatingActionItemImageView view : itemViews) {
93 | view.hide(animate, play_together ? 0 : view.getItem().delay);
94 | }
95 | visible = false;
96 | }
97 |
98 | /**
99 | * @param animate
100 | * @param play_together play all the items together (override the item's delay value)
101 | * @see #show(boolean)
102 | */
103 | public void show(boolean animate, boolean play_together) {
104 | for (FloatingActionItemImageView view : itemViews) {
105 | view.show(animate, play_together ? 0 : view.getItem().delay);
106 | }
107 | visible = true;
108 | }
109 |
110 | /**
111 | * Get the current menu visibility
112 | *
113 | * @return
114 | */
115 | public boolean getVisible() {
116 | return visible;
117 | }
118 |
119 | private ViewGroup createRoot(final Activity activity) {
120 | ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
121 |
122 | final ViewGroup viewGroup = new RelativeLayout(activity);
123 | decorView
124 | .addView(
125 | viewGroup,
126 | 1,
127 | new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
128 |
129 | return viewGroup;
130 | }
131 |
132 | @TargetApi (Build.VERSION_CODES.LOLLIPOP)
133 | private List create(final List items) {
134 | List result = new ArrayList<>();
135 |
136 | FrameLayout.LayoutParams params;
137 | ArrayList focusables = new ArrayList<>();
138 |
139 | for (FloatingActionItem actionItem : items) {
140 | params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
141 | params.gravity = android.view.Gravity.TOP | android.view.Gravity.LEFT;
142 | FloatingActionItemImageView view =
143 | (FloatingActionItemImageView) LayoutInflater.from(activity).inflate(
144 | R.layout.floating_action_item_view, root, false);
145 | view.setImageResource(actionItem.resId);
146 | view.setPadding(actionItem.paddingLeft, actionItem.paddingTop, actionItem.paddingRight, actionItem.paddingBottom);
147 | view.setItem(actionItem);
148 |
149 | if (actionItem.backgroundResId != 0) {
150 | view.setBackgroundResource(actionItem.backgroundResId);
151 | } else {
152 | view.setBackgroundResource(R.drawable.action_item_background_selector);
153 | }
154 |
155 | if (ApiHelper.AT_LEAST_21) {
156 | if (actionItem.stateListAnimId != 0) {
157 | view.setStateListAnimator(AnimatorInflater.loadStateListAnimator(activity, actionItem.stateListAnimId));
158 | }
159 | }
160 |
161 | view.setOnClickListener(this);
162 | root.addView(view, params);
163 | result.add(view);
164 | focusables.add(view);
165 | }
166 | root.addFocusables(focusables, View.FOCUS_FORWARD);
167 | // root.addTouchables(focusables);
168 | return result;
169 | }
170 |
171 | private void layout() {
172 | Resources resources = activity.getResources();
173 | DisplayMetrics metrics = resources.getDisplayMetrics();
174 | boolean measured = false;
175 |
176 | int x = 0;
177 | int y = 0;
178 |
179 | boolean right = false;
180 | boolean bottom = false;
181 |
182 | if ((gravity & Gravity.RIGHT) == Gravity.RIGHT) {
183 | x = metrics.widthPixels - horizontalPadding;
184 | right = true;
185 | } else if ((gravity & Gravity.LEFT) == Gravity.LEFT) {
186 | x = horizontalPadding;
187 | }
188 |
189 | if ((gravity & Gravity.BOTTOM) == Gravity.BOTTOM) {
190 | y = metrics.heightPixels - verticalPadding;
191 | bottom = true;
192 | } else if ((gravity & Gravity.TOP) == Gravity.TOP) {
193 | y = verticalPadding;
194 | }
195 |
196 | if ((gravity & Gravity.CENTER_VERTICAL) == Gravity.CENTER_VERTICAL ||
197 | (gravity & Gravity.CENTER_HORIZONTAL) == Gravity.CENTER_HORIZONTAL) {
198 | int totalWidth = 0;
199 | int totalHeight = 0;
200 | for (FloatingActionItemImageView view : itemViews) {
201 | view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
202 | totalWidth += view.getMeasuredWidth() + itemGap;
203 | totalHeight += view.getMeasuredHeight() + itemGap;
204 | }
205 | totalWidth -= itemGap;
206 | totalHeight -= itemGap;
207 |
208 | if ((gravity & Gravity.CENTER_HORIZONTAL) != 0) {
209 | x = ((metrics.widthPixels - totalWidth) / 2) + horizontalPadding;
210 | }
211 |
212 | if ((gravity & Gravity.CENTER_VERTICAL) != 0) {
213 | y = ((metrics.heightPixels - totalHeight) / 2) + verticalPadding;
214 | }
215 |
216 | measured = true;
217 | }
218 |
219 | int x1;
220 | int x2;
221 | int y1;
222 | int y2;
223 |
224 | for (FloatingActionItemImageView view : itemViews) {
225 | if (!measured) {
226 | view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
227 | }
228 |
229 | int width = view.getMeasuredWidth();
230 | int height = view.getMeasuredHeight();
231 |
232 | view.setDirection(direction);
233 | view.setAnimationDuration(animationDuration);
234 | view.setAnimationInterpolator(animationInterpolator);
235 |
236 | if (direction == Direction.Vertical) {
237 | if (bottom) {
238 | y1 = metrics.heightPixels - height - verticalPadding;
239 | y2 = metrics.heightPixels + height;
240 | } else {
241 | y1 = verticalPadding * 2;
242 | y2 = -height - verticalPadding;
243 | }
244 |
245 | view.setX1(x - (right ? width : 0), true);
246 | view.setY1(y1, false);
247 | view.setY2(y2);
248 | } else {
249 | if (right) {
250 | x1 = metrics.widthPixels - width - horizontalPadding;
251 | x2 = metrics.widthPixels + width;
252 | } else {
253 | x1 = horizontalPadding * 2;
254 | x2 = -width - horizontalPadding;
255 | }
256 | view.setY1(y - (bottom ? height : 0), true);
257 | view.setX1(x1, false);
258 | view.setX2(x2);
259 | }
260 |
261 | if (visible) {
262 | view.show(false, 0);
263 | } else {
264 | view.hide(false, 0);
265 | }
266 |
267 | if (direction == Direction.Vertical) {
268 | if (right) {
269 | x -= (width + itemGap);
270 | } else {
271 | x += (width + itemGap);
272 | }
273 | } else {
274 | if (bottom) {
275 | y -= (height + itemGap);
276 | } else {
277 | y += (height + itemGap);
278 | }
279 | }
280 | }
281 | }
282 |
283 | View mFirstView;
284 | boolean mScrollStarted;
285 | int mFirstVisibleItem = -1;
286 | int mFirstViewTop;
287 | int mFirstViewTopAmount;
288 | int mAnimationDirection;
289 |
290 | @Override
291 | public void onScrollStateChanged(final AbsListView view, final int scrollState) {
292 | if (scrollState == 1) {
293 | // scroll starting
294 | if (view.getChildCount() > 0) {
295 | mFirstView = view.getChildAt(0);
296 | if (null == mFirstView) {
297 | return;
298 | }
299 | mFirstViewTop = mFirstView.getTop();
300 | mScrollStarted = true;
301 | }
302 | mFirstViewTopAmount = 0;
303 | mAnimationDirection = 0;
304 | } else if (scrollState == 0) {
305 | // scroll ended
306 | mFirstVisibleItem = -1;
307 | mScrollStarted = false;
308 | }
309 | }
310 |
311 | @Override
312 | public void onScroll(
313 | final AbsListView view, final int firstVisibleItem, final int visibleItemCount, final int totalItemCount) {
314 | if (totalItemCount == 0 || !mScrollStarted) {
315 | return;
316 | }
317 |
318 | if (mFirstVisibleItem != firstVisibleItem) {
319 | mFirstVisibleItem = firstVisibleItem;
320 | mFirstView = view.getChildAt(0);
321 | if (null == mFirstView) {
322 | return;
323 | }
324 | mFirstViewTop = mFirstView.getTop();
325 | }
326 |
327 | int newTop = mFirstView.getTop();
328 | int delta = (mFirstViewTop - newTop);
329 |
330 | mFirstViewTopAmount += delta;
331 | mFirstViewTop = newTop;
332 |
333 | if (Math.abs(mFirstViewTopAmount) > threshold) {
334 | if (mFirstViewTopAmount > 0) {
335 | if (mAnimationDirection != 1) {
336 | hide(true, false);
337 | }
338 | mAnimationDirection = 1;
339 | } else {
340 | if (mAnimationDirection != -1) {
341 | show(true, false);
342 | }
343 | mAnimationDirection = -1;
344 | }
345 | mFirstViewTopAmount = 0;
346 | }
347 |
348 | }
349 |
350 | @Override
351 | public void onClick(final View v) {
352 | FloatingActionItemImageView view = (FloatingActionItemImageView) v;
353 |
354 | if (null != clickListener) {
355 | clickListener.onItemClick(this, view.getItem().id);
356 | }
357 | }
358 |
359 | /**
360 | * Remove immediately all the items from their parent.
361 | */
362 | public void clear() {
363 | // Iterator iterator = itemViews.iterator();
364 | // while (iterator.hasNext()) {
365 | // FloatingActionItemImageView item = iterator.next();
366 | // root.removeView(item);
367 | // iterator.remove();
368 | // }
369 |
370 | itemViews.clear();
371 | ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
372 | decorView.removeView(root);
373 | root = null;
374 |
375 | if (null != scrollDelegate) {
376 | scrollDelegate.setOnScrollListener(null);
377 | }
378 |
379 | }
380 |
381 | public static interface OnItemClickListener {
382 | void onItemClick(FloatingActionMenu menu, final int id);
383 | }
384 |
385 | static public class Builder {
386 | private final List items = new ArrayList<>();
387 | private final Activity activity;
388 | private final Resources res;
389 | boolean visible;
390 | int threshold = 36;
391 | int gap = 12;
392 | int verticalPadding = 0;
393 | int horitontalPadding = 0;
394 | int gravity = Gravity.RIGHT;
395 | Direction direction = Direction.Vertical;
396 | ScrollDelegate scrollDelegate;
397 | int animationDuration = 300;
398 | Interpolator interpolator = new LinearInterpolator();
399 |
400 | public Builder(Activity context) {
401 | this(context, 0);
402 | }
403 |
404 | public Builder(Activity context, int styleId) {
405 | this.activity = context;
406 | this.res = activity.getResources();
407 |
408 | Resources.Theme theme = context.getTheme();
409 | TypedArray array = theme.obtainStyledAttributes(styleId, R.styleable.FloatingActionMenu);
410 |
411 | this.threshold = array.getDimensionPixelSize(R.styleable.FloatingActionMenu_fam_threshold, 36);
412 | this.gap = array.getDimensionPixelSize(R.styleable.FloatingActionMenu_fam_gap, 12);
413 | this.horitontalPadding = array.getDimensionPixelSize(R.styleable.FloatingActionMenu_fam_horizontalPadding, 0);
414 | this.verticalPadding = array.getDimensionPixelSize(R.styleable.FloatingActionMenu_fam_verticalPadding, 0);
415 | this.gravity = array.getInt(R.styleable.FloatingActionMenu_fam_gravity, Gravity.RIGHT);
416 | this.direction = Direction.values()[array.getInt(
417 | R.styleable.FloatingActionMenu_fam_direction,
418 | Direction.Horizontal.ordinal())];
419 | this.animationDuration = array.getInt(R.styleable.FloatingActionMenu_android_animationDuration, 200);
420 |
421 | int resId = array.getResourceId(
422 | R.styleable.FloatingActionMenu_android_interpolator,
423 | android.R.anim.anticipate_overshoot_interpolator);
424 | this.interpolator = AnimationUtils.loadInterpolator(activity, resId);
425 | array.recycle();
426 |
427 | this.visible = true;
428 | }
429 |
430 | /**
431 | * Interpolator used for the item's animation
432 | *
433 | * @param interpolator
434 | * @return
435 | */
436 | public Builder animationInterpolator(Interpolator interpolator) {
437 | this.interpolator = interpolator;
438 | return this;
439 | }
440 |
441 | /**
442 | * Duration for the item's animation
443 | *
444 | * @param ms
445 | * @return
446 | */
447 | public Builder animationDuration(int ms) {
448 | this.animationDuration = ms;
449 | return this;
450 | }
451 |
452 | /**
453 | * Direction of the menu
454 | *
455 | * @param direction
456 | * @return
457 | */
458 | public Builder withDirection(Direction direction) {
459 | this.direction = direction;
460 | return this;
461 | }
462 |
463 | /**
464 | * Menu gravity. Use one of the constants defined in the {@link it.sephiroth.android.library.floatingmenu
465 | * .FloatingActionMenu.Gravity } class.
466 | * Both horizontal and vertical gravity should be passed using the '|' bitwise operator
467 | * Example:
468 | *
469 | * .withGravity(Gravity.RIGHT | Gravity.BOTTOM);
470 | *
471 | *
472 | * @param gravity
473 | * @return
474 | * @see it.sephiroth.android.library.floatingmenu.FloatingActionMenu.Gravity
475 | */
476 | public Builder withGravity(int gravity) {
477 | this.gravity = gravity;
478 | return this;
479 | }
480 |
481 | /**
482 | * Distance of the menu from the top/bottom border. If gravity is Gravity.BOTTOM then the distance is measured from the
483 | * screen bottom.
484 | *
485 | * @param resId resource-id
486 | * @return
487 | */
488 | public Builder withVerticalPadding(int resId) {
489 | verticalPadding = res.getDimensionPixelSize(resId);
490 | return this;
491 | }
492 |
493 | /**
494 | * Distance of the menu from the left/right border. If gravity is Gravity.RIGHT then the distance is measured from the
495 | * screen right.
496 | *
497 | * @param resId resource-id
498 | * @return
499 | */
500 | public Builder withHorizontalPadding(int resId) {
501 | horitontalPadding = res.getDimensionPixelSize(resId);
502 | return this;
503 | }
504 |
505 | /**
506 | * Movement required to hide/show the floating menu
507 | *
508 | * @param resId resource-id
509 | * @return
510 | */
511 | public Builder withThreshold(int resId) {
512 | this.threshold = res.getDimensionPixelSize(resId);
513 | return this;
514 | }
515 |
516 | /**
517 | * Gap distance between items
518 | *
519 | * @param resId resource-id
520 | * @return
521 | */
522 | public Builder withGap(int resId) {
523 | gap = res.getDimensionPixelSize(resId);
524 | return this;
525 | }
526 |
527 | /**
528 | * Set a scroll delegate
529 | *
530 | * @param delegate
531 | * @return
532 | */
533 | public Builder withScrollDelegate(ScrollDelegate delegate) {
534 | this.scrollDelegate = delegate;
535 | return this;
536 | }
537 |
538 | public Builder visible(boolean visible) {
539 | this.visible = visible;
540 | return this;
541 | }
542 |
543 | public Builder addItem(FloatingActionItem item) {
544 | items.add(item);
545 | return this;
546 | }
547 |
548 | public FloatingActionMenu build() {
549 | return new FloatingActionMenu(this);
550 | }
551 | }
552 |
553 | /**
554 | * Default scroll delegate abstract class
555 | */
556 | public static interface ScrollDelegate {
557 | void setOnScrollListener(AbsListView.OnScrollListener listener);
558 | }
559 |
560 | /**
561 | * Scroll delegate used for all the AbsListView(s)
562 | */
563 | public static class AbsListViewScrollDelegate implements ScrollDelegate {
564 | final AbsListView listView;
565 |
566 | public AbsListViewScrollDelegate(AbsListView listView) {
567 | this.listView = listView;
568 | }
569 |
570 | @Override
571 | public void setOnScrollListener(final AbsListView.OnScrollListener listener) {
572 | listView.setOnScrollListener(listener);
573 | }
574 | }
575 |
576 | static public enum Direction {
577 | Horizontal, Vertical
578 | }
579 |
580 | @SuppressWarnings ("unused")
581 | static public class Gravity {
582 | public static final int LEFT = 1 << 0;
583 | public static final int RIGHT = 1 << 1;
584 | public static final int TOP = 1 << 2;
585 | public static final int BOTTOM = 1 << 3;
586 | public static final int CENTER_VERTICAL = 1 << 4;
587 | public static final int CENTER_HORIZONTAL = 1 << 5;
588 | public static final int CENTER = CENTER_HORIZONTAL | CENTER_VERTICAL;
589 | }
590 |
591 | private static class RootTag {
592 | }
593 | }
594 |
--------------------------------------------------------------------------------
/floatingmenu/src/main/res/anim-v21/action_item_raise.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
10 |
11 | -
12 |
18 |
19 |
--------------------------------------------------------------------------------
/floatingmenu/src/main/res/anim/action_item_raise.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/floatingmenu/src/main/res/drawable-v21/action_item_background_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | -
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/floatingmenu/src/main/res/drawable-xxhdpi/action_item_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sephiroth74/android-floating-action-menu/6529698eddfaac1a5e7c69cb3cd11d6f3f76fad4/floatingmenu/src/main/res/drawable-xxhdpi/action_item_background.png
--------------------------------------------------------------------------------
/floatingmenu/src/main/res/drawable/action_item_background_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/floatingmenu/src/main/res/layout/floating_action_item_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/floatingmenu/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/floatingmenu/src/main/res/values/dimen.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 2dip
4 | 8dip
5 | 18dip
6 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | VERSION_NAME=1.0.7
2 | VERSION_CODE=15
3 | GROUP=it.sephiroth.android.library.floatingmenu
4 |
5 | POM_DESCRIPTION=Floating Action Menu for Android. Inspired by the Google Plus floating menu
6 | POM_URL=https://github.com/sephiroth74/android-floating-action-menu
7 | POM_SCM_URL=https://github.com/sephiroth74/android-floating-action-menu
8 | POM_SCM_CONNECTION=scm:git@github.com:sephiroth74/android-floating-action-menu.git
9 | POM_SCM_DEV_CONNECTION=scm:git@github.com:sephiroth74/android-floating-action-menu.git
10 | POM_LICENCE_NAME=The Apache Software License, Version 2.0
11 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
12 | POM_LICENCE_DIST=repo
13 | POM_DEVELOPER_ID=sephiroth74
14 | POM_DEVELOPER_NAME=Alessandro Crugnola
15 | POM_DEVELOPER_EMAIL=alessandro.crugnola@gmail.com
16 | POM_DEVELOPER_URL=http://blog.sephiroth.it
17 | POM_DEVELOPER_ROLE=author
18 |
19 | ANDROID_BUILD_TARGET_SDK_VERSION=21
20 | ANDROID_BUILD_TOOLS_VERSION=21.1.2
21 | ANDROID_BUILD_SDK_VERSION=21
22 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':floatingmenu'
2 | include ':app'
3 |
4 |
--------------------------------------------------------------------------------