├── .gitignore
├── .idea
├── .name
├── compiler.xml
├── copyright
│ └── profiles_settings.xml
├── encodings.xml
├── gradle.xml
├── misc.xml
├── modules.xml
└── vcs.xml
├── FloatMenu
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── yw
│ │ └── game
│ │ └── floatmenu
│ │ ├── DotImageView.java
│ │ ├── FloatItem.java
│ │ ├── FloatLogoMenu.java
│ │ ├── FloatMenuView.java
│ │ ├── Utils.java
│ │ └── customfloat
│ │ └── FloatManager.java
│ └── res
│ └── values
│ └── strings.xml
├── FloatMenuDemo
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── yw
│ │ └── game
│ │ └── floatmenu
│ │ └── demo
│ │ └── ApplicationTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── yw
│ │ │ └── game
│ │ │ └── floatmenu
│ │ │ └── demo
│ │ │ └── MainActivity.java
│ └── res
│ │ ├── drawable-xxxhdpi
│ │ ├── yw_game_logo.png
│ │ ├── yw_image_float_logo.png
│ │ ├── yw_menu_account.png
│ │ ├── yw_menu_fb.png
│ │ ├── yw_menu_msg.png
│ │ ├── ywgame_floatmenu_gift.png
│ │ └── ywgame_floatmenu_user.png
│ │ ├── drawable
│ │ └── yw_game_float_menu_bg.xml
│ │ ├── layout
│ │ └── activity_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
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── yw
│ └── game
│ └── floatmenu
│ └── demo
│ └── ExampleUnitTest.java
├── README.md
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── picture
├── 20160503125603.png
├── 201605031543.gif
├── 201605041543.gif
├── 201606161036.gif
├── 2016112315.gif
├── chinareadlogo.png
├── floatmen.png
└── floatmenu2.gif
└── 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 | FloatMenuSample
--------------------------------------------------------------------------------
/.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 |
26 |
27 |
--------------------------------------------------------------------------------
/.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 | Encapsulation issuesJava
39 |
40 |
41 | Java
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | C:\Users\wengyiming\AppData\Roaming\Subversion
62 |
63 |
64 |
65 |
66 |
67 | 1.7
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/FloatMenu/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/FloatMenu/build.gradle:
--------------------------------------------------------------------------------
1 |
2 |
3 | apply plugin: 'com.android.library'
4 | apply plugin: 'com.github.dcendents.android-maven'
5 | apply plugin: 'com.jfrog.bintray'
6 |
7 | version = "2.0.1"
8 |
9 | android {
10 | compileSdkVersion 25
11 | buildToolsVersion '25.0.2'
12 |
13 | defaultConfig {
14 | minSdkVersion 11
15 | targetSdkVersion 25
16 | versionCode 1
17 | versionName "1.1"
18 | }
19 | buildTypes {
20 | release {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
23 | }
24 | }
25 |
26 | lintOptions{
27 | abortOnError false
28 | // This seems to be firing due to okio referencing java.nio.File
29 | // which is harmless for us.
30 | warning 'InvalidPackage',
31 | // don't need parcel creator for the sub-class of MessageSnapshot.
32 | 'ParcelCreator'
33 | }
34 |
35 | compileOptions {
36 | sourceCompatibility JavaVersion.VERSION_1_7
37 | targetCompatibility JavaVersion.VERSION_1_7
38 | }
39 |
40 | }
41 |
42 | dependencies {
43 | compile fileTree(include: ['*.jar'], dir: 'libs')
44 | compile 'com.android.support:support-v4:25.3.1'
45 | }
46 |
47 |
48 | def siteUrl = 'https://github.com/fanOfDemo/FloatMenuSample' // 项目的主页
49 | def gitUrl = 'https://github.com/fanOfDemo/FloatMenuSample.git' // Git仓库的url
50 | group = "com.yw.game.floatmenu" // Maven Group ID for the artifact,一般填你唯一的包名
51 | install {
52 | repositories.mavenInstaller {
53 | // This generates POM.xml with proper parameters
54 | pom {
55 | project {
56 | packaging 'aar'
57 | // Add your description here
58 | name 'FloatMenu' //项目描述
59 | url siteUrl
60 | // Set your license
61 | licenses {
62 | license {
63 | name 'The BSD 3-Clause License'
64 | url 'http://www.linfo.org/bsdlicense.html'
65 | }
66 | }
67 | developers {
68 | developer {
69 | id 'yw' //填写开发者基本信息
70 | name 'fanofdemo'
71 | email '18720625976@163.com'
72 | }
73 | }
74 | scm {
75 | connection gitUrl
76 | developerConnection gitUrl
77 | url siteUrl
78 | }
79 | }
80 | }
81 | }
82 | }
83 |
84 | task sourcesJar(type: Jar) {
85 | from android.sourceSets.main.java.srcDirs
86 | classifier = 'sources'
87 | }
88 |
89 |
90 |
91 | //tasks.findByPath(":FloatMenu:androidJavadocs").enabled = false
92 |
93 | task javadoc(type: Javadoc) {
94 | options.encoding = "utf-8"
95 | source = android.sourceSets.main.java.srcDirs
96 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
97 | }
98 |
99 | task javadocJar(type: Jar, dependsOn: javadoc) {
100 | classifier = 'javadoc'
101 | from javadoc.destinationDir
102 | }
103 |
104 | artifacts {
105 | archives javadocJar
106 | archives sourcesJar
107 | }
108 |
109 |
110 | Properties properties = new Properties()
111 | properties.load(project.rootProject.file('local.properties').newDataInputStream())
112 |
113 | bintray {
114 | user = properties.getProperty("bintray.user")
115 | key = properties.getProperty("bintray.apikey")
116 |
117 | configurations = ['archives']
118 | pkg {
119 | repo = "maven"
120 | name = "FloatMenu"
121 | websiteUrl = siteUrl
122 | vcsUrl = gitUrl
123 | licenses = ["The BSD 3-Clause License"]
124 | publish = true
125 | }
126 | }
127 |
128 |
129 |
--------------------------------------------------------------------------------
/FloatMenu/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 |
--------------------------------------------------------------------------------
/FloatMenu/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/FloatMenu/src/main/java/com/yw/game/floatmenu/DotImageView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2016, Shanghai YUEWEN Information Technology Co., Ltd.
3 | * All rights reserved.
4 | * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5 | *
6 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8 | * Neither the name of Shanghai YUEWEN Information Technology Co., Ltd. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
9 | *
10 | * THIS SOFTWARE IS PROVIDED BY SHANGHAI YUEWEN INFORMATION TECHNOLOGY CO., LTD. AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
11 | *
12 | */
13 | package com.yw.game.floatmenu;
14 |
15 | import android.animation.Animator;
16 | import android.animation.ValueAnimator;
17 | import android.content.Context;
18 | import android.graphics.Bitmap;
19 | import android.graphics.Camera;
20 | import android.graphics.Canvas;
21 | import android.graphics.Color;
22 | import android.graphics.Matrix;
23 | import android.graphics.Paint;
24 | import android.graphics.Rect;
25 | import android.support.annotation.Nullable;
26 | import android.text.TextUtils;
27 | import android.util.AttributeSet;
28 | import android.view.View;
29 | import android.view.animation.LinearInterpolator;
30 |
31 |
32 | /**
33 | * Created by wengyiming on 2017/7/21.
34 | */
35 |
36 | /**
37 | * 00%=FF(不透明) 5%=F2 10%=E5 15%=D8 20%=CC 25%=BF 30%=B2 35%=A5 40%=99 45%=8c 50%=7F
38 | * 55%=72 60%=66 65%=59 70%=4c 75%=3F 80%=33 85%=21 90%=19 95%=0c 100%=00(全透明)
39 | */
40 | public class DotImageView extends View {
41 | private static final String TAG = DotImageView.class.getSimpleName();
42 | public static final int NORMAL = 0;//不隐藏
43 | public static final int HIDE_LEFT = 1;//左边隐藏
44 | public static final int HIDE_RIGHT = 2;//右边隐藏
45 | private Paint mPaint;//用于画anything
46 |
47 | private Paint mPaintBg;//用于画anything
48 | private String dotNum = null;//红点数字
49 | private float mAlphValue;//透明度动画值
50 | private float mRolateValue = 1f;//旋转动画值
51 | private boolean inited = false;//标记透明动画是否执行过,防止因onreseme 切换导致重复执行
52 |
53 |
54 | private Bitmap mBitmap;//logo
55 | private final int mLogoBackgroundRadius = dip2px(25);//logo的灰色背景圆的半径
56 | private final int mLogoWhiteRadius = dip2px(20);//logo的白色背景的圆的半径
57 | private final int mRedPointRadiusWithNum = dip2px(6);//红点圆半径
58 | private final int mRedPointRadius = dip2px(3);//红点圆半径
59 | private final int mRedPointOffset = dip2px(10);//红点对logo的偏移量,比如左红点就是logo中心的 x - mRedPointOffset
60 |
61 | private boolean isDraging = false;//是否 绘制旋转放大动画,只有 非停靠边缘才绘制
62 | private float scaleOffset;//放大偏移值
63 | private ValueAnimator mDragingValueAnimator;//放大、旋转 属性动画
64 | private LinearInterpolator mLinearInterpolator = new LinearInterpolator();//通用用加速器
65 | public boolean mDrawDarkBg = true;//是否绘制黑色背景,当菜单关闭时,才绘制灰色背景
66 | private static final float hideOffset = 0.4f;//往左右隐藏多少宽度的偏移值, 隐藏宽度的0.4
67 | private Camera mCamera;//camera用于执行3D动画
68 |
69 | private boolean mDrawNum = false;//只绘制红点还是红点+白色数字
70 |
71 | private int mStatus = NORMAL;//0 正常,1 左,2右,3 中间方法旋转
72 | private int mLastStatus = mStatus;
73 | private Matrix mMatrix;
74 | private boolean mIsResetPosition;
75 |
76 | private int mBgColor = 0x99000000;
77 |
78 |
79 | public void setBgColor(int bgColor) {
80 | mBgColor = bgColor;
81 | }
82 |
83 |
84 | public void setDrawNum(boolean drawNum) {
85 | this.mDrawNum = drawNum;
86 | }
87 |
88 | public void setDrawDarkBg(boolean drawDarkBg) {
89 | mDrawDarkBg = drawDarkBg;
90 | invalidate();
91 | }
92 |
93 | public int getStatus() {
94 | return mStatus;
95 | }
96 |
97 |
98 | public void setStatus(int status) {
99 | this.mStatus = status;
100 | isDraging = false;
101 | if (this.mStatus != NORMAL) {
102 | setDrawNum(mDrawNum);
103 | this.mDrawDarkBg = true;
104 | }
105 | invalidate();
106 |
107 | }
108 |
109 | public void setBitmap(Bitmap bitmap) {
110 | mBitmap = bitmap;
111 | }
112 |
113 | public DotImageView(Context context, Bitmap bitmap) {
114 | super(context);
115 | this.mBitmap = bitmap;
116 | init();
117 | }
118 |
119 |
120 | public DotImageView(Context context) {
121 | super(context);
122 | init();
123 | }
124 |
125 | public DotImageView(Context context, @Nullable AttributeSet attrs) {
126 | super(context, attrs);
127 | init();
128 | }
129 |
130 | public DotImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
131 | super(context, attrs, defStyleAttr);
132 | init();
133 | }
134 |
135 | private void init() {
136 | mPaint = new Paint();
137 | mPaint.setAntiAlias(true);
138 | mPaint.setTextSize(sp2px(10));
139 | mPaint.setStyle(Paint.Style.FILL);
140 |
141 | mPaintBg = new Paint();
142 | mPaintBg.setAntiAlias(true);
143 | mPaintBg.setStyle(Paint.Style.FILL);
144 | mPaintBg.setColor(mBgColor);//60% 黑色背景 (透明度 40%)
145 |
146 | mCamera = new Camera();
147 | mMatrix = new Matrix();
148 | }
149 |
150 | /**
151 | * 这个方法是否有优化空间
152 | */
153 | @Override
154 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
155 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
156 | int wh = mLogoBackgroundRadius * 2;
157 | setMeasuredDimension(wh, wh);
158 | }
159 |
160 | @Override
161 | protected void onDraw(Canvas canvas) {
162 | super.onDraw(canvas);
163 | float centryX = getWidth() / 2;
164 | float centryY = getHeight() / 2;
165 | canvas.save();//保存一份快照,方便后面恢复
166 | mCamera.save();
167 | if (mStatus == NORMAL) {
168 | if (mLastStatus != NORMAL) {
169 | canvas.restore();//恢复画布的原始快照
170 | mCamera.restore();
171 | }
172 |
173 | if (isDraging) {
174 | //如果当前是拖动状态则放大并旋转
175 | canvas.scale((scaleOffset + 1f), (scaleOffset + 1f), getWidth() / 2, getHeight() / 2);
176 | if (mIsResetPosition) {
177 | //手指拖动后离开屏幕复位时使用 x轴旋转 3d动画
178 | mCamera.save();
179 | mCamera.rotateX(720 * scaleOffset);//0-720度 最多转两圈
180 | mCamera.getMatrix(mMatrix);
181 |
182 | mMatrix.preTranslate(-getWidth() / 2, -getHeight() / 2);
183 | mMatrix.postTranslate(getWidth() / 2, getHeight() / 2);
184 | canvas.concat(mMatrix);
185 | mCamera.restore();
186 | } else {
187 | //手指拖动且手指未离开屏幕则使用 绕图心2d旋转动画
188 | canvas.rotate(60 * mRolateValue, getWidth() / 2, getHeight() / 2);
189 | }
190 | }
191 |
192 |
193 | } else if (mStatus == HIDE_LEFT) {
194 | canvas.translate(-getWidth() * hideOffset, 0);
195 | canvas.rotate(-45, getWidth() / 2, getHeight() / 2);
196 |
197 | } else if (mStatus == HIDE_RIGHT) {
198 | canvas.translate(getWidth() * hideOffset, 0);
199 | canvas.rotate(45, getWidth() / 2, getHeight() / 2);
200 | }
201 | canvas.save();
202 | if (!isDraging) {
203 | if (mDrawDarkBg) {
204 | mPaintBg.setColor(mBgColor);
205 | canvas.drawCircle(centryX, centryY, mLogoBackgroundRadius, mPaintBg);
206 | // 60% 白色 (透明度 40%)
207 | mPaint.setColor(0x99ffffff);
208 | } else {
209 | //100% 白色背景 (透明度 0%)
210 | mPaint.setColor(0xFFFFFFFF);
211 | }
212 | if (mAlphValue != 0) {
213 | mPaint.setAlpha((int) (mAlphValue * 255));
214 | }
215 | canvas.drawCircle(centryX, centryY, mLogoWhiteRadius, mPaint);
216 | }
217 |
218 | canvas.restore();
219 | //100% 白色背景 (透明度 0%)
220 | mPaint.setColor(0xFFFFFFFF);
221 | int left = (int) (centryX - mBitmap.getWidth() / 2);
222 | int top = (int) (centryY - mBitmap.getHeight() / 2);
223 | canvas.drawBitmap(mBitmap, left, top, mPaint);
224 |
225 |
226 | if (!TextUtils.isEmpty(dotNum)) {
227 | int readPointRadus = (mDrawNum ? mRedPointRadiusWithNum : mRedPointRadius);
228 | mPaint.setColor(Color.RED);
229 | if (mStatus == HIDE_LEFT) {
230 | canvas.drawCircle(centryX + mRedPointOffset, centryY - mRedPointOffset, readPointRadus, mPaint);
231 | if (mDrawNum) {
232 | mPaint.setColor(Color.WHITE);
233 | canvas.drawText(dotNum, centryX + mRedPointOffset - getTextWidth(dotNum, mPaint) / 2, centryY - mRedPointOffset + getTextHeight(dotNum, mPaint) / 2, mPaint);
234 | }
235 | } else if (mStatus == HIDE_RIGHT) {
236 | canvas.drawCircle(centryX - mRedPointOffset, centryY - mRedPointOffset, readPointRadus, mPaint);
237 | if (mDrawNum) {
238 | mPaint.setColor(Color.WHITE);
239 | canvas.drawText(dotNum, centryX - mRedPointOffset - getTextWidth(dotNum, mPaint) / 2, centryY - mRedPointOffset + getTextHeight(dotNum, mPaint) / 2, mPaint);
240 | }
241 | } else {
242 | if (mLastStatus == HIDE_LEFT) {
243 | canvas.drawCircle(centryX + mRedPointOffset, centryY - mRedPointOffset, readPointRadus, mPaint);
244 | if (mDrawNum) {
245 | mPaint.setColor(Color.WHITE);
246 | canvas.drawText(dotNum, centryX + mRedPointOffset - getTextWidth(dotNum, mPaint) / 2, centryY - mRedPointOffset + getTextHeight(dotNum, mPaint) / 2, mPaint);
247 | }
248 | } else if (mLastStatus == HIDE_RIGHT) {
249 | canvas.drawCircle(centryX - mRedPointOffset, centryY - mRedPointOffset, readPointRadus, mPaint);
250 | if (mDrawNum) {
251 | mPaint.setColor(Color.WHITE);
252 | canvas.drawText(dotNum, centryX - mRedPointOffset - getTextWidth(dotNum, mPaint) / 2, centryY - mRedPointOffset + getTextHeight(dotNum, mPaint) / 2, mPaint);
253 | }
254 | }
255 | }
256 | }
257 | mLastStatus = mStatus;
258 | }
259 |
260 |
261 | public void setDotNum(int num, Animator.AnimatorListener l) {
262 | if (!inited) {
263 | startAnim(num, l);
264 | } else {
265 | refreshDot(num);
266 | }
267 | }
268 |
269 | private void refreshDot(int num) {
270 | if (num > 0) {
271 | String dotNumTmp = String.valueOf(num);
272 | if (!TextUtils.equals(dotNum, dotNumTmp)) {
273 | dotNum = dotNumTmp;
274 | invalidate();
275 | }
276 | } else {
277 | dotNum = null;
278 | }
279 | }
280 |
281 |
282 | public void startAnim(final int num, Animator.AnimatorListener l) {
283 | ValueAnimator valueAnimator = ValueAnimator.ofFloat(1.f, 0.6f, 1f, 0.6f);
284 | valueAnimator.setInterpolator(mLinearInterpolator);
285 | valueAnimator.setDuration(3000);
286 | valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
287 | @Override
288 | public void onAnimationUpdate(ValueAnimator animation) {
289 | mAlphValue = (float) animation.getAnimatedValue();
290 | invalidate();
291 |
292 | }
293 | });
294 | valueAnimator.addListener(l);
295 | valueAnimator.addListener(new Animator.AnimatorListener() {
296 | @Override
297 | public void onAnimationStart(Animator animation) {
298 |
299 | }
300 |
301 | @Override
302 | public void onAnimationEnd(Animator animation) {
303 | inited = true;
304 | refreshDot(num);
305 | mAlphValue = 0;
306 |
307 | }
308 |
309 | @Override
310 | public void onAnimationCancel(Animator animation) {
311 | mAlphValue = 0;
312 | }
313 |
314 | @Override
315 | public void onAnimationRepeat(Animator animation) {
316 |
317 | }
318 | });
319 | valueAnimator.start();
320 | }
321 |
322 | public void setDraging(boolean draging, float offset, boolean isResetPosition) {
323 | isDraging = draging;
324 | this.mIsResetPosition = isResetPosition;
325 | if (offset > 0 && offset != this.scaleOffset) {
326 | this.scaleOffset = offset;
327 | }
328 | if (isDraging && mStatus == NORMAL) {
329 | if (mDragingValueAnimator != null) {
330 | if (mDragingValueAnimator.isRunning()) return;
331 | }
332 | mDragingValueAnimator = ValueAnimator.ofFloat(0, 6f, 12f, 0f);
333 | mDragingValueAnimator.setInterpolator(mLinearInterpolator);
334 | mDragingValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
335 | @Override
336 | public void onAnimationUpdate(ValueAnimator animation) {
337 | mRolateValue = (float) animation.getAnimatedValue();
338 | invalidate();
339 | }
340 | });
341 | mDragingValueAnimator.addListener(new Animator.AnimatorListener() {
342 | @Override
343 | public void onAnimationStart(Animator animation) {
344 |
345 | }
346 |
347 | @Override
348 | public void onAnimationEnd(Animator animation) {
349 | isDraging = false;
350 | mIsResetPosition = false;
351 | }
352 |
353 | @Override
354 | public void onAnimationCancel(Animator animation) {
355 |
356 | }
357 |
358 | @Override
359 | public void onAnimationRepeat(Animator animation) {
360 |
361 | }
362 | });
363 | mDragingValueAnimator.setDuration(1000);
364 | mDragingValueAnimator.start();
365 | }
366 | }
367 |
368 | private int dip2px(float dipValue) {
369 | final float scale = getContext().getResources().getDisplayMetrics().density;
370 | return (int) (dipValue * scale + 0.5f);
371 | }
372 |
373 | private int sp2px(float spValue) {
374 | final float fontScale = getContext().getResources().getDisplayMetrics().scaledDensity;
375 | return (int) (spValue * fontScale + 0.5f);
376 | }
377 |
378 | private float getTextHeight(String text, Paint paint) {
379 | Rect rect = new Rect();
380 | paint.getTextBounds(text, 0, text.length(), rect);
381 | return rect.height() / 1.1f;
382 | }
383 |
384 | private float getTextWidth(String text, Paint paint) {
385 | return paint.measureText(text);
386 | }
387 | }
388 |
--------------------------------------------------------------------------------
/FloatMenu/src/main/java/com/yw/game/floatmenu/FloatItem.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2016, Shanghai YUEWEN Information Technology Co., Ltd.
3 | * All rights reserved.
4 | * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5 | *
6 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8 | * Neither the name of Shanghai YUEWEN Information Technology Co., Ltd. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
9 | *
10 | * THIS SOFTWARE IS PROVIDED BY SHANGHAI YUEWEN INFORMATION TECHNOLOGY CO., LTD. AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
11 | *
12 | */
13 | package com.yw.game.floatmenu;
14 |
15 | import android.graphics.Bitmap;
16 | import android.graphics.Color;
17 |
18 | /**
19 | * Created by wengyiming on 2017/7/21.
20 | */
21 |
22 | public class FloatItem {
23 | public String title;
24 | public int titleColor = Color.BLACK;
25 | public int bgColor = Color.WHITE;
26 | public Bitmap icon;
27 | public String dotNum = null;
28 |
29 | public FloatItem(String title, int titleColor, int bgColor, Bitmap icon, String dotNum) {
30 | this.title = title;
31 | this.titleColor = titleColor;
32 | this.bgColor = bgColor;
33 | this.icon = icon;
34 | this.dotNum = dotNum;
35 | }
36 |
37 | public String getDotNum() {
38 | return dotNum;
39 | }
40 |
41 |
42 | public FloatItem(String title, int titleColor, int bgColor, Bitmap bitmap) {
43 | this.title = title;
44 | this.titleColor = titleColor;
45 | this.bgColor = bgColor;
46 | this.icon = bitmap;
47 | }
48 |
49 | public String getTitle() {
50 | return title;
51 | }
52 |
53 | public void setTitle(String title) {
54 | this.title = title;
55 | }
56 |
57 | public int getTitleColor() {
58 | return titleColor;
59 | }
60 |
61 | public void setTitleColor(int titleColor) {
62 | this.titleColor = titleColor;
63 | }
64 |
65 | public int getBgColor() {
66 | return bgColor;
67 | }
68 |
69 | public void setBgColor(int bgColor) {
70 | this.bgColor = bgColor;
71 | }
72 |
73 | public Bitmap getIcon() {
74 | return icon;
75 | }
76 |
77 | public void setIcon(Bitmap icon) {
78 | this.icon = icon;
79 | }
80 |
81 |
82 | @Override
83 | public boolean equals(Object obj) {
84 | if (obj == null) {
85 | return false;
86 | }
87 | if (obj == this) return true;
88 |
89 | if (obj instanceof FloatItem) {
90 | FloatItem floatItem = (FloatItem) obj;
91 | return floatItem.title.equals(this.title);
92 | } else {
93 | return false;
94 | }
95 | }
96 |
97 | @Override
98 | public int hashCode() {
99 | return title.hashCode();
100 | }
101 |
102 | @Override
103 | public String toString() {
104 | return "FloatItem{" +
105 | "title='" + title + '\'' +
106 | ", titleColor=" + titleColor +
107 | ", bgColor=" + bgColor +
108 | ", icon=" + icon +
109 | ", dotNum='" + dotNum + '\'' +
110 | '}';
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/FloatMenu/src/main/java/com/yw/game/floatmenu/FloatLogoMenu.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2016, Shanghai YUEWEN Information Technology Co., Ltd.
3 | * All rights reserved.
4 | * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5 | *
6 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8 | * Neither the name of Shanghai YUEWEN Information Technology Co., Ltd. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
9 | *
10 | * THIS SOFTWARE IS PROVIDED BY SHANGHAI YUEWEN INFORMATION TECHNOLOGY CO., LTD. AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
11 | *
12 | */
13 | package com.yw.game.floatmenu;
14 |
15 | import android.animation.Animator;
16 | import android.animation.ValueAnimator;
17 | import android.app.Activity;
18 | import android.content.Context;
19 | import android.content.SharedPreferences;
20 | import android.graphics.Bitmap;
21 | import android.graphics.Color;
22 | import android.graphics.PixelFormat;
23 | import android.graphics.drawable.Drawable;
24 | import android.os.Build;
25 | import android.os.Bundle;
26 | import android.os.CountDownTimer;
27 | import android.os.Handler;
28 | import android.os.Looper;
29 | import android.text.TextUtils;
30 | import android.util.Log;
31 | import android.util.TypedValue;
32 | import android.view.Gravity;
33 | import android.view.MotionEvent;
34 | import android.view.View;
35 | import android.view.View.OnClickListener;
36 | import android.view.View.OnTouchListener;
37 | import android.view.ViewGroup;
38 | import android.view.WindowManager;
39 | import android.view.animation.Interpolator;
40 | import android.view.animation.LinearInterpolator;
41 | import android.widget.LinearLayout;
42 |
43 |
44 | import java.util.ArrayList;
45 | import java.util.List;
46 |
47 | /**
48 | * Created by wengyiming on 2017/7/20.
49 | */
50 | public class FloatLogoMenu {
51 | /**
52 | * 记录 logo 停放的位置,以备下次恢复
53 | */
54 | private static final String LOCATION_X = "hintLocation";
55 | private static final String LOCATION_Y = "locationY";
56 |
57 | /**
58 | * 悬浮球 坐落 左 右 标记
59 | */
60 | public static final int LEFT = 0;
61 | public static final int RIGHT = 1;
62 |
63 | /**
64 | * 记录系统状态栏的高度
65 | */
66 | private int mStatusBarHeight;
67 | /**
68 | * 记录当前手指位置在屏幕上的横坐标值
69 | */
70 | private float mXInScreen;
71 |
72 | /**
73 | * 记录当前手指位置在屏幕上的纵坐标值
74 | */
75 | private float mYInScreen;
76 |
77 | /**
78 | * 记录手指按下时在屏幕上的横坐标的值
79 | */
80 | private float mXDownInScreen;
81 |
82 | /**
83 | * 记录手指按下时在屏幕上的纵坐标的值
84 | */
85 | private float mYDownInScreen;
86 |
87 | /**
88 | * 记录手指按下时在小悬浮窗的View上的横坐标的值
89 | */
90 | private float mXInView;
91 |
92 | /**
93 | * 记录手指按下时在小悬浮窗的View上的纵坐标的值
94 | */
95 | private float mYinview;
96 |
97 | /**
98 | * 记录屏幕的宽度
99 | */
100 | private int mScreenWidth;
101 |
102 | /**
103 | * 来自 activity 的 wManager
104 | */
105 | private WindowManager wManager;
106 |
107 |
108 | /**
109 | * 为 wManager 设置 LayoutParams
110 | */
111 | private WindowManager.LayoutParams wmParams;
112 |
113 | /**
114 | * 带透明度动画、旋转、放大的悬浮球
115 | */
116 | private DotImageView mFloatLogo;
117 |
118 |
119 | /**
120 | * 用于 定时 隐藏 logo的定时器
121 | */
122 | private CountDownTimer mHideTimer;
123 |
124 |
125 | /**
126 | * float menu的高度
127 | */
128 | private Handler mHandler = new Handler(Looper.getMainLooper());
129 |
130 |
131 | /**
132 | * 悬浮窗左右移动到默认位置 动画的 加速器
133 | */
134 | private Interpolator mLinearInterpolator = new LinearInterpolator();
135 |
136 | /**
137 | * 用于记录上次菜单打开的时间,判断时间间隔
138 | */
139 | private static double DOUBLE_CLICK_TIME = 0L;
140 |
141 | /**
142 | * 标记是否拖动中
143 | */
144 | private boolean isDraging = false;
145 |
146 | /**
147 | * 用于恢复悬浮球的location的属性动画值
148 | */
149 | private int mResetLocationValue;
150 |
151 | /**
152 | * 手指离开屏幕后 用于恢复 悬浮球的 logo 的左右位置
153 | */
154 | private Runnable updatePositionRunnable = new Runnable() {
155 | @Override
156 | public void run() {
157 | isDraging = true;
158 | checkPosition();
159 | }
160 | };
161 |
162 | /**
163 | * 这个事件不做任何事情、直接return false则 onclick 事件生效
164 | */
165 | private OnTouchListener mDefaultOnTouchListerner = new OnTouchListener() {
166 | @Override
167 | public boolean onTouch(View v, MotionEvent event) {
168 | isDraging = false;
169 | return false;
170 | }
171 | };
172 |
173 | /**
174 | * 这个事件用于处理移动、自定义点击或者其它事情,return true可以保证onclick事件失效
175 | */
176 | private OnTouchListener touchListener = new OnTouchListener() {
177 | @Override
178 | public boolean onTouch(View v, MotionEvent event) {
179 | switch (event.getAction()) {
180 | case MotionEvent.ACTION_DOWN:
181 | floatEventDown(event);
182 | break;
183 | case MotionEvent.ACTION_MOVE:
184 | floatEventMove(event);
185 | break;
186 | case MotionEvent.ACTION_UP:
187 | case MotionEvent.ACTION_CANCEL:
188 | floatEventUp();
189 | break;
190 | }
191 | return true;
192 | }
193 | };
194 |
195 |
196 | /**
197 | * 菜单背景颜色
198 | */
199 | private int mBackMenuColor = 0xffe4e3e1;
200 |
201 | /**
202 | * 是否绘制红点数字
203 | */
204 | private boolean mDrawRedPointNum;
205 |
206 |
207 | /**
208 | * 是否绘制圆形菜单项,false绘制方形
209 | */
210 | private boolean mCicleMenuBg;
211 |
212 |
213 | /**
214 | * R.drawable.yw_game_logo
215 | *
216 | * @param floatItems
217 | */
218 | private Bitmap mLogoRes;
219 |
220 | /**
221 | * 用于显示在 mActivity 上的 mActivity
222 | */
223 | private Context mActivity;
224 |
225 | /**
226 | * 菜单 点击、关闭 监听
227 | */
228 | private FloatMenuView.OnMenuClickListener mOnMenuClickListener;
229 |
230 |
231 | /**
232 | * 停靠默认位置
233 | */
234 | private int mDefaultLocation = RIGHT;
235 |
236 |
237 | /**
238 | * 悬浮窗 坐落 位置
239 | */
240 | private int mHintLocation = mDefaultLocation;
241 |
242 |
243 | /**
244 | * 用于记录菜单项内容
245 | */
246 | private List mFloatItems = new ArrayList<>();
247 |
248 | private LinearLayout rootViewRight;
249 |
250 | private LinearLayout rootView;
251 |
252 | private ValueAnimator valueAnimator;
253 |
254 | private boolean isExpaned = false;
255 |
256 | private Drawable mBackground;
257 |
258 | private boolean mNeedPermision = true;
259 |
260 | private FloatLogoMenu(Builder builder) {
261 | mBackMenuColor = builder.mBackMenuColor;
262 | mDrawRedPointNum = builder.mDrawRedPointNum;
263 | mCicleMenuBg = builder.mCicleMenuBg;
264 | mLogoRes = builder.mLogoRes;
265 | mActivity = builder.mActivity;
266 | mOnMenuClickListener = builder.mOnMenuClickListener;
267 | mDefaultLocation = builder.mDefaultLocation;
268 | mFloatItems = builder.mFloatItems;
269 | mBackground = builder.mDrawable;
270 |
271 | // if (mActivity == null || mActivity.isFinishing() || mActivity.getWindowManager() == null) {
272 | // throw new IllegalArgumentException("Activity = null, or Activity is isFinishing ,or this Activity`s token is bad");
273 | // }
274 |
275 | if (mLogoRes == null) {
276 | throw new IllegalArgumentException("No logo found,you can setLogo/showWithLogo to set a FloatLogo ");
277 | }
278 |
279 | if (mFloatItems.isEmpty()) {
280 | throw new IllegalArgumentException("At least one menu item!");
281 | }
282 |
283 | initFloatWindow();
284 | initTimer();
285 | initFloat();
286 |
287 | }
288 |
289 | public void setFloatItemList(List floatItems) {
290 | this.mFloatItems = floatItems;
291 | caculateDotNum();
292 | }
293 |
294 | /**
295 | * 初始化悬浮球 window
296 | */
297 | private void initFloatWindow() {
298 | if(mActivity instanceof Activity){
299 | Activity activity = (Activity) mActivity;
300 | wManager = activity.getWindowManager();
301 | mNeedPermision = false;
302 | }else {
303 | mNeedPermision = true;
304 | wManager = (WindowManager) mActivity.getSystemService(Context.WINDOW_SERVICE);
305 | }
306 | mScreenWidth = wManager.getDefaultDisplay().getWidth();
307 | int screenHeigth = wManager.getDefaultDisplay().getHeight();
308 |
309 | //判断状态栏是否显示 如果不显示则statusBarHeight为0
310 | mStatusBarHeight = dp2Px(25, mActivity);
311 |
312 | wmParams = new WindowManager.LayoutParams();
313 |
314 | if(mNeedPermision) {
315 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
316 | if (Build.VERSION.SDK_INT > 23) {
317 | //在android7.1以上系统需要使用TYPE_PHONE类型 配合运行时权限
318 | wmParams.type = WindowManager.LayoutParams.TYPE_PHONE;
319 | } else {
320 | wmParams.type = WindowManager.LayoutParams.TYPE_TOAST;
321 | }
322 | } else {
323 | wmParams.type = WindowManager.LayoutParams.TYPE_PHONE;
324 | }
325 | }
326 |
327 |
328 | wmParams.format = PixelFormat.RGBA_8888;
329 | wmParams.gravity = Gravity.LEFT | Gravity.TOP;
330 | wmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
331 | mHintLocation = getSetting(LOCATION_X, mDefaultLocation);
332 | int defaultY = ((screenHeigth - mStatusBarHeight) / 2) / 3;
333 | int y = getSetting(LOCATION_Y, defaultY);
334 | if (mHintLocation == LEFT) {
335 | wmParams.x = 0;
336 | } else {
337 | wmParams.x = mScreenWidth;
338 | }
339 |
340 | if (y != 0 && y != defaultY) {
341 | wmParams.y = y;
342 | } else {
343 | wmParams.y = defaultY;
344 | }
345 | wmParams.alpha = 1;
346 | wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
347 | wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
348 | }
349 |
350 |
351 | /**
352 | * 初始化悬浮球
353 | */
354 | private void initFloat() {
355 | genarateLeftLineLayout();
356 | genarateRightLineLayout();
357 | mFloatLogo = new DotImageView(mActivity, mLogoRes);
358 | mFloatLogo.setLayoutParams(new WindowManager.LayoutParams(dp2Px(50, mActivity), dp2Px(50, mActivity)));
359 | mFloatLogo.setDrawNum(mDrawRedPointNum);
360 | mFloatLogo.setBgColor(mBackMenuColor);
361 | mFloatLogo.setDrawDarkBg(true);
362 | caculateDotNum();
363 | floatBtnEvent();
364 | try {
365 | wManager.addView(mFloatLogo, wmParams);
366 | } catch (Exception e) {
367 | e.printStackTrace();
368 | }
369 |
370 | }
371 |
372 | private void genarateLeftLineLayout() {
373 | DotImageView floatLogo = new DotImageView(mActivity, mLogoRes);
374 | floatLogo.setLayoutParams(new WindowManager.LayoutParams(dp2Px(50, mActivity), dp2Px(50, mActivity)));
375 | floatLogo.setDrawNum(mDrawRedPointNum);
376 | floatLogo.setDrawDarkBg(false);
377 |
378 | rootView = new LinearLayout(mActivity);
379 | rootView.setOrientation(LinearLayout.HORIZONTAL);
380 | rootView.setGravity(Gravity.CENTER);
381 | rootView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, dp2Px(50, mActivity)));
382 |
383 | rootView.setBackgroundDrawable(mBackground);
384 |
385 |
386 | FloatMenuView mFloatMenuView = new FloatMenuView.Builder(mActivity)
387 | .setFloatItems(mFloatItems)
388 | .setBackgroundColor(Color.TRANSPARENT)
389 | .setCicleBg(mCicleMenuBg)
390 | .setStatus(FloatMenuView.STATUS_LEFT)
391 | .setMenuBackgroundColor(Color.TRANSPARENT)
392 | .drawNum(mDrawRedPointNum)
393 | .create();
394 | setMenuClickListener(mFloatMenuView);
395 |
396 | rootView.addView(floatLogo);
397 | rootView.addView(mFloatMenuView);
398 |
399 |
400 | floatLogo.setOnClickListener(new OnClickListener() {
401 | @Override
402 | public void onClick(View v) {
403 | if (isExpaned) {
404 | try {
405 | wManager.removeViewImmediate(rootView);
406 | wManager.addView(FloatLogoMenu.this.mFloatLogo, wmParams);
407 | } catch (Exception e) {
408 | e.printStackTrace();
409 | }
410 | isExpaned = false;
411 | }
412 | }
413 | });
414 | }
415 |
416 | private void genarateRightLineLayout() {
417 | final DotImageView floatLogo = new DotImageView(mActivity, mLogoRes);
418 | floatLogo.setLayoutParams(new WindowManager.LayoutParams(dp2Px(50, mActivity), dp2Px(50, mActivity)));
419 | floatLogo.setDrawNum(mDrawRedPointNum);
420 | floatLogo.setDrawDarkBg(false);
421 |
422 | floatLogo.setOnClickListener(new OnClickListener() {
423 | @Override
424 | public void onClick(View v) {
425 | if (isExpaned) {
426 | try {
427 | wManager.removeViewImmediate(rootViewRight);
428 | wManager.addView(FloatLogoMenu.this.mFloatLogo, wmParams);
429 | } catch (Exception e) {
430 | e.printStackTrace();
431 | }
432 | isExpaned = false;
433 | }
434 | }
435 | });
436 |
437 | rootViewRight = new LinearLayout(mActivity);
438 | rootViewRight.setOrientation(LinearLayout.HORIZONTAL);
439 | rootViewRight.setGravity(Gravity.CENTER);
440 | rootViewRight.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, dp2Px(50, mActivity)));
441 |
442 |
443 | rootViewRight.setBackgroundDrawable(mBackground);
444 |
445 |
446 | FloatMenuView mFloatMenuView = new FloatMenuView.Builder(mActivity)
447 | .setFloatItems(mFloatItems)
448 | .setBackgroundColor(Color.TRANSPARENT)
449 | .setCicleBg(mCicleMenuBg)
450 | .setStatus(FloatMenuView.STATUS_RIGHT)
451 | .setMenuBackgroundColor(Color.TRANSPARENT)
452 | .drawNum(mDrawRedPointNum)
453 | .create();
454 | setMenuClickListener(mFloatMenuView);
455 |
456 | rootViewRight.addView(mFloatMenuView);
457 | rootViewRight.addView(floatLogo);
458 |
459 |
460 | }
461 |
462 | /**
463 | * 初始化 隐藏悬浮球的定时器
464 | */
465 | private void initTimer() {
466 | mHideTimer = new CountDownTimer(2000, 10) { //悬浮窗超过5秒没有操作的话会自动隐藏
467 | @Override
468 | public void onTick(long millisUntilFinished) {
469 | if (isExpaned) {
470 | mHideTimer.cancel();
471 | }
472 | }
473 |
474 | @Override
475 | public void onFinish() {
476 | if (isExpaned) {
477 | mHideTimer.cancel();
478 | return;
479 | }
480 | if (!isDraging) {
481 | if (mHintLocation == LEFT) {
482 | mFloatLogo.setStatus(DotImageView.HIDE_LEFT);
483 | mFloatLogo.setDrawDarkBg(true);
484 | } else {
485 | mFloatLogo.setStatus(DotImageView.HIDE_RIGHT);
486 | mFloatLogo.setDrawDarkBg(true);
487 | }
488 | // mFloatLogo.setOnTouchListener(mDefaultOnTouchListerner);//把onClick事件分发下去,防止onclick无效
489 | }
490 | }
491 | };
492 | }
493 |
494 |
495 | /**
496 | * 用于 拦截 菜单项的 关闭事件,以方便开始 隐藏定时器
497 | *
498 | * @param mFloatMenuView
499 | */
500 | private void setMenuClickListener(FloatMenuView mFloatMenuView) {
501 | mFloatMenuView.setOnMenuClickListener(new FloatMenuView.OnMenuClickListener() {
502 | @Override
503 | public void onItemClick(int position, String title) {
504 | mOnMenuClickListener.onItemClick(position, title);
505 | }
506 |
507 | @Override
508 | public void dismiss() {
509 | mFloatLogo.setDrawDarkBg(true);
510 | mOnMenuClickListener.dismiss();
511 | mHideTimer.start();
512 | }
513 | });
514 |
515 | }
516 |
517 |
518 | /**
519 | * 悬浮窗的点击事件和touch事件的切换
520 | */
521 | private void floatBtnEvent() {
522 | //这里的onCick只有 touchListener = mDefaultOnTouchListerner 才会触发
523 | // mFloatLogo.setOnClickListener(new OnClickListener() {
524 | // @Override
525 | // public void onClick(View v) {
526 | // if (!isDraging) {
527 | // if (mFloatLogo.getStatus() != DotImageView.NORMAL) {
528 | // mFloatLogo.setBitmap(mLogoRes);
529 | // mFloatLogo.setStatus(DotImageView.NORMAL);
530 | // if (!mFloatLogo.mDrawDarkBg) {
531 | // mFloatLogo.setDrawDarkBg(true);
532 | // }
533 | // }
534 | // mFloatLogo.setOnTouchListener(touchListener);
535 | // mHideTimer.start();
536 | // }
537 | // }
538 | // });
539 |
540 | mFloatLogo.setOnTouchListener(touchListener);//恢复touch事件
541 | }
542 |
543 | /**
544 | * 悬浮窗touch事件的 down 事件
545 | */
546 | private void floatEventDown(MotionEvent event) {
547 | isDraging = false;
548 | mHideTimer.cancel();
549 | if (mFloatLogo.getStatus() != DotImageView.NORMAL) {
550 | mFloatLogo.setStatus(DotImageView.NORMAL);
551 | }
552 | if (!mFloatLogo.mDrawDarkBg) {
553 | mFloatLogo.setDrawDarkBg(true);
554 | }
555 | if (mFloatLogo.getStatus() != DotImageView.NORMAL) {
556 | mFloatLogo.setStatus(DotImageView.NORMAL);
557 | }
558 | mXInView = event.getX();
559 | mYinview = event.getY();
560 | mXDownInScreen = event.getRawX();
561 | mYDownInScreen = event.getRawY();
562 | mXInScreen = event.getRawX();
563 | mYInScreen = event.getRawY();
564 |
565 |
566 | }
567 |
568 | /**
569 | * 悬浮窗touch事件的 move 事件
570 | */
571 | private void floatEventMove(MotionEvent event) {
572 | mXInScreen = event.getRawX();
573 | mYInScreen = event.getRawY();
574 |
575 |
576 | //连续移动的距离超过3则更新一次视图位置
577 | if (Math.abs(mXInScreen - mXDownInScreen) > mFloatLogo.getWidth() / 4 || Math.abs(mYInScreen - mYDownInScreen) > mFloatLogo.getWidth() / 4) {
578 | wmParams.x = (int) (mXInScreen - mXInView);
579 | wmParams.y = (int) (mYInScreen - mYinview) - mFloatLogo.getHeight() / 2;
580 | updateViewPosition(); // 手指移动的时候更新小悬浮窗的位置
581 | double a = mScreenWidth / 2;
582 | float offset = (float) ((a - (Math.abs(wmParams.x - a))) / a);
583 | mFloatLogo.setDraging(isDraging, offset, false);
584 | } else {
585 | isDraging = false;
586 | mFloatLogo.setDraging(false, 0, true);
587 | }
588 | }
589 |
590 | /**
591 | * 悬浮窗touch事件的 up 事件
592 | */
593 | private void floatEventUp() {
594 | if (mXInScreen < mScreenWidth / 2) { //在左边
595 | mHintLocation = LEFT;
596 | } else { //在右边
597 | mHintLocation = RIGHT;
598 | }
599 | if (valueAnimator == null) {
600 | valueAnimator = ValueAnimator.ofInt(64);
601 | valueAnimator.setInterpolator(mLinearInterpolator);
602 | valueAnimator.setDuration(1000);
603 | valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
604 | @Override
605 | public void onAnimationUpdate(ValueAnimator animation) {
606 | mResetLocationValue = (int) animation.getAnimatedValue();
607 | mHandler.post(updatePositionRunnable);
608 | }
609 | });
610 |
611 | valueAnimator.addListener(new Animator.AnimatorListener() {
612 | @Override
613 | public void onAnimationStart(Animator animation) {
614 |
615 | }
616 |
617 | @Override
618 | public void onAnimationEnd(Animator animation) {
619 | if (Math.abs(wmParams.x) < 0) {
620 | wmParams.x = 0;
621 | } else if (Math.abs(wmParams.x) > mScreenWidth) {
622 | wmParams.x = mScreenWidth;
623 | }
624 | updateViewPosition();
625 | isDraging = false;
626 | mFloatLogo.setDraging(false, 0, true);
627 | mHideTimer.start();
628 | }
629 |
630 | @Override
631 | public void onAnimationCancel(Animator animation) {
632 | if (Math.abs(wmParams.x) < 0) {
633 | wmParams.x = 0;
634 | } else if (Math.abs(wmParams.x) > mScreenWidth) {
635 | wmParams.x = mScreenWidth;
636 | }
637 |
638 | updateViewPosition();
639 | isDraging = false;
640 | mFloatLogo.setDraging(false, 0, true);
641 | mHideTimer.start();
642 |
643 | }
644 |
645 | @Override
646 | public void onAnimationRepeat(Animator animation) {
647 |
648 | }
649 | });
650 | }
651 | if (!valueAnimator.isRunning()) {
652 | valueAnimator.start();
653 | }
654 |
655 | // //这里需要判断如果如果手指所在位置和logo所在位置在一个宽度内则不移动,
656 | if (Math.abs(mXInScreen - mXDownInScreen) > mFloatLogo.getWidth() / 5 || Math.abs(mYInScreen - mYDownInScreen) > mFloatLogo.getHeight() / 5) {
657 | isDraging = false;
658 | } else {
659 | openMenu();
660 | }
661 |
662 | }
663 |
664 |
665 | /**
666 | * 用于检查并更新悬浮球的位置
667 | */
668 | private void checkPosition() {
669 | if (wmParams.x > 0 && wmParams.x < mScreenWidth) {
670 | if (mHintLocation == LEFT) {
671 | wmParams.x = wmParams.x - mResetLocationValue;
672 | } else {
673 | wmParams.x = wmParams.x + mResetLocationValue;
674 | }
675 | updateViewPosition();
676 | double a = mScreenWidth / 2;
677 | float offset = (float) ((a - (Math.abs(wmParams.x - a))) / a);
678 | mFloatLogo.setDraging(isDraging, offset, true);
679 | return;
680 | }
681 |
682 |
683 | if (Math.abs(wmParams.x) < 0) {
684 | wmParams.x = 0;
685 | } else if (Math.abs(wmParams.x) > mScreenWidth) {
686 | wmParams.x = mScreenWidth;
687 | }
688 | if (valueAnimator.isRunning()) {
689 | valueAnimator.cancel();
690 | }
691 |
692 |
693 | updateViewPosition();
694 | isDraging = false;
695 |
696 |
697 | }
698 |
699 |
700 | /**
701 | * 打开菜单
702 | */
703 | private void openMenu() {
704 | if (isDraging) return;
705 |
706 | if (!isExpaned) {
707 | mFloatLogo.setDrawDarkBg(false);
708 | try {
709 | wManager.removeViewImmediate(mFloatLogo);
710 | if (mHintLocation == RIGHT) {
711 | wManager.addView(rootViewRight, wmParams);
712 | } else {
713 | wManager.addView(rootView, wmParams);
714 | }
715 | } catch (Exception e) {
716 | e.printStackTrace();
717 | }
718 |
719 | isExpaned = true;
720 | mHideTimer.cancel();
721 | } else {
722 | mFloatLogo.setDrawDarkBg(true);
723 | if (isExpaned) {
724 | try {
725 | wManager.removeViewImmediate(mHintLocation == LEFT ? rootView : rootViewRight);
726 | wManager.addView(mFloatLogo, wmParams);
727 | } catch (Exception e) {
728 | e.printStackTrace();
729 | }
730 |
731 | isExpaned = false;
732 | }
733 | mHideTimer.start();
734 | }
735 |
736 | }
737 |
738 |
739 |
740 |
741 | /**
742 | * 更新悬浮窗在屏幕中的位置。
743 | */
744 | private void updateViewPosition() {
745 | isDraging = true;
746 | try {
747 | if (!isExpaned) {
748 | if (wmParams.y - mFloatLogo.getHeight() / 2 <= 0) {
749 | wmParams.y = mStatusBarHeight;
750 | isDraging = true;
751 | }
752 | wManager.updateViewLayout(mFloatLogo, wmParams);
753 | }
754 | } catch (Exception e) {
755 | e.printStackTrace();
756 | }
757 | }
758 |
759 | public void show() {
760 | try {
761 | if (wManager != null && wmParams != null && mFloatLogo != null) {
762 | wManager.addView(mFloatLogo, wmParams);
763 | }
764 | if (mHideTimer != null) {
765 | mHideTimer.start();
766 | } else {
767 | initTimer();
768 | mHideTimer.start();
769 | }
770 | } catch (Exception e) {
771 | e.printStackTrace();
772 | }
773 | }
774 |
775 | /**
776 | * 关闭菜单
777 | */
778 | public void hide() {
779 | destoryFloat();
780 | }
781 |
782 |
783 | /**
784 | * 移除所有悬浮窗 释放资源
785 | */
786 | public void destoryFloat() {
787 | //记录上次的位置logo的停放位置,以备下次恢复
788 | saveSetting(LOCATION_X, mHintLocation);
789 | saveSetting(LOCATION_Y, wmParams.y);
790 | mFloatLogo.clearAnimation();
791 | try {
792 | mHideTimer.cancel();
793 | if (isExpaned) {
794 | wManager.removeViewImmediate(mHintLocation == LEFT ? rootView : rootViewRight);
795 | } else {
796 | wManager.removeViewImmediate(mFloatLogo);
797 | }
798 | isExpaned = false;
799 | isDraging = false;
800 | } catch (Exception e) {
801 | e.printStackTrace();
802 | }
803 | }
804 |
805 | /**
806 | * 计算总红点数
807 | */
808 | private void caculateDotNum() {
809 | int dotNum = 0;
810 | for (FloatItem floatItem : mFloatItems) {
811 | if (!TextUtils.isEmpty(floatItem.getDotNum())) {
812 | int num = Integer.parseInt(floatItem.getDotNum());
813 | dotNum = dotNum + num;
814 | }
815 | }
816 | mFloatLogo.setDrawNum(mDrawRedPointNum);
817 | setDotNum(dotNum);
818 | }
819 |
820 | /**
821 | * 绘制悬浮球的红点
822 | *
823 | * @param dotNum d
824 | */
825 | private void setDotNum(int dotNum) {
826 | mFloatLogo.setDotNum(dotNum, new Animator.AnimatorListener() {
827 | @Override
828 | public void onAnimationStart(Animator animation) {
829 |
830 | }
831 |
832 | @Override
833 | public void onAnimationEnd(Animator animation) {
834 | if (!isDraging) {
835 | mHideTimer.start();
836 | }
837 | }
838 |
839 | @Override
840 | public void onAnimationCancel(Animator animation) {
841 |
842 | }
843 |
844 | @Override
845 | public void onAnimationRepeat(Animator animation) {
846 |
847 | }
848 | });
849 | }
850 |
851 | /**
852 | * 用于暴露给外部判断是否包含某个菜单项
853 | *
854 | * @param menuLabel string
855 | * @return boolean
856 | */
857 | public boolean hasMenu(String menuLabel) {
858 | for (FloatItem menuItem : mFloatItems) {
859 | if (TextUtils.equals(menuItem.getTitle(), menuLabel)) {
860 | return true;
861 | }
862 | }
863 | return false;
864 | }
865 |
866 | /**
867 | * 用于保存悬浮球的位置记录
868 | *
869 | * @param key String
870 | * @param defaultValue int
871 | * @return int
872 | */
873 | private int getSetting(String key, int defaultValue) {
874 | try {
875 | SharedPreferences sharedata = mActivity.getSharedPreferences("floatLogo", 0);
876 | return sharedata.getInt(key, defaultValue);
877 | } catch (Exception e) {
878 | e.printStackTrace();
879 | }
880 | return defaultValue;
881 | }
882 |
883 | /**
884 | * 用于保存悬浮球的位置记录
885 | *
886 | * @param key String
887 | * @param value int
888 | */
889 | public void saveSetting(String key, int value) {
890 | try {
891 | SharedPreferences.Editor sharedata = mActivity.getSharedPreferences("floatLogo", 0).edit();
892 | sharedata.putInt(key, value);
893 | sharedata.apply();
894 | } catch (Exception e) {
895 | e.printStackTrace();
896 | }
897 | }
898 |
899 | public static int dp2Px(float dp, Context mContext) {
900 | return (int) TypedValue.applyDimension(
901 | TypedValue.COMPLEX_UNIT_DIP,
902 | dp,
903 | mContext.getResources().getDisplayMetrics());
904 | }
905 |
906 |
907 |
908 | public interface OnMenuClickListener {
909 | void onMenuExpended(boolean isExpened);
910 | }
911 |
912 |
913 | public void setValueAnimator(){
914 |
915 | }
916 |
917 | public static final class Builder {
918 | private int mBackMenuColor;
919 | private boolean mDrawRedPointNum;
920 | private boolean mCicleMenuBg;
921 | private Bitmap mLogoRes;
922 | private int mDefaultLocation;
923 | private List mFloatItems = new ArrayList<>();
924 | private Context mActivity;
925 | private FloatMenuView.OnMenuClickListener mOnMenuClickListener;
926 | private Drawable mDrawable;
927 |
928 |
929 | public Builder setBgDrawable(Drawable drawable) {
930 | mDrawable = drawable;
931 | return this;
932 | }
933 |
934 | public Builder() {
935 | }
936 |
937 | public Builder setFloatItems(List mFloatItems) {
938 | this.mFloatItems = mFloatItems;
939 | return this;
940 | }
941 |
942 | public Builder addFloatItem(FloatItem floatItem) {
943 | this.mFloatItems.add(floatItem);
944 | return this;
945 | }
946 |
947 | public Builder backMenuColor(int val) {
948 | mBackMenuColor = val;
949 | return this;
950 | }
951 |
952 | public Builder drawRedPointNum(boolean val) {
953 | mDrawRedPointNum = val;
954 | return this;
955 | }
956 |
957 | public Builder drawCicleMenuBg(boolean val) {
958 | mCicleMenuBg = val;
959 | return this;
960 | }
961 |
962 | public Builder logo(Bitmap val) {
963 | mLogoRes = val;
964 | return this;
965 | }
966 |
967 | public Builder withActivity(Activity val) {
968 | mActivity = val;
969 | return this;
970 | }
971 |
972 | public Builder withContext(Context val){
973 | mActivity = val;
974 | return this;
975 | }
976 |
977 | public Builder setOnMenuItemClickListener(FloatMenuView.OnMenuClickListener val) {
978 | mOnMenuClickListener = val;
979 | return this;
980 | }
981 |
982 | public Builder defaultLocation(int val) {
983 | mDefaultLocation = val;
984 | return this;
985 | }
986 |
987 | public FloatLogoMenu showWithListener(FloatMenuView.OnMenuClickListener val) {
988 | mOnMenuClickListener = val;
989 | return new FloatLogoMenu(this);
990 | }
991 |
992 | public FloatLogoMenu showWithLogo(Bitmap val) {
993 | mLogoRes = val;
994 | return new FloatLogoMenu(this);
995 | }
996 |
997 | public FloatLogoMenu show() {
998 | return new FloatLogoMenu(this);
999 | }
1000 | }
1001 |
1002 |
1003 | }
1004 |
--------------------------------------------------------------------------------
/FloatMenu/src/main/java/com/yw/game/floatmenu/FloatMenuView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2016, Shanghai YUEWEN Information Technology Co., Ltd.
3 | * All rights reserved.
4 | * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5 | *
6 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8 | * Neither the name of Shanghai YUEWEN Information Technology Co., Ltd. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
9 | *
10 | * THIS SOFTWARE IS PROVIDED BY SHANGHAI YUEWEN INFORMATION TECHNOLOGY CO., LTD. AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
11 | *
12 | */
13 | package com.yw.game.floatmenu;
14 |
15 | import android.animation.Animator;
16 | import android.animation.ObjectAnimator;
17 | import android.content.Context;
18 | import android.graphics.Canvas;
19 | import android.graphics.Color;
20 | import android.graphics.Paint;
21 | import android.graphics.PointF;
22 | import android.graphics.Rect;
23 | import android.graphics.RectF;
24 | import android.text.TextUtils;
25 | import android.util.AttributeSet;
26 | import android.view.MotionEvent;
27 | import android.view.View;
28 | import android.view.ViewGroup;
29 |
30 |
31 | import java.util.ArrayList;
32 | import java.util.List;
33 |
34 | /**
35 | * Created by wengyiming on 2017/7/21.
36 | */
37 |
38 | public class FloatMenuView extends View {
39 | public static final int STATUS_LEFT = 3;//展开左边菜单
40 | public static final int STATUS_RIGHT = 4;//展开右边菜单
41 |
42 | private int mStatus = STATUS_RIGHT;//默认右边
43 |
44 | private Paint mPaint;//画笔
45 | private int mBackgroundColor = 0x00FFFFFF;//默认背景颜色 完全透明的白色
46 |
47 | private int mMenuBackgroundColor = -1;//菜单的背景颜色
48 |
49 | private RectF mBgRect;//菜单的背景矩阵
50 | private int mItemWidth = dip2px(50);//菜单项的宽度
51 | private int mItemHeight = dip2px(50);//菜单项的高度
52 | private int mItemLeft = 0;//菜单项左边的默认偏移值,这里是0
53 | private int mCorner = dip2px(2);//菜单背景的圆角多出的宽度
54 |
55 |
56 | private int mRadius = dip2px(4);//红点消息半径
57 | private final int mRedPointRadiuWithNoNum = dip2px(3);//红点圆半径
58 |
59 | private int mFontSizePointNum = sp2px(10);//红点消息数字的文字大小
60 |
61 | private int mFontSizeTitle = sp2px(12);//菜单项的title的文字大小
62 | private float mFirstItemTop;//菜单项的最小y值,上面起始那条线
63 | private boolean mDrawNum = false;//是否绘制数字,false则只绘制红点
64 | private boolean cicleBg = false;//菜单项背景是否绘制成圆形,false则绘制矩阵
65 |
66 | private List mItemList = new ArrayList<>();//菜单项的内容
67 | private List mItemRectList = new ArrayList<>();//菜单项所占用位置的记录,用于判断点击事件
68 |
69 | private OnMenuClickListener mOnMenuClickListener;//菜单项的点击事件回调
70 |
71 | private ObjectAnimator mAlphaAnim;//消失关闭动画的透明值
72 |
73 | //设置菜单内容集合
74 | public void setItemList(List itemList) {
75 | mItemList = itemList;
76 | }
77 |
78 | //设置是否绘制红点数字
79 | public void drawNum(boolean drawNum) {
80 | mDrawNum = drawNum;
81 | }
82 |
83 | //设置是否绘制圆形菜单,否则矩阵
84 | public void setCicleBg(boolean cicleBg) {
85 | this.cicleBg = cicleBg;
86 | }
87 |
88 | //用于标记所依赖的view的screen的坐标,实际view的坐标是window坐标,所以这里后面会减去状态栏的高度
89 |
90 |
91 | //设置菜单的背景颜色
92 | public void setMenuBackgroundColor(int mMenuBackgroundColor) {
93 | this.mMenuBackgroundColor = mMenuBackgroundColor;
94 | }
95 |
96 | //设置这个view(整个屏幕)的背景,这里默认透明
97 | public void setBackgroundColor(int BackgroundColor) {
98 | this.mBackgroundColor = BackgroundColor;
99 | }
100 |
101 |
102 | //下面开始的注释我写不动了,看不懂的话请自行领悟吧
103 | public FloatMenuView(Context context) {
104 | super(context);
105 | }
106 |
107 | public FloatMenuView(Context context, AttributeSet attrs) {
108 | super(context, attrs);
109 | }
110 |
111 | public FloatMenuView(Context context, AttributeSet attrs, int defStyleAttr) {
112 | super(context, attrs, defStyleAttr);
113 | }
114 |
115 | public FloatMenuView(Context baseContext, int status) {
116 | super(baseContext);
117 | mStatus = status;
118 | int screenWidth = getResources().getDisplayMetrics().widthPixels;
119 | int screenHeight = getResources().getDisplayMetrics().heightPixels;
120 | mBgRect = new RectF(0, 0, screenWidth, screenHeight);
121 | initView();
122 | }
123 |
124 | @Override
125 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
126 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
127 | setMeasuredDimension(mItemWidth * mItemList.size(), mItemHeight);
128 | }
129 |
130 | private void initView( ) {
131 | mPaint = new Paint();
132 | mPaint.setAntiAlias(true);
133 | mPaint.setStyle(Paint.Style.FILL);
134 | mPaint.setTextSize(sp2px(12));
135 |
136 | mAlphaAnim = ObjectAnimator.ofFloat(this, "alpha", 1.0f, 0f);
137 | mAlphaAnim.setDuration(50);
138 | mAlphaAnim.addListener(new MyAnimListener() {
139 | @Override
140 | public void onAnimationEnd(Animator animation) {
141 | if (mOnMenuClickListener != null) {
142 | removeView();
143 | mOnMenuClickListener.dismiss();
144 | }
145 | }
146 | });
147 |
148 | mFirstItemTop = 0;
149 | if (mStatus == STATUS_LEFT) {
150 | mItemLeft = 0;
151 | } else {
152 | mItemLeft = 0;
153 | }
154 |
155 | }
156 |
157 | @Override
158 | protected void onDraw(Canvas canvas) {
159 | super.onDraw(canvas);
160 | switch (mStatus) {
161 | case STATUS_LEFT:
162 | drawBackground(canvas);
163 | drawFloatLeftItem(canvas);
164 | break;
165 | case STATUS_RIGHT:
166 | drawBackground(canvas);
167 | drawFloatLeftItem(canvas);
168 |
169 | break;
170 | }
171 | }
172 |
173 | private void drawBackground(Canvas canvas) {
174 | mPaint.setColor(mBackgroundColor);
175 | canvas.drawRect(mBgRect, mPaint);
176 |
177 | }
178 |
179 | private void drawFloatLeftItem(Canvas canvas) {
180 | mItemRectList.clear();
181 | for (int i = 0; i < mItemList.size(); i++) {
182 | canvas.save();
183 | mPaint.setColor(mMenuBackgroundColor);
184 | if (cicleBg) {
185 | float cx = (mItemLeft + i * mItemWidth) + mItemWidth / 2;//x中心点
186 | float cy = mFirstItemTop + mItemHeight / 2;//y中心点
187 | float radius = mItemWidth / 2;//半径
188 | canvas.drawCircle(cx, cy, radius, mPaint);
189 | } else {
190 | mPaint.setColor(mItemList.get(i).bgColor);
191 | canvas.drawRect(mItemLeft + i * mItemWidth, mFirstItemTop, mItemLeft + mItemWidth + i * mItemWidth, mFirstItemTop + mItemHeight, mPaint);
192 | }
193 |
194 | mItemRectList.add(new RectF(mItemLeft + i * mItemWidth, mFirstItemTop, mItemLeft + mItemWidth + i * mItemWidth, mFirstItemTop + mItemHeight));
195 | mPaint.setColor(mItemList.get(i).bgColor);
196 | drawIconTitleDot(canvas, i);
197 | }
198 | canvas.restore();
199 | }
200 |
201 |
202 | private void drawIconTitleDot(Canvas canvas, int position) {
203 | FloatItem floatItem = mItemList.get(position);
204 |
205 | if (floatItem.icon != null) {
206 | float centryX = mItemLeft + mItemWidth / 2 + (mItemWidth) * position;//计算每一个item的中心点x的坐标值
207 | float centryY = mFirstItemTop + mItemHeight / 2;//计算每一个item的中心点的y坐标值
208 |
209 | float left = centryX - mItemWidth / 4;//计算icon的左坐标值 中心点往左移宽度的四分之一
210 | float right = centryX + mItemWidth / 4;
211 |
212 | float iconH = mItemHeight * 0.5f;//计算出icon的宽度 = icon的高度
213 |
214 | float textH = getTextHeight(floatItem.getTitle(), mPaint);
215 | float paddH = (mItemHeight - iconH - textH - mRadius) / 2;//总高度减去文字的高度,减去icon高度,再除以2就是上下的间距剩余
216 |
217 | float top = centryY - mItemHeight / 2 + paddH;//计算icon的上坐标值
218 | float bottom = top + iconH;//剩下的高度空间用于画文字
219 |
220 | //画icon
221 | mPaint.setColor(floatItem.titleColor);
222 | canvas.drawBitmap(floatItem.icon, null, new RectF(left, top, right, bottom), mPaint);
223 | if (!TextUtils.isEmpty(floatItem.dotNum) && !floatItem.dotNum.equals("0")) {
224 | float dotLeft = centryX + mItemWidth / 5;
225 | float cx = dotLeft + mCorner;//x中心点
226 | float cy = top + mCorner;//y中心点
227 |
228 | int radiu = mDrawNum ? mRadius : mRedPointRadiuWithNoNum;
229 | //画红点
230 | mPaint.setColor(Color.RED);
231 | canvas.drawCircle(cx, cy, radiu, mPaint);
232 | if (mDrawNum) {
233 | mPaint.setColor(Color.WHITE);
234 | mPaint.setTextSize(mFontSizePointNum);
235 | //画红点消息数
236 | canvas.drawText(floatItem.dotNum, cx - getTextWidth(floatItem.getDotNum(), mPaint) / 2, cy + getTextHeight(floatItem.getDotNum(), mPaint) / 2, mPaint);
237 | }
238 | }
239 | mPaint.setColor(floatItem.titleColor);
240 | mPaint.setTextSize(mFontSizeTitle);
241 | //画menu title
242 | canvas.drawText(floatItem.title, centryX - getTextWidth(floatItem.getTitle(), mPaint) / 2, centryY + iconH / 2 + getTextHeight(floatItem.getTitle(), mPaint) / 2, mPaint);
243 | }
244 | }
245 |
246 |
247 | public void startAnim() {
248 | if (mItemList.size() == 0) {
249 | return;
250 | }
251 | invalidate();
252 | }
253 |
254 |
255 | public void dismiss() {
256 | if (!mAlphaAnim.isRunning()) {
257 | mAlphaAnim.start();
258 | }
259 | }
260 |
261 | private void removeView() {
262 | ViewGroup vg = (ViewGroup) this.getParent();
263 | if (vg != null) {
264 | vg.removeView(this);
265 | }
266 | }
267 |
268 | @Override
269 | protected void onWindowVisibilityChanged(int visibility) {
270 | if (visibility == GONE) {
271 | if (mOnMenuClickListener != null) {
272 | mOnMenuClickListener.dismiss();
273 | }
274 | }
275 | super.onWindowVisibilityChanged(visibility);
276 |
277 |
278 | }
279 |
280 | public void setOnMenuClickListener(OnMenuClickListener onMenuClickListener) {
281 | this.mOnMenuClickListener = onMenuClickListener;
282 | }
283 |
284 | public interface OnMenuClickListener {
285 | void onItemClick(int position, String title);
286 |
287 | void dismiss();
288 |
289 | }
290 |
291 | private abstract class MyAnimListener implements Animator.AnimatorListener {
292 | @Override
293 | public void onAnimationStart(Animator animation) {
294 |
295 | }
296 |
297 | @Override
298 | public void onAnimationCancel(Animator animation) {
299 |
300 | }
301 |
302 | @Override
303 | public void onAnimationRepeat(Animator animation) {
304 |
305 | }
306 | }
307 |
308 |
309 | @Override
310 | public boolean onTouchEvent(MotionEvent event) {
311 | switch (event.getAction()) {
312 | case MotionEvent.ACTION_DOWN:
313 | for (int i = 0; i < mItemRectList.size(); i++) {
314 | if (mOnMenuClickListener != null && isPointInRect(new PointF(event.getX(), event.getY()), mItemRectList.get(i))) {
315 | mOnMenuClickListener.onItemClick(i, mItemList.get(i).title);
316 | return true;
317 | }
318 | }
319 | dismiss();
320 | }
321 | return false;
322 | }
323 |
324 | private boolean isPointInRect(PointF pointF, RectF targetRect) {
325 | return pointF.x >= targetRect.left && pointF.x <= targetRect.right && pointF.y >= targetRect.top && pointF.y <= targetRect.bottom;
326 | }
327 |
328 |
329 | public static class Builder {
330 |
331 | private Context mActivity;
332 | private List mFloatItems = new ArrayList<>();
333 | private int mBgColor = Color.TRANSPARENT;
334 | private int mStatus = STATUS_LEFT;
335 | private boolean cicleBg = false;
336 | private int mMenuBackgroundColor = -1;
337 | private boolean mDrawNum = false;
338 |
339 |
340 | public Builder drawNum(boolean drawNum) {
341 | mDrawNum = drawNum;
342 | return this;
343 | }
344 |
345 |
346 | public Builder setMenuBackgroundColor(int mMenuBackgroundColor) {
347 | this.mMenuBackgroundColor = mMenuBackgroundColor;
348 | return this;
349 | }
350 |
351 |
352 | public Builder setCicleBg(boolean cicleBg) {
353 | this.cicleBg = cicleBg;
354 | return this;
355 | }
356 |
357 | public Builder setStatus(int status) {
358 | mStatus = status;
359 | return this;
360 | }
361 |
362 | public Builder setFloatItems(List floatItems) {
363 | this.mFloatItems = floatItems;
364 | return this;
365 | }
366 |
367 |
368 | public Builder(Context activity ) {
369 | mActivity = activity;
370 | }
371 |
372 | public Builder addItem(FloatItem floatItem) {
373 | mFloatItems.add(floatItem);
374 | return this;
375 | }
376 |
377 | public Builder addItems(List list) {
378 | mFloatItems.addAll(list);
379 | return this;
380 | }
381 |
382 | public Builder setBackgroundColor(int color) {
383 | mBgColor = color;
384 | return this;
385 | }
386 |
387 | public FloatMenuView create() {
388 | FloatMenuView floatMenuView = new FloatMenuView(mActivity, mStatus);
389 | floatMenuView.setItemList(mFloatItems);
390 | floatMenuView.setBackgroundColor(mBgColor);
391 | floatMenuView.setCicleBg(cicleBg);
392 | floatMenuView.startAnim();
393 | floatMenuView.drawNum(mDrawNum);
394 | floatMenuView.setMenuBackgroundColor(mMenuBackgroundColor);
395 | return floatMenuView;
396 | }
397 |
398 | }
399 |
400 |
401 | private int dip2px(float dipValue) {
402 | final float scale = getContext().getResources().getDisplayMetrics().density;
403 | return (int) (dipValue * scale + 0.5f);
404 | }
405 |
406 | private int sp2px(float spValue) {
407 | final float fontScale = getContext().getResources().getDisplayMetrics().scaledDensity;
408 | return (int) (spValue * fontScale + 0.5f);
409 | }
410 |
411 | private float getTextHeight(String text, Paint paint) {
412 | Rect rect = new Rect();
413 | paint.getTextBounds(text, 0, text.length(), rect);
414 | return rect.height() / 1.1f;
415 | }
416 |
417 | private float getTextWidth(String text, Paint paint) {
418 | return paint.measureText(text);
419 | }
420 | }
421 |
--------------------------------------------------------------------------------
/FloatMenu/src/main/java/com/yw/game/floatmenu/Utils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2016, Shanghai YUEWEN Information Technology Co., Ltd.
3 | * All rights reserved.
4 | * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5 | *
6 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8 | * Neither the name of Shanghai YUEWEN Information Technology Co., Ltd. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
9 | *
10 | * THIS SOFTWARE IS PROVIDED BY SHANGHAI YUEWEN INFORMATION TECHNOLOGY CO., LTD. AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
11 | *
12 | */
13 |
14 | package com.yw.game.floatmenu;
15 |
16 |
17 | import android.content.Context;
18 | import android.util.TypedValue;
19 | import android.view.ViewGroup;
20 | import android.view.WindowManager;
21 | import android.view.animation.AccelerateInterpolator;
22 | import android.view.animation.AlphaAnimation;
23 | import android.view.animation.Animation;
24 | import android.view.animation.AnimationSet;
25 | import android.view.animation.LinearInterpolator;
26 | import android.view.animation.ScaleAnimation;
27 | import android.widget.FrameLayout;
28 |
29 | import java.lang.reflect.Field;
30 |
31 | public class Utils {
32 |
33 | private static int statusBarHeight;
34 |
35 | /**
36 | * 用于获取状态栏的高度。
37 | *
38 | * @return 返回状态栏高度的像素值。
39 | */
40 | public static int getStatusBarHeight(Context context) {
41 | if (statusBarHeight == 0) {
42 | try {
43 | Class> c = Class.forName("com.android.internal.R$dimen");
44 | Object o = c.newInstance();
45 | Field field = c.getField("status_bar_height");
46 | int x = (Integer) field.get(o);
47 | statusBarHeight = context.getResources().getDimensionPixelSize(x);
48 | } catch (Exception e) {
49 | e.printStackTrace();
50 | }
51 | }
52 | return statusBarHeight;
53 | }
54 |
55 |
56 |
57 |
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/FloatMenu/src/main/java/com/yw/game/floatmenu/customfloat/FloatManager.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2016, Shanghai YUEWEN Information Technology Co., Ltd.
3 | * All rights reserved.
4 | * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5 | *
6 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8 | * Neither the name of Shanghai YUEWEN Information Technology Co., Ltd. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
9 | *
10 | * THIS SOFTWARE IS PROVIDED BY SHANGHAI YUEWEN INFORMATION TECHNOLOGY CO., LTD. AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
11 | *
12 | */
13 | package com.yw.game.floatmenu.customfloat;
14 |
15 | import android.animation.Animator;
16 | import android.animation.ValueAnimator;
17 | import android.app.Activity;
18 | import android.content.Context;
19 | import android.content.SharedPreferences;
20 | import android.graphics.PixelFormat;
21 | import android.os.Build;
22 | import android.os.CountDownTimer;
23 | import android.os.Handler;
24 | import android.os.Looper;
25 | import android.util.TypedValue;
26 | import android.view.Gravity;
27 | import android.view.MotionEvent;
28 | import android.view.View;
29 | import android.view.WindowManager;
30 | import android.view.animation.Interpolator;
31 | import android.view.animation.LinearInterpolator;
32 |
33 |
34 | /**
35 | * Created by wengyiming on 2017/8/25.
36 | */
37 |
38 | public class FloatManager {
39 |
40 | /**
41 | * 悬浮球 坐落 左 右 标记
42 | */
43 | public static final int LEFT = 0;
44 | public static final int RIGHT = 1;
45 |
46 | /**
47 | * 记录 logo 停放的位置,以备下次恢复
48 | */
49 | private static final String LOCATION_X = "hintLocation";
50 | private static final String LOCATION_Y = "locationY";
51 |
52 |
53 | /**
54 | * 停靠默认位置
55 | */
56 | private int mDefaultLocation = RIGHT;
57 |
58 |
59 | /**
60 | * 悬浮窗 坐落 位置
61 | */
62 | private int mHintLocation = mDefaultLocation;
63 |
64 |
65 | /**
66 | * 记录当前手指位置在屏幕上的横坐标值
67 | */
68 | private float mXInScreen;
69 |
70 | /**
71 | * 记录当前手指位置在屏幕上的纵坐标值
72 | */
73 | private float mYInScreen;
74 |
75 | /**
76 | * 记录手指按下时在屏幕上的横坐标的值
77 | */
78 | private float mXDownInScreen;
79 |
80 | /**
81 | * 记录手指按下时在屏幕上的纵坐标的值
82 | */
83 | private float mYDownInScreen;
84 |
85 | /**
86 | * 记录手指按下时在小悬浮窗的View上的横坐标的值
87 | */
88 | private float mXInView;
89 |
90 | /**
91 | * 记录手指按下时在小悬浮窗的View上的纵坐标的值
92 | */
93 | private float mYinview;
94 |
95 | /**
96 | * 记录屏幕的宽度
97 | */
98 | private int mScreenWidth;
99 |
100 | /**
101 | * 来自 activity 的 wManager
102 | */
103 | private WindowManager wManager;
104 |
105 |
106 | /**
107 | * 为 wManager 设置 LayoutParams
108 | */
109 | private WindowManager.LayoutParams wmParams;
110 |
111 | /**
112 | * 用于显示在 mActivity 上的 mActivity
113 | */
114 | private Context mActivity;
115 |
116 |
117 | /**
118 | * 用于 定时 隐藏 logo的定时器
119 | */
120 | private CountDownTimer mHideTimer;
121 |
122 |
123 | /**
124 | * float menu的高度
125 | */
126 | private Handler mHandler = new Handler(Looper.getMainLooper());
127 |
128 |
129 | /**
130 | * 悬浮窗左右移动到默认位置 动画的 加速器
131 | */
132 | private Interpolator mLinearInterpolator = new LinearInterpolator();
133 |
134 | /**
135 | * 标记是否拖动中
136 | */
137 | private boolean isDraging = false;
138 |
139 | /**
140 | * 用于恢复悬浮球的location的属性动画值
141 | */
142 | private int mResetLocationValue;
143 |
144 | /**
145 | * 这个事件用于处理移动、自定义点击或者其它事情,return true可以保证onclick事件失效
146 | */
147 | private View.OnTouchListener touchListener = new View.OnTouchListener() {
148 | @Override
149 | public boolean onTouch(View v, MotionEvent event) {
150 | switch (event.getAction()) {
151 | case MotionEvent.ACTION_DOWN:
152 | floatEventDown(event);
153 | break;
154 | case MotionEvent.ACTION_MOVE:
155 | floatEventMove(event);
156 | break;
157 | case MotionEvent.ACTION_UP:
158 | case MotionEvent.ACTION_CANCEL:
159 | floatEventUp();
160 | break;
161 | }
162 | return true;
163 | }
164 | };
165 |
166 | ValueAnimator valueAnimator = null;
167 | private boolean isExpaned = false;
168 |
169 | private View smallView;
170 | private View rightView;
171 | private View leftView;
172 |
173 | private GetViewCallback mGetViewCallback;
174 |
175 | public interface GetViewCallback {
176 | View getLeftView(View.OnTouchListener touchListener);
177 |
178 | View getRightView(View.OnTouchListener touchListener);
179 |
180 | View getLogoView();
181 |
182 |
183 |
184 | void resetLogoViewSize(int hintLocation, View logoView);//logo恢复原始大小
185 | void dragingLogoViewOffset(View logoView, boolean isDraging, boolean isResetPosition, float offset);//logo正被拖动,或真在恢复原位
186 |
187 | void shrinkLeftLogoView(View logoView);//logo左边收缩
188 |
189 | void shrinkRightLogoView(View logoView);//logo右边收缩
190 |
191 | void leftViewOpened(View leftView);//左菜单打开
192 |
193 | void rightViewOpened(View rightView);//右菜单打开
194 |
195 | void leftOrRightViewClosed(View logoView);
196 |
197 | void onDestoryed();
198 |
199 | }
200 |
201 |
202 | public FloatManager(Context context, GetViewCallback getViewCallback) {
203 | this.mActivity = context;
204 | this.mGetViewCallback = getViewCallback;
205 |
206 | if (mGetViewCallback == null) {
207 | throw new IllegalArgumentException("Must impl GetViewCallback !");
208 | }
209 | initFloatWindow();
210 | initTimer();
211 | initFloatView();
212 |
213 | }
214 |
215 |
216 |
217 | private void initFloatView() {
218 | smallView = mGetViewCallback.getLogoView();
219 | leftView = mGetViewCallback.getLeftView(touchListener);
220 | rightView = mGetViewCallback.getRightView(touchListener);
221 |
222 | if (smallView == null) {
223 | throw new IllegalArgumentException("Must impl GetViewCallback and make getLogoView() return a logoView !");
224 | }
225 |
226 | smallView.setOnTouchListener(touchListener);//恢复touch事件
227 |
228 | }
229 |
230 | /**
231 | * 初始化 隐藏悬浮球的定时器
232 | */
233 | private void initTimer() {
234 | mHideTimer = new CountDownTimer(2000, 10) { //悬浮窗超过5秒没有操作的话会自动隐藏
235 | @Override
236 | public void onTick(long millisUntilFinished) {
237 | if (isExpaned) {
238 | mHideTimer.cancel();
239 | }
240 | }
241 |
242 | @Override
243 | public void onFinish() {
244 | if (isExpaned) {
245 | mHideTimer.cancel();
246 | return;
247 | }
248 | if (!isDraging) {
249 | if (mHintLocation == LEFT) {
250 | // mFloatLogo.setStatus(DotImageView.HIDE_LEFT);
251 | // mFloatLogo.setDrawDarkBg(true);
252 | mGetViewCallback.shrinkLeftLogoView(smallView);
253 | } else {
254 | mGetViewCallback.shrinkRightLogoView(smallView);
255 | // mFloatLogo.setStatus(DotImageView.HIDE_RIGHT);
256 | // mFloatLogo.setDrawDarkBg(true);
257 | }
258 | // mFloatLogo.setOnTouchListener(mDefaultOnTouchListerner);//把onClick事件分发下去,防止onclick无效
259 | }
260 | }
261 | };
262 | }
263 |
264 |
265 |
266 |
267 | /**
268 | * 初始化悬浮球 window
269 | */
270 | private void initFloatWindow() {
271 | wmParams = new WindowManager.LayoutParams();
272 | if (mActivity instanceof Activity) {
273 | Activity activity = (Activity) mActivity;
274 | wManager = activity.getWindowManager();
275 | } else {
276 | wManager = (WindowManager) mActivity.getSystemService(Context.WINDOW_SERVICE);
277 | //判断状态栏是否显示 如果不显示则statusBarHeight为0
278 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
279 | if (Build.VERSION.SDK_INT > 23) {
280 | //在android7.1以上系统需要使用TYPE_PHONE类型 配合运行时权限
281 | wmParams.type = WindowManager.LayoutParams.TYPE_PHONE;
282 | } else {
283 | wmParams.type = WindowManager.LayoutParams.TYPE_TOAST;
284 | }
285 | } else {
286 | wmParams.type = WindowManager.LayoutParams.TYPE_PHONE;
287 | }
288 | }
289 | mScreenWidth = wManager.getDefaultDisplay().getWidth();
290 | int screenHeigth = wManager.getDefaultDisplay().getHeight();
291 | wmParams.format = PixelFormat.RGBA_8888;
292 | wmParams.gravity = Gravity.LEFT | Gravity.TOP;
293 | wmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
294 | mHintLocation = getSetting(LOCATION_X, mDefaultLocation);
295 | int defaultY = ((screenHeigth) / 2) / 3;
296 | int y = getSetting(LOCATION_Y, defaultY);
297 | if (mHintLocation == LEFT) {
298 | wmParams.x = 0;
299 | } else {
300 | wmParams.x = mScreenWidth;
301 | }
302 | if (y != 0 && y != defaultY) {
303 | wmParams.y = y;
304 | } else {
305 | wmParams.y = defaultY;
306 | }
307 | wmParams.alpha = 1;
308 | wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
309 | wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
310 | }
311 |
312 | /**
313 | * 悬浮窗touch事件的 down 事件
314 | */
315 | private void floatEventDown(MotionEvent event) {
316 | isDraging = false;
317 | mHideTimer.cancel();
318 |
319 | mGetViewCallback.resetLogoViewSize(mHintLocation,smallView);
320 |
321 | // if (smallView.getStatus() != DotImageView.NORMAL) {
322 | // smallView.setStatus(DotImageView.NORMAL);
323 | // }
324 | // if (!smallView.mDrawDarkBg) {
325 | // smallView.setDrawDarkBg(true);
326 | // }
327 | // if (smallView.getStatus() != DotImageView.NORMAL) {
328 | // smallView.setStatus(DotImageView.NORMAL);
329 | // }
330 |
331 |
332 | mXInView = event.getX();
333 | mYinview = event.getY();
334 | mXDownInScreen = event.getRawX();
335 | mYDownInScreen = event.getRawY();
336 | mXInScreen = event.getRawX();
337 | mYInScreen = event.getRawY();
338 |
339 |
340 | }
341 |
342 | /**
343 | * 悬浮窗touch事件的 move 事件
344 | */
345 | private void floatEventMove(MotionEvent event) {
346 | mXInScreen = event.getRawX();
347 | mYInScreen = event.getRawY();
348 |
349 |
350 | //连续移动的距离超过3则更新一次视图位置
351 | if (Math.abs(mXInScreen - mXDownInScreen) > smallView.getWidth() / 4 || Math.abs(mYInScreen - mYDownInScreen) > smallView.getWidth() / 4) {
352 | wmParams.x = (int) (mXInScreen - mXInView);
353 | wmParams.y = (int) (mYInScreen - mYinview) - smallView.getHeight() / 2;
354 | updateViewPosition(); // 手指移动的时候更新小悬浮窗的位置
355 | double a = mScreenWidth / 2;
356 | float offset = (float) ((a - (Math.abs(wmParams.x - a))) / a);
357 | // smallView.setDraging(isDraging, offset, false);
358 | mGetViewCallback.dragingLogoViewOffset(smallView,isDraging,false,offset);
359 | } else {
360 | isDraging = false;
361 | // smallView.setDraging(false, 0, true);
362 | mGetViewCallback.dragingLogoViewOffset(smallView,false,true,0);
363 | }
364 | }
365 |
366 | /**
367 | * 悬浮窗touch事件的 up 事件
368 | */
369 | private void floatEventUp() {
370 | if (mXInScreen < mScreenWidth / 2) { //在左边
371 | mHintLocation = LEFT;
372 | } else { //在右边
373 | mHintLocation = RIGHT;
374 | }
375 |
376 |
377 | valueAnimator = ValueAnimator.ofInt(64);
378 | valueAnimator.setInterpolator(mLinearInterpolator);
379 | valueAnimator.setDuration(1000);
380 | valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
381 | @Override
382 | public void onAnimationUpdate(ValueAnimator animation) {
383 | mResetLocationValue = (int) animation.getAnimatedValue();
384 | mHandler.post(updatePositionRunnable);
385 | }
386 | });
387 |
388 | valueAnimator.addListener(new Animator.AnimatorListener() {
389 | @Override
390 | public void onAnimationStart(Animator animation) {
391 |
392 | }
393 |
394 | @Override
395 | public void onAnimationEnd(Animator animation) {
396 | if (Math.abs(wmParams.x) < 0) {
397 | wmParams.x = 0;
398 | } else if (Math.abs(wmParams.x) > mScreenWidth) {
399 | wmParams.x = mScreenWidth;
400 | }
401 | updateViewPosition();
402 | isDraging = false;
403 | mGetViewCallback.dragingLogoViewOffset(smallView,false,true,0);
404 | // smallView.setDraging(false, 0, true);
405 | mHideTimer.start();
406 | }
407 |
408 | @Override
409 | public void onAnimationCancel(Animator animation) {
410 | if (Math.abs(wmParams.x) < 0) {
411 | wmParams.x = 0;
412 | } else if (Math.abs(wmParams.x) > mScreenWidth) {
413 | wmParams.x = mScreenWidth;
414 | }
415 |
416 | updateViewPosition();
417 | isDraging = false;
418 | mGetViewCallback.dragingLogoViewOffset(smallView,false,true,0);
419 | // smallView.setDraging(false, 0, true);
420 | mHideTimer.start();
421 |
422 | }
423 |
424 | @Override
425 | public void onAnimationRepeat(Animator animation) {
426 |
427 | }
428 | });
429 | if (!valueAnimator.isRunning()) {
430 | valueAnimator.start();
431 | }
432 |
433 | // //这里需要判断如果如果手指所在位置和logo所在位置在一个宽度内则不移动,
434 | if (Math.abs(mXInScreen - mXDownInScreen) > smallView.getWidth() / 5 || Math.abs(mYInScreen - mYDownInScreen) > smallView.getHeight() / 5) {
435 | isDraging = false;
436 | } else {
437 | openMenu();
438 | }
439 |
440 | }
441 |
442 | /**
443 | * 手指离开屏幕后 用于恢复 悬浮球的 logo 的左右位置
444 | */
445 | private Runnable updatePositionRunnable = new Runnable() {
446 | @Override
447 | public void run() {
448 | isDraging = true;
449 | checkPosition();
450 | }
451 | };
452 |
453 |
454 |
455 | /**
456 | * 用于检查并更新悬浮球的位置
457 | */
458 | private void checkPosition() {
459 | if (wmParams.x > 0 && wmParams.x < mScreenWidth) {
460 | if (mHintLocation == LEFT) {
461 | wmParams.x = wmParams.x - mResetLocationValue;
462 | } else {
463 | wmParams.x = wmParams.x + mResetLocationValue;
464 | }
465 | updateViewPosition();
466 | double a = mScreenWidth / 2;
467 | float offset = (float) ((a - (Math.abs(wmParams.x - a))) / a);
468 | // smallView.setDraging(isDraging, offset, true);
469 | mGetViewCallback.dragingLogoViewOffset(smallView,isDraging,true,offset);
470 | return;
471 | }
472 |
473 |
474 | if (Math.abs(wmParams.x) < 0) {
475 | wmParams.x = 0;
476 | } else if (Math.abs(wmParams.x) > mScreenWidth) {
477 | wmParams.x = mScreenWidth;
478 | }
479 | if (valueAnimator.isRunning()) {
480 | valueAnimator.cancel();
481 | }
482 |
483 |
484 | updateViewPosition();
485 | isDraging = false;
486 |
487 |
488 | }
489 |
490 | public void show() {
491 | try {
492 | if (wManager != null && wmParams != null && smallView != null) {
493 | wManager.addView(smallView, wmParams);
494 | }
495 | if (mHideTimer != null) {
496 | mHideTimer.start();
497 | } else {
498 | initTimer();
499 | mHideTimer.start();
500 | }
501 | } catch (Exception e) {
502 | e.printStackTrace();
503 | }
504 | }
505 |
506 | /**
507 | * 打开菜单
508 | */
509 | private void openMenu() {
510 | if (isDraging) return;
511 |
512 | if (!isExpaned) {
513 | // smallView.setDrawDarkBg(false);
514 | try {
515 | wManager.removeViewImmediate(smallView);
516 | if (mHintLocation == RIGHT) {
517 | wManager.addView(rightView, wmParams);
518 | mGetViewCallback.rightViewOpened(rightView);
519 | } else {
520 | wManager.addView(leftView, wmParams);
521 | mGetViewCallback.leftViewOpened(leftView);
522 | }
523 | } catch (Exception e) {
524 | e.printStackTrace();
525 | }
526 |
527 | isExpaned = true;
528 | mHideTimer.cancel();
529 | } else {
530 | // smallView.setDrawDarkBg(true);
531 | try {
532 | wManager.removeViewImmediate(mHintLocation == LEFT ? leftView : rightView);
533 | wManager.addView(smallView, wmParams);
534 | mGetViewCallback.leftOrRightViewClosed(smallView);
535 | } catch (Exception e) {
536 | e.printStackTrace();
537 | }
538 |
539 | isExpaned = false;
540 | mHideTimer.start();
541 | }
542 |
543 | }
544 |
545 |
546 | /**
547 | * 更新悬浮窗在屏幕中的位置。
548 | */
549 | private void updateViewPosition() {
550 | isDraging = true;
551 | try {
552 | if (!isExpaned) {
553 | if (wmParams.y - smallView.getHeight() / 2 <= 0) {
554 | wmParams.y = 25;
555 | isDraging = true;
556 | }
557 | wManager.updateViewLayout(smallView, wmParams);
558 | }
559 | } catch (Exception e) {
560 | e.printStackTrace();
561 | }
562 | }
563 |
564 | /**
565 | * 移除所有悬浮窗 释放资源
566 | */
567 | public void destoryFloat() {
568 | //记录上次的位置logo的停放位置,以备下次恢复
569 | saveSetting(LOCATION_X, mHintLocation);
570 | saveSetting(LOCATION_Y, wmParams.y);
571 | smallView.clearAnimation();
572 | try {
573 | mHideTimer.cancel();
574 | if (isExpaned) {
575 | wManager.removeViewImmediate(mHintLocation == LEFT ? leftView : rightView);
576 | } else {
577 | wManager.removeViewImmediate(smallView);
578 | }
579 | isExpaned = false;
580 | isDraging = false;
581 | mGetViewCallback.onDestoryed();
582 | } catch (Exception e) {
583 | e.printStackTrace();
584 | }
585 | }
586 |
587 | /**
588 | * 用于保存悬浮球的位置记录
589 | *
590 | * @param key String
591 | * @param defaultValue int
592 | * @return int
593 | */
594 | private int getSetting(String key, int defaultValue) {
595 | try {
596 | SharedPreferences sharedata = mActivity.getSharedPreferences("floatLogo", 0);
597 | return sharedata.getInt(key, defaultValue);
598 | } catch (Exception e) {
599 | e.printStackTrace();
600 | }
601 | return defaultValue;
602 | }
603 |
604 | /**
605 | * 用于保存悬浮球的位置记录
606 | *
607 | * @param key String
608 | * @param value int
609 | */
610 | public void saveSetting(String key, int value) {
611 | try {
612 | SharedPreferences.Editor sharedata = mActivity.getSharedPreferences("floatLogo", 0).edit();
613 | sharedata.putInt(key, value);
614 | sharedata.apply();
615 | } catch (Exception e) {
616 | e.printStackTrace();
617 | }
618 | }
619 |
620 | public static int dp2Px(float dp, Context mContext) {
621 | return (int) TypedValue.applyDimension(
622 | TypedValue.COMPLEX_UNIT_DIP,
623 | dp,
624 | mContext.getResources().getDisplayMetrics());
625 | }
626 | }
627 |
--------------------------------------------------------------------------------
/FloatMenu/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 | FloatMenu
16 |
17 |
--------------------------------------------------------------------------------
/FloatMenuDemo/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/FloatMenuDemo/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 25
5 | buildToolsVersion '25.0.2'
6 |
7 | defaultConfig {
8 | applicationId "com.yw.game.floatmenu.demo"
9 | minSdkVersion 11
10 | targetSdkVersion 25
11 | versionCode 1
12 | versionName "1.1"
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:25.3.1'
26 | compile 'com.yw.game.sclib:shortCutLib:0.0.3'
27 | compile project(':FloatMenu')
28 | compile 'com.android.support:support-v4:25.3.1'
29 | }
30 |
--------------------------------------------------------------------------------
/FloatMenuDemo/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/wengyiming/android/adt-bundle-mac-x86_64-20140702/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 |
--------------------------------------------------------------------------------
/FloatMenuDemo/src/androidTest/java/com/yw/game/floatmenu/demo/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.yw.game.floatmenu.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 | }
--------------------------------------------------------------------------------
/FloatMenuDemo/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 | // .withContext(mActivity.getApplication())需要此权限
6 |
7 |
8 |
9 |
10 |
11 |
17 |
19 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/FloatMenuDemo/src/main/java/com/yw/game/floatmenu/demo/MainActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2016, Shanghai YUEWEN Information Technology Co., Ltd.
3 | * All rights reserved.
4 | * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5 | *
6 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8 | * Neither the name of Shanghai YUEWEN Information Technology Co., Ltd. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
9 | *
10 | * THIS SOFTWARE IS PROVIDED BY SHANGHAI YUEWEN INFORMATION TECHNOLOGY CO., LTD. AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
11 | *
12 | */
13 |
14 | package com.yw.game.floatmenu.demo;
15 |
16 | import android.animation.ValueAnimator;
17 | import android.app.Activity;
18 | import android.graphics.BitmapFactory;
19 | import android.os.Build;
20 | import android.os.Bundle;
21 | import android.os.Handler;
22 | import android.os.Looper;
23 | import android.text.TextUtils;
24 | import android.view.Gravity;
25 | import android.view.View;
26 | import android.view.animation.LinearInterpolator;
27 | import android.widget.FrameLayout;
28 | import android.widget.ImageView;
29 | import android.widget.LinearLayout;
30 | import android.widget.TextView;
31 | import android.widget.Toast;
32 |
33 | import com.yw.game.floatmenu.FloatItem;
34 | import com.yw.game.floatmenu.FloatLogoMenu;
35 | import com.yw.game.floatmenu.FloatMenuView;
36 | import com.yw.game.floatmenu.customfloat.FloatManager;
37 |
38 | import java.util.ArrayList;
39 |
40 | public class MainActivity extends Activity {
41 |
42 | FloatLogoMenu mFloatMenu;
43 | FloatLogoMenu mFloatMenu1;
44 | ArrayList itemList = new ArrayList<>();
45 | private Activity mActivity;
46 |
47 | String HOME = "首页";
48 | String FEEDBACK = "客服";
49 | String MESSAGE = "消息";
50 |
51 | String[] MENU_ITEMS = {HOME, FEEDBACK, MESSAGE};
52 |
53 | private int[] menuIcons = new int[]{R.drawable.yw_menu_account, R.drawable.yw_menu_fb, R.drawable.yw_menu_msg};
54 |
55 | FloatManager floatManager;
56 |
57 | @Override
58 | protected void onCreate(Bundle savedInstanceState) {
59 | super.onCreate(savedInstanceState);
60 | setContentView(R.layout.activity_main);
61 |
62 |
63 | mActivity = this;
64 | for (int i = 0; i < menuIcons.length; i++) {
65 | itemList.add(new FloatItem(MENU_ITEMS[i], 0x99000000, 0x99000000, BitmapFactory.decodeResource(this.getResources(), menuIcons[i]), String.valueOf(i + 1)));
66 | }
67 | }
68 |
69 |
70 | @Override
71 | protected void onResume() {
72 | super.onResume();
73 | if (mFloatMenu == null) {
74 | mFloatMenu = new FloatLogoMenu.Builder()
75 | .withActivity(mActivity)
76 | // .withContext(mActivity.getApplication())//这个在7.0(包括7.0)以上以及大部分7.0以下的国产手机上需要用户授权,需要搭配
77 | .logo(BitmapFactory.decodeResource(getResources(), R.drawable.yw_game_logo))
78 | .drawCicleMenuBg(true)
79 | .backMenuColor(0xffe4e3e1)
80 | .setBgDrawable(this.getResources().getDrawable(R.drawable.yw_game_float_menu_bg))
81 | //这个背景色需要和logo的背景色一致
82 | .setFloatItems(itemList)
83 | .defaultLocation(FloatLogoMenu.RIGHT)
84 | .drawRedPointNum(false)
85 | .showWithListener(new FloatMenuView.OnMenuClickListener() {
86 | @Override
87 | public void onItemClick(int position, String title) {
88 | Toast.makeText(MainActivity.this, "position " + position + " title:" + title + " is clicked.", Toast.LENGTH_SHORT).show();
89 | }
90 |
91 | @Override
92 | public void dismiss() {
93 |
94 | }
95 | });
96 |
97 | new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
98 | @Override
99 | public void run() {
100 | refreshDot();
101 | }
102 | }, 5000);
103 |
104 | //同时只能new一个
105 | }
106 |
107 |
108 | if (floatManager != null) return;
109 | floatManager = new FloatManager(this, new FloatManager.GetViewCallback() {
110 | @Override
111 | public View getLeftView(View.OnTouchListener touchListener) {
112 | LinearLayout linearLayout = new LinearLayout(mActivity);
113 | linearLayout.setOrientation(LinearLayout.HORIZONTAL);
114 | linearLayout.setGravity(Gravity.CENTER);
115 |
116 | TextView textView = new TextView(mActivity);
117 | textView.setText("左边");
118 |
119 | textView.setOnClickListener(new View.OnClickListener() {
120 | @Override
121 | public void onClick(View v) {
122 | Toast.makeText(mActivity, "左边的菜单被点击了", Toast.LENGTH_SHORT).show();
123 | }
124 | });
125 | ImageView imageView = new ImageView(mActivity);
126 | imageView.setLayoutParams(new LinearLayout.LayoutParams(dip2px(50), dip2px(50)));
127 | imageView.setImageResource(R.drawable.yw_game_logo);
128 | imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
129 | imageView.setOnTouchListener(touchListener);
130 |
131 | linearLayout.setLayoutParams(new LinearLayout.LayoutParams(dip2px(50), dip2px(50)));
132 | linearLayout.setBackgroundResource(R.drawable.yw_game_float_menu_bg);
133 | linearLayout.addView(imageView);
134 | linearLayout.addView(textView);
135 | return linearLayout;
136 | }
137 |
138 | @Override
139 | public View getRightView(View.OnTouchListener touchListener) {
140 | LinearLayout linearLayout = new LinearLayout(mActivity);
141 | linearLayout.setOrientation(LinearLayout.HORIZONTAL);
142 | linearLayout.setGravity(Gravity.CENTER);
143 | TextView textView = new TextView(mActivity);
144 | textView.setText("右边");
145 | textView.setOnClickListener(new View.OnClickListener() {
146 | @Override
147 | public void onClick(View v) {
148 | Toast.makeText(mActivity, "右边的菜单被点击了", Toast.LENGTH_SHORT).show();
149 | }
150 | });
151 |
152 | ImageView imageView = new ImageView(mActivity);
153 | imageView.setLayoutParams(new LinearLayout.LayoutParams(dip2px(50), dip2px(50)));
154 | imageView.setImageResource(R.drawable.yw_game_logo);
155 | imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
156 | imageView.setOnTouchListener(touchListener);
157 |
158 | linearLayout.setLayoutParams(new LinearLayout.LayoutParams(dip2px(50), dip2px(50)));
159 | linearLayout.setBackgroundResource(R.drawable.yw_game_float_menu_bg);
160 | linearLayout.addView(textView);
161 | linearLayout.addView(imageView);
162 | return linearLayout;
163 | }
164 |
165 | @Override
166 | public View getLogoView() {
167 |
168 |
169 | LinearLayout linearLayout = new LinearLayout(mActivity);
170 | linearLayout.setOrientation(LinearLayout.HORIZONTAL);
171 | linearLayout.setGravity(Gravity.CENTER);
172 |
173 | ImageView imageView = new ImageView(mActivity);
174 | imageView.setLayoutParams(new LinearLayout.LayoutParams(dip2px(50), dip2px(50)));
175 | imageView.setScaleType(ImageView.ScaleType.CENTER);
176 | imageView.setImageResource(R.drawable.yw_game_logo);
177 |
178 | linearLayout.setLayoutParams(new LinearLayout.LayoutParams(dip2px(50), dip2px(50)));
179 | linearLayout.setBackgroundResource(R.drawable.yw_game_float_menu_bg);
180 |
181 | linearLayout.addView(imageView);
182 | return linearLayout;
183 | }
184 |
185 | @Override
186 | public void resetLogoViewSize(int hintLocation, View logoView) {
187 | logoView.clearAnimation();
188 | logoView.setTranslationX(0);
189 | logoView.setScaleX(1);
190 | logoView.setScaleY(1);
191 | }
192 |
193 | @Override
194 | public void dragingLogoViewOffset(final View smallView, boolean isDraging, boolean isResetPosition, float offset) {
195 | if (isDraging && offset > 0) {
196 | smallView.setBackgroundDrawable(null);
197 | smallView.setScaleX(1 + offset);
198 | smallView.setScaleY(1 + offset);
199 | } else {
200 | smallView.setBackgroundResource(R.drawable.yw_game_float_menu_bg);
201 | smallView.setTranslationX(0);
202 | smallView.setScaleX(1);
203 | smallView.setScaleY(1);
204 | }
205 |
206 |
207 | if (isResetPosition) {
208 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
209 | smallView.setRotation(offset * 360);
210 | }
211 | } else {
212 | ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 360 * 4);
213 | valueAnimator.setInterpolator(new LinearInterpolator());
214 | valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
215 | @Override
216 | public void onAnimationUpdate(ValueAnimator animation) {
217 | int v = (int) animation.getAnimatedValue();
218 | smallView.setRotation(v);
219 | }
220 | });
221 | valueAnimator.setDuration(800);
222 | valueAnimator.start();
223 | }
224 | }
225 |
226 | @Override
227 | public void shrinkLeftLogoView(View smallView) {
228 | smallView.setTranslationX(-smallView.getWidth() / 3);
229 | }
230 |
231 | @Override
232 | public void shrinkRightLogoView(View smallView) {
233 | smallView.setTranslationX(smallView.getWidth() / 3);
234 | }
235 |
236 | @Override
237 | public void leftViewOpened(View leftView) {
238 | Toast.makeText(mActivity, "左边的菜单被打开了", Toast.LENGTH_SHORT).show();
239 | }
240 |
241 | @Override
242 | public void rightViewOpened(View rightView) {
243 | Toast.makeText(mActivity, "右边的菜单被打开了", Toast.LENGTH_SHORT).show();
244 | }
245 |
246 | @Override
247 | public void leftOrRightViewClosed(View smallView) {
248 | Toast.makeText(mActivity, "菜单被关闭了", Toast.LENGTH_SHORT).show();
249 | }
250 |
251 | @Override
252 | public void onDestoryed() {
253 |
254 | }
255 | });
256 | floatManager.show();
257 | }
258 |
259 |
260 | private int dip2px(float dipValue) {
261 | final float scale = getResources().getDisplayMetrics().density;
262 | return (int) (dipValue * scale + 0.5f);
263 | }
264 |
265 | public void refreshDot() {
266 | for (FloatItem menuItem : itemList) {
267 | if (TextUtils.equals(menuItem.getTitle(), "我的")) {
268 | menuItem.dotNum = String.valueOf(8);
269 | }
270 | }
271 | mFloatMenu.setFloatItemList(itemList);
272 | }
273 |
274 |
275 | @Override
276 | protected void onPause() {
277 | super.onPause();
278 | hideFloat();
279 | }
280 |
281 | @Override
282 | protected void onDestroy() {
283 | destroyFloat();
284 | super.onDestroy();
285 |
286 | if (floatManager != null) floatManager.destoryFloat();
287 |
288 | }
289 |
290 |
291 | public void hideFloat() {
292 | if (mFloatMenu != null) {
293 | mFloatMenu.hide();
294 | }
295 | }
296 |
297 | public void destroyFloat() {
298 | if (mFloatMenu != null) {
299 | mFloatMenu.destoryFloat();
300 | }
301 | mFloatMenu = null;
302 | mActivity = null;
303 | }
304 |
305 |
306 | }
307 |
--------------------------------------------------------------------------------
/FloatMenuDemo/src/main/res/drawable-xxxhdpi/yw_game_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatMenuSample/9eda722e950ba882709810525b9bdc2e585f3591/FloatMenuDemo/src/main/res/drawable-xxxhdpi/yw_game_logo.png
--------------------------------------------------------------------------------
/FloatMenuDemo/src/main/res/drawable-xxxhdpi/yw_image_float_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatMenuSample/9eda722e950ba882709810525b9bdc2e585f3591/FloatMenuDemo/src/main/res/drawable-xxxhdpi/yw_image_float_logo.png
--------------------------------------------------------------------------------
/FloatMenuDemo/src/main/res/drawable-xxxhdpi/yw_menu_account.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatMenuSample/9eda722e950ba882709810525b9bdc2e585f3591/FloatMenuDemo/src/main/res/drawable-xxxhdpi/yw_menu_account.png
--------------------------------------------------------------------------------
/FloatMenuDemo/src/main/res/drawable-xxxhdpi/yw_menu_fb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatMenuSample/9eda722e950ba882709810525b9bdc2e585f3591/FloatMenuDemo/src/main/res/drawable-xxxhdpi/yw_menu_fb.png
--------------------------------------------------------------------------------
/FloatMenuDemo/src/main/res/drawable-xxxhdpi/yw_menu_msg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatMenuSample/9eda722e950ba882709810525b9bdc2e585f3591/FloatMenuDemo/src/main/res/drawable-xxxhdpi/yw_menu_msg.png
--------------------------------------------------------------------------------
/FloatMenuDemo/src/main/res/drawable-xxxhdpi/ywgame_floatmenu_gift.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatMenuSample/9eda722e950ba882709810525b9bdc2e585f3591/FloatMenuDemo/src/main/res/drawable-xxxhdpi/ywgame_floatmenu_gift.png
--------------------------------------------------------------------------------
/FloatMenuDemo/src/main/res/drawable-xxxhdpi/ywgame_floatmenu_user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatMenuSample/9eda722e950ba882709810525b9bdc2e585f3591/FloatMenuDemo/src/main/res/drawable-xxxhdpi/ywgame_floatmenu_user.png
--------------------------------------------------------------------------------
/FloatMenuDemo/src/main/res/drawable/yw_game_float_menu_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
12 |
--------------------------------------------------------------------------------
/FloatMenuDemo/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
22 |
23 |
30 |
31 |
--------------------------------------------------------------------------------
/FloatMenuDemo/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatMenuSample/9eda722e950ba882709810525b9bdc2e585f3591/FloatMenuDemo/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/FloatMenuDemo/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatMenuSample/9eda722e950ba882709810525b9bdc2e585f3591/FloatMenuDemo/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/FloatMenuDemo/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatMenuSample/9eda722e950ba882709810525b9bdc2e585f3591/FloatMenuDemo/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/FloatMenuDemo/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatMenuSample/9eda722e950ba882709810525b9bdc2e585f3591/FloatMenuDemo/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/FloatMenuDemo/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatMenuSample/9eda722e950ba882709810525b9bdc2e585f3591/FloatMenuDemo/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/FloatMenuDemo/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 | #4DBFF1
17 | #4DBFF1
18 | #FF4081
19 |
20 |
--------------------------------------------------------------------------------
/FloatMenuDemo/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
16 | 16dp
17 | 16dp
18 |
19 |
--------------------------------------------------------------------------------
/FloatMenuDemo/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 | FloatMenuSample
16 |
17 |
--------------------------------------------------------------------------------
/FloatMenuDemo/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
16 |
17 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/FloatMenuDemo/src/test/java/com/yw/game/floatmenu/demo/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.yw.game.floatmenu.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 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # FloatMenuSample
2 |
3 | [  ](https://bintray.com/fanofdemo/maven/FloatMenu/_latestVersion)[
](http://www.yuewen.com/)
4 |
5 |
6 | [
游戏产品持续更新](http://xs.qidian.com/Home/Pc/Index/index)
7 |
8 | 欢迎关注 [https://github.com/crosg ](https://github.com/crosg)
9 |
10 | [crosg/FloatMenuSample](https://github.com/crosg/FloatMenuSample)
11 | transfer from [yiming/FloatMenuSample](https://github.com/fanOfDemo/FloatMenuSample)
12 |
13 |
14 |
15 | ## GIF
16 |
17 |
18 |
19 | ## GRADLE:
20 |
21 | compile 'com.yw.game.floatmenu:FloatMenu:2.0.1'
22 |
23 |
24 | android float menu in app
25 |
26 | ## 权限 compatibility & permissions
27 |
28 | 无权限需求,支持 api 11 ++
29 |
30 |
31 | for use:
32 |
33 |
34 |
35 | 使用示例
36 | see sample
37 |
38 | [MainActivity](https://github.com/fanOfDemo/FloatMenuSample/blob/master/FloatMenuDemo/src/main/java/com/yw/game/floatmenu/demo/MainActivity.java)
39 |
40 |
41 | 1:
42 |
43 | mFloatMenu = new FloatLogoMenu.Builder()
44 | .withActivity(mActivity)
45 | // .withContext(mActivity.getApplication())//这个在7.0(包括7.0)以上以及大部分7.0以下的国产手机上需要用户授权,需要搭配
46 | .logo(BitmapFactory.decodeResource(getResources(),R.drawable.yw_game_logo))
47 | .drawCicleMenuBg(true)
48 | .backMenuColor(0xffe4e3e1)
49 | .setBgDrawable(this.getResources().getDrawable(R.drawable.yw_game_float_menu_bg))
50 | //这个背景色需要和logo的背景色一致
51 | .setFloatItems(itemList)
52 | .defaultLocation(FloatLogoMenu.RIGHT)
53 | .drawRedPointNum(false)
54 | .showWithListener(new FloatMenuView.OnMenuClickListener() {
55 | @Override
56 | public void onItemClick(int position, String title) {
57 | Toast.makeText(MainActivity.this, "position " + position + " title:" + title + " is clicked.", Toast.LENGTH_SHORT).show();
58 | }
59 |
60 | @Override
61 | public void dismiss() {
62 |
63 | }
64 | });
65 |
66 |
67 | 2:
68 |
69 | mFloatMenu = new FloatLogoMenu.Builder()
70 | .withActivity(mActivity)
71 | .logo(R.drawable.yw_image_float_logo)
72 | .backMenuColor(0xffe4e3e1)
73 | .drawCicleMenuBg(true)
74 | .setFloatItems(itemList)
75 | .defaultLocation(FloatLogoMenu.RIGHT)
76 | .drawRedPointNum(false)
77 | .showWithListener(new FloatMenuView.OnMenuClickListener() {
78 | @Override
79 | public void onItemClick(int position, String title) {
80 | Toast.makeText(MainActivity.this, "position " + position + " title:" + title + " is clicked.", Toast.LENGTH_SHORT).show();
81 | }
82 |
83 | @Override
84 | public void dismiss() {
85 |
86 | }
87 | });
88 |
89 |
90 | 3: 因上述代码自定义功能较低,提供下面的方式供自行开发,这里可以直接将 FloatManager (https://github.com/crosg/FloatMenuSample/blob/master/FloatMenu/src/main/java/com/yw/game/floatmenu/customfloat/FloatManager.java) 文件拷贝出来直接使用
91 |
92 |
93 |
94 |
95 | floatManager = new FloatManager(this, new FloatManager.GetViewCallback() {
96 | @Override
97 | public View getLeftView(View.OnTouchListener touchListener) {
98 | //这里可以从xml导入一个用于出现在左边的菜单,,如果要模拟上面的效果,需要add一个logo,并设置touchListener
99 | //这里可以对xml中导入的子元素设置点击事件
100 | return null;
101 | }
102 |
103 | @Override
104 | public View getRightView(View.OnTouchListener touchListener) {
105 | //这里可以从xml导入一个用于出现在右边的菜单,如果要模拟上面的效果,需要add一个logo,并设置touchListener
106 | //这里可以对xml中导入的子元素设置点击事件
107 | return null;
108 | }
109 |
110 | @Override
111 | public View getLogoView() {
112 | //这里仍然可以从xml导入布局 ,这个logoView会响应点击拖动事件
113 | return null;
114 | }
115 |
116 | @Override
117 | public void resetLogoViewSize(int hintLocation, View smallView) {
118 | //可以对smallView执行恢复动画之类的
119 | }
120 |
121 | @Override
122 | public void dragingLogoViewOffset(View samllView, boolean isDraging, boolean isResetPosition, float offset) {
123 | //这里可以根据是否拖动,是否靠边 对logoView执行旋转等动画
124 | }
125 |
126 | @Override
127 | public void shrinkLeftLogoView(View logoView) {
128 | //对logo进行左隐藏动画
129 | }
130 |
131 | @Override
132 | public void shrinkRightLogoView(View logoView) {
133 | //对logo进行右隐藏动画
134 | }
135 |
136 | @Override
137 | public void leftViewOpened(View leftView) {
138 | //左菜单被打开
139 | }
140 |
141 | @Override
142 | public void rightViewOpened(View rightView) {
143 | //右菜单被打开
144 | }
145 |
146 | @Override
147 | public void leftOrRightViewClosed(View logoView) {
148 | //右或左菜单被关闭
149 | }
150 |
151 | @Override
152 | public void onDestoryed() {
153 | //所有的view都被移除
154 | }
155 | });
156 |
157 | 示例:
158 |
159 | FloatManager floatManager = new FloatManager(this, new FloatManager.GetViewCallback() {
160 | @Override
161 | public View getLeftView(View.OnTouchListener touchListener) {
162 | LinearLayout linearLayout = new LinearLayout(mActivity);
163 | linearLayout.setOrientation(LinearLayout.HORIZONTAL);
164 | linearLayout.setGravity(Gravity.CENTER);
165 |
166 | TextView textView = new TextView(mActivity);
167 | textView.setText("左边");
168 |
169 | textView.setOnClickListener(new View.OnClickListener() {
170 | @Override
171 | public void onClick(View v) {
172 | Toast.makeText(mActivity, "左边的菜单被点击了", Toast.LENGTH_SHORT).show();
173 | }
174 | });
175 | ImageView imageView = new ImageView(mActivity);
176 | imageView.setLayoutParams(new LinearLayout.LayoutParams(dip2px(50), dip2px(50)));
177 | imageView.setImageResource(R.drawable.yw_game_logo);
178 | imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
179 | imageView.setOnTouchListener(touchListener);
180 |
181 | linearLayout.setLayoutParams(new LinearLayout.LayoutParams(dip2px(50), dip2px(50)));
182 | linearLayout.setBackgroundResource(R.drawable.yw_game_float_menu_bg);
183 | linearLayout.addView(imageView);
184 | linearLayout.addView(textView);
185 | return linearLayout;
186 | }
187 |
188 | @Override
189 | public View getRightView(View.OnTouchListener touchListener) {
190 | LinearLayout linearLayout = new LinearLayout(mActivity);
191 | linearLayout.setOrientation(LinearLayout.HORIZONTAL);
192 | linearLayout.setGravity(Gravity.CENTER);
193 | TextView textView = new TextView(mActivity);
194 | textView.setText("右边");
195 | textView.setOnClickListener(new View.OnClickListener() {
196 | @Override
197 | public void onClick(View v) {
198 | Toast.makeText(mActivity, "右边的菜单被点击了", Toast.LENGTH_SHORT).show();
199 | }
200 | });
201 |
202 | ImageView imageView = new ImageView(mActivity);
203 | imageView.setLayoutParams(new LinearLayout.LayoutParams(dip2px(50), dip2px(50)));
204 | imageView.setImageResource(R.drawable.yw_game_logo);
205 | imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
206 | imageView.setOnTouchListener(touchListener);
207 |
208 | linearLayout.setLayoutParams(new LinearLayout.LayoutParams(dip2px(50), dip2px(50)));
209 | linearLayout.setBackgroundResource(R.drawable.yw_game_float_menu_bg);
210 | linearLayout.addView(textView);
211 | linearLayout.addView(imageView);
212 | return linearLayout;
213 | }
214 |
215 | @Override
216 | public View getLogoView() {
217 |
218 | LinearLayout linearLayout = new LinearLayout(mActivity);
219 | linearLayout.setOrientation(LinearLayout.HORIZONTAL);
220 | linearLayout.setGravity(Gravity.CENTER);
221 |
222 | ImageView imageView = new ImageView(mActivity);
223 | imageView.setLayoutParams(new LinearLayout.LayoutParams(dip2px(50), dip2px(50)));
224 | imageView.setScaleType(ImageView.ScaleType.CENTER);
225 | imageView.setImageResource(R.drawable.yw_game_logo);
226 |
227 | linearLayout.setLayoutParams(new LinearLayout.LayoutParams(dip2px(50), dip2px(50)));
228 | linearLayout.setBackgroundResource(R.drawable.yw_game_float_menu_bg);
229 |
230 | linearLayout.addView(imageView);
231 | return linearLayout;
232 | }
233 |
234 | @Override
235 | public void resetLogoViewSize(int hintLocation, View logoView) {
236 |
237 | logoView.setTranslationX(0);
238 | logoView.setScaleX(1);
239 | logoView.setScaleY(1);
240 | }
241 |
242 | @Override
243 | public void dragingLogoViewOffset(View smallView, boolean isDraging, boolean isResetPosition, float offset) {
244 | if (isDraging && offset > 0) {
245 | smallView.setBackgroundDrawable(null);
246 | smallView.setScaleX(1 + offset);
247 | smallView.setScaleY(1 + offset);
248 | } else {
249 | smallView.setBackgroundResource(R.drawable.yw_game_float_menu_bg);
250 | smallView.setTranslationX(0);
251 | smallView.setScaleX(1);
252 | smallView.setScaleY(1);
253 | }
254 | }
255 |
256 | @Override
257 | public void shrinkLeftLogoView(View smallView) {
258 | smallView.setTranslationX(-smallView.getWidth() / 3);
259 | }
260 |
261 | @Override
262 | public void shrinkRightLogoView(View smallView) {
263 | smallView.setTranslationX(smallView.getWidth() / 3);
264 | }
265 |
266 | @Override
267 | public void leftViewOpened(View leftView) {
268 | Toast.makeText(mActivity, "左边的菜单被打开了", Toast.LENGTH_SHORT).show();
269 | }
270 |
271 | @Override
272 | public void rightViewOpened(View rightView) {
273 | Toast.makeText(mActivity, "右边的菜单被打开了", Toast.LENGTH_SHORT).show();
274 | }
275 |
276 | @Override
277 | public void leftOrRightViewClosed(View smallView) {
278 | Toast.makeText(mActivity, "菜单被关闭了", Toast.LENGTH_SHORT).show();
279 | }
280 |
281 | @Override
282 | public void onDestoryed() {
283 |
284 | }
285 | });
286 | floatManager.show();
287 |
288 |
289 |
290 |
291 |
292 | ## 更新日志
293 | UPDATE LOG:
294 |
295 | * 1.+ 桌面或应用内悬浮窗 (removed)
296 |
297 | compile 'com.yw.game.floatmenu:FloatMenu:1.0.0'
298 |
299 |
300 | * 2.0.0 重构版,应用内悬浮窗
301 |
302 | compile 'com.yw.game.floatmenu:FloatMenu:2.0.0'
303 |
304 | //2.0.0版使用windows.addContentView接口,在unity3D游戏引擎需要开启该选项
305 |
306 |
307 |
308 | * 2.0.1 可选择支持出现在桌面(需权限),应用内无权限需要。
309 |
310 | 移除windows.addContentView接口
311 | 出现在桌面的话需要:
312 | 使用全局上下文 .withContext(mService.getApplication())
313 | 并配合权限
314 |
315 |
316 |
317 |
318 | ## License
319 |
320 |
321 | Copyright (c) 2016, Shanghai YUEWEN Information Technology Co., Ltd.
322 | All rights reserved.
323 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
324 |
325 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
326 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
327 | * Neither the name of Shanghai YUEWEN Information Technology Co., Ltd. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
328 |
329 | THIS SOFTWARE IS PROVIDED BY SHANGHAI YUEWEN INFORMATION TECHNOLOGY CO., LTD. AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
330 |
331 |
332 |
333 |
334 |
--------------------------------------------------------------------------------
/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.3.3'
9 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3'
10 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'
11 | // NOTE: Do not place your application dependencies here; they belong
12 | // in the individual module build.gradle files
13 | }
14 | }
15 |
16 | allprojects {
17 | repositories {
18 | jcenter()
19 | maven {
20 | url 'https://dl.bintray.com/fanofdemo/maven/'
21 | }
22 | }
23 | }
24 |
25 | tasks.getByPath(":FloatMenu:javadoc").enabled = false
26 | task clean(type: Delete) {
27 | delete rootProject.buildDir
28 | }
29 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | systemProp.http.proxyHost=127.0.0.1
2 | org.gradle.parallel=true
3 | systemProp.http.proxyPort=1080
4 |
5 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatMenuSample/9eda722e950ba882709810525b9bdc2e585f3591/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Jul 28 10:16:15 CST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
7 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/picture/20160503125603.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatMenuSample/9eda722e950ba882709810525b9bdc2e585f3591/picture/20160503125603.png
--------------------------------------------------------------------------------
/picture/201605031543.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatMenuSample/9eda722e950ba882709810525b9bdc2e585f3591/picture/201605031543.gif
--------------------------------------------------------------------------------
/picture/201605041543.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatMenuSample/9eda722e950ba882709810525b9bdc2e585f3591/picture/201605041543.gif
--------------------------------------------------------------------------------
/picture/201606161036.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatMenuSample/9eda722e950ba882709810525b9bdc2e585f3591/picture/201606161036.gif
--------------------------------------------------------------------------------
/picture/2016112315.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatMenuSample/9eda722e950ba882709810525b9bdc2e585f3591/picture/2016112315.gif
--------------------------------------------------------------------------------
/picture/chinareadlogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatMenuSample/9eda722e950ba882709810525b9bdc2e585f3591/picture/chinareadlogo.png
--------------------------------------------------------------------------------
/picture/floatmen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatMenuSample/9eda722e950ba882709810525b9bdc2e585f3591/picture/floatmen.png
--------------------------------------------------------------------------------
/picture/floatmenu2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fanOfDemo/FloatMenuSample/9eda722e950ba882709810525b9bdc2e585f3591/picture/floatmenu2.gif
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':FloatMenuDemo'
2 | include ':FloatMenu'
--------------------------------------------------------------------------------