├── .gitignore
├── LICENSE
├── README.md
├── UpdateDoc.md
├── build.gradle
├── easyfloat
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── lzf
│ │ └── easyfloat
│ │ └── ExampleInstrumentedTest.java
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── lzf
│ │ └── easyfloat
│ │ ├── EasyFloat.kt
│ │ ├── EasyFloatInitializer.kt
│ │ ├── EasyFloatMessage.kt
│ │ ├── anim
│ │ ├── AnimatorManager.kt
│ │ └── DefaultAnimator.kt
│ │ ├── core
│ │ ├── FloatingWindowHelper.kt
│ │ ├── FloatingWindowManager.kt
│ │ └── TouchUtils.kt
│ │ ├── data
│ │ └── FloatConfig.kt
│ │ ├── enums
│ │ ├── ShowPattern.kt
│ │ └── SidePattern.kt
│ │ ├── interfaces
│ │ ├── FloatCallbacks.kt
│ │ ├── OnDisplayHeight.java
│ │ ├── OnFloatAnimator.kt
│ │ ├── OnFloatCallbacks.kt
│ │ ├── OnFloatTouchListener.kt
│ │ ├── OnInvokeView.java
│ │ ├── OnPermissionResult.kt
│ │ └── OnTouchRangeListener.kt
│ │ ├── permission
│ │ ├── PermissionFragment.kt
│ │ ├── PermissionUtils.kt
│ │ └── rom
│ │ │ ├── HuaweiUtils.java
│ │ │ ├── MeizuUtils.java
│ │ │ ├── MiuiUtils.java
│ │ │ ├── OppoUtils.java
│ │ │ ├── QikuUtils.java
│ │ │ └── RomUtils.kt
│ │ ├── utils
│ │ ├── DefaultDisplayHeight.kt
│ │ ├── DisplayUtils.kt
│ │ ├── DragUtils.kt
│ │ ├── InputMethodUtils.kt
│ │ ├── LifecycleUtils.kt
│ │ └── Logger.kt
│ │ └── widget
│ │ ├── BaseSwitchView.kt
│ │ ├── DefaultAddView.kt
│ │ ├── DefaultCloseView.kt
│ │ └── ParentFrameLayout.kt
│ └── res
│ ├── drawable-xxxhdpi
│ ├── icon_delete_normal.png
│ └── icon_delete_selected.png
│ ├── drawable
│ ├── add_normal.xml
│ ├── add_selected.xml
│ ├── icon_delete_normal.png
│ └── icon_delete_selected.png
│ ├── layout
│ ├── default_add_layout.xml
│ └── default_close_layout.xml
│ └── values
│ ├── attrs.xml
│ └── strings.xml
├── example
├── .gitignore
├── build.gradle
├── proguard-rules.pro
├── release
│ ├── EasyFloat.apk
│ ├── easyfloat.keystore
│ ├── output-metadata.json
│ └── output.json
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── lzf
│ │ └── example
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── lzf
│ │ │ └── easyfloat
│ │ │ └── example
│ │ │ ├── App.kt
│ │ │ ├── MyAdapter.kt
│ │ │ ├── Reified.kt
│ │ │ ├── activity
│ │ │ ├── BaseActivity.kt
│ │ │ ├── BorderTestActivity.kt
│ │ │ ├── JavaTestActivity.java
│ │ │ ├── MainActivity.kt
│ │ │ ├── SecondActivity.kt
│ │ │ └── SwipeTestActivity.kt
│ │ │ ├── logger.kt
│ │ │ └── widget
│ │ │ ├── BubbleSurfaceView.kt
│ │ │ ├── CircleLoadingView.kt
│ │ │ ├── MyCustomView.kt
│ │ │ ├── RoundProgressBar.kt
│ │ │ └── ScaleImage.kt
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ ├── corners_green.xml
│ │ ├── corners_left.xml
│ │ ├── corners_red.xml
│ │ ├── corners_right.xml
│ │ ├── corners_right_green.xml
│ │ ├── ic_launcher_background.xml
│ │ ├── icon_scale.png
│ │ └── icon_x.png
│ │ ├── layout
│ │ ├── activity_border_test.xml
│ │ ├── activity_java.xml
│ │ ├── activity_main.xml
│ │ ├── activity_second.xml
│ │ ├── activity_swipe_test.xml
│ │ ├── activity_third.xml
│ │ ├── float_app.xml
│ │ ├── float_app_scale.xml
│ │ ├── float_border_test.xml
│ │ ├── float_contract.xml
│ │ ├── float_custom.xml
│ │ ├── float_edit.xml
│ │ ├── float_seekbar.xml
│ │ ├── float_swipe.xml
│ │ ├── float_top_dialog.xml
│ │ ├── item_simple_list.xml
│ │ ├── popup_test.xml
│ │ └── test_float.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ └── values
│ │ ├── attrs.xml
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── lzf
│ └── example
│ └── ExampleUnitTest.kt
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── readme
├── Alipay.jpeg
├── BoarderAndSlide.gif
├── Callbacks.gif
├── DragUtils.gif
├── EasyFloatGroup.jpeg
├── README_1.3.4.md
├── WeChatPay.jpeg
├── downloadImage.png
├── 权限申请.gif
├── 浮窗缩放.gif
└── 系统浮窗.gif
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea
5 | .DS_Store
6 | /build
7 | /captures
8 | .externalNativeBuild
9 |
--------------------------------------------------------------------------------
/UpdateDoc.md:
--------------------------------------------------------------------------------
1 | ## 版本更新日志
2 | #### v 2.0.4:
3 | - [修复延迟初始化单页面浮窗导致的一系列问题](https://github.com/princekin-f/EasyFloat/issues/173);
4 | - [修复悬浮窗设置动画为空报错的问题](https://github.com/princekin-f/EasyFloat/issues/185);
5 | - [修复aar打包混淆配置未生效的问题](https://github.com/princekin-f/EasyFloat/issues/164);
6 | - [`DefaultCloseView`属性替换:`shapeType`->`closeShapeType`](https://github.com/princekin-f/EasyFloat/issues/132);
7 | - [API扩展,支持更新浮窗大小`updateFloat`](https://github.com/princekin-f/EasyFloat/blob/40fb05f12e8fdc99af096a9cb50a72dfc3707aa8/easyfloat/src/main/java/com/lzf/easyfloat/EasyFloat.kt#L102)。
8 |
9 |
10 | #### v 2.0.3:
11 | - [支持直接设置View(`xml/View`即可使用)](https://github.com/princekin-f/EasyFloat/issues/110);
12 | - [支持设置布局大小变化后,整体View的位置对齐方式](https://github.com/princekin-f/EasyFloat/pull/159);
13 | - [修复`onCreate`无法创建单页面浮窗的问题](https://github.com/princekin-f/EasyFloat/issues/132);
14 | - [修复在别的应用,横屏宽度异常问题](https://github.com/princekin-f/EasyFloat/issues/135);
15 | - [修复浮窗创建失败,无法创建相同TAG的问题](https://github.com/princekin-f/EasyFloat/issues/138)。
16 |
17 | #### v 2.0.2:
18 | - 解决Java关键字导致的包名冲突。
19 |
20 | #### v 2.0.0:
21 | - 新增拖拽关闭、侧滑创建、状态栏沉浸、拖拽边界等功能;
22 | - 重构单页面浮窗(创建和PopupWindow同类型的子窗口),减少了API数目,提高利用率;
23 | - 优化使用体验,无需手动init,无需手动调起软键盘。
24 |
25 | #### v 1.3.4:
26 | - 优化细节。
27 |
28 | #### v 1.3.3:
29 | - 支持传入`with(context)`实现部分系统浮窗功能;
30 | - 对未合理`init`、未合理`with(context)`的情况,直接进行抛异常;
31 | - 其他一些细节性的优化。
32 |
33 | #### v 1.3.2:
34 | - 优化细节。
35 |
36 | #### v 1.3.1:
37 | - 优化`createdResult`回调。
38 |
39 | #### v 1.3.0:
40 | - 可动态适配虚拟导航栏:`setDisplayHeight(displayHeight: OnDisplayHeight)`
41 |
42 | #### v 1.2.9:
43 | - [优化细节。](https://github.com/princekin-f/EasyFloat/issues/57)
44 |
45 | #### v 1.2.8:
46 | - 优化细节。
47 |
48 | #### v 1.2.7:
49 | - 优化浮窗闪烁,横屏不能拖拽到底等问题。
50 |
51 | #### v 1.2.6:
52 | - 优化细节。
53 |
54 | #### v 1.2.5:
55 | - 优化页面过滤。
56 |
57 | #### v 1.2.4:
58 | - 优化全面屏适配。
59 |
60 | #### v 1.2.3:
61 | - 新增仅后台显示(`ShowPattern.BACKGROUND`)。
62 |
63 | #### v 1.2.2:
64 | - 优化细节。
65 |
66 | #### v 1.2.1:
67 | - 适配小米全面屏。
68 |
69 | #### v 1.2.0:
70 | - 优化系统浮窗的管理方式。
71 |
72 | #### v 1.1.3:
73 | - 优化部分全面屏手机,系统浮窗不能拖到底部的问题。
74 |
75 | #### v 1.1.2:
76 | - 优化页面过滤和主动隐藏的逻辑。
77 |
78 | #### v 1.1.1:
79 | - 当系统浮窗包含`EditText`时,优化返回键的相关处理。
80 |
81 | #### v 1.1.0:
82 | - 开放浮窗权限申请的API;
83 | - 更名浮窗回调方法名,不再使用方法重载的方式。
84 |
85 | #### v 1.0.7:
86 | - 优化页面过滤细节上的不足。
87 |
88 | #### v 1.0.6:
89 | - 新增浮窗`View`的获取,方便`View`的修改。
90 |
91 | #### v 1.0.5:
92 | - 优化代码和功能,支持`FloatCallbacks`的按需调用(Kotlin DSL)。
93 |
94 | #### v 1.0.4:
95 | - 可选择是否开启前台Service,可自定义通知栏消息。
96 |
97 | #### v 1.0.3:
98 | - 修改魅族手机,权限申请回调异常的问题;
99 | - 为系统浮窗的`EditText`,提供了软键盘的打开、关闭后的焦点移除;
100 | - 但暂未提供软键盘的关闭监听方案,希望大家一起努力。
101 |
102 | #### v 1.0.2:
103 | - 修改`enum`包名,解决Java特殊路径无法调用的问题;
104 | - 添加`@JvmOverloads`注解,支持对Java的方法重载。
105 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | ext.kotlin_version = '1.5.31'
5 | repositories {
6 | google()
7 | jcenter()
8 | maven { url "https://jitpack.io" }
9 | }
10 | dependencies {
11 | classpath 'com.android.tools.build:gradle:4.1.3'
12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
13 | classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
14 | // NOTE: Do not place your application dependencies here; they belong
15 | // in the individual module build.gradle files
16 | }
17 | }
18 |
19 | allprojects {
20 | repositories {
21 | google()
22 | jcenter()
23 | maven { url "https://jitpack.io" }
24 | }
25 | }
26 |
27 | task clean(type: Delete) {
28 | delete rootProject.buildDir
29 | }
30 |
--------------------------------------------------------------------------------
/easyfloat/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/easyfloat/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android-extensions'
3 | apply plugin: 'kotlin-android'
4 | apply plugin: 'com.github.dcendents.android-maven'
5 | group = 'com.github.princekin-f'
6 |
7 | android {
8 | compileSdkVersion 29
9 |
10 | defaultConfig {
11 | minSdkVersion 17
12 | targetSdkVersion 29
13 | versionCode 1
14 | versionName "1.0"
15 |
16 | // aar 打包混淆配置
17 | consumerProguardFiles "proguard-rules.pro"
18 | }
19 |
20 | buildTypes {
21 | release {
22 | minifyEnabled false
23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
24 | }
25 | }
26 |
27 | compileOptions {
28 | sourceCompatibility JavaVersion.VERSION_1_8
29 | targetCompatibility JavaVersion.VERSION_1_8
30 | }
31 |
32 | }
33 |
34 | dependencies {
35 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
36 | }
37 | repositories {
38 | mavenCentral()
39 | }
40 |
--------------------------------------------------------------------------------
/easyfloat/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the filterSet of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
23 | # 保持配置类 config 不被混淆
24 | -keep class com.lzf.easyfloat.data.FloatConfig {*;}
25 |
26 | # 保持自定义控件、ContentProvider 不被混淆
27 | -keep public class * extends android.view.View
28 | -keep public class * extends android.content.ContentProvider
29 |
30 | # 保持枚举 enum 类不被混淆
31 | -keepclassmembers enum * {
32 | public static **[] values();
33 | public static ** valueOf(java.lang.String);
34 | }
35 |
36 | # 保持反射不被混淆
37 | -keepattributes EnclosingMethod
--------------------------------------------------------------------------------
/easyfloat/src/androidTest/java/com/lzf/easyfloat/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat;
2 |
3 | import android.content.Context;
4 | import androidx.test.InstrumentationRegistry;
5 | import androidx.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.lzf.easyfloat.test", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/easyfloat/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/easyfloat/src/main/java/com/lzf/easyfloat/EasyFloatInitializer.kt:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat
2 |
3 | import android.app.Application
4 | import android.content.ContentProvider
5 | import android.content.ContentValues
6 | import android.database.Cursor
7 | import android.net.Uri
8 | import com.lzf.easyfloat.utils.LifecycleUtils
9 |
10 | /**
11 | * @author: liuzhenfeng
12 | * @github:https://github.com/princekin-f
13 | * @function: 通过内容提供者的上下文,进行生命周期回调的初始化
14 | * @date: 2020/10/23 13:41
15 | */
16 | class EasyFloatInitializer : ContentProvider() {
17 |
18 | override fun onCreate(): Boolean {
19 | LifecycleUtils.setLifecycleCallbacks(context!!.applicationContext as Application)
20 | return true
21 | }
22 |
23 | override fun query(
24 | uri: Uri,
25 | projection: Array?,
26 | selection: String?,
27 | selectionArgs: Array?,
28 | sortOrder: String?
29 | ): Cursor? = null
30 |
31 | override fun getType(uri: Uri): String? = null
32 |
33 | override fun insert(uri: Uri, values: ContentValues?): Uri? = null
34 |
35 | override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Int = 0
36 |
37 | override fun update(
38 | uri: Uri,
39 | values: ContentValues?,
40 | selection: String?,
41 | selectionArgs: Array?
42 | ): Int = 0
43 | }
--------------------------------------------------------------------------------
/easyfloat/src/main/java/com/lzf/easyfloat/EasyFloatMessage.kt:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat
2 |
3 | /**
4 | * @author: liuzhenfeng
5 | * @github:https://github.com/princekin-f
6 | * @function: 创建失败提示信息
7 | * @date: 2020/4/10 14:25
8 | */
9 | const val WARN_PERMISSION = "No permission exception. You need to turn on overlay permissions."
10 | const val WARN_NO_LAYOUT = "No layout exception. You need to set up the layout file."
11 | const val WARN_UNINITIALIZED = "Uninitialized exception. You need to initialize in the application."
12 | const val WARN_REPEATED_TAG = "Tag exception. You need to set different EasyFloat tag."
13 | const val WARN_CONTEXT_ACTIVITY = "Context exception. Activity float need to pass in a activity context."
14 | const val WARN_CONTEXT_REQUEST = "Context exception. Request Permission need to pass in a activity context."
15 | const val WARN_ACTIVITY_NULL = "Activity is null."
--------------------------------------------------------------------------------
/easyfloat/src/main/java/com/lzf/easyfloat/anim/AnimatorManager.kt:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat.anim
2 |
3 | import android.animation.Animator
4 | import android.view.View
5 | import android.view.WindowManager
6 | import com.lzf.easyfloat.data.FloatConfig
7 |
8 | /**
9 | * @author: liuzhenfeng
10 | * @function: App浮窗的出入动画管理类,只需传入具体的动画实现类(策略模式)
11 | * @date: 2019-07-22 16:44
12 | */
13 | internal class AnimatorManager(
14 | private val view: View,
15 | private val params: WindowManager.LayoutParams,
16 | private val windowManager: WindowManager,
17 | private val config: FloatConfig
18 | ) {
19 |
20 | fun enterAnim(): Animator? =
21 | config.floatAnimator?.enterAnim(view, params, windowManager, config.sidePattern)
22 |
23 | fun exitAnim(): Animator? =
24 | config.floatAnimator?.exitAnim(view, params, windowManager, config.sidePattern)
25 | }
--------------------------------------------------------------------------------
/easyfloat/src/main/java/com/lzf/easyfloat/anim/DefaultAnimator.kt:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat.anim
2 |
3 | import android.animation.Animator
4 | import android.animation.ValueAnimator
5 | import android.graphics.Rect
6 | import android.view.View
7 | import android.view.WindowManager
8 | import com.lzf.easyfloat.enums.SidePattern
9 | import com.lzf.easyfloat.interfaces.OnFloatAnimator
10 | import com.lzf.easyfloat.utils.DisplayUtils
11 | import kotlin.math.min
12 |
13 | /**
14 | * @author: liuzhenfeng
15 | * @function: 系统浮窗的默认效果,选择靠近左右侧的一边进行出入
16 | * @date: 2019-07-22 17:22
17 | */
18 | open class DefaultAnimator : OnFloatAnimator {
19 |
20 | override fun enterAnim(
21 | view: View,
22 | params: WindowManager.LayoutParams,
23 | windowManager: WindowManager,
24 | sidePattern: SidePattern
25 | ): Animator? = getAnimator(view, params, windowManager, sidePattern, false)
26 |
27 | override fun exitAnim(
28 | view: View,
29 | params: WindowManager.LayoutParams,
30 | windowManager: WindowManager,
31 | sidePattern: SidePattern
32 | ): Animator? = getAnimator(view, params, windowManager, sidePattern, true)
33 |
34 | private fun getAnimator(
35 | view: View,
36 | params: WindowManager.LayoutParams,
37 | windowManager: WindowManager,
38 | sidePattern: SidePattern,
39 | isExit: Boolean
40 | ): Animator {
41 | val triple = initValue(view, params, windowManager, sidePattern)
42 | // 退出动画的起始值、终点值,与入场动画相反
43 | val start = if (isExit) triple.second else triple.first
44 | val end = if (isExit) triple.first else triple.second
45 | return ValueAnimator.ofInt(start, end).apply {
46 | addUpdateListener {
47 | try {
48 | val value = it.animatedValue as Int
49 | if (triple.third) params.x = value else params.y = value
50 | // 动画执行过程中页面关闭,出现异常
51 | windowManager.updateViewLayout(view, params)
52 | } catch (e: Exception) {
53 | cancel()
54 | }
55 | }
56 | }
57 | }
58 |
59 | /**
60 | * 计算边距,起始坐标等
61 | */
62 | private fun initValue(
63 | view: View,
64 | params: WindowManager.LayoutParams,
65 | windowManager: WindowManager,
66 | sidePattern: SidePattern
67 | ): Triple {
68 | val parentRect = Rect()
69 | windowManager.defaultDisplay.getRectSize(parentRect)
70 | // 浮窗各边到窗口边框的距离
71 | val leftDistance = params.x
72 | val rightDistance = parentRect.right - (leftDistance + view.right)
73 | val topDistance = params.y
74 | val bottomDistance = parentRect.bottom - (topDistance + view.bottom)
75 | // 水平、垂直方向的距离最小值
76 | val minX = min(leftDistance, rightDistance)
77 | val minY = min(topDistance, bottomDistance)
78 |
79 | val isHorizontal: Boolean
80 | val endValue: Int
81 | val startValue: Int = when (sidePattern) {
82 | SidePattern.LEFT, SidePattern.RESULT_LEFT -> {
83 | // 从左侧到目标位置,右移
84 | isHorizontal = true
85 | endValue = params.x
86 | -view.right
87 | }
88 | SidePattern.RIGHT, SidePattern.RESULT_RIGHT -> {
89 | // 从右侧到目标位置,左移
90 | isHorizontal = true
91 | endValue = params.x
92 | parentRect.right
93 | }
94 | SidePattern.TOP, SidePattern.RESULT_TOP -> {
95 | // 从顶部到目标位置,下移
96 | isHorizontal = false
97 | endValue = params.y
98 | -view.bottom
99 | }
100 | SidePattern.BOTTOM, SidePattern.RESULT_BOTTOM -> {
101 | // 从底部到目标位置,上移
102 | isHorizontal = false
103 | endValue = params.y
104 | parentRect.bottom + getCompensationHeight(view, params)
105 | }
106 |
107 | SidePattern.DEFAULT, SidePattern.AUTO_HORIZONTAL, SidePattern.RESULT_HORIZONTAL -> {
108 | // 水平位移,哪边距离屏幕近,从哪侧移动
109 | isHorizontal = true
110 | endValue = params.x
111 | if (leftDistance < rightDistance) -view.right else parentRect.right
112 | }
113 | SidePattern.AUTO_VERTICAL, SidePattern.RESULT_VERTICAL -> {
114 | // 垂直位移,哪边距离屏幕近,从哪侧移动
115 | isHorizontal = false
116 | endValue = params.y
117 | if (topDistance < bottomDistance) -view.bottom
118 | else parentRect.bottom + getCompensationHeight(view, params)
119 | }
120 |
121 | else -> if (minX <= minY) {
122 | isHorizontal = true
123 | endValue = params.x
124 | if (leftDistance < rightDistance) -view.right else parentRect.right
125 | } else {
126 | isHorizontal = false
127 | endValue = params.y
128 | if (topDistance < bottomDistance) -view.bottom
129 | else parentRect.bottom + getCompensationHeight(view, params)
130 | }
131 | }
132 | return Triple(startValue, endValue, isHorizontal)
133 | }
134 |
135 | /**
136 | * 单页面浮窗(popupWindow),坐标从顶部计算,需要加上状态栏的高度
137 | */
138 | private fun getCompensationHeight(view: View, params: WindowManager.LayoutParams): Int {
139 | val location = IntArray(2)
140 | // 获取在整个屏幕内的绝对坐标
141 | view.getLocationOnScreen(location)
142 | // 绝对高度和相对高度相等,说明是单页面浮窗(popupWindow),计算底部动画时需要加上状态栏高度
143 | return if (location[1] == params.y) DisplayUtils.statusBarHeight(view) else 0
144 | }
145 |
146 | }
--------------------------------------------------------------------------------
/easyfloat/src/main/java/com/lzf/easyfloat/core/FloatingWindowManager.kt:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat.core
2 |
3 | import android.content.Context
4 | import android.view.View
5 | import com.lzf.easyfloat.WARN_REPEATED_TAG
6 | import com.lzf.easyfloat.data.FloatConfig
7 | import com.lzf.easyfloat.utils.Logger
8 | import java.util.concurrent.ConcurrentHashMap
9 |
10 | /**
11 | * @author: Liuzhenfeng
12 | * @date: 12/1/20 23:36
13 | * @Description: 负责多个悬浮窗的管理
14 | */
15 | internal object FloatingWindowManager {
16 |
17 | private const val DEFAULT_TAG = "default"
18 | val windowMap = ConcurrentHashMap()
19 |
20 | /**
21 | * 创建浮窗,tag不存在创建,tag存在创建失败
22 | * 创建结果通过tag添加到相应的map进行管理
23 | */
24 | fun create(context: Context, config: FloatConfig) {
25 | if (!checkTag(config)) {
26 | val helper = FloatingWindowHelper(context, config)
27 | helper.createWindow(object : FloatingWindowHelper.CreateCallback {
28 | override fun onCreate(success: Boolean) {
29 | if (success) windowMap[config.floatTag!!] = helper
30 | }
31 | })
32 | } else {
33 | // 存在相同的tag,直接创建失败
34 | config.callbacks?.createdResult(false, WARN_REPEATED_TAG, null)
35 | config.floatCallbacks?.builder?.createdResult?.invoke(false, WARN_REPEATED_TAG, null)
36 | Logger.w(WARN_REPEATED_TAG)
37 | }
38 | }
39 |
40 | /**
41 | * 关闭浮窗,执行浮窗的退出动画
42 | */
43 | fun dismiss(tag: String? = null, force: Boolean = false) =
44 | getHelper(tag)?.run { if (force) remove(force) else exitAnim() }
45 |
46 | /**
47 | * 移除当条浮窗信息,在退出完成后调用
48 | */
49 | fun remove(floatTag: String?) = windowMap.remove(getTag(floatTag))
50 |
51 | /**
52 | * 设置浮窗的显隐,用户主动调用隐藏时,needShow需要为false
53 | */
54 | fun visible(
55 | isShow: Boolean,
56 | tag: String? = null,
57 | needShow: Boolean = windowMap[tag]?.config?.needShow ?: true
58 | ) = getHelper(tag)?.setVisible(if (isShow) View.VISIBLE else View.GONE, needShow)
59 |
60 | /**
61 | * 检测浮窗的tag是否有效,不同的浮窗必须设置不同的tag
62 | */
63 | private fun checkTag(config: FloatConfig): Boolean {
64 | // 如果未设置tag,设置默认tag
65 | config.floatTag = getTag(config.floatTag)
66 | return windowMap.containsKey(config.floatTag!!)
67 | }
68 |
69 | /**
70 | * 获取浮窗tag,为空则使用默认值
71 | */
72 | private fun getTag(tag: String?) = tag ?: DEFAULT_TAG
73 |
74 | /**
75 | * 获取具体的系统浮窗管理类
76 | */
77 | fun getHelper(tag: String?) = windowMap[getTag(tag)]
78 |
79 | }
--------------------------------------------------------------------------------
/easyfloat/src/main/java/com/lzf/easyfloat/data/FloatConfig.kt:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat.data
2 |
3 | import android.view.Gravity
4 | import android.view.View
5 | import com.lzf.easyfloat.anim.DefaultAnimator
6 | import com.lzf.easyfloat.enums.ShowPattern
7 | import com.lzf.easyfloat.enums.SidePattern
8 | import com.lzf.easyfloat.interfaces.*
9 | import com.lzf.easyfloat.utils.DefaultDisplayHeight
10 |
11 | /**
12 | * @author: liuzhenfeng
13 | * @function: 浮窗的数据类,方便管理各属性
14 | * @date: 2019-07-29 10:14
15 | */
16 | data class FloatConfig(
17 |
18 | // 浮窗的xml布局文件
19 | var layoutId: Int? = null,
20 | var layoutView: View? = null,
21 |
22 | // 当前浮窗的tag
23 | var floatTag: String? = null,
24 |
25 | // 是否可拖拽
26 | var dragEnable: Boolean = true,
27 | // 是否正在被拖拽
28 | var isDrag: Boolean = false,
29 | // 是否正在执行动画
30 | var isAnim: Boolean = false,
31 | // 是否显示
32 | var isShow: Boolean = false,
33 | // 是否包含EditText
34 | var hasEditText: Boolean = false,
35 | // 状态栏沉浸
36 | var immersionStatusBar: Boolean = false,
37 |
38 | // 浮窗的吸附方式(默认不吸附,拖到哪里是哪里)
39 | var sidePattern: SidePattern = SidePattern.DEFAULT,
40 |
41 | // 浮窗显示类型(默认只在当前页显示)
42 | var showPattern: ShowPattern = ShowPattern.CURRENT_ACTIVITY,
43 |
44 | // 宽高是否充满父布局
45 | var widthMatch: Boolean = false,
46 | var heightMatch: Boolean = false,
47 |
48 | // 浮窗的摆放方式,使用系统的Gravity属性
49 | var gravity: Int = 0,
50 | // 坐标的偏移量
51 | var offsetPair: Pair = Pair(0, 0),
52 | // 固定的初始坐标,左上角坐标
53 | var locationPair: Pair = Pair(0, 0),
54 | // ps:优先使用固定坐标,若固定坐标不为原点坐标,gravity属性和offset属性无效
55 |
56 | // 四周边界值
57 | var leftBorder: Int = 0,
58 | var topBorder: Int = -999,
59 | var rightBorder: Int = 9999,
60 | var bottomBorder: Int = 9999,
61 |
62 | // Callbacks
63 | var invokeView: OnInvokeView? = null,
64 | var callbacks: OnFloatCallbacks? = null,
65 | // 通过Kotlin DSL设置回调,无需复写全部方法,按需复写
66 | var floatCallbacks: FloatCallbacks? = null,
67 |
68 | // 出入动画
69 | var floatAnimator: OnFloatAnimator? = DefaultAnimator(),
70 |
71 | // 设置屏幕的有效显示高度(不包含虚拟导航栏的高度),仅针对系统浮窗,一般不用复写
72 | var displayHeight: OnDisplayHeight = DefaultDisplayHeight(),
73 |
74 | // 不需要显示系统浮窗的页面集合,参数为类名
75 | val filterSet: MutableSet = mutableSetOf(),
76 | // 是否设置,当前创建的页面也被过滤
77 | internal var filterSelf: Boolean = false,
78 | // 是否需要显示,当过滤信息匹配上时,该值为false(用户手动调用隐藏,该值也为false,相当于手动过滤)
79 | internal var needShow: Boolean = true,
80 |
81 | // 当layout大小变化后,整体view的位置的摆放
82 | var layoutChangedGravity: Int = Gravity.TOP.or(Gravity.START)
83 | )
--------------------------------------------------------------------------------
/easyfloat/src/main/java/com/lzf/easyfloat/enums/ShowPattern.kt:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat.enums
2 |
3 | /**
4 | * @author: liuzhenfeng
5 | * @function: 浮窗显示类别
6 | * @date: 2019-07-08 17:05
7 | */
8 | enum class ShowPattern {
9 |
10 | // 只在当前Activity显示、仅应用前台时显示、仅应用后台时显示,一直显示(不分前后台)
11 | CURRENT_ACTIVITY, FOREGROUND, BACKGROUND, ALL_TIME
12 | }
--------------------------------------------------------------------------------
/easyfloat/src/main/java/com/lzf/easyfloat/enums/SidePattern.kt:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat.enums
2 |
3 | /**
4 | * @author: liuzhenfeng
5 | * @function: 浮窗的贴边模式
6 | * @date: 2019-07-01 17:34
7 | */
8 | enum class SidePattern {
9 |
10 | // 默认不贴边,跟随手指移动
11 | DEFAULT,
12 | // 左、右、上、下四个方向固定(一直吸附在该方向边缘,只能在该方向的边缘移动)
13 | LEFT, RIGHT, TOP, BOTTOM,
14 | // 根据手指到屏幕边缘的距离,自动选择水平方向的贴边、垂直方向的贴边、四周方向的贴边
15 | AUTO_HORIZONTAL, AUTO_VERTICAL, AUTO_SIDE,
16 | // 拖拽时跟随手指移动,结束时贴边
17 | RESULT_LEFT, RESULT_RIGHT, RESULT_TOP, RESULT_BOTTOM,
18 | RESULT_HORIZONTAL, RESULT_VERTICAL, RESULT_SIDE
19 |
20 | }
--------------------------------------------------------------------------------
/easyfloat/src/main/java/com/lzf/easyfloat/interfaces/FloatCallbacks.kt:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat.interfaces
2 |
3 | import android.view.MotionEvent
4 | import android.view.View
5 |
6 | /**
7 | * @author: liuzhenfeng
8 | * @function: 通过Kotlin DSL实现接口回调效果
9 | * @date: 2019-08-26 17:06
10 | */
11 | class FloatCallbacks {
12 |
13 | lateinit var builder: Builder
14 |
15 | // 带Builder返回值的lambda
16 | fun registerListener(builder: Builder.() -> Unit) {
17 | this.builder = Builder().also(builder)
18 | }
19 |
20 | inner class Builder {
21 | internal var createdResult: ((Boolean, String?, View?) -> Unit)? = null
22 | internal var show: ((View) -> Unit)? = null
23 | internal var hide: ((View) -> Unit)? = null
24 | internal var dismiss: (() -> Unit)? = null
25 | internal var touchEvent: ((View, MotionEvent) -> Unit)? = null
26 | internal var drag: ((View, MotionEvent) -> Unit)? = null
27 | internal var dragEnd: ((View) -> Unit)? = null
28 |
29 | fun createResult(action: (Boolean, String?, View?) -> Unit) {
30 | createdResult = action
31 | }
32 |
33 | fun show(action: (View) -> Unit) {
34 | show = action
35 | }
36 |
37 | fun hide(action: (View) -> Unit) {
38 | hide = action
39 | }
40 |
41 | fun dismiss(action: () -> Unit) {
42 | dismiss = action
43 | }
44 |
45 | fun touchEvent(action: (View, MotionEvent) -> Unit) {
46 | touchEvent = action
47 | }
48 |
49 | fun drag(action: (View, MotionEvent) -> Unit) {
50 | drag = action
51 | }
52 |
53 | fun dragEnd(action: (View) -> Unit) {
54 | dragEnd = action
55 | }
56 | }
57 |
58 | }
--------------------------------------------------------------------------------
/easyfloat/src/main/java/com/lzf/easyfloat/interfaces/OnDisplayHeight.java:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat.interfaces;
2 |
3 | import android.content.Context;
4 |
5 | import org.jetbrains.annotations.NotNull;
6 |
7 | /**
8 | * @author: liuzhenfeng
9 | * @function: 通过接口获取屏幕的有效显示高度
10 | * @date: 2020-02-16 16:21
11 | */
12 | public interface OnDisplayHeight {
13 |
14 | /**
15 | * 获取屏幕有效的显示高度,不包含虚拟导航栏
16 | *
17 | * @param context ApplicationContext
18 | * @return 高度值(int类型)
19 | */
20 | int getDisplayRealHeight(@NotNull Context context);
21 | }
22 |
--------------------------------------------------------------------------------
/easyfloat/src/main/java/com/lzf/easyfloat/interfaces/OnFloatAnimator.kt:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat.interfaces
2 |
3 | import android.animation.Animator
4 | import android.view.View
5 | import android.view.WindowManager
6 | import com.lzf.easyfloat.enums.SidePattern
7 |
8 | /**
9 | * @author: liuzhenfeng
10 | * @function: 系统浮窗的出入动画
11 | * @date: 2019-07-22 16:40
12 | */
13 | interface OnFloatAnimator {
14 |
15 | fun enterAnim(
16 | view: View,
17 | params: WindowManager.LayoutParams,
18 | windowManager: WindowManager,
19 | sidePattern: SidePattern
20 | ): Animator? = null
21 |
22 | fun exitAnim(
23 | view: View,
24 | params: WindowManager.LayoutParams,
25 | windowManager: WindowManager,
26 | sidePattern: SidePattern
27 | ): Animator? = null
28 |
29 | }
--------------------------------------------------------------------------------
/easyfloat/src/main/java/com/lzf/easyfloat/interfaces/OnFloatCallbacks.kt:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat.interfaces
2 |
3 | import android.view.MotionEvent
4 | import android.view.View
5 |
6 | /**
7 | * @author: liuzhenfeng
8 | * @function: 浮窗的一些状态回调
9 | * @date: 2019-07-16 14:11
10 | */
11 | interface OnFloatCallbacks {
12 |
13 | /**
14 | * 浮窗的创建结果,是否创建成功
15 | *
16 | * @param isCreated 是否创建成功
17 | * @param msg 失败返回的结果
18 | * @param view 浮窗xml布局
19 | */
20 | fun createdResult(isCreated: Boolean, msg: String?, view: View?)
21 |
22 | fun show(view: View)
23 |
24 | fun hide(view: View)
25 |
26 | fun dismiss()
27 |
28 | /**
29 | * 触摸事件的回调
30 | */
31 | fun touchEvent(view: View, event: MotionEvent)
32 |
33 | /**
34 | * 浮窗被拖拽时的回调,坐标为浮窗的左上角坐标
35 | */
36 | fun drag(view: View, event: MotionEvent)
37 |
38 | /**
39 | * 拖拽结束时的回调,坐标为浮窗的左上角坐标
40 | */
41 | fun dragEnd(view: View)
42 |
43 | }
--------------------------------------------------------------------------------
/easyfloat/src/main/java/com/lzf/easyfloat/interfaces/OnFloatTouchListener.kt:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat.interfaces
2 |
3 | import android.view.MotionEvent
4 |
5 | /**
6 | * @author: liuzhenfeng
7 | * @function: 系统浮窗的触摸事件
8 | * @date: 2019-07-11 11:06
9 | */
10 | internal interface OnFloatTouchListener {
11 |
12 | fun onTouch(event: MotionEvent)
13 | }
--------------------------------------------------------------------------------
/easyfloat/src/main/java/com/lzf/easyfloat/interfaces/OnInvokeView.java:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat.interfaces;
2 |
3 | import android.view.View;
4 |
5 | /**
6 | * @author: liuzhenfeng
7 | * @function: 设置浮窗内容的接口,由于kotlin暂不支持SAM,所以使用Java接口
8 | * @date: 2019-06-30 14:19
9 | */
10 | public interface OnInvokeView {
11 |
12 | /**
13 | * 设置浮窗布局的具体内容
14 | *
15 | * @param view 浮窗布局
16 | */
17 | void invoke(View view);
18 | }
19 |
--------------------------------------------------------------------------------
/easyfloat/src/main/java/com/lzf/easyfloat/interfaces/OnPermissionResult.kt:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat.interfaces
2 |
3 | /**
4 | * @author: liuzhenfeng
5 | * @function: 浮窗权限的申请结果
6 | * @date: 2019-07-15 16:18
7 | */
8 | interface OnPermissionResult {
9 |
10 | fun permissionResult(isOpen: Boolean)
11 | }
--------------------------------------------------------------------------------
/easyfloat/src/main/java/com/lzf/easyfloat/interfaces/OnTouchRangeListener.kt:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat.interfaces
2 |
3 | import com.lzf.easyfloat.widget.BaseSwitchView
4 |
5 | /**
6 | * @author: liuzhenfeng
7 | * @date: 2020/10/25 20:25
8 | * @Package: com.lzf.easyfloat.interfaces
9 | * @Description: 区域触摸事件回调
10 | */
11 | interface OnTouchRangeListener {
12 |
13 | /**
14 | * 手指触摸到指定区域
15 | */
16 | fun touchInRange(inRange: Boolean, view: BaseSwitchView)
17 |
18 | /**
19 | * 在指定区域抬起手指
20 | */
21 | fun touchUpInRange()
22 |
23 | }
--------------------------------------------------------------------------------
/easyfloat/src/main/java/com/lzf/easyfloat/permission/PermissionFragment.kt:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat.permission
2 |
3 | import android.app.Activity
4 | import android.app.Fragment
5 | import android.content.Intent
6 | import android.os.Bundle
7 | import android.os.Handler
8 | import android.os.Looper
9 | import com.lzf.easyfloat.interfaces.OnPermissionResult
10 | import com.lzf.easyfloat.utils.Logger
11 |
12 | /**
13 | * @author: liuzhenfeng
14 | * @function: 用于浮窗权限的申请,自动处理回调结果
15 | * @date: 2019-07-15 10:36
16 | */
17 | internal class PermissionFragment : Fragment() {
18 |
19 | companion object {
20 | private var onPermissionResult: OnPermissionResult? = null
21 |
22 | fun requestPermission(activity: Activity, onPermissionResult: OnPermissionResult) {
23 | this.onPermissionResult = onPermissionResult
24 | activity.fragmentManager
25 | .beginTransaction()
26 | .add(PermissionFragment(), activity.localClassName)
27 | .commitAllowingStateLoss()
28 | }
29 | }
30 |
31 | override fun onActivityCreated(savedInstanceState: Bundle?) {
32 | super.onActivityCreated(savedInstanceState)
33 | // 权限申请
34 | PermissionUtils.requestPermission(this)
35 | Logger.i("PermissionFragment:requestPermission")
36 | }
37 |
38 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
39 | if (requestCode == PermissionUtils.requestCode) {
40 | // 需要延迟执行,不然即使授权,仍有部分机型获取不到权限
41 | Handler(Looper.getMainLooper()).postDelayed({
42 | val activity = activity ?: return@postDelayed
43 | val check = PermissionUtils.checkPermission(activity)
44 | Logger.i("PermissionFragment onActivityResult: $check")
45 | // 回调权限结果
46 | onPermissionResult?.permissionResult(check)
47 | onPermissionResult = null
48 | // 将Fragment移除
49 | fragmentManager.beginTransaction().remove(this).commitAllowingStateLoss()
50 | }, 500)
51 | }
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/easyfloat/src/main/java/com/lzf/easyfloat/permission/PermissionUtils.kt:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat.permission
2 |
3 | import android.app.Activity
4 | import android.app.Fragment
5 | import android.content.Context
6 | import android.content.Intent
7 | import android.net.Uri
8 | import android.os.Build
9 | import android.provider.Settings
10 | import android.util.Log
11 | import com.lzf.easyfloat.interfaces.OnPermissionResult
12 | import com.lzf.easyfloat.permission.rom.*
13 | import com.lzf.easyfloat.utils.Logger
14 |
15 | /**
16 | * @author: liuzhenfeng
17 | * @function: 悬浮窗权限工具类
18 | * @date: 2019-07-15 10:22
19 | */
20 | object PermissionUtils {
21 |
22 | internal const val requestCode = 199
23 | private const val TAG = "PermissionUtils--->"
24 |
25 | /**
26 | * 检测是否有悬浮窗权限
27 | * 6.0 版本之后由于 google 增加了对悬浮窗权限的管理,所以方式就统一了
28 | */
29 | @JvmStatic
30 | fun checkPermission(context: Context): Boolean =
31 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) when {
32 | RomUtils.checkIsHuaweiRom() -> huaweiPermissionCheck(context)
33 | RomUtils.checkIsMiuiRom() -> miuiPermissionCheck(context)
34 | RomUtils.checkIsOppoRom() -> oppoROMPermissionCheck(context)
35 | RomUtils.checkIsMeizuRom() -> meizuPermissionCheck(context)
36 | RomUtils.checkIs360Rom() -> qikuPermissionCheck(context)
37 | else -> true
38 | } else commonROMPermissionCheck(context)
39 |
40 | /**
41 | * 申请悬浮窗权限
42 | */
43 | @JvmStatic
44 | fun requestPermission(activity: Activity, onPermissionResult: OnPermissionResult) =
45 | PermissionFragment.requestPermission(activity, onPermissionResult)
46 |
47 | internal fun requestPermission(fragment: Fragment) =
48 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) when {
49 | RomUtils.checkIsHuaweiRom() -> HuaweiUtils.applyPermission(fragment)
50 | RomUtils.checkIsMiuiRom() -> MiuiUtils.applyMiuiPermission(fragment)
51 | RomUtils.checkIsOppoRom() -> OppoUtils.applyOppoPermission(fragment)
52 | RomUtils.checkIsMeizuRom() -> MeizuUtils.applyPermission(fragment)
53 | RomUtils.checkIs360Rom() -> QikuUtils.applyPermission(fragment)
54 | else -> Logger.i(TAG, "原生 Android 6.0 以下无需权限申请")
55 | } else commonROMPermissionApply(fragment)
56 |
57 | private fun huaweiPermissionCheck(context: Context) =
58 | HuaweiUtils.checkFloatWindowPermission(context)
59 |
60 | private fun miuiPermissionCheck(context: Context) =
61 | MiuiUtils.checkFloatWindowPermission(context)
62 |
63 | private fun meizuPermissionCheck(context: Context) =
64 | MeizuUtils.checkFloatWindowPermission(context)
65 |
66 | private fun qikuPermissionCheck(context: Context) =
67 | QikuUtils.checkFloatWindowPermission(context)
68 |
69 | private fun oppoROMPermissionCheck(context: Context) =
70 | OppoUtils.checkFloatWindowPermission(context)
71 |
72 | /**
73 | * 6.0以后,通用悬浮窗权限检测
74 | * 但是魅族6.0的系统这种方式不好用,需要单独适配一下
75 | */
76 | private fun commonROMPermissionCheck(context: Context): Boolean =
77 | if (RomUtils.checkIsMeizuRom()) meizuPermissionCheck(context) else {
78 | var result = true
79 | if (Build.VERSION.SDK_INT >= 23) try {
80 | val clazz = Settings::class.java
81 | val canDrawOverlays =
82 | clazz.getDeclaredMethod("canDrawOverlays", Context::class.java)
83 | result = canDrawOverlays.invoke(null, context) as Boolean
84 | } catch (e: Exception) {
85 | Log.e(TAG, Log.getStackTraceString(e))
86 | }
87 | result
88 | }
89 |
90 | /**
91 | * 通用 rom 权限申请
92 | */
93 | private fun commonROMPermissionApply(fragment: Fragment) = when {
94 | // 这里也一样,魅族系统需要单独适配
95 | RomUtils.checkIsMeizuRom() -> MeizuUtils.applyPermission(fragment)
96 | Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> try {
97 | commonROMPermissionApplyInternal(fragment)
98 | } catch (e: Exception) {
99 | Logger.e(TAG, Log.getStackTraceString(e))
100 | }
101 | // 需要做统计效果
102 | else -> Logger.d(TAG, "user manually refuse OVERLAY_PERMISSION")
103 | }
104 |
105 | @JvmStatic
106 | fun commonROMPermissionApplyInternal(fragment: Fragment) = try {
107 | val clazz = Settings::class.java
108 | val field = clazz.getDeclaredField("ACTION_MANAGE_OVERLAY_PERMISSION")
109 | val intent = Intent(field.get(null).toString())
110 | intent.data = Uri.parse("package:${fragment.activity.packageName}")
111 | fragment.startActivityForResult(intent, requestCode)
112 | } catch (e: Exception) {
113 | Logger.e(TAG, "$e")
114 | }
115 |
116 | }
117 |
118 |
--------------------------------------------------------------------------------
/easyfloat/src/main/java/com/lzf/easyfloat/permission/rom/HuaweiUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Facishare Technology Co., Ltd. All Rights Reserved.
3 | */
4 | package com.lzf.easyfloat.permission.rom;
5 |
6 | import android.annotation.TargetApi;
7 | import android.app.AppOpsManager;
8 | import android.app.Fragment;
9 | import android.content.ActivityNotFoundException;
10 | import android.content.ComponentName;
11 | import android.content.Context;
12 | import android.content.Intent;
13 | import android.os.Binder;
14 | import android.os.Build;
15 | import android.util.Log;
16 | import android.widget.Toast;
17 |
18 | import com.lzf.easyfloat.permission.PermissionUtils;
19 |
20 | import java.lang.reflect.Method;
21 |
22 | public class HuaweiUtils {
23 | private static final String TAG = "HuaweiUtils";
24 |
25 | /**
26 | * 检测 Huawei 悬浮窗权限
27 | */
28 | public static boolean checkFloatWindowPermission(Context context) {
29 | final int version = Build.VERSION.SDK_INT;
30 | if (version >= 19) {
31 | return checkOp(context, 24); //OP_SYSTEM_ALERT_WINDOW = 24;
32 | }
33 | return true;
34 | }
35 |
36 | /**
37 | * 去华为权限申请页面
38 | */
39 | public static void applyPermission(Fragment fragment) {
40 | try {
41 | Intent intent = new Intent();
42 | //华为权限管理,跳转到指定app的权限管理位置需要华为接口权限,未解决
43 | ComponentName comp = new ComponentName("com.huawei.systemmanager", "com.huawei.systemmanager.addviewmonitor.AddViewMonitorActivity");//悬浮窗管理页面
44 | intent.setComponent(comp);
45 | if (RomUtils.getEmuiVersion() == 3.1) {
46 | //emui 3.1 的适配
47 | fragment.startActivityForResult(intent, PermissionUtils.requestCode);
48 | } else {
49 | //emui 3.0 的适配
50 | comp = new ComponentName("com.huawei.systemmanager", "com.huawei.notificationmanager.ui.NotificationManagmentActivity");//悬浮窗管理页面
51 | intent.setComponent(comp);
52 | fragment.startActivityForResult(intent, PermissionUtils.requestCode);
53 | }
54 | } catch (SecurityException e) {
55 | Intent intent = new Intent();
56 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
57 | //华为权限管理
58 | ComponentName comp = new ComponentName("com.huawei.systemmanager",
59 | "com.huawei.permissionmanager.ui.MainActivity");
60 | //华为权限管理,跳转到本app的权限管理页面,这个需要华为接口权限,未解决
61 | // 悬浮窗管理页面
62 | intent.setComponent(comp);
63 | fragment.startActivityForResult(intent, PermissionUtils.requestCode);
64 | Log.e(TAG, Log.getStackTraceString(e));
65 | } catch (ActivityNotFoundException e) {
66 | /**
67 | * 手机管家版本较低 HUAWEI SC-UL10
68 | */
69 | Intent intent = new Intent();
70 | //权限管理页面 android4.4
71 | ComponentName comp = new ComponentName("com.Android.settings", "com.android.settings.permission.TabItem");
72 | //此处可跳转到指定app对应的权限管理页面,但是需要相关权限,未解决
73 | intent.setComponent(comp);
74 | fragment.startActivityForResult(intent, PermissionUtils.requestCode);
75 | e.printStackTrace();
76 | Log.e(TAG, Log.getStackTraceString(e));
77 | } catch (Exception e) {
78 | //抛出异常时提示信息
79 | Toast.makeText(fragment.getActivity(), "进入设置页面失败,请手动设置", Toast.LENGTH_LONG).show();
80 | Log.e(TAG, Log.getStackTraceString(e));
81 | }
82 | }
83 |
84 | @TargetApi(Build.VERSION_CODES.KITKAT)
85 | private static boolean checkOp(Context context, int op) {
86 | final int version = Build.VERSION.SDK_INT;
87 | if (version >= 19) {
88 | AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
89 | try {
90 | Class clazz = AppOpsManager.class;
91 | Method method = clazz.getDeclaredMethod("checkOp", int.class, int.class, String.class);
92 | return AppOpsManager.MODE_ALLOWED == (int) method.invoke(manager, op, Binder.getCallingUid(), context.getPackageName());
93 | } catch (Exception e) {
94 | Log.e(TAG, Log.getStackTraceString(e));
95 | }
96 | } else {
97 | Log.e(TAG, "Below API 19 cannot invoke!");
98 | }
99 | return false;
100 | }
101 | }
102 |
103 |
104 |
--------------------------------------------------------------------------------
/easyfloat/src/main/java/com/lzf/easyfloat/permission/rom/MeizuUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Facishare Technology Co., Ltd. All Rights Reserved.
3 | */
4 | package com.lzf.easyfloat.permission.rom;
5 |
6 | import android.annotation.TargetApi;
7 | import android.app.AppOpsManager;
8 | import android.app.Fragment;
9 | import android.content.Context;
10 | import android.content.Intent;
11 | import android.os.Binder;
12 | import android.os.Build;
13 | import android.util.Log;
14 |
15 | import com.lzf.easyfloat.permission.PermissionUtils;
16 |
17 | import java.lang.reflect.Method;
18 |
19 |
20 | public class MeizuUtils {
21 | private static final String TAG = "MeizuUtils";
22 |
23 | /**
24 | * 检测 meizu 悬浮窗权限
25 | */
26 | public static boolean checkFloatWindowPermission(Context context) {
27 | final int version = Build.VERSION.SDK_INT;
28 | if (version >= 19) {
29 | // OP_SYSTEM_ALERT_WINDOW = 24;
30 | return checkOp(context, 24);
31 | }
32 | return true;
33 | }
34 |
35 | /**
36 | * 去魅族权限申请页面
37 | */
38 | public static void applyPermission(Fragment fragment) {
39 | try {
40 | Intent intent = new Intent("com.meizu.safe.security.SHOW_APPSEC");
41 | intent.putExtra("packageName", fragment.getActivity().getPackageName());
42 | fragment.startActivityForResult(intent, PermissionUtils.requestCode);
43 | } catch (Exception e) {
44 | try {
45 | Log.e(TAG, "获取悬浮窗权限, 打开AppSecActivity失败, " + Log.getStackTraceString(e));
46 | // 最新的魅族flyme 6.2.5 用上述方法获取权限失败, 不过又可以用下述方法获取权限了
47 | PermissionUtils.commonROMPermissionApplyInternal(fragment);
48 | } catch (Exception eFinal) {
49 | Log.e(TAG, "获取悬浮窗权限失败, 通用获取方法失败, " + Log.getStackTraceString(eFinal));
50 | }
51 | }
52 |
53 | }
54 |
55 | @TargetApi(Build.VERSION_CODES.KITKAT)
56 | private static boolean checkOp(Context context, int op) {
57 | final int version = Build.VERSION.SDK_INT;
58 | if (version >= 19) {
59 | AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
60 | try {
61 | Class clazz = AppOpsManager.class;
62 | Method method = clazz.getDeclaredMethod("checkOp", int.class, int.class, String.class);
63 | return AppOpsManager.MODE_ALLOWED == (int) method.invoke(manager, op, Binder.getCallingUid(), context.getPackageName());
64 | } catch (Exception e) {
65 | Log.e(TAG, Log.getStackTraceString(e));
66 | }
67 | } else {
68 | Log.e(TAG, "Below API 19 cannot invoke!");
69 | }
70 | return false;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/easyfloat/src/main/java/com/lzf/easyfloat/permission/rom/MiuiUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Facishare Technology Co., Ltd. All Rights Reserved.
3 | */
4 | package com.lzf.easyfloat.permission.rom;
5 |
6 | import android.annotation.TargetApi;
7 | import android.app.AppOpsManager;
8 | import android.app.Fragment;
9 | import android.content.Context;
10 | import android.content.Intent;
11 | import android.content.pm.PackageManager;
12 | import android.net.Uri;
13 | import android.os.Binder;
14 | import android.os.Build;
15 | import android.provider.Settings;
16 | import android.util.Log;
17 |
18 | import com.lzf.easyfloat.permission.PermissionUtils;
19 |
20 | import java.lang.reflect.Method;
21 |
22 | public class MiuiUtils {
23 | private static final String TAG = "MiuiUtils";
24 |
25 | /**
26 | * 获取小米 rom 版本号,获取失败返回 -1
27 | *
28 | * @return miui rom version code, if fail , return -1
29 | */
30 | public static int getMiuiVersion() {
31 | String version = RomUtils.getSystemProperty("ro.miui.ui.version.name");
32 | if (version != null) {
33 | try {
34 | return Integer.parseInt(version.substring(1));
35 | } catch (Exception e) {
36 | Log.e(TAG, "get miui version code error, version : " + version);
37 | Log.e(TAG, Log.getStackTraceString(e));
38 | }
39 | }
40 | return -1;
41 | }
42 |
43 | /**
44 | * 检测 miui 悬浮窗权限
45 | */
46 | public static boolean checkFloatWindowPermission(Context context) {
47 | final int version = Build.VERSION.SDK_INT;
48 | if (version >= 19) {
49 | return checkOp(context, 24); //OP_SYSTEM_ALERT_WINDOW = 24;
50 | } else {
51 | return true;
52 | }
53 | }
54 |
55 | @TargetApi(Build.VERSION_CODES.KITKAT)
56 | private static boolean checkOp(Context context, int op) {
57 | final int version = Build.VERSION.SDK_INT;
58 | if (version >= 19) {
59 | AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
60 | try {
61 | Class clazz = AppOpsManager.class;
62 | Method method = clazz.getDeclaredMethod("checkOp", int.class, int.class, String.class);
63 | return AppOpsManager.MODE_ALLOWED == (int) method.invoke(manager, op, Binder.getCallingUid(), context.getPackageName());
64 | } catch (Exception e) {
65 | Log.e(TAG, Log.getStackTraceString(e));
66 | }
67 | } else {
68 | Log.e(TAG, "Below API 19 cannot invoke!");
69 | }
70 | return false;
71 | }
72 |
73 | /**
74 | * 小米 ROM 权限申请
75 | */
76 | public static void applyMiuiPermission(Fragment fragment) {
77 | int versionCode = getMiuiVersion();
78 | if (versionCode == 5) {
79 | goToMiuiPermissionActivity_V5(fragment);
80 | } else if (versionCode == 6) {
81 | goToMiuiPermissionActivity_V6(fragment);
82 | } else if (versionCode == 7) {
83 | goToMiuiPermissionActivity_V7(fragment);
84 | } else if (versionCode >= 8) {
85 | goToMiuiPermissionActivity_V8(fragment);
86 | } else {
87 | Log.e(TAG, "this is a special MIUI rom version, its version code " + versionCode);
88 | }
89 | }
90 |
91 | private static boolean isIntentAvailable(Intent intent, Context context) {
92 | if (intent == null) {
93 | return false;
94 | }
95 | return context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
96 | }
97 |
98 | /**
99 | * 小米 V5 版本 ROM权限申请
100 | */
101 | public static void goToMiuiPermissionActivity_V5(Fragment fragment) {
102 | String packageName = fragment.getActivity().getPackageName();
103 | Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
104 | Uri uri = Uri.fromParts("package", packageName, null);
105 | intent.setData(uri);
106 | if (isIntentAvailable(intent, fragment.getActivity())) {
107 | fragment.startActivityForResult(intent, PermissionUtils.requestCode);
108 | } else {
109 | Log.e(TAG, "intent is not available!");
110 | }
111 | }
112 |
113 | /**
114 | * 小米 V6 版本 ROM权限申请
115 | */
116 | public static void goToMiuiPermissionActivity_V6(Fragment fragment) {
117 | Intent intent = new Intent("miui.intent.action.APP_PERM_EDITOR");
118 | intent.setClassName("com.miui.securitycenter", "com.miui.permcenter.permissions.AppPermissionsEditorActivity");
119 | intent.putExtra("extra_pkgname", fragment.getActivity().getPackageName());
120 | if (isIntentAvailable(intent, fragment.getActivity())) {
121 | fragment.startActivityForResult(intent, PermissionUtils.requestCode);
122 | } else {
123 | Log.e(TAG, "Intent is not available!");
124 | }
125 | }
126 |
127 | /**
128 | * 小米 V7 版本 ROM权限申请
129 | */
130 | public static void goToMiuiPermissionActivity_V7(Fragment fragment) {
131 | Intent intent = new Intent("miui.intent.action.APP_PERM_EDITOR");
132 | intent.setClassName("com.miui.securitycenter", "com.miui.permcenter.permissions.AppPermissionsEditorActivity");
133 | intent.putExtra("extra_pkgname", fragment.getActivity().getPackageName());
134 | if (isIntentAvailable(intent, fragment.getActivity())) {
135 | fragment.startActivityForResult(intent, PermissionUtils.requestCode);
136 | } else {
137 | Log.e(TAG, "Intent is not available!");
138 | }
139 | }
140 |
141 | /**
142 | * 小米 V8 版本 ROM权限申请
143 | */
144 | public static void goToMiuiPermissionActivity_V8(Fragment fragment) {
145 | Intent intent = new Intent("miui.intent.action.APP_PERM_EDITOR");
146 | intent.setClassName("com.miui.securitycenter", "com.miui.permcenter.permissions.PermissionsEditorActivity");
147 | intent.putExtra("extra_pkgname", fragment.getActivity().getPackageName());
148 | if (isIntentAvailable(intent, fragment.getActivity())) {
149 | fragment.startActivityForResult(intent, PermissionUtils.requestCode);
150 | } else {
151 | intent = new Intent("miui.intent.action.APP_PERM_EDITOR");
152 | intent.setPackage("com.miui.securitycenter");
153 | intent.putExtra("extra_pkgname", fragment.getActivity().getPackageName());
154 | if (isIntentAvailable(intent, fragment.getActivity())) {
155 | fragment.startActivityForResult(intent, PermissionUtils.requestCode);
156 | } else {
157 | Log.e(TAG, "Intent is not available!");
158 | }
159 | }
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/easyfloat/src/main/java/com/lzf/easyfloat/permission/rom/OppoUtils.java:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat.permission.rom;
2 |
3 | import android.annotation.TargetApi;
4 | import android.app.AppOpsManager;
5 | import android.app.Fragment;
6 | import android.content.ComponentName;
7 | import android.content.Context;
8 | import android.content.Intent;
9 | import android.os.Binder;
10 | import android.os.Build;
11 | import android.util.Log;
12 |
13 | import com.lzf.easyfloat.permission.PermissionUtils;
14 |
15 | import java.lang.reflect.Method;
16 |
17 | /**
18 | * Description:
19 | *
20 | * @author Shawn_Dut
21 | * @since 2018-02-01
22 | */
23 | public class OppoUtils {
24 |
25 | private static final String TAG = "OppoUtils";
26 |
27 | /**
28 | * 检测 360 悬浮窗权限
29 | */
30 | public static boolean checkFloatWindowPermission(Context context) {
31 | final int version = Build.VERSION.SDK_INT;
32 | if (version >= 19) {
33 | // OP_SYSTEM_ALERT_WINDOW = 24;
34 | return checkOp(context, 24);
35 | }
36 | return true;
37 | }
38 |
39 | @TargetApi(Build.VERSION_CODES.KITKAT)
40 | private static boolean checkOp(Context context, int op) {
41 | final int version = Build.VERSION.SDK_INT;
42 | if (version >= 19) {
43 | AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
44 | try {
45 | Class clazz = AppOpsManager.class;
46 | Method method = clazz.getDeclaredMethod("checkOp", int.class, int.class, String.class);
47 | return AppOpsManager.MODE_ALLOWED == (int) method.invoke(manager, op, Binder.getCallingUid(), context.getPackageName());
48 | } catch (Exception e) {
49 | Log.e(TAG, Log.getStackTraceString(e));
50 | }
51 | } else {
52 | Log.e(TAG, "Below API 19 cannot invoke!");
53 | }
54 | return false;
55 | }
56 |
57 | /**
58 | * oppo ROM 权限申请
59 | */
60 | public static void applyOppoPermission(Fragment fragment) {
61 | //merge requestPermission from https://github.com/zhaozepeng/FloatWindowPermission/pull/26
62 | try {
63 | Intent intent = new Intent();
64 | //悬浮窗管理页面
65 | ComponentName comp = new ComponentName("com.coloros.safecenter", "com.coloros.safecenter.sysfloatwindow.FloatWindowListActivity");
66 | intent.setComponent(comp);
67 | fragment.startActivityForResult(intent, PermissionUtils.requestCode);
68 | } catch (Exception e) {
69 | e.printStackTrace();
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/easyfloat/src/main/java/com/lzf/easyfloat/permission/rom/QikuUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Facishare Technology Co., Ltd. All Rights Reserved.
3 | */
4 | package com.lzf.easyfloat.permission.rom;
5 |
6 | import android.annotation.TargetApi;
7 | import android.app.AppOpsManager;
8 | import android.app.Fragment;
9 | import android.content.Context;
10 | import android.content.Intent;
11 | import android.content.pm.PackageManager;
12 | import android.os.Binder;
13 | import android.os.Build;
14 | import android.util.Log;
15 |
16 | import com.lzf.easyfloat.permission.PermissionUtils;
17 |
18 | import java.lang.reflect.Method;
19 |
20 | public class QikuUtils {
21 | private static final String TAG = "QikuUtils";
22 |
23 | /**
24 | * 检测 360 悬浮窗权限
25 | */
26 | public static boolean checkFloatWindowPermission(Context context) {
27 | final int version = Build.VERSION.SDK_INT;
28 | if (version >= 19) {
29 | // OP_SYSTEM_ALERT_WINDOW = 24;
30 | return checkOp(context, 24);
31 | }
32 | return true;
33 | }
34 |
35 | @TargetApi(Build.VERSION_CODES.KITKAT)
36 | private static boolean checkOp(Context context, int op) {
37 | final int version = Build.VERSION.SDK_INT;
38 | if (version >= 19) {
39 | AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
40 | try {
41 | Class clazz = AppOpsManager.class;
42 | Method method = clazz.getDeclaredMethod("checkOp", int.class, int.class, String.class);
43 | return AppOpsManager.MODE_ALLOWED == (int) method.invoke(manager, op, Binder.getCallingUid(), context.getPackageName());
44 | } catch (Exception e) {
45 | Log.e(TAG, Log.getStackTraceString(e));
46 | }
47 | } else {
48 | Log.e("", "Below API 19 cannot invoke!");
49 | }
50 | return false;
51 | }
52 |
53 | /**
54 | * 去360权限申请页面
55 | */
56 | public static void applyPermission(Fragment fragment) {
57 | Intent intent = new Intent();
58 | intent.setClassName("com.android.settings", "com.android.settings.Settings$OverlaySettingsActivity");
59 | if (isIntentAvailable(intent, fragment.getActivity())) {
60 | fragment.startActivityForResult(intent, PermissionUtils.requestCode);
61 | } else {
62 | intent.setClassName("com.qihoo360.mobilesafe", "com.qihoo360.mobilesafe.ui.index.AppEnterActivity");
63 | if (isIntentAvailable(intent, fragment.getActivity())) {
64 | fragment.startActivityForResult(intent, PermissionUtils.requestCode);
65 | } else {
66 | Log.e(TAG, "can't open permission page with particular name, please use " +
67 | "\"adb shell dumpsys activity\" command and tell me the name of the float window permission page");
68 | }
69 | }
70 | }
71 |
72 | private static boolean isIntentAvailable(Intent intent, Context context) {
73 | if (intent == null) {
74 | return false;
75 | }
76 | return context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/easyfloat/src/main/java/com/lzf/easyfloat/permission/rom/RomUtils.kt:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat.permission.rom
2 |
3 | import android.os.Build
4 | import android.text.TextUtils
5 | import android.util.Log
6 | import java.io.BufferedReader
7 | import java.io.IOException
8 | import java.io.InputStreamReader
9 |
10 | /**
11 | * @author: liuzhenfeng
12 | * @github:https://github.com/princekin-f/EasyFloat
13 | * @function: 判断手机ROM
14 | * @date: 2020-01-07 22:30
15 | */
16 | object RomUtils {
17 | private const val TAG = "RomUtils--->"
18 |
19 | /**
20 | * 获取 emui 版本号
21 | */
22 | @JvmStatic
23 | fun getEmuiVersion(): Double {
24 | try {
25 | val emuiVersion = getSystemProperty("ro.build.version.emui")
26 | val version = emuiVersion!!.substring(emuiVersion.indexOf("_") + 1)
27 | return version.toDouble()
28 | } catch (e: Exception) {
29 | e.printStackTrace()
30 | }
31 | return 4.0
32 | }
33 |
34 | @JvmStatic
35 | fun getSystemProperty(propName: String): String? {
36 | val line: String
37 | var input: BufferedReader? = null
38 | try {
39 | val p = Runtime.getRuntime().exec("getprop $propName")
40 | input = BufferedReader(InputStreamReader(p.inputStream), 1024)
41 | line = input.readLine()
42 | input.close()
43 | } catch (ex: Exception) {
44 | Log.e(TAG, "Unable to read sysprop $propName", ex)
45 | return null
46 | } finally {
47 | if (input != null) {
48 | try {
49 | input.close()
50 | } catch (e: IOException) {
51 | Log.e(TAG, "Exception while closing InputStream", e)
52 | }
53 | }
54 | }
55 | return line
56 | }
57 |
58 | fun checkIsHuaweiRom() = Build.MANUFACTURER.contains("HUAWEI")
59 |
60 | fun checkIsMiuiRom() = !TextUtils.isEmpty(getSystemProperty("ro.miui.ui.version.name"))
61 |
62 | fun checkIsMeizuRom(): Boolean {
63 | val systemProperty = getSystemProperty("ro.build.display.id")
64 | return if (TextUtils.isEmpty(systemProperty)) false
65 | else systemProperty!!.contains("flyme") || systemProperty.toLowerCase().contains("flyme")
66 | }
67 |
68 | fun checkIs360Rom(): Boolean =
69 | Build.MANUFACTURER.contains("QiKU") || Build.MANUFACTURER.contains("360")
70 |
71 | fun checkIsOppoRom() =
72 | Build.MANUFACTURER.contains("OPPO") || Build.MANUFACTURER.contains("oppo")
73 |
74 | fun checkIsVivoRom() =
75 | Build.MANUFACTURER.contains("VIVO") || Build.MANUFACTURER.contains("vivo")
76 |
77 | }
--------------------------------------------------------------------------------
/easyfloat/src/main/java/com/lzf/easyfloat/utils/DefaultDisplayHeight.kt:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat.utils
2 |
3 | import android.content.Context
4 | import com.lzf.easyfloat.interfaces.OnDisplayHeight
5 |
6 | /**
7 | * @author: liuzhenfeng
8 | * @function: 获取屏幕有效高度的实现类
9 | * @date: 2020-02-16 16:26
10 | */
11 | internal class DefaultDisplayHeight : OnDisplayHeight {
12 |
13 | override fun getDisplayRealHeight(context: Context) = DisplayUtils.rejectedNavHeight(context)
14 |
15 | }
--------------------------------------------------------------------------------
/easyfloat/src/main/java/com/lzf/easyfloat/utils/DisplayUtils.kt:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat.utils
2 |
3 | import android.app.Service
4 | import android.content.Context
5 | import android.content.res.Configuration
6 | import android.graphics.Point
7 | import android.os.Build
8 | import android.provider.Settings
9 | import android.util.DisplayMetrics
10 | import android.view.View
11 | import android.view.WindowManager
12 | import com.lzf.easyfloat.permission.rom.RomUtils
13 |
14 | /**
15 | * @author: liuzhenfeng
16 | * @function: 屏幕显示相关工具类
17 | * @date: 2019-05-23 15:23
18 | */
19 | object DisplayUtils {
20 |
21 | private const val TAG = "DisplayUtils--->"
22 |
23 | fun px2dp(context: Context, pxVal: Float): Int {
24 | val density = context.resources.displayMetrics.density
25 | return (pxVal / density + 0.5f).toInt()
26 | }
27 |
28 | fun dp2px(context: Context, dpVal: Float): Int {
29 | val density = context.resources.displayMetrics.density
30 | return (dpVal * density + 0.5f).toInt()
31 | }
32 |
33 | fun px2sp(context: Context, pxValue: Float): Int {
34 | val fontScale = context.resources.displayMetrics.scaledDensity
35 | return (pxValue / fontScale + 0.5f).toInt()
36 | }
37 |
38 | fun sp2px(context: Context, spValue: Float): Int {
39 | val fontScale = context.resources.displayMetrics.scaledDensity
40 | return (spValue * fontScale + 0.5f).toInt()
41 | }
42 |
43 | /**
44 | * 获取屏幕宽度(显示宽度,横屏的时候可能会小于物理像素值)
45 | */
46 | fun getScreenWidth(context: Context): Int {
47 | val manager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
48 | val metrics = DisplayMetrics()
49 | manager.defaultDisplay.getRealMetrics(metrics)
50 | return if (context.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
51 | metrics.widthPixels
52 | } else {
53 | metrics.widthPixels - getNavigationBarCurrentHeight(context)
54 | }
55 | }
56 |
57 | /**
58 | * 获取屏幕高度(物理像素值的高度)
59 | */
60 | fun getScreenHeight(context: Context) = getScreenSize(context).y
61 |
62 | /**
63 | * 获取屏幕宽高
64 | */
65 | fun getScreenSize(context: Context) = Point().apply {
66 | val windowManager = context.getSystemService(Service.WINDOW_SERVICE) as WindowManager
67 | val display = windowManager.defaultDisplay
68 | display.getRealSize(this)
69 | }
70 |
71 | /**
72 | * 获取状态栏高度
73 | */
74 | fun getStatusBarHeight(context: Context): Int {
75 | var result = 0
76 | val resources = context.resources
77 | val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android")
78 | if (resourceId > 0) result = resources.getDimensionPixelSize(resourceId)
79 | return result
80 | }
81 |
82 | fun statusBarHeight(view: View) = getStatusBarHeight(view.context.applicationContext)
83 |
84 | /**
85 | * 获取导航栏真实的高度(可能未显示)
86 | */
87 | fun getNavigationBarHeight(context: Context): Int {
88 | var result = 0
89 | val resources = context.resources
90 | val resourceId =
91 | resources.getIdentifier("navigation_bar_height", "dimen", "android")
92 | if (resourceId > 0) result = resources.getDimensionPixelSize(resourceId)
93 | return result
94 | }
95 |
96 | /**
97 | * 获取导航栏当前的高度
98 | */
99 | fun getNavigationBarCurrentHeight(context: Context) =
100 | if (hasNavigationBar(context)) getNavigationBarHeight(context) else 0
101 |
102 | /**
103 | * 判断虚拟导航栏是否显示
104 | *
105 | * @param context 上下文对象
106 | * @return true(显示虚拟导航栏),false(不显示或不支持虚拟导航栏)
107 | */
108 | fun hasNavigationBar(context: Context) = when {
109 | getNavigationBarHeight(context) == 0 -> false
110 | RomUtils.checkIsHuaweiRom() && isHuaWeiHideNav(context) -> false
111 | RomUtils.checkIsMiuiRom() && isMiuiFullScreen(context) -> false
112 | RomUtils.checkIsVivoRom() && isVivoFullScreen(context) -> false
113 | else -> isHasNavigationBar(context)
114 | }
115 |
116 | /**
117 | * 不包含导航栏的有效高度(没有导航栏,或者已去除导航栏的高度)
118 | */
119 | fun rejectedNavHeight(context: Context): Int {
120 | val point = getScreenSize(context)
121 | if (point.x > point.y) return point.y
122 | return point.y - getNavigationBarCurrentHeight(context)
123 | }
124 |
125 | /**
126 | * 华为手机是否隐藏了虚拟导航栏
127 | * @return true 表示隐藏了,false 表示未隐藏
128 | */
129 | private fun isHuaWeiHideNav(context: Context) =
130 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
131 | Settings.System.getInt(context.contentResolver, "navigationbar_is_min", 0)
132 | } else {
133 | Settings.Global.getInt(context.contentResolver, "navigationbar_is_min", 0)
134 | } != 0
135 |
136 | /**
137 | * 小米手机是否开启手势操作
138 | * @return false 表示使用的是虚拟导航键(NavigationBar), true 表示使用的是手势, 默认是false
139 | */
140 | private fun isMiuiFullScreen(context: Context) =
141 | Settings.Global.getInt(context.contentResolver, "force_fsg_nav_bar", 0) != 0
142 |
143 | /**
144 | * Vivo手机是否开启手势操作
145 | * @return false 表示使用的是虚拟导航键(NavigationBar), true 表示使用的是手势, 默认是false
146 | */
147 | private fun isVivoFullScreen(context: Context): Boolean =
148 | Settings.Secure.getInt(context.contentResolver, "navigation_gesture_on", 0) != 0
149 |
150 | /**
151 | * 其他手机根据屏幕真实高度与显示高度是否相同来判断
152 | */
153 | private fun isHasNavigationBar(context: Context): Boolean {
154 | val windowManager: WindowManager =
155 | context.getSystemService(Service.WINDOW_SERVICE) as WindowManager
156 | val d = windowManager.defaultDisplay
157 |
158 | val realDisplayMetrics = DisplayMetrics()
159 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
160 | d.getRealMetrics(realDisplayMetrics)
161 | }
162 | val realHeight = realDisplayMetrics.heightPixels
163 | val realWidth = realDisplayMetrics.widthPixels
164 |
165 | val displayMetrics = DisplayMetrics()
166 | d.getMetrics(displayMetrics)
167 | val displayHeight = displayMetrics.heightPixels
168 | val displayWidth = displayMetrics.widthPixels
169 |
170 | // 部分无良厂商的手势操作,显示高度 + 导航栏高度,竟然大于物理高度,对于这种情况,直接默认未启用导航栏
171 | if (displayHeight + getNavigationBarHeight(context) > realHeight) return false
172 |
173 | return realWidth - displayWidth > 0 || realHeight - displayHeight > 0
174 | }
175 |
176 | }
--------------------------------------------------------------------------------
/easyfloat/src/main/java/com/lzf/easyfloat/utils/DragUtils.kt:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat.utils
2 |
3 | import android.view.*
4 | import com.lzf.easyfloat.EasyFloat
5 | import com.lzf.easyfloat.R
6 | import com.lzf.easyfloat.anim.DefaultAnimator
7 | import com.lzf.easyfloat.enums.ShowPattern
8 | import com.lzf.easyfloat.enums.SidePattern
9 | import com.lzf.easyfloat.interfaces.OnFloatAnimator
10 | import com.lzf.easyfloat.interfaces.OnTouchRangeListener
11 | import com.lzf.easyfloat.widget.BaseSwitchView
12 |
13 | /**
14 | * @author: liuzhenfeng
15 | * @date: 2020/10/24 21:29
16 | * @Package: com.lzf.easyfloat.utils
17 | * @Description: 拖拽打开、关闭浮窗
18 | */
19 | object DragUtils {
20 |
21 | private const val ADD_TAG = "ADD_TAG"
22 | private const val CLOSE_TAG = "CLOSE_TAG"
23 | private var addView: BaseSwitchView? = null
24 | private var closeView: BaseSwitchView? = null
25 | private var downX = 0f
26 | private var screenWidth = 0
27 | private var offset = 0f
28 |
29 | /**
30 | * 注册侧滑创建浮窗
31 | * @param event Activity 的触摸事件
32 | * @param listener 右下角区域触摸事件回调
33 | * @param layoutId 右下角区域的布局文件
34 | * @param slideOffset 当前屏幕侧滑进度
35 | * @param start 动画开始阈值
36 | * @param end 动画结束阈值
37 | */
38 | @JvmOverloads
39 | fun registerSwipeAdd(
40 | event: MotionEvent?,
41 | listener: OnTouchRangeListener? = null,
42 | layoutId: Int = R.layout.default_add_layout,
43 | slideOffset: Float = -1f,
44 | start: Float = 0.1f,
45 | end: Float = 0.5f
46 | ) {
47 | if (event == null) return
48 |
49 | // 设置了侧滑监听,使用侧滑数据
50 | if (slideOffset != -1f) {
51 | // 如果滑动偏移,超过了动画起始位置,开始显示浮窗,并执行偏移动画
52 | if (slideOffset >= start) {
53 | val progress = minOf((slideOffset - start) / (end - start), 1f)
54 | setAddView(event, progress, listener, layoutId)
55 | } else dismissAdd()
56 | } else {
57 | // 未提供侧滑监听,根据手指坐标信息,判断浮窗信息
58 | screenWidth = DisplayUtils.getScreenWidth(LifecycleUtils.application)
59 | offset = event.rawX / screenWidth
60 | when (event.action) {
61 | MotionEvent.ACTION_DOWN -> downX = event.rawX
62 | MotionEvent.ACTION_MOVE -> {
63 | // 起始值小于最小边界值,并且当前偏离量大于最小边界
64 | if (downX < start * screenWidth && offset >= start) {
65 | val progress = minOf((offset - start) / (end - start), 1f)
66 | setAddView(event, progress, listener, layoutId)
67 | } else dismissAdd()
68 | }
69 | MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
70 | downX = 0f
71 | setAddView(event, offset, listener, layoutId)
72 | }
73 | }
74 | }
75 | }
76 |
77 | private fun setAddView(
78 | event: MotionEvent,
79 | progress: Float,
80 | listener: OnTouchRangeListener? = null,
81 | layoutId: Int
82 | ) {
83 | // 设置触摸状态监听
84 | addView?.let {
85 | it.setTouchRangeListener(event, listener)
86 | it.translationX = it.width * (1 - progress)
87 | it.translationY = it.width * (1 - progress)
88 | }
89 | // 手指抬起或者事件取消,关闭添加浮窗
90 | if (event.action == MotionEvent.ACTION_UP || event.action == MotionEvent.ACTION_CANCEL) dismissAdd()
91 | else showAdd(layoutId)
92 | }
93 |
94 | private fun showAdd(layoutId: Int) {
95 | if (EasyFloat.isShow(ADD_TAG)) return
96 | EasyFloat.with(LifecycleUtils.application)
97 | .setLayout(layoutId)
98 | .setShowPattern(ShowPattern.CURRENT_ACTIVITY)
99 | .setTag(ADD_TAG)
100 | .setDragEnable(false)
101 | .setSidePattern(SidePattern.BOTTOM)
102 | .setGravity(Gravity.BOTTOM or Gravity.END)
103 | .setAnimator(null)
104 | .registerCallback {
105 | createResult { isCreated, _, view ->
106 | if (!isCreated || view == null) return@createResult
107 | if ((view as ViewGroup).childCount > 0) {
108 | // 获取区间判断布局
109 | view.getChildAt(0).apply {
110 | if (this is BaseSwitchView) {
111 | addView = this
112 | translationX = width.toFloat()
113 | translationY = width.toFloat()
114 | }
115 | }
116 | }
117 | }
118 | dismiss { addView = null }
119 | }
120 | .show()
121 | }
122 |
123 | /**
124 | * 注册侧滑关闭浮窗
125 | * @param event 浮窗的触摸事件
126 | * @param listener 关闭区域触摸事件回调
127 | * @param layoutId 关闭区域的布局文件
128 | * @param showPattern 关闭区域的浮窗类型
129 | * @param appFloatAnimator 关闭区域的浮窗出入动画
130 | */
131 | @JvmOverloads
132 | fun registerDragClose(
133 | event: MotionEvent,
134 | listener: OnTouchRangeListener? = null,
135 | layoutId: Int = R.layout.default_close_layout,
136 | showPattern: ShowPattern = ShowPattern.CURRENT_ACTIVITY,
137 | appFloatAnimator: OnFloatAnimator? = DefaultAnimator()
138 | ) {
139 | showClose(layoutId, showPattern, appFloatAnimator)
140 | // 设置触摸状态监听
141 | closeView?.setTouchRangeListener(event, listener)
142 | // 抬起手指时,关闭删除选项
143 | if (event.action == MotionEvent.ACTION_UP || event.action == MotionEvent.ACTION_CANCEL) dismissClose()
144 | }
145 |
146 | private fun showClose(
147 | layoutId: Int,
148 | showPattern: ShowPattern,
149 | appFloatAnimator: OnFloatAnimator?
150 | ) {
151 | if (EasyFloat.isShow(CLOSE_TAG)) return
152 | EasyFloat.with(LifecycleUtils.application)
153 | .setLayout(layoutId)
154 | .setShowPattern(showPattern)
155 | .setMatchParent(widthMatch = true)
156 | .setTag(CLOSE_TAG)
157 | .setSidePattern(SidePattern.BOTTOM)
158 | .setGravity(Gravity.BOTTOM)
159 | .setAnimator(appFloatAnimator)
160 | .registerCallback {
161 | createResult { isCreated, _, view ->
162 | if (!isCreated || view == null) return@createResult
163 | if ((view as ViewGroup).childCount > 0) {
164 | // 获取区间判断布局
165 | view.getChildAt(0).apply { if (this is BaseSwitchView) closeView = this }
166 | }
167 | }
168 | dismiss { closeView = null }
169 | }
170 | .show()
171 | }
172 |
173 | private fun dismissAdd() = EasyFloat.dismiss(ADD_TAG)
174 |
175 | private fun dismissClose() = EasyFloat.dismiss(CLOSE_TAG)
176 |
177 | }
--------------------------------------------------------------------------------
/easyfloat/src/main/java/com/lzf/easyfloat/utils/InputMethodUtils.kt:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat.utils
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context.INPUT_METHOD_SERVICE
5 | import android.os.Handler
6 | import android.os.Looper
7 | import android.view.MotionEvent
8 | import android.view.WindowManager
9 | import android.view.inputmethod.InputMethodManager
10 | import android.widget.EditText
11 | import com.lzf.easyfloat.core.FloatingWindowManager
12 |
13 | /**
14 | * @author: liuzhenfeng
15 | * @function: 软键盘工具类:解决浮窗内的EditText,无法弹起软键盘的问题
16 | * @date: 2019-08-17 11:11
17 | */
18 | object InputMethodUtils {
19 |
20 | @SuppressLint("ClickableViewAccessibility")
21 | internal fun initInputMethod(editText: EditText, tag: String? = null) {
22 | editText.setOnTouchListener { _, event ->
23 | if (event.action == MotionEvent.ACTION_DOWN) openInputMethod(editText, tag)
24 | false
25 | }
26 | }
27 |
28 | /**
29 | * 让浮窗获取焦点,并打开软键盘
30 | */
31 | @JvmStatic
32 | @JvmOverloads
33 | fun openInputMethod(editText: EditText, tag: String? = null) {
34 | FloatingWindowManager.getHelper(tag)?.apply {
35 | // 更改flags,并刷新布局,让系统浮窗获取焦点
36 | params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
37 | windowManager.updateViewLayout(frameLayout, params)
38 | }
39 |
40 | Handler(Looper.getMainLooper()).postDelayed({
41 | // 打开软键盘
42 | val inputManager =
43 | editText.context.getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager?
44 | inputManager?.showSoftInput(editText, 0)
45 | }, 100)
46 | }
47 |
48 | /**
49 | * 当软键盘关闭时,调用此方法,移除系统浮窗的焦点,不然系统返回键无效
50 | */
51 | @JvmStatic
52 | @JvmOverloads
53 | fun closedInputMethod(tag: String? = null) =
54 | FloatingWindowManager.getHelper(tag)?.run {
55 | params.flags =
56 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
57 | windowManager.updateViewLayout(frameLayout, params)
58 | }
59 |
60 | }
--------------------------------------------------------------------------------
/easyfloat/src/main/java/com/lzf/easyfloat/utils/LifecycleUtils.kt:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat.utils
2 |
3 | import android.app.Activity
4 | import android.app.Application
5 | import android.os.Bundle
6 | import com.lzf.easyfloat.core.FloatingWindowManager
7 | import com.lzf.easyfloat.enums.ShowPattern
8 | import java.lang.ref.WeakReference
9 |
10 | /**
11 | * @author: liuzhenfeng
12 | * @function: 通过生命周期回调,判断系统浮窗的过滤信息,以及app是否位于前台,控制浮窗显隐
13 | * @date: 2019-07-11 15:51
14 | */
15 | internal object LifecycleUtils {
16 |
17 | lateinit var application: Application
18 | private var activityCount = 0
19 | private var mTopActivity: WeakReference? = null
20 |
21 | fun getTopActivity(): Activity? = mTopActivity?.get()
22 |
23 | fun setLifecycleCallbacks(application: Application) {
24 | this.application = application
25 | application.registerActivityLifecycleCallbacks(object :
26 | Application.ActivityLifecycleCallbacks {
27 |
28 | override fun onActivityCreated(activity: Activity?, savedInstanceState: Bundle?) {}
29 |
30 | override fun onActivityStarted(activity: Activity?) {
31 | // 计算启动的activity数目
32 | activity?.let { activityCount++ }
33 | }
34 |
35 | override fun onActivityResumed(activity: Activity?) {
36 | activity?.let {
37 | mTopActivity?.clear()
38 | mTopActivity = WeakReference(it)
39 | // 每次都要判断当前页面是否需要显示
40 | checkShow(it)
41 | }
42 | }
43 |
44 | override fun onActivityPaused(activity: Activity?) {}
45 |
46 | override fun onActivityStopped(activity: Activity?) {
47 | activity?.let {
48 | // 计算关闭的activity数目,并判断当前App是否处于后台
49 | activityCount--
50 | checkHide(it)
51 | }
52 | }
53 |
54 | override fun onActivityDestroyed(activity: Activity?) {}
55 |
56 | override fun onActivitySaveInstanceState(activity: Activity?, outState: Bundle?) {}
57 | })
58 | }
59 |
60 | /**
61 | * 判断浮窗是否需要显示
62 | */
63 | private fun checkShow(activity: Activity) =
64 | FloatingWindowManager.windowMap.forEach { (tag, manager) ->
65 | manager.config.apply {
66 | when {
67 | // 当前页面的浮窗,不需要处理
68 | showPattern == ShowPattern.CURRENT_ACTIVITY -> return@apply
69 | // 仅后台显示模式下,隐藏浮窗
70 | showPattern == ShowPattern.BACKGROUND -> setVisible(false, tag)
71 | // 如果没有手动隐藏浮窗,需要考虑过滤信息
72 | needShow -> setVisible(activity.componentName.className !in filterSet, tag)
73 | }
74 | }
75 | }
76 |
77 | /**
78 | * 判断浮窗是否需要隐藏
79 | */
80 | private fun checkHide(activity: Activity) {
81 | // 如果不是finish,并且处于前台,无需判断
82 | if (!activity.isFinishing && isForeground()) return
83 | FloatingWindowManager.windowMap.forEach { (tag, manager) ->
84 | // 判断浮窗是否需要关闭
85 | if (activity.isFinishing) manager.params.token?.let {
86 | // 如果token不为空,并且是当前销毁的Activity,关闭浮窗,防止窗口泄漏
87 | if (it == activity.window?.decorView?.windowToken) {
88 | FloatingWindowManager.dismiss(tag, true)
89 | }
90 | }
91 |
92 | manager.config.apply {
93 | if (!isForeground() && manager.config.showPattern != ShowPattern.CURRENT_ACTIVITY) {
94 | // 当app处于后台时,全局、仅后台显示的浮窗,如果没有手动隐藏,需要显示
95 | setVisible(showPattern != ShowPattern.FOREGROUND && needShow, tag)
96 | }
97 | }
98 | }
99 | }
100 |
101 | fun isForeground() = activityCount > 0
102 |
103 | private fun setVisible(isShow: Boolean = isForeground(), tag: String?) =
104 | FloatingWindowManager.visible(isShow, tag)
105 |
106 | }
--------------------------------------------------------------------------------
/easyfloat/src/main/java/com/lzf/easyfloat/utils/Logger.kt:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat.utils
2 |
3 | import android.util.Log
4 | import com.lzf.easyfloat.BuildConfig
5 |
6 | /**
7 | * @author: liuzhenfeng
8 | * @function:
9 | * @date: 2019-05-27 16:48
10 | */
11 | internal object Logger {
12 |
13 | private var tag = "EasyFloat--->"
14 |
15 | // 设为false关闭日志
16 | private var logEnable = BuildConfig.DEBUG
17 |
18 | fun i(msg: Any) = i(tag, msg.toString())
19 |
20 | fun v(msg: Any) = v(tag, msg.toString())
21 |
22 | fun d(msg: Any) = d(tag, msg.toString())
23 |
24 | fun w(msg: Any) = w(tag, msg.toString())
25 |
26 | fun e(msg: Any) = e(tag, msg.toString())
27 |
28 | fun i(tag: String, msg: String) {
29 | if (logEnable) Log.i(tag, msg)
30 | }
31 |
32 | fun v(tag: String, msg: String) {
33 | if (logEnable) Log.v(tag, msg)
34 | }
35 |
36 | fun d(tag: String, msg: String) {
37 | if (logEnable) Log.d(tag, msg)
38 | }
39 |
40 | fun w(tag: String, msg: String) {
41 | if (logEnable) Log.w(tag, msg)
42 | }
43 |
44 | fun e(tag: String, msg: String) {
45 | if (logEnable) Log.e(tag, msg)
46 | }
47 |
48 | }
--------------------------------------------------------------------------------
/easyfloat/src/main/java/com/lzf/easyfloat/widget/BaseSwitchView.kt:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat.widget
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.view.MotionEvent
6 | import android.widget.RelativeLayout
7 | import com.lzf.easyfloat.interfaces.OnTouchRangeListener
8 |
9 | /**
10 | * @author: liuzhenfeng
11 | * @date: 2020/10/25 11:08
12 | * @Package: com.lzf.easyfloat.widget
13 | * @Description:
14 | */
15 | abstract class BaseSwitchView @JvmOverloads constructor(
16 | context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
17 | ) : RelativeLayout(context, attrs, defStyleAttr) {
18 |
19 | abstract fun setTouchRangeListener(event: MotionEvent, listener: OnTouchRangeListener? = null)
20 |
21 | }
--------------------------------------------------------------------------------
/easyfloat/src/main/java/com/lzf/easyfloat/widget/DefaultAddView.kt:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat.widget
2 |
3 | import android.content.Context
4 | import android.graphics.*
5 | import android.util.AttributeSet
6 | import android.view.MotionEvent
7 | import com.lzf.easyfloat.interfaces.OnTouchRangeListener
8 |
9 | /**
10 | * @author: liuzhenfeng
11 | * @date: 11/21/20 17:49
12 | * @Package: com.lzf.easyfloat.widget
13 | * @Description:
14 | */
15 | class DefaultAddView @JvmOverloads constructor(
16 | context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
17 | ) : BaseSwitchView(context, attrs, defStyleAttr) {
18 |
19 | private lateinit var paint: Paint
20 | private var path = Path()
21 | private var width = 0f
22 | private var height = 0f
23 | private var region = Region()
24 | private val totalRegion = Region()
25 | private var inRange = false
26 | private var zoomSize = 18f
27 | private var listener: OnTouchRangeListener? = null
28 |
29 | init {
30 | initPath()
31 | setWillNotDraw(false)
32 | }
33 |
34 | private fun initPath() {
35 | paint = Paint().apply {
36 | color = Color.parseColor("#AA000000")
37 | strokeWidth = 10f
38 | style = Paint.Style.FILL
39 | isAntiAlias = true
40 | }
41 | }
42 |
43 | override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
44 | super.onSizeChanged(w, h, oldw, oldh)
45 | width = w.toFloat()
46 | height = h.toFloat()
47 | }
48 |
49 | override fun onDraw(canvas: Canvas?) {
50 | path.reset()
51 | if (inRange) {
52 | path.addCircle(width, height, minOf(width, height), Path.Direction.CW)
53 | } else {
54 | path.addCircle(width, height, minOf(width, height) - zoomSize, Path.Direction.CW)
55 | totalRegion.set(zoomSize.toInt(), zoomSize.toInt(), width.toInt(), height.toInt())
56 | region.setPath(path, totalRegion)
57 | }
58 | canvas?.drawPath(path, paint)
59 | super.onDraw(canvas)
60 | }
61 |
62 | override fun setTouchRangeListener(event: MotionEvent, listener: OnTouchRangeListener?) {
63 | this.listener = listener
64 | initTouchRange(event)
65 | }
66 |
67 | private fun initTouchRange(event: MotionEvent): Boolean {
68 | val location = IntArray(2)
69 | // 获取在整个屏幕内的绝对坐标
70 | getLocationOnScreen(location)
71 | val currentInRange = region.contains(
72 | event.rawX.toInt() - location[0], event.rawY.toInt() - location[1]
73 | )
74 | if (currentInRange != inRange) {
75 | inRange = currentInRange
76 | invalidate()
77 | }
78 | listener?.touchInRange(currentInRange, this)
79 | if (event.action == MotionEvent.ACTION_UP && currentInRange) {
80 | listener?.touchUpInRange()
81 | }
82 | return currentInRange
83 | }
84 |
85 | }
--------------------------------------------------------------------------------
/easyfloat/src/main/java/com/lzf/easyfloat/widget/DefaultCloseView.kt:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat.widget
2 |
3 | import android.content.Context
4 | import android.graphics.*
5 | import android.util.AttributeSet
6 | import android.view.MotionEvent
7 | import com.lzf.easyfloat.R
8 | import com.lzf.easyfloat.interfaces.OnTouchRangeListener
9 | import com.lzf.easyfloat.utils.DisplayUtils
10 |
11 | /**
12 | * @author: liuzhenfeng
13 | * @date: 2020/10/25 11:16
14 | * @Package: com.lzf.easyfloat.widget
15 | * @Description: 自定义拖拽关闭视图,支持椭圆、矩形、半圆
16 | */
17 | class DefaultCloseView @JvmOverloads constructor(
18 | context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
19 | ) : BaseSwitchView(context, attrs, defStyleAttr) {
20 |
21 | private var normalColor = Color.parseColor("#99000000")
22 | private var inRangeColor = Color.parseColor("#99FF0000")
23 | private var shapeType = 0
24 |
25 | private lateinit var paint: Paint
26 | private var path = Path()
27 | private var width = 0f
28 | private var height = 0f
29 | private var rectF = RectF()
30 | private var region = Region()
31 | private val totalRegion = Region()
32 | private var inRange = false
33 | private var zoomSize = DisplayUtils.dp2px(context, 4f).toFloat()
34 | private var listener: OnTouchRangeListener? = null
35 |
36 | init {
37 | attrs?.apply { initAttrs(this) }
38 | initPaint()
39 | setWillNotDraw(false)
40 | }
41 |
42 | private fun initAttrs(attrs: AttributeSet) =
43 | context.theme.obtainStyledAttributes(attrs, R.styleable.DefaultCloseView, 0, 0).apply {
44 | normalColor = getColor(R.styleable.DefaultCloseView_normalColor, normalColor)
45 | inRangeColor = getColor(R.styleable.DefaultCloseView_inRangeColor, inRangeColor)
46 | shapeType = getInt(R.styleable.DefaultCloseView_closeShapeType, shapeType)
47 | zoomSize = getDimension(R.styleable.DefaultCloseView_zoomSize, zoomSize)
48 | }.recycle()
49 |
50 |
51 | private fun initPaint() {
52 | paint = Paint().apply {
53 | color = normalColor
54 | strokeWidth = 10f
55 | style = Paint.Style.FILL
56 | isAntiAlias = true
57 | }
58 | }
59 |
60 | override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
61 | super.onSizeChanged(w, h, oldw, oldh)
62 | width = w.toFloat()
63 | height = h.toFloat()
64 | }
65 |
66 | override fun onDraw(canvas: Canvas?) {
67 | path.reset()
68 | if (inRange) {
69 | paint.color = inRangeColor
70 | when (shapeType) {
71 | // 半椭圆
72 | 0 -> {
73 | rectF.set(paddingLeft.toFloat(), 0f, width - paddingRight, height * 2)
74 | path.addOval(rectF, Path.Direction.CW)
75 | }
76 | // 矩形
77 | 1 -> {
78 | rectF.set(paddingLeft.toFloat(), 0f, width - paddingRight, height)
79 | path.addRect(rectF, Path.Direction.CW)
80 | }
81 | // 半圆
82 | 2 -> path.addCircle(width / 2, height, height, Path.Direction.CW)
83 | }
84 | } else {
85 | paint.color = normalColor
86 | when (shapeType) {
87 | // 半椭圆
88 | 0 -> {
89 | rectF.set(
90 | paddingLeft + zoomSize,
91 | zoomSize,
92 | width - paddingRight - zoomSize,
93 | (height - zoomSize) * 2
94 | )
95 | path.addOval(rectF, Path.Direction.CW)
96 | totalRegion.set(
97 | paddingLeft + zoomSize.toInt(),
98 | zoomSize.toInt(),
99 | (width - paddingRight - zoomSize).toInt(),
100 | height.toInt()
101 | )
102 | }
103 | // 矩形
104 | 1 -> {
105 | rectF.set(
106 | paddingLeft.toFloat(),
107 | zoomSize,
108 | width - paddingRight,
109 | height
110 | )
111 | path.addRect(rectF, Path.Direction.CW)
112 | totalRegion.set(
113 | paddingLeft,
114 | zoomSize.toInt(),
115 | width.toInt() - paddingRight,
116 | height.toInt()
117 | )
118 | }
119 | // 半圆
120 | 2 -> {
121 | path.addCircle(width / 2, height, height - zoomSize, Path.Direction.CW)
122 | totalRegion.set(0, zoomSize.toInt(), width.toInt(), height.toInt())
123 | }
124 | }
125 | region.setPath(path, totalRegion)
126 | }
127 | canvas?.drawPath(path, paint)
128 | super.onDraw(canvas)
129 | }
130 |
131 | override fun setTouchRangeListener(event: MotionEvent, listener: OnTouchRangeListener?) {
132 | this.listener = listener
133 | initTouchRange(event)
134 | }
135 |
136 | private fun initTouchRange(event: MotionEvent): Boolean {
137 | val location = IntArray(2)
138 | // 获取在整个屏幕内的绝对坐标
139 | getLocationOnScreen(location)
140 | val currentInRange = region.contains(
141 | event.rawX.toInt() - location[0], event.rawY.toInt() - location[1]
142 | )
143 | if (currentInRange != inRange) {
144 | inRange = currentInRange
145 | invalidate()
146 | }
147 | listener?.touchInRange(currentInRange, this)
148 | if (event.action == MotionEvent.ACTION_UP && currentInRange) {
149 | listener?.touchUpInRange()
150 | }
151 | return currentInRange
152 | }
153 |
154 | }
--------------------------------------------------------------------------------
/easyfloat/src/main/java/com/lzf/easyfloat/widget/ParentFrameLayout.kt:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat.widget
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.util.AttributeSet
6 | import android.view.KeyEvent
7 | import android.view.MotionEvent
8 | import android.widget.FrameLayout
9 | import com.lzf.easyfloat.data.FloatConfig
10 | import com.lzf.easyfloat.interfaces.OnFloatTouchListener
11 | import com.lzf.easyfloat.utils.InputMethodUtils
12 |
13 | /**
14 | * @author: liuzhenfeng
15 | * @function: 系统浮窗的父布局,对touch事件进行了重新分发
16 | * @date: 2019-07-10 14:16
17 | */
18 | @SuppressLint("ViewConstructor")
19 | internal class ParentFrameLayout(
20 | context: Context,
21 | private val config: FloatConfig,
22 | attrs: AttributeSet? = null,
23 | defStyleAttr: Int = 0
24 | ) : FrameLayout(context, attrs, defStyleAttr) {
25 |
26 | var touchListener: OnFloatTouchListener? = null
27 | var layoutListener: OnLayoutListener? = null
28 | private var isCreated = false
29 |
30 | // 布局绘制完成的接口,用于通知外部做一些View操作,不然无法获取view宽高
31 | interface OnLayoutListener {
32 | fun onLayout()
33 | }
34 |
35 | override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
36 | super.onLayout(changed, left, top, right, bottom)
37 | // 初次绘制完成的时候,需要设置对齐方式、坐标偏移量、入场动画
38 | if (!isCreated) {
39 | isCreated = true
40 | layoutListener?.onLayout()
41 | }
42 | }
43 |
44 | override fun onInterceptTouchEvent(event: MotionEvent?): Boolean {
45 | if (event != null) touchListener?.onTouch(event)
46 | // 是拖拽事件就进行拦截,反之不拦截
47 | // ps:拦截后将不再回调该方法,会交给该view的onTouchEvent进行处理,所以后续事件需要在onTouchEvent中回调
48 | return config.isDrag || super.onInterceptTouchEvent(event)
49 | }
50 |
51 | @SuppressLint("ClickableViewAccessibility")
52 | override fun onTouchEvent(event: MotionEvent?): Boolean {
53 | if (event != null) touchListener?.onTouch(event)
54 | return config.isDrag || super.onTouchEvent(event)
55 | }
56 |
57 | /**
58 | * 按键转发到视图的分发方法,在这里关闭输入法
59 | */
60 | override fun dispatchKeyEventPreIme(event: KeyEvent?): Boolean {
61 | if (config.hasEditText && event?.action == KeyEvent.ACTION_DOWN && event.keyCode == KeyEvent.KEYCODE_BACK) {
62 | InputMethodUtils.closedInputMethod(config.floatTag)
63 | }
64 | return super.dispatchKeyEventPreIme(event)
65 | }
66 |
67 | override fun onDetachedFromWindow() {
68 | super.onDetachedFromWindow()
69 | config.callbacks?.dismiss()
70 | config.floatCallbacks?.builder?.dismiss?.invoke()
71 | }
72 | }
--------------------------------------------------------------------------------
/easyfloat/src/main/res/drawable-xxxhdpi/icon_delete_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/princekin-f/EasyFloat/1a65226084a46cafc7ebcb27f0a077aebefa9b0e/easyfloat/src/main/res/drawable-xxxhdpi/icon_delete_normal.png
--------------------------------------------------------------------------------
/easyfloat/src/main/res/drawable-xxxhdpi/icon_delete_selected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/princekin-f/EasyFloat/1a65226084a46cafc7ebcb27f0a077aebefa9b0e/easyfloat/src/main/res/drawable-xxxhdpi/icon_delete_selected.png
--------------------------------------------------------------------------------
/easyfloat/src/main/res/drawable/add_normal.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
9 |
12 |
13 |
14 | -
15 |
20 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/easyfloat/src/main/res/drawable/add_selected.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
9 |
12 |
13 |
14 | -
15 |
20 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/easyfloat/src/main/res/drawable/icon_delete_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/princekin-f/EasyFloat/1a65226084a46cafc7ebcb27f0a077aebefa9b0e/easyfloat/src/main/res/drawable/icon_delete_normal.png
--------------------------------------------------------------------------------
/easyfloat/src/main/res/drawable/icon_delete_selected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/princekin-f/EasyFloat/1a65226084a46cafc7ebcb27f0a077aebefa9b0e/easyfloat/src/main/res/drawable/icon_delete_selected.png
--------------------------------------------------------------------------------
/easyfloat/src/main/res/layout/default_add_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
19 |
20 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/easyfloat/src/main/res/layout/default_close_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
23 |
24 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/easyfloat/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/easyfloat/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | EasyFloat
3 | 浮窗
4 | 删除浮窗
5 |
6 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/example/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlin-android-extensions'
4 |
5 | android {
6 | compileSdkVersion 31
7 | defaultConfig {
8 | applicationId "com.lzf.easyfloat.example"
9 | minSdkVersion 19
10 | targetSdkVersion 31
11 | versionCode 1
12 | versionName "1.0"
13 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
14 | }
15 |
16 | signingConfigs {
17 | release {
18 | storeFile file("../example/release/easyfloat.keystore")
19 | storePassword "123123"
20 | keyAlias "easyfloat"
21 | keyPassword "123123"
22 | }
23 | }
24 |
25 | buildTypes {
26 | release {
27 | shrinkResources true
28 | minifyEnabled true
29 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
30 | signingConfig signingConfigs.release
31 | android.applicationVariants.all { variant ->
32 | variant.outputs.all {
33 | outputFileName = "EasyFloat.apk"
34 | }
35 | }
36 | }
37 | }
38 | compileOptions {
39 | sourceCompatibility JavaVersion.VERSION_1_8
40 | targetCompatibility JavaVersion.VERSION_1_8
41 | }
42 | }
43 |
44 | dependencies {
45 | implementation fileTree(dir: 'libs', include: ['*.jar'])
46 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
47 | implementation 'androidx.appcompat:appcompat:1.2.0'
48 | implementation 'androidx.core:core-ktx:1.3.2'
49 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
50 | testImplementation 'junit:junit:4.13.1'
51 | androidTestImplementation 'androidx.test:runner:1.3.0'
52 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
53 | implementation project(path: ':easyfloat')
54 | implementation 'cn.bingoogolapple:bga-swipebacklayout:2.0.1@aar'
55 | implementation "androidx.cardview:cardview:1.0.0"
56 | implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.0'
57 | }
58 |
--------------------------------------------------------------------------------
/example/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the filterSet of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/example/release/EasyFloat.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/princekin-f/EasyFloat/1a65226084a46cafc7ebcb27f0a077aebefa9b0e/example/release/EasyFloat.apk
--------------------------------------------------------------------------------
/example/release/easyfloat.keystore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/princekin-f/EasyFloat/1a65226084a46cafc7ebcb27f0a077aebefa9b0e/example/release/easyfloat.keystore
--------------------------------------------------------------------------------
/example/release/output-metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 2,
3 | "artifactType": {
4 | "type": "APK",
5 | "kind": "Directory"
6 | },
7 | "applicationId": "com.lzf.easyfloat.example",
8 | "variantName": "processReleaseResources",
9 | "elements": [
10 | {
11 | "type": "SINGLE",
12 | "filters": [],
13 | "versionCode": 1,
14 | "versionName": "1.0",
15 | "outputFile": "EasyFloat.apk"
16 | }
17 | ]
18 | }
--------------------------------------------------------------------------------
/example/release/output.json:
--------------------------------------------------------------------------------
1 | [{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":1,"versionName":"1.0","enabled":true,"outputFile":"EasyFloat.apk","fullName":"release","baseName":"release"},"path":"EasyFloat.apk","properties":{}}]
--------------------------------------------------------------------------------
/example/src/androidTest/java/com/lzf/example/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.lzf.example
2 |
3 | import androidx.test.InstrumentationRegistry
4 | import androidx.test.runner.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getTargetContext()
22 | assertEquals("com.lzf.example", appContext.packageName)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/example/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
20 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/example/src/main/java/com/lzf/easyfloat/example/App.kt:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat.example
2 |
3 | import android.annotation.SuppressLint
4 | import android.app.Application
5 | import android.content.Context
6 | import cn.bingoogolapple.swipebacklayout.BGASwipeBackHelper
7 |
8 | /**
9 | * @author: liuzhenfeng
10 | * @function:
11 | * @date: 2019-07-11 15:28
12 | */
13 | class App : Application() {
14 |
15 | companion object {
16 | @SuppressLint("StaticFieldLeak")
17 | lateinit var context: Context
18 | }
19 |
20 | override fun onCreate() {
21 | super.onCreate()
22 | context = this
23 | BGASwipeBackHelper.init(this, null)
24 | }
25 |
26 | }
--------------------------------------------------------------------------------
/example/src/main/java/com/lzf/easyfloat/example/MyAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat.example
2 |
3 | import android.content.Context
4 | import android.view.LayoutInflater
5 | import android.view.MotionEvent
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import android.widget.ArrayAdapter
9 | import android.widget.CheckBox
10 | import android.widget.TextView
11 | import com.lzf.easyfloat.EasyFloat
12 |
13 | /**
14 | * @author: liuzhenfeng
15 | * @github:https://github.com/princekin-f
16 | * @function:
17 | * @date: 2020/4/16 13:57
18 | */
19 | class MyAdapter(
20 | context: Context,
21 | stringArray: Array,
22 | private val resourceId: Int = R.layout.item_simple_list
23 | ) : ArrayAdapter(context, resourceId, stringArray) {
24 |
25 | inner class ViewHolder(view: View) {
26 | val textView: TextView = view.findViewById(R.id.tv_item)
27 | val checkBox: CheckBox = view.findViewById(R.id.checkbox)
28 | }
29 |
30 | override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
31 | val view: View
32 | val viewHolder: ViewHolder
33 | if (convertView == null) {
34 | view = LayoutInflater.from(context).inflate(resourceId, parent, false)
35 | viewHolder = ViewHolder(view)
36 | view.tag = viewHolder
37 | } else {
38 | view = convertView
39 | viewHolder = view.tag as ViewHolder
40 | }
41 |
42 | viewHolder.textView.text = getItem(position)
43 | viewHolder.checkBox.apply {
44 | setOnTouchListener { _, event ->
45 | logger.e("setOnTouchListener: ${event.action}")
46 | EasyFloat.dragEnable(event?.action == MotionEvent.ACTION_CANCEL)
47 | false
48 | }
49 |
50 | setOnCheckedChangeListener { _, isChecked ->
51 | logger.e("点击了:$position isChecked:$isChecked")
52 | }
53 | }
54 | return view
55 | }
56 |
57 | }
--------------------------------------------------------------------------------
/example/src/main/java/com/lzf/easyfloat/example/Reified.kt:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat.example
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 |
6 | /**
7 | * @author: liuzhenfeng
8 | * @github:https://github.com/princekin-f
9 | * @function:
10 | * @date: 2020/4/23 17:27
11 | */
12 | inline fun startActivity(context: Context, block: Intent.() -> Unit = {}) =
13 | context.startActivity(Intent(context, T::class.java).apply(block))
--------------------------------------------------------------------------------
/example/src/main/java/com/lzf/easyfloat/example/activity/BaseActivity.kt:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat.example.activity
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.os.Bundle
6 | import android.os.VibrationEffect
7 | import android.os.Vibrator
8 | import android.view.KeyEvent
9 | import android.view.MotionEvent
10 | import android.widget.ImageView
11 | import android.widget.TextView
12 | import androidx.appcompat.app.AppCompatActivity
13 | import cn.bingoogolapple.swipebacklayout.BGASwipeBackHelper
14 | import com.lzf.easyfloat.EasyFloat
15 | import com.lzf.easyfloat.enums.ShowPattern
16 | import com.lzf.easyfloat.example.R
17 | import com.lzf.easyfloat.interfaces.OnTouchRangeListener
18 | import com.lzf.easyfloat.utils.DragUtils
19 | import com.lzf.easyfloat.widget.BaseSwitchView
20 |
21 | /**
22 | * @author: liuzhenfeng
23 | * @date: 2/8/21 13:39
24 | * @Package: com.lzf.easyfloat.example.activity
25 | * @Description:
26 | */
27 | open class BaseActivity : AppCompatActivity(), BGASwipeBackHelper.Delegate {
28 |
29 | lateinit var bgaSwipeBackHelper: BGASwipeBackHelper
30 | private lateinit var vibrator: Vibrator
31 | private var vibrating = false
32 | var slideOffset = 0f
33 | private var contractTag = "contractFloat"
34 |
35 | override fun onCreate(savedInstanceState: Bundle?) {
36 | super.onCreate(savedInstanceState)
37 | vibrator = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
38 | bgaSwipeBackHelper = BGASwipeBackHelper(this, this)
39 | }
40 |
41 | override fun isSupportSwipeBack(): Boolean = false
42 |
43 | override fun onSwipeBackLayoutSlide(slideOffset: Float) {
44 | this.slideOffset = slideOffset
45 | }
46 |
47 | override fun onSwipeBackLayoutCancel() {}
48 |
49 | override fun onSwipeBackLayoutExecuted() {
50 | bgaSwipeBackHelper.swipeBackward()
51 | }
52 |
53 | override fun onBackPressed() {
54 | // 正在滑动返回的时候取消返回按钮事件
55 | if (bgaSwipeBackHelper.isSliding) return
56 | bgaSwipeBackHelper.backward()
57 | }
58 |
59 | @SuppressLint("MissingPermission")
60 | fun setVibrator(inRange: Boolean) {
61 | if (!vibrator.hasVibrator() || (inRange && vibrating)) return
62 | vibrating = inRange
63 | if (inRange) if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
64 | vibrator.vibrate(VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE))
65 | } else vibrator.vibrate(100)
66 | else vibrator.cancel()
67 | }
68 |
69 | /**
70 | * 注册拖拽关闭
71 | */
72 | fun registerDragClose(event: MotionEvent) =
73 | DragUtils.registerDragClose(event, object : OnTouchRangeListener {
74 | override fun touchInRange(inRange: Boolean, view: BaseSwitchView) {
75 | setVibrator(inRange)
76 | view.findViewById(com.lzf.easyfloat.R.id.tv_delete).text =
77 | if (inRange) "松手删除" else "删除浮窗"
78 |
79 | view.findViewById(com.lzf.easyfloat.R.id.iv_delete)
80 | .setImageResource(
81 | if (inRange) com.lzf.easyfloat.R.drawable.icon_delete_selected
82 | else com.lzf.easyfloat.R.drawable.icon_delete_normal
83 | )
84 | }
85 |
86 | override fun touchUpInRange() {
87 | EasyFloat.dismiss(SwipeTestActivity.FLOAT_TAG, true)
88 | }
89 | }, showPattern = ShowPattern.ALL_TIME)
90 |
91 |
92 | /**
93 | * 显示控制中心(假装)
94 | */
95 | fun showContractFloat() = EasyFloat.with(application)
96 | .setTag(contractTag)
97 | .setLayout(R.layout.float_contract) {
98 | it.findViewById(R.id.tv_back).setOnClickListener {
99 | EasyFloat.dismiss(contractTag)
100 | }
101 | }
102 | .setShowPattern(ShowPattern.FOREGROUND)
103 | .setImmersionStatusBar(true)
104 | .setMatchParent(widthMatch = true, heightMatch = true)
105 | .setDragEnable(false)
106 | .setAnimator(null)
107 | .show()
108 |
109 | override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
110 | if (keyCode == KeyEvent.KEYCODE_BACK) {
111 | if (EasyFloat.isShow(contractTag)) {
112 | EasyFloat.dismiss(contractTag)
113 | return true
114 | }
115 | }
116 | return super.onKeyDown(keyCode, event)
117 | }
118 |
119 | }
--------------------------------------------------------------------------------
/example/src/main/java/com/lzf/easyfloat/example/activity/BorderTestActivity.kt:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat.example.activity
2 |
3 | import android.os.Bundle
4 | import android.view.Gravity
5 | import android.view.View
6 | import android.widget.ImageView
7 | import com.lzf.easyfloat.EasyFloat
8 | import com.lzf.easyfloat.enums.SidePattern
9 | import com.lzf.easyfloat.example.R
10 | import kotlinx.android.synthetic.main.activity_border_test.*
11 |
12 | /**
13 | * @author: liuzhenfeng
14 | * @date: 3/9/21 11:27
15 | * @Package: com.lzf.easyfloat.example.activity
16 | * @Description: 悬浮窗边界测试页面
17 | */
18 | class BorderTestActivity : BaseActivity() {
19 |
20 | private val tag = "borderTest"
21 |
22 | override fun onCreate(savedInstanceState: Bundle?) {
23 | super.onCreate(savedInstanceState)
24 | setContentView(R.layout.activity_border_test)
25 |
26 | tv_show.setOnClickListener { showBorderTest() }
27 | tv_dismiss.setOnClickListener { EasyFloat.dismiss(tag) }
28 | }
29 |
30 | private fun showBorderTest() {
31 | EasyFloat.with(this)
32 | .setTag(tag)
33 | .setLayout(R.layout.float_border_test) {
34 | val ivLogo = it.findViewById(R.id.iv_logo)
35 | val ivLogo2 = it.findViewById(R.id.iv_logo2)
36 | ivLogo.setOnClickListener {
37 | ivLogo2.visibility =
38 | if (ivLogo2.visibility == View.VISIBLE) View.GONE else View.VISIBLE
39 | EasyFloat.updateFloat(tag)
40 | }
41 | ivLogo2.setOnClickListener {
42 | ivLogo.visibility =
43 | if (ivLogo.visibility == View.VISIBLE) View.GONE else View.VISIBLE
44 | EasyFloat.updateFloat(tag)
45 | }
46 | }
47 | .setBorder(view_bg.left, view_bg.top, view_bg.right, view_bg.bottom)
48 | .setGravity(Gravity.CENTER)
49 | .setSidePattern(SidePattern.RESULT_SIDE)
50 | .show()
51 | }
52 |
53 | }
--------------------------------------------------------------------------------
/example/src/main/java/com/lzf/easyfloat/example/activity/JavaTestActivity.java:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat.example.activity;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 | import android.view.Gravity;
6 | import android.view.MotionEvent;
7 | import android.view.View;
8 | import android.widget.Toast;
9 |
10 | import com.lzf.easyfloat.EasyFloat;
11 | import com.lzf.easyfloat.anim.DefaultAnimator;
12 | import com.lzf.easyfloat.enums.ShowPattern;
13 | import com.lzf.easyfloat.enums.SidePattern;
14 | import com.lzf.easyfloat.example.R;
15 | import com.lzf.easyfloat.example.logger;
16 | import com.lzf.easyfloat.interfaces.OnFloatCallbacks;
17 | import com.lzf.easyfloat.permission.PermissionUtils;
18 | import com.lzf.easyfloat.utils.DisplayUtils;
19 |
20 | import org.jetbrains.annotations.NotNull;
21 | import org.jetbrains.annotations.Nullable;
22 |
23 | /**
24 | * @author: liuzhenfeng
25 | * @function: Java兼容性测试
26 | * @date: 2019-08-15 18:38
27 | */
28 | public class JavaTestActivity extends Activity {
29 |
30 | private final String TAG = "JavaTestActivity";
31 |
32 | @Override
33 | protected void onCreate(@androidx.annotation.Nullable Bundle savedInstanceState) {
34 | super.onCreate(savedInstanceState);
35 | setContentView(R.layout.activity_java);
36 |
37 | findViewById(R.id.testJava).setOnClickListener(v -> {
38 | EasyFloat.with(this)
39 | .setTag(TAG)
40 | .setLayout(R.layout.float_custom, view ->
41 | view.findViewById(R.id.textView).setOnClickListener(v1 -> toast("onClick")))
42 | .setGravity(Gravity.END, 0, 100)
43 | // 在Java中使用Kotlin DSL回调
44 | .registerCallback(builder -> {
45 | builder.createResult((aBoolean, s, view) -> {
46 | toast("createResult:" + aBoolean.toString());
47 | return null;
48 | });
49 |
50 | builder.dismiss(() -> {
51 | toast("dismiss");
52 | return null;
53 | });
54 |
55 | // ...可根据需求复写其他方法
56 |
57 | return null;
58 | })
59 | .show();
60 | });
61 |
62 | findViewById(R.id.tvCloseFloat).setOnClickListener(v -> EasyFloat.dismiss(TAG));
63 | }
64 |
65 | private void toast(String text) {
66 | Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
67 | }
68 |
69 | private void test() {
70 | EasyFloat.with(this)
71 | // 设置浮窗xml布局文件
72 | .setLayout(R.layout.float_app, view -> {
73 | // view就是我们传入的浮窗xml布局
74 | })
75 | // 设置浮窗显示类型,默认只在当前Activity显示,可选一直显示、仅前台显示
76 | .setShowPattern(ShowPattern.ALL_TIME)
77 | // 设置吸附方式,共15种模式,详情参考SidePattern
78 | .setSidePattern(SidePattern.RESULT_HORIZONTAL)
79 | // 设置浮窗的标签,用于区分多个浮窗
80 | .setTag("testFloat")
81 | // 设置浮窗是否可拖拽
82 | .setDragEnable(true)
83 | // 浮窗是否包含EditText,默认不包含
84 | .hasEditText(false)
85 | // 设置浮窗固定坐标,ps:设置固定坐标,Gravity属性和offset属性将无效
86 | .setLocation(100, 200)
87 | // 设置浮窗的对齐方式和坐标偏移量
88 | .setGravity(Gravity.END | Gravity.CENTER_VERTICAL, 0, 200)
89 | // 设置拖拽边界值
90 | .setBorder(100, 100, 800, 800)
91 | // 设置宽高是否充满父布局,直接在xml设置match_parent属性无效
92 | .setMatchParent(false, false)
93 | // 设置浮窗的出入动画,可自定义,实现相应接口即可(策略模式),无需动画直接设置为null
94 | .setAnimator(new DefaultAnimator())
95 | // 设置系统浮窗的不需要显示的页面
96 | .setFilter(MainActivity.class, SecondActivity.class)
97 | // 设置系统浮窗的有效显示高度(不包含虚拟导航栏的高度),基本用不到,除非有虚拟导航栏适配问题
98 | .setDisplayHeight(DisplayUtils.INSTANCE::rejectedNavHeight)
99 | // 浮窗的一些状态回调,如:创建结果、显示、隐藏、销毁、touchEvent、拖拽过程、拖拽结束。
100 | .registerCallbacks(new OnFloatCallbacks() {
101 | @Override
102 | public void createdResult(boolean isCreated, @Nullable String msg, @Nullable View view) { }
103 |
104 | @Override
105 | public void show(@NotNull View view) { }
106 |
107 | @Override
108 | public void hide(@NotNull View view) { }
109 |
110 | @Override
111 | public void dismiss() { }
112 |
113 | @Override
114 | public void touchEvent(@NotNull View view, @NotNull MotionEvent event) { }
115 |
116 | @Override
117 | public void drag(@NotNull View view, @NotNull MotionEvent event) { }
118 |
119 | @Override
120 | public void dragEnd(@NotNull View view) { }
121 | })
122 | // Kotlin DSL实现回调效果,和registerCallbacks二选一即可,该方式主要针对Kotlin,Java使用起来并不怎么方便
123 | .registerCallback(builder -> {
124 | builder.createResult((aBoolean, s, view) -> {
125 | logger.e("Java使用kotlin DSL:" + aBoolean);
126 | return null;
127 | });
128 |
129 | builder.dismiss(() -> {
130 | toast("dismiss");
131 | return null;
132 | });
133 |
134 | // ...可根据需求复写其他方法
135 |
136 | return null;
137 | })
138 | // 创建浮窗(这是关键哦😂)
139 | .show();
140 |
141 |
142 | // 测试方法重载
143 | EasyFloat.dragEnable(false);
144 |
145 | PermissionUtils.checkPermission(this);
146 |
147 | }
148 |
149 | }
150 |
--------------------------------------------------------------------------------
/example/src/main/java/com/lzf/easyfloat/example/activity/SecondActivity.kt:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat.example.activity
2 |
3 | import android.animation.Animator
4 | import android.annotation.SuppressLint
5 | import android.os.Bundle
6 | import android.view.Gravity
7 | import android.view.MotionEvent
8 | import android.view.View
9 | import android.view.WindowManager
10 | import android.view.animation.BounceInterpolator
11 | import android.widget.RelativeLayout
12 | import android.widget.TextView
13 | import androidx.core.content.ContextCompat
14 | import com.lzf.easyfloat.EasyFloat
15 | import com.lzf.easyfloat.anim.DefaultAnimator
16 | import com.lzf.easyfloat.enums.ShowPattern
17 | import com.lzf.easyfloat.enums.SidePattern
18 | import com.lzf.easyfloat.example.R
19 | import com.lzf.easyfloat.example.startActivity
20 | import com.lzf.easyfloat.interfaces.OnDisplayHeight
21 | import com.lzf.easyfloat.interfaces.OnFloatCallbacks
22 | import com.lzf.easyfloat.utils.DisplayUtils
23 | import kotlinx.android.synthetic.main.activity_second.*
24 | import kotlinx.android.synthetic.main.activity_second.changeBackground
25 | import kotlinx.android.synthetic.main.activity_second.openEditTextFloat
26 | import kotlinx.android.synthetic.main.activity_second.openJavaTestActivity
27 | import kotlinx.android.synthetic.main.activity_second.recoverBackground
28 | import kotlinx.android.synthetic.main.activity_third.*
29 | import kotlin.random.Random
30 |
31 | /**
32 | * @author: liuzhenfeng
33 | * @function:
34 | * @date: 2019-06-28 16:10
35 | */
36 | class SecondActivity : BaseActivity() {
37 |
38 | override fun onCreate(savedInstanceState: Bundle?) {
39 | super.onCreate(savedInstanceState)
40 | setContentView(R.layout.activity_second)
41 |
42 | tvShow.setOnClickListener {
43 | EasyFloat.with(this)
44 | .setLayout(R.layout.float_top_dialog) {
45 | it.postDelayed({ EasyFloat.dismiss(it.tag.toString()) }, 2333)
46 | }
47 | .setMatchParent(true)
48 | .setSidePattern(SidePattern.TOP)
49 | .setDragEnable(false)
50 | .setTag(Random.nextDouble().toString())
51 | .setAnimator(object : DefaultAnimator() {
52 | override fun enterAnim(
53 | view: View,
54 | params: WindowManager.LayoutParams,
55 | windowManager: WindowManager,
56 | sidePattern: SidePattern
57 | ): Animator? =
58 | super.enterAnim(view, params, windowManager, sidePattern)?.apply {
59 | interpolator = BounceInterpolator()
60 | }
61 |
62 | override fun exitAnim(
63 | view: View,
64 | params: WindowManager.LayoutParams,
65 | windowManager: WindowManager,
66 | sidePattern: SidePattern
67 | ): Animator? =
68 | super.exitAnim(view, params, windowManager, sidePattern)?.setDuration(200)
69 | })
70 | .show()
71 | }
72 |
73 | openEditTextFloat.setOnClickListener { showEditTextFloat() }
74 |
75 | openJavaTestActivity.setOnClickListener { startActivity(this) }
76 |
77 | changeBackground.setOnClickListener {
78 | EasyFloat.getFloatView()?.apply {
79 | findViewById(R.id.rlContent)
80 | .setBackgroundColor(ContextCompat.getColor(this@SecondActivity, R.color.violet))
81 |
82 | // ...其他View操作
83 | }
84 | }
85 |
86 | recoverBackground.setOnClickListener {
87 | EasyFloat.getFloatView()?.findViewById(R.id.rlContent)
88 | ?.setBackgroundColor(ContextCompat.getColor(this, R.color.translucent))
89 | }
90 | }
91 |
92 | @SuppressLint("ClickableViewAccessibility")
93 | private fun showEditTextFloat(tag: String? = "editTextFloat") {
94 | EasyFloat.with(this)
95 | .setShowPattern(ShowPattern.ALL_TIME)
96 | .setGravity(Gravity.CENTER, 0, -300)
97 | .setTag(tag)
98 | .hasEditText(true)
99 | .setLayout(R.layout.float_edit) {
100 | // 注意看注释!
101 | // it.findViewById(R.id.editText).apply {
102 | // setOnTouchListener { _, event ->
103 | // // 如果设置了setOnTouchListener,需要在ACTION_DOWN时手动打开软键盘
104 | // // 如果未设置触摸监听,无需此操作,EasyFloat内部已经监听
105 | // if (event.action == MotionEvent.ACTION_DOWN) {
106 | // InputMethodUtils.openInputMethod(this, tag)
107 | // }
108 | //
109 | // // ....
110 | // // 其他业务逻辑....
111 | // false
112 | // }
113 | // }
114 |
115 | it.findViewById(R.id.tvCloseFloat).setOnClickListener {
116 | EasyFloat.dismiss(tag)
117 | }
118 | }
119 | .show()
120 | }
121 |
122 | private fun showFloat() {
123 |
124 | EasyFloat.with(this).setLayout(R.layout.float_app).show()
125 |
126 | EasyFloat.with(this)
127 | // 设置浮窗xml布局文件,并可设置详细信息
128 | .setLayout(R.layout.float_app) { }
129 | // 设置浮窗显示类型,默认只在当前Activity显示,可选一直显示、仅前台显示
130 | .setShowPattern(ShowPattern.ALL_TIME)
131 | // 设置吸附方式,共15种模式,详情参考SidePattern
132 | .setSidePattern(SidePattern.RESULT_HORIZONTAL)
133 | // 设置浮窗的标签,用于区分多个浮窗
134 | .setTag("testFloat")
135 | // 设置浮窗是否可拖拽
136 | .setDragEnable(true)
137 | // 浮窗是否包含EditText,默认不包含
138 | .hasEditText(false)
139 | // 设置浮窗固定坐标,ps:设置固定坐标,Gravity属性和offset属性将无效
140 | .setLocation(100, 200)
141 | // 设置浮窗的对齐方式和坐标偏移量
142 | .setGravity(Gravity.END or Gravity.CENTER_VERTICAL, 0, 200)
143 | // 设置拖拽边界值
144 | .setBorder(100, 100, 800, 800)
145 | // 设置宽高是否充满父布局,直接在xml设置match_parent属性无效
146 | .setMatchParent(widthMatch = false, heightMatch = false)
147 | // 设置浮窗的出入动画,可自定义,实现相应接口即可(策略模式),无需动画直接设置为null
148 | .setAnimator(DefaultAnimator())
149 | // 设置系统浮窗的不需要显示的页面
150 | .setFilter(MainActivity::class.java, SecondActivity::class.java)
151 | // 设置系统浮窗的有效显示高度(不包含虚拟导航栏的高度),基本用不到,除非有虚拟导航栏适配问题
152 | .setDisplayHeight { context -> DisplayUtils.rejectedNavHeight(context) }
153 | // 浮窗的一些状态回调,如:创建结果、显示、隐藏、销毁、touchEvent、拖拽过程、拖拽结束。
154 | // ps:通过Kotlin DSL实现的回调,可以按需复写方法,用到哪个写哪个
155 | .registerCallback {
156 | createResult { isCreated, msg, view -> }
157 | show { }
158 | hide { }
159 | dismiss { }
160 | touchEvent { view, motionEvent -> }
161 | drag { view, motionEvent -> }
162 | dragEnd { }
163 | }
164 | .registerCallbacks(object : OnFloatCallbacks {
165 | override fun createdResult(isCreated: Boolean, msg: String?, view: View?) {}
166 |
167 | override fun show(view: View) {}
168 |
169 | override fun hide(view: View) {}
170 |
171 | override fun dismiss() {}
172 |
173 | override fun touchEvent(view: View, event: MotionEvent) {}
174 |
175 | override fun drag(view: View, event: MotionEvent) {}
176 |
177 | override fun dragEnd(view: View) {}
178 | })
179 | // 创建浮窗(这是关键哦😂)
180 | .show()
181 | }
182 |
183 | }
--------------------------------------------------------------------------------
/example/src/main/java/com/lzf/easyfloat/example/activity/SwipeTestActivity.kt:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat.example.activity
2 |
3 | import android.os.Bundle
4 | import android.view.Gravity
5 | import android.view.MotionEvent
6 | import android.widget.*
7 | import androidx.constraintlayout.widget.ConstraintLayout
8 | import com.lzf.easyfloat.EasyFloat
9 | import com.lzf.easyfloat.enums.ShowPattern
10 | import com.lzf.easyfloat.enums.SidePattern
11 | import com.lzf.easyfloat.example.R
12 | import com.lzf.easyfloat.interfaces.OnTouchRangeListener
13 | import com.lzf.easyfloat.permission.PermissionUtils
14 | import com.lzf.easyfloat.utils.DragUtils
15 | import com.lzf.easyfloat.widget.BaseSwitchView
16 | import kotlinx.android.synthetic.main.activity_swipe_test.*
17 |
18 | /**
19 | * @author: liuzhenfeng
20 | * @date: 2020/10/26 18:21
21 | * @Package: com.lzf.easyfloat.example.activity
22 | * @Description:
23 | */
24 | class SwipeTestActivity : BaseActivity() {
25 |
26 | companion object {
27 | const val FLOAT_TAG = "SwipeTestActivity"
28 | }
29 |
30 | private var noPermission = false
31 |
32 | override fun onCreate(savedInstanceState: Bundle?) {
33 | super.onCreate(savedInstanceState)
34 | setContentView(R.layout.activity_swipe_test)
35 |
36 | }
37 |
38 | override fun isSupportSwipeBack(): Boolean = true
39 |
40 | override fun onSwipeBackLayoutExecuted() {
41 | if (!noPermission) bgaSwipeBackHelper.swipeBackward()
42 | }
43 |
44 | override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
45 | DragUtils.registerSwipeAdd(ev, object : OnTouchRangeListener {
46 | override fun touchInRange(inRange: Boolean, view: BaseSwitchView) {
47 | setVibrator(inRange)
48 | view.findViewById(com.lzf.easyfloat.R.id.iv_add)
49 | .setImageResource(
50 | if (inRange) com.lzf.easyfloat.R.drawable.add_selected else com.lzf.easyfloat.R.drawable.add_normal
51 | )
52 | }
53 |
54 | override fun touchUpInRange() {
55 | noPermission = !PermissionUtils.checkPermission(this@SwipeTestActivity)
56 | showFloat()
57 | }
58 | }, slideOffset = slideOffset)
59 | return super.dispatchTouchEvent(ev)
60 | }
61 |
62 | private fun showFloat() = EasyFloat.with(this.applicationContext)
63 | .setTag(FLOAT_TAG)
64 | .setShowPattern(ShowPattern.FOREGROUND)
65 | .setImmersionStatusBar(true)
66 | .setGravity(Gravity.END, 0, 500)
67 | .setSidePattern(SidePattern.RESULT_HORIZONTAL)
68 | .setFilter(SecondActivity::class.java)
69 | .setLayout(R.layout.float_swipe) {
70 | it.findViewById(R.id.cl_content).setOnClickListener {
71 | showContractFloat()
72 | }
73 | }
74 | .registerCallback {
75 | createResult { _, _, _ ->
76 | if (noPermission && !this@SwipeTestActivity.isFinishing) bgaSwipeBackHelper.swipeBackward()
77 | }
78 |
79 | drag { view, event ->
80 | // 注册拖拽关闭
81 | registerDragClose(event)
82 |
83 | view.findViewById(R.id.cl_content)
84 | .setBackgroundResource(R.drawable.corners_red)
85 | }
86 |
87 | dragEnd {
88 | it.findViewById(R.id.cl_content).apply {
89 | val location = IntArray(2)
90 | getLocationOnScreen(location)
91 | setBackgroundResource(if (location[0] > 10) R.drawable.corners_left else R.drawable.corners_right)
92 | }
93 | }
94 | }
95 | .show()
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/example/src/main/java/com/lzf/easyfloat/example/logger.kt:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat.example
2 |
3 | import android.util.Log
4 | import com.lzf.easyfloat.EasyFloat
5 |
6 | /**
7 | * @author: liuzhenfeng
8 | * @function:
9 | * @date: 2019-05-27 16:48
10 | */
11 | object logger {
12 |
13 | var tag = "EasyFloat--->"
14 |
15 | // 设为false关闭日志
16 | private var logEnable = true
17 |
18 | fun i(msg: Any) {
19 | i(tag, msg.toString())
20 | }
21 |
22 | fun v(msg: Any) {
23 | v(tag, msg.toString())
24 | }
25 |
26 | fun d(msg: Any) {
27 | d(tag, msg.toString())
28 | }
29 |
30 | fun w(msg: Any) {
31 | w(tag, msg.toString())
32 | }
33 |
34 | @JvmStatic
35 | fun e(msg: Any) {
36 | e(tag, msg.toString())
37 | }
38 |
39 |
40 | fun i(tag: String, msg: String) {
41 | if (logEnable) {
42 | Log.i(tag, msg)
43 | }
44 | }
45 |
46 | fun v(tag: String, msg: String) {
47 | if (logEnable) {
48 | Log.v(tag, msg)
49 | }
50 | }
51 |
52 | fun d(tag: String, msg: String) {
53 | if (logEnable) {
54 | Log.d(tag, msg)
55 | }
56 | }
57 |
58 | fun w(tag: String, msg: String) {
59 | if (logEnable) {
60 | Log.w(tag, msg)
61 | }
62 | }
63 |
64 | @JvmStatic
65 | fun e(tag: String, msg: String) {
66 | if (logEnable) {
67 | Log.e(tag, msg)
68 | }
69 | }
70 |
71 | }
72 |
73 | fun T.log(desc: String = this::class.java.simpleName, tag: String = logger.tag) =
74 | Log.e(tag, "$desc = $this")
--------------------------------------------------------------------------------
/example/src/main/java/com/lzf/easyfloat/example/widget/BubbleSurfaceView.kt:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat.example.widget
2 |
3 | import android.content.Context
4 | import android.graphics.*
5 | import android.util.AttributeSet
6 | import android.view.MotionEvent
7 | import android.view.SurfaceView
8 | import androidx.core.content.ContextCompat
9 | import com.lzf.easyfloat.example.R
10 | import kotlinx.coroutines.*
11 |
12 | /**
13 | * @Description: Copy https://github.com/longway777/CustomViews
14 | */
15 | class BubbleSurfaceView @JvmOverloads constructor(
16 | context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
17 | ) : SurfaceView(context, attrs, defStyleAttr), CoroutineScope by MainScope() {
18 |
19 | private var isRunning = false
20 |
21 | var deviation = 50f
22 | set(value) {
23 | field = value
24 | paint.pathEffect =
25 | ComposePathEffect(CornerPathEffect(50f), DiscretePathEffect(30f, deviation / 2))
26 | }
27 |
28 | private val paintColors = arrayOf(
29 | Color.CYAN,
30 | Color.RED,
31 | Color.GREEN,
32 | Color.MAGENTA,
33 | Color.YELLOW,
34 | Color.WHITE,
35 | Color.GRAY
36 | )
37 |
38 | private var drawingCirclesList = mutableListOf()
39 | private var job: Job? = null
40 | private var touchX = 0f
41 | private var touchY = 0f
42 |
43 | private val paint = Paint().apply {
44 | style = Paint.Style.STROKE
45 | strokeWidth = 10f
46 | }
47 |
48 | private data class DrawingCircle(val x: Float, val y: Float, val color: Int, var radius: Float)
49 |
50 | override fun onTouchEvent(event: MotionEvent?): Boolean {
51 | performClick()
52 | touchX = (event?.x) ?: 0f
53 | touchY = (event?.y) ?: 0f
54 | val drawingCircle = DrawingCircle(touchX, touchY, paintColors.random(), 1f)
55 | drawingCirclesList.add(drawingCircle)
56 | if (drawingCirclesList.size > 20) drawingCirclesList.removeAt(0)
57 | return super.onTouchEvent(event)
58 | }
59 |
60 | private fun createJob() {
61 | job?.cancel()
62 | job = launch(Dispatchers.Default) {
63 | while (isRunning) {
64 | if (holder.surface.isValid) {
65 | val canvas = holder.lockCanvas()
66 | canvas?.drawColor(ContextCompat.getColor(context, R.color.light_gray))
67 | drawingCirclesList.toList().filter { it.radius < 3000 }.forEach {
68 | paint.color = it.color
69 | canvas?.drawCircle(it.x, it.y, it.radius, paint)
70 | it.radius += 10f
71 | }
72 | if (holder.surface.isValid)
73 | holder?.unlockCanvasAndPost(canvas)
74 | }
75 | }
76 | }
77 | }
78 |
79 | override fun onAttachedToWindow() {
80 | super.onAttachedToWindow()
81 | isRunning = true
82 | createJob()
83 | }
84 |
85 | override fun onDetachedFromWindow() {
86 | super.onDetachedFromWindow()
87 | isRunning = false
88 | job?.cancel()
89 | }
90 |
91 | }
--------------------------------------------------------------------------------
/example/src/main/java/com/lzf/easyfloat/example/widget/CircleLoadingView.kt:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat.example.widget
2 |
3 | import android.animation.Animator
4 | import android.animation.ValueAnimator
5 | import android.annotation.SuppressLint
6 | import android.content.Context
7 | import android.graphics.Canvas
8 | import android.graphics.Color
9 | import android.graphics.Paint
10 | import android.graphics.RectF
11 | import android.util.AttributeSet
12 | import android.view.View
13 | import android.view.animation.LinearInterpolator
14 | import com.lzf.easyfloat.example.R
15 | import com.lzf.easyfloat.utils.DisplayUtils
16 | import kotlin.math.cos
17 | import kotlin.math.min
18 | import kotlin.math.sin
19 |
20 | /**
21 | * @author: liuzhenfeng
22 | * @date: 12/24/20 12:20
23 | * @Package: com.lzf.easyfloat.example.widget
24 | * @Description:
25 | */
26 | class CircleLoadingView @JvmOverloads constructor(
27 | context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
28 | ) : View(context, attrs, defStyleAttr) {
29 |
30 | // 环形圆点的相关属性
31 | private var dotRadius = 0f
32 | private var dotPI = 0f
33 | private var dotRotatePI = 0f
34 | private var dotRotateStandard = 0f
35 | private var dotRealRotatePI = 0f
36 | private lateinit var dotPaint: Paint
37 |
38 | // 圆弧的相关属性
39 | private var arcRadius = 0f
40 | private var startAngle = -30f
41 | private var sweepAngle = 240f
42 | private lateinit var arcPaint: Paint
43 | private lateinit var rectF: RectF
44 |
45 | // 可自定义的属性
46 | private var arcWidth = dp2px(context, 2f)
47 | private var loadingColor = Color.WHITE
48 | private var dotSize = 16
49 | private var durationTime = 1500L
50 | // 圆点每周期旋转角度
51 | private var dotAngle = 90f
52 |
53 | private var centX = 0
54 | private var centY = 0
55 | private var animatorValue = 0f
56 | private var animator: ValueAnimator? = null
57 |
58 | init {
59 | attrs?.apply { initAttrs(this) }
60 | initValue()
61 | initPaint()
62 | }
63 |
64 | private fun initAttrs(attrs: AttributeSet) =
65 | context.theme.obtainStyledAttributes(attrs, R.styleable.CircleLoadingView, 0, 0).apply {
66 | arcWidth = getDimension(R.styleable.CircleLoadingView_arcWidth, arcWidth)
67 | loadingColor = getColor(R.styleable.CircleLoadingView_loadingColor, loadingColor)
68 | dotSize = getInt(R.styleable.CircleLoadingView_dotSize, dotSize)
69 | durationTime =
70 | getFloat(R.styleable.CircleLoadingView_durationTime, durationTime.toFloat()).toLong()
71 | dotAngle = getFloat(R.styleable.CircleLoadingView_dotAngle, dotAngle)
72 | }.recycle()
73 |
74 | private fun initValue() {
75 | dotPI = Math.PI.toFloat() * 2 / dotSize
76 | dotRadius = arcWidth * 0.5f
77 | }
78 |
79 | private fun initPaint() {
80 | dotPaint = Paint().apply {
81 | color = loadingColor
82 | style = Paint.Style.FILL
83 | isAntiAlias = true
84 | }
85 |
86 | arcPaint = Paint().apply {
87 | color = loadingColor
88 | style = Paint.Style.STROKE
89 | strokeCap = Paint.Cap.ROUND
90 | strokeWidth = arcWidth
91 | isAntiAlias = true
92 | }
93 | }
94 |
95 | override fun onAttachedToWindow() {
96 | super.onAttachedToWindow()
97 | initAnimator()
98 | }
99 |
100 | override fun onDetachedFromWindow() {
101 | super.onDetachedFromWindow()
102 | animator?.cancel()
103 | }
104 |
105 | private fun initAnimator() {
106 | animator = ValueAnimator.ofFloat(0f, 1f).apply {
107 | duration = durationTime
108 | // 匀速运动,圆弧单独计算插值器
109 | interpolator = LinearInterpolator()
110 | repeatCount = -1
111 | addUpdateListener { animation ->
112 | animatorValue = animation.animatedValue as Float
113 | // 计算环形圆点的旋转角度,再转成圆周率格式
114 | dotRotatePI =
115 | (dotRotateStandard + dotAngle * animatorValue) / 180f * Math.PI.toFloat()
116 | // 为圆弧设置插值器,计算逻辑和设置动画插值器相同,这里为了能和环形圆点共用一个动画,计算移到内部
117 | animatorValue = getInterpolation(animatorValue) * 120f
118 | if (animatorValue <= 60f) {
119 | startAngle = -30f + animatorValue * 5
120 | sweepAngle = 240f - animatorValue * 4
121 | } else {
122 | startAngle = animatorValue - 150f
123 | sweepAngle = (animatorValue - 60f) * 4
124 | }
125 | invalidate()
126 | }
127 | addListener(object : Animator.AnimatorListener {
128 | override fun onAnimationRepeat(p0: Animator?) {
129 | dotRotateStandard += dotAngle
130 | if (dotRotateStandard >= 360f) dotRotateStandard -= 360f
131 | }
132 |
133 | override fun onAnimationEnd(p0: Animator?) {}
134 | override fun onAnimationCancel(p0: Animator?) {}
135 | override fun onAnimationStart(p0: Animator?) {}
136 | })
137 | start()
138 | }
139 | }
140 |
141 | private fun getInterpolation(t: Float) = if (t > 0.5f) 1 - 2 * (1 - t) * t else 2 * (1 - t) * t
142 |
143 | @SuppressLint("DrawAllocation")
144 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
145 | var width = getMeasureSize(widthMeasureSpec, dp2pxInt(context, 120f))
146 | var height = getMeasureSize(heightMeasureSpec, dp2pxInt(context, 120f))
147 | setMeasuredDimension(width, height)
148 |
149 | width = width - paddingLeft - paddingRight
150 | height = height - paddingTop - paddingBottom
151 | centX = width.shr(1) + paddingLeft
152 | centY = height.shr(1) + paddingTop
153 | arcRadius = (min(width, height) - arcWidth) * 0.5f
154 | rectF = RectF(centX - arcRadius, centY - arcRadius, centX + arcRadius, centY + arcRadius)
155 | }
156 |
157 | private fun getMeasureSize(measureSpec: Int, defaultSize: Int): Int {
158 | val specMode = MeasureSpec.getMode(measureSpec)
159 | val specSize = MeasureSpec.getSize(measureSpec)
160 | return when (specMode) {
161 | // 确切大小,所以将得到的尺寸给view
162 | MeasureSpec.EXACTLY -> specSize
163 | // 默认值为 xxx px,此处要结合父控件给子控件的最多大小(要不然会填充父控件),所以采用最小值
164 | MeasureSpec.AT_MOST -> min(defaultSize, specSize)
165 | else -> defaultSize
166 | }
167 | }
168 |
169 | override fun onDraw(canvas: Canvas?) {
170 | canvas?.apply {
171 | for (i in 1..dotSize) {
172 | dotRealRotatePI = i * dotPI + dotRotatePI
173 | drawCircle(
174 | arcRadius * cos(dotRealRotatePI) + centX,
175 | arcRadius * sin(dotRealRotatePI) + centY,
176 | dotRadius, dotPaint
177 | )
178 | }
179 | drawArc(rectF, startAngle, sweepAngle, false, arcPaint)
180 | }
181 | }
182 |
183 | }
184 |
185 | fun dp2px(context: Context, dpVal: Float) = DisplayUtils.dp2px(context, dpVal).toFloat()
186 |
187 | fun dp2pxInt(context: Context, dpVal: Float): Int = DisplayUtils.dp2px(context, dpVal)
188 |
--------------------------------------------------------------------------------
/example/src/main/java/com/lzf/easyfloat/example/widget/MyCustomView.kt:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat.example.widget
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.view.LayoutInflater
6 | import android.widget.FrameLayout
7 | import com.lzf.easyfloat.example.R
8 |
9 | /**
10 | * @author: Liuzhenfeng
11 | * @date: 2021/7/14 20:19
12 | * @Package: com.lzf.easyfloat.example.widget
13 | * @Description: 自定义view测试
14 | */
15 | class MyCustomView @JvmOverloads constructor(
16 | context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
17 | ) : FrameLayout(context, attrs, defStyleAttr) {
18 |
19 | init {
20 | LayoutInflater.from(context).inflate(R.layout.float_custom, this)
21 | }
22 |
23 | }
--------------------------------------------------------------------------------
/example/src/main/java/com/lzf/easyfloat/example/widget/RoundProgressBar.kt:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat.example.widget
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.graphics.*
6 | import android.util.AttributeSet
7 | import android.view.View
8 | import androidx.core.content.ContextCompat
9 | import com.lzf.easyfloat.example.R
10 | import com.lzf.easyfloat.utils.DisplayUtils
11 | import kotlin.math.ceil
12 | import kotlin.math.min
13 |
14 | /**
15 | * @author: liuzhenfeng
16 | * @function: 圆形进度条
17 | * @date: 2019-06-04 09:31
18 | */
19 | class RoundProgressBar(context: Context, attrs: AttributeSet? = null) : View(context, attrs) {
20 |
21 | // 内圆
22 | private var circlePaint = Paint()
23 | private var circleColor = ContextCompat.getColor(context,
24 | R.color.smallCircle
25 | )
26 | private var circleRadius = dp2px(20f)
27 | // 进度条
28 | private var progressPaint = Paint()
29 | private var progressColor = ContextCompat.getColor(context,
30 | R.color.colorPrimary
31 | )
32 | private var progressTextColor = ContextCompat.getColor(context,
33 | R.color.colorPrimary
34 | )
35 | private var progressRadius = dp2px(21f)
36 | private var progressWidth = dp2px(2f)
37 | // 进度条背景
38 | private var progressBgPaint = Paint()
39 | private var progressBgColor = ContextCompat.getColor(context,
40 | R.color.progressBgColor
41 | )
42 | // 进度条文字
43 | private var progressTextPaint = Paint()
44 | private var progressStr = ""
45 | // 圆心坐标
46 | private var circleX = 0f
47 | private var circleY = 0f
48 | // 字的宽高
49 | private var mTxtWidth: Float = 0.toFloat()
50 | private var mTxtHeight: Float = 0.toFloat()
51 | // 总进度
52 | private val mTotalProgress = 100
53 | // 当前进度
54 | private var mProgress = 0
55 | // 默认宽高
56 | private var mWidth = dp2px(60f).toInt()
57 | private var mHeight = dp2px(60f).toInt()
58 |
59 | init {
60 | View(context, attrs)
61 | // 获取自定义的属性
62 | initAttrs(context, attrs)
63 | initVariable()
64 | }
65 |
66 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
67 | super.onMeasure(widthMeasureSpec, heightMeasureSpec)
68 | mWidth = getMySize(widthMeasureSpec, mWidth)
69 | mHeight = getMySize(heightMeasureSpec, mHeight)
70 | }
71 |
72 | private fun getMySize(measureSpec: Int, defaultSize: Int): Int {
73 | val specMode = MeasureSpec.getMode(measureSpec)
74 | val specSize = MeasureSpec.getSize(measureSpec)
75 | return when (specMode) {
76 | // 确切大小,所以将得到的尺寸给view
77 | MeasureSpec.EXACTLY -> specSize
78 | // 默认值为 xxx px,此处要结合父控件给子控件的最多大小(要不然会填充父控件),所以采用最小值
79 | MeasureSpec.AT_MOST -> min(defaultSize, specSize)
80 | else -> defaultSize
81 | }
82 | }
83 |
84 | //属性
85 | private fun initAttrs(context: Context, attrs: AttributeSet?) {
86 | if (attrs == null) return
87 | val typeArray = context.theme.obtainStyledAttributes(
88 | attrs, R.styleable.TasksCompletedView, 0, 0
89 | )
90 | // 内圆
91 | circleColor = typeArray.getColor(R.styleable.TasksCompletedView_circleColor, circleColor)
92 | circleRadius = typeArray.getDimension(R.styleable.TasksCompletedView_radius, circleRadius)
93 | // 进度条
94 | progressWidth =
95 | typeArray.getDimension(R.styleable.TasksCompletedView_progressWidth, progressWidth)
96 | progressColor =
97 | typeArray.getColor(R.styleable.TasksCompletedView_progressColor, progressColor)
98 | progressTextColor =
99 | typeArray.getColor(R.styleable.TasksCompletedView_progressTextColor, progressTextColor)
100 | progressBgColor =
101 | typeArray.getColor(R.styleable.TasksCompletedView_progressBgColor, progressBgColor)
102 | progressRadius = circleRadius + progressWidth / 2
103 | }
104 |
105 | // 初始化画笔
106 | private fun initVariable() {
107 | // 内圆
108 | circlePaint.isAntiAlias = true
109 | circlePaint.color = circleColor
110 | circlePaint.style = Paint.Style.FILL
111 | circlePaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_OVER)
112 | // 进度条背景
113 | progressBgPaint.isAntiAlias = true
114 | progressBgPaint.color = progressBgColor
115 | progressBgPaint.style = Paint.Style.STROKE
116 | progressBgPaint.strokeWidth = progressWidth
117 | // 进度条
118 | progressPaint.isAntiAlias = true
119 | progressPaint.color = progressColor
120 | progressPaint.style = Paint.Style.STROKE
121 | progressPaint.strokeWidth = progressWidth
122 | // 进度条文字
123 | progressTextPaint.isAntiAlias = true
124 | progressTextPaint.style = Paint.Style.FILL
125 | progressTextPaint.color = progressTextColor
126 | progressTextPaint.textSize = circleRadius * 0.7f
127 | progressTextPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_OVER)
128 | // 进行文字测量
129 | val fm = progressTextPaint.fontMetrics
130 | // 文字顶部 - 底部,然后进一法处理
131 | mTxtHeight = ceil((fm.descent - fm.ascent).toDouble()).toFloat()
132 | }
133 |
134 | //画图
135 | @SuppressLint("DrawAllocation")
136 | override fun onDraw(canvas: Canvas) {
137 | circleY = mHeight * 0.5f
138 | circleX = mWidth * 0.5f
139 |
140 | // 内圆
141 | canvas.drawCircle(circleX, circleY, circleRadius, circlePaint)
142 |
143 | // 进度条背景
144 | val rectF = RectF(
145 | circleX - progressRadius,
146 | circleY - progressRadius,
147 | progressRadius * 2 + (circleX - progressRadius),
148 | progressRadius * 2 + (circleY - progressRadius)
149 | )
150 |
151 | // 圆弧所在的椭圆对象、圆弧的起始角度、圆弧的角度、是否显示半径连线
152 | canvas.drawArc(rectF, 0f, 360f, false, progressBgPaint)
153 |
154 | // 进度条,矩形边框和背景是同一个,只是绘制角度范围不同
155 | canvas.drawArc(
156 | rectF,
157 | -90f,
158 | (mProgress * 360 / mTotalProgress).toFloat(),
159 | false,
160 | progressPaint
161 | )
162 |
163 | // 进度条文字
164 | mTxtWidth = progressTextPaint.measureText(progressStr, 0, progressStr.length)
165 | canvas.drawText(
166 | progressStr,
167 | circleX - mTxtWidth / 2,
168 | circleY + mTxtHeight * 0.3f,
169 | progressTextPaint
170 | )
171 | }
172 |
173 | // 设置进度
174 | fun setProgress(progress: Int) {
175 | mProgress = progress
176 | //重绘
177 | postInvalidate()
178 | }
179 |
180 | fun setProgress(progress: Int, progressStr: String) {
181 | mProgress = progress
182 | this.progressStr = progressStr
183 | postInvalidate()
184 | }
185 |
186 | fun getProgress(): Int = mProgress
187 |
188 | fun getProgressStr(): String = progressStr
189 |
190 | private fun dp2px(dpValue: Float): Float = DisplayUtils.dp2px(context, dpValue).toFloat()
191 |
192 | }
193 |
--------------------------------------------------------------------------------
/example/src/main/java/com/lzf/easyfloat/example/widget/ScaleImage.kt:
--------------------------------------------------------------------------------
1 | package com.lzf.easyfloat.example.widget
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.util.AttributeSet
6 | import android.view.MotionEvent
7 | import androidx.appcompat.widget.AppCompatImageView
8 |
9 | /**
10 | * @author: liuzhenfeng
11 | * @function: 通过消费触摸事件,监听手指滑动距离的变化,设置浮窗的大小
12 | * @date: 2019-08-05 09:55
13 | */
14 | class ScaleImage(context: Context, attrs: AttributeSet? = null) : AppCompatImageView(context, attrs) {
15 |
16 | private var touchDownX = 0f
17 | private var touchDownY = 0f
18 |
19 | var onScaledListener: OnScaledListener? = null
20 |
21 | interface OnScaledListener {
22 | fun onScaled(x: Float, y: Float, event: MotionEvent)
23 | }
24 |
25 | @SuppressLint("ClickableViewAccessibility")
26 | override fun onTouchEvent(event: MotionEvent?): Boolean {
27 |
28 | if (event == null) return super.onTouchEvent(event)
29 |
30 | // 屏蔽掉浮窗的事件拦截,仅由自身消费
31 | parent?.requestDisallowInterceptTouchEvent(true)
32 |
33 | when (event.action) {
34 | MotionEvent.ACTION_DOWN -> {
35 | touchDownX = event.x
36 | touchDownY = event.y
37 | }
38 |
39 | MotionEvent.ACTION_MOVE ->
40 | onScaledListener?.onScaled(event.x - touchDownX, event.y - touchDownY, event)
41 |
42 | }
43 | return true
44 | }
45 |
46 | }
--------------------------------------------------------------------------------
/example/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/example/src/main/res/drawable/corners_green.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/example/src/main/res/drawable/corners_left.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/example/src/main/res/drawable/corners_red.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/example/src/main/res/drawable/corners_right.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/example/src/main/res/drawable/corners_right_green.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/example/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
10 |
12 |
14 |
16 |
18 |
20 |
22 |
24 |
26 |
28 |
30 |
32 |
34 |
36 |
38 |
40 |
42 |
44 |
46 |
48 |
50 |
52 |
54 |
56 |
58 |
60 |
62 |
64 |
66 |
68 |
70 |
72 |
74 |
75 |
--------------------------------------------------------------------------------
/example/src/main/res/drawable/icon_scale.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/princekin-f/EasyFloat/1a65226084a46cafc7ebcb27f0a077aebefa9b0e/example/src/main/res/drawable/icon_scale.png
--------------------------------------------------------------------------------
/example/src/main/res/drawable/icon_x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/princekin-f/EasyFloat/1a65226084a46cafc7ebcb27f0a077aebefa9b0e/example/src/main/res/drawable/icon_x.png
--------------------------------------------------------------------------------
/example/src/main/res/layout/activity_border_test.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
16 |
17 |
24 |
25 |
32 |
33 |
--------------------------------------------------------------------------------
/example/src/main/res/layout/activity_java.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
18 |
19 |
--------------------------------------------------------------------------------
/example/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
13 |
14 |
17 |
18 |
22 |
23 |
31 |
32 |
36 |
37 |
41 |
42 |
46 |
47 |
51 |
52 |
56 |
57 |
58 |
59 |
67 |
68 |
72 |
73 |
77 |
78 |
82 |
83 |
87 |
88 |
92 |
93 |
94 |
102 |
103 |
107 |
108 |
112 |
113 |
117 |
118 |
122 |
123 |
127 |
128 |
129 |
137 |
138 |
142 |
143 |
147 |
148 |
152 |
153 |
157 |
158 |
162 |
163 |
164 |
169 |
170 |
175 |
176 |
181 |
182 |
183 |
184 |
--------------------------------------------------------------------------------
/example/src/main/res/layout/activity_second.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
19 |
20 |
25 |
26 |
31 |
32 |
37 |
--------------------------------------------------------------------------------
/example/src/main/res/layout/activity_swipe_test.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
16 |
17 |
--------------------------------------------------------------------------------
/example/src/main/res/layout/activity_third.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
19 |
20 |
25 |
26 |
31 |
32 |
--------------------------------------------------------------------------------
/example/src/main/res/layout/float_app.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
17 |
18 |
29 |
30 |
40 |
41 |
50 |
51 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/example/src/main/res/layout/float_app_scale.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
16 |
17 |
24 |
25 |
33 |
34 |
--------------------------------------------------------------------------------
/example/src/main/res/layout/float_border_test.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
15 |
16 |
24 |
25 |
--------------------------------------------------------------------------------
/example/src/main/res/layout/float_contract.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
12 |
20 |
21 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/example/src/main/res/layout/float_custom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
15 |
16 |
--------------------------------------------------------------------------------
/example/src/main/res/layout/float_edit.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
15 |
16 |
24 |
25 |
--------------------------------------------------------------------------------
/example/src/main/res/layout/float_seekbar.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
15 |
16 |
25 |
26 |
33 |
34 |
42 |
43 |
44 |
56 |
57 |
--------------------------------------------------------------------------------
/example/src/main/res/layout/float_swipe.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
19 |
20 |
--------------------------------------------------------------------------------
/example/src/main/res/layout/float_top_dialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
18 |
19 |
28 |
29 |
40 |
41 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/example/src/main/res/layout/item_simple_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
15 |
16 |
20 |
21 |
--------------------------------------------------------------------------------
/example/src/main/res/layout/popup_test.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
18 |
19 |
--------------------------------------------------------------------------------
/example/src/main/res/layout/test_float.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/example/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/princekin-f/EasyFloat/1a65226084a46cafc7ebcb27f0a077aebefa9b0e/example/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/princekin-f/EasyFloat/1a65226084a46cafc7ebcb27f0a077aebefa9b0e/example/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/example/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/princekin-f/EasyFloat/1a65226084a46cafc7ebcb27f0a077aebefa9b0e/example/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/princekin-f/EasyFloat/1a65226084a46cafc7ebcb27f0a077aebefa9b0e/example/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/example/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/princekin-f/EasyFloat/1a65226084a46cafc7ebcb27f0a077aebefa9b0e/example/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/princekin-f/EasyFloat/1a65226084a46cafc7ebcb27f0a077aebefa9b0e/example/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/example/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/princekin-f/EasyFloat/1a65226084a46cafc7ebcb27f0a077aebefa9b0e/example/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/princekin-f/EasyFloat/1a65226084a46cafc7ebcb27f0a077aebefa9b0e/example/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/example/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/princekin-f/EasyFloat/1a65226084a46cafc7ebcb27f0a077aebefa9b0e/example/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/princekin-f/EasyFloat/1a65226084a46cafc7ebcb27f0a077aebefa9b0e/example/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/example/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/example/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #008577
4 | #00574B
5 | #D81B60
6 |
7 | #FFFFFF
8 | #F3F3F3
9 | #EE2802
10 | #FFB40A
11 | #6CC354
12 | #00C7EE
13 | #EF0050
14 | #7B20A2
15 | #DD000000
16 | #999999
17 |
18 | #00000000
19 | #E0E8EC
20 | #d50f09
21 | #3D8EC0CD
22 | #E6FFFFFF
23 | #989898
24 | #242424
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | EasyFloat
3 |
4 |
--------------------------------------------------------------------------------
/example/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
17 |
18 |
28 |
29 |
37 |
38 |
--------------------------------------------------------------------------------
/example/src/test/java/com/lzf/example/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.lzf.example
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
22 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/princekin-f/EasyFloat/1a65226084a46cafc7ebcb27f0a077aebefa9b0e/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Dec 11 22:15:09 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-6.5-bin.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/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 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
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 Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/readme/Alipay.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/princekin-f/EasyFloat/1a65226084a46cafc7ebcb27f0a077aebefa9b0e/readme/Alipay.jpeg
--------------------------------------------------------------------------------
/readme/BoarderAndSlide.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/princekin-f/EasyFloat/1a65226084a46cafc7ebcb27f0a077aebefa9b0e/readme/BoarderAndSlide.gif
--------------------------------------------------------------------------------
/readme/Callbacks.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/princekin-f/EasyFloat/1a65226084a46cafc7ebcb27f0a077aebefa9b0e/readme/Callbacks.gif
--------------------------------------------------------------------------------
/readme/DragUtils.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/princekin-f/EasyFloat/1a65226084a46cafc7ebcb27f0a077aebefa9b0e/readme/DragUtils.gif
--------------------------------------------------------------------------------
/readme/EasyFloatGroup.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/princekin-f/EasyFloat/1a65226084a46cafc7ebcb27f0a077aebefa9b0e/readme/EasyFloatGroup.jpeg
--------------------------------------------------------------------------------
/readme/WeChatPay.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/princekin-f/EasyFloat/1a65226084a46cafc7ebcb27f0a077aebefa9b0e/readme/WeChatPay.jpeg
--------------------------------------------------------------------------------
/readme/downloadImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/princekin-f/EasyFloat/1a65226084a46cafc7ebcb27f0a077aebefa9b0e/readme/downloadImage.png
--------------------------------------------------------------------------------
/readme/权限申请.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/princekin-f/EasyFloat/1a65226084a46cafc7ebcb27f0a077aebefa9b0e/readme/权限申请.gif
--------------------------------------------------------------------------------
/readme/浮窗缩放.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/princekin-f/EasyFloat/1a65226084a46cafc7ebcb27f0a077aebefa9b0e/readme/浮窗缩放.gif
--------------------------------------------------------------------------------
/readme/系统浮窗.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/princekin-f/EasyFloat/1a65226084a46cafc7ebcb27f0a077aebefa9b0e/readme/系统浮窗.gif
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':example', ':easyfloat'
2 |
--------------------------------------------------------------------------------