The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── .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 <a href="http://d.android.com/tools/testing">Testing documentation</a>
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 | <manifest xmlns:android="http://schemas.android.com/apk/res/android"
 2 |         package="com.lzf.easyfloat">
 3 | 
 4 |     <application>
 5 |         <provider
 6 |                 android:name="com.lzf.easyfloat.EasyFloatInitializer"
 7 |                 android:authorities="${applicationId}.EasyFloatInitializer"
 8 |                 android:exported="false"
 9 |                 android:multiprocess="true" />
10 |     </application>
11 | 
12 | </manifest>
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<String>?,
26 |         selection: String?,
27 |         selectionArgs: Array<String>?,
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<String>?): Int = 0
36 | 
37 |     override fun update(
38 |         uri: Uri,
39 |         values: ContentValues?,
40 |         selection: String?,
41 |         selectionArgs: Array<String>?
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<Int, Int, Boolean> {
 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<String, FloatingWindowHelper>()
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<Int, Int> = Pair(0, 0),
52 |     // 固定的初始坐标,左上角坐标
53 |     var locationPair: Pair<Int, Int> = 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<String> = 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<Activity>? = 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<Activity>(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 | <?xml version="1.0" encoding="utf-8"?>
 2 | <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
 3 |     <item>
 4 |         <shape
 5 |                 android:innerRadius="9dp"
 6 |                 android:shape="ring"
 7 |                 android:thickness="1dp"
 8 |                 android:useLevel="false">
 9 |             <stroke
10 |                     android:width="1dp"
11 |                     android:color="#ffffff" />
12 |         </shape>
13 |     </item>
14 |     <item>
15 |         <shape
16 |                 android:innerRadius="16dp"
17 |                 android:shape="ring"
18 |                 android:thickness="1dp"
19 |                 android:useLevel="false">
20 |             <stroke
21 |                     android:width="1dp"
22 |                     android:color="#ffffff" />
23 |         </shape>
24 |     </item>
25 | </layer-list>


--------------------------------------------------------------------------------
/easyfloat/src/main/res/drawable/add_selected.xml:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="utf-8"?>
 2 | <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
 3 |     <item>
 4 |         <shape
 5 |                 android:innerRadius="9dp"
 6 |                 android:shape="ring"
 7 |                 android:thickness="1dp"
 8 |                 android:useLevel="false">
 9 |             <stroke
10 |                     android:width="1dp"
11 |                     android:color="#ffffff" />
12 |         </shape>
13 |     </item>
14 |     <item>
15 |         <shape
16 |                 android:innerRadius="20dp"
17 |                 android:shape="ring"
18 |                 android:thickness="1dp"
19 |                 android:useLevel="false">
20 |             <stroke
21 |                     android:width="1dp"
22 |                     android:color="#ffffff" />
23 |         </shape>
24 |     </item>
25 | </layer-list>


--------------------------------------------------------------------------------
/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 | <?xml version="1.0" encoding="utf-8"?>
 2 | <com.lzf.easyfloat.widget.DefaultAddView xmlns:android="http://schemas.android.com/apk/res/android"
 3 |         android:layout_width="160dp"
 4 |         android:layout_height="160dp"
 5 |         android:clipChildren="false">
 6 | 
 7 |     <TextView
 8 |             android:id="@+id/tv_add"
 9 |             android:layout_width="wrap_content"
10 |             android:layout_height="wrap_content"
11 |             android:layout_alignParentBottom="true"
12 |             android:layout_centerHorizontal="true"
13 |             android:layout_marginBottom="24dp"
14 |             android:gravity="center"
15 |             android:paddingStart="26dp"
16 |             android:text="@string/add_floating_window"
17 |             android:textColor="#FFFFFF"
18 |             android:textSize="12sp" />
19 | 
20 |     <ImageView
21 |             android:id="@+id/iv_add"
22 |             android:layout_width="70dp"
23 |             android:layout_height="44dp"
24 |             android:layout_above="@id/tv_add"
25 |             android:layout_centerHorizontal="true"
26 |             android:layout_marginBottom="6dp"
27 |             android:paddingStart="26dp"
28 |             android:src="@drawable/add_normal" />
29 | 
30 | </com.lzf.easyfloat.widget.DefaultAddView>
31 | 


--------------------------------------------------------------------------------
/easyfloat/src/main/res/layout/default_close_layout.xml:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="utf-8"?>
 2 | <com.lzf.easyfloat.widget.DefaultCloseView xmlns:android="http://schemas.android.com/apk/res/android"
 3 |     xmlns:app="http://schemas.android.com/apk/res-auto"
 4 |     android:layout_width="match_parent"
 5 |     android:layout_height="110dp"
 6 |     android:paddingHorizontal="50dp"
 7 |     app:closeShapeType="oval"
 8 |     app:inRangeColor="#99FF0000"
 9 |     app:normalColor="#99000000"
10 |     app:zoomSize="4dp">
11 | 
12 |     <TextView
13 |         android:id="@+id/tv_delete"
14 |         android:layout_width="wrap_content"
15 |         android:layout_height="wrap_content"
16 |         android:layout_alignParentBottom="true"
17 |         android:layout_centerHorizontal="true"
18 |         android:layout_marginBottom="24dp"
19 |         android:gravity="center"
20 |         android:text="@string/delete_floating_window"
21 |         android:textColor="#FFFFFF"
22 |         android:textSize="12sp" />
23 | 
24 |     <ImageView
25 |         android:id="@+id/iv_delete"
26 |         android:layout_width="20dp"
27 |         android:layout_height="20dp"
28 |         android:layout_above="@id/tv_delete"
29 |         android:layout_centerHorizontal="true"
30 |         android:layout_marginBottom="6dp"
31 |         android:src="@drawable/icon_delete_normal" />
32 | 
33 | </com.lzf.easyfloat.widget.DefaultCloseView>
34 | 


--------------------------------------------------------------------------------
/easyfloat/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="UTF-8"?>
 2 | <resources>
 3 | 
 4 |     <!--圆弧进度条-->
 5 |     <declare-styleable name="TasksCompletedView">
 6 |         <!--内圆半径-->
 7 |         <attr name="radius" format="dimension" />
 8 |         <!--内圆颜色-->
 9 |         <attr name="circleColor" format="color" />
10 |         <!--进度条宽度-->
11 |         <attr name="progressWidth" format="dimension" />
12 |         <!--进度条颜色-->
13 |         <attr name="progressColor" format="color" />
14 |         <!--进度条背景色-->
15 |         <attr name="progressBgColor" format="color" />
16 |         <!--进度条中间的文字-->
17 |         <attr name="progressText" format="string" />
18 |         <!--进度条中间的文字大小-->
19 |         <attr name="progressTextSize" format="dimension" />
20 |         <!--进度条中间的文字颜色-->
21 |         <attr name="progressTextColor" format="color" />
22 |     </declare-styleable>
23 | 
24 |     <declare-styleable name="CircleLoadingView">
25 |         <!--圆弧宽度-->
26 |         <attr name="arcWidth" format="dimension" />
27 |         <!--加载动画的颜色-->
28 |         <attr name="loadingColor" format="color" />
29 |         <!--环形圆点的数量-->
30 |         <attr name="dotSize" format="integer" />
31 |         <!--圆环转动一周的时间-->
32 |         <attr name="durationTime" format="float" />
33 |         <!--每周期圆点旋转的角度-->
34 |         <attr name="dotAngle" format="float" />
35 |     </declare-styleable>
36 | 
37 |     <declare-styleable name="DefaultCloseView">
38 |         <!--区域默认颜色-->
39 |         <attr name="normalColor" format="color" />
40 |         <!--区域选中颜色-->
41 |         <attr name="inRangeColor" format="color" />
42 |         <!--区域形状-->
43 |         <attr name="closeShapeType" format="enum">
44 |             <enum name="oval" value="0" />
45 |             <enum name="rect" value="1" />
46 |             <enum name="circle" value="2" />
47 |         </attr>
48 |         <!--选中切换时的缩放大小-->
49 |         <attr name="zoomSize" format="dimension" />
50 |     </declare-styleable>
51 | 
52 | </resources>


--------------------------------------------------------------------------------
/easyfloat/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 | <resources>
2 |     <string name="app_name">EasyFloat</string>
3 |     <string name="add_floating_window">浮窗</string>
4 |     <string name="delete_floating_window">删除浮窗</string>
5 | </resources>
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 | <?xml version="1.0" encoding="utf-8"?>
 2 | <manifest xmlns:android="http://schemas.android.com/apk/res/android"
 3 |         xmlns:tools="http://schemas.android.com/tools"
 4 |         package="com.lzf.easyfloat.example">
 5 | 
 6 |     <!--系统悬浮窗权限-->
 7 |     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
 8 |     <!--手机震动权限(EasyFloat不需要)-->
 9 |     <uses-permission android:name="android.permission.VIBRATE" />
10 | 
11 |     <application
12 |             android:name=".App"
13 |             android:allowBackup="true"
14 |             android:icon="@mipmap/ic_launcher"
15 |             android:label="@string/app_name"
16 |             android:roundIcon="@mipmap/ic_launcher_round"
17 |             android:supportsRtl="true"
18 |             android:theme="@style/AppThemeNoActionBar"
19 |             tools:ignore="GoogleAppIndexingWarning">
20 |         <activity
21 |                 android:name=".activity.MainActivity"
22 |                 android:configChanges="keyboardHidden|orientation|screenSize"
23 |                 android:launchMode="standard"
24 |                 android:theme="@style/AppTheme">
25 |             <intent-filter>
26 |                 <action android:name="android.intent.action.MAIN" />
27 | 
28 |                 <category android:name="android.intent.category.LAUNCHER" />
29 |             </intent-filter>
30 |         </activity>
31 |         <activity android:name=".activity.SecondActivity" />
32 |         <activity android:name=".activity.JavaTestActivity" />
33 |         <activity android:name=".activity.SwipeTestActivity" />
34 |         <activity android:name=".activity.BorderTestActivity" />
35 | 
36 |     </application>
37 | 
38 | </manifest>


--------------------------------------------------------------------------------
/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<String>,
22 |     private val resourceId: Int = R.layout.item_simple_list
23 | ) : ArrayAdapter<String>(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 <reified T> 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<TextView>(com.lzf.easyfloat.R.id.tv_delete).text =
 77 |                     if (inRange) "松手删除" else "删除浮窗"
 78 | 
 79 |                 view.findViewById<ImageView>(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<TextView>(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<ImageView>(R.id.iv_logo)
35 |                 val ivLogo2 = it.findViewById<ImageView>(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<JavaTestActivity>(this) }
 76 | 
 77 |         changeBackground.setOnClickListener {
 78 |             EasyFloat.getFloatView()?.apply {
 79 |                 findViewById<RelativeLayout>(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<RelativeLayout>(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<EditText>(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<TextView>(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<ImageView>(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<ConstraintLayout>(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<ConstraintLayout>(R.id.cl_content)
84 |                     .setBackgroundResource(R.drawable.corners_red)
85 |             }
86 | 
87 |             dragEnd {
88 |                 it.findViewById<ConstraintLayout>(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 : Any> 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<DrawingCircle>()
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 | <vector xmlns:android="http://schemas.android.com/apk/res/android"
 2 |         xmlns:aapt="http://schemas.android.com/aapt"
 3 |         android:width="108dp"
 4 |         android:height="108dp"
 5 |         android:viewportHeight="108"
 6 |         android:viewportWidth="108">
 7 |     <path
 8 |             android:fillType="evenOdd"
 9 |             android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
10 |             android:strokeColor="#00000000"
11 |             android:strokeWidth="1">
12 |         <aapt:attr name="android:fillColor">
13 |             <gradient
14 |                     android:endX="78.5885"
15 |                     android:endY="90.9159"
16 |                     android:startX="48.7653"
17 |                     android:startY="61.0927"
18 |                     android:type="linear">
19 |                 <item
20 |                         android:color="#44000000"
21 |                         android:offset="0.0"/>
22 |                 <item
23 |                         android:color="#00000000"
24 |                         android:offset="1.0"/>
25 |             </gradient>
26 |         </aapt:attr>
27 |     </path>
28 |     <path
29 |             android:fillColor="#FFFFFF"
30 |             android:fillType="nonZero"
31 |             android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
32 |             android:strokeColor="#00000000"
33 |             android:strokeWidth="1"/>
34 | </vector>
35 | 


--------------------------------------------------------------------------------
/example/src/main/res/drawable/corners_green.xml:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="utf-8"?>
 2 | <shape xmlns:android="http://schemas.android.com/apk/res/android"
 3 |         android:shape="rectangle">
 4 | 
 5 |     <solid android:color="@color/green" />
 6 | 
 7 |     <corners
 8 |             android:bottomLeftRadius="30dp"
 9 |             android:bottomRightRadius="30dp"
10 |             android:topLeftRadius="30dp"
11 |             android:topRightRadius="30dp" />
12 | 
13 | </shape>
14 | 


--------------------------------------------------------------------------------
/example/src/main/res/drawable/corners_left.xml:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="utf-8"?>
 2 | <shape xmlns:android="http://schemas.android.com/apk/res/android"
 3 |         android:shape="rectangle">
 4 | 
 5 |     <solid android:color="@color/violet" />
 6 | 
 7 |     <corners
 8 |             android:bottomLeftRadius="30dp"
 9 |             android:bottomRightRadius="0dp"
10 |             android:topLeftRadius="30dp"
11 |             android:topRightRadius="0dp" />
12 | 
13 | </shape>
14 | 


--------------------------------------------------------------------------------
/example/src/main/res/drawable/corners_red.xml:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="utf-8"?>
 2 | <shape xmlns:android="http://schemas.android.com/apk/res/android"
 3 |         android:shape="rectangle">
 4 | 
 5 |     <solid android:color="@color/pinkRed" />
 6 | 
 7 |     <corners
 8 |             android:bottomLeftRadius="30dp"
 9 |             android:bottomRightRadius="30dp"
10 |             android:topLeftRadius="30dp"
11 |             android:topRightRadius="30dp" />
12 | 
13 | </shape>
14 | 


--------------------------------------------------------------------------------
/example/src/main/res/drawable/corners_right.xml:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="utf-8"?>
 2 | <shape xmlns:android="http://schemas.android.com/apk/res/android"
 3 |         android:shape="rectangle">
 4 | 
 5 |     <solid android:color="@color/violet" />
 6 | 
 7 |     <corners
 8 |             android:bottomLeftRadius="0dp"
 9 |             android:bottomRightRadius="30dp"
10 |             android:topLeftRadius="0dp"
11 |             android:topRightRadius="30dp" />
12 | 
13 | </shape>
14 | 


--------------------------------------------------------------------------------
/example/src/main/res/drawable/corners_right_green.xml:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="utf-8"?>
 2 | <shape xmlns:android="http://schemas.android.com/apk/res/android"
 3 |         android:shape="rectangle">
 4 | 
 5 |     <solid android:color="@color/green" />
 6 | 
 7 |     <corners
 8 |             android:bottomLeftRadius="30dp"
 9 |             android:bottomRightRadius="0dp"
10 |             android:topLeftRadius="30dp"
11 |             android:topRightRadius="0dp" />
12 | 
13 | </shape>
14 | 


--------------------------------------------------------------------------------
/example/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="utf-8"?>
 2 | <vector
 3 |         xmlns:android="http://schemas.android.com/apk/res/android"
 4 |         android:height="108dp"
 5 |         android:width="108dp"
 6 |         android:viewportHeight="108"
 7 |         android:viewportWidth="108">
 8 |     <path android:fillColor="#008577"
 9 |           android:pathData="M0,0h108v108h-108z"/>
10 |     <path android:fillColor="#00000000" android:pathData="M9,0L9,108"
11 |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
12 |     <path android:fillColor="#00000000" android:pathData="M19,0L19,108"
13 |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
14 |     <path android:fillColor="#00000000" android:pathData="M29,0L29,108"
15 |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
16 |     <path android:fillColor="#00000000" android:pathData="M39,0L39,108"
17 |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
18 |     <path android:fillColor="#00000000" android:pathData="M49,0L49,108"
19 |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
20 |     <path android:fillColor="#00000000" android:pathData="M59,0L59,108"
21 |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
22 |     <path android:fillColor="#00000000" android:pathData="M69,0L69,108"
23 |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
24 |     <path android:fillColor="#00000000" android:pathData="M79,0L79,108"
25 |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
26 |     <path android:fillColor="#00000000" android:pathData="M89,0L89,108"
27 |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
28 |     <path android:fillColor="#00000000" android:pathData="M99,0L99,108"
29 |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
30 |     <path android:fillColor="#00000000" android:pathData="M0,9L108,9"
31 |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
32 |     <path android:fillColor="#00000000" android:pathData="M0,19L108,19"
33 |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
34 |     <path android:fillColor="#00000000" android:pathData="M0,29L108,29"
35 |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
36 |     <path android:fillColor="#00000000" android:pathData="M0,39L108,39"
37 |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
38 |     <path android:fillColor="#00000000" android:pathData="M0,49L108,49"
39 |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
40 |     <path android:fillColor="#00000000" android:pathData="M0,59L108,59"
41 |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
42 |     <path android:fillColor="#00000000" android:pathData="M0,69L108,69"
43 |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
44 |     <path android:fillColor="#00000000" android:pathData="M0,79L108,79"
45 |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
46 |     <path android:fillColor="#00000000" android:pathData="M0,89L108,89"
47 |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
48 |     <path android:fillColor="#00000000" android:pathData="M0,99L108,99"
49 |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
50 |     <path android:fillColor="#00000000" android:pathData="M19,29L89,29"
51 |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
52 |     <path android:fillColor="#00000000" android:pathData="M19,39L89,39"
53 |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
54 |     <path android:fillColor="#00000000" android:pathData="M19,49L89,49"
55 |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
56 |     <path android:fillColor="#00000000" android:pathData="M19,59L89,59"
57 |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
58 |     <path android:fillColor="#00000000" android:pathData="M19,69L89,69"
59 |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
60 |     <path android:fillColor="#00000000" android:pathData="M19,79L89,79"
61 |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
62 |     <path android:fillColor="#00000000" android:pathData="M29,19L29,89"
63 |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
64 |     <path android:fillColor="#00000000" android:pathData="M39,19L39,89"
65 |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
66 |     <path android:fillColor="#00000000" android:pathData="M49,19L49,89"
67 |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
68 |     <path android:fillColor="#00000000" android:pathData="M59,19L59,89"
69 |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
70 |     <path android:fillColor="#00000000" android:pathData="M69,19L69,89"
71 |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
72 |     <path android:fillColor="#00000000" android:pathData="M79,19L79,89"
73 |           android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
74 | </vector>
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 | <?xml version="1.0" encoding="utf-8"?>
 2 | <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3 |         xmlns:app="http://schemas.android.com/apk/res-auto"
 4 |         android:layout_width="match_parent"
 5 |         android:layout_height="match_parent"
 6 |         android:background="@color/white2">
 7 | 
 8 |     <View
 9 |             android:id="@+id/view_bg"
10 |             android:layout_width="match_parent"
11 |             android:layout_height="match_parent"
12 |             android:layout_marginHorizontal="30dp"
13 |             android:layout_marginTop="50dp"
14 |             android:layout_marginBottom="180dp"
15 |             android:background="@color/violet" />
16 | 
17 |     <TextView
18 |             android:id="@+id/tv_show"
19 |             style="@style/TextStyle2"
20 |             android:layout_marginTop="20dp"
21 |             android:text="Show Border Test"
22 |             app:layout_constraintStart_toStartOf="parent"
23 |             app:layout_constraintTop_toBottomOf="@id/view_bg" />
24 | 
25 |     <TextView
26 |             android:id="@+id/tv_dismiss"
27 |             style="@style/TextStyle2"
28 |             android:layout_marginTop="20dp"
29 |             android:text="Dismiss Border Test"
30 |             app:layout_constraintStart_toStartOf="parent"
31 |             app:layout_constraintTop_toBottomOf="@id/tv_show" />
32 | 
33 | </androidx.constraintlayout.widget.ConstraintLayout>


--------------------------------------------------------------------------------
/example/src/main/res/layout/activity_java.xml:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="utf-8"?>
 2 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3 |         android:layout_width="match_parent"
 4 |         android:layout_height="match_parent"
 5 |         android:orientation="vertical">
 6 | 
 7 |     <TextView
 8 |             android:id="@+id/testJava"
 9 |             style="@style/TextStyle2"
10 |             android:layout_marginTop="120dp"
11 |             android:text="使用Java中创建一个浮窗" />
12 | 
13 |     <TextView
14 |             android:id="@+id/tvCloseFloat"
15 |             style="@style/TextStyle2"
16 |             android:layout_marginTop="20dp"
17 |             android:text="关闭浮窗" />
18 | 
19 | </LinearLayout>


--------------------------------------------------------------------------------
/example/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
  1 | <?xml version="1.0" encoding="utf-8"?>
  2 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3 |         xmlns:tools="http://schemas.android.com/tools"
  4 |         android:layout_width="match_parent"
  5 |         android:layout_height="match_parent"
  6 |         android:background="@color/white2"
  7 |         android:orientation="vertical"
  8 |         tools:context="com.lzf.easyfloat.example.activity.MainActivity">
  9 | 
 10 |     <androidx.appcompat.widget.ActionMenuView
 11 |             android:layout_width="match_parent"
 12 |             android:layout_height="wrap_content" />
 13 | 
 14 |     <androidx.core.widget.NestedScrollView
 15 |             android:layout_width="match_parent"
 16 |             android:layout_height="match_parent">
 17 | 
 18 |         <LinearLayout
 19 |                 android:layout_width="match_parent"
 20 |                 android:layout_height="match_parent"
 21 |                 android:orientation="vertical">
 22 | 
 23 |             <TextView
 24 |                     android:layout_width="wrap_content"
 25 |                     android:layout_height="wrap_content"
 26 |                     android:layout_marginStart="14dp"
 27 |                     android:layout_marginTop="8dp"
 28 |                     android:layout_marginBottom="8dp"
 29 |                     android:text="当页浮窗1(Activity Float,状态栏沉浸)"
 30 |                     android:textSize="13sp" />
 31 | 
 32 |             <LinearLayout
 33 |                     android:layout_width="match_parent"
 34 |                     android:layout_height="46dp"
 35 |                     android:orientation="horizontal">
 36 | 
 37 |                 <TextView
 38 |                         android:id="@+id/open1"
 39 |                         style="@style/TextStyle"
 40 |                         android:text="open" />
 41 | 
 42 |                 <TextView
 43 |                         android:id="@+id/hide1"
 44 |                         style="@style/TextStyle"
 45 |                         android:text="hide" />
 46 | 
 47 |                 <TextView
 48 |                         android:id="@+id/show1"
 49 |                         style="@style/TextStyle"
 50 |                         android:text="show" />
 51 | 
 52 |                 <TextView
 53 |                         android:id="@+id/dismiss1"
 54 |                         style="@style/TextStyle"
 55 |                         android:text="dismiss" />
 56 | 
 57 |             </LinearLayout>
 58 | 
 59 |             <TextView
 60 |                     android:layout_width="wrap_content"
 61 |                     android:layout_height="wrap_content"
 62 |                     android:layout_marginStart="14dp"
 63 |                     android:layout_marginTop="8dp"
 64 |                     android:layout_marginBottom="8dp"
 65 |                     android:text="当页浮窗2"
 66 |                     android:textSize="13sp" />
 67 | 
 68 |             <LinearLayout
 69 |                     android:layout_width="match_parent"
 70 |                     android:layout_height="46dp"
 71 |                     android:orientation="horizontal">
 72 | 
 73 |                 <TextView
 74 |                         android:id="@+id/open2"
 75 |                         style="@style/TextStyle"
 76 |                         android:text="open" />
 77 | 
 78 |                 <TextView
 79 |                         android:id="@+id/hide2"
 80 |                         style="@style/TextStyle"
 81 |                         android:text="hide" />
 82 | 
 83 |                 <TextView
 84 |                         android:id="@+id/show2"
 85 |                         style="@style/TextStyle"
 86 |                         android:text="show" />
 87 | 
 88 |                 <TextView
 89 |                         android:id="@+id/dismiss2"
 90 |                         style="@style/TextStyle"
 91 |                         android:text="dismiss" />
 92 |             </LinearLayout>
 93 | 
 94 |             <TextView
 95 |                     android:layout_width="wrap_content"
 96 |                     android:layout_height="wrap_content"
 97 |                     android:layout_marginStart="14dp"
 98 |                     android:layout_marginTop="8dp"
 99 |                     android:layout_marginBottom="8dp"
100 |                     android:text="系统浮窗1(全局显示,状态栏沉浸)"
101 |                     android:textSize="13sp" />
102 | 
103 |             <LinearLayout
104 |                     android:layout_width="match_parent"
105 |                     android:layout_height="46dp"
106 |                     android:orientation="horizontal">
107 | 
108 |                 <TextView
109 |                         android:id="@+id/open3"
110 |                         style="@style/TextStyle"
111 |                         android:text="open" />
112 | 
113 |                 <TextView
114 |                         android:id="@+id/hide3"
115 |                         style="@style/TextStyle"
116 |                         android:text="hide" />
117 | 
118 |                 <TextView
119 |                         android:id="@+id/show3"
120 |                         style="@style/TextStyle"
121 |                         android:text="show" />
122 | 
123 |                 <TextView
124 |                         android:id="@+id/dismiss3"
125 |                         style="@style/TextStyle"
126 |                         android:text="dismiss" />
127 |             </LinearLayout>
128 | 
129 |             <TextView
130 |                     android:layout_width="wrap_content"
131 |                     android:layout_height="wrap_content"
132 |                     android:layout_marginStart="14dp"
133 |                     android:layout_marginTop="8dp"
134 |                     android:layout_marginBottom="8dp"
135 |                     android:text="系统浮窗2,仅前台显示"
136 |                     android:textSize="13sp" />
137 | 
138 |             <LinearLayout
139 |                     android:layout_width="match_parent"
140 |                     android:layout_height="46dp"
141 |                     android:orientation="horizontal">
142 | 
143 |                 <TextView
144 |                         android:id="@+id/open4"
145 |                         style="@style/TextStyle"
146 |                         android:text="open" />
147 | 
148 |                 <TextView
149 |                         android:id="@+id/hide4"
150 |                         style="@style/TextStyle"
151 |                         android:text="hide" />
152 | 
153 |                 <TextView
154 |                         android:id="@+id/show4"
155 |                         style="@style/TextStyle"
156 |                         android:text="show" />
157 | 
158 |                 <TextView
159 |                         android:id="@+id/dismiss4"
160 |                         style="@style/TextStyle"
161 |                         android:text="dismiss" />
162 |             </LinearLayout>
163 | 
164 |             <TextView
165 |                     android:id="@+id/openSecond"
166 |                     style="@style/TextStyle2"
167 |                     android:layout_marginTop="20dp"
168 |                     android:text="Open SecondActivity" />
169 | 
170 |             <TextView
171 |                     android:id="@+id/openSwipeTest"
172 |                     style="@style/TextStyle2"
173 |                     android:layout_marginTop="20dp"
174 |                     android:text="Open SeipeTestActivity" />
175 | 
176 |             <TextView
177 |                     android:id="@+id/openBorderTest"
178 |                     style="@style/TextStyle2"
179 |                     android:layout_marginTop="20dp"
180 |                     android:text="Open BorderTestActivity" />
181 |         </LinearLayout>
182 | 
183 |     </androidx.core.widget.NestedScrollView>
184 | </LinearLayout>


--------------------------------------------------------------------------------
/example/src/main/res/layout/activity_second.xml:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="utf-8"?>
 2 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3 |         android:layout_width="match_parent"
 4 |         android:layout_height="match_parent"
 5 |         android:background="@color/white2"
 6 |         android:orientation="vertical">
 7 | 
 8 |     <TextView
 9 |             android:id="@+id/tvShow"
10 |             style="@style/TextStyle2"
11 |             android:layout_marginTop="120dp"
12 |             android:text="Show" />
13 | 
14 |     <TextView
15 |             android:id="@+id/openEditTextFloat"
16 |             style="@style/TextStyle2"
17 |             android:layout_marginTop="20dp"
18 |             android:text="OpenEditTextFloat" />
19 | 
20 |     <TextView
21 |             android:id="@+id/openJavaTestActivity"
22 |             style="@style/TextStyle2"
23 |             android:layout_marginTop="20dp"
24 |             android:text="OpenJavaTestActivity" />
25 | 
26 |     <TextView
27 |             android:id="@+id/changeBackground"
28 |             style="@style/TextStyle2"
29 |             android:layout_marginTop="20dp"
30 |             android:text="改变首页系统浮窗1的背景" />
31 | 
32 |     <TextView
33 |             android:id="@+id/recoverBackground"
34 |             style="@style/TextStyle2"
35 |             android:layout_marginTop="20dp"
36 |             android:text="恢复首页系统浮窗1的背景" />
37 | </LinearLayout>


--------------------------------------------------------------------------------
/example/src/main/res/layout/activity_swipe_test.xml:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="utf-8"?>
 2 | <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3 |         xmlns:app="http://schemas.android.com/apk/res-auto"
 4 |         android:id="@+id/swipeBackLayout"
 5 |         android:layout_width="match_parent"
 6 |         android:layout_height="match_parent"
 7 |         android:background="@color/colorPrimaryDark">
 8 | 
 9 |     <TextView
10 |             android:layout_width="match_parent"
11 |             android:layout_height="match_parent"
12 |             android:textColor="@color/white"
13 |             android:gravity="center"
14 |             android:text="SwipeTest"
15 |             android:textSize="30sp" />
16 | 
17 | </androidx.constraintlayout.widget.ConstraintLayout>


--------------------------------------------------------------------------------
/example/src/main/res/layout/activity_third.xml:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="utf-8"?>
 2 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3 |         android:layout_width="match_parent"
 4 |         android:layout_height="match_parent"
 5 |         android:background="@color/white2"
 6 |         android:orientation="vertical">
 7 | 
 8 |     <TextView
 9 |             android:id="@+id/openEditTextFloat"
10 |             style="@style/TextStyle2"
11 |             android:layout_marginTop="120dp"
12 |             android:text="OpenEditTextFloat" />
13 | 
14 |     <TextView
15 |             android:id="@+id/openJavaTestActivity"
16 |             style="@style/TextStyle2"
17 |             android:layout_marginTop="20dp"
18 |             android:text="OpenJavaTestActivity" />
19 | 
20 |     <TextView
21 |             android:id="@+id/changeBackground"
22 |             style="@style/TextStyle2"
23 |             android:layout_marginTop="20dp"
24 |             android:text="改变首页系统浮窗1的背景" />
25 | 
26 |     <TextView
27 |             android:id="@+id/recoverBackground"
28 |             style="@style/TextStyle2"
29 |             android:layout_marginTop="20dp"
30 |             android:text="恢复首页系统浮窗1的背景" />
31 | 
32 | </LinearLayout>


--------------------------------------------------------------------------------
/example/src/main/res/layout/float_app.xml:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="utf-8"?>
 2 | <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3 |         xmlns:app="http://schemas.android.com/apk/res-auto"
 4 |         android:id="@+id/rlContent"
 5 |         android:layout_width="180dp"
 6 |         android:layout_height="wrap_content"
 7 |         android:background="@color/translucent"
 8 |         android:orientation="vertical">
 9 | 
10 |     <ImageView
11 |             android:id="@+id/ivClose"
12 |             android:layout_width="36dp"
13 |             android:layout_height="36dp"
14 |             android:layout_alignParentEnd="true"
15 |             android:padding="10dp"
16 |             android:src="@drawable/icon_x" />
17 | 
18 |     <com.lzf.easyfloat.example.widget.RoundProgressBar
19 |             android:id="@+id/roundProgressBar"
20 |             android:layout_width="60dp"
21 |             android:layout_height="60dp"
22 |             android:layout_marginStart="6dp"
23 |             android:layout_marginTop="22dp"
24 |             app:circleColor="@color/transparent"
25 |             app:progressColor="@color/colorPrimary"
26 |             app:progressTextColor="@color/white"
27 |             app:progressWidth="2dp"
28 |             app:radius="20dp" />
29 | 
30 |     <TextView
31 |             android:id="@+id/tvOpenMain"
32 |             android:layout_width="wrap_content"
33 |             android:layout_height="wrap_content"
34 |             android:layout_marginStart="2dp"
35 |             android:layout_marginTop="20dp"
36 |             android:layout_toEndOf="@id/roundProgressBar"
37 |             android:padding="5dp"
38 |             android:text="打开首页"
39 |             android:textColor="@color/white" />
40 | 
41 |     <CheckBox
42 |             android:id="@+id/checkbox"
43 |             android:layout_width="wrap_content"
44 |             android:layout_height="wrap_content"
45 |             android:layout_below="@id/tvOpenMain"
46 |             android:layout_toEndOf="@id/roundProgressBar"
47 |             android:checked="true"
48 |             android:text="是否可拖拽"
49 |             android:textColor="@color/white" />
50 | 
51 |     <SeekBar
52 |             android:id="@+id/seekBar"
53 |             android:layout_width="match_parent"
54 |             android:layout_height="wrap_content"
55 |             android:layout_below="@id/checkbox"
56 |             android:layout_marginTop="4dp"
57 |             android:layout_marginBottom="8dp"
58 |             android:max="100"
59 |             android:progress="66" />
60 | 
61 | <!--    <ListView-->
62 | <!--            android:id="@+id/lv_test"-->
63 | <!--            android:layout_width="match_parent"-->
64 | <!--            android:layout_height="120dp"-->
65 | <!--            android:layout_below="@id/seekBar" />-->
66 | 
67 | </RelativeLayout>


--------------------------------------------------------------------------------
/example/src/main/res/layout/float_app_scale.xml:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="utf-8"?>
 2 | <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3 |         android:id="@+id/rlContent"
 4 |         android:layout_width="180dp"
 5 |         android:layout_height="100dp"
 6 |         android:background="@color/translucent"
 7 |         android:orientation="vertical">
 8 | 
 9 |     <ImageView
10 |             android:id="@+id/ivClose"
11 |             android:layout_width="36dp"
12 |             android:layout_height="36dp"
13 |             android:layout_alignParentEnd="true"
14 |             android:padding="10dp"
15 |             android:src="@drawable/icon_x" />
16 | 
17 |     <TextView
18 |             android:layout_width="wrap_content"
19 |             android:layout_height="wrap_content"
20 |             android:layout_margin="20dp"
21 |             android:lineSpacingMultiplier="1.5"
22 |             android:text="仅前台显示\n过滤:SecondActivity"
23 |             android:textColor="@color/white" />
24 | 
25 |     <com.lzf.easyfloat.example.widget.ScaleImage
26 |             android:id="@+id/ivScale"
27 |             android:layout_width="36dp"
28 |             android:layout_height="36dp"
29 |             android:layout_alignParentEnd="true"
30 |             android:layout_alignParentBottom="true"
31 |             android:padding="8dp"
32 |             android:src="@drawable/icon_scale" />
33 | 
34 | </RelativeLayout>


--------------------------------------------------------------------------------
/example/src/main/res/layout/float_border_test.xml:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="utf-8"?>
 2 | <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3 |         xmlns:app="http://schemas.android.com/apk/res-auto"
 4 |         android:layout_width="wrap_content"
 5 |         android:layout_height="wrap_content">
 6 | 
 7 |     <ImageView
 8 |             android:id="@+id/iv_logo"
 9 |             android:layout_width="56dp"
10 |             android:layout_height="56dp"
11 |             android:src="@mipmap/ic_launcher_round"
12 |             app:layout_constraintBottom_toBottomOf="parent"
13 |             app:layout_constraintStart_toStartOf="parent"
14 |             app:layout_constraintTop_toTopOf="parent" />
15 | 
16 |     <ImageView
17 |             android:id="@+id/iv_logo2"
18 |             android:layout_width="56dp"
19 |             android:layout_height="56dp"
20 |             android:src="@mipmap/ic_launcher_round"
21 |             app:layout_constraintBottom_toBottomOf="parent"
22 |             app:layout_constraintStart_toEndOf="@id/iv_logo"
23 |             app:layout_constraintTop_toTopOf="parent" />
24 | 
25 | </androidx.constraintlayout.widget.ConstraintLayout>


--------------------------------------------------------------------------------
/example/src/main/res/layout/float_contract.xml:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="utf-8"?>
 2 | <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3 |         xmlns:app="http://schemas.android.com/apk/res-auto"
 4 |         android:layout_width="match_parent"
 5 |         android:layout_height="match_parent">
 6 | 
 7 |     <com.lzf.easyfloat.example.widget.BubbleSurfaceView
 8 |             android:id="@+id/bubbleSurfaceView"
 9 |             android:layout_width="match_parent"
10 |             android:layout_height="match_parent" />
11 | 
12 |     <androidx.cardview.widget.CardView
13 |             android:layout_width="match_parent"
14 |             android:layout_height="50dp"
15 |             android:layout_marginHorizontal="20dp"
16 |             android:layout_marginBottom="80dp"
17 |             app:cardBackgroundColor="@color/colorPrimaryDark"
18 |             app:cardCornerRadius="8dp"
19 |             app:layout_constraintBottom_toBottomOf="parent">
20 | 
21 |         <TextView
22 |                 android:id="@+id/tv_back"
23 |                 android:layout_width="match_parent"
24 |                 android:layout_height="match_parent"
25 |                 android:gravity="center"
26 |                 android:text="Back"
27 |                 android:textColor="@color/white"
28 |                 android:textSize="20sp" />
29 | 
30 |     </androidx.cardview.widget.CardView>
31 | 
32 | </androidx.constraintlayout.widget.ConstraintLayout>


--------------------------------------------------------------------------------
/example/src/main/res/layout/float_custom.xml:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="utf-8"?>
 2 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3 |         android:layout_width="wrap_content"
 4 |         android:layout_height="wrap_content">
 5 | 
 6 |     <TextView
 7 |             android:id="@+id/textView"
 8 |             android:layout_width="120dp"
 9 |             android:layout_height="60dp"
10 |             android:background="@drawable/corners_right_green"
11 |             android:gravity="center"
12 |             android:textColor="@color/white"
13 |             android:text="Activity Float"
14 |             android:textSize="16sp" />
15 | </LinearLayout>
16 | 


--------------------------------------------------------------------------------
/example/src/main/res/layout/float_edit.xml:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="utf-8"?>
 2 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3 |         android:layout_width="200dp"
 4 |         android:layout_height="wrap_content"
 5 |         android:background="@color/translucent"
 6 |         android:gravity="center_horizontal"
 7 |         android:orientation="vertical"
 8 |         android:padding="10dp">
 9 | 
10 |     <EditText
11 |             android:id="@+id/editText"
12 |             android:layout_width="match_parent"
13 |             android:layout_height="wrap_content"
14 |             android:textColor="@color/white" />
15 | 
16 |     <TextView
17 |             android:id="@+id/tvCloseFloat"
18 |             android:layout_width="wrap_content"
19 |             android:layout_height="wrap_content"
20 |             android:layout_marginTop="10dp"
21 |             android:padding="10dp"
22 |             android:text="关闭浮窗"
23 |             android:textColor="@color/white" />
24 | 
25 | </LinearLayout>


--------------------------------------------------------------------------------
/example/src/main/res/layout/float_seekbar.xml:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="utf-8"?>
 2 | <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3 |         xmlns:app="http://schemas.android.com/apk/res-auto"
 4 |         android:layout_width="wrap_content"
 5 |         android:layout_height="wrap_content"
 6 |         android:orientation="horizontal">
 7 | 
 8 |     <RelativeLayout
 9 |             android:id="@+id/layoutContent"
10 |             android:layout_width="100dp"
11 |             android:layout_height="wrap_content"
12 |             android:background="@color/translucent"
13 |             app:layout_constraintStart_toStartOf="parent"
14 |             app:layout_constraintTop_toTopOf="parent">
15 | 
16 |         <TextView
17 |                 android:id="@+id/tvProgress"
18 |                 android:layout_width="wrap_content"
19 |                 android:layout_height="wrap_content"
20 |                 android:layout_centerHorizontal="true"
21 |                 android:padding="10dp"
22 |                 android:text="20"
23 |                 android:textColor="@color/pinkRed"
24 |                 android:textSize="20sp" />
25 | 
26 |         <ImageView
27 |                 android:id="@+id/ivClose"
28 |                 android:layout_width="36dp"
29 |                 android:layout_height="36dp"
30 |                 android:layout_alignParentEnd="true"
31 |                 android:padding="10dp"
32 |                 android:src="@drawable/icon_x" />
33 | 
34 |         <SeekBar
35 |                 android:id="@+id/seekBar"
36 |                 android:layout_width="match_parent"
37 |                 android:layout_height="wrap_content"
38 |                 android:layout_below="@id/tvProgress"
39 |                 android:layout_marginBottom="5dp"
40 |                 android:max="100"
41 |                 android:progress="20" />
42 |     </RelativeLayout>
43 | 
44 |     <TextView
45 |             android:id="@+id/viewOther"
46 |             android:layout_width="wrap_content"
47 |             android:layout_height="wrap_content"
48 |             android:background="@color/translucent"
49 |             android:gravity="center"
50 |             android:paddingHorizontal="2dp"
51 |             android:paddingVertical="10dp"
52 |             android:text="简化模式"
53 |             android:textColor="@color/white"
54 |             app:layout_constraintStart_toEndOf="@id/layoutContent"
55 |             app:layout_constraintTop_toTopOf="parent" />
56 | 
57 | </androidx.constraintlayout.widget.ConstraintLayout>


--------------------------------------------------------------------------------
/example/src/main/res/layout/float_swipe.xml:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="utf-8"?>
 2 | <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3 |         xmlns:app="http://schemas.android.com/apk/res-auto"
 4 |         android:id="@+id/cl_content"
 5 |         android:layout_width="60dp"
 6 |         android:layout_height="60dp"
 7 |         android:background="@drawable/corners_left">
 8 | 
 9 |     <com.lzf.easyfloat.example.widget.CircleLoadingView
10 |             android:layout_width="match_parent"
11 |             android:layout_height="match_parent"
12 |             android:layout_gravity="center"
13 |             android:layout_margin="6dp"
14 |             android:padding="4dp"
15 |             app:layout_constraintBottom_toBottomOf="parent"
16 |             app:layout_constraintEnd_toEndOf="parent"
17 |             app:layout_constraintStart_toStartOf="parent"
18 |             app:layout_constraintTop_toTopOf="parent" />
19 | 
20 | </androidx.constraintlayout.widget.ConstraintLayout>


--------------------------------------------------------------------------------
/example/src/main/res/layout/float_top_dialog.xml:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="utf-8"?>
 2 | <androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
 3 |         xmlns:app="http://schemas.android.com/apk/res-auto"
 4 |         android:layout_width="match_parent"
 5 |         android:layout_height="wrap_content"
 6 |         android:layout_marginHorizontal="8dp"
 7 |         android:layout_marginTop="6dp"
 8 |         android:layout_marginBottom="4dp"
 9 |         app:cardBackgroundColor="@color/translucent"
10 |         app:cardCornerRadius="12dp"
11 |         app:cardElevation="4dp">
12 | 
13 |     <androidx.constraintlayout.widget.ConstraintLayout
14 |             android:layout_width="match_parent"
15 |             android:layout_height="wrap_content"
16 |             android:gravity="center"
17 |             android:paddingVertical="12dp">
18 | 
19 |         <ImageView
20 |                 android:id="@+id/iv_logo"
21 |                 android:layout_width="38dp"
22 |                 android:layout_height="38dp"
23 |                 android:layout_marginStart="20dp"
24 |                 android:src="@mipmap/ic_launcher_round"
25 |                 app:layout_constraintBottom_toBottomOf="parent"
26 |                 app:layout_constraintStart_toStartOf="parent"
27 |                 app:layout_constraintTop_toTopOf="parent" />
28 | 
29 |         <TextView
30 |                 android:id="@+id/tv_title"
31 |                 android:layout_width="wrap_content"
32 |                 android:layout_height="wrap_content"
33 |                 android:layout_gravity="center_vertical"
34 |                 android:layout_marginStart="12dp"
35 |                 android:text="EasyFloat"
36 |                 android:textColor="@color/white"
37 |                 android:textSize="16sp"
38 |                 app:layout_constraintStart_toEndOf="@id/iv_logo"
39 |                 app:layout_constraintTop_toTopOf="parent" />
40 | 
41 |         <TextView
42 |                 android:layout_width="wrap_content"
43 |                 android:layout_height="wrap_content"
44 |                 android:layout_gravity="center_vertical"
45 |                 android:layout_marginTop="4dp"
46 |                 android:text="浮窗从未如此简单..."
47 |                 android:textColor="@color/white"
48 |                 android:textSize="13sp"
49 |                 app:layout_constraintStart_toStartOf="@id/tv_title"
50 |                 app:layout_constraintTop_toBottomOf="@id/tv_title" />
51 | 
52 |     </androidx.constraintlayout.widget.ConstraintLayout>
53 | 
54 | </androidx.cardview.widget.CardView>


--------------------------------------------------------------------------------
/example/src/main/res/layout/item_simple_list.xml:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="utf-8"?>
 2 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3 |         android:layout_width="match_parent"
 4 |         android:layout_height="wrap_content"
 5 |         android:background="@color/green">
 6 | 
 7 |     <TextView
 8 |             android:id="@+id/tv_item"
 9 |             android:layout_width="40dp"
10 |             android:layout_height="wrap_content"
11 |             android:gravity="center"
12 |             android:padding="10dp"
13 |             android:text="A"
14 |             android:textColor="@color/white" />
15 | 
16 |     <CheckBox
17 |             android:id="@+id/checkbox"
18 |             android:layout_width="match_parent"
19 |             android:layout_height="match_parent" />
20 | 
21 | </LinearLayout>


--------------------------------------------------------------------------------
/example/src/main/res/layout/popup_test.xml:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="utf-8"?>
 2 | <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3 |         xmlns:app="http://schemas.android.com/apk/res-auto"
 4 |         android:layout_width="match_parent"
 5 |         android:layout_height="200dp"
 6 |         android:background="@color/colorPrimaryDark"
 7 |         android:clipChildren="false">
 8 | 
 9 |     <TextView
10 |             android:id="@+id/tv_test"
11 |             android:layout_width="match_parent"
12 |             android:layout_height="200dp"
13 |             android:clipChildren="false"
14 |             android:text="11111"
15 |             app:layout_constraintBottom_toBottomOf="parent"
16 |             app:layout_constraintEnd_toEndOf="parent"
17 |             app:layout_constraintTop_toTopOf="parent" />
18 | 
19 | </androidx.constraintlayout.widget.ConstraintLayout>


--------------------------------------------------------------------------------
/example/src/main/res/layout/test_float.xml:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="utf-8"?>
 2 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3 |         android:layout_width="match_parent"
 4 |         android:layout_height="match_parent">
 5 | 
 6 |     <com.lzf.easyfloat.widget.activityfloat.FloatingView
 7 |             android:id="@+id/floatingView"
 8 |             android:layout_width="wrap_content"
 9 |             android:layout_height="wrap_content" />
10 | 
11 | </LinearLayout>
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 | <?xml version="1.0" encoding="UTF-8"?>
 2 | <resources>
 3 | 
 4 |     <!--圆弧进度条-->
 5 |     <declare-styleable name="TasksCompletedView">
 6 |         <!--内圆半径-->
 7 |         <attr name="radius" format="dimension" />
 8 |         <!--内圆颜色-->
 9 |         <attr name="circleColor" format="color" />
10 |         <!--进度条宽度-->
11 |         <attr name="progressWidth" format="dimension" />
12 |         <!--进度条颜色-->
13 |         <attr name="progressColor" format="color" />
14 |         <!--进度条背景色-->
15 |         <attr name="progressBgColor" format="color" />
16 |         <!--进度条中间的文字-->
17 |         <attr name="progressText" format="string" />
18 |         <!--进度条中间的文字大小-->
19 |         <attr name="progressTextSize" format="dimension" />
20 |         <!--进度条中间的文字颜色-->
21 |         <attr name="progressTextColor" format="color" />
22 |     </declare-styleable>
23 | 
24 |     <declare-styleable name="CircleLoadingView">
25 |         <!--圆弧宽度-->
26 |         <attr name="arcWidth" format="dimension" />
27 |         <!--加载动画的颜色-->
28 |         <attr name="loadingColor" format="color" />
29 |         <!--环形圆点的数量-->
30 |         <attr name="dotSize" format="integer" />
31 |         <!--圆环转动一周的时间-->
32 |         <attr name="durationTime" format="float" />
33 |         <!--每周期圆点旋转的角度-->
34 |         <attr name="dotAngle" format="float" />
35 |     </declare-styleable>
36 | 
37 | </resources>


--------------------------------------------------------------------------------
/example/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="utf-8"?>
 2 | <resources>
 3 |     <color name="colorPrimary">#008577</color>
 4 |     <color name="colorPrimaryDark">#00574B</color>
 5 |     <color name="colorAccent">#D81B60</color>
 6 | 
 7 |     <color name="white">#FFFFFF</color>
 8 |     <color name="white2">#F3F3F3</color>
 9 |     <color name="red">#EE2802</color>
10 |     <color name="orange">#FFB40A</color>
11 |     <color name="green">#6CC354</color>
12 |     <color name="blue">#00C7EE</color>
13 |     <color name="pinkRed">#EF0050</color>
14 |     <color name="violet">#7B20A2</color>
15 |     <color name="translucent">#DD000000</color>
16 |     <color name="text_color">#999999</color>
17 | 
18 |     <color name="transparent">#00000000</color>
19 |     <color name="progressBgColor">#E0E8EC</color>
20 |     <color name="colorRed">#d50f09</color>
21 |     <color name="smallCircle">#3D8EC0CD</color>
22 |     <color name="mainBackground">#E6FFFFFF</color>
23 |     <color name="titleColor">#989898</color>
24 |     <color name="light_gray">#242424</color>
25 | 
26 | </resources>
27 | 


--------------------------------------------------------------------------------
/example/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 | <resources>
2 |     <string name="app_name">EasyFloat</string>
3 | </resources>
4 | 


--------------------------------------------------------------------------------
/example/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
 1 | <resources>
 2 | 
 3 |     <!-- Base application theme. -->
 4 |     <style name="AppThemeNoActionBar" parent="Theme.AppCompat.Light.NoActionBar">
 5 |         <!-- Customize your theme here. -->
 6 |         <item name="colorPrimary">@color/colorPrimary</item>
 7 |         <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
 8 |         <item name="colorAccent">@color/colorAccent</item>
 9 |     </style>
10 | 
11 |     <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
12 |         <!-- Customize your theme here. -->
13 |         <item name="colorPrimary">@color/colorPrimary</item>
14 |         <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
15 |         <item name="colorAccent">@color/colorAccent</item>
16 |     </style>
17 | 
18 |     <style name="TextStyle">
19 |         <item name="android:layout_width">match_parent</item>
20 |         <item name="android:layout_height">46dp</item>
21 |         <item name="android:layout_weight">1</item>
22 |         <item name="android:textSize">16sp</item>
23 |         <item name="android:gravity">center</item>
24 |         <item name="android:layout_margin">0.5dp</item>
25 |         <item name="android:background">@color/white</item>
26 |         <item name="android:textColor">@color/text_color</item>
27 |     </style>
28 | 
29 |     <style name="TextStyle2">
30 |         <item name="android:layout_width">match_parent</item>
31 |         <item name="android:layout_height">46dp</item>
32 |         <item name="android:textSize">16sp</item>
33 |         <item name="android:gravity">center</item>
34 |         <item name="android:background">@color/white</item>
35 |         <item name="android:textColor">@color/text_color</item>
36 |     </style>
37 | </resources>
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" : '.*-> \(.*\)
#39;`
 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 | 


--------------------------------------------------------------------------------