├── .gitignore
├── .idea
├── .name
├── checkstyle-idea.xml
├── compiler.xml
├── copyright
│ └── profiles_settings.xml
├── encodings.xml
├── gradle.xml
├── inspectionProfiles
│ ├── Project_Default.xml
│ └── profiles_settings.xml
├── misc.xml
├── modules.xml
├── runConfigurations.xml
└── vcs.xml
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── ym
│ │ └── floatmenu
│ │ └── path
│ │ └── demo
│ │ └── ApplicationTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── ym
│ │ │ └── floatmenu
│ │ │ └── path
│ │ │ └── demo
│ │ │ ├── FlHandler.java
│ │ │ ├── FloatLayout.java
│ │ │ └── MainActivity.java
│ └── res
│ │ ├── drawable
│ │ ├── composer_camera.png
│ │ ├── composer_music.png
│ │ ├── composer_place.png
│ │ ├── composer_sleep.png
│ │ ├── composer_thought.png
│ │ ├── composer_with.png
│ │ └── ic_launcher.png
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ └── main.xml
│ │ ├── mipmap-hdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxxhdpi
│ │ └── ic_launcher.png
│ │ ├── values-w820dp
│ │ └── dimens.xml
│ │ └── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── ym
│ └── floatmenu
│ └── path
│ └── demo
│ └── ExampleUnitTest.java
├── build.gradle
├── floatpathmenu
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── ym
│ │ └── floatmenu
│ │ └── path
│ │ └── ApplicationTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── ym
│ │ │ └── floatmenu
│ │ │ └── path
│ │ │ ├── ArcPathMenu.java
│ │ │ ├── PathMenu.java
│ │ │ ├── PathMenuActivity.java
│ │ │ ├── PathMenuLayout.java
│ │ │ ├── PathMenuService.java
│ │ │ └── RotateAndTranslateAnimation.java
│ └── res
│ │ ├── drawable-hdpi
│ │ ├── composer_button.png
│ │ ├── composer_camera.png
│ │ ├── composer_icn_plus.png
│ │ ├── composer_music.png
│ │ ├── composer_place.png
│ │ ├── composer_sleep.png
│ │ ├── composer_sun.png
│ │ ├── composer_thought.png
│ │ ├── composer_with.png
│ │ ├── friends_delete.png
│ │ └── ic_launcher.png
│ │ ├── drawable-mdpi
│ │ └── ic_launcher.png
│ │ ├── drawable-xhdpi
│ │ └── ic_launcher.png
│ │ ├── drawable-xxhdpi
│ │ └── ic_launcher.png
│ │ ├── drawable
│ │ └── button.xml
│ │ ├── layout
│ │ ├── activity_float.xml
│ │ └── float_menu.xml
│ │ └── values
│ │ ├── attrs.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── ym
│ └── floatmenu
│ └── path
│ └── ExampleUnitTest.java
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── pic
├── QQ图片20160918183808.jpg
├── QQ图片20160919111057.jpg
└── QQ图片20160919111211.jpg
├── projectFilesBackup
└── .idea
│ └── workspace.xml
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 |
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | FloatPathMenuSample
--------------------------------------------------------------------------------
/.idea/checkstyle-idea.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
18 |
19 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | 1.7
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # FloatPathMenuSample
2 | modified from daCapricorn/ArcMenu[ https://github.com/daCapricorn/ArcMenu](https://github.com/daCapricorn/ArcMenu)
3 |
4 |
5 | demo:
6 |
7 |
8 |
9 | 半径计算公式 :
10 | 半径==( 间距+子菜单大小) /2 x sin(夹角/2)
11 |
12 |
13 |
14 | 原理图:
15 |
16 |
17 |
18 |
19 | 原作者代码有一处bug,悬浮球居中时,子菜单数有两个重叠,原因是计算子菜单夹角时没考虑360度的情况,核心代码:
20 |
21 | final float perDegrees = Math.abs(mToDegrees - mFromDegrees) == 360 ?
22 | (mToDegrees - mFromDegrees) / (childCount)
23 | : (mToDegrees - mFromDegrees) / (childCount - 1);
24 |
25 |
26 | 需求来自 [linh47](https://github.com/linh47/PathMenu)的提问,在 [https://github.com/linh47/PathMenu](https://github.com/linh47/PathMenu)基础上增加悬浮可拖动功能,对应区域自动切换扇形区域,目前还有点小问题
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | - modified form [daCapricorn`s ArcMenu](https://github.com/daCapricorn/ArcMenu)
36 | - [https://github.com/crosg/FloatMenuSample](https://github.com/crosg/FloatMenuSample)
37 |
38 | - Contributors:
39 | [yiming](https://github.com/fanOfDemo) , [linh47](https://github.com/linh47/PathMenu)
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion '24'
6 |
7 | defaultConfig {
8 | applicationId "com.ym.floatmenu.path.demo"
9 | minSdkVersion 9
10 | targetSdkVersion 23
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(include: ['*.jar'], dir: 'libs')
24 | testCompile 'junit:junit:4.12'
25 | compile 'com.android.support:appcompat-v7:23.3.0'
26 | compile project(':floatpathmenu')
27 | }
28 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in D:\Android\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/ym/floatmenu/path/demo/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.ym.floatmenu.path.demo;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ym/floatmenu/path/demo/FlHandler.java:
--------------------------------------------------------------------------------
1 | package com.ym.floatmenu.path.demo;
2 |
3 | import android.view.LayoutInflater;
4 | import android.view.View;
5 | import android.view.ViewGroup;
6 |
7 | /**
8 | * 项目名称:androidUITraFloatLayout
9 | * 类描述:
10 | * 创建人:wengyiming
11 | * 创建时间:2016/9/14 17:13
12 | * 修改人:wengyiming
13 | * 修改时间:2016/9/14 17:13
14 | * 修改备注:
15 | */
16 | public interface FlHandler {
17 |
18 | View onCreateView(LayoutInflater inflater, ViewGroup viewGroup);
19 |
20 | void onViewCreated(FloatLayout parent);
21 |
22 | void onFloatMoving(boolean isMoving, float deltX, float deltY);
23 |
24 | void onMoveCompletedLocation(boolean isRight);
25 |
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ym/floatmenu/path/demo/FloatLayout.java:
--------------------------------------------------------------------------------
1 | package com.ym.floatmenu.path.demo;
2 |
3 | import android.content.Context;
4 | import android.graphics.PixelFormat;
5 | import android.os.Build;
6 | import android.util.AttributeSet;
7 | import android.util.DisplayMetrics;
8 | import android.view.Gravity;
9 | import android.view.LayoutInflater;
10 | import android.view.MotionEvent;
11 | import android.view.View;
12 | import android.view.WindowManager;
13 | import android.widget.FrameLayout;
14 |
15 | /**
16 | * 项目名称:androidUITraFloatLayout
17 | * 类描述:
18 | * 创建人:wengyiming
19 | * 创建时间:2016/9/14 17:12
20 | * 修改人:wengyiming
21 | * 修改时间:2016/9/14 17:12
22 | * 修改备注:
23 | */
24 | public class FloatLayout extends FrameLayout implements View.OnTouchListener {
25 | //WindowMananger的params,控制这个值可以将自定义的view设置到窗口管理器中
26 | private WindowManager.LayoutParams mWmParams;
27 | private WindowManager mWindowManager;//当前view的窗口管理器
28 | private Context mContext;//一个全局上下文,demo使用的service的上下文
29 |
30 | private float mTouchStartX;//记录首次按下的位置x
31 | private float mTouchStartY;//记录首次按下的位置y
32 |
33 | private int mScreenWidth;//屏幕宽度
34 | private int mScreenHeight;//屏幕高度
35 | private boolean mDraging;//是否拖动中
36 |
37 |
38 | private FlHandler mFlHandler;
39 |
40 | public FloatLayout(Context context) {
41 | super(context);
42 | init(context);
43 | }
44 |
45 | public FloatLayout(Context context, AttributeSet attrs) {
46 | super(context, attrs);
47 | init(context);
48 | }
49 |
50 | public FloatLayout(Context context, AttributeSet attrs, int defStyleAttr) {
51 | super(context, attrs, defStyleAttr);
52 | init(context);
53 | }
54 |
55 |
56 | private void init(Context context) {
57 | this.mContext = context;
58 | initVM();
59 | }
60 |
61 |
62 | public void setFlHandler(FlHandler flHandler) {
63 | mFlHandler = flHandler;
64 | View rootView = mFlHandler.onCreateView(LayoutInflater.from(mContext), this);//将xml布局导入当前View
65 | // rootView.setOnTouchListener(this);
66 | rootView.measure(MeasureSpec.makeMeasureSpec(0,
67 | MeasureSpec.UNSPECIFIED), MeasureSpec
68 | .makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
69 | addView(rootView);
70 | //将当前view设置为前置窗口
71 | bringToFront();
72 | mWindowManager.addView(this, mWmParams);
73 | mFlHandler.onViewCreated(this);
74 | }
75 |
76 | public void attachToView(View view){
77 | view.setOnTouchListener(this);
78 | }
79 |
80 |
81 |
82 | private void initVM() {
83 | mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);//获取系统的窗口服务
84 | DisplayMetrics dm = new DisplayMetrics();
85 | mWindowManager.getDefaultDisplay().getMetrics(dm);
86 | mScreenWidth = dm.widthPixels;//根据当前屏幕信息拿到屏幕的宽高
87 | mScreenHeight = dm.heightPixels;
88 |
89 | this.mWmParams = new WindowManager.LayoutParams();//获取窗口参数
90 |
91 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
92 | mWmParams.type = WindowManager.LayoutParams.TYPE_TOAST;//等于API19或API19以下需要指定窗口参数type值为TYPE_TOAST才可以作为悬浮控件显示出来
93 | } else {
94 | mWmParams.type = WindowManager.LayoutParams.TYPE_PHONE;//API19以上侧只需指定为TYPE_PHONE即可
95 | }
96 | mWmParams.format = PixelFormat.RGBA_8888;//当前窗口的像素格式为RGBA_8888,即为最高质量
97 |
98 |
99 | //NOT_FOCUSABLE可以是悬浮控件可以响应事件,LAYOUT_IN_SCREEN可以指定悬浮球指定在屏幕内,部分虚拟按键的手机,虚拟按键隐藏时,虚拟按键的位置则属于屏幕内,此时悬浮球会出现在原虚拟按键的位置
100 | mWmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
101 |
102 | //默认指定位置在屏幕的左上方,可以根据需要自己修改
103 | mWmParams.gravity = Gravity.LEFT | Gravity.TOP;
104 |
105 | //默认指定的横坐标为屏幕最左边
106 | mWmParams.x = 0;
107 |
108 | //默认指定的纵坐标为屏幕高度的10分之一,这里只是大概约束,因为上的flags参数限制,悬浮球不会出现在屏幕外
109 | mWmParams.y = mScreenHeight / 10;
110 |
111 | //宽度指定为内容自适应
112 | mWmParams.width = LayoutParams.WRAP_CONTENT;
113 | mWmParams.height = LayoutParams.WRAP_CONTENT;
114 | }
115 |
116 | @Override
117 | public boolean onTouch(View view, MotionEvent event) {
118 | int x = (int) event.getRawX();
119 | int y = (int) event.getRawY();
120 |
121 | switch (event.getAction()) {
122 | case MotionEvent.ACTION_DOWN:
123 | mTouchStartX = event.getX();
124 | mTouchStartY = event.getY();
125 | mWmParams.alpha = 1f;
126 | mWindowManager.updateViewLayout(this, mWmParams);
127 | mDraging = false;
128 | break;
129 | case MotionEvent.ACTION_MOVE:
130 | float mMoveStartX = event.getX();
131 | float mMoveStartY = event.getY();
132 |
133 | float deltX = Math.abs(mTouchStartX - mMoveStartX);
134 | float deltY = Math.abs(mTouchStartY - mMoveStartY);
135 | if (deltX > 3 && deltY > 3) {
136 | mDraging = true;
137 | mWmParams.x = (int) (x - mTouchStartX);
138 | mWmParams.y = (int) (y - mTouchStartY);
139 | mWindowManager.updateViewLayout(this, mWmParams);
140 | mFlHandler.onFloatMoving(mDraging, deltX > 3 ? deltX : 0, deltY > 3 ? deltY : 0);
141 | return true;
142 | }
143 | break;
144 | case MotionEvent.ACTION_UP:
145 | mDraging = false;
146 | case MotionEvent.ACTION_CANCEL:
147 | if (mWmParams.x >= mScreenWidth / 2) {
148 | mWmParams.x = mScreenWidth;
149 | mFlHandler.onMoveCompletedLocation(true);
150 | } else if (mWmParams.x < mScreenWidth / 2) {
151 | mWmParams.x = 0;
152 | mFlHandler.onMoveCompletedLocation(false);
153 | }
154 | mWindowManager.updateViewLayout(this, mWmParams);
155 | mTouchStartX = mTouchStartY = 0;
156 | return false;
157 | }
158 | return false;
159 | }
160 |
161 |
162 | }
163 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ym/floatmenu/path/demo/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.ym.floatmenu.path.demo;
2 |
3 | import com.ym.floatmenu.path.PathMenu;
4 |
5 | import android.os.Bundle;
6 | import android.support.v7.app.AppCompatActivity;
7 | import android.view.View;
8 |
9 |
10 | public class MainActivity extends AppCompatActivity {
11 |
12 | @Override
13 | protected void onCreate(Bundle savedInstanceState) {
14 | super.onCreate(savedInstanceState);
15 | PathMenu pathMenu = new PathMenu(this.getApplicationContext());
16 | pathMenu.setVisibility(View.VISIBLE);
17 |
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/composer_camera.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatPathMenuSample/212c65608bb41c99e27b4cd5a331aaa571ef880c/app/src/main/res/drawable/composer_camera.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/composer_music.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatPathMenuSample/212c65608bb41c99e27b4cd5a331aaa571ef880c/app/src/main/res/drawable/composer_music.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/composer_place.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatPathMenuSample/212c65608bb41c99e27b4cd5a331aaa571ef880c/app/src/main/res/drawable/composer_place.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/composer_sleep.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatPathMenuSample/212c65608bb41c99e27b4cd5a331aaa571ef880c/app/src/main/res/drawable/composer_sleep.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/composer_thought.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatPathMenuSample/212c65608bb41c99e27b4cd5a331aaa571ef880c/app/src/main/res/drawable/composer_thought.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/composer_with.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatPathMenuSample/212c65608bb41c99e27b4cd5a331aaa571ef880c/app/src/main/res/drawable/composer_with.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatPathMenuSample/212c65608bb41c99e27b4cd5a331aaa571ef880c/app/src/main/res/drawable/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
13 |
19 |
20 |
24 |
25 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatPathMenuSample/212c65608bb41c99e27b4cd5a331aaa571ef880c/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatPathMenuSample/212c65608bb41c99e27b4cd5a331aaa571ef880c/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatPathMenuSample/212c65608bb41c99e27b4cd5a331aaa571ef880c/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatPathMenuSample/212c65608bb41c99e27b4cd5a331aaa571ef880c/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatPathMenuSample/212c65608bb41c99e27b4cd5a331aaa571ef880c/app/src/main/res/mipmap-xxxhdpi/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 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | FloatPathMenuSample
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/test/java/com/ym/floatmenu/path/demo/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.ym.floatmenu.path.demo;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 | @Test
12 | public void addition_isCorrect() throws Exception {
13 | assertEquals(4, 2 + 2);
14 | }
15 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:2.2.0'
9 |
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | jcenter()
18 | }
19 | }
20 |
21 | task clean(type: Delete) {
22 | delete rootProject.buildDir
23 | }
24 |
--------------------------------------------------------------------------------
/floatpathmenu/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/floatpathmenu/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion '24'
6 |
7 | defaultConfig {
8 | minSdkVersion 9
9 | targetSdkVersion 23
10 | versionCode 1
11 | versionName "1.0"
12 | }
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 | }
20 |
21 | dependencies {
22 | compile fileTree(dir: 'libs', include: ['*.jar'])
23 | testCompile 'junit:junit:4.12'
24 | compile 'com.android.support:appcompat-v7:23.3.0'
25 | }
26 |
--------------------------------------------------------------------------------
/floatpathmenu/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in D:\Android\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/floatpathmenu/src/androidTest/java/com/ym/floatmenu/path/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.ym.floatmenu.path;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/floatpathmenu/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/floatpathmenu/src/main/java/com/ym/floatmenu/path/ArcPathMenu.java:
--------------------------------------------------------------------------------
1 | package com.ym.floatmenu.path;
2 |
3 | import android.content.Context;
4 | import android.graphics.Bitmap;
5 | import android.graphics.Canvas;
6 | import android.graphics.Color;
7 | import android.graphics.Paint;
8 | import android.graphics.PixelFormat;
9 | import android.graphics.RectF;
10 | import android.graphics.drawable.BitmapDrawable;
11 | import android.graphics.drawable.Drawable;
12 | import android.os.Build;
13 | import android.util.AttributeSet;
14 | import android.util.DisplayMetrics;
15 | import android.view.Gravity;
16 | import android.view.MotionEvent;
17 | import android.view.View;
18 | import android.view.WindowManager;
19 | import android.widget.FrameLayout;
20 |
21 | /**
22 | * 项目名称:FloatPathMenuSample
23 | * 类描述:
24 | * 创建人:wengyiming
25 | * 创建时间:2016/9/20 15:35
26 | * 修改人:wengyiming
27 | * 修改时间:2016/9/20 15:35
28 | * 修改备注:
29 | */
30 |
31 | public class ArcPathMenu extends View implements View.OnTouchListener {
32 |
33 | private static final int MAX_DURATION = 3000; //最大间隔时长
34 | private static final int MIN_DURATION = 500; //最小间隔时长
35 | private static final int MIN_RADIUS = 100;
36 |
37 | public static final int LEFT_TOP = 1;
38 | public static final int CENTER_TOP = 2;
39 | public static final int RIGHT_TOP = 3;
40 | public static final int LEFT_CENTER = 4;
41 | public static final int CENTER = 5;
42 | public static final int RIGHT_CENTER = 6;
43 | public static final int LEFT_BOTTOM = 7;
44 | public static final int CENTER_BOTTOM = 8;
45 | public static final int RIGHT_BOTTOM = 9;
46 |
47 |
48 | private static final int[] mColors = new int[]{0xB07ECBDA, 0xB0E6A92C, 0xB0D6014D, 0xB05ABA94};
49 | private static final int[] ITEM_DRAWABLES = {R.drawable.composer_camera,
50 | R.drawable.composer_music, R.drawable.composer_place,
51 | R.drawable.composer_sleep, R.drawable.composer_thought,
52 | R.drawable.composer_with};
53 |
54 |
55 | private WindowManager mWindowManager;// 当前view的窗口管理器
56 | private WindowManager.LayoutParams mWmParams; // WindowMananger的params,控制这个值可以将自定义的view设置到窗口管理器中
57 | private float mTouchStartX;// 记录首次按下的位置x
58 | private float mTouchStartY;// 记录首次按下的位置y
59 | private int mScreenWidth;// 屏幕宽度
60 | private int mScreenHeight;// 屏幕高度
61 | private boolean mDraging;// 是否拖动中
62 |
63 | private int position;// 按钮的位置
64 | private int mRadius = MIN_RADIUS;// 中心菜单圆点到子菜单中心的距离
65 | private boolean mExpanded = false;
66 | private Bitmap logo;
67 |
68 |
69 | private Paint mPaint;
70 | private int mCanvasAngle; //Canvas旋转角度
71 | private int mDuration = MIN_DURATION;//动画间隔时长
72 | private Context mContext;
73 |
74 | public ArcPathMenu(Context context) {
75 | super(context);
76 | init(context);
77 | }
78 |
79 | public ArcPathMenu(Context context, AttributeSet attrs) {
80 | super(context, attrs);
81 | init(context);
82 | }
83 |
84 | public ArcPathMenu(Context context, AttributeSet attrs, int defStyleAttr) {
85 | super(context, attrs, defStyleAttr);
86 | init(context);
87 | }
88 |
89 | private void init(Context context) {
90 | this.mContext = context;
91 | initWindowsManager(context);
92 | Drawable drawable = context.getResources().getDrawable(R.drawable.composer_icn_plus);
93 | BitmapDrawable bd = (BitmapDrawable) drawable;
94 | logo = bd.getBitmap();
95 |
96 | mPaint = new Paint();
97 | mPaint.setAntiAlias(true);
98 | mPaint.setColor(mColors[0]);
99 | mPaint.setStrokeCap(Paint.Cap.ROUND);
100 |
101 | mRadius = logo.getWidth() * 2 + mRadius;
102 |
103 | }
104 |
105 | @Override
106 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
107 | int sizeW = 0;
108 | int sizeH = 0;
109 |
110 | sizeW = mRadius;
111 | sizeH = mRadius;
112 | // switch (position) {
113 | // case LEFT_TOP:
114 | // sizeW = mRadius;
115 | // sizeH = mRadius;
116 | // break;
117 | // case LEFT_CENTER:
118 | // sizeW = mRadius;
119 | // sizeH = mRadius;
120 | // break;
121 | // case LEFT_BOTTOM:
122 | // sizeW = mRadius;
123 | // sizeH = mRadius;
124 | // break;
125 | // default:
126 | // sizeW = mRadius;
127 | // sizeH = mRadius;
128 | // break;
129 | // }
130 | setMeasuredDimension(sizeW, sizeH);
131 | }
132 |
133 | @Override
134 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
135 | int centerX = getWidth() / 2;
136 | int centerY = getHeight() / 2;
137 |
138 | left = centerX - logo.getWidth() / 2;
139 | right = centerX + logo.getWidth() / 2;
140 | top = centerY - logo.getHeight() / 2;
141 | bottom = centerY + logo.getHeight() / 2;
142 | super.onLayout(changed, left, top, right, bottom);
143 |
144 | }
145 |
146 | @Override
147 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
148 | super.onSizeChanged(w, h, oldw, oldh);
149 | }
150 |
151 | @Override
152 | protected void onDraw(Canvas canvas) {
153 | super.onDraw(canvas);
154 | int centerX = getWidth() / 2;
155 | int centerY = getHeight() / 2;
156 | canvas.save();
157 |
158 | int left = centerX - getWidth() / 2;
159 | int top = centerY - logo.getHeight() / 2;
160 | canvas.drawBitmap(logo, left, top, mPaint);
161 | canvas.restore();
162 |
163 |
164 | canvas.save();
165 | mPaint.setColor(Color.GREEN);
166 | mPaint.setStyle(Paint.Style.STROKE);
167 |
168 |
169 | RectF rect = new RectF(centerX - getWidth() / 2, centerX - getWidth() / 2,
170 | centerY + getWidth() / 2, centerY + getWidth() / 2);
171 |
172 | canvas.drawArc(rect, 270, 180, false, mPaint);
173 | canvas.restore();
174 |
175 | // for (int subMenuImg : ITEM_DRAWABLES) {
176 | // Drawable drawable = mContext.getResources().getDrawable(subMenuImg);
177 | // BitmapDrawable bd = (BitmapDrawable) drawable;
178 | // Bitmap subMenuBitmap = bd.getBitmap();
179 | //
180 | // Rect frame = computeChildFrame(left, top, mRadius, degrees,
181 | // logo.getWidth());
182 | //
183 | // degrees += perDegrees;
184 | // canvas.drawBitmap(subMenuBitmap, frame.left, frame.top, mPaint);
185 | // }
186 | }
187 |
188 | private void initWindowsManager(Context context) {
189 | mWindowManager = (WindowManager) context
190 | .getSystemService(Context.WINDOW_SERVICE);// 获取系统的窗口服务
191 | DisplayMetrics dm = new DisplayMetrics();
192 | mWindowManager.getDefaultDisplay().getMetrics(dm);
193 | mScreenWidth = dm.widthPixels;// 根据当前屏幕信息拿到屏幕的宽高
194 | mScreenHeight = dm.heightPixels;
195 | this.mWmParams = new WindowManager.LayoutParams();// 获取窗口参数
196 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
197 | mWmParams.type = WindowManager.LayoutParams.TYPE_TOAST;// 等于API19或API19以下需要指定窗口参数type值为TYPE_TOAST才可以作为悬浮控件显示出来
198 | } else {
199 | mWmParams.type = WindowManager.LayoutParams.TYPE_PHONE;// API19以上侧只需指定为TYPE_PHONE即可
200 | }
201 |
202 |
203 | mWmParams.format = PixelFormat.RGBA_8888;// 当前窗口的像素格式为RGBA_8888,即为最高质量
204 |
205 | // NOT_FOCUSABLE可以是悬浮控件可以响应事件,LAYOUT_IN_SCREEN可以指定悬浮球指定在屏幕内,部分虚拟按键的手机,虚拟按键隐藏时,虚拟按键的位置则属于屏幕内,此时悬浮球会出现在原虚拟按键的位置
206 | mWmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
207 | // | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
208 | // 默认指定位置在屏幕的左上方,可以根据需要自己修改
209 | mWmParams.gravity = Gravity.LEFT | Gravity.TOP;
210 | position = 1;
211 | // 默认指定的横坐标为屏幕一半
212 | mWmParams.x = 0;
213 | // 默认指定的纵坐标为屏幕高度的一半,这里只是大概约束,因为上的flags参数限制,悬浮球不会出现在屏幕外
214 | mWmParams.y = 0;
215 |
216 | // 宽度指定为内容自适应
217 | mWmParams.width = FrameLayout.LayoutParams.WRAP_CONTENT;
218 | mWmParams.height = FrameLayout.LayoutParams.WRAP_CONTENT;
219 | setBackgroundColor(context.getColor(android.R.color.holo_red_light));
220 | mWindowManager.addView(this, mWmParams);
221 |
222 | setOnTouchListener(this);
223 | }
224 |
225 | /**
226 | * 触摸事件
227 | */
228 | @Override
229 | public boolean onTouch(View v, MotionEvent event) {
230 | float x = (int) event.getRawX();
231 | float y = (int) event.getRawY();
232 | switch (event.getAction()) {
233 | case MotionEvent.ACTION_DOWN:
234 | mTouchStartX = event.getX();
235 | mTouchStartY = event.getY();
236 | mWmParams.alpha = 1f;
237 | mWindowManager.updateViewLayout(this, mWmParams);
238 | mDraging = false;
239 | break;
240 |
241 | case MotionEvent.ACTION_MOVE:
242 | float mMoveStartX = event.getX();
243 | float mMoveStartY = event.getY();
244 | if (Math.abs(mTouchStartX - mMoveStartX) > 3
245 | && Math.abs(mTouchStartY - mMoveStartY) > 3) {
246 | mDraging = true;
247 | mWmParams.x = (int) (x - mTouchStartX);
248 | mWmParams.y = (int) (y - mTouchStartY);
249 | mWindowManager.updateViewLayout(this, mWmParams);
250 | return false;
251 | }
252 | break;
253 |
254 | case MotionEvent.ACTION_UP:
255 | case MotionEvent.ACTION_CANCEL:
256 | refreshMenuXY();
257 | break;
258 | }
259 | return false;
260 | }
261 |
262 | private void refreshMenuXY() {
263 | int wmX = mWmParams.x;
264 | int wmY = mWmParams.y;
265 | int halfLayoutW = 0;
266 | int halfLayoutH = 0;
267 |
268 | if (wmX < (mScreenWidth / 3)) //左边 竖区域
269 | {
270 | mWmParams.x = 0;
271 | if (wmY < mScreenHeight / 3) {
272 | position = LEFT_TOP;//左上
273 | mWmParams.y = 0;
274 | } else if (wmY >= mScreenHeight / 3
275 | && wmY <= mScreenHeight * 2 / 3) {
276 | position = LEFT_CENTER;//左中
277 | mWmParams.y = mScreenHeight / 2 - halfLayoutH;
278 | } else if (wmY > mScreenHeight * 2 / 3 && wmY <= mScreenHeight) {
279 | position = LEFT_BOTTOM;//左下
280 | mWmParams.y = mScreenHeight;
281 | }
282 | } else if (wmX >= mScreenWidth / 3 && wmX <= mScreenWidth * 2 / 3)//中间 竖区域
283 | {
284 | mWmParams.x = mScreenWidth / 2 - halfLayoutW;
285 | if (wmY < mScreenHeight / 3) {
286 | position = CENTER_TOP;//中上
287 | mWmParams.y = 0;
288 | } else if (wmY >= mScreenHeight / 3
289 | && wmY <= mScreenHeight * 2 / 3) {
290 | position = CENTER;//中
291 | mWmParams.y = mScreenHeight - halfLayoutH;
292 | } else if (wmY > mScreenHeight * 2 / 3) {
293 | position = CENTER_BOTTOM;//中下
294 | mWmParams.y = mScreenHeight;
295 | }
296 | } else if (wmX > mScreenWidth * 2 / 3 && wmX <= mScreenWidth)//右边竖区域
297 | {
298 | mWmParams.x = mScreenWidth;
299 | if (wmY < mScreenHeight / 3) {
300 | position = RIGHT_TOP;//上右
301 | mWmParams.y = 0;
302 | } else if (wmY >= mScreenHeight / 3
303 | && wmY <= mScreenHeight * 2 / 3) {
304 | position = RIGHT_CENTER;//中右
305 | mWmParams.y = mScreenHeight / 2 - halfLayoutH;
306 | } else if (wmY > mScreenHeight * 2 / 3 && wmY <= mScreenHeight) {
307 | position = RIGHT_BOTTOM;//下右
308 | mWmParams.y = mScreenHeight;
309 | }
310 | }
311 | mWindowManager.updateViewLayout(this, mWmParams);
312 | mTouchStartX = mTouchStartY = 0;
313 | }
314 |
315 | }
316 |
--------------------------------------------------------------------------------
/floatpathmenu/src/main/java/com/ym/floatmenu/path/PathMenu.java:
--------------------------------------------------------------------------------
1 | package com.ym.floatmenu.path;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.graphics.PixelFormat;
6 | import android.os.Build;
7 | import android.util.AttributeSet;
8 | import android.util.DisplayMetrics;
9 | import android.view.Gravity;
10 | import android.view.LayoutInflater;
11 | import android.view.MotionEvent;
12 | import android.view.View;
13 | import android.view.View.OnTouchListener;
14 | import android.view.WindowManager;
15 | import android.view.animation.AlphaAnimation;
16 | import android.view.animation.Animation;
17 | import android.view.animation.Animation.AnimationListener;
18 | import android.view.animation.AnimationSet;
19 | import android.view.animation.DecelerateInterpolator;
20 | import android.view.animation.RotateAnimation;
21 | import android.view.animation.ScaleAnimation;
22 | import android.widget.FrameLayout;
23 | import android.widget.ImageView;
24 | import android.widget.Toast;
25 |
26 | /**
27 | * 自定义菜单
28 | *
29 | * @author 何凌波
30 | */
31 | public class PathMenu extends FrameLayout implements OnTouchListener {
32 | FrameLayout controlLayout;
33 | private PathMenuLayout mPathMenuLayout;
34 | private ImageView mHintView;// 中心按钮显示图片
35 | private WindowManager mWindowManager;// 当前view的窗口管理器
36 | // WindowMananger的params,控制这个值可以将自定义的view设置到窗口管理器中
37 | private WindowManager.LayoutParams mWmParams;
38 | private float mTouchStartX;// 记录首次按下的位置x
39 | private float mTouchStartY;// 记录首次按下的位置y
40 |
41 | private int mScreenWidth;// 屏幕宽度
42 | private int mScreenHeight;// 屏幕高度
43 | private boolean mDraging;// 是否拖动中
44 |
45 | private Context mContext;
46 |
47 | private int position;// 按钮的位置
48 | public static final int LEFT_TOP = 1;
49 | public static final int CENTER_TOP = 2;
50 | public static final int RIGHT_TOP = 3;
51 | public static final int LEFT_CENTER = 4;
52 | public static final int CENTER = 5;
53 | public static final int RIGHT_CENTER = 6;
54 | public static final int LEFT_BOTTOM = 7;
55 | public static final int CENTER_BOTTOM = 8;
56 | public static final int RIGHT_BOTTOM = 9;
57 |
58 |
59 | private static final int[] ITEM_DRAWABLES = {R.drawable.composer_camera,
60 | R.drawable.composer_music, R.drawable.composer_place,
61 | R.drawable.composer_sleep, R.drawable.composer_thought,
62 | R.drawable.composer_with};
63 |
64 | public PathMenu(Context context) {
65 | super(context);
66 | this.mContext = context;
67 | init(mContext);
68 | }
69 |
70 | public PathMenu(Context context, AttributeSet attrs) {
71 | super(context, attrs);
72 | this.mContext = context;
73 | init(mContext);
74 | applyAttrs(attrs);
75 | }
76 |
77 |
78 | public static class Builder {
79 | private int[] mDrables;
80 | private boolean showInFullSceen = true;
81 | }
82 |
83 | /**
84 | * 初始化中心按钮布局,加载布局文件,设置触摸事件
85 | */
86 | private void init(Context context) {
87 | LayoutInflater li = (LayoutInflater) context
88 | .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
89 | li.inflate(R.layout.float_menu, this);
90 | setClipToPadding(false);
91 | setClipToPadding(false);
92 | mWindowManager = (WindowManager) context
93 | .getSystemService(Context.WINDOW_SERVICE);// 获取系统的窗口服务
94 | DisplayMetrics dm = new DisplayMetrics();
95 | mWindowManager.getDefaultDisplay().getMetrics(dm);
96 | mScreenWidth = dm.widthPixels;// 根据当前屏幕信息拿到屏幕的宽高
97 | mScreenHeight = dm.heightPixels - getStatusBarHeight();
98 |
99 | this.mWmParams = new WindowManager.LayoutParams();// 获取窗口参数
100 |
101 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
102 | mWmParams.type = WindowManager.LayoutParams.TYPE_TOAST;// 等于API19或API19以下需要指定窗口参数type值为TYPE_TOAST才可以作为悬浮控件显示出来
103 | } else {
104 | mWmParams.type = WindowManager.LayoutParams.TYPE_PHONE;// API19以上侧只需指定为TYPE_PHONE即可
105 | }
106 | mWmParams.format = PixelFormat.RGBA_8888;// 当前窗口的像素格式为RGBA_8888,即为最高质量
107 |
108 | // NOT_FOCUSABLE可以是悬浮控件可以响应事件,LAYOUT_IN_SCREEN可以指定悬浮球指定在屏幕内,部分虚拟按键的手机,虚拟按键隐藏时,虚拟按键的位置则属于屏幕内,此时悬浮球会出现在原虚拟按键的位置
109 | mWmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
110 | | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
111 | // 默认指定位置在屏幕的左上方,可以根据需要自己修改
112 | mWmParams.gravity = Gravity.LEFT | Gravity.TOP;
113 | position = 1;
114 | // 默认指定的横坐标为屏幕一半
115 | mWmParams.x = 0;
116 | // 默认指定的纵坐标为屏幕高度的一半,这里只是大概约束,因为上的flags参数限制,悬浮球不会出现在屏幕外
117 | mWmParams.y = 0;
118 |
119 | // 宽度指定为内容自适应
120 | mWmParams.width = LayoutParams.WRAP_CONTENT;
121 | mWmParams.height = LayoutParams.WRAP_CONTENT;
122 |
123 | mPathMenuLayout = (PathMenuLayout) findViewById(R.id.item_layout);
124 | controlLayout = (FrameLayout) findViewById(R.id.control_layout);
125 | controlLayout.setClickable(true);
126 | controlLayout.setOnTouchListener(this);
127 |
128 | mHintView = (ImageView) findViewById(R.id.control_hint);
129 | mWindowManager.addView(this, mWmParams);
130 | initPathMenu(this, ITEM_DRAWABLES);// 初始化子菜单
131 | controlLayout.setOnClickListener(new OnClickListener() {
132 | @Override
133 | public void onClick(View v) {
134 | if (!mDraging) {
135 | mHintView
136 | .startAnimation(createHintSwitchAnimation(mPathMenuLayout
137 | .isExpanded()));
138 | mPathMenuLayout.switchState(true, position);
139 | }
140 | }
141 | });
142 | }
143 |
144 | /**
145 | * 应用自定义属性,设置弧度、子菜单项大小
146 | */
147 | private void applyAttrs(AttributeSet attrs) {
148 | if (attrs != null) {
149 | TypedArray a = getContext().obtainStyledAttributes(attrs,
150 | R.styleable.ArcLayout, 0, 0);
151 |
152 | float fromDegrees = a.getFloat(R.styleable.ArcLayout_fromDegrees,
153 | PathMenuLayout.DEFAULT_FROM_DEGREES);
154 | float toDegrees = a.getFloat(R.styleable.ArcLayout_toDegrees,
155 | PathMenuLayout.DEFAULT_TO_DEGREES);
156 | mPathMenuLayout.setArc(fromDegrees, toDegrees);
157 |
158 | int defaultChildSize = mPathMenuLayout.getChildSize();
159 |
160 | int newChildSize = a.getDimensionPixelSize(
161 | R.styleable.ArcLayout_childSize, defaultChildSize);
162 | mPathMenuLayout.setChildSize(newChildSize);
163 | a.recycle();
164 | }
165 | }
166 |
167 | /**
168 | * 添加子菜单项和对应的点击事件
169 | */
170 | public void addItem(View item, OnClickListener listener) {
171 | mPathMenuLayout.addView(item);
172 | item.setOnClickListener(getItemClickListener(listener));
173 | }
174 |
175 | /**
176 | * 子菜单项的点击事件
177 | */
178 | private OnClickListener getItemClickListener(final OnClickListener listener) {
179 | return new OnClickListener() {
180 | // 点击,child放大,其他child消失
181 | @Override
182 | public void onClick(final View viewClicked) {
183 | Animation animation = bindItemAnimation(viewClicked, true, 400);
184 | animation.setAnimationListener(new AnimationListener() {
185 |
186 | @Override
187 | public void onAnimationStart(Animation animation) {
188 |
189 | }
190 |
191 | @Override
192 | public void onAnimationRepeat(Animation animation) {
193 |
194 | }
195 |
196 | @Override
197 | public void onAnimationEnd(Animation animation) {
198 | postDelayed(new Runnable() {
199 |
200 | @Override
201 | public void run() {
202 | itemDidDisappear();
203 | }
204 | }, 0);
205 | }
206 | });
207 | // child 被点击的时候,自身放大,其他child消失的效果
208 | final int itemCount = mPathMenuLayout.getChildCount();
209 | for (int i = 0; i < itemCount; i++) {
210 | View item = mPathMenuLayout.getChildAt(i);
211 | if (viewClicked != item) {
212 | bindItemAnimation(item, false, 300);
213 | }
214 | }
215 | // 中心控制点动画 旋转
216 | mPathMenuLayout.invalidate();
217 | mHintView.startAnimation(createHintSwitchAnimation(true));
218 |
219 | if (listener != null) {
220 | listener.onClick(viewClicked);
221 | }
222 | }
223 | };
224 | }
225 |
226 | /**
227 | * 绑定子菜单项动画
228 | */
229 | private Animation bindItemAnimation(final View child,
230 | final boolean isClicked, final long duration) {
231 | Animation animation = createItemDisapperAnimation(duration, isClicked);
232 | child.setAnimation(animation);
233 |
234 | return animation;
235 | }
236 |
237 | /**
238 | * 子菜单项关闭时消失
239 | */
240 | private void itemDidDisappear() {
241 | final int itemCount = mPathMenuLayout.getChildCount();
242 | for (int i = 0; i < itemCount; i++) {
243 | View item = mPathMenuLayout.getChildAt(i);
244 | item.clearAnimation();
245 | }
246 |
247 | mPathMenuLayout.switchState(false, position);
248 | }
249 |
250 | /**
251 | * 子菜单消失动画
252 | */
253 | private static Animation createItemDisapperAnimation(final long duration,
254 | final boolean isClicked) {
255 | AnimationSet animationSet = new AnimationSet(true);
256 | // 放大缩小动画,isClicked 为true,放大两倍;为false,缩小至0
257 | animationSet.addAnimation(new ScaleAnimation(1.0f, isClicked ? 2.0f
258 | : 0.0f, 1.0f, isClicked ? 2.0f : 0.0f,
259 | Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
260 | 0.5f));
261 | // Alpha改为0,child消失
262 | animationSet.addAnimation(new AlphaAnimation(1.0f, 0.0f));
263 |
264 | animationSet.setDuration(duration);
265 | animationSet.setInterpolator(new DecelerateInterpolator());
266 | animationSet.setFillAfter(true);
267 |
268 | return animationSet;
269 | }
270 |
271 | /**
272 | * 中心按钮点击旋转45度动画
273 | */
274 | private static Animation createHintSwitchAnimation(final boolean expanded) {
275 | Animation animation = new RotateAnimation(expanded ? 45 : 0,
276 | expanded ? 0 : 45, Animation.RELATIVE_TO_SELF, 0.5f,
277 | Animation.RELATIVE_TO_SELF, 0.5f);
278 | animation.setStartOffset(0);
279 | animation.setDuration(100);
280 | animation.setInterpolator(new DecelerateInterpolator());
281 | animation.setFillAfter(true);
282 |
283 | return animation;
284 | }
285 |
286 | /**
287 | * 触摸事件
288 | */
289 | @Override
290 | public boolean onTouch(View v, MotionEvent event) {
291 | float x = (int) event.getRawX();
292 | float y = (int) event.getRawY();
293 | switch (event.getAction()) {
294 | case MotionEvent.ACTION_DOWN:
295 | mTouchStartX = event.getX();
296 | mTouchStartY = event.getY();
297 | mWmParams.alpha = 1f;
298 | mWindowManager.updateViewLayout(this, mWmParams);
299 | mDraging = false;
300 | break;
301 |
302 | case MotionEvent.ACTION_MOVE:
303 | float mMoveStartX = event.getX();
304 | float mMoveStartY = event.getY();
305 | if (Math.abs(mTouchStartX - mMoveStartX) > 3
306 | && Math.abs(mTouchStartY - mMoveStartY) > 3) {
307 | // if (mPathMenuLayout.isExpanded()) {
308 | // mPathMenuLayout.switchState(false);
309 | // }
310 | mDraging = true;
311 | mWmParams.x = (int) (x - mTouchStartX);
312 | mWmParams.y = (int) (y - mTouchStartY);
313 | mWindowManager.updateViewLayout(this, mWmParams);
314 | return false;
315 | }
316 | break;
317 |
318 | case MotionEvent.ACTION_UP:
319 | case MotionEvent.ACTION_CANCEL:
320 | int wmX = mWmParams.x;
321 | int wmY = mWmParams.y;
322 | if (wmX <= mScreenWidth / 3) //左边 竖区域
323 | {
324 | mWmParams.x = 0;
325 | if (wmY <= mScreenHeight / 3) {
326 | position = LEFT_TOP;//左上
327 | mWmParams.y = 0;
328 | } else if (wmY > mScreenHeight / 3
329 | && wmY < mScreenHeight * 2 / 3) {
330 | position = LEFT_CENTER;//左中
331 | mWmParams.y = mScreenHeight / 2;
332 | } else if (wmY >= mScreenHeight * 2 / 3 && wmY < mScreenHeight) {
333 | position = LEFT_BOTTOM;//左下
334 | mWmParams.y = mScreenHeight;
335 | }
336 | } else if (wmX >= mScreenWidth / 3 && wmX <= mScreenWidth * 2 / 3)//中间 竖区域
337 | {
338 | mWmParams.x = mScreenWidth / 2;
339 | if (wmY <= mScreenHeight / 3) {
340 | position = CENTER_TOP;//中上
341 | mWmParams.y = 0;
342 | } else if (wmY > mScreenHeight / 3
343 | && wmY < mScreenHeight * 2 / 3) {
344 | position = CENTER;//中
345 | mWmParams.y = mScreenHeight / 2;
346 | } else if (wmY >= mScreenHeight * 2 / 3) {
347 | position = CENTER_BOTTOM;//中下
348 | mWmParams.y = mScreenHeight;
349 | }
350 | } else if (wmX >= mScreenWidth * 2 / 3 && wmX < mScreenWidth)//右边竖区域
351 | {
352 | mWmParams.x = mScreenWidth;
353 | if (wmY <= mScreenHeight / 3) {
354 | position = RIGHT_TOP;//上右
355 | mWmParams.y = 0;
356 | } else if (wmY > mScreenHeight / 3
357 | && wmY < mScreenHeight * 2 / 3) {
358 | position = RIGHT_CENTER;//中右
359 | mWmParams.y = mScreenHeight / 2;
360 |
361 | } else if (wmY >= mScreenHeight * 2 / 3 && wmY < mScreenHeight) {
362 | position = RIGHT_BOTTOM;//下右
363 | mWmParams.y = mScreenHeight;
364 | }
365 | }
366 | refreshPathMenu(position);
367 | mWindowManager.updateViewLayout(this, mWmParams);
368 | mTouchStartX = mTouchStartY = 0;
369 | break;
370 | }
371 | return false;
372 | }
373 |
374 | /**
375 | * 根据按钮位置改变子菜单方向
376 | */
377 | private void refreshPathMenu(int position) {
378 | FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) mPathMenuLayout.getLayoutParams();
379 | FrameLayout.LayoutParams params1 = (FrameLayout.LayoutParams) controlLayout.getLayoutParams();
380 |
381 | switch (position) {
382 | case PathMenu.LEFT_TOP://左上
383 | params1.gravity = Gravity.LEFT | Gravity.TOP;
384 | params.gravity = Gravity.LEFT | Gravity.TOP;
385 | mPathMenuLayout.setArc(0, 90, position);
386 | break;
387 | case PathMenu.LEFT_CENTER://左中
388 | params1.gravity = Gravity.LEFT | Gravity.CENTER_VERTICAL;
389 | params.gravity = Gravity.LEFT | Gravity.CENTER_VERTICAL;
390 | mPathMenuLayout.setArc(270, 270 + 180, position);
391 | break;
392 | case PathMenu.LEFT_BOTTOM://左下
393 | params1.gravity = Gravity.LEFT | Gravity.BOTTOM;
394 | params.gravity = Gravity.LEFT | Gravity.BOTTOM;
395 | mPathMenuLayout.setArc(270, 360, position);
396 | break;
397 | case PathMenu.RIGHT_TOP://右上
398 | params1.gravity = Gravity.RIGHT | Gravity.TOP;
399 | params.gravity = Gravity.RIGHT | Gravity.TOP;
400 | mPathMenuLayout.setArc(90, 180, position);
401 | break;
402 | case PathMenu.RIGHT_CENTER://右中
403 | params1.gravity = Gravity.RIGHT | Gravity.CENTER_VERTICAL;
404 | params.gravity = Gravity.RIGHT | Gravity.CENTER_VERTICAL;
405 | mPathMenuLayout.setArc(90, 270, position);
406 | break;
407 | case PathMenu.RIGHT_BOTTOM://右下
408 | params1.gravity = Gravity.BOTTOM | Gravity.RIGHT;
409 | params.gravity = Gravity.BOTTOM | Gravity.RIGHT;
410 | mPathMenuLayout.setArc(180, 270, position);
411 | break;
412 |
413 | case PathMenu.CENTER_TOP://上中
414 | params1.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
415 | params.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
416 | mPathMenuLayout.setArc(0, 180, position);
417 | break;
418 | case PathMenu.CENTER_BOTTOM://下中
419 | params1.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
420 | params.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
421 | mPathMenuLayout.setArc(180, 360, position);
422 | break;
423 | case PathMenu.CENTER:
424 | params1.gravity = Gravity.CENTER;
425 | params.gravity = Gravity.CENTER;
426 | mPathMenuLayout.setArc(0, 360, position);
427 | break;
428 | }
429 | controlLayout.setLayoutParams(params1);
430 | mPathMenuLayout.setLayoutParams(params);
431 | }
432 |
433 | /**
434 | * 初始化子菜单图片、点击事件
435 | */
436 | private void initPathMenu(PathMenu menu, int[] itemDrawables) {
437 | final int itemCount = itemDrawables.length;
438 | for (int i = 0; i < itemCount; i++) {
439 | ImageView item = new ImageView(mContext);
440 | item.setImageResource(itemDrawables[i]);
441 | final int index = i;
442 | menu.addItem(item, new OnClickListener() {
443 |
444 | @Override
445 | public void onClick(View v) {
446 | switch (index) {
447 | case 0:
448 | Toast.makeText(getContext(), "第0个被点击",
449 | Toast.LENGTH_SHORT).show();
450 | break;
451 |
452 | case 1:
453 | Toast.makeText(getContext(), "第1个被点击",
454 | Toast.LENGTH_SHORT).show();
455 | break;
456 | }
457 | }
458 | });
459 | }
460 | }
461 |
462 | int statusBarHeight = 0;
463 |
464 | public int getStatusBarHeight() {
465 | if (statusBarHeight <= 0) {
466 | int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
467 | if (resourceId > 0) {
468 | statusBarHeight = getResources().getDimensionPixelSize(resourceId);
469 | }
470 | }
471 | return statusBarHeight;
472 | }
473 |
474 |
475 | }
--------------------------------------------------------------------------------
/floatpathmenu/src/main/java/com/ym/floatmenu/path/PathMenuActivity.java:
--------------------------------------------------------------------------------
1 | package com.ym.floatmenu.path;
2 |
3 | import android.app.Activity;
4 | import android.content.Intent;
5 | import android.os.Bundle;
6 | import android.view.View;
7 | import android.view.View.OnClickListener;
8 | import android.widget.Button;
9 | import android.widget.ImageView;
10 | import android.widget.Toast;
11 |
12 | public class PathMenuActivity extends Activity {
13 |
14 | private static final int[] ITEM_DRAWABLES = { R.drawable.composer_camera,
15 | R.drawable.composer_music, R.drawable.composer_place,
16 | R.drawable.composer_sleep, R.drawable.composer_thought,
17 | R.drawable.composer_with };
18 |
19 | private static final int[] ITEM_DRAWABLES2 = { R.drawable.composer_camera,
20 | R.drawable.composer_music, R.drawable.composer_place,
21 | R.drawable.composer_sleep, R.drawable.composer_thought,
22 | R.drawable.composer_with,R.drawable.composer_with,R.drawable.composer_with,R.drawable.composer_with,R.drawable.composer_with };
23 | @Override
24 | protected void onCreate(Bundle savedInstanceState) {
25 | super.onCreate(savedInstanceState);
26 | setContentView(R.layout.activity_float);
27 | Button btnOpen = (Button) findViewById(R.id.btnOpenFloat);
28 | Button btnClose = (Button) findViewById(R.id.btnCloseFloat);
29 | btnOpen.setOnClickListener(mClickListener);
30 | btnClose.setOnClickListener(mClickListener);
31 | PathMenu pathMenu = (PathMenu) findViewById(R.id.path_menu);
32 | PathMenu pathMenu2 = (PathMenu) findViewById(R.id.path_menu2);
33 | PathMenu pathMenu3 = (PathMenu) findViewById(R.id.path_menu3);
34 | PathMenu pathMenu4 = (PathMenu) findViewById(R.id.path_menu4);
35 | PathMenu pathMenu5 = (PathMenu) findViewById(R.id.path_menu5);
36 | PathMenu pathMenu6 = (PathMenu) findViewById(R.id.path_menu6);
37 | PathMenu pathMenu7 = (PathMenu) findViewById(R.id.path_menu7);
38 | PathMenu pathMenu8 = (PathMenu) findViewById(R.id.path_menu8);
39 |
40 | initPathMenu(pathMenu, ITEM_DRAWABLES);
41 | initPathMenu(pathMenu2, ITEM_DRAWABLES);
42 | initPathMenu(pathMenu3, ITEM_DRAWABLES);
43 | initPathMenu(pathMenu4, ITEM_DRAWABLES);
44 | initPathMenu(pathMenu5, ITEM_DRAWABLES);
45 | initPathMenu(pathMenu6, ITEM_DRAWABLES);
46 | initPathMenu(pathMenu7, ITEM_DRAWABLES);
47 | initPathMenu(pathMenu8, ITEM_DRAWABLES2);
48 | }
49 | private void initPathMenu(PathMenu menu, int[] itemDrawables) {
50 | final int itemCount = itemDrawables.length;
51 | for (int i = 0; i < itemCount; i++) {
52 | ImageView item = new ImageView(this);
53 | item.setImageResource(itemDrawables[i]);
54 |
55 | final int position = i;
56 | menu.addItem(item, new OnClickListener() {
57 |
58 | @Override
59 | public void onClick(View v) {
60 | Toast.makeText(PathMenuActivity.this,
61 | "position:" + position, Toast.LENGTH_SHORT).show();
62 | }
63 | });
64 | }
65 | }
66 |
67 | OnClickListener mClickListener = new OnClickListener() {
68 |
69 | @Override
70 | public void onClick(View v) {
71 | if (v.getId() == R.id.btnOpenFloat) {
72 | Intent intent = new Intent(PathMenuActivity.this,
73 | PathMenuService.class);
74 | startService(intent);
75 | } else if (v.getId() == R.id.btnCloseFloat) {
76 | Intent intent = new Intent(PathMenuActivity.this,
77 | PathMenuService.class);
78 | stopService(intent);
79 | }
80 | }
81 | };
82 | }
83 |
--------------------------------------------------------------------------------
/floatpathmenu/src/main/java/com/ym/floatmenu/path/PathMenuLayout.java:
--------------------------------------------------------------------------------
1 | package com.ym.floatmenu.path;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.graphics.Rect;
6 | import android.util.AttributeSet;
7 | import android.util.DisplayMetrics;
8 | import android.util.Log;
9 | import android.view.View;
10 | import android.view.ViewGroup;
11 | import android.view.animation.AccelerateInterpolator;
12 | import android.view.animation.Animation;
13 | import android.view.animation.Animation.AnimationListener;
14 | import android.view.animation.AnimationSet;
15 | import android.view.animation.Interpolator;
16 | import android.view.animation.LinearInterpolator;
17 | import android.view.animation.OvershootInterpolator;
18 | import android.view.animation.RotateAnimation;
19 |
20 | /**
21 | * 子菜单项布局
22 | *
23 | * @author 何凌波
24 | */
25 | public class PathMenuLayout extends ViewGroup {
26 | private int mChildSize; // 子菜单项大小相同
27 | private int mChildPadding = 5;
28 | public static final float DEFAULT_FROM_DEGREES = 270.0f;
29 | public static final float DEFAULT_TO_DEGREES = 360.0f;
30 | private float mFromDegrees = DEFAULT_FROM_DEGREES;
31 | private float mToDegrees = DEFAULT_TO_DEGREES;
32 | private static final int MIN_RADIUS = 100;
33 | private int mRadius;// 中心菜单圆点到子菜单中心的距离
34 | private boolean mExpanded = false;
35 |
36 | private int position = PathMenu.LEFT_TOP;
37 | private int centerX = 0;
38 | private int centerY = 0;
39 |
40 | public void computeCenterXY(int position) {
41 | switch (position) {
42 | case PathMenu.LEFT_TOP://左上
43 | centerX = getWidth() / 2 - getRadiusAndPadding();
44 | centerY = getHeight() / 2 - getRadiusAndPadding();
45 | break;
46 | case PathMenu.LEFT_CENTER://左中
47 | centerX = getWidth() / 2 - getRadiusAndPadding();
48 | centerY = getHeight() / 2;
49 | break;
50 | case PathMenu.LEFT_BOTTOM://左下
51 | centerX = getWidth() / 2 - getRadiusAndPadding();
52 | centerY = getHeight() / 2 + getRadiusAndPadding();
53 | break;
54 | case PathMenu.CENTER_TOP://上中
55 | centerX = getWidth() / 2;
56 | centerY = getHeight() / 2 - getRadiusAndPadding();
57 | break;
58 | case PathMenu.CENTER_BOTTOM://下中
59 | centerX = getWidth() / 2;
60 | centerY = getHeight() / 2 + getRadiusAndPadding();
61 | break;
62 | case PathMenu.RIGHT_TOP://右上
63 | centerX = getWidth() / 2 + getRadiusAndPadding();
64 | centerY = getHeight() / 2 - getRadiusAndPadding();
65 | break;
66 | case PathMenu.RIGHT_CENTER://右中
67 | centerX = getWidth() / 2 + getRadiusAndPadding();
68 | centerY = getHeight() / 2;
69 | break;
70 | case PathMenu.RIGHT_BOTTOM://右下
71 | centerX = getWidth() / 2 + getRadiusAndPadding();
72 | centerY = getHeight() / 2 + getRadiusAndPadding();
73 | break;
74 |
75 | case PathMenu.CENTER:
76 | centerX = getWidth() / 2;
77 | centerY = getHeight() / 2;
78 | break;
79 | }
80 | }
81 |
82 | private int getRadiusAndPadding() {
83 | return mRadius + (mChildPadding * 2);
84 | }
85 |
86 | public PathMenuLayout(Context context, AttributeSet attrs) {
87 | super(context, attrs);
88 | // 获取自定义属性,设定默认值
89 | if (attrs != null) {
90 | TypedArray a = getContext().obtainStyledAttributes(attrs,
91 | R.styleable.ArcLayout, 0, 0);
92 | mFromDegrees = a.getFloat(R.styleable.ArcLayout_fromDegrees,
93 | DEFAULT_FROM_DEGREES);
94 | mToDegrees = a.getFloat(R.styleable.ArcLayout_toDegrees,
95 | DEFAULT_TO_DEGREES);
96 | mChildSize = Math
97 | .max(a.getDimensionPixelSize(
98 | R.styleable.ArcLayout_childSize, 0), 0);
99 |
100 | a.recycle();
101 | }
102 | }
103 |
104 | /**
105 | * 计算半径
106 | */
107 | private static int computeRadius(final float arcDegrees,
108 | final int childCount, final int childSize, final int childPadding,
109 | final int minRadius) {
110 | if (childCount < 2) {
111 | return minRadius;
112 | }
113 | // final float perDegrees = arcDegrees / (childCount - 1);
114 |
115 |
116 | final float perDegrees = arcDegrees == 360 ? (arcDegrees) / (childCount) : (arcDegrees) / (childCount - 1);
117 |
118 |
119 | final float perHalfDegrees = perDegrees / 2;
120 | final int perSize = childSize + childPadding;
121 |
122 | final int radius = (int) ((perSize / 2) / Math.sin(Math
123 | .toRadians(perHalfDegrees)));
124 |
125 | return Math.max(radius, minRadius);
126 | }
127 |
128 | /**
129 | * 计算子菜单项的范围
130 | */
131 | private static Rect computeChildFrame(final int centerX, final int centerY,
132 | final int radius, final float degrees, final int size) {
133 | //子菜单项中心点
134 | final double childCenterX = centerX + radius
135 | * Math.cos(Math.toRadians(degrees));
136 | final double childCenterY = centerY + radius
137 | * Math.sin(Math.toRadians(degrees));
138 | //子菜单项的左上角,右上角,左下角,右下角
139 | return new Rect((int) (childCenterX - size / 2),
140 | (int) (childCenterY - size / 2),
141 | (int) (childCenterX + size / 2),
142 | (int) (childCenterY + size / 2));
143 | }
144 |
145 | public int getRadius() {
146 | return mRadius;
147 | }
148 |
149 |
150 | /**
151 | * 子菜单项大小
152 | */
153 | @Override
154 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
155 | DisplayMetrics dm = getResources().getDisplayMetrics();
156 | int screenWidth = dm.widthPixels;
157 | int radius = mRadius = computeRadius(
158 | Math.abs(mToDegrees - mFromDegrees), getChildCount(),
159 | mChildSize, mChildPadding, MIN_RADIUS);
160 | Log.i("layout", "radius:" + radius);
161 |
162 | int layoutPadding = 10;
163 | int size = radius * 2 + mChildSize + mChildPadding
164 | + layoutPadding * 2;
165 | Log.i("layout", "size:" + size);
166 |
167 | setMeasuredDimension(size, size);
168 |
169 | final int count = getChildCount();
170 | for (int i = 0; i < count; i++) {
171 | getChildAt(i)
172 | .measure(
173 | MeasureSpec.makeMeasureSpec(mChildSize,
174 | MeasureSpec.EXACTLY),
175 | MeasureSpec.makeMeasureSpec(mChildSize,
176 | MeasureSpec.EXACTLY));
177 | }
178 | }
179 |
180 | /**
181 | * 子菜单项位置
182 | */
183 | @Override
184 | protected void onLayout(boolean changed, int l, int t, int r, int b) {
185 | // final int centerX = getWidth() / 2 - mRadius;
186 | // final int centerY = getHeight() / 2;
187 | computeCenterXY(position);
188 | //当子菜单要收缩时radius=0,在ViewGroup坐标中心
189 | final int radius = mExpanded ? mRadius : 0;
190 |
191 | final int childCount = getChildCount();
192 | // final float perDegrees =Math.abs (mToDegrees - mFromDegrees) / (childCount - 1);
193 | final float perDegrees = Math.abs(mToDegrees - mFromDegrees) == 360 ? (Math.abs(mToDegrees - mFromDegrees)) / (childCount) : (Math.abs(mToDegrees - mFromDegrees)) / (childCount - 1);
194 |
195 |
196 | float degrees = mFromDegrees;
197 | for (int i = 0; i < childCount; i++) {
198 | Rect frame = computeChildFrame(centerX, centerY, radius, degrees,
199 | mChildSize);
200 | degrees += perDegrees;
201 | getChildAt(i).layout(frame.left, frame.top, frame.right,
202 | frame.bottom);
203 | }
204 | }
205 |
206 | /**
207 | * 计算动画开始时的偏移量
208 | */
209 | private static long computeStartOffset(final int childCount,
210 | final boolean expanded, final int index, final float delayPercent,
211 | final long duration, Interpolator interpolator) {
212 | final float delay = delayPercent * duration;
213 | final long viewDelay = (long) (getTransformedIndex(expanded,
214 | childCount, index) * delay);
215 | final float totalDelay = delay * childCount;
216 |
217 | float normalizedDelay = viewDelay / totalDelay;
218 | normalizedDelay = interpolator.getInterpolation(normalizedDelay);
219 |
220 | return (long) (normalizedDelay * totalDelay);
221 | }
222 |
223 | /**
224 | * 变换时的子菜单项索引
225 | */
226 | private static int getTransformedIndex(final boolean expanded,
227 | final int count, final int index) {
228 | if (expanded) {
229 | return count - 1 - index;
230 | }
231 |
232 | return index;
233 | }
234 |
235 | /**
236 | * 展开动画
237 | */
238 | private static Animation createExpandAnimation(float fromXDelta,
239 | float toXDelta, float fromYDelta, float toYDelta, long startOffset,
240 | long duration, Interpolator interpolator) {
241 | Animation animation = new RotateAndTranslateAnimation(0, toXDelta, 0,
242 | toYDelta, 0, 720);
243 | animation.setStartOffset(startOffset);
244 | animation.setDuration(duration);
245 | animation.setInterpolator(interpolator);
246 | animation.setFillAfter(true);
247 |
248 | return animation;
249 | }
250 |
251 | /**
252 | * 收缩动画
253 | */
254 | private static Animation createShrinkAnimation(float fromXDelta,
255 | float toXDelta, float fromYDelta, float toYDelta, long startOffset,
256 | long duration, Interpolator interpolator) {
257 | AnimationSet animationSet = new AnimationSet(false);
258 | animationSet.setFillAfter(true);
259 | //收缩过程中,child 逆时针自旋转360度
260 | final long preDuration = duration / 2;
261 | Animation rotateAnimation = new RotateAnimation(0, 360,
262 | Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
263 | 0.5f);
264 | rotateAnimation.setStartOffset(startOffset);
265 | rotateAnimation.setDuration(preDuration);
266 | rotateAnimation.setInterpolator(new LinearInterpolator());
267 | rotateAnimation.setFillAfter(true);
268 |
269 | animationSet.addAnimation(rotateAnimation);
270 | //收缩过程中位移,并逆时针旋转360度
271 | Animation translateAnimation = new RotateAndTranslateAnimation(0,
272 | toXDelta, 0, toYDelta, 360, 720);
273 | translateAnimation.setStartOffset(startOffset + preDuration);
274 | translateAnimation.setDuration(duration - preDuration);
275 | translateAnimation.setInterpolator(interpolator);
276 | translateAnimation.setFillAfter(true);
277 |
278 | animationSet.addAnimation(translateAnimation);
279 |
280 | return animationSet;
281 | }
282 |
283 | /**
284 | * 绑定子菜单项动画
285 | */
286 | private void bindChildAnimation(final View child, final int index,
287 | final long duration) {
288 | final boolean expanded = mExpanded;
289 | // final int centerX = getWidth() / 2 - mRadius; //ViewGroup的中心X坐标
290 | // final int centerY = getHeight() / 2;
291 |
292 | computeCenterXY(position);
293 | final int radius = expanded ? 0 : mRadius;
294 |
295 | final int childCount = getChildCount();
296 | final float perDegrees = Math.abs(mToDegrees - mFromDegrees) == 360 ? (mToDegrees - mFromDegrees) / (childCount) : (mToDegrees - mFromDegrees) / (childCount - 1);
297 | Rect frame = computeChildFrame(centerX, centerY, radius, mFromDegrees
298 | + index * perDegrees, mChildSize);
299 |
300 | final int toXDelta = frame.left - child.getLeft();//展开或收缩动画,child沿X轴位移距离
301 | final int toYDelta = frame.top - child.getTop();//展开或收缩动画,child沿Y轴位移距离
302 |
303 | Interpolator interpolator = mExpanded ? new AccelerateInterpolator()
304 | : new OvershootInterpolator(1.5f);
305 | final long startOffset = computeStartOffset(childCount, mExpanded,
306 | index, 0.1f, duration, interpolator);
307 |
308 | //mExpanded为true,已经展开,收缩动画;为false,展开动画
309 | Animation animation = mExpanded ? createShrinkAnimation(0, toXDelta, 0,
310 | toYDelta, startOffset, duration, interpolator)
311 | : createExpandAnimation(0, toXDelta, 0, toYDelta, startOffset,
312 | duration, interpolator);
313 |
314 | final boolean isLast = getTransformedIndex(expanded, childCount, index) == childCount - 1;
315 | animation.setAnimationListener(new AnimationListener() {
316 |
317 | @Override
318 | public void onAnimationStart(Animation animation) {
319 |
320 | }
321 |
322 | @Override
323 | public void onAnimationRepeat(Animation animation) {
324 |
325 | }
326 |
327 | @Override
328 | public void onAnimationEnd(Animation animation) {
329 | if (isLast) {
330 | postDelayed(new Runnable() {
331 |
332 | @Override
333 | public void run() {
334 | onAllAnimationsEnd();
335 | }
336 | }, 0);
337 | }
338 | }
339 | });
340 |
341 | child.setAnimation(animation);
342 | }
343 |
344 | public boolean isExpanded() {
345 | return mExpanded;
346 | }
347 |
348 |
349 | /**
350 | * 设定弧度
351 | */
352 | public void setArc(float fromDegrees, float toDegrees, int position) {
353 | this.position = position;
354 | if (mFromDegrees == fromDegrees && mToDegrees == toDegrees) {
355 | return;
356 | }
357 |
358 | mFromDegrees = fromDegrees;
359 | mToDegrees = toDegrees;
360 | computeCenterXY(position);
361 | requestLayout();
362 | }
363 |
364 | /**
365 | * 设定弧度
366 | */
367 | public void setArc(float fromDegrees, float toDegrees) {
368 | if (mFromDegrees == fromDegrees && mToDegrees == toDegrees) {
369 | return;
370 | }
371 |
372 | mFromDegrees = fromDegrees;
373 | mToDegrees = toDegrees;
374 | computeCenterXY(position);
375 | requestLayout();
376 | }
377 |
378 | /**
379 | * 设定子菜单项大小
380 | */
381 | public void setChildSize(int size) {
382 | if (mChildSize == size || size < 0) {
383 | return;
384 | }
385 |
386 | mChildSize = size;
387 |
388 | requestLayout();
389 | }
390 |
391 | public int getChildSize() {
392 | return mChildSize;
393 | }
394 |
395 |
396 | /**
397 | * 切换中心按钮的展开缩小
398 | */
399 | public void switchState(final boolean showAnimation, int position) {
400 | this.position = position;
401 | if (showAnimation) {
402 | final int childCount = getChildCount();
403 | for (int i = 0; i < childCount; i++) {
404 | bindChildAnimation(getChildAt(i), i, 300);
405 | }
406 | }
407 |
408 | mExpanded = !mExpanded;
409 |
410 | if (!showAnimation) {
411 | requestLayout();
412 | }
413 |
414 | invalidate();
415 | }
416 |
417 |
418 | /**
419 | * 切换中心按钮的展开缩小
420 | */
421 | public void switchState(final boolean showAnimation) {
422 | if (showAnimation) {
423 | final int childCount = getChildCount();
424 | for (int i = 0; i < childCount; i++) {
425 | bindChildAnimation(getChildAt(i), i, 300);
426 | }
427 | }
428 |
429 | mExpanded = !mExpanded;
430 |
431 | if (!showAnimation) {
432 | requestLayout();
433 | }
434 |
435 | invalidate();
436 | }
437 |
438 |
439 | /**
440 | * 结束所有动画
441 | */
442 | private void onAllAnimationsEnd() {
443 | final int childCount = getChildCount();
444 | for (int i = 0; i < childCount; i++) {
445 | getChildAt(i).clearAnimation();
446 | }
447 |
448 | requestLayout();
449 | }
450 | }
--------------------------------------------------------------------------------
/floatpathmenu/src/main/java/com/ym/floatmenu/path/PathMenuService.java:
--------------------------------------------------------------------------------
1 | package com.ym.floatmenu.path;
2 |
3 | import android.app.Service;
4 | import android.content.Intent;
5 | import android.os.IBinder;
6 |
7 |
8 | public class PathMenuService extends Service {
9 |
10 | @Override
11 | public IBinder onBind(Intent intent) {
12 | return null;
13 | }
14 | @Override
15 | public void onCreate() {
16 | super.onCreate();
17 |
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/floatpathmenu/src/main/java/com/ym/floatmenu/path/RotateAndTranslateAnimation.java:
--------------------------------------------------------------------------------
1 | package com.ym.floatmenu.path;
2 |
3 | import android.view.animation.Animation;
4 | import android.view.animation.Transformation;
5 | /**
6 | * 自定义动画类
7 | * 控制对象的位置,以对象的中心旋转
8 | * @author 何凌波
9 | *
10 | */
11 | public class RotateAndTranslateAnimation extends Animation {
12 | private int mFromXType = ABSOLUTE;
13 |
14 | private int mToXType = ABSOLUTE;
15 |
16 | private int mFromYType = ABSOLUTE;
17 |
18 | private int mToYType = ABSOLUTE;
19 |
20 | private float mFromXValue = 0.0f;
21 |
22 | private float mToXValue = 0.0f;
23 |
24 | private float mFromYValue = 0.0f;
25 |
26 | private float mToYValue = 0.0f;
27 |
28 | private float mFromXDelta;
29 |
30 | private float mToXDelta;
31 |
32 | private float mFromYDelta;
33 |
34 | private float mToYDelta;
35 |
36 | private float mFromDegrees;
37 |
38 | private float mToDegrees;
39 |
40 | private int mPivotXType = ABSOLUTE;
41 |
42 | private int mPivotYType = ABSOLUTE;
43 |
44 | private float mPivotXValue = 0.0f;
45 |
46 | private float mPivotYValue = 0.0f;
47 |
48 | private float mPivotX;
49 |
50 | private float mPivotY;
51 |
52 | /**
53 | * 位移动画的构造函数
54 | * @param fromXDelta
55 | * 动画开始时的X坐标
56 | * @param toXDelta
57 | * 动画结束时的X坐标
58 | * @param fromYDelta
59 | * 动画开始时的Y坐标
60 | * @param toYDelta
61 | * 动画结束时的Y坐标
62 | *
63 | * @param fromDegrees
64 | * 旋转开始时的角度
65 | * @param toDegrees
66 | * 旋转结束时的角度
67 | */
68 | public RotateAndTranslateAnimation(float fromXDelta, float toXDelta,
69 | float fromYDelta, float toYDelta, float fromDegrees, float toDegrees) {
70 | mFromXValue = fromXDelta;
71 | mToXValue = toXDelta;
72 | mFromYValue = fromYDelta;
73 | mToYValue = toYDelta;
74 |
75 | mFromXType = ABSOLUTE;
76 | mToXType = ABSOLUTE;
77 | mFromYType = ABSOLUTE;
78 | mToYType = ABSOLUTE;
79 |
80 | mFromDegrees = fromDegrees;
81 | mToDegrees = toDegrees;
82 |
83 | mPivotXValue = 0.5f;
84 | mPivotXType = RELATIVE_TO_SELF;
85 | mPivotYValue = 0.5f;
86 | mPivotYType = RELATIVE_TO_SELF;
87 | }
88 |
89 | @Override
90 | protected void applyTransformation(float interpolatedTime, Transformation t) {
91 | final float degrees = mFromDegrees
92 | + ((mToDegrees - mFromDegrees) * interpolatedTime);
93 | if (mPivotX == 0.0f && mPivotY == 0.0f) {
94 | t.getMatrix().setRotate(degrees);
95 | } else {
96 | t.getMatrix().setRotate(degrees, mPivotX, mPivotY);
97 | }
98 |
99 | float dx = mFromXDelta;
100 | float dy = mFromYDelta;
101 | if (mFromXDelta != mToXDelta) {
102 | dx = mFromXDelta + ((mToXDelta - mFromXDelta) * interpolatedTime);
103 | }
104 | if (mFromYDelta != mToYDelta) {
105 | dy = mFromYDelta + ((mToYDelta - mFromYDelta) * interpolatedTime);
106 | }
107 |
108 | t.getMatrix().postTranslate(dx, dy);
109 | }
110 |
111 | @Override
112 | public void initialize(int width, int height, int parentWidth,
113 | int parentHeight) {
114 | super.initialize(width, height, parentWidth, parentHeight);
115 | mFromXDelta = resolveSize(mFromXType, mFromXValue, width, parentWidth);
116 | mToXDelta = resolveSize(mToXType, mToXValue, width, parentWidth);
117 | mFromYDelta = resolveSize(mFromYType, mFromYValue, height, parentHeight);
118 | mToYDelta = resolveSize(mToYType, mToYValue, height, parentHeight);
119 |
120 | mPivotX = resolveSize(mPivotXType, mPivotXValue, width, parentWidth);
121 | mPivotY = resolveSize(mPivotYType, mPivotYValue, height, parentHeight);
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/floatpathmenu/src/main/res/drawable-hdpi/composer_button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatPathMenuSample/212c65608bb41c99e27b4cd5a331aaa571ef880c/floatpathmenu/src/main/res/drawable-hdpi/composer_button.png
--------------------------------------------------------------------------------
/floatpathmenu/src/main/res/drawable-hdpi/composer_camera.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatPathMenuSample/212c65608bb41c99e27b4cd5a331aaa571ef880c/floatpathmenu/src/main/res/drawable-hdpi/composer_camera.png
--------------------------------------------------------------------------------
/floatpathmenu/src/main/res/drawable-hdpi/composer_icn_plus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatPathMenuSample/212c65608bb41c99e27b4cd5a331aaa571ef880c/floatpathmenu/src/main/res/drawable-hdpi/composer_icn_plus.png
--------------------------------------------------------------------------------
/floatpathmenu/src/main/res/drawable-hdpi/composer_music.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatPathMenuSample/212c65608bb41c99e27b4cd5a331aaa571ef880c/floatpathmenu/src/main/res/drawable-hdpi/composer_music.png
--------------------------------------------------------------------------------
/floatpathmenu/src/main/res/drawable-hdpi/composer_place.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatPathMenuSample/212c65608bb41c99e27b4cd5a331aaa571ef880c/floatpathmenu/src/main/res/drawable-hdpi/composer_place.png
--------------------------------------------------------------------------------
/floatpathmenu/src/main/res/drawable-hdpi/composer_sleep.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatPathMenuSample/212c65608bb41c99e27b4cd5a331aaa571ef880c/floatpathmenu/src/main/res/drawable-hdpi/composer_sleep.png
--------------------------------------------------------------------------------
/floatpathmenu/src/main/res/drawable-hdpi/composer_sun.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatPathMenuSample/212c65608bb41c99e27b4cd5a331aaa571ef880c/floatpathmenu/src/main/res/drawable-hdpi/composer_sun.png
--------------------------------------------------------------------------------
/floatpathmenu/src/main/res/drawable-hdpi/composer_thought.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatPathMenuSample/212c65608bb41c99e27b4cd5a331aaa571ef880c/floatpathmenu/src/main/res/drawable-hdpi/composer_thought.png
--------------------------------------------------------------------------------
/floatpathmenu/src/main/res/drawable-hdpi/composer_with.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatPathMenuSample/212c65608bb41c99e27b4cd5a331aaa571ef880c/floatpathmenu/src/main/res/drawable-hdpi/composer_with.png
--------------------------------------------------------------------------------
/floatpathmenu/src/main/res/drawable-hdpi/friends_delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatPathMenuSample/212c65608bb41c99e27b4cd5a331aaa571ef880c/floatpathmenu/src/main/res/drawable-hdpi/friends_delete.png
--------------------------------------------------------------------------------
/floatpathmenu/src/main/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatPathMenuSample/212c65608bb41c99e27b4cd5a331aaa571ef880c/floatpathmenu/src/main/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/floatpathmenu/src/main/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatPathMenuSample/212c65608bb41c99e27b4cd5a331aaa571ef880c/floatpathmenu/src/main/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/floatpathmenu/src/main/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatPathMenuSample/212c65608bb41c99e27b4cd5a331aaa571ef880c/floatpathmenu/src/main/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/floatpathmenu/src/main/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatPathMenuSample/212c65608bb41c99e27b4cd5a331aaa571ef880c/floatpathmenu/src/main/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/floatpathmenu/src/main/res/drawable/button.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | -
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/floatpathmenu/src/main/res/layout/activity_float.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
12 |
16 |
17 |
23 |
24 |
30 |
31 |
32 |
36 |
37 |
45 |
46 |
47 |
55 |
56 |
57 |
58 |
62 |
63 |
71 |
72 |
73 |
81 |
82 |
83 |
84 |
88 |
89 |
97 |
98 |
99 |
107 |
108 |
109 |
110 |
114 |
115 |
123 |
124 |
125 |
133 |
134 |
135 |
136 |
137 |
--------------------------------------------------------------------------------
/floatpathmenu/src/main/res/layout/float_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
14 |
15 |
20 |
21 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/floatpathmenu/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 |
--------------------------------------------------------------------------------
/floatpathmenu/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 16dp
5 | 16dp
6 | - 180.0
7 | - 360.0
8 | 30dp
9 |
10 |
--------------------------------------------------------------------------------
/floatpathmenu/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | FloatPathMenu
3 |
4 |
--------------------------------------------------------------------------------
/floatpathmenu/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
14 |
15 |
16 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/floatpathmenu/src/test/java/com/ym/floatmenu/path/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.ym.floatmenu.path;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 | @Test
12 | public void addition_isCorrect() throws Exception {
13 | assertEquals(4, 2 + 2);
14 | }
15 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatPathMenuSample/212c65608bb41c99e27b4cd5a331aaa571ef880c/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Sep 14 10:35:25 GMT+08:00 2016
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
7 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/pic/QQ图片20160918183808.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatPathMenuSample/212c65608bb41c99e27b4cd5a331aaa571ef880c/pic/QQ图片20160918183808.jpg
--------------------------------------------------------------------------------
/pic/QQ图片20160919111057.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatPathMenuSample/212c65608bb41c99e27b4cd5a331aaa571ef880c/pic/QQ图片20160919111057.jpg
--------------------------------------------------------------------------------
/pic/QQ图片20160919111211.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatPathMenuSample/212c65608bb41c99e27b4cd5a331aaa571ef880c/pic/QQ图片20160919111211.jpg
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':floatpathmenu'
2 |
--------------------------------------------------------------------------------