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