diff --git a/README.md b/README.md
index 2821729..575dec0 100644
--- a/README.md
+++ b/README.md
@@ -15,16 +15,16 @@
- **支持出入动画的设定,有默认动画,可自行替换(策略模式)**
- **使用简单、链式调用、可轻松修改浮窗View**
- **支持Kotlin DSL,可按需回调状态,摆脱Java的繁琐**
-- **支持xml直接使用,满足拖拽控件的需求**
+- **支持状态栏沉浸,侧滑打开、拖拽关闭**
- **支持解锁更多姿势,如:拖拽缩放、通知弹窗...**
|权限申请|系统浮窗|前台和过滤|
|:---:|:---:|:---:|
-|![](https://github.com/princekin-f/EasyFloat/blob/master/gif/%E6%9D%83%E9%99%90%E7%94%B3%E8%AF%B7.gif)|![](https://github.com/princekin-f/EasyFloat/blob/master/gif/%E7%B3%BB%E7%BB%9F%E6%B5%AE%E7%AA%97.gif)|![](https://github.com/princekin-f/EasyFloat/blob/master/gif/%E6%B5%AE%E7%AA%97%E7%BC%A9%E6%94%BE.gif)|
+|![](https://github.com/princekin-f/EasyFloat/blob/master/readme/%E6%9D%83%E9%99%90%E7%94%B3%E8%AF%B7.gif)|![](https://github.com/princekin-f/EasyFloat/blob/master/readme/%E7%B3%BB%E7%BB%9F%E6%B5%AE%E7%AA%97.gif)|![](https://github.com/princekin-f/EasyFloat/blob/master/readme/%E6%B5%AE%E7%AA%97%E7%BC%A9%E6%94%BE.gif)|
|状态回调|View修改|拓展使用|
|:---:|:---:|:---:|
-|![](https://github.com/princekin-f/EasyFloat/blob/master/gif/%E6%B5%AE%E7%AA%97Callbacks.gif)|![](https://github.com/princekin-f/EasyFloat/blob/master/gif/%E6%96%B9%E4%BE%BF%E7%9A%84view%E4%BF%AE%E6%94%B9.gif)|![](https://github.com/princekin-f/EasyFloat/blob/master/gif/dialog%E5%92%8Cxml%E4%BD%BF%E7%94%A8.gif)|
+|![](https://github.com/princekin-f/EasyFloat/blob/master/readme/%E6%B5%AE%E7%AA%97Callbacks.gif)|![](https://github.com/princekin-f/EasyFloat/blob/master/readme/%E6%96%B9%E4%BE%BF%E7%9A%84view%E4%BF%AE%E6%94%B9.gif)|![](https://github.com/princekin-f/EasyFloat/blob/master/readme/dialog%E5%92%8Cxml%E4%BD%BF%E7%94%A8.gif)|
## 下载体验:
- [直接下载测试APK](https://raw.githubusercontent.com/princekin-f/EasyFloat/master/example/release/EasyFloat.apk),或者扫码下载:
@@ -44,7 +44,7 @@ allprojects {
- **在应用模块的`build.gradle`添加:**
```
dependencies {
- implementation 'com.github.princekin-f:EasyFloat:1.3.4'
+ implementation 'com.github.princekin-f:EasyFloat:2.0.0-beta'
}
```
@@ -54,12 +54,8 @@ EasyFloat.with(this).setLayout(R.layout.float_test).show()
```
## 关于初始化:
-- 全局初始化为非必须;
-- **当浮窗为仅前台、仅后台显示,或者设置了浮窗过滤页面;**
-- 需要在项目的`Application`中进行全局初始化,进行页面生命周期检测。
-```
-EasyFloat.init(this, isDebug)
-```
+> 2.0.0开始,无需初始化
+
## 关于权限声明:
- 权限声明为非必须;
@@ -73,16 +69,16 @@ EasyFloat.init(this, isDebug)
```
EasyFloat.with(this)
// 设置浮窗xml布局文件,并可设置详细信息
- .setLayout(R.layout.float_app, OnInvokeView { })
- // 设置浮窗显示类型,默认只在当前Activity显示,可选一直显示、仅前台显示、仅后台显示
+ .setLayout(R.layout.float_app) { }
+ // 设置浮窗显示类型,默认只在当前Activity显示,可选一直显示、仅前台显示
.setShowPattern(ShowPattern.ALL_TIME)
// 设置吸附方式,共15种模式,详情参考SidePattern
.setSidePattern(SidePattern.RESULT_HORIZONTAL)
// 设置浮窗的标签,用于区分多个浮窗
.setTag("testFloat")
- // 设置浮窗是否可拖拽,默认可拖拽
+ // 设置浮窗是否可拖拽
.setDragEnable(true)
- // 系统浮窗是否包含EditText,仅针对系统浮窗,默认不包含
+ // 浮窗是否包含EditText,默认不包含
.hasEditText(false)
// 设置浮窗固定坐标,ps:设置固定坐标,Gravity属性和offset属性将无效
.setLocation(100, 200)
@@ -90,14 +86,12 @@ EasyFloat.with(this)
.setGravity(Gravity.END or Gravity.CENTER_VERTICAL, 0, 200)
// 设置宽高是否充满父布局,直接在xml设置match_parent属性无效
.setMatchParent(widthMatch = false, heightMatch = false)
- // 设置Activity浮窗的出入动画,可自定义,实现相应接口即可(策略模式),无需动画直接设置为null
+ // 设置浮窗的出入动画,可自定义,实现相应接口即可(策略模式),无需动画直接设置为null
.setAnimator(DefaultAnimator())
- // 设置系统浮窗的出入动画,使用同上
- .setAppFloatAnimator(AppFloatDefaultAnimator())
// 设置系统浮窗的不需要显示的页面
.setFilter(MainActivity::class.java, SecondActivity::class.java)
// 设置系统浮窗的有效显示高度(不包含虚拟导航栏的高度),基本用不到,除非有虚拟导航栏适配问题
- .setDisplayHeight(OnDisplayHeight { context -> DisplayUtils.rejectedNavHeight(context) })
+ .setDisplayHeight { context -> DisplayUtils.rejectedNavHeight(context) }
// 浮窗的一些状态回调,如:创建结果、显示、隐藏、销毁、touchEvent、拖拽过程、拖拽结束。
// ps:通过Kotlin DSL实现的回调,可以按需复写方法,用到哪个写哪个
.registerCallback {
@@ -112,7 +106,9 @@ EasyFloat.with(this)
// 创建浮窗(这是关键哦😂)
.show()
```
+
**在Java中使用Kotlin DSL不是很方便,状态回调还有一种常规的接口方式:**
+
```
.registerCallbacks(new OnFloatCallbacks() {
@Override
@@ -137,10 +133,12 @@ EasyFloat.with(this)
public void dragEnd(@NotNull View view) { }
})
```
+
如果想要在Java是使用Kotlin DSL,可以参考Demo。
### 悬浮窗权限的检测、申请:
- **无需主动进行权限申请,创建结果、申请结果可在`OnFloatCallbacks`的`createdResult`获取。**
+
```
// 权限检测
PermissionUtils.checkPermission(this)
@@ -149,49 +147,27 @@ PermissionUtils.checkPermission(this)
PermissionUtils.requestPermission(this,OnPermissionResult)
```
-### Activity浮窗的相关API:
-```
-// 关闭浮窗
-dismiss(activity: Activity? = null, floatTag: String? = null)
-
-// 隐藏浮窗
-hide(activity: Activity? = null, floatTag: String? = null)
-
-// 显示浮窗
-show(activity: Activity? = null, floatTag: String? = null)
-
-// 设置是否可拖拽
-setDragEnable(activity: Activity? = null, dragEnable: Boolean, floatTag: String? = null )
-
-// 浮窗是否显示
-isShow(activity: Activity? = null, floatTag: String? = null)
-
-// 获取我们设置的浮窗View
-getFloatView(activity: Activity? = null, tag: String? = null)
-```
-
-**PS:`? = null` 代表可选参数,不填也行,默认值为null。下同。**
-
-### 系统浮窗的相关API:
+### 浮窗的相关API:
```
-// 关闭浮窗
-dismissAppFloat(tag: String? = null)
+// 关闭浮窗,force为强制关闭,有退出动画也不执行
+dismiss(tag: String? = null, force: Boolean = false)
// 隐藏浮窗
-hideAppFloat(tag: String? = null)
+hide(tag: String? = null)
// 显示浮窗
-showAppFloat(tag: String? = null)
+show(tag: String? = null)
// 设置是否可拖拽
-appFloatDragEnable(dragEnable: Boolean, tag: String? = null)
+dragEnable(dragEnable: Boolean, tag: String? = null)
// 浮窗是否显示
-appFloatIsShow(tag: String? = null)
+isShow(tag: String? = null)
// 获取我们设置的浮窗View
-getAppFloatView(tag: String? = null)
+getFloatView(tag: String? = null)
+// ******************* 系统浮窗独有 *******************
// 添加单个浮窗过滤页面
filterActivity(activity: Activity, tag: String? = null)
@@ -206,39 +182,43 @@ removeFilters(tag: String? = null, vararg clazz: Class<*>)
// 清空过滤页面
clearFilters(tag: String? = null)
-```
-### 系统浮窗中使用`EditText`:
-- **首先设置`.hasEditText(true)`,用于内部监听返回键;**
-- **当点击`EditText`时,主动调用`openInputMethod`方法:**
-```
-InputMethodUtils.openInputMethod(editText, tag)
-```
-软键盘关闭时调用`closedInputMethod`方法(`1.1.1`开始无需再调用):
-```
-InputMethodUtils.closedInputMethod(tag)
```
+**PS:`? = null` 代表可选参数,不填也行,默认值为null。**
-### 直接在xml布局使用拖拽控件:
+
+## 拖拽关闭、侧滑创建:
```
-
-
-
-
-
+// 在拖拽回调中,注册拖拽关闭
+drag { view, motionEvent ->
+ DragUtils.registerDragClose(motionEvent, object : OnTouchRangeListener {
+ override fun touchInRange(inRange: Boolean, view: BaseSwitchView) {
+ // 震动、视图调整等...
+ }
+
+ override fun touchUpInRange() {
+ // 关闭浮窗等...
+ EasyFloat.dismiss(tag, true)
+ }
+ })
+}
+
+// 在Activity的dispatchTouchEvent中,注册侧滑创建
+DragUtils.registerSwipeAdd(ev, object : OnTouchRangeListener {
+ override fun touchInRange(inRange: Boolean, view: BaseSwitchView) {
+ // 震动、视图调整等...
+ }
+
+ override fun touchUpInRange() {
+ // 浮窗创建等,详情参考:SwipeTestActivity
+ showFloat()
+ }
+})
```
+
## 关于混淆:
-```
--keep class com.lzf.easyfloat.** {*;}
-```
+> 自带混淆规则,正常情况下无需手动导入。
## 关于感谢:
- **权限适配:[FloatWindowPermission](https://github.com/zhaozepeng/FloatWindowPermission)**
@@ -251,9 +231,9 @@ InputMethodUtils.closedInputMethod(tag)
- 如果该库对你提供了帮助,你可以小小的赏赞一下作者,同样作者也会非常感谢你!我们一起众筹云测😘
diff --git a/UpdateDoc.md b/UpdateDoc.md
index 7a42c58..86684f2 100644
--- a/UpdateDoc.md
+++ b/UpdateDoc.md
@@ -1,4 +1,9 @@
## 版本更新日志
+#### v 2.0.0-beta:
+- 新增拖拽关闭、侧滑创建、状态栏沉浸等功能;
+- 重构单页面浮窗(创建和PopupWindow同类型的子窗口),减少了API数目,提高利用率;
+- 优化使用体验,无需手动init,无需手动调起软键盘。
+
#### v 1.3.4:
- 优化细节。
diff --git a/build.gradle b/build.gradle
index 6c08101..d0ff424 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,14 +1,14 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
- ext.kotlin_version = '1.3.71'
+ ext.kotlin_version = '1.4.20'
repositories {
google()
jcenter()
maven { url "https://jitpack.io" }
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.5.0'
+ classpath 'com.android.tools.build:gradle:4.1.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
// NOTE: Do not place your application dependencies here; they belong
diff --git a/easyfloat/proguard-rules.pro b/easyfloat/proguard-rules.pro
index 7657a07..201c2e7 100644
--- a/easyfloat/proguard-rules.pro
+++ b/easyfloat/proguard-rules.pro
@@ -19,3 +19,19 @@
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
+
+# 保持配置类 config 不被混淆
+-keep class com.lzf.easyfloat.data.FloatConfig {*;}
+
+# 保持自定义控件、ContentProvider 不被混淆
+-keep public class * extends android.view.View
+-keep public class * extends android.content.ContentProvider
+
+# 保持枚举 enum 类不被混淆
+-keepclassmembers enum * {
+ public static **[] values();
+ public static ** valueOf(java.lang.String);
+}
+
+# 保持反射不被混淆
+-keepattributes EnclosingMethod
\ No newline at end of file
diff --git a/easyfloat/src/main/AndroidManifest.xml b/easyfloat/src/main/AndroidManifest.xml
index f2b4b04..abd6189 100644
--- a/easyfloat/src/main/AndroidManifest.xml
+++ b/easyfloat/src/main/AndroidManifest.xml
@@ -1,2 +1,12 @@
+ package="com.lzf.easyfloat">
+
+
+
+
+
+
diff --git a/easyfloat/src/main/java/com/lzf/easyfloat/EasyFloat.kt b/easyfloat/src/main/java/com/lzf/easyfloat/EasyFloat.kt
index edfa383..86835aa 100644
--- a/easyfloat/src/main/java/com/lzf/easyfloat/EasyFloat.kt
+++ b/easyfloat/src/main/java/com/lzf/easyfloat/EasyFloat.kt
@@ -1,9 +1,9 @@
package com.lzf.easyfloat
import android.app.Activity
-import android.app.Application
import android.content.Context
import android.view.View
+import com.lzf.easyfloat.core.FloatingWindowManager
import com.lzf.easyfloat.data.FloatConfig
import com.lzf.easyfloat.enums.ShowPattern
import com.lzf.easyfloat.enums.SidePattern
@@ -13,10 +13,7 @@ import com.lzf.easyfloat.permission.PermissionUtils
import com.lzf.easyfloat.utils.LifecycleUtils
import com.lzf.easyfloat.interfaces.FloatCallbacks
import com.lzf.easyfloat.utils.Logger
-import com.lzf.easyfloat.widget.activityfloat.ActivityFloatManager
-import com.lzf.easyfloat.widget.appfloat.FloatManager
import java.lang.Exception
-import java.lang.ref.WeakReference
/**
* @author: liuzhenfeng
@@ -27,142 +24,128 @@ import java.lang.ref.WeakReference
class EasyFloat {
companion object {
- internal var isDebug = false
- // 通过弱引用持有Activity,防止内容泄漏,适用于只在一个Activity创建浮窗的情况
- private var activityWr: WeakReference? = null
- private var isInitialized = false
-
- @JvmStatic
- @JvmOverloads
- fun init(application: Application, isDebug: Boolean = false) {
- this.isDebug = isDebug
- isInitialized = true
- // 注册Activity生命周期回调
- LifecycleUtils.setLifecycleCallbacks(application)
- }
-
- @JvmStatic
- fun with(activity: Context): Builder {
- if (activity is Activity) activityWr = WeakReference(activity)
- return Builder(activity)
- }
-
- // *************************** Activity浮窗的相关方法 ***************************
- // 通过浮窗管理类,实现相应的功能,详情参考ActivityFloatManager
- @JvmStatic
- @JvmOverloads
- fun dismiss(activity: Activity? = null, tag: String? = null) =
- manager(activity)?.dismiss(tag)
-
- @JvmStatic
- @JvmOverloads
- fun hide(activity: Activity? = null, tag: String? = null) =
- manager(activity)?.setVisibility(tag, View.GONE)
-
- @JvmStatic
- @JvmOverloads
- fun show(activity: Activity? = null, tag: String? = null) =
- manager(activity)?.setVisibility(tag, View.VISIBLE)
-
- @JvmStatic
- @JvmOverloads
- fun setDragEnable(activity: Activity? = null, dragEnable: Boolean, tag: String? = null) =
- manager(activity)?.setDragEnable(dragEnable, tag)
-
- @JvmStatic
- @JvmOverloads
- fun isShow(activity: Activity? = null, tag: String? = null) = manager(activity)?.isShow(tag)
/**
- * 获取我们传入的浮窗View
+ * 通过上下文,创建浮窗的构建者信息,使浮窗拥有一些默认属性
+ * @param activity 上下文信息,优先使用Activity上下文,因为系统浮窗权限的自动申请,需要使用Activity信息
+ * @return 浮窗属性构建者
*/
@JvmStatic
- @JvmOverloads
- fun getFloatView(activity: Activity? = null, tag: String? = null) =
- manager(activity)?.getFloatView(tag)
-
- /**
- * 获取Activity浮窗管理类
- */
- private fun manager(activity: Activity?) =
- (activity ?: activityWr?.get())?.run { ActivityFloatManager(this) }
+ fun with(activity: Context): Builder = if (activity is Activity) Builder(activity)
+ else Builder(LifecycleUtils.getTopActivity() ?: activity)
- // *************************** 以下系统浮窗的相关方法 ***************************
/**
- * 关闭系统级浮窗
+ * 关闭当前浮窗
+ * @param tag 浮窗标签
+ * @param force 立即关闭,有退出动画也不执行
*/
@JvmStatic
@JvmOverloads
- fun dismissAppFloat(tag: String? = null) = FloatManager.dismiss(tag)
+ fun dismiss(tag: String? = null, force: Boolean = false) =
+ FloatingWindowManager.dismiss(tag, force)
/**
- * 隐藏系统浮窗
+ * 隐藏当前浮窗
+ * @param tag 浮窗标签
*/
@JvmStatic
@JvmOverloads
- fun hideAppFloat(tag: String? = null) = FloatManager.visible(false, tag, false)
+ fun hide(tag: String? = null) = FloatingWindowManager.visible(false, tag, false)
/**
- * 显示系统浮窗
+ * 设置当前浮窗可见
+ * @param tag 浮窗标签
*/
@JvmStatic
@JvmOverloads
- fun showAppFloat(tag: String? = null) = FloatManager.visible(true, tag, true)
+ fun show(tag: String? = null) = FloatingWindowManager.visible(true, tag, true)
/**
- * 设置系统浮窗是否可拖拽,先获取浮窗的config,后修改相应属性
+ * 设置当前浮窗是否可拖拽,先获取浮窗的config,后修改相应属性
+ * @param dragEnable 是否可拖拽
+ * @param tag 浮窗标签
*/
@JvmStatic
@JvmOverloads
- fun appFloatDragEnable(dragEnable: Boolean, tag: String? = null) =
- getConfig(tag).let { it?.dragEnable = dragEnable }
+ fun dragEnable(dragEnable: Boolean, tag: String? = null) =
+ getConfig(tag)?.let { it.dragEnable = dragEnable }
/**
- * 获取系统浮窗是否显示,通过浮窗的config,获取显示状态
+ * 获取当前浮窗是否显示,通过浮窗的config,获取显示状态
+ * @param tag 浮窗标签
+ * @return 当前浮窗是否显示
*/
@JvmStatic
@JvmOverloads
- fun appFloatIsShow(tag: String? = null) = getConfig(tag) != null && getConfig(tag)!!.isShow
+ fun isShow(tag: String? = null) = getConfig(tag)?.isShow ?: false
/**
- * 获取系统浮窗中,我们传入的View
+ * 获取当前浮窗中,我们传入的View
+ * @param tag 浮窗标签
*/
@JvmStatic
@JvmOverloads
- fun getAppFloatView(tag: String? = null): View? = getConfig(tag)?.layoutView
+ fun getFloatView(tag: String? = null): View? = getConfig(tag)?.layoutView
+ // 以下几个方法为:系统浮窗过滤页面的添加、移除、清空
/**
- * 以下几个方法为:系统浮窗过滤页面的添加、移除、清空
+ * 为当前浮窗过滤,设置需要过滤的Activity
+ * @param activity 需要过滤的Activity
+ * @param tag 浮窗标签
*/
@JvmStatic
@JvmOverloads
fun filterActivity(activity: Activity, tag: String? = null) =
getFilterSet(tag)?.add(activity.componentName.className)
+ /**
+ * 为当前浮窗,设置需要过滤的Activity类名(一个或者多个)
+ * @param tag 浮窗标签
+ * @param clazz 需要过滤的Activity类名,一个或者多个
+ */
@JvmStatic
@JvmOverloads
fun filterActivities(tag: String? = null, vararg clazz: Class<*>) =
getFilterSet(tag)?.addAll(clazz.map { it.name })
+ /**
+ * 为当前浮窗,移除需要过滤的Activity
+ * @param activity 需要移除过滤的Activity
+ * @param tag 浮窗标签
+ */
@JvmStatic
@JvmOverloads
fun removeFilter(activity: Activity, tag: String? = null) =
getFilterSet(tag)?.remove(activity.componentName.className)
+ /**
+ * 为当前浮窗,移除需要过滤的Activity类名(一个或者多个)
+ * @param tag 浮窗标签
+ * @param clazz 需要移除过滤的Activity类名,一个或者多个
+ */
@JvmStatic
@JvmOverloads
fun removeFilters(tag: String? = null, vararg clazz: Class<*>) =
getFilterSet(tag)?.removeAll(clazz.map { it.name })
+ /**
+ * 清除当前浮窗的所有过滤信息
+ * @param tag 浮窗标签
+ */
@JvmStatic
@JvmOverloads
fun clearFilters(tag: String? = null) = getFilterSet(tag)?.clear()
/**
- * 获取系统浮窗的config
+ * 获取当前浮窗的config
+ * @param tag 浮窗标签
*/
- private fun getConfig(tag: String?) = FloatManager.getAppFloatManager(tag)?.config
+ private fun getConfig(tag: String?) = FloatingWindowManager.getHelper(tag)?.config
+ /**
+ * 获取当前浮窗的过滤集合
+ * @param tag 浮窗标签
+ */
private fun getFilterSet(tag: String?) = getConfig(tag)?.filterSet
}
@@ -175,67 +158,115 @@ class EasyFloat {
// 创建浮窗数据类,方便管理配置
private val config = FloatConfig()
+ /**
+ * 设置浮窗的吸附模式
+ * @param sidePattern 浮窗吸附模式
+ */
fun setSidePattern(sidePattern: SidePattern) = apply { config.sidePattern = sidePattern }
+ /**
+ * 设置浮窗的显示模式
+ * @param showPattern 浮窗显示模式
+ */
fun setShowPattern(showPattern: ShowPattern) = apply { config.showPattern = showPattern }
+ /**
+ * 设置浮窗的布局文件,以及布局的操作接口
+ * @param layoutId 布局文件的资源Id
+ * @param invokeView 布局文件的操作接口
+ */
@JvmOverloads
fun setLayout(layoutId: Int, invokeView: OnInvokeView? = null) = apply {
config.layoutId = layoutId
config.invokeView = invokeView
}
+ /**
+ * 设置浮窗的对齐方式,以及偏移量
+ * @param gravity 对齐方式
+ * @param offsetX 目标坐标的水平偏移量
+ * @param offsetY 目标坐标的竖直偏移量
+ */
@JvmOverloads
fun setGravity(gravity: Int, offsetX: Int = 0, offsetY: Int = 0) = apply {
config.gravity = gravity
config.offsetPair = Pair(offsetX, offsetY)
}
+ /**
+ * 设置浮窗的起始坐标,优先级高于setGravity
+ * @param x 起始水平坐标
+ * @param y 起始竖直坐标
+ */
fun setLocation(x: Int, y: Int) = apply { config.locationPair = Pair(x, y) }
+ /**
+ * 设置浮窗的标签:只有一个浮窗时,可以不设置;
+ * 有多个浮窗必须设置不容的浮窗,不然没法管理,所以禁止创建相同标签的浮窗
+ * @param floatTag 浮窗标签
+ */
fun setTag(floatTag: String?) = apply { config.floatTag = floatTag }
+ /**
+ * 设置浮窗是否可拖拽
+ * @param dragEnable 是否可拖拽
+ */
fun setDragEnable(dragEnable: Boolean) = apply { config.dragEnable = dragEnable }
/**
- * 该方法针对系统浮窗,单页面浮窗无需设置
+ * 设置浮窗是否状态栏沉浸
+ * @param immersionStatusBar 是否状态栏沉浸
*/
- fun hasEditText(hasEditText: Boolean) = apply { config.hasEditText = hasEditText }
+ fun setImmersionStatusBar(immersionStatusBar: Boolean) =
+ apply { config.immersionStatusBar = immersionStatusBar }
- @Deprecated("建议直接在 setLayout 设置详细布局")
- fun invokeView(invokeView: OnInvokeView) = apply { config.invokeView = invokeView }
+ /**
+ * 浮窗是否包含EditText,浮窗默认不获取焦点,无法弹起软键盘,所以需要适配
+ * @param hasEditText 是否包含EditText
+ */
+ fun hasEditText(hasEditText: Boolean) = apply { config.hasEditText = hasEditText }
/**
* 通过传统接口,进行浮窗的各种状态回调
+ * @param callbacks 浮窗的各种事件回调
*/
fun registerCallbacks(callbacks: OnFloatCallbacks) = apply { config.callbacks = callbacks }
/**
* 针对kotlin 用户,传入带FloatCallbacks.Builder 返回值的 lambda,可按需回调
* 为了避免方法重载时 出现编译错误的情况,更改了方法名
+ * @param builder 事件回调的构建者
*/
fun registerCallback(builder: FloatCallbacks.Builder.() -> Unit) =
apply { config.floatCallbacks = FloatCallbacks().apply { registerListener(builder) } }
+ /**
+ * 设置浮窗的出入动画
+ * @param floatAnimator 浮窗的出入动画,为空时不执行动画
+ */
fun setAnimator(floatAnimator: OnFloatAnimator?) =
apply { config.floatAnimator = floatAnimator }
- fun setAppFloatAnimator(appFloatAnimator: OnAppFloatAnimator?) =
- apply { config.appFloatAnimator = appFloatAnimator }
-
/**
* 设置屏幕的有效显示高度(不包含虚拟导航栏的高度)
+ * @param displayHeight 屏幕的有效高度
*/
fun setDisplayHeight(displayHeight: OnDisplayHeight) =
apply { config.displayHeight = displayHeight }
+ /**
+ * 设置浮窗宽高是否充满屏幕
+ * @param widthMatch 宽度是否充满屏幕
+ * @param heightMatch 高度是否充满屏幕
+ */
fun setMatchParent(widthMatch: Boolean = false, heightMatch: Boolean = false) = apply {
config.widthMatch = widthMatch
config.heightMatch = heightMatch
}
/**
- * 设置需要过滤的Activity,仅对系统浮窗有效
+ * 设置需要过滤的Activity类名,仅对系统浮窗有效
+ * @param clazz 需要过滤的Activity类名
*/
fun setFilter(vararg clazz: Class<*>) = apply {
clazz.forEach {
@@ -253,36 +284,18 @@ class EasyFloat {
fun show() = when {
// 未设置浮窗布局文件,不予创建
config.layoutId == null -> callbackCreateFailed(WARN_NO_LAYOUT)
- // 检测是否有初始化异常的情况,防止显示/过滤异常
- checkUninitialized() -> callbackCreateFailed(WARN_UNINITIALIZED)
// 仅当页显示,则直接创建activity浮窗
- config.showPattern == ShowPattern.CURRENT_ACTIVITY -> createActivityFloat()
+ config.showPattern == ShowPattern.CURRENT_ACTIVITY -> createFloat()
// 系统浮窗需要先进行权限审核,有权限则创建app浮窗
- PermissionUtils.checkPermission(activity) -> createAppFloat()
+ PermissionUtils.checkPermission(activity) -> createFloat()
// 申请浮窗权限
else -> requestPermission()
}
/**
- * 检测是否存在全局初始化的异常
- */
- private fun checkUninitialized() = when (config.showPattern) {
- ShowPattern.CURRENT_ACTIVITY -> false
- ShowPattern.FOREGROUND, ShowPattern.BACKGROUND -> !isInitialized
- ShowPattern.ALL_TIME -> config.filterSet.isNotEmpty() && !isInitialized
- }
-
- /**
- * 通过Activity浮窗管理类,创建Activity浮窗
- */
- private fun createActivityFloat() =
- if (activity is Activity) ActivityFloatManager(activity).createFloat(config)
- else callbackCreateFailed(WARN_CONTEXT_ACTIVITY)
-
- /**
- * 通过浮窗管理类创建系统浮窗
+ * 通过浮窗管理类,统一创建浮窗
*/
- private fun createAppFloat() = FloatManager.create(activity, config)
+ private fun createFloat() = FloatingWindowManager.create(activity, config)
/**
* 通过Fragment去申请系统悬浮窗权限
@@ -293,12 +306,14 @@ class EasyFloat {
/**
* 申请浮窗权限的结果回调
+ * @param isOpen 悬浮窗权限是否打开
*/
override fun permissionResult(isOpen: Boolean) =
- if (isOpen) createAppFloat() else callbackCreateFailed(WARN_PERMISSION)
+ if (isOpen) createFloat() else callbackCreateFailed(WARN_PERMISSION)
/**
* 回调创建失败
+ * @param reason 失败原因
*/
private fun callbackCreateFailed(reason: String) {
config.callbacks?.createdResult(false, reason, null)
diff --git a/easyfloat/src/main/java/com/lzf/easyfloat/EasyFloatInitializer.kt b/easyfloat/src/main/java/com/lzf/easyfloat/EasyFloatInitializer.kt
new file mode 100644
index 0000000..2df6f95
--- /dev/null
+++ b/easyfloat/src/main/java/com/lzf/easyfloat/EasyFloatInitializer.kt
@@ -0,0 +1,43 @@
+package com.lzf.easyfloat
+
+import android.app.Application
+import android.content.ContentProvider
+import android.content.ContentValues
+import android.database.Cursor
+import android.net.Uri
+import com.lzf.easyfloat.utils.LifecycleUtils
+
+/**
+ * @author: liuzhenfeng
+ * @github:https://github.com/princekin-f
+ * @function:
+ * @date: 2020/10/23 13:41
+ */
+class EasyFloatInitializer : ContentProvider() {
+
+ override fun onCreate(): Boolean {
+ LifecycleUtils.setLifecycleCallbacks(context!!.applicationContext as Application)
+ return true
+ }
+
+ override fun query(
+ uri: Uri,
+ projection: Array?,
+ selection: String?,
+ selectionArgs: Array?,
+ sortOrder: String?
+ ): Cursor? = null
+
+ override fun getType(uri: Uri): String? = null
+
+ override fun insert(uri: Uri, values: ContentValues?): Uri? = null
+
+ override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Int = 0
+
+ override fun update(
+ uri: Uri,
+ values: ContentValues?,
+ selection: String?,
+ selectionArgs: Array?
+ ): Int = 0
+}
\ No newline at end of file
diff --git a/easyfloat/src/main/java/com/lzf/easyfloat/anim/AnimatorManager.kt b/easyfloat/src/main/java/com/lzf/easyfloat/anim/AnimatorManager.kt
index 223bc66..cde32e2 100644
--- a/easyfloat/src/main/java/com/lzf/easyfloat/anim/AnimatorManager.kt
+++ b/easyfloat/src/main/java/com/lzf/easyfloat/anim/AnimatorManager.kt
@@ -2,24 +2,24 @@ package com.lzf.easyfloat.anim
import android.animation.Animator
import android.view.View
-import android.view.ViewGroup
-import com.lzf.easyfloat.enums.SidePattern
-import com.lzf.easyfloat.interfaces.OnFloatAnimator
+import android.view.WindowManager
+import com.lzf.easyfloat.data.FloatConfig
/**
* @author: liuzhenfeng
- * @function: Activity浮窗的出入动画管理类,只需传入具体的动画实现类(策略模式)
- * @date: 2019-07-19 14:24
+ * @function: App浮窗的出入动画管理类,只需传入具体的动画实现类(策略模式)
+ * @date: 2019-07-22 16:44
*/
internal class AnimatorManager(
- private val onFloatAnimator: OnFloatAnimator?,
private val view: View,
- private val parentView: ViewGroup,
- private val sidePattern: SidePattern
+ private val params: WindowManager.LayoutParams,
+ private val windowManager: WindowManager,
+ private val config: FloatConfig
) {
- // 通过接口实现具体动画,所以只需要更改接口的具体实现
- fun enterAnim(): Animator? = onFloatAnimator?.enterAnim(view, parentView, sidePattern)
+ fun enterAnim(): Animator? =
+ config.floatAnimator?.enterAnim(view, params, windowManager, config.sidePattern)
- fun exitAnim(): Animator? = onFloatAnimator?.exitAnim(view, parentView, sidePattern)
+ fun exitAnim(): Animator? =
+ config.floatAnimator?.exitAnim(view, params, windowManager, config.sidePattern)
}
\ No newline at end of file
diff --git a/easyfloat/src/main/java/com/lzf/easyfloat/anim/AppFloatAnimatorManager.kt b/easyfloat/src/main/java/com/lzf/easyfloat/anim/AppFloatAnimatorManager.kt
deleted file mode 100644
index bdeadd0..0000000
--- a/easyfloat/src/main/java/com/lzf/easyfloat/anim/AppFloatAnimatorManager.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.lzf.easyfloat.anim
-
-import android.animation.Animator
-import android.view.View
-import android.view.WindowManager
-import com.lzf.easyfloat.data.FloatConfig
-
-/**
- * @author: liuzhenfeng
- * @function: App浮窗的出入动画管理类,只需传入具体的动画实现类(策略模式)
- * @date: 2019-07-22 16:44
- */
-internal class AppFloatAnimatorManager(
- private val view: View,
- private val params: WindowManager.LayoutParams,
- private val windowManager: WindowManager,
- private val config: FloatConfig
-) {
-
- fun enterAnim(): Animator? =
- config.appFloatAnimator?.enterAnim(view, params, windowManager, config.sidePattern)
-
- fun exitAnim(): Animator? =
- config.appFloatAnimator?.exitAnim(view, params, windowManager, config.sidePattern)
-}
\ No newline at end of file
diff --git a/easyfloat/src/main/java/com/lzf/easyfloat/anim/AppFloatDefaultAnimator.kt b/easyfloat/src/main/java/com/lzf/easyfloat/anim/AppFloatDefaultAnimator.kt
deleted file mode 100644
index 006d01b..0000000
--- a/easyfloat/src/main/java/com/lzf/easyfloat/anim/AppFloatDefaultAnimator.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-package com.lzf.easyfloat.anim
-
-import android.animation.Animator
-import android.animation.ValueAnimator
-import android.graphics.Rect
-import android.view.View
-import android.view.WindowManager
-import com.lzf.easyfloat.enums.SidePattern
-import com.lzf.easyfloat.interfaces.OnAppFloatAnimator
-
-/**
- * @author: liuzhenfeng
- * @function: 系统浮窗的默认效果,选择靠近左右侧的一边进行出入
- * @date: 2019-07-22 17:22
- */
-open class AppFloatDefaultAnimator : OnAppFloatAnimator {
-
- override fun enterAnim(
- view: View,
- params: WindowManager.LayoutParams,
- windowManager: WindowManager,
- sidePattern: SidePattern
- ): Animator? = ValueAnimator.ofInt(initValue(view, params, windowManager), params.x).apply {
- duration = 500
- addUpdateListener {
- params.x = it.animatedValue as Int
- windowManager.updateViewLayout(view, params)
- }
- }
-
- override fun exitAnim(
- view: View,
- params: WindowManager.LayoutParams,
- windowManager: WindowManager,
- sidePattern: SidePattern
- ): Animator? = ValueAnimator.ofInt(params.x, initValue(view, params, windowManager)).apply {
- addUpdateListener {
- params.x = it.animatedValue as Int
- windowManager.updateViewLayout(view, params)
- }
- }
-
- private fun initValue(
- view: View,
- params: WindowManager.LayoutParams,
- windowManager: WindowManager
- ): Int {
- val parentRect = Rect()
- windowManager.defaultDisplay.getRectSize(parentRect)
- val leftDistance = params.x
- val rightDistance = parentRect.right - (leftDistance + view.right)
- return if (leftDistance < rightDistance) -view.right else parentRect.right
- }
-
-}
\ No newline at end of file
diff --git a/easyfloat/src/main/java/com/lzf/easyfloat/anim/DefaultAnimator.kt b/easyfloat/src/main/java/com/lzf/easyfloat/anim/DefaultAnimator.kt
index 54f91e9..bef36ce 100644
--- a/easyfloat/src/main/java/com/lzf/easyfloat/anim/DefaultAnimator.kt
+++ b/easyfloat/src/main/java/com/lzf/easyfloat/anim/DefaultAnimator.kt
@@ -1,122 +1,146 @@
package com.lzf.easyfloat.anim
import android.animation.Animator
-import android.animation.ObjectAnimator
+import android.animation.ValueAnimator
import android.graphics.Rect
import android.view.View
-import android.view.ViewGroup
+import android.view.WindowManager
import com.lzf.easyfloat.enums.SidePattern
import com.lzf.easyfloat.interfaces.OnFloatAnimator
+import com.lzf.easyfloat.utils.DisplayUtils
import kotlin.math.min
/**
* @author: liuzhenfeng
- * @function: 默认的出入动画:选择距离边框最近的的一个边,进行出入。
- * 想要实现其他动画效果,只需实现OnFloatAnimator接口,自行定义内容;也可为null,不执行动画。
- * @date: 2019-07-19 14:19
+ * @function: 系统浮窗的默认效果,选择靠近左右侧的一边进行出入
+ * @date: 2019-07-22 17:22
*/
open class DefaultAnimator : OnFloatAnimator {
- // 浮窗各边到窗口边框的距离
- private var leftDistance = 0
- private var rightDistance = 0
- private var topDistance = 0
- private var bottomDistance = 0
- // x轴和y轴距离的最小值
- private var minX = 0
- private var minY = 0
- // 浮窗和窗口所在的矩形
- private var floatRect = Rect()
- private var parentRect = Rect()
-
override fun enterAnim(
view: View,
- parentView: ViewGroup,
+ params: WindowManager.LayoutParams,
+ windowManager: WindowManager,
sidePattern: SidePattern
- ): Animator? {
- initValue(view, parentView)
- val (animType, startValue, endValue) = animTriple(view, sidePattern)
- return ObjectAnimator.ofFloat(view, animType, startValue, endValue).setDuration(500)
- }
+ ): Animator? = getAnimator(view, params, windowManager, sidePattern, false)
override fun exitAnim(
view: View,
- parentView: ViewGroup,
+ params: WindowManager.LayoutParams,
+ windowManager: WindowManager,
sidePattern: SidePattern
- ): Animator? {
- initValue(view, parentView)
- val (animType, startValue, endValue) = animTriple(view, sidePattern)
- return ObjectAnimator.ofFloat(view, animType, endValue, startValue).setDuration(500)
+ ): Animator? = getAnimator(view, params, windowManager, sidePattern, true)
+
+ private fun getAnimator(
+ view: View,
+ params: WindowManager.LayoutParams,
+ windowManager: WindowManager,
+ sidePattern: SidePattern,
+ isExit: Boolean
+ ): Animator {
+ val triple = initValue(view, params, windowManager, sidePattern)
+ // 退出动画的起始值、终点值,与入场动画相反
+ val start = if (isExit) triple.second else triple.first
+ val end = if (isExit) triple.first else triple.second
+ return ValueAnimator.ofInt(start, end).apply {
+ addUpdateListener {
+ try {
+ val value = it.animatedValue as Int
+ if (triple.third) params.x = value else params.y = value
+ // 动画执行过程中页面关闭,出现异常
+ windowManager.updateViewLayout(view, params)
+ } catch (e: Exception) {
+ cancel()
+ }
+ }
+ }
}
/**
- * 设置动画类型,计算具体数值
+ * 计算边距,起始坐标等
*/
- private fun animTriple(view: View, sidePattern: SidePattern): Triple {
- val animType: String
- val startValue: Float = when (sidePattern) {
+ private fun initValue(
+ view: View,
+ params: WindowManager.LayoutParams,
+ windowManager: WindowManager,
+ sidePattern: SidePattern
+ ): Triple {
+ val parentRect = Rect()
+ windowManager.defaultDisplay.getRectSize(parentRect)
+ // 浮窗各边到窗口边框的距离
+ val leftDistance = params.x
+ val rightDistance = parentRect.right - (leftDistance + view.right)
+ val topDistance = params.y
+ val bottomDistance = parentRect.bottom - (topDistance + view.bottom)
+ // 水平、垂直方向的距离最小值
+ val minX = min(leftDistance, rightDistance)
+ val minY = min(topDistance, bottomDistance)
+
+ val isHorizontal: Boolean
+ val endValue: Int
+ val startValue: Int = when (sidePattern) {
SidePattern.LEFT, SidePattern.RESULT_LEFT -> {
- animType = "translationX"
- leftValue(view)
+ // 从左侧到目标位置,右移
+ isHorizontal = true
+ endValue = params.x
+ -view.right
}
SidePattern.RIGHT, SidePattern.RESULT_RIGHT -> {
- animType = "translationX"
- rightValue(view)
+ // 从右侧到目标位置,左移
+ isHorizontal = true
+ endValue = params.x
+ parentRect.right
}
SidePattern.TOP, SidePattern.RESULT_TOP -> {
- animType = "translationY"
- topValue(view)
+ // 从顶部到目标位置,下移
+ isHorizontal = false
+ endValue = params.y
+ -view.bottom
}
SidePattern.BOTTOM, SidePattern.RESULT_BOTTOM -> {
- animType = "translationY"
- rightValue(view)
+ // 从底部到目标位置,上移
+ isHorizontal = false
+ endValue = params.y
+ parentRect.bottom + getCompensationHeight(view, params)
}
SidePattern.DEFAULT, SidePattern.AUTO_HORIZONTAL, SidePattern.RESULT_HORIZONTAL -> {
- animType = "translationX"
- if (leftDistance < rightDistance) leftValue(view) else rightValue(view)
+ // 水平位移,哪边距离屏幕近,从哪侧移动
+ isHorizontal = true
+ endValue = params.x
+ if (leftDistance < rightDistance) -view.right else parentRect.right
}
SidePattern.AUTO_VERTICAL, SidePattern.RESULT_VERTICAL -> {
- animType = "translationY"
- if (topDistance < bottomDistance) topValue(view) else bottomValue(view)
+ // 垂直位移,哪边距离屏幕近,从哪侧移动
+ isHorizontal = false
+ endValue = params.y
+ if (topDistance < bottomDistance) -view.bottom
+ else parentRect.bottom + getCompensationHeight(view, params)
}
else -> if (minX <= minY) {
- animType = "translationX"
- if (leftDistance < rightDistance) leftValue(view) else rightValue(view)
+ isHorizontal = true
+ endValue = params.x
+ if (leftDistance < rightDistance) -view.right else parentRect.right
} else {
- animType = "translationY"
- if (topDistance < bottomDistance) topValue(view) else bottomValue(view)
+ isHorizontal = false
+ endValue = params.y
+ if (topDistance < bottomDistance) -view.bottom
+ else parentRect.bottom + getCompensationHeight(view, params)
}
}
-
- val endValue = if (animType == "translationX") view.translationX else view.translationY
- return Triple(animType, startValue, endValue)
+ return Triple(startValue, endValue, isHorizontal)
}
- private fun leftValue(view: View) = -(leftDistance + view.width) + view.translationX
-
- private fun rightValue(view: View) = rightDistance + view.width + view.translationX
-
- private fun topValue(view: View) = -(topDistance + view.height) + view.translationY
-
- private fun bottomValue(view: View) = bottomDistance + view.height + view.translationY
-
-
/**
- * 计算一些数值,方便使用
+ * 单页面浮窗(popupWindow),坐标从顶部计算,需要加上状态栏的高度
*/
- private fun initValue(view: View, parentView: ViewGroup) {
- view.getGlobalVisibleRect(floatRect)
- parentView.getGlobalVisibleRect(parentRect)
-
- leftDistance = floatRect.left
- rightDistance = parentRect.right - floatRect.right
- topDistance = floatRect.top - parentRect.top
- bottomDistance = parentRect.bottom - floatRect.bottom
-
- minX = min(leftDistance, rightDistance)
- minY = min(topDistance, bottomDistance)
+ private fun getCompensationHeight(view: View, params: WindowManager.LayoutParams): Int {
+ val location = IntArray(2)
+ // 获取在整个屏幕内的绝对坐标
+ view.getLocationOnScreen(location)
+ // 绝对高度和相对高度相等,说明是单页面浮窗(popupWindow),计算底部动画时需要加上状态栏高度
+ return if (location[1] == params.y) DisplayUtils.statusBarHeight(view) else 0
}
}
\ No newline at end of file
diff --git a/easyfloat/src/main/java/com/lzf/easyfloat/widget/appfloat/AppFloatManager.kt b/easyfloat/src/main/java/com/lzf/easyfloat/core/FloatingWindowHelper.kt
similarity index 57%
rename from easyfloat/src/main/java/com/lzf/easyfloat/widget/appfloat/AppFloatManager.kt
rename to easyfloat/src/main/java/com/lzf/easyfloat/core/FloatingWindowHelper.kt
index f66a080..6e00437 100644
--- a/easyfloat/src/main/java/com/lzf/easyfloat/widget/appfloat/AppFloatManager.kt
+++ b/easyfloat/src/main/java/com/lzf/easyfloat/core/FloatingWindowHelper.kt
@@ -1,37 +1,41 @@
-package com.lzf.easyfloat.widget.appfloat
+package com.lzf.easyfloat.core
import android.animation.Animator
import android.annotation.SuppressLint
+import android.app.Activity
import android.app.Service
import android.content.Context
import android.graphics.PixelFormat
import android.graphics.Rect
import android.os.Build
+import android.os.IBinder
import android.view.*
-import com.lzf.easyfloat.anim.AppFloatAnimatorManager
+import android.view.WindowManager.LayoutParams.*
+import android.widget.EditText
+import com.lzf.easyfloat.anim.AnimatorManager
import com.lzf.easyfloat.data.FloatConfig
import com.lzf.easyfloat.enums.ShowPattern
import com.lzf.easyfloat.interfaces.OnFloatTouchListener
import com.lzf.easyfloat.utils.DisplayUtils
+import com.lzf.easyfloat.utils.InputMethodUtils
import com.lzf.easyfloat.utils.LifecycleUtils
import com.lzf.easyfloat.utils.Logger
+import com.lzf.easyfloat.widget.ParentFrameLayout
/**
- * @author: liuzhenfeng
- * @function: 系统浮窗的管理类,包括浮窗的创建、销毁、动画管理等
- * @date: 2019-07-29 16:29
+ * @author: Liuzhenfeng
+ * @date: 12/1/20 23:40
+ * @Description:
*/
-internal class AppFloatManager(val context: Context, var config: FloatConfig) {
+internal class FloatingWindowHelper(val context: Context, var config: FloatConfig) {
lateinit var windowManager: WindowManager
lateinit var params: WindowManager.LayoutParams
var frameLayout: ParentFrameLayout? = null
private lateinit var touchUtils: TouchUtils
+ private var enterAnimator: Animator? = null
- /**
- * 创建系统浮窗
- */
- fun createFloat() = try {
+ fun createWindow() = try {
touchUtils = TouchUtils(context, config)
initParams()
addView()
@@ -44,18 +48,31 @@ internal class AppFloatManager(val context: Context, var config: FloatConfig) {
private fun initParams() {
windowManager = context.getSystemService(Service.WINDOW_SERVICE) as WindowManager
params = WindowManager.LayoutParams().apply {
- // 安卓6.0 以后,全局的Window类别,必须使用TYPE_APPLICATION_OVERLAY
- type =
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY else WindowManager.LayoutParams.TYPE_PHONE
+ if (config.showPattern == ShowPattern.CURRENT_ACTIVITY) {
+ // 设置窗口类型为应用子窗口,和PopupWindow同类型
+ type = TYPE_APPLICATION_PANEL
+ // 子窗口必须和创建它的Activity的windowToken绑定
+ token = getToken()
+ } else {
+ // 系统全局窗口,可覆盖在任何应用之上,以及单独显示在桌面上
+ // 安卓6.0 以后,全局的Window类别,必须使用TYPE_APPLICATION_OVERLAY
+ type = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) TYPE_APPLICATION_OVERLAY
+ else TYPE_PHONE
+ }
format = PixelFormat.RGBA_8888
gravity = Gravity.START or Gravity.TOP
// 设置浮窗以外的触摸事件可以传递给后面的窗口、不自动获取焦点
- flags =
- WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- width =
- if (config.widthMatch) WindowManager.LayoutParams.MATCH_PARENT else WindowManager.LayoutParams.WRAP_CONTENT
- height =
- if (config.heightMatch) WindowManager.LayoutParams.MATCH_PARENT else WindowManager.LayoutParams.WRAP_CONTENT
+ flags = if (config.immersionStatusBar)
+ // 没有边界限制,允许窗口扩展到屏幕外
+ FLAG_NOT_TOUCH_MODAL or FLAG_NOT_FOCUSABLE or FLAG_LAYOUT_NO_LIMITS
+ else FLAG_NOT_TOUCH_MODAL or FLAG_NOT_FOCUSABLE
+ width = if (config.widthMatch) MATCH_PARENT else WRAP_CONTENT
+ height = if (config.heightMatch) MATCH_PARENT else WRAP_CONTENT
+
+ if (config.immersionStatusBar && config.heightMatch) {
+ height = DisplayUtils.getScreenSize(context).y
+ }
+
// 如若设置了固定坐标,直接定位
if (config.locationPair != Pair(0, 0)) {
x = config.locationPair.first
@@ -64,6 +81,11 @@ internal class AppFloatManager(val context: Context, var config: FloatConfig) {
}
}
+ private fun getToken(): IBinder? {
+ val activity = if (context is Activity) context else LifecycleUtils.getTopActivity()
+ return activity?.window?.decorView?.windowToken
+ }
+
/**
* 将自定义的布局,作为xml布局的父布局,添加到windowManager中,
* 重写自定义布局的touch事件,实现拖拽效果。
@@ -95,7 +117,10 @@ internal class AppFloatManager(val context: Context, var config: FloatConfig) {
if (filterSelf
|| (showPattern == ShowPattern.BACKGROUND && LifecycleUtils.isForeground())
|| (showPattern == ShowPattern.FOREGROUND && !LifecycleUtils.isForeground())
- ) setVisible(View.GONE) else enterAnim(floatingView)
+ ) {
+ setVisible(View.GONE)
+ initEditText()
+ } else enterAnim(floatingView)
// 设置callbacks
layoutView = floatingView
@@ -107,6 +132,25 @@ internal class AppFloatManager(val context: Context, var config: FloatConfig) {
}
}
+ private fun initEditText() {
+ if (config.hasEditText) frameLayout?.let { traverseViewGroup(it) }
+ }
+
+ private fun traverseViewGroup(view: View?) {
+ view?.let {
+ // 遍历ViewGroup,是子view判断是否是EditText,是ViewGroup递归调用
+ if (it is ViewGroup) for (i in 0 until it.childCount) {
+ val child = it.getChildAt(i)
+ if (child is ViewGroup) traverseViewGroup(child) else checkEditText(child)
+ } else checkEditText(it)
+ }
+ }
+
+ private fun checkEditText(view: View) {
+ if (view is EditText) InputMethodUtils.initInputMethod(view, config.floatTag)
+ }
+
+
/**
* 设置浮窗的对齐方式,支持上下左右、居中、上中、下中、左中和右中,默认左上角
* 支持手动设置的偏移量
@@ -117,7 +161,13 @@ internal class AppFloatManager(val context: Context, var config: FloatConfig) {
val parentRect = Rect()
// 获取浮窗所在的矩形
windowManager.defaultDisplay.getRectSize(parentRect)
- val parentBottom = parentRect.bottom - DisplayUtils.statusBarHeight(view)
+ val location = IntArray(2)
+ // 获取在整个屏幕内的绝对坐标
+ view.getLocationOnScreen(location)
+ // 通过绝对高度和相对高度比较,判断包含顶部状态栏
+ val statusBarHeight = if (location[1] > params.y) DisplayUtils.statusBarHeight(view) else 0
+ val parentBottom =
+ config.displayHeight.getDisplayRealHeight(context) - statusBarHeight
when (config.gravity) {
// 右上
Gravity.END, Gravity.END or Gravity.TOP, Gravity.RIGHT, Gravity.RIGHT or Gravity.TOP ->
@@ -159,6 +209,16 @@ internal class AppFloatManager(val context: Context, var config: FloatConfig) {
// 设置偏移量
params.x += config.offsetPair.first
params.y += config.offsetPair.second
+
+ if (config.immersionStatusBar) {
+ if (config.showPattern != ShowPattern.CURRENT_ACTIVITY) {
+ params.y -= statusBarHeight
+ }
+ } else {
+ if (config.showPattern == ShowPattern.CURRENT_ACTIVITY) {
+ params.y += statusBarHeight
+ }
+ }
// 更新浮窗位置信息
windowManager.updateViewLayout(view, params)
}
@@ -188,32 +248,34 @@ internal class AppFloatManager(val context: Context, var config: FloatConfig) {
*/
private fun enterAnim(floatingView: View) {
if (frameLayout == null || config.isAnim) return
- val manager: AppFloatAnimatorManager? =
- AppFloatAnimatorManager(frameLayout!!, params, windowManager, config)
- val animator: Animator? = manager?.enterAnim()
- if (animator != null) {
- // 可以延伸到屏幕外,动画结束需要去除该属性,不然旋转屏幕可能置于屏幕外部
- params.flags =
- WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
- animator.addListener(object : Animator.AnimatorListener {
- override fun onAnimationRepeat(animation: Animator?) {}
+ enterAnimator = AnimatorManager(frameLayout!!, params, windowManager, config)
+ .enterAnim()?.apply {
+ // 可以延伸到屏幕外,动画结束按需去除该属性,不然旋转屏幕可能置于屏幕外部
+ params.flags =
+ FLAG_NOT_TOUCH_MODAL or FLAG_NOT_FOCUSABLE or FLAG_LAYOUT_NO_LIMITS
- override fun onAnimationEnd(animation: Animator?) {
- config.isAnim = false
- // 不需要延伸到屏幕外了,防止屏幕旋转的时候,浮窗处于屏幕外
- params.flags =
- WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- }
+ addListener(object : Animator.AnimatorListener {
+ override fun onAnimationRepeat(animation: Animator?) {}
- override fun onAnimationCancel(animation: Animator?) {}
+ override fun onAnimationEnd(animation: Animator?) {
+ config.isAnim = false
+ if (!config.immersionStatusBar) {
+ // 不需要延伸到屏幕外了,防止屏幕旋转的时候,浮窗处于屏幕外
+ params.flags = FLAG_NOT_TOUCH_MODAL or FLAG_NOT_FOCUSABLE
+ }
+ initEditText()
+ }
- override fun onAnimationStart(animation: Animator?) {
- floatingView.visibility = View.VISIBLE
- config.isAnim = true
- }
- })
- animator.start()
- } else {
+ override fun onAnimationCancel(animation: Animator?) {}
+
+ override fun onAnimationStart(animation: Animator?) {
+ floatingView.visibility = View.VISIBLE
+ config.isAnim = true
+ }
+ })
+ start()
+ }
+ if (enterAnimator == null) {
floatingView.visibility = View.VISIBLE
windowManager.updateViewLayout(floatingView, params)
}
@@ -223,23 +285,23 @@ internal class AppFloatManager(val context: Context, var config: FloatConfig) {
* 退出动画
*/
fun exitAnim() {
- if (frameLayout == null || config.isAnim) return
- val manager: AppFloatAnimatorManager? =
- AppFloatAnimatorManager(frameLayout!!, params, windowManager, config)
- val animator: Animator? = manager?.exitAnim()
- if (animator == null) floatOver() else {
- params.flags =
- WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
+ if (frameLayout == null || (config.isAnim && enterAnimator == null)) return
+ enterAnimator?.cancel()
+ val animator: Animator? =
+ AnimatorManager(frameLayout!!, params, windowManager, config).exitAnim()
+ if (animator == null) remove() else {
+ // 二次判断,防止重复调用引发异常
+ if (config.isAnim) return
+ config.isAnim = true
+ params.flags = FLAG_NOT_TOUCH_MODAL or FLAG_NOT_FOCUSABLE or FLAG_LAYOUT_NO_LIMITS
animator.addListener(object : Animator.AnimatorListener {
override fun onAnimationRepeat(animation: Animator?) {}
- override fun onAnimationEnd(animation: Animator?) = floatOver()
+ override fun onAnimationEnd(animation: Animator?) = remove()
override fun onAnimationCancel(animation: Animator?) {}
- override fun onAnimationStart(animation: Animator?) {
- config.isAnim = true
- }
+ override fun onAnimationStart(animation: Animator?) {}
})
animator.start()
}
@@ -248,10 +310,11 @@ internal class AppFloatManager(val context: Context, var config: FloatConfig) {
/**
* 退出动画执行结束/没有退出动画,进行回调、移除等操作
*/
- private fun floatOver() = try {
+ fun remove(force: Boolean = false) = try {
config.isAnim = false
- FloatManager.remove(config.floatTag)
- windowManager.removeView(frameLayout)
+ FloatingWindowManager.remove(config.floatTag)
+ // removeView是异步删除,在Activity销毁的时候会导致窗口泄漏,所以使用removeViewImmediate直接删除view
+ windowManager.run { if (force) removeViewImmediate(frameLayout) else removeView(frameLayout) }
} catch (e: Exception) {
Logger.e("浮窗关闭出现异常:$e")
}
diff --git a/easyfloat/src/main/java/com/lzf/easyfloat/core/FloatingWindowManager.kt b/easyfloat/src/main/java/com/lzf/easyfloat/core/FloatingWindowManager.kt
new file mode 100644
index 0000000..b21c226
--- /dev/null
+++ b/easyfloat/src/main/java/com/lzf/easyfloat/core/FloatingWindowManager.kt
@@ -0,0 +1,74 @@
+package com.lzf.easyfloat.core
+
+import android.content.Context
+import android.view.View
+import com.lzf.easyfloat.WARN_REPEATED_TAG
+import com.lzf.easyfloat.data.FloatConfig
+import com.lzf.easyfloat.utils.Logger
+import java.util.concurrent.ConcurrentHashMap
+
+/**
+ * @author: Liuzhenfeng
+ * @date: 12/1/20 23:36
+ * @Description:
+ */
+internal object FloatingWindowManager {
+
+ private const val DEFAULT_TAG = "default"
+ val windowMap = ConcurrentHashMap()
+// val map :ConcurrentHashMap
+// val list = java.util.Collections.synchronizedList(mutableListOf())
+
+ /**
+ * 创建浮窗,tag不存在创建,tag存在创建失败
+ * 创建结果通过tag添加到相应的map进行管理
+ */
+ fun create(context: Context, config: FloatConfig) = if (!checkTag(config)) {
+ windowMap[config.floatTag!!] =
+ FloatingWindowHelper(context, config).apply { createWindow() }
+ } else {
+ // 存在相同的tag,直接创建失败
+ config.callbacks?.createdResult(false, WARN_REPEATED_TAG, null)
+ Logger.w(WARN_REPEATED_TAG)
+ }
+
+ /**
+ * 关闭浮窗,执行浮窗的退出动画
+ */
+ fun dismiss(tag: String? = null, force: Boolean = false) =
+ getHelper(tag)?.run { if (force) remove(force) else exitAnim() }
+
+ /**
+ * 移除当条浮窗信息,在退出完成后调用
+ */
+ fun remove(floatTag: String?) = windowMap.remove(getTag(floatTag))
+
+ /**
+ * 设置浮窗的显隐,用户主动调用隐藏时,needShow需要为false
+ */
+ fun visible(
+ isShow: Boolean,
+ tag: String? = null,
+ needShow: Boolean = windowMap[tag]?.config?.needShow ?: true
+ ) = getHelper(tag)?.setVisible(if (isShow) View.VISIBLE else View.GONE, needShow)
+
+ /**
+ * 检测浮窗的tag是否有效,不同的浮窗必须设置不同的tag
+ */
+ private fun checkTag(config: FloatConfig): Boolean {
+ // 如果未设置tag,设置默认tag
+ config.floatTag = getTag(config.floatTag)
+ return windowMap.containsKey(config.floatTag!!)
+ }
+
+ /**
+ * 获取浮窗tag,为空则使用默认值
+ */
+ private fun getTag(tag: String?) = tag ?: DEFAULT_TAG
+
+ /**
+ * 获取具体的系统浮窗管理类
+ */
+ fun getHelper(tag: String?) = windowMap[getTag(tag)]
+
+}
\ No newline at end of file
diff --git a/easyfloat/src/main/java/com/lzf/easyfloat/widget/appfloat/TouchUtils.kt b/easyfloat/src/main/java/com/lzf/easyfloat/core/TouchUtils.kt
similarity index 85%
rename from easyfloat/src/main/java/com/lzf/easyfloat/widget/appfloat/TouchUtils.kt
rename to easyfloat/src/main/java/com/lzf/easyfloat/core/TouchUtils.kt
index 17f22dd..8bbf92c 100644
--- a/easyfloat/src/main/java/com/lzf/easyfloat/widget/appfloat/TouchUtils.kt
+++ b/easyfloat/src/main/java/com/lzf/easyfloat/core/TouchUtils.kt
@@ -1,4 +1,4 @@
-package com.lzf.easyfloat.widget.appfloat
+package com.lzf.easyfloat.core
import android.animation.Animator
import android.animation.ValueAnimator
@@ -9,6 +9,7 @@ import android.view.View
import android.view.WindowManager
import android.view.WindowManager.LayoutParams
import com.lzf.easyfloat.data.FloatConfig
+import com.lzf.easyfloat.enums.ShowPattern
import com.lzf.easyfloat.enums.SidePattern
import com.lzf.easyfloat.utils.DisplayUtils
import kotlin.math.min
@@ -41,13 +42,11 @@ internal class TouchUtils(val context: Context, val config: FloatConfig) {
private var minX = 0
private var minY = 0
private val location = IntArray(2)
+ private var statusBarHeight = 0
// 屏幕可用高度 - 浮窗自身高度 的剩余高度
private var emptyHeight = 0
- // 是否包含状态栏
- private var hasStatusBar = true
-
/**
* 根据吸附模式,实现相应的拖拽效果
*/
@@ -77,8 +76,8 @@ internal class TouchUtils(val context: Context, val config: FloatConfig) {
// 获取在整个屏幕内的绝对坐标
view.getLocationOnScreen(location)
// 通过绝对高度和相对高度比较,判断包含顶部状态栏
- hasStatusBar = location[1] > params.y
- emptyHeight = parentHeight - view.height
+ statusBarHeight = if (location[1] > params.y) statusBarHeight(view) else 0
+ emptyHeight = parentHeight - view.height - statusBarHeight
}
MotionEvent.ACTION_MOVE -> {
@@ -97,15 +96,19 @@ internal class TouchUtils(val context: Context, val config: FloatConfig) {
x > parentWidth - view.width -> parentWidth - view.width
else -> x
}
+
+ if (config.showPattern == ShowPattern.CURRENT_ACTIVITY) {
+ // 单页面浮窗,设置状态栏不沉浸时,最小高度为状态栏高度
+ if (y < statusBarHeight(view) && !config.immersionStatusBar) y =
+ statusBarHeight(view)
+ }
+
y = when {
- y < 0 -> 0
- y > emptyHeight - statusBarHeight(view) -> {
- when {
- hasStatusBar -> emptyHeight - statusBarHeight(view)
- y > emptyHeight -> emptyHeight
- else -> y
- }
- }
+ // 状态栏沉浸时,最小高度为-statusBarHeight,反之最小高度为0
+ y < 0 -> if (config.immersionStatusBar) {
+ if (y < -statusBarHeight) -statusBarHeight else y
+ } else 0
+ y > emptyHeight -> emptyHeight
else -> y
}
@@ -113,7 +116,7 @@ internal class TouchUtils(val context: Context, val config: FloatConfig) {
SidePattern.LEFT -> x = 0
SidePattern.RIGHT -> x = parentWidth - view.width
SidePattern.TOP -> y = 0
- SidePattern.BOTTOM -> y = parentHeight - view.height
+ SidePattern.BOTTOM -> y = emptyHeight
SidePattern.AUTO_HORIZONTAL ->
x = if (event.rawX * 2 > parentWidth) parentWidth - view.width else 0
@@ -133,7 +136,7 @@ internal class TouchUtils(val context: Context, val config: FloatConfig) {
if (minX < minY) {
x = if (leftDistance == minX) 0 else parentWidth - view.width
} else {
- y = if (topDistance == minY) 0 else parentHeight - view.height
+ y = if (topDistance == minY) 0 else emptyHeight
}
}
else -> {
@@ -153,6 +156,9 @@ internal class TouchUtils(val context: Context, val config: FloatConfig) {
MotionEvent.ACTION_UP -> {
if (!config.isDrag) return
+ // 回调拖拽事件的ACTION_UP
+ config.callbacks?.drag(view, event)
+ config.floatCallbacks?.builder?.drag?.invoke(view, event)
when (config.sidePattern) {
SidePattern.RESULT_LEFT,
SidePattern.RESULT_RIGHT,
@@ -195,18 +201,16 @@ internal class TouchUtils(val context: Context, val config: FloatConfig) {
SidePattern.RESULT_TOP -> {
isX = false
- 0
+ if (config.immersionStatusBar) -statusBarHeight else 0
}
SidePattern.RESULT_BOTTOM -> {
isX = false
// 不要轻易使用此相关模式,需要考虑虚拟导航栏的情况
- if (hasStatusBar) emptyHeight - statusBarHeight(view) else emptyHeight
+ emptyHeight
}
SidePattern.RESULT_VERTICAL -> {
isX = false
- if (topDistance < bottomDistance) 0 else {
- if (hasStatusBar) emptyHeight - statusBarHeight(view) else emptyHeight
- }
+ if (topDistance < bottomDistance) 0 else emptyHeight
}
SidePattern.RESULT_SIDE -> {
@@ -215,9 +219,7 @@ internal class TouchUtils(val context: Context, val config: FloatConfig) {
if (leftDistance < rightDistance) 0 else params.x + rightDistance
} else {
isX = false
- if (topDistance < bottomDistance) 0 else {
- if (hasStatusBar) emptyHeight - statusBarHeight(view) else emptyHeight
- }
+ if (topDistance < bottomDistance) if (config.immersionStatusBar) -statusBarHeight else 0 else emptyHeight
}
}
else -> return
@@ -264,9 +266,7 @@ internal class TouchUtils(val context: Context, val config: FloatConfig) {
leftDistance = params.x
rightDistance = parentWidth - (leftDistance + view.right)
topDistance = params.y
- bottomDistance = if (hasStatusBar) {
- parentHeight - statusBarHeight(view) - topDistance - view.height
- } else parentHeight - topDistance - view.height
+ bottomDistance = emptyHeight - topDistance
minX = min(leftDistance, rightDistance)
minY = min(topDistance, bottomDistance)
diff --git a/easyfloat/src/main/java/com/lzf/easyfloat/data/FloatConfig.kt b/easyfloat/src/main/java/com/lzf/easyfloat/data/FloatConfig.kt
index ecf7c15..b7fd641 100644
--- a/easyfloat/src/main/java/com/lzf/easyfloat/data/FloatConfig.kt
+++ b/easyfloat/src/main/java/com/lzf/easyfloat/data/FloatConfig.kt
@@ -1,7 +1,6 @@
package com.lzf.easyfloat.data
import android.view.View
-import com.lzf.easyfloat.anim.AppFloatDefaultAnimator
import com.lzf.easyfloat.anim.DefaultAnimator
import com.lzf.easyfloat.enums.ShowPattern
import com.lzf.easyfloat.enums.SidePattern
@@ -32,6 +31,8 @@ data class FloatConfig(
var isShow: Boolean = false,
// 是否包含EditText
var hasEditText: Boolean = false,
+ // 状态栏沉浸
+ var immersionStatusBar: Boolean = false,
// 浮窗的吸附方式(默认不吸附,拖到哪里是哪里)
var sidePattern: SidePattern = SidePattern.DEFAULT,
@@ -59,7 +60,6 @@ data class FloatConfig(
// 出入动画
var floatAnimator: OnFloatAnimator? = DefaultAnimator(),
- var appFloatAnimator: OnAppFloatAnimator? = AppFloatDefaultAnimator(),
// 设置屏幕的有效显示高度(不包含虚拟导航栏的高度),仅针对系统浮窗,一般不用复写
var displayHeight: OnDisplayHeight = DefaultDisplayHeight(),
diff --git a/easyfloat/src/main/java/com/lzf/easyfloat/interfaces/OnAppFloatAnimator.kt b/easyfloat/src/main/java/com/lzf/easyfloat/interfaces/OnAppFloatAnimator.kt
deleted file mode 100644
index 5b16297..0000000
--- a/easyfloat/src/main/java/com/lzf/easyfloat/interfaces/OnAppFloatAnimator.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-package com.lzf.easyfloat.interfaces
-
-import android.animation.Animator
-import android.view.View
-import android.view.WindowManager
-import com.lzf.easyfloat.enums.SidePattern
-
-/**
- * @author: liuzhenfeng
- * @function: 系统浮窗的出入动画
- * @date: 2019-07-22 16:40
- */
-interface OnAppFloatAnimator {
-
- fun enterAnim(
- view: View,
- params: WindowManager.LayoutParams,
- windowManager: WindowManager,
- sidePattern: SidePattern
- ): Animator? = null
-
- fun exitAnim(
- view: View,
- params: WindowManager.LayoutParams,
- windowManager: WindowManager,
- sidePattern: SidePattern
- ): Animator? = null
-
-}
\ No newline at end of file
diff --git a/easyfloat/src/main/java/com/lzf/easyfloat/interfaces/OnFloatAnimator.kt b/easyfloat/src/main/java/com/lzf/easyfloat/interfaces/OnFloatAnimator.kt
index 5ee022c..f4e3150 100644
--- a/easyfloat/src/main/java/com/lzf/easyfloat/interfaces/OnFloatAnimator.kt
+++ b/easyfloat/src/main/java/com/lzf/easyfloat/interfaces/OnFloatAnimator.kt
@@ -2,19 +2,28 @@ package com.lzf.easyfloat.interfaces
import android.animation.Animator
import android.view.View
-import android.view.ViewGroup
+import android.view.WindowManager
import com.lzf.easyfloat.enums.SidePattern
/**
* @author: liuzhenfeng
- * @function: 浮窗动画的接口类,实现该接口,可自定义不同的动画效果(策略模式)
- * @date: 2019-07-19 14:14
+ * @function: 系统浮窗的出入动画
+ * @date: 2019-07-22 16:40
*/
interface OnFloatAnimator {
- // 入场动画
- fun enterAnim(view: View, parentView: ViewGroup, sidePattern: SidePattern): Animator? = null
+ fun enterAnim(
+ view: View,
+ params: WindowManager.LayoutParams,
+ windowManager: WindowManager,
+ sidePattern: SidePattern
+ ): Animator? = null
+
+ fun exitAnim(
+ view: View,
+ params: WindowManager.LayoutParams,
+ windowManager: WindowManager,
+ sidePattern: SidePattern
+ ): Animator? = null
- // 退出动画
- fun exitAnim(view: View, parentView: ViewGroup, sidePattern: SidePattern): Animator? = null
}
\ No newline at end of file
diff --git a/easyfloat/src/main/java/com/lzf/easyfloat/interfaces/OnTouchRangeListener.kt b/easyfloat/src/main/java/com/lzf/easyfloat/interfaces/OnTouchRangeListener.kt
new file mode 100644
index 0000000..a123aca
--- /dev/null
+++ b/easyfloat/src/main/java/com/lzf/easyfloat/interfaces/OnTouchRangeListener.kt
@@ -0,0 +1,23 @@
+package com.lzf.easyfloat.interfaces
+
+import com.lzf.easyfloat.widget.switch.BaseSwitchView
+
+/**
+ * @author: liuzhenfeng
+ * @date: 2020/10/25 20:25
+ * @Package: com.lzf.easyfloat.interfaces
+ * @Description: 区域触摸事件回调
+ */
+interface OnTouchRangeListener {
+
+ /**
+ * 手指触摸到指定区域
+ */
+ fun touchInRange(inRange: Boolean, view: BaseSwitchView)
+
+ /**
+ * 在指定区域抬起手指
+ */
+ fun touchUpInRange()
+
+}
\ No newline at end of file
diff --git a/easyfloat/src/main/java/com/lzf/easyfloat/permission/PermissionFragment.kt b/easyfloat/src/main/java/com/lzf/easyfloat/permission/PermissionFragment.kt
index 5b4a3e8..f5c7853 100644
--- a/easyfloat/src/main/java/com/lzf/easyfloat/permission/PermissionFragment.kt
+++ b/easyfloat/src/main/java/com/lzf/easyfloat/permission/PermissionFragment.kt
@@ -44,6 +44,7 @@ internal class PermissionFragment : Fragment() {
Logger.i("PermissionFragment onActivityResult: $check")
// 回调权限结果
onPermissionResult?.permissionResult(check)
+ onPermissionResult = null
// 将Fragment移除
fragmentManager.beginTransaction().remove(this).commitAllowingStateLoss()
}, 500)
diff --git a/easyfloat/src/main/java/com/lzf/easyfloat/permission/PermissionUtils.kt b/easyfloat/src/main/java/com/lzf/easyfloat/permission/PermissionUtils.kt
index 010618a..a904c44 100644
--- a/easyfloat/src/main/java/com/lzf/easyfloat/permission/PermissionUtils.kt
+++ b/easyfloat/src/main/java/com/lzf/easyfloat/permission/PermissionUtils.kt
@@ -46,11 +46,11 @@ object PermissionUtils {
internal fun requestPermission(fragment: Fragment) =
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) when {
- RomUtils.checkIsHuaweiRom() -> HuaweiUtils.applyPermission(fragment.activity)
- RomUtils.checkIsMiuiRom() -> MiuiUtils.applyMiuiPermission(fragment.activity)
- RomUtils.checkIsOppoRom() -> OppoUtils.applyOppoPermission(fragment.activity)
+ RomUtils.checkIsHuaweiRom() -> HuaweiUtils.applyPermission(fragment)
+ RomUtils.checkIsMiuiRom() -> MiuiUtils.applyMiuiPermission(fragment)
+ RomUtils.checkIsOppoRom() -> OppoUtils.applyOppoPermission(fragment)
RomUtils.checkIsMeizuRom() -> MeizuUtils.applyPermission(fragment)
- RomUtils.checkIs360Rom() -> QikuUtils.applyPermission(fragment.activity)
+ RomUtils.checkIs360Rom() -> QikuUtils.applyPermission(fragment)
else -> Logger.i(TAG, "原生 Android 6.0 以下无需权限申请")
} else commonROMPermissionApply(fragment)
diff --git a/easyfloat/src/main/java/com/lzf/easyfloat/permission/rom/HuaweiUtils.java b/easyfloat/src/main/java/com/lzf/easyfloat/permission/rom/HuaweiUtils.java
index cfb7d69..a696a58 100755
--- a/easyfloat/src/main/java/com/lzf/easyfloat/permission/rom/HuaweiUtils.java
+++ b/easyfloat/src/main/java/com/lzf/easyfloat/permission/rom/HuaweiUtils.java
@@ -5,6 +5,7 @@
import android.annotation.TargetApi;
import android.app.AppOpsManager;
+import android.app.Fragment;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
@@ -14,6 +15,8 @@
import android.util.Log;
import android.widget.Toast;
+import com.lzf.easyfloat.permission.PermissionUtils;
+
import java.lang.reflect.Method;
public class HuaweiUtils {
@@ -33,50 +36,47 @@ public static boolean checkFloatWindowPermission(Context context) {
/**
* 去华为权限申请页面
*/
- public static void applyPermission(Context context) {
+ public static void applyPermission(Fragment fragment) {
try {
Intent intent = new Intent();
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-// ComponentName comp = new ComponentName("com.huawei.systemmanager","com.huawei.permissionmanager.ui.MainActivity");//华为权限管理
-// ComponentName comp = new ComponentName("com.huawei.systemmanager",
-// "com.huawei.permissionmanager.ui.SingleAppActivity");//华为权限管理,跳转到指定app的权限管理位置需要华为接口权限,未解决
+ //华为权限管理,跳转到指定app的权限管理位置需要华为接口权限,未解决
ComponentName comp = new ComponentName("com.huawei.systemmanager", "com.huawei.systemmanager.addviewmonitor.AddViewMonitorActivity");//悬浮窗管理页面
intent.setComponent(comp);
if (RomUtils.getEmuiVersion() == 3.1) {
//emui 3.1 的适配
- context.startActivity(intent);
+ fragment.startActivityForResult(intent, PermissionUtils.requestCode);
} else {
//emui 3.0 的适配
comp = new ComponentName("com.huawei.systemmanager", "com.huawei.notificationmanager.ui.NotificationManagmentActivity");//悬浮窗管理页面
intent.setComponent(comp);
- context.startActivity(intent);
+ fragment.startActivityForResult(intent, PermissionUtils.requestCode);
}
} catch (SecurityException e) {
Intent intent = new Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-// ComponentName comp = new ComponentName("com.huawei.systemmanager","com.huawei.permissionmanager.ui.MainActivity");//华为权限管理
+ //华为权限管理
ComponentName comp = new ComponentName("com.huawei.systemmanager",
- "com.huawei.permissionmanager.ui.MainActivity");//华为权限管理,跳转到本app的权限管理页面,这个需要华为接口权限,未解决
-// ComponentName comp = new ComponentName("com.huawei.systemmanager","com.huawei.systemmanager.addviewmonitor.AddViewMonitorActivity");//悬浮窗管理页面
+ "com.huawei.permissionmanager.ui.MainActivity");
+ //华为权限管理,跳转到本app的权限管理页面,这个需要华为接口权限,未解决
+ // 悬浮窗管理页面
intent.setComponent(comp);
- context.startActivity(intent);
+ fragment.startActivityForResult(intent, PermissionUtils.requestCode);
Log.e(TAG, Log.getStackTraceString(e));
} catch (ActivityNotFoundException e) {
/**
* 手机管家版本较低 HUAWEI SC-UL10
*/
-// Toast.makeText(MainActivity.this, "act找不到", Toast.LENGTH_LONG).show();
Intent intent = new Intent();
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- ComponentName comp = new ComponentName("com.Android.settings", "com.android.settings.permission.TabItem");//权限管理页面 android4.4
-// ComponentName comp = new ComponentName("com.android.settings","com.android.settings.permission.single_app_activity");//此处可跳转到指定app对应的权限管理页面,但是需要相关权限,未解决
+ //权限管理页面 android4.4
+ ComponentName comp = new ComponentName("com.Android.settings", "com.android.settings.permission.TabItem");
+ //此处可跳转到指定app对应的权限管理页面,但是需要相关权限,未解决
intent.setComponent(comp);
- context.startActivity(intent);
+ fragment.startActivityForResult(intent, PermissionUtils.requestCode);
e.printStackTrace();
Log.e(TAG, Log.getStackTraceString(e));
} catch (Exception e) {
//抛出异常时提示信息
- Toast.makeText(context, "进入设置页面失败,请手动设置", Toast.LENGTH_LONG).show();
+ Toast.makeText(fragment.getActivity(), "进入设置页面失败,请手动设置", Toast.LENGTH_LONG).show();
Log.e(TAG, Log.getStackTraceString(e));
}
}
diff --git a/easyfloat/src/main/java/com/lzf/easyfloat/permission/rom/MeizuUtils.java b/easyfloat/src/main/java/com/lzf/easyfloat/permission/rom/MeizuUtils.java
index 21476fc..b8702c1 100755
--- a/easyfloat/src/main/java/com/lzf/easyfloat/permission/rom/MeizuUtils.java
+++ b/easyfloat/src/main/java/com/lzf/easyfloat/permission/rom/MeizuUtils.java
@@ -26,7 +26,8 @@ public class MeizuUtils {
public static boolean checkFloatWindowPermission(Context context) {
final int version = Build.VERSION.SDK_INT;
if (version >= 19) {
- return checkOp(context, 24); //OP_SYSTEM_ALERT_WINDOW = 24;
+ // OP_SYSTEM_ALERT_WINDOW = 24;
+ return checkOp(context, 24);
}
return true;
}
@@ -37,7 +38,6 @@ public static boolean checkFloatWindowPermission(Context context) {
public static void applyPermission(Fragment fragment) {
try {
Intent intent = new Intent("com.meizu.safe.security.SHOW_APPSEC");
-// intent.setClassName("com.meizu.safe", "com.meizu.safe.security.AppSecActivity");//remove this line code for fix flyme6.3
intent.putExtra("packageName", fragment.getActivity().getPackageName());
fragment.startActivityForResult(intent, PermissionUtils.requestCode);
} catch (Exception e) {
diff --git a/easyfloat/src/main/java/com/lzf/easyfloat/permission/rom/MiuiUtils.java b/easyfloat/src/main/java/com/lzf/easyfloat/permission/rom/MiuiUtils.java
index 40cd9b6..3578d29 100755
--- a/easyfloat/src/main/java/com/lzf/easyfloat/permission/rom/MiuiUtils.java
+++ b/easyfloat/src/main/java/com/lzf/easyfloat/permission/rom/MiuiUtils.java
@@ -5,6 +5,7 @@
import android.annotation.TargetApi;
import android.app.AppOpsManager;
+import android.app.Fragment;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -14,6 +15,8 @@
import android.provider.Settings;
import android.util.Log;
+import com.lzf.easyfloat.permission.PermissionUtils;
+
import java.lang.reflect.Method;
public class MiuiUtils {
@@ -42,15 +45,9 @@ public static int getMiuiVersion() {
*/
public static boolean checkFloatWindowPermission(Context context) {
final int version = Build.VERSION.SDK_INT;
-
if (version >= 19) {
return checkOp(context, 24); //OP_SYSTEM_ALERT_WINDOW = 24;
} else {
-// if ((context.getApplicationInfo().flags & 1 << 27) == 1) {
-// return true;
-// } else {
-// return false;
-// }
return true;
}
}
@@ -76,16 +73,16 @@ private static boolean checkOp(Context context, int op) {
/**
* 小米 ROM 权限申请
*/
- public static void applyMiuiPermission(Context context) {
+ public static void applyMiuiPermission(Fragment fragment) {
int versionCode = getMiuiVersion();
if (versionCode == 5) {
- goToMiuiPermissionActivity_V5(context);
+ goToMiuiPermissionActivity_V5(fragment);
} else if (versionCode == 6) {
- goToMiuiPermissionActivity_V6(context);
+ goToMiuiPermissionActivity_V6(fragment);
} else if (versionCode == 7) {
- goToMiuiPermissionActivity_V7(context);
+ goToMiuiPermissionActivity_V7(fragment);
} else if (versionCode >= 8) {
- goToMiuiPermissionActivity_V8(context);
+ goToMiuiPermissionActivity_V8(fragment);
} else {
Log.e(TAG, "this is a special MIUI rom version, its version code " + versionCode);
}
@@ -101,49 +98,27 @@ private static boolean isIntentAvailable(Intent intent, Context context) {
/**
* 小米 V5 版本 ROM权限申请
*/
- public static void goToMiuiPermissionActivity_V5(Context context) {
- Intent intent = null;
- String packageName = context.getPackageName();
- intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+ public static void goToMiuiPermissionActivity_V5(Fragment fragment) {
+ String packageName = fragment.getActivity().getPackageName();
+ Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", packageName, null);
intent.setData(uri);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- if (isIntentAvailable(intent, context)) {
- context.startActivity(intent);
+ if (isIntentAvailable(intent, fragment.getActivity())) {
+ fragment.startActivityForResult(intent, PermissionUtils.requestCode);
} else {
Log.e(TAG, "intent is not available!");
}
-
- //设置页面在应用详情页面
-// Intent intent = new Intent("miui.intent.action.APP_PERM_EDITOR");
-// PackageInfo pInfo = null;
-// try {
-// pInfo = context.getPackageManager().getPackageInfo
-// (HostInterfaceManager.getHostInterface().getApp().getPackageName(), 0);
-// } catch (PackageManager.NameNotFoundException e) {
-// AVLogUtils.e(TAG, e.getMessage());
-// }
-// intent.setClassName("com.android.settings", "com.miui.securitycenter.permission.AppPermissionsEditor");
-// intent.putExtra("extra_package_uid", pInfo.applicationInfo.uid);
-// intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-// if (isIntentAvailable(intent, context)) {
-// context.startActivity(intent);
-// } else {
-// AVLogUtils.e(TAG, "Intent is not available!");
-// }
}
/**
* 小米 V6 版本 ROM权限申请
*/
- public static void goToMiuiPermissionActivity_V6(Context context) {
+ public static void goToMiuiPermissionActivity_V6(Fragment fragment) {
Intent intent = new Intent("miui.intent.action.APP_PERM_EDITOR");
intent.setClassName("com.miui.securitycenter", "com.miui.permcenter.permissions.AppPermissionsEditorActivity");
- intent.putExtra("extra_pkgname", context.getPackageName());
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
- if (isIntentAvailable(intent, context)) {
- context.startActivity(intent);
+ intent.putExtra("extra_pkgname", fragment.getActivity().getPackageName());
+ if (isIntentAvailable(intent, fragment.getActivity())) {
+ fragment.startActivityForResult(intent, PermissionUtils.requestCode);
} else {
Log.e(TAG, "Intent is not available!");
}
@@ -152,14 +127,12 @@ public static void goToMiuiPermissionActivity_V6(Context context) {
/**
* 小米 V7 版本 ROM权限申请
*/
- public static void goToMiuiPermissionActivity_V7(Context context) {
+ public static void goToMiuiPermissionActivity_V7(Fragment fragment) {
Intent intent = new Intent("miui.intent.action.APP_PERM_EDITOR");
intent.setClassName("com.miui.securitycenter", "com.miui.permcenter.permissions.AppPermissionsEditorActivity");
- intent.putExtra("extra_pkgname", context.getPackageName());
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
- if (isIntentAvailable(intent, context)) {
- context.startActivity(intent);
+ intent.putExtra("extra_pkgname", fragment.getActivity().getPackageName());
+ if (isIntentAvailable(intent, fragment.getActivity())) {
+ fragment.startActivityForResult(intent, PermissionUtils.requestCode);
} else {
Log.e(TAG, "Intent is not available!");
}
@@ -168,23 +141,18 @@ public static void goToMiuiPermissionActivity_V7(Context context) {
/**
* 小米 V8 版本 ROM权限申请
*/
- public static void goToMiuiPermissionActivity_V8(Context context) {
+ public static void goToMiuiPermissionActivity_V8(Fragment fragment) {
Intent intent = new Intent("miui.intent.action.APP_PERM_EDITOR");
intent.setClassName("com.miui.securitycenter", "com.miui.permcenter.permissions.PermissionsEditorActivity");
-// intent.setPackage("com.miui.securitycenter");
- intent.putExtra("extra_pkgname", context.getPackageName());
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
- if (isIntentAvailable(intent, context)) {
- context.startActivity(intent);
+ intent.putExtra("extra_pkgname", fragment.getActivity().getPackageName());
+ if (isIntentAvailable(intent, fragment.getActivity())) {
+ fragment.startActivityForResult(intent, PermissionUtils.requestCode);
} else {
intent = new Intent("miui.intent.action.APP_PERM_EDITOR");
intent.setPackage("com.miui.securitycenter");
- intent.putExtra("extra_pkgname", context.getPackageName());
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
- if (isIntentAvailable(intent, context)) {
- context.startActivity(intent);
+ intent.putExtra("extra_pkgname", fragment.getActivity().getPackageName());
+ if (isIntentAvailable(intent, fragment.getActivity())) {
+ fragment.startActivityForResult(intent, PermissionUtils.requestCode);
} else {
Log.e(TAG, "Intent is not available!");
}
diff --git a/easyfloat/src/main/java/com/lzf/easyfloat/permission/rom/OppoUtils.java b/easyfloat/src/main/java/com/lzf/easyfloat/permission/rom/OppoUtils.java
index 54a1e10..1238a28 100755
--- a/easyfloat/src/main/java/com/lzf/easyfloat/permission/rom/OppoUtils.java
+++ b/easyfloat/src/main/java/com/lzf/easyfloat/permission/rom/OppoUtils.java
@@ -2,6 +2,7 @@
import android.annotation.TargetApi;
import android.app.AppOpsManager;
+import android.app.Fragment;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -9,6 +10,8 @@
import android.os.Build;
import android.util.Log;
+import com.lzf.easyfloat.permission.PermissionUtils;
+
import java.lang.reflect.Method;
/**
@@ -27,7 +30,8 @@ public class OppoUtils {
public static boolean checkFloatWindowPermission(Context context) {
final int version = Build.VERSION.SDK_INT;
if (version >= 19) {
- return checkOp(context, 24); //OP_SYSTEM_ALERT_WINDOW = 24;
+ // OP_SYSTEM_ALERT_WINDOW = 24;
+ return checkOp(context, 24);
}
return true;
}
@@ -53,17 +57,15 @@ private static boolean checkOp(Context context, int op) {
/**
* oppo ROM 权限申请
*/
- public static void applyOppoPermission(Context context) {
+ public static void applyOppoPermission(Fragment fragment) {
//merge requestPermission from https://github.com/zhaozepeng/FloatWindowPermission/pull/26
try {
Intent intent = new Intent();
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- //com.coloros.safecenter/.sysfloatwindow.FloatWindowListActivity
- ComponentName comp = new ComponentName("com.coloros.safecenter", "com.coloros.safecenter.sysfloatwindow.FloatWindowListActivity");//悬浮窗管理页面
+ //悬浮窗管理页面
+ ComponentName comp = new ComponentName("com.coloros.safecenter", "com.coloros.safecenter.sysfloatwindow.FloatWindowListActivity");
intent.setComponent(comp);
- context.startActivity(intent);
- }
- catch(Exception e){
+ fragment.startActivityForResult(intent, PermissionUtils.requestCode);
+ } catch (Exception e) {
e.printStackTrace();
}
}
diff --git a/easyfloat/src/main/java/com/lzf/easyfloat/permission/rom/QikuUtils.java b/easyfloat/src/main/java/com/lzf/easyfloat/permission/rom/QikuUtils.java
index 7a0f570..8406a24 100755
--- a/easyfloat/src/main/java/com/lzf/easyfloat/permission/rom/QikuUtils.java
+++ b/easyfloat/src/main/java/com/lzf/easyfloat/permission/rom/QikuUtils.java
@@ -5,6 +5,7 @@
import android.annotation.TargetApi;
import android.app.AppOpsManager;
+import android.app.Fragment;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -12,6 +13,8 @@
import android.os.Build;
import android.util.Log;
+import com.lzf.easyfloat.permission.PermissionUtils;
+
import java.lang.reflect.Method;
public class QikuUtils {
@@ -23,7 +26,8 @@ public class QikuUtils {
public static boolean checkFloatWindowPermission(Context context) {
final int version = Build.VERSION.SDK_INT;
if (version >= 19) {
- return checkOp(context, 24); //OP_SYSTEM_ALERT_WINDOW = 24;
+ // OP_SYSTEM_ALERT_WINDOW = 24;
+ return checkOp(context, 24);
}
return true;
}
@@ -36,7 +40,7 @@ private static boolean checkOp(Context context, int op) {
try {
Class clazz = AppOpsManager.class;
Method method = clazz.getDeclaredMethod("checkOp", int.class, int.class, String.class);
- return AppOpsManager.MODE_ALLOWED == (int)method.invoke(manager, op, Binder.getCallingUid(), context.getPackageName());
+ return AppOpsManager.MODE_ALLOWED == (int) method.invoke(manager, op, Binder.getCallingUid(), context.getPackageName());
} catch (Exception e) {
Log.e(TAG, Log.getStackTraceString(e));
}
@@ -49,16 +53,15 @@ private static boolean checkOp(Context context, int op) {
/**
* 去360权限申请页面
*/
- public static void applyPermission(Context context) {
+ public static void applyPermission(Fragment fragment) {
Intent intent = new Intent();
intent.setClassName("com.android.settings", "com.android.settings.Settings$OverlaySettingsActivity");
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- if (isIntentAvailable(intent, context)) {
- context.startActivity(intent);
+ if (isIntentAvailable(intent, fragment.getActivity())) {
+ fragment.startActivityForResult(intent, PermissionUtils.requestCode);
} else {
intent.setClassName("com.qihoo360.mobilesafe", "com.qihoo360.mobilesafe.ui.index.AppEnterActivity");
- if (isIntentAvailable(intent, context)) {
- context.startActivity(intent);
+ if (isIntentAvailable(intent, fragment.getActivity())) {
+ fragment.startActivityForResult(intent, PermissionUtils.requestCode);
} else {
Log.e(TAG, "can't open permission page with particular name, please use " +
"\"adb shell dumpsys activity\" command and tell me the name of the float window permission page");
diff --git a/easyfloat/src/main/java/com/lzf/easyfloat/permission/rom/RomUtils.kt b/easyfloat/src/main/java/com/lzf/easyfloat/permission/rom/RomUtils.kt
index 8cc1327..cb7459d 100644
--- a/easyfloat/src/main/java/com/lzf/easyfloat/permission/rom/RomUtils.kt
+++ b/easyfloat/src/main/java/com/lzf/easyfloat/permission/rom/RomUtils.kt
@@ -31,23 +31,6 @@ object RomUtils {
return 4.0
}
- /**
- * 获取小米 rom 版本号,获取失败返回 -1
- *
- * @return miui rom version code, if fail , return -1
- */
- fun getMiuiVersion(): Int {
- val version = getSystemProperty("ro.miui.ui.version.name")
- if (version != null) {
- try {
- return version.substring(1).toInt()
- } catch (e: Exception) {
- Log.e(TAG, "get miui version code error, version : $version")
- }
- }
- return -1
- }
-
@JvmStatic
fun getSystemProperty(propName: String): String? {
val line: String
diff --git a/easyfloat/src/main/java/com/lzf/easyfloat/utils/DisplayUtils.kt b/easyfloat/src/main/java/com/lzf/easyfloat/utils/DisplayUtils.kt
index bf7bedf..bff7b9c 100644
--- a/easyfloat/src/main/java/com/lzf/easyfloat/utils/DisplayUtils.kt
+++ b/easyfloat/src/main/java/com/lzf/easyfloat/utils/DisplayUtils.kt
@@ -81,7 +81,6 @@ object DisplayUtils {
val resourceId =
resources.getIdentifier("navigation_bar_height", "dimen", "android")
if (resourceId > 0) result = resources.getDimensionPixelSize(resourceId)
- Log.d(TAG, "getNavigationBarHeight = $result")
return result
}
diff --git a/easyfloat/src/main/java/com/lzf/easyfloat/utils/DragUtils.kt b/easyfloat/src/main/java/com/lzf/easyfloat/utils/DragUtils.kt
new file mode 100644
index 0000000..1999df9
--- /dev/null
+++ b/easyfloat/src/main/java/com/lzf/easyfloat/utils/DragUtils.kt
@@ -0,0 +1,177 @@
+package com.lzf.easyfloat.utils
+
+import android.view.*
+import com.lzf.easyfloat.EasyFloat
+import com.lzf.easyfloat.R
+import com.lzf.easyfloat.anim.DefaultAnimator
+import com.lzf.easyfloat.enums.ShowPattern
+import com.lzf.easyfloat.enums.SidePattern
+import com.lzf.easyfloat.interfaces.OnFloatAnimator
+import com.lzf.easyfloat.interfaces.OnTouchRangeListener
+import com.lzf.easyfloat.widget.switch.BaseSwitchView
+
+/**
+ * @author: liuzhenfeng
+ * @date: 2020/10/24 21:29
+ * @Package: com.lzf.easyfloat.utils
+ * @Description: 拖拽打开、关闭浮窗
+ */
+object DragUtils {
+
+ private const val ADD_TAG = "ADD_TAG"
+ private const val CLOSE_TAG = "CLOSE_TAG"
+ private var addView: BaseSwitchView? = null
+ private var closeView: BaseSwitchView? = null
+ private var downX = 0f
+ private var screenWidth = 0
+ private var offset = 0f
+
+ /**
+ * 注册侧滑创建浮窗
+ * @param event Activity 的触摸事件
+ * @param listener 右下角区域触摸事件回调
+ * @param layoutId 右下角区域的布局文件
+ * @param slideOffset 当前屏幕侧滑进度
+ * @param start 动画开始阈值
+ * @param end 动画结束阈值
+ */
+ @JvmOverloads
+ fun registerSwipeAdd(
+ event: MotionEvent?,
+ listener: OnTouchRangeListener? = null,
+ layoutId: Int = R.layout.default_add_layout,
+ slideOffset: Float = -1f,
+ start: Float = 0.1f,
+ end: Float = 0.5f
+ ) {
+ if (event == null) return
+
+ // 设置了侧滑监听,使用侧滑数据
+ if (slideOffset != -1f) {
+ // 如果滑动偏移,超过了动画起始位置,开始显示浮窗,并执行偏移动画
+ if (slideOffset >= start) {
+ val progress = minOf((slideOffset - start) / (end - start), 1f)
+ setAddView(event, progress, listener, layoutId)
+ } else dismissAdd()
+ } else {
+ // 未提供侧滑监听,根据手指坐标信息,判断浮窗信息
+ screenWidth = DisplayUtils.getScreenWidth(LifecycleUtils.application)
+ offset = event.rawX / screenWidth
+ when (event.action) {
+ MotionEvent.ACTION_DOWN -> downX = event.rawX
+ MotionEvent.ACTION_MOVE -> {
+ // 起始值小于最小边界值,并且当前偏离量大于最小边界
+ if (downX < start * screenWidth && offset >= start) {
+ val progress = minOf((offset - start) / (end - start), 1f)
+ setAddView(event, progress, listener, layoutId)
+ } else dismissAdd()
+ }
+ MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
+ downX = 0f
+ setAddView(event, offset, listener, layoutId)
+ }
+ }
+ }
+ }
+
+ private fun setAddView(
+ event: MotionEvent,
+ progress: Float,
+ listener: OnTouchRangeListener? = null,
+ layoutId: Int
+ ) {
+ // 设置触摸状态监听
+ addView?.let {
+ it.setTouchRangeListener(event, listener)
+ it.translationX = it.width * (1 - progress)
+ it.translationY = it.width * (1 - progress)
+ }
+ // 手指抬起或者事件取消,关闭添加浮窗
+ if (event.action == MotionEvent.ACTION_UP || event.action == MotionEvent.ACTION_CANCEL) dismissAdd()
+ else showAdd(layoutId)
+ }
+
+ private fun showAdd(layoutId: Int) {
+ if (EasyFloat.isShow(ADD_TAG)) return
+ EasyFloat.with(LifecycleUtils.application)
+ .setLayout(layoutId)
+ .setShowPattern(ShowPattern.CURRENT_ACTIVITY)
+ .setTag(ADD_TAG)
+ .setDragEnable(false)
+ .setSidePattern(SidePattern.BOTTOM)
+ .setGravity(Gravity.BOTTOM or Gravity.END)
+ .setAnimator(null)
+ .registerCallback {
+ createResult { isCreated, _, view ->
+ if (!isCreated || view == null) return@createResult
+ if ((view as ViewGroup).childCount > 0) {
+ // 获取区间判断布局
+ view.getChildAt(0).apply {
+ if (this is BaseSwitchView) {
+ addView = this
+ translationX = width.toFloat()
+ translationY = width.toFloat()
+ }
+ }
+ }
+ }
+ dismiss { addView = null }
+ }
+ .show()
+ }
+
+ /**
+ * 注册侧滑关闭浮窗
+ * @param event 浮窗的触摸事件
+ * @param listener 关闭区域触摸事件回调
+ * @param layoutId 关闭区域的布局文件
+ * @param showPattern 关闭区域的浮窗类型
+ * @param appFloatAnimator 关闭区域的浮窗出入动画
+ */
+ @JvmOverloads
+ fun registerDragClose(
+ event: MotionEvent,
+ listener: OnTouchRangeListener? = null,
+ layoutId: Int = R.layout.default_close_layout,
+ showPattern: ShowPattern = ShowPattern.CURRENT_ACTIVITY,
+ appFloatAnimator: OnFloatAnimator? = DefaultAnimator()
+ ) {
+ showClose(layoutId, showPattern, appFloatAnimator)
+ // 设置触摸状态监听
+ closeView?.setTouchRangeListener(event, listener)
+ // 抬起手指时,关闭删除选项
+ if (event.action == MotionEvent.ACTION_UP || event.action == MotionEvent.ACTION_CANCEL) dismissClose()
+ }
+
+ private fun showClose(
+ layoutId: Int,
+ showPattern: ShowPattern,
+ appFloatAnimator: OnFloatAnimator?
+ ) {
+ if (EasyFloat.isShow(CLOSE_TAG)) return
+ EasyFloat.with(LifecycleUtils.application)
+ .setLayout(layoutId)
+ .setShowPattern(showPattern)
+ .setMatchParent(widthMatch = true)
+ .setTag(CLOSE_TAG)
+ .setSidePattern(SidePattern.BOTTOM)
+ .setGravity(Gravity.BOTTOM)
+ .setAnimator(appFloatAnimator)
+ .registerCallback {
+ createResult { isCreated, _, view ->
+ if (!isCreated || view == null) return@createResult
+ if ((view as ViewGroup).childCount > 0) {
+ // 获取区间判断布局
+ view.getChildAt(0).apply { if (this is BaseSwitchView) closeView = this }
+ }
+ }
+ dismiss { closeView = null }
+ }
+ .show()
+ }
+
+ private fun dismissAdd() = EasyFloat.dismiss(ADD_TAG)
+
+ private fun dismissClose() = EasyFloat.dismiss(CLOSE_TAG)
+
+}
\ No newline at end of file
diff --git a/easyfloat/src/main/java/com/lzf/easyfloat/utils/InputMethodUtils.kt b/easyfloat/src/main/java/com/lzf/easyfloat/utils/InputMethodUtils.kt
index 129b761..742b972 100644
--- a/easyfloat/src/main/java/com/lzf/easyfloat/utils/InputMethodUtils.kt
+++ b/easyfloat/src/main/java/com/lzf/easyfloat/utils/InputMethodUtils.kt
@@ -1,27 +1,37 @@
package com.lzf.easyfloat.utils
+import android.annotation.SuppressLint
import android.content.Context.INPUT_METHOD_SERVICE
import android.os.Handler
import android.os.Looper
+import android.view.MotionEvent
import android.view.WindowManager
import android.view.inputmethod.InputMethodManager
import android.widget.EditText
-import com.lzf.easyfloat.widget.appfloat.FloatManager
+import com.lzf.easyfloat.core.FloatingWindowManager
/**
* @author: liuzhenfeng
- * @function: 软键盘工具类:解决系统浮窗内的EditText,无法弹起软键盘的问题
+ * @function: 软键盘工具类:解决浮窗内的EditText,无法弹起软键盘的问题
* @date: 2019-08-17 11:11
*/
object InputMethodUtils {
+ @SuppressLint("ClickableViewAccessibility")
+ internal fun initInputMethod(editText: EditText, tag: String? = null) {
+ editText.setOnTouchListener { _, event ->
+ if (event.action == MotionEvent.ACTION_DOWN) openInputMethod(editText, tag)
+ false
+ }
+ }
+
/**
- * 让系统浮窗获取焦点,并打开软键盘
+ * 让浮窗获取焦点,并打开软键盘
*/
@JvmStatic
@JvmOverloads
fun openInputMethod(editText: EditText, tag: String? = null) {
- FloatManager.getAppFloatManager(tag)?.apply {
+ FloatingWindowManager.getHelper(tag)?.apply {
// 更改flags,并刷新布局,让系统浮窗获取焦点
params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
windowManager.updateViewLayout(frameLayout, params)
@@ -40,9 +50,11 @@ object InputMethodUtils {
*/
@JvmStatic
@JvmOverloads
- fun closedInputMethod(tag: String? = null) = FloatManager.getAppFloatManager(tag)?.run {
- params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- windowManager.updateViewLayout(frameLayout, params)
- }
+ fun closedInputMethod(tag: String? = null) =
+ FloatingWindowManager.getHelper(tag)?.run {
+ params.flags =
+ WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ windowManager.updateViewLayout(frameLayout, params)
+ }
}
\ No newline at end of file
diff --git a/easyfloat/src/main/java/com/lzf/easyfloat/utils/LifecycleUtils.kt b/easyfloat/src/main/java/com/lzf/easyfloat/utils/LifecycleUtils.kt
index fcb3b52..781092c 100644
--- a/easyfloat/src/main/java/com/lzf/easyfloat/utils/LifecycleUtils.kt
+++ b/easyfloat/src/main/java/com/lzf/easyfloat/utils/LifecycleUtils.kt
@@ -3,8 +3,9 @@ package com.lzf.easyfloat.utils
import android.app.Activity
import android.app.Application
import android.os.Bundle
+import com.lzf.easyfloat.core.FloatingWindowManager
import com.lzf.easyfloat.enums.ShowPattern
-import com.lzf.easyfloat.widget.appfloat.FloatManager
+import java.lang.ref.WeakReference
/**
* @author: liuzhenfeng
@@ -13,8 +14,11 @@ import com.lzf.easyfloat.widget.appfloat.FloatManager
*/
internal object LifecycleUtils {
+ lateinit var application: Application
private var activityCount = 0
- private lateinit var application: Application
+ private var mTopActivity: WeakReference? = null
+
+ fun getTopActivity(): Activity? = mTopActivity?.get()
fun setLifecycleCallbacks(application: Application) {
this.application = application
@@ -25,37 +29,43 @@ internal object LifecycleUtils {
override fun onActivityStarted(activity: Activity?) {
// 计算启动的activity数目
- if (activity != null) activityCount++
+ activity?.let { activityCount++ }
}
- // 每次都要判断当前页面是否需要显示
- override fun onActivityResumed(activity: Activity?) = checkShow(activity)
+ override fun onActivityResumed(activity: Activity?) {
+ activity?.let {
+ mTopActivity?.clear()
+ mTopActivity = WeakReference(it)
+ // 每次都要判断当前页面是否需要显示
+ checkShow(it)
+ }
+ }
override fun onActivityPaused(activity: Activity?) {}
override fun onActivityStopped(activity: Activity?) {
- if (activity != null) {
+ activity?.let {
// 计算关闭的activity数目,并判断当前App是否处于后台
activityCount--
- checkHide()
+ checkHide(it)
}
}
override fun onActivityDestroyed(activity: Activity?) {}
override fun onActivitySaveInstanceState(activity: Activity?, outState: Bundle?) {}
-
})
}
/**
* 判断浮窗是否需要显示
*/
- private fun checkShow(activity: Activity?) {
- if (activity == null) return
- FloatManager.floatMap.forEach { (tag, manager) ->
+ private fun checkShow(activity: Activity) =
+ FloatingWindowManager.windowMap.forEach { (tag, manager) ->
manager.config.apply {
when {
+ // 当前页面的浮窗,不需要处理
+ showPattern == ShowPattern.CURRENT_ACTIVITY -> return@apply
// 仅后台显示模式下,隐藏浮窗
showPattern == ShowPattern.BACKGROUND -> setVisible(false, tag)
// 如果没有手动隐藏浮窗,需要考虑过滤信息
@@ -63,17 +73,27 @@ internal object LifecycleUtils {
}
}
}
- }
/**
* 判断浮窗是否需要隐藏
*/
- private fun checkHide() {
- if (isForeground()) return
- FloatManager.floatMap.forEach { (tag, manager) ->
+ private fun checkHide(activity: Activity) {
+ // 如果不是finish,并且处于前台,无需判断
+ if (!activity.isFinishing && isForeground()) return
+ FloatingWindowManager.windowMap.forEach { (tag, manager) ->
+ // 判断浮窗是否需要关闭
+ if (activity.isFinishing) manager.params.token?.let {
+ // 如果token不为空,并且是当前销毁的Activity,关闭浮窗,防止窗口泄漏
+ if (it == activity.window?.decorView?.windowToken) {
+ FloatingWindowManager.dismiss(tag, true)
+ }
+ }
+
manager.config.apply {
- // 当app处于后台时,不是仅前台显示的浮窗,如果没有手动隐藏,都需要显示
- setVisible(showPattern != ShowPattern.FOREGROUND && needShow, tag)
+ if (!isForeground() && manager.config.showPattern != ShowPattern.CURRENT_ACTIVITY) {
+ // 当app处于后台时,全局、仅后台显示的浮窗,如果没有手动隐藏,需要显示
+ setVisible(showPattern != ShowPattern.FOREGROUND && needShow, tag)
+ }
}
}
}
@@ -81,6 +101,6 @@ internal object LifecycleUtils {
fun isForeground() = activityCount > 0
private fun setVisible(isShow: Boolean = isForeground(), tag: String?) =
- FloatManager.visible(isShow, tag)
+ FloatingWindowManager.visible(isShow, tag)
}
\ No newline at end of file
diff --git a/easyfloat/src/main/java/com/lzf/easyfloat/utils/Logger.kt b/easyfloat/src/main/java/com/lzf/easyfloat/utils/Logger.kt
index bf1a8e3..f2bb9c3 100644
--- a/easyfloat/src/main/java/com/lzf/easyfloat/utils/Logger.kt
+++ b/easyfloat/src/main/java/com/lzf/easyfloat/utils/Logger.kt
@@ -1,7 +1,7 @@
package com.lzf.easyfloat.utils
import android.util.Log
-import com.lzf.easyfloat.EasyFloat
+import com.lzf.easyfloat.BuildConfig
/**
* @author: liuzhenfeng
@@ -13,7 +13,7 @@ internal object Logger {
private var tag = "EasyFloat--->"
// 设为false关闭日志
- private var logEnable = EasyFloat.isDebug
+ private var logEnable = BuildConfig.DEBUG
fun i(msg: Any) = i(tag, msg.toString())
diff --git a/easyfloat/src/main/java/com/lzf/easyfloat/widget/appfloat/ParentFrameLayout.kt b/easyfloat/src/main/java/com/lzf/easyfloat/widget/ParentFrameLayout.kt
similarity index 95%
rename from easyfloat/src/main/java/com/lzf/easyfloat/widget/appfloat/ParentFrameLayout.kt
rename to easyfloat/src/main/java/com/lzf/easyfloat/widget/ParentFrameLayout.kt
index d269202..2d7d6ff 100644
--- a/easyfloat/src/main/java/com/lzf/easyfloat/widget/appfloat/ParentFrameLayout.kt
+++ b/easyfloat/src/main/java/com/lzf/easyfloat/widget/ParentFrameLayout.kt
@@ -1,4 +1,4 @@
-package com.lzf.easyfloat.widget.appfloat
+package com.lzf.easyfloat.widget
import android.annotation.SuppressLint
import android.content.Context
@@ -59,7 +59,7 @@ internal class ParentFrameLayout(
*/
override fun dispatchKeyEventPreIme(event: KeyEvent?): Boolean {
if (config.hasEditText && event?.action == KeyEvent.ACTION_DOWN && event.keyCode == KeyEvent.KEYCODE_BACK) {
- InputMethodUtils.closedInputMethod(FloatManager.getTag(config.floatTag))
+ InputMethodUtils.closedInputMethod(config.floatTag)
}
return super.dispatchKeyEventPreIme(event)
}
diff --git a/easyfloat/src/main/java/com/lzf/easyfloat/widget/activityfloat/AbstractDragFloatingView.kt b/easyfloat/src/main/java/com/lzf/easyfloat/widget/activityfloat/AbstractDragFloatingView.kt
deleted file mode 100644
index 076b8d1..0000000
--- a/easyfloat/src/main/java/com/lzf/easyfloat/widget/activityfloat/AbstractDragFloatingView.kt
+++ /dev/null
@@ -1,401 +0,0 @@
-package com.lzf.easyfloat.widget.activityfloat
-
-import android.animation.Animator
-import android.animation.ObjectAnimator
-import android.annotation.SuppressLint
-import android.content.Context
-import android.graphics.Rect
-import android.util.AttributeSet
-import android.view.*
-import android.widget.FrameLayout
-import com.lzf.easyfloat.anim.AnimatorManager
-import com.lzf.easyfloat.data.FloatConfig
-import com.lzf.easyfloat.enums.SidePattern
-import com.lzf.easyfloat.utils.Logger
-import kotlin.math.min
-
-/**
- * @author: liuzhenfeng
- * @function: 拖拽控件的抽象类
- * @date: 2019-06-21 10:40
- */
-abstract class AbstractDragFloatingView(
- context: Context,
- attrs: AttributeSet? = null,
- defStyleAttr: Int = 0
-) : FrameLayout(context, attrs, defStyleAttr) {
-
- abstract fun getLayoutId(): Int?
-
- abstract fun renderView(view: View)
-
- // 浮窗配置
- var config: FloatConfig
-
- // 悬浮的父布局高度、宽度
- private var parentHeight = 0
- private var parentWidth = 0
-
- // 终点坐标
- private var lastX = 0
- private var lastY = 0
-
- // 浮窗各边距离父布局的距离
- private var leftDistance = 0
- private var rightDistance = 0
- private var topDistance = 0
- private var bottomDistance = 0
- private var minX = 0
- private var minY = 0
- private var parentRect = Rect()
- private var floatRect = Rect()
- private var parentView: ViewGroup? = null
- private var isCreated = false
-
- init {
- FrameLayout(context, attrs, defStyleAttr)
- config = FloatConfig()
- initView(context)
- // 设置空点击事件,用于接收触摸事件
- setOnClickListener { }
- }
-
- protected fun initView(context: Context) {
- if (getLayoutId() != null) {
- val view: View = LayoutInflater.from(context).inflate(getLayoutId()!!, this)
- this.renderView(view)
- config.invokeView?.invoke(this)
- }
- }
-
- @SuppressLint("DrawAllocation")
- override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
- super.onLayout(changed, l, t, r, b)
-
- // 初次显示,设置默认坐标、入场动画
- if (!isCreated) {
- isCreated = true
- // 有固定坐标使用固定坐标,没有固定坐标设置偏移量
- if (config.locationPair != Pair(0, 0)) {
- x = config.locationPair.first.toFloat()
- y = config.locationPair.second.toFloat()
- } else {
- x += config.offsetPair.first
- y += config.offsetPair.second
- }
-
- initParent()
- initDistanceValue()
- enterAnim()
- }
- }
-
- private fun initParent() {
- if (parent != null && parent is ViewGroup) {
- parentView = parent as ViewGroup
- parentHeight = parentView!!.height
- parentWidth = parentView!!.width
- parentView!!.getGlobalVisibleRect(parentRect)
- Logger.e("parentRect: $parentRect")
- }
- }
-
- override fun onInterceptTouchEvent(event: MotionEvent?): Boolean {
- if (event != null) updateView(event)
- // 是拖拽事件就进行拦截,反之不拦截
- // ps:拦截后将不再回调该方法,所以后续事件需要在onTouchEvent中回调
- return config.isDrag || super.onInterceptTouchEvent(event)
- }
-
- override fun onTouchEvent(event: MotionEvent?): Boolean {
- // updateView(event)是拖拽功能的具体实现
- if (event != null) updateView(event)
- // 如果是拖拽,这消费此事件,否则返回默认情况,防止影响子View事件的消费
- return config.isDrag || super.onTouchEvent(event)
- }
-
- /**
- * 更新位置信息
- */
- private fun updateView(event: MotionEvent) {
- config.callbacks?.touchEvent(this, event)
- config.floatCallbacks?.builder?.touchEvent?.invoke(this, event)
- // 关闭拖拽/执行动画阶段,不可拖动
- if (!config.dragEnable || config.isAnim) {
- config.isDrag = false
- isPressed = true
- return
- }
-
- val rawX = event.rawX.toInt()
- val rawY = event.rawY.toInt()
- when (event.action and MotionEvent.ACTION_MASK) {
- MotionEvent.ACTION_DOWN -> {
- // 默认是点击事件,而非拖拽事件
- config.isDrag = false
- isPressed = true
- lastX = rawX
- lastY = rawY
- // 父布局不要拦截子布局的监听
- parent.requestDisallowInterceptTouchEvent(true)
- initParent()
- }
-
- MotionEvent.ACTION_MOVE -> {
- // 只有父布局存在才可以拖动
- if (parentHeight <= 0 || parentWidth <= 0) return
-
- val dx = rawX - lastX
- val dy = rawY - lastY
- // 忽略过小的移动,防止点击无效
- if (!config.isDrag && dx * dx + dy * dy < 81) return
- config.isDrag = true
-
- var tempX = x + dx
- var tempY = y + dy
- // 检测是否到达边缘
- tempX = when {
- tempX < 0 -> 0f
- tempX > parentWidth - width -> parentWidth - width.toFloat()
- else -> tempX
- }
- tempY = when {
- tempY < 0 -> 0f
- tempY > parentHeight - height -> parentHeight - height.toFloat()
- else -> tempY
- }
-
- when (config.sidePattern) {
- SidePattern.LEFT -> tempX = 0f
- SidePattern.RIGHT -> tempX = parentRect.right - width.toFloat()
- SidePattern.TOP -> tempY = 0f
- SidePattern.BOTTOM -> tempY = parentRect.bottom - height.toFloat()
-
- SidePattern.AUTO_HORIZONTAL ->
- tempX = if (rawX * 2 - parentRect.left > parentRect.right)
- parentRect.right - width.toFloat() else 0f
-
- SidePattern.AUTO_VERTICAL ->
- tempY = if (rawY - parentRect.top > parentRect.bottom - rawY)
- parentRect.bottom - height.toFloat() else 0f
-
- SidePattern.AUTO_SIDE -> {
- leftDistance = rawX - parentRect.left
- rightDistance = parentRect.right - rawX
- topDistance = rawY - parentRect.top
- bottomDistance = parentRect.bottom - rawY
-
- minX = min(leftDistance, rightDistance)
- minY = min(topDistance, bottomDistance)
-
- val pair = sideForLatest(tempX, tempY)
- tempX = pair.first
- tempY = pair.second
- }
- else -> Unit
- }
- // 更新位置
- x = tempX
- y = tempY
- lastX = rawX
- lastY = rawY
- config.callbacks?.drag(this, event)
- config.floatCallbacks?.builder?.drag?.invoke(this, event)
- }
-
- MotionEvent.ACTION_UP -> {
- // 如果是拖动状态下即非点击按压事件
- isPressed = !config.isDrag
-
- if (config.isDrag) {
- when (config.sidePattern) {
- SidePattern.RESULT_LEFT,
- SidePattern.RESULT_RIGHT,
- SidePattern.RESULT_TOP,
- SidePattern.RESULT_BOTTOM,
- SidePattern.RESULT_HORIZONTAL,
- SidePattern.RESULT_VERTICAL,
- SidePattern.RESULT_SIDE -> sideAnim()
- else -> touchOver()
- }
- }
- }
-
- else -> return
- }
- }
-
- /**
- * 拖拽结束或者吸附动画执行结束,更新配置
- */
- private fun touchOver() {
- config.isAnim = false
- config.isDrag = false
- config.callbacks?.dragEnd(this)
- config.floatCallbacks?.builder?.dragEnd?.invoke(this)
- }
-
- /**
- * 拖拽结束,吸附屏幕边缘
- */
- private fun sideAnim() {
- // 计算一些数据
- initDistanceValue()
- var animType = "translationX"
- var startValue = 0f
- val endValue: Float = when (config.sidePattern) {
- SidePattern.RESULT_LEFT -> {
- animType = "translationX"
- startValue = translationX
- -leftDistance + translationX
- }
- SidePattern.RESULT_RIGHT -> {
- animType = "translationX"
- startValue = translationX
- rightDistance + translationX
- }
- SidePattern.RESULT_HORIZONTAL -> {
- animType = "translationX"
- startValue = translationX
- if (leftDistance < rightDistance) -leftDistance + translationX else rightDistance + translationX
- }
-
- SidePattern.RESULT_TOP -> {
- animType = "translationY"
- startValue = translationY
- -topDistance + translationY
- }
- SidePattern.RESULT_BOTTOM -> {
- animType = "translationY"
- startValue = translationY
- bottomDistance + translationY
- }
- SidePattern.RESULT_VERTICAL -> {
- animType = "translationY"
- startValue = translationY
- if (topDistance < bottomDistance) -topDistance + translationY else bottomDistance + translationY
- }
-
- SidePattern.RESULT_SIDE -> {
- if (minX < minY) {
- animType = "translationX"
- startValue = translationX
- if (leftDistance < rightDistance) -leftDistance + translationX else rightDistance + translationX
- } else {
- animType = "translationY"
- startValue = translationY
- if (topDistance < bottomDistance) -topDistance + translationY else bottomDistance + translationY
- }
- }
- else -> 0f
- }
-
- val animator = ObjectAnimator.ofFloat(this, animType, startValue, endValue)
- animator.addListener(object : Animator.AnimatorListener {
- override fun onAnimationRepeat(animation: Animator?) {}
-
- override fun onAnimationEnd(animation: Animator?) {
- touchOver()
- }
-
- override fun onAnimationCancel(animation: Animator?) {}
-
- override fun onAnimationStart(animation: Animator?) {
- config.isAnim = true
- }
- })
- animator.start()
- }
-
- /**
- * 吸附在距离最近的那个边
- */
- private fun sideForLatest(x: Float, y: Float): Pair {
- var x1 = x
- var y1 = y
- if (minX < minY) {
- x1 = if (leftDistance == minX) 0f else parentWidth - width.toFloat()
- } else {
- y1 = if (topDistance == minY) 0f else parentHeight - height.toFloat()
- }
- return Pair(x1, y1)
- }
-
- /**
- * 计算一些边界距离数据
- */
- private fun initDistanceValue() {
- // 获取 floatingView 所显示的矩形
- getGlobalVisibleRect(floatRect)
-
- leftDistance = floatRect.left - parentRect.left
- rightDistance = parentRect.right - floatRect.right
- topDistance = floatRect.top - parentRect.top
- bottomDistance = parentRect.bottom - floatRect.bottom
-
- minX = min(leftDistance, rightDistance)
- minY = min(topDistance, bottomDistance)
- Logger.i("$leftDistance $rightDistance $topDistance $bottomDistance")
- }
-
- /**
- * 入场动画
- */
- private fun enterAnim() {
- if (parentView == null) return
- val manager: AnimatorManager? =
- AnimatorManager(config.floatAnimator, this, parentView!!, config.sidePattern)
- val animator: Animator? = manager?.enterAnim()
- animator?.addListener(object : Animator.AnimatorListener {
- override fun onAnimationRepeat(animation: Animator?) {}
-
- override fun onAnimationEnd(animation: Animator?) {
- config.isAnim = false
- }
-
- override fun onAnimationCancel(animation: Animator?) {}
-
- override fun onAnimationStart(animation: Animator?) {
- config.isAnim = true
- }
- })
- animator?.start()
- }
-
- /**
- * 退出动画
- */
- internal fun exitAnim() {
- // 正在执行动画,防止重复调用
- if (config.isAnim || parentView == null) return
- val manager: AnimatorManager? =
- AnimatorManager(config.floatAnimator, this, parentView!!, config.sidePattern)
- val animator: Animator? = manager?.exitAnim()
- // 动画为空,直接移除浮窗视图
- if (animator == null) parentView?.removeView(this)
- else {
- animator.addListener(object : Animator.AnimatorListener {
- override fun onAnimationRepeat(animation: Animator?) {}
-
- override fun onAnimationEnd(animation: Animator?) {
- config.isAnim = false
- parentView?.removeView(this@AbstractDragFloatingView)
- }
-
- override fun onAnimationCancel(animation: Animator?) {}
-
- override fun onAnimationStart(animation: Animator?) {
- config.isAnim = true
- }
- })
- animator.start()
- }
- }
-
- override fun onDetachedFromWindow() {
- super.onDetachedFromWindow()
- config.callbacks?.dismiss()
- config.floatCallbacks?.builder?.dismiss?.invoke()
- }
-
-}
\ No newline at end of file
diff --git a/easyfloat/src/main/java/com/lzf/easyfloat/widget/activityfloat/ActivityFloatManager.kt b/easyfloat/src/main/java/com/lzf/easyfloat/widget/activityfloat/ActivityFloatManager.kt
deleted file mode 100644
index fd4eff7..0000000
--- a/easyfloat/src/main/java/com/lzf/easyfloat/widget/activityfloat/ActivityFloatManager.kt
+++ /dev/null
@@ -1,97 +0,0 @@
-package com.lzf.easyfloat.widget.activityfloat
-
-import android.app.Activity
-import android.view.View
-import android.widget.FrameLayout
-import com.lzf.easyfloat.data.FloatConfig
-
-/**
- * @author: liuzhenfeng
- * @function: Activity浮窗管理类,包括浮窗的创建、销毁、可见性等操作。
- * @date: 2019-07-31 09:10
- */
-internal class ActivityFloatManager(val activity: Activity) {
-
- // 通过DecorView 获取屏幕底层FrameLayout,即activity的根布局,作为浮窗的父布局
- private var parentFrame: FrameLayout =
- activity.window.decorView.findViewById(android.R.id.content)
-
- /**
- * 创建Activity浮窗
- * 拖拽效果由自定义的拖拽布局实现;
- * 将拖拽布局,添加到Activity的根布局;
- * 再将浮窗的xml布局,添加到拖拽布局中,从而实现拖拽效果。
- */
- fun createFloat(config: FloatConfig) {
- // 设置浮窗的拖拽外壳FloatingView
- val floatingView = FloatingView(activity).apply {
- // 为浮窗打上tag,如果未设置tag,使用类名作为tag
- tag = getTag(config.floatTag)
- // 默认wrap_content,会导致子view的match_parent无效,所以手动设置params
- layoutParams = FrameLayout.LayoutParams(
- if (config.widthMatch) FrameLayout.LayoutParams.MATCH_PARENT else FrameLayout.LayoutParams.WRAP_CONTENT,
- if (config.heightMatch) FrameLayout.LayoutParams.MATCH_PARENT else FrameLayout.LayoutParams.WRAP_CONTENT
- ).apply {
- // 如若未设置固定坐标,设置浮窗Gravity
- if (config.locationPair == Pair(0, 0)) gravity = config.gravity
- }
- // 同步配置
- setFloatConfig(config)
- }
-
- // 将FloatingView添加到根布局中
- parentFrame.addView(floatingView)
-
- // 设置Callbacks
- config.layoutView = floatingView
- config.callbacks?.createdResult(true, null, floatingView)
- config.floatCallbacks?.builder?.createdResult?.invoke(true, null, floatingView)
- }
-
- /**
- * 关闭activity浮窗
- */
- fun dismiss(tag: String?) = floatingView(tag)?.exitAnim()
-
- /**
- * 设置浮窗的可见性
- */
- fun setVisibility(tag: String?, visibility: Int) = floatingView(tag)?.apply {
- this.visibility = visibility
- if (visibility == View.GONE) {
- config.callbacks?.hide(this)
- config.floatCallbacks?.builder?.hide?.invoke(this)
- } else {
- config.callbacks?.show(this)
- config.floatCallbacks?.builder?.show?.invoke(this)
- }
- }
-
- /**
- * 获取浮窗是否显示
- */
- fun isShow(tag: String? = null): Boolean = floatingView(tag)?.visibility == View.VISIBLE
-
- /**
- * 设置是否可拖拽
- */
- fun setDragEnable(dragEnable: Boolean, tag: String? = null) {
- floatingView(tag)?.config?.dragEnable = dragEnable
- }
-
- /**
- * 获取我们传入的浮窗View
- */
- fun getFloatView(tag: String? = null): View? = floatingView(tag)?.config?.layoutView
-
- /**
- * 获取浮窗的拖拽外壳FloatingView
- */
- private fun floatingView(tag: String?): FloatingView? = parentFrame.findViewWithTag(getTag(tag))
-
- /**
- * 如果未设置tag,使用类名作为tag
- */
- private fun getTag(tag: String?) = tag ?: activity.componentName.className
-
-}
\ No newline at end of file
diff --git a/easyfloat/src/main/java/com/lzf/easyfloat/widget/activityfloat/FloatingView.kt b/easyfloat/src/main/java/com/lzf/easyfloat/widget/activityfloat/FloatingView.kt
deleted file mode 100644
index 0c71bca..0000000
--- a/easyfloat/src/main/java/com/lzf/easyfloat/widget/activityfloat/FloatingView.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.lzf.easyfloat.widget.activityfloat
-
-import android.content.Context
-import android.util.AttributeSet
-import android.view.View
-import com.lzf.easyfloat.data.FloatConfig
-
-/**
- * @author: liuzhenfeng
- * @function: 拖拽布局的实体类
- * @date: 2019-06-21 14:49
- */
-class FloatingView(context: Context, attrs: AttributeSet? = null) :
- AbstractDragFloatingView(context, attrs, 0) {
-
- fun setFloatConfig(config: FloatConfig) {
- this.config = config
- initView(context)
- requestLayout()
- }
-
- override fun getLayoutId(): Int? = config.layoutId
-
- override fun renderView(view: View) {}
-
-}
\ No newline at end of file
diff --git a/easyfloat/src/main/java/com/lzf/easyfloat/widget/appfloat/FloatManager.kt b/easyfloat/src/main/java/com/lzf/easyfloat/widget/appfloat/FloatManager.kt
deleted file mode 100644
index 45504b9..0000000
--- a/easyfloat/src/main/java/com/lzf/easyfloat/widget/appfloat/FloatManager.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-package com.lzf.easyfloat.widget.appfloat
-
-import android.content.Context
-import android.view.View
-import com.lzf.easyfloat.WARN_REPEATED_TAG
-import com.lzf.easyfloat.data.FloatConfig
-import com.lzf.easyfloat.utils.Logger
-
-/**
- * @author: liuzhenfeng
- * @function: 系统浮窗的集合管理类,通过浮窗tag管理各个浮窗
- * @date: 2019-12-06 10:14
- */
-internal object FloatManager {
-
- private const val DEFAULT_TAG = "default"
- val floatMap = mutableMapOf()
-
- /**
- * 创建系统浮窗,首先检查浮窗是否存在:不存在则创建,存在则回调提示
- */
- fun create(context: Context, config: FloatConfig) = if (checkTag(config)) {
- // 通过floatManager创建浮窗,并将floatManager添加到map中
- floatMap[config.floatTag!!] = AppFloatManager(context.applicationContext, config)
- .apply { createFloat() }
- } else {
- config.callbacks?.createdResult(false, WARN_REPEATED_TAG, null)
- Logger.w(WARN_REPEATED_TAG)
- }
-
- /**
- * 设置浮窗的显隐,用户主动调用隐藏时,needShow需要为false
- */
- fun visible(
- isShow: Boolean,
- tag: String? = null,
- needShow: Boolean = floatMap[tag]?.config?.needShow ?: true
- ) = floatMap[getTag(tag)]?.setVisible(if (isShow) View.VISIBLE else View.GONE, needShow)
-
- /**
- * 关闭浮窗,执行浮窗的退出动画
- */
- fun dismiss(tag: String? = null) = floatMap[getTag(tag)]?.exitAnim()
-
- /**
- * 移除当条浮窗信息,在退出完成后调用
- */
- fun remove(floatTag: String?) = floatMap.remove(floatTag)
-
- /**
- * 获取浮窗tag,为空则使用默认值
- */
- fun getTag(tag: String?) = tag ?: DEFAULT_TAG
-
- /**
- * 获取具体的系统浮窗管理类
- */
- fun getAppFloatManager(tag: String?) = floatMap[getTag(tag)]
-
- /**
- * 检测浮窗的tag是否有效,不同的浮窗必须设置不同的tag
- */
- private fun checkTag(config: FloatConfig): Boolean {
- // 如果未设置tag,设置默认tag
- config.floatTag = getTag(config.floatTag)
- return !floatMap.containsKey(config.floatTag!!)
- }
-
-}
\ No newline at end of file
diff --git a/easyfloat/src/main/java/com/lzf/easyfloat/widget/switch/BaseSwitchView.kt b/easyfloat/src/main/java/com/lzf/easyfloat/widget/switch/BaseSwitchView.kt
new file mode 100644
index 0000000..5b92453
--- /dev/null
+++ b/easyfloat/src/main/java/com/lzf/easyfloat/widget/switch/BaseSwitchView.kt
@@ -0,0 +1,21 @@
+package com.lzf.easyfloat.widget.switch
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.MotionEvent
+import android.widget.RelativeLayout
+import com.lzf.easyfloat.interfaces.OnTouchRangeListener
+
+/**
+ * @author: liuzhenfeng
+ * @date: 2020/10/25 11:08
+ * @Package: com.lzf.easyfloat.widget.switch
+ * @Description:
+ */
+abstract class BaseSwitchView @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
+) : RelativeLayout(context, attrs, defStyleAttr) {
+
+ abstract fun setTouchRangeListener(event: MotionEvent, listener: OnTouchRangeListener? = null)
+
+}
\ No newline at end of file
diff --git a/easyfloat/src/main/java/com/lzf/easyfloat/widget/switch/DefaultAddView.kt b/easyfloat/src/main/java/com/lzf/easyfloat/widget/switch/DefaultAddView.kt
new file mode 100644
index 0000000..1982825
--- /dev/null
+++ b/easyfloat/src/main/java/com/lzf/easyfloat/widget/switch/DefaultAddView.kt
@@ -0,0 +1,85 @@
+package com.lzf.easyfloat.widget.switch
+
+import android.content.Context
+import android.graphics.*
+import android.util.AttributeSet
+import android.view.MotionEvent
+import com.lzf.easyfloat.interfaces.OnTouchRangeListener
+
+/**
+ * @author: liuzhenfeng
+ * @date: 11/21/20 17:49
+ * @Package: com.lzf.easyfloat.widget.switch
+ * @Description:
+ */
+class DefaultAddView @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
+) : BaseSwitchView(context, attrs, defStyleAttr) {
+
+ private lateinit var paint: Paint
+ private var path = Path()
+ private var width = 0f
+ private var height = 0f
+ private var region = Region()
+ private val totalRegion = Region()
+ private var inRange = false
+ private var zoomSize = 18f
+ private var listener: OnTouchRangeListener? = null
+
+ init {
+ initPath()
+ setWillNotDraw(false)
+ }
+
+ private fun initPath() {
+ paint = Paint().apply {
+ color = Color.parseColor("#AA000000")
+ strokeWidth = 10f
+ style = Paint.Style.FILL
+ isAntiAlias = true
+ }
+ }
+
+ override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
+ super.onSizeChanged(w, h, oldw, oldh)
+ width = w.toFloat()
+ height = h.toFloat()
+ }
+
+ override fun onDraw(canvas: Canvas?) {
+ path.reset()
+ if (inRange) {
+ path.addCircle(width, height, minOf(width, height), Path.Direction.CW)
+ } else {
+ path.addCircle(width, height, minOf(width, height) - zoomSize, Path.Direction.CW)
+ totalRegion.set(zoomSize.toInt(), zoomSize.toInt(), width.toInt(), height.toInt())
+ region.setPath(path, totalRegion)
+ }
+ canvas?.drawPath(path, paint)
+ super.onDraw(canvas)
+ }
+
+ override fun setTouchRangeListener(event: MotionEvent, listener: OnTouchRangeListener?) {
+ this.listener = listener
+ initTouchRange(event)
+ }
+
+ private fun initTouchRange(event: MotionEvent): Boolean {
+ val location = IntArray(2)
+ // 获取在整个屏幕内的绝对坐标
+ getLocationOnScreen(location)
+ val currentInRange = region.contains(
+ event.rawX.toInt() - location[0], event.rawY.toInt() - location[1]
+ )
+ if (currentInRange != inRange) {
+ inRange = currentInRange
+ invalidate()
+ }
+ listener?.touchInRange(currentInRange, this)
+ if (event.action == MotionEvent.ACTION_UP && currentInRange) {
+ listener?.touchUpInRange()
+ }
+ return currentInRange
+ }
+
+}
\ No newline at end of file
diff --git a/easyfloat/src/main/java/com/lzf/easyfloat/widget/switch/DefaultCloseView.kt b/easyfloat/src/main/java/com/lzf/easyfloat/widget/switch/DefaultCloseView.kt
new file mode 100644
index 0000000..d874e90
--- /dev/null
+++ b/easyfloat/src/main/java/com/lzf/easyfloat/widget/switch/DefaultCloseView.kt
@@ -0,0 +1,154 @@
+package com.lzf.easyfloat.widget.switch
+
+import android.content.Context
+import android.graphics.*
+import android.util.AttributeSet
+import android.view.MotionEvent
+import com.lzf.easyfloat.R
+import com.lzf.easyfloat.interfaces.OnTouchRangeListener
+import com.lzf.easyfloat.utils.DisplayUtils
+
+/**
+ * @author: liuzhenfeng
+ * @date: 2020/10/25 11:16
+ * @Package: com.lzf.easyfloat.widget.switch
+ * @Description:
+ */
+class DefaultCloseView @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
+) : BaseSwitchView(context, attrs, defStyleAttr) {
+
+ private var normalColor = Color.parseColor("#99000000")
+ private var inRangeColor = Color.parseColor("#99FF0000")
+ private var shapeType = 0
+
+ private lateinit var paint: Paint
+ private var path = Path()
+ private var width = 0f
+ private var height = 0f
+ private var rectF = RectF()
+ private var region = Region()
+ private val totalRegion = Region()
+ private var inRange = false
+ private var zoomSize = DisplayUtils.dp2px(context, 4f).toFloat()
+ private var listener: OnTouchRangeListener? = null
+
+ init {
+ attrs?.apply { initAttrs(this) }
+ initPaint()
+ setWillNotDraw(false)
+ }
+
+ private fun initAttrs(attrs: AttributeSet) =
+ context.theme.obtainStyledAttributes(attrs, R.styleable.DefaultCloseView, 0, 0).apply {
+ normalColor = getColor(R.styleable.DefaultCloseView_normalColor, normalColor)
+ inRangeColor = getColor(R.styleable.DefaultCloseView_inRangeColor, inRangeColor)
+ shapeType = getInt(R.styleable.DefaultCloseView_shapeType, shapeType)
+ zoomSize = getDimension(R.styleable.DefaultCloseView_zoomSize, zoomSize)
+ }.recycle()
+
+
+ private fun initPaint() {
+ paint = Paint().apply {
+ color = normalColor
+ strokeWidth = 10f
+ style = Paint.Style.FILL
+ isAntiAlias = true
+ }
+ }
+
+ override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
+ super.onSizeChanged(w, h, oldw, oldh)
+ width = w.toFloat()
+ height = h.toFloat()
+ }
+
+ override fun onDraw(canvas: Canvas?) {
+ path.reset()
+ if (inRange) {
+ paint.color = inRangeColor
+ when (shapeType) {
+ // 半椭圆
+ 0 -> {
+ rectF.set(paddingLeft.toFloat(), 0f, width - paddingRight, height * 2)
+ path.addOval(rectF, Path.Direction.CW)
+ }
+ // 矩形
+ 1 -> {
+ rectF.set(paddingLeft.toFloat(), 0f, width - paddingRight, height)
+ path.addRect(rectF, Path.Direction.CW)
+ }
+ // 半圆
+ 2 -> path.addCircle(width / 2, height, height, Path.Direction.CW)
+ }
+ } else {
+ paint.color = normalColor
+ when (shapeType) {
+ // 半椭圆
+ 0 -> {
+ rectF.set(
+ paddingLeft + zoomSize,
+ zoomSize,
+ width - paddingRight - zoomSize,
+ (height - zoomSize) * 2
+ )
+ path.addOval(rectF, Path.Direction.CW)
+ totalRegion.set(
+ paddingLeft + zoomSize.toInt(),
+ zoomSize.toInt(),
+ (width - paddingRight - zoomSize).toInt(),
+ height.toInt()
+ )
+ }
+ // 矩形
+ 1 -> {
+ rectF.set(
+ paddingLeft.toFloat(),
+ zoomSize,
+ width - paddingRight,
+ height
+ )
+ path.addRect(rectF, Path.Direction.CW)
+ totalRegion.set(
+ paddingLeft,
+ zoomSize.toInt(),
+ width.toInt() - paddingRight,
+ height.toInt()
+ )
+ }
+ // 半圆
+ 2 -> {
+ path.addCircle(width / 2, height, height - zoomSize, Path.Direction.CW)
+ totalRegion.set(0, zoomSize.toInt(), width.toInt(), height.toInt())
+ }
+ }
+ region.setPath(path, totalRegion)
+ }
+ canvas?.drawPath(path, paint)
+ super.onDraw(canvas)
+ }
+
+ override fun setTouchRangeListener(event: MotionEvent, listener: OnTouchRangeListener?) {
+ this.listener = listener
+ initTouchRange(event)
+ }
+
+ private fun initTouchRange(event: MotionEvent): Boolean {
+ val location = IntArray(2)
+ // 获取在整个屏幕内的绝对坐标
+ getLocationOnScreen(location)
+ val currentInRange = region.contains(
+ event.rawX.toInt() - location[0], event.rawY.toInt() - location[1]
+ )
+ if (currentInRange != inRange) {
+ inRange = currentInRange
+ invalidate()
+ }
+ listener?.touchInRange(currentInRange, this)
+ if (event.action == MotionEvent.ACTION_UP && currentInRange) {
+ listener?.touchUpInRange()
+ }
+ return currentInRange
+ }
+
+}
\ No newline at end of file
diff --git a/easyfloat/src/main/res/drawable-xxxhdpi/icon_delete_normal.png b/easyfloat/src/main/res/drawable-xxxhdpi/icon_delete_normal.png
new file mode 100644
index 0000000..161278d
Binary files /dev/null and b/easyfloat/src/main/res/drawable-xxxhdpi/icon_delete_normal.png differ
diff --git a/easyfloat/src/main/res/drawable-xxxhdpi/icon_delete_selected.png b/easyfloat/src/main/res/drawable-xxxhdpi/icon_delete_selected.png
new file mode 100644
index 0000000..6037ac4
Binary files /dev/null and b/easyfloat/src/main/res/drawable-xxxhdpi/icon_delete_selected.png differ
diff --git a/easyfloat/src/main/res/drawable/add_normal.xml b/easyfloat/src/main/res/drawable/add_normal.xml
new file mode 100644
index 0000000..69c60a6
--- /dev/null
+++ b/easyfloat/src/main/res/drawable/add_normal.xml
@@ -0,0 +1,25 @@
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/easyfloat/src/main/res/drawable/add_selected.xml b/easyfloat/src/main/res/drawable/add_selected.xml
new file mode 100644
index 0000000..a7caecc
--- /dev/null
+++ b/easyfloat/src/main/res/drawable/add_selected.xml
@@ -0,0 +1,25 @@
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/easyfloat/src/main/res/drawable/icon_delete_normal.png b/easyfloat/src/main/res/drawable/icon_delete_normal.png
new file mode 100644
index 0000000..2aa9c7f
Binary files /dev/null and b/easyfloat/src/main/res/drawable/icon_delete_normal.png differ
diff --git a/easyfloat/src/main/res/drawable/icon_delete_selected.png b/easyfloat/src/main/res/drawable/icon_delete_selected.png
new file mode 100644
index 0000000..34a200c
Binary files /dev/null and b/easyfloat/src/main/res/drawable/icon_delete_selected.png differ
diff --git a/easyfloat/src/main/res/layout/default_add_layout.xml b/easyfloat/src/main/res/layout/default_add_layout.xml
new file mode 100644
index 0000000..3636355
--- /dev/null
+++ b/easyfloat/src/main/res/layout/default_add_layout.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
diff --git a/easyfloat/src/main/res/layout/default_close_layout.xml b/easyfloat/src/main/res/layout/default_close_layout.xml
new file mode 100644
index 0000000..98ecdbe
--- /dev/null
+++ b/easyfloat/src/main/res/layout/default_close_layout.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
diff --git a/easyfloat/src/main/res/values/attrs.xml b/easyfloat/src/main/res/values/attrs.xml
new file mode 100644
index 0000000..76a4f41
--- /dev/null
+++ b/easyfloat/src/main/res/values/attrs.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/easyfloat/src/main/res/values/strings.xml b/easyfloat/src/main/res/values/strings.xml
index 8cb0bc5..6f56364 100644
--- a/easyfloat/src/main/res/values/strings.xml
+++ b/easyfloat/src/main/res/values/strings.xml
@@ -1,3 +1,5 @@
- easyfloat
+ EasyFloat
+ 浮窗
+ 删除浮窗
diff --git a/example/build.gradle b/example/build.gradle
index cf6d6bd..001e06d 100644
--- a/example/build.gradle
+++ b/example/build.gradle
@@ -44,12 +44,14 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
- implementation 'androidx.appcompat:appcompat:1.1.0'
- implementation 'androidx.core:core-ktx:1.1.0'
- implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
- testImplementation 'junit:junit:4.12'
- androidTestImplementation 'androidx.test:runner:1.2.0'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
+ implementation 'androidx.appcompat:appcompat:1.2.0'
+ implementation 'androidx.core:core-ktx:1.3.2'
+ implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
+ testImplementation 'junit:junit:4.13.1'
+ androidTestImplementation 'androidx.test:runner:1.3.0'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation project(path: ':easyfloat')
-// implementation 'com.github.princekin-f:EasyFloat:1.2.1'
+ implementation 'cn.bingoogolapple:bga-swipebacklayout:2.0.1@aar'
+ implementation "androidx.cardview:cardview:1.0.0"
+ implementation 'androidx.navigation:navigation-ui-ktx:2.3.1'
}
diff --git a/example/release/EasyFloat.apk b/example/release/EasyFloat.apk
index b4224c5..382a28a 100644
Binary files a/example/release/EasyFloat.apk and b/example/release/EasyFloat.apk differ
diff --git a/example/release/output-metadata.json b/example/release/output-metadata.json
new file mode 100644
index 0000000..202c317
--- /dev/null
+++ b/example/release/output-metadata.json
@@ -0,0 +1,18 @@
+{
+ "version": 2,
+ "artifactType": {
+ "type": "APK",
+ "kind": "Directory"
+ },
+ "applicationId": "com.lzf.easyfloat.example",
+ "variantName": "processReleaseResources",
+ "elements": [
+ {
+ "type": "SINGLE",
+ "filters": [],
+ "versionCode": 1,
+ "versionName": "1.0",
+ "outputFile": "EasyFloat.apk"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/example/src/main/AndroidManifest.xml b/example/src/main/AndroidManifest.xml
index c9c2526..daa7eb7 100644
--- a/example/src/main/AndroidManifest.xml
+++ b/example/src/main/AndroidManifest.xml
@@ -3,7 +3,10 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.lzf.easyfloat.example">
+
+
+
+ android:launchMode="standard"
+ android:theme="@style/AppTheme">
@@ -25,8 +29,8 @@
-
+
diff --git a/example/src/main/java/com/lzf/easyfloat/example/App.kt b/example/src/main/java/com/lzf/easyfloat/example/App.kt
index 524863b..41444ed 100644
--- a/example/src/main/java/com/lzf/easyfloat/example/App.kt
+++ b/example/src/main/java/com/lzf/easyfloat/example/App.kt
@@ -3,7 +3,7 @@ package com.lzf.easyfloat.example
import android.annotation.SuppressLint
import android.app.Application
import android.content.Context
-import com.lzf.easyfloat.EasyFloat
+import cn.bingoogolapple.swipebacklayout.BGASwipeBackHelper
/**
* @author: liuzhenfeng
@@ -20,8 +20,7 @@ class App : Application() {
override fun onCreate() {
super.onCreate()
context = this
-
- EasyFloat.init(this, true)
+ BGASwipeBackHelper.init(this, null)
}
}
\ No newline at end of file
diff --git a/example/src/main/java/com/lzf/easyfloat/example/MyAdapter.kt b/example/src/main/java/com/lzf/easyfloat/example/MyAdapter.kt
index 58d072e..85af71e 100644
--- a/example/src/main/java/com/lzf/easyfloat/example/MyAdapter.kt
+++ b/example/src/main/java/com/lzf/easyfloat/example/MyAdapter.kt
@@ -43,7 +43,7 @@ class MyAdapter(
viewHolder.checkBox.apply {
setOnTouchListener { _, event ->
logger.e("setOnTouchListener: ${event.action}")
- EasyFloat.appFloatDragEnable(event?.action == MotionEvent.ACTION_CANCEL)
+ EasyFloat.dragEnable(event?.action == MotionEvent.ACTION_CANCEL)
false
}
diff --git a/example/src/main/java/com/lzf/easyfloat/example/activity/BaseActivity.kt b/example/src/main/java/com/lzf/easyfloat/example/activity/BaseActivity.kt
new file mode 100644
index 0000000..b3e9178
--- /dev/null
+++ b/example/src/main/java/com/lzf/easyfloat/example/activity/BaseActivity.kt
@@ -0,0 +1,117 @@
+package com.lzf.easyfloat.example.activity
+
+import android.content.Context
+import android.os.Bundle
+import android.os.VibrationEffect
+import android.os.Vibrator
+import android.view.KeyEvent
+import android.view.MotionEvent
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.appcompat.app.AppCompatActivity
+import cn.bingoogolapple.swipebacklayout.BGASwipeBackHelper
+import com.lzf.easyfloat.EasyFloat
+import com.lzf.easyfloat.enums.ShowPattern
+import com.lzf.easyfloat.example.R
+import com.lzf.easyfloat.interfaces.OnTouchRangeListener
+import com.lzf.easyfloat.utils.DragUtils
+import com.lzf.easyfloat.widget.switch.BaseSwitchView
+
+/**
+ * @author: liuzhenfeng
+ * @date: 2/8/21 13:39
+ * @Package: com.lzf.easyfloat.example.activity
+ * @Description:
+ */
+open class BaseActivity : AppCompatActivity(), BGASwipeBackHelper.Delegate {
+
+ lateinit var bgaSwipeBackHelper: BGASwipeBackHelper
+ private lateinit var vibrator: Vibrator
+ private var vibrating = false
+ var slideOffset = 0f
+ private var contractTag = "contractFloat"
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ vibrator = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
+ bgaSwipeBackHelper = BGASwipeBackHelper(this, this)
+ }
+
+ override fun isSupportSwipeBack(): Boolean = false
+
+ override fun onSwipeBackLayoutSlide(slideOffset: Float) {
+ this.slideOffset = slideOffset
+ }
+
+ override fun onSwipeBackLayoutCancel() {}
+
+ override fun onSwipeBackLayoutExecuted() {
+ bgaSwipeBackHelper.swipeBackward()
+ }
+
+ override fun onBackPressed() {
+ // 正在滑动返回的时候取消返回按钮事件
+ if (bgaSwipeBackHelper.isSliding) return
+ bgaSwipeBackHelper.backward()
+ }
+
+ fun setVibrator(inRange: Boolean) {
+ if (!vibrator.hasVibrator() || (inRange && vibrating)) return
+ vibrating = inRange
+ if (inRange) if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
+ vibrator.vibrate(VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE))
+ } else vibrator.vibrate(100)
+ else vibrator.cancel()
+ }
+
+ /**
+ * 注册拖拽关闭
+ */
+ fun registerDragClose(event: MotionEvent) =
+ DragUtils.registerDragClose(event, object : OnTouchRangeListener {
+ override fun touchInRange(inRange: Boolean, view: BaseSwitchView) {
+ setVibrator(inRange)
+ view.findViewById(com.lzf.easyfloat.R.id.tv_delete).text =
+ if (inRange) "松手删除" else "删除浮窗"
+
+ view.findViewById(com.lzf.easyfloat.R.id.iv_delete)
+ .setImageResource(
+ if (inRange) com.lzf.easyfloat.R.drawable.icon_delete_selected
+ else com.lzf.easyfloat.R.drawable.icon_delete_normal
+ )
+ }
+
+ override fun touchUpInRange() {
+ EasyFloat.dismiss(SwipeTestActivity.FLOAT_TAG, true)
+ }
+ }, showPattern = ShowPattern.ALL_TIME)
+
+
+ /**
+ * 显示控制中心(假装)
+ */
+ fun showContractFloat() = EasyFloat.with(application)
+ .setTag(contractTag)
+ .setLayout(R.layout.float_contract) {
+ it.findViewById(R.id.tv_back).setOnClickListener {
+ EasyFloat.dismiss(contractTag)
+ }
+ }
+ .setShowPattern(ShowPattern.FOREGROUND)
+ .setImmersionStatusBar(true)
+ .setMatchParent(widthMatch = true, heightMatch = true)
+ .setDragEnable(false)
+ .setAnimator(null)
+ .show()
+
+ override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ if (EasyFloat.isShow(contractTag)) {
+ EasyFloat.dismiss(contractTag)
+ return true
+ }
+ }
+ return super.onKeyDown(keyCode, event)
+ }
+
+}
\ No newline at end of file
diff --git a/example/src/main/java/com/lzf/easyfloat/example/activity/JavaTestActivity.java b/example/src/main/java/com/lzf/easyfloat/example/activity/JavaTestActivity.java
index 5a15c9a..3e00cfc 100644
--- a/example/src/main/java/com/lzf/easyfloat/example/activity/JavaTestActivity.java
+++ b/example/src/main/java/com/lzf/easyfloat/example/activity/JavaTestActivity.java
@@ -8,7 +8,6 @@
import android.widget.Toast;
import com.lzf.easyfloat.EasyFloat;
-import com.lzf.easyfloat.anim.AppFloatDefaultAnimator;
import com.lzf.easyfloat.anim.DefaultAnimator;
import com.lzf.easyfloat.enums.ShowPattern;
import com.lzf.easyfloat.enums.SidePattern;
@@ -28,35 +27,39 @@
*/
public class JavaTestActivity extends Activity {
+ private final String TAG = "JavaTestActivity";
+
@Override
protected void onCreate(@androidx.annotation.Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_java);
- findViewById(R.id.testJava).setOnClickListener(v ->
- EasyFloat.with(this)
- .setLayout(R.layout.float_custom, view ->
- view.findViewById(R.id.textView).setOnClickListener(v1 -> toast("onClick")))
- .setGravity(Gravity.END, 0, 100)
- // 在Java中使用Kotlin DSL回调
- .registerCallback(builder -> {
- builder.createResult((aBoolean, s, view) -> {
- toast("创建成功:" + aBoolean.toString());
- return null;
- });
-
- builder.dismiss(() -> {
- toast("dismiss");
- return null;
- });
-
- // ...可根据需求复写其他方法
+ findViewById(R.id.testJava).setOnClickListener(v -> {
+ EasyFloat.with(this)
+ .setTag(TAG)
+ .setLayout(R.layout.float_custom, view ->
+ view.findViewById(R.id.textView).setOnClickListener(v1 -> toast("onClick")))
+ .setGravity(Gravity.END, 0, 100)
+ // 在Java中使用Kotlin DSL回调
+ .registerCallback(builder -> {
+ builder.createResult((aBoolean, s, view) -> {
+ toast("createResult:" + aBoolean.toString());
+ return null;
+ });
+ builder.dismiss(() -> {
+ toast("dismiss");
return null;
- })
- .show());
+ });
- findViewById(R.id.tvCloseFloat).setOnClickListener(v -> EasyFloat.dismiss(this));
+ // ...可根据需求复写其他方法
+
+ return null;
+ })
+ .show();
+ });
+
+ findViewById(R.id.tvCloseFloat).setOnClickListener(v -> EasyFloat.dismiss(TAG));
}
private void toast(String text) {
@@ -64,15 +67,11 @@ private void toast(String text) {
}
private void test() {
-
EasyFloat.with(this)
// 设置浮窗xml布局文件
.setLayout(R.layout.float_app, view -> {
// view就是我们传入的浮窗xml布局
})
- // 设置我们传入xml布局的详细信息(建议直接在setLayout方法中设置)
- .invokeView(view -> {
- })
// 设置浮窗显示类型,默认只在当前Activity显示,可选一直显示、仅前台显示
.setShowPattern(ShowPattern.ALL_TIME)
// 设置吸附方式,共15种模式,详情参考SidePattern
@@ -81,7 +80,7 @@ private void test() {
.setTag("testFloat")
// 设置浮窗是否可拖拽
.setDragEnable(true)
- // 系统浮窗是否包含EditText,仅针对系统浮窗,默认不包含
+ // 浮窗是否包含EditText,默认不包含
.hasEditText(false)
// 设置浮窗固定坐标,ps:设置固定坐标,Gravity属性和offset属性将无效
.setLocation(100, 200)
@@ -89,10 +88,8 @@ private void test() {
.setGravity(Gravity.END | Gravity.CENTER_VERTICAL, 0, 200)
// 设置宽高是否充满父布局,直接在xml设置match_parent属性无效
.setMatchParent(false, false)
- // 设置Activity浮窗的出入动画,可自定义,实现相应接口即可(策略模式),无需动画直接设置为null
+ // 设置浮窗的出入动画,可自定义,实现相应接口即可(策略模式),无需动画直接设置为null
.setAnimator(new DefaultAnimator())
- // 设置系统浮窗的出入动画,使用同上
- .setAppFloatAnimator(new AppFloatDefaultAnimator())
// 设置系统浮窗的不需要显示的页面
.setFilter(MainActivity.class, SecondActivity.class)
// 设置系统浮窗的有效显示高度(不包含虚拟导航栏的高度),基本用不到,除非有虚拟导航栏适配问题
@@ -155,7 +152,7 @@ public void dragEnd(@NotNull View view) {
// 测试方法重载
- EasyFloat.setDragEnable(this, false);
+ EasyFloat.dragEnable(false);
PermissionUtils.checkPermission(this);
diff --git a/example/src/main/java/com/lzf/easyfloat/example/activity/MainActivity.kt b/example/src/main/java/com/lzf/easyfloat/example/activity/MainActivity.kt
index 03923f2..7a335d8 100644
--- a/example/src/main/java/com/lzf/easyfloat/example/activity/MainActivity.kt
+++ b/example/src/main/java/com/lzf/easyfloat/example/activity/MainActivity.kt
@@ -7,7 +7,6 @@ import android.view.Gravity
import android.view.MotionEvent
import android.view.View
import android.widget.*
-import androidx.appcompat.app.AppCompatActivity
import com.lzf.easyfloat.EasyFloat
import com.lzf.easyfloat.enums.ShowPattern
import com.lzf.easyfloat.enums.SidePattern
@@ -16,15 +15,16 @@ import com.lzf.easyfloat.example.logger
import com.lzf.easyfloat.example.startActivity
import com.lzf.easyfloat.example.widget.RoundProgressBar
import com.lzf.easyfloat.example.widget.ScaleImage
-import com.lzf.easyfloat.interfaces.OnInvokeView
import com.lzf.easyfloat.interfaces.OnPermissionResult
+import com.lzf.easyfloat.interfaces.OnTouchRangeListener
import com.lzf.easyfloat.permission.PermissionUtils
+import com.lzf.easyfloat.utils.DragUtils
+import com.lzf.easyfloat.widget.switch.BaseSwitchView
import kotlinx.android.synthetic.main.activity_main.*
-import kotlinx.android.synthetic.main.float_seekbar.*
import kotlin.math.max
-class MainActivity : AppCompatActivity(), View.OnClickListener {
+class MainActivity : BaseActivity(), View.OnClickListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -51,32 +51,34 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
dismiss4.setOnClickListener(this)
openSecond.setOnClickListener(this)
+ openSwipeTest.setOnClickListener(this)
}
override fun onClick(v: View?) {
when (v) {
open1 -> showActivityFloat()
- hide1 -> EasyFloat.hide(this)
- show1 -> EasyFloat.show(this)
- dismiss1 -> EasyFloat.dismiss(this)
+ hide1 -> EasyFloat.hide("showActivityFloat")
+ show1 -> EasyFloat.show("showActivityFloat")
+ dismiss1 -> EasyFloat.dismiss("showActivityFloat")
open2 -> showActivity2()
- hide2 -> EasyFloat.hide(this, "seekBar")
- show2 -> EasyFloat.show(this, "seekBar")
- dismiss2 -> EasyFloat.dismiss(this, "seekBar")
+ hide2 -> EasyFloat.hide("seekBar")
+ show2 -> EasyFloat.show("seekBar")
+ dismiss2 -> EasyFloat.dismiss("seekBar")
// 检测权限根据需求考虑有无即可,权限申请为内部进行
open3 -> checkPermission()
- hide3 -> EasyFloat.hideAppFloat()
- show3 -> EasyFloat.showAppFloat()
- dismiss3 -> EasyFloat.dismissAppFloat()
+ hide3 -> EasyFloat.hide()
+ show3 -> EasyFloat.show()
+ dismiss3 -> EasyFloat.dismiss()
open4 -> checkPermission("scaleFloat")
- hide4 -> EasyFloat.hideAppFloat("scaleFloat")
- show4 -> EasyFloat.showAppFloat("scaleFloat")
- dismiss4 -> EasyFloat.dismissAppFloat("scaleFloat")
+ hide4 -> EasyFloat.hide("scaleFloat")
+ show4 -> EasyFloat.show("scaleFloat")
+ dismiss4 -> EasyFloat.dismiss("scaleFloat")
openSecond -> startActivity(this)
+ openSwipeTest -> startActivity(this)
else -> return
}
@@ -86,13 +88,15 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
* 测试Callback回调
*/
@SuppressLint("SetTextI18n")
- private fun showActivityFloat() {
+ private fun showActivityFloat(tag: String = "showActivityFloat") {
EasyFloat.with(this)
.setSidePattern(SidePattern.RESULT_HORIZONTAL)
- .setGravity(Gravity.END, 0, 100)
- .setLayout(R.layout.float_custom, OnInvokeView {
+ .setImmersionStatusBar(true)
+ .setGravity(Gravity.END, 0, 10)
+ .setLayout(R.layout.float_custom) {
it.findViewById(R.id.textView).setOnClickListener { toast() }
- })
+ }
+ .setTag(tag)
.registerCallback {
// 在此处设置view也可以,建议在setLayout进行view操作
createResult { isCreated, msg, _ -> logger.e("DSL: $isCreated $msg") }
@@ -112,11 +116,20 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
}
}
- drag { view, _ ->
+ drag { view, motionEvent ->
view.findViewById(R.id.textView).apply {
text = "我被拖拽..."
setBackgroundResource(R.drawable.corners_red)
}
+ DragUtils.registerDragClose(motionEvent, object : OnTouchRangeListener {
+ override fun touchInRange(inRange: Boolean, view: BaseSwitchView) {
+ setVibrator(inRange)
+ }
+
+ override fun touchUpInRange() {
+ EasyFloat.dismiss(tag, true)
+ }
+ })
}
dragEnd {
@@ -124,27 +137,27 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
text = "拖拽结束"
val location = IntArray(2)
getLocationOnScreen(location)
- setBackgroundResource(if (location[0] > 0) R.drawable.corners_left else R.drawable.corners_right)
+ setBackgroundResource(if (location[0] > 10) R.drawable.corners_left else R.drawable.corners_right)
}
}
}
.show()
}
- private fun showActivity2() {
+ private fun showActivity2(tag: String = "seekBar") {
// 改变浮窗1的文字
EasyFloat.getFloatView()?.findViewById(R.id.textView)?.text = "恭喜浮窗2"
EasyFloat.with(this)
- .setTag("seekBar")
+ .setTag(tag)
.setGravity(Gravity.CENTER)
- .setLayout(R.layout.float_seekbar, OnInvokeView {
+ .setLayout(R.layout.float_seekbar) {
it.findViewById(R.id.ivClose).setOnClickListener {
- EasyFloat.dismiss(this@MainActivity, "seekBar")
- }
- it.findViewById(R.id.tvProgress).setOnClickListener { tv ->
- toast((tv as TextView).text.toString())
+ EasyFloat.dismiss(tag)
}
+ val tvProgress = it.findViewById(R.id.tvProgress)
+ tvProgress.setOnClickListener { toast(tvProgress.text.toString()) }
+
it.findViewById(R.id.seekBar)
.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(
@@ -157,26 +170,25 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
override fun onStopTrackingTouch(seekBar: SeekBar?) {}
})
- })
+ }
.show()
}
private fun showAppFloat() {
- EasyFloat.with(this)
+ EasyFloat.with(this.applicationContext)
.setShowPattern(ShowPattern.ALL_TIME)
.setSidePattern(SidePattern.RESULT_SIDE)
- .setGravity(Gravity.CENTER)
- .setLayout(R.layout.float_app, OnInvokeView {
+ .setImmersionStatusBar(true)
+ .setGravity(Gravity.END, -20, 10)
+ .setLayout(R.layout.float_app) {
it.findViewById(R.id.ivClose).setOnClickListener {
- EasyFloat.dismissAppFloat()
+ EasyFloat.dismiss()
}
it.findViewById(R.id.tvOpenMain).setOnClickListener {
startActivity(this)
}
it.findViewById(R.id.checkbox)
- .setOnCheckedChangeListener { _, isChecked ->
- EasyFloat.appFloatDragEnable(isChecked)
- }
+ .setOnCheckedChangeListener { _, isChecked -> EasyFloat.dragEnable(isChecked) }
val progressBar = it.findViewById(R.id.roundProgressBar).apply {
setProgress(66, "66")
@@ -207,34 +219,54 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
// false
// }
// }
- })
+ }
+ .registerCallback {
+ drag { _, motionEvent ->
+ DragUtils.registerDragClose(motionEvent, object : OnTouchRangeListener {
+ override fun touchInRange(inRange: Boolean, view: BaseSwitchView) {
+ setVibrator(inRange)
+ view.findViewById(com.lzf.easyfloat.R.id.tv_delete).text =
+ if (inRange) "松手删除" else "删除浮窗"
+
+ view.findViewById(com.lzf.easyfloat.R.id.iv_delete)
+ .setImageResource(
+ if (inRange) com.lzf.easyfloat.R.drawable.icon_delete_selected
+ else com.lzf.easyfloat.R.drawable.icon_delete_normal
+ )
+ }
+
+ override fun touchUpInRange() {
+ EasyFloat.dismiss()
+ }
+ }, showPattern = ShowPattern.ALL_TIME)
+ }
+ }
.show()
}
-
private fun showAppFloat2(tag: String) {
- EasyFloat.with(this)
+ EasyFloat.with(this.applicationContext)
.setTag(tag)
.setShowPattern(ShowPattern.FOREGROUND)
.setLocation(100, 100)
- .setAppFloatAnimator(null)
+ .setAnimator(null)
.setFilter(SecondActivity::class.java)
- .setLayout(R.layout.float_app_scale, OnInvokeView {
+ .setLayout(R.layout.float_app_scale) {
val content = it.findViewById(R.id.rlContent)
val params = content.layoutParams as FrameLayout.LayoutParams
it.findViewById(R.id.ivScale).onScaledListener =
object : ScaleImage.OnScaledListener {
override fun onScaled(x: Float, y: Float, event: MotionEvent) {
- params.width = max(params.width + x.toInt(), 100)
- params.height = max(params.height + y.toInt(), 100)
+ params.width = max(params.width + x.toInt(), 200)
+ params.height = max(params.height + y.toInt(), 200)
content.layoutParams = params
}
}
it.findViewById(R.id.ivClose).setOnClickListener {
- EasyFloat.dismissAppFloat(tag)
+ EasyFloat.dismiss(tag)
}
- })
+ }
.show()
}
@@ -266,7 +298,6 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
})
}
-
private fun toast(string: String = "onClick") =
Toast.makeText(this, string, Toast.LENGTH_SHORT).show()
diff --git a/example/src/main/java/com/lzf/easyfloat/example/activity/SecondActivity.kt b/example/src/main/java/com/lzf/easyfloat/example/activity/SecondActivity.kt
index 62d926d..99c8567 100644
--- a/example/src/main/java/com/lzf/easyfloat/example/activity/SecondActivity.kt
+++ b/example/src/main/java/com/lzf/easyfloat/example/activity/SecondActivity.kt
@@ -1,21 +1,31 @@
package com.lzf.easyfloat.example.activity
import android.animation.Animator
-import android.animation.AnimatorSet
-import android.animation.ObjectAnimator
-import android.app.Activity
+import android.annotation.SuppressLint
import android.os.Bundle
+import android.view.Gravity
+import android.view.MotionEvent
import android.view.View
-import android.view.ViewGroup
+import android.view.WindowManager
import android.view.animation.BounceInterpolator
+import android.widget.RelativeLayout
+import android.widget.TextView
+import androidx.core.content.ContextCompat
import com.lzf.easyfloat.EasyFloat
import com.lzf.easyfloat.anim.DefaultAnimator
+import com.lzf.easyfloat.enums.ShowPattern
import com.lzf.easyfloat.enums.SidePattern
import com.lzf.easyfloat.example.R
import com.lzf.easyfloat.example.startActivity
-import com.lzf.easyfloat.interfaces.OnFloatAnimator
-import com.lzf.easyfloat.interfaces.OnInvokeView
+import com.lzf.easyfloat.interfaces.OnDisplayHeight
+import com.lzf.easyfloat.interfaces.OnFloatCallbacks
+import com.lzf.easyfloat.utils.DisplayUtils
import kotlinx.android.synthetic.main.activity_second.*
+import kotlinx.android.synthetic.main.activity_second.changeBackground
+import kotlinx.android.synthetic.main.activity_second.openEditTextFloat
+import kotlinx.android.synthetic.main.activity_second.openJavaTestActivity
+import kotlinx.android.synthetic.main.activity_second.recoverBackground
+import kotlinx.android.synthetic.main.activity_third.*
import kotlin.random.Random
/**
@@ -23,7 +33,7 @@ import kotlin.random.Random
* @function:
* @date: 2019-06-28 16:10
*/
-class SecondActivity : Activity() {
+class SecondActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -31,42 +41,156 @@ class SecondActivity : Activity() {
tvShow.setOnClickListener {
EasyFloat.with(this)
- .setLayout(R.layout.float_top_dialog, OnInvokeView {
- it.postDelayed({ EasyFloat.dismiss(this, it.tag.toString()) }, 2500)
- })
+ .setLayout(R.layout.float_top_dialog) {
+ it.postDelayed({ EasyFloat.dismiss(it.tag.toString()) }, 2333)
+ }
.setMatchParent(true)
.setSidePattern(SidePattern.TOP)
.setDragEnable(false)
.setTag(Random.nextDouble().toString())
.setAnimator(object : DefaultAnimator() {
override fun enterAnim(
- view: View, parentView: ViewGroup, sidePattern: SidePattern
- ): Animator? = super.enterAnim(view, parentView, sidePattern)?.apply {
- interpolator = BounceInterpolator()
- }
+ view: View,
+ params: WindowManager.LayoutParams,
+ windowManager: WindowManager,
+ sidePattern: SidePattern
+ ): Animator? =
+ super.enterAnim(view, params, windowManager, sidePattern)?.apply {
+ interpolator = BounceInterpolator()
+ }
override fun exitAnim(
- view: View, parentView: ViewGroup, sidePattern: SidePattern
- ): Animator? = super.exitAnim(view, parentView, sidePattern)?.setDuration(300)
+ view: View,
+ params: WindowManager.LayoutParams,
+ windowManager: WindowManager,
+ sidePattern: SidePattern
+ ): Animator? =
+ super.exitAnim(view, params, windowManager, sidePattern)?.setDuration(200)
})
.show()
}
- floatingView.apply {
- config.floatAnimator = object : OnFloatAnimator {
- override fun enterAnim(
- view: View, parentView: ViewGroup, sidePattern: SidePattern
- ): Animator = AnimatorSet().apply {
- play(ObjectAnimator.ofFloat(view, "alpha", 0f, 0.3f, 1f))
- .with(ObjectAnimator.ofFloat(view, "scaleX", 0f, 2f, 1f))
- .with(ObjectAnimator.ofFloat(view, "scaleY", 0f, 2f, 1f))
- .before(ObjectAnimator.ofFloat(view, "rotation", 0f, 360f, 0f))
- duration = 1000
- }
+ openEditTextFloat.setOnClickListener { showEditTextFloat() }
+
+ openJavaTestActivity.setOnClickListener { startActivity(this) }
+
+ changeBackground.setOnClickListener {
+ EasyFloat.getFloatView()?.apply {
+ findViewById(R.id.rlContent)
+ .setBackgroundColor(ContextCompat.getColor(this@SecondActivity, R.color.violet))
+
+ // ...其他View操作
}
}
- openThird.setOnClickListener { startActivity(this) }
+ recoverBackground.setOnClickListener {
+ EasyFloat.getFloatView()?.findViewById(R.id.rlContent)
+ ?.setBackgroundColor(ContextCompat.getColor(this, R.color.translucent))
+ }
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ private fun showEditTextFloat(tag: String? = "editTextFloat") {
+ EasyFloat.with(this)
+ .setShowPattern(ShowPattern.ALL_TIME)
+ .setGravity(Gravity.CENTER, 0, -300)
+ .setTag(tag)
+ .hasEditText(true)
+ .setLayout(R.layout.float_edit) {
+ // 注意看注释!
+// it.findViewById(R.id.editText).apply {
+// setOnTouchListener { _, event ->
+// // 如果设置了setOnTouchListener,需要在ACTION_DOWN时手动打开软键盘
+// // 如果未设置触摸监听,无需此操作,EasyFloat内部已经监听
+// if (event.action == MotionEvent.ACTION_DOWN) {
+// InputMethodUtils.openInputMethod(this, tag)
+// }
+//
+// // ....
+// // 其他业务逻辑....
+// false
+// }
+// }
+
+ it.findViewById(R.id.tvCloseFloat).setOnClickListener {
+ EasyFloat.dismiss(tag)
+ }
+ }
+ .show()
+ }
+
+ private fun showFloat() {
+
+ EasyFloat.with(this).setLayout(R.layout.float_app).show()
+
+ EasyFloat.with(this)
+ // 设置浮窗xml布局文件,并可设置详细信息
+ .setLayout(R.layout.float_app) { }
+ // 设置浮窗显示类型,默认只在当前Activity显示,可选一直显示、仅前台显示
+ .setShowPattern(ShowPattern.ALL_TIME)
+ // 设置吸附方式,共15种模式,详情参考SidePattern
+ .setSidePattern(SidePattern.RESULT_HORIZONTAL)
+ // 设置浮窗的标签,用于区分多个浮窗
+ .setTag("testFloat")
+ // 设置浮窗是否可拖拽
+ .setDragEnable(true)
+ // 浮窗是否包含EditText,默认不包含
+ .hasEditText(false)
+ // 设置浮窗固定坐标,ps:设置固定坐标,Gravity属性和offset属性将无效
+ .setLocation(100, 200)
+ // 设置浮窗的对齐方式和坐标偏移量
+ .setGravity(Gravity.END or Gravity.CENTER_VERTICAL, 0, 200)
+ // 设置宽高是否充满父布局,直接在xml设置match_parent属性无效
+ .setMatchParent(widthMatch = false, heightMatch = false)
+ // 设置浮窗的出入动画,可自定义,实现相应接口即可(策略模式),无需动画直接设置为null
+ .setAnimator(DefaultAnimator())
+ // 设置系统浮窗的不需要显示的页面
+ .setFilter(MainActivity::class.java, SecondActivity::class.java)
+ // 设置系统浮窗的有效显示高度(不包含虚拟导航栏的高度),基本用不到,除非有虚拟导航栏适配问题
+ .setDisplayHeight { context -> DisplayUtils.rejectedNavHeight(context) }
+ // 浮窗的一些状态回调,如:创建结果、显示、隐藏、销毁、touchEvent、拖拽过程、拖拽结束。
+ // ps:通过Kotlin DSL实现的回调,可以按需复写方法,用到哪个写哪个
+ .registerCallback {
+ createResult { isCreated, msg, view -> }
+ show { }
+ hide { }
+ dismiss { }
+ touchEvent { view, motionEvent -> }
+ drag { view, motionEvent -> }
+ dragEnd { }
+ }
+ .registerCallbacks(object : OnFloatCallbacks {
+ override fun createdResult(isCreated: Boolean, msg: String?, view: View?) {
+
+ }
+
+ override fun show(view: View) {
+
+ }
+
+ override fun hide(view: View) {
+
+ }
+
+ override fun dismiss() {
+
+ }
+
+ override fun touchEvent(view: View, event: MotionEvent) {
+
+ }
+
+ override fun drag(view: View, event: MotionEvent) {
+
+ }
+
+ override fun dragEnd(view: View) {
+
+ }
+
+ })
+ // 创建浮窗(这是关键哦😂)
+ .show()
}
}
\ No newline at end of file
diff --git a/example/src/main/java/com/lzf/easyfloat/example/activity/SwipeTestActivity.kt b/example/src/main/java/com/lzf/easyfloat/example/activity/SwipeTestActivity.kt
new file mode 100644
index 0000000..a237b23
--- /dev/null
+++ b/example/src/main/java/com/lzf/easyfloat/example/activity/SwipeTestActivity.kt
@@ -0,0 +1,97 @@
+package com.lzf.easyfloat.example.activity
+
+import android.os.Bundle
+import android.view.Gravity
+import android.view.MotionEvent
+import android.widget.*
+import androidx.constraintlayout.widget.ConstraintLayout
+import com.lzf.easyfloat.EasyFloat
+import com.lzf.easyfloat.enums.ShowPattern
+import com.lzf.easyfloat.enums.SidePattern
+import com.lzf.easyfloat.example.R
+import com.lzf.easyfloat.interfaces.OnTouchRangeListener
+import com.lzf.easyfloat.permission.PermissionUtils
+import com.lzf.easyfloat.utils.DragUtils
+import com.lzf.easyfloat.widget.switch.BaseSwitchView
+import kotlinx.android.synthetic.main.activity_swipe_test.*
+
+/**
+ * @author: liuzhenfeng
+ * @date: 2020/10/26 18:21
+ * @Package: com.lzf.easyfloat.example.activity
+ * @Description:
+ */
+class SwipeTestActivity : BaseActivity() {
+
+ companion object {
+ const val FLOAT_TAG = "SwipeTestActivity"
+ }
+
+ private var noPermission = false
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_swipe_test)
+
+ }
+
+ override fun isSupportSwipeBack(): Boolean = true
+
+ override fun onSwipeBackLayoutExecuted() {
+ if (!noPermission) bgaSwipeBackHelper.swipeBackward()
+ }
+
+ override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
+ DragUtils.registerSwipeAdd(ev, object : OnTouchRangeListener {
+ override fun touchInRange(inRange: Boolean, view: BaseSwitchView) {
+ setVibrator(inRange)
+ view.findViewById(com.lzf.easyfloat.R.id.iv_add)
+ .setImageResource(
+ if (inRange) com.lzf.easyfloat.R.drawable.add_selected else com.lzf.easyfloat.R.drawable.add_normal
+ )
+ }
+
+ override fun touchUpInRange() {
+ noPermission = !PermissionUtils.checkPermission(this@SwipeTestActivity)
+ showFloat()
+ }
+ }, slideOffset = slideOffset)
+ return super.dispatchTouchEvent(ev)
+ }
+
+ private fun showFloat() = EasyFloat.with(this.applicationContext)
+ .setTag(FLOAT_TAG)
+ .setShowPattern(ShowPattern.FOREGROUND)
+ .setImmersionStatusBar(true)
+ .setGravity(Gravity.END, 0, 500)
+ .setSidePattern(SidePattern.RESULT_HORIZONTAL)
+ .setFilter(SecondActivity::class.java)
+ .setLayout(R.layout.float_swipe) {
+ it.findViewById(R.id.cl_content).setOnClickListener {
+ showContractFloat()
+ }
+ }
+ .registerCallback {
+ createResult { _, _, _ ->
+ if (noPermission && !this@SwipeTestActivity.isFinishing) bgaSwipeBackHelper.swipeBackward()
+ }
+
+ drag { view, event ->
+ // 注册拖拽关闭
+ registerDragClose(event)
+
+ view.findViewById(R.id.cl_content)
+ .setBackgroundResource(R.drawable.corners_red)
+ }
+
+ dragEnd {
+ it.findViewById(R.id.cl_content).apply {
+ val location = IntArray(2)
+ getLocationOnScreen(location)
+ setBackgroundResource(if (location[0] > 10) R.drawable.corners_left else R.drawable.corners_right)
+ }
+ }
+ }
+ .show()
+
+}
diff --git a/example/src/main/java/com/lzf/easyfloat/example/activity/ThirdActivity.kt b/example/src/main/java/com/lzf/easyfloat/example/activity/ThirdActivity.kt
deleted file mode 100644
index fbbb199..0000000
--- a/example/src/main/java/com/lzf/easyfloat/example/activity/ThirdActivity.kt
+++ /dev/null
@@ -1,158 +0,0 @@
-package com.lzf.easyfloat.example.activity
-
-import android.app.Activity
-import android.os.Bundle
-import android.view.Gravity
-import android.view.MotionEvent
-import android.view.View
-import android.widget.EditText
-import android.widget.RelativeLayout
-import android.widget.TextView
-import androidx.core.content.ContextCompat
-import com.lzf.easyfloat.EasyFloat
-import com.lzf.easyfloat.anim.AppFloatDefaultAnimator
-import com.lzf.easyfloat.anim.DefaultAnimator
-import com.lzf.easyfloat.enums.ShowPattern
-import com.lzf.easyfloat.enums.SidePattern
-import com.lzf.easyfloat.example.R
-import com.lzf.easyfloat.example.startActivity
-import com.lzf.easyfloat.interfaces.OnDisplayHeight
-import com.lzf.easyfloat.interfaces.OnFloatCallbacks
-import com.lzf.easyfloat.interfaces.OnInvokeView
-import com.lzf.easyfloat.utils.DisplayUtils
-import com.lzf.easyfloat.utils.InputMethodUtils
-import kotlinx.android.synthetic.main.activity_third.*
-
-/**
- * @author: liuzhenfeng
- * @function: 测试EditText
- * @date: 2019-07-26 13:13
- */
-class ThirdActivity : Activity() {
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_third)
-
- openEditTextFloat.setOnClickListener { showEditTextFloat() }
-
- openJavaTestActivity.setOnClickListener { startActivity(this) }
-
- changeBackground.setOnClickListener {
- EasyFloat.getAppFloatView()?.apply {
- findViewById(R.id.rlContent)
- .setBackgroundColor(ContextCompat.getColor(this@ThirdActivity, R.color.violet))
-
- // ...其他View操作
- }
- }
-
- recoverBackground.setOnClickListener {
- EasyFloat.getAppFloatView()?.findViewById(R.id.rlContent)
- ?.setBackgroundColor(ContextCompat.getColor(this, R.color.translucent))
- }
- }
-
- private fun showEditTextFloat(tag: String? = "editTextFloat") {
- EasyFloat.with(this)
- .setShowPattern(ShowPattern.ALL_TIME)
- .setGravity(Gravity.CENTER, 0, -300)
- .setTag(tag)
- .hasEditText(true)
- .setLayout(R.layout.float_edit, OnInvokeView {
- it.findViewById(R.id.editText).apply {
- // 点击打开软键盘
- setOnClickListener { InputMethodUtils.openInputMethod(this as EditText, tag) }
- // 首次点击,不会回调onClick事件,但是会改变焦点
- setOnFocusChangeListener { _, hasFocus ->
- if (hasFocus) InputMethodUtils.openInputMethod(this as EditText, tag)
- }
- }
-
- it.findViewById(R.id.tvCloseFloat).setOnClickListener {
- EasyFloat.dismissAppFloat(tag)
- }
- })
- .show()
- }
-
- private fun showFloat() {
-
- EasyFloat.with(this).setLayout(R.layout.float_app).show()
-
- EasyFloat.with(this)
- // 设置浮窗xml布局文件,并可设置详细信息
- .setLayout(R.layout.float_app, OnInvokeView { })
- // 设置我们传入xml布局的详细信息
- .invokeView(OnInvokeView { })
- // 设置浮窗显示类型,默认只在当前Activity显示,可选一直显示、仅前台显示
- .setShowPattern(ShowPattern.ALL_TIME)
- // 设置吸附方式,共15种模式,详情参考SidePattern
- .setSidePattern(SidePattern.RESULT_HORIZONTAL)
- // 设置浮窗的标签,用于区分多个浮窗
- .setTag("testFloat")
- // 设置浮窗是否可拖拽
- .setDragEnable(true)
- // 系统浮窗是否包含EditText,仅针对系统浮窗,默认不包含
- .hasEditText(false)
- // 设置浮窗固定坐标,ps:设置固定坐标,Gravity属性和offset属性将无效
- .setLocation(100, 200)
- // 设置浮窗的对齐方式和坐标偏移量
- .setGravity(Gravity.END or Gravity.CENTER_VERTICAL, 0, 200)
- // 设置宽高是否充满父布局,直接在xml设置match_parent属性无效
- .setMatchParent(widthMatch = false, heightMatch = false)
- // 设置Activity浮窗的出入动画,可自定义,实现相应接口即可(策略模式),无需动画直接设置为null
- .setAnimator(DefaultAnimator())
- // 设置系统浮窗的出入动画,使用同上
- .setAppFloatAnimator(AppFloatDefaultAnimator())
- // 设置系统浮窗的不需要显示的页面
- .setFilter(MainActivity::class.java, SecondActivity::class.java)
- // 设置系统浮窗的有效显示高度(不包含虚拟导航栏的高度),基本用不到,除非有虚拟导航栏适配问题
- .setDisplayHeight(OnDisplayHeight { context -> DisplayUtils.rejectedNavHeight(context) })
- // 浮窗的一些状态回调,如:创建结果、显示、隐藏、销毁、touchEvent、拖拽过程、拖拽结束。
- // ps:通过Kotlin DSL实现的回调,可以按需复写方法,用到哪个写哪个
- .registerCallback {
- createResult { isCreated, msg, view -> }
- show { }
- hide { }
- dismiss { }
- touchEvent { view, motionEvent -> }
- drag { view, motionEvent -> }
- dragEnd { }
- }
- .registerCallbacks(object : OnFloatCallbacks {
- override fun createdResult(isCreated: Boolean, msg: String?, view: View?) {
- TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
- }
-
- override fun show(view: View) {
- TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
- }
-
- override fun hide(view: View) {
- TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
- }
-
- override fun dismiss() {
- TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
- }
-
- override fun touchEvent(view: View, event: MotionEvent) {
- TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
- }
-
- override fun drag(view: View, event: MotionEvent) {
- TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
- }
-
- override fun dragEnd(view: View) {
- TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
- }
-
- })
- // 创建浮窗(这是关键哦😂)
- .show()
- }
-
-}
-
diff --git a/example/src/main/java/com/lzf/easyfloat/example/widget/BubbleSurfaceView.kt b/example/src/main/java/com/lzf/easyfloat/example/widget/BubbleSurfaceView.kt
new file mode 100644
index 0000000..cd969d7
--- /dev/null
+++ b/example/src/main/java/com/lzf/easyfloat/example/widget/BubbleSurfaceView.kt
@@ -0,0 +1,91 @@
+package com.lzf.easyfloat.example.widget
+
+import android.content.Context
+import android.graphics.*
+import android.util.AttributeSet
+import android.view.MotionEvent
+import android.view.SurfaceView
+import androidx.core.content.ContextCompat
+import com.lzf.easyfloat.example.R
+import kotlinx.coroutines.*
+
+/**
+ * @Description: Copy https://github.com/longway777/CustomViews
+ */
+class BubbleSurfaceView @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
+) : SurfaceView(context, attrs, defStyleAttr), CoroutineScope by MainScope() {
+
+ private var isRunning = false
+
+ var deviation = 50f
+ set(value) {
+ field = value
+ paint.pathEffect =
+ ComposePathEffect(CornerPathEffect(50f), DiscretePathEffect(30f, deviation / 2))
+ }
+
+ private val paintColors = arrayOf(
+ Color.CYAN,
+ Color.RED,
+ Color.GREEN,
+ Color.MAGENTA,
+ Color.YELLOW,
+ Color.WHITE,
+ Color.GRAY
+ )
+
+ private var drawingCirclesList = mutableListOf()
+ private var job: Job? = null
+ private var touchX = 0f
+ private var touchY = 0f
+
+ private val paint = Paint().apply {
+ style = Paint.Style.STROKE
+ strokeWidth = 10f
+ }
+
+ private data class DrawingCircle(val x: Float, val y: Float, val color: Int, var radius: Float)
+
+ override fun onTouchEvent(event: MotionEvent?): Boolean {
+ performClick()
+ touchX = (event?.x) ?: 0f
+ touchY = (event?.y) ?: 0f
+ val drawingCircle = DrawingCircle(touchX, touchY, paintColors.random(), 1f)
+ drawingCirclesList.add(drawingCircle)
+ if (drawingCirclesList.size > 20) drawingCirclesList.removeAt(0)
+ return super.onTouchEvent(event)
+ }
+
+ private fun createJob() {
+ job?.cancel()
+ job = launch(Dispatchers.Default) {
+ while (isRunning) {
+ if (holder.surface.isValid) {
+ val canvas = holder.lockCanvas()
+ canvas?.drawColor(ContextCompat.getColor(context, R.color.light_gray))
+ drawingCirclesList.toList().filter { it.radius < 3000 }.forEach {
+ paint.color = it.color
+ canvas?.drawCircle(it.x, it.y, it.radius, paint)
+ it.radius += 10f
+ }
+ if (holder.surface.isValid)
+ holder?.unlockCanvasAndPost(canvas)
+ }
+ }
+ }
+ }
+
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+ isRunning = true
+ createJob()
+ }
+
+ override fun onDetachedFromWindow() {
+ super.onDetachedFromWindow()
+ isRunning = false
+ job?.cancel()
+ }
+
+}
\ No newline at end of file
diff --git a/example/src/main/java/com/lzf/easyfloat/example/widget/CircleLoadingView.kt b/example/src/main/java/com/lzf/easyfloat/example/widget/CircleLoadingView.kt
new file mode 100644
index 0000000..2037de8
--- /dev/null
+++ b/example/src/main/java/com/lzf/easyfloat/example/widget/CircleLoadingView.kt
@@ -0,0 +1,187 @@
+package com.lzf.easyfloat.example.widget
+
+import android.animation.Animator
+import android.animation.ValueAnimator
+import android.annotation.SuppressLint
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.RectF
+import android.util.AttributeSet
+import android.view.View
+import android.view.animation.LinearInterpolator
+import com.lzf.easyfloat.example.R
+import com.lzf.easyfloat.utils.DisplayUtils
+import kotlin.math.cos
+import kotlin.math.min
+import kotlin.math.sin
+
+/**
+ * @author: liuzhenfeng
+ * @date: 12/24/20 12:20
+ * @Package: com.lzf.easyfloat.example.widget
+ * @Description:
+ */
+class CircleLoadingView @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
+) : View(context, attrs, defStyleAttr) {
+
+ // 环形圆点的相关属性
+ private var dotRadius = 0f
+ private var dotPI = 0f
+ private var dotRotatePI = 0f
+ private var dotRotateStandard = 0f
+ private var dotRealRotatePI = 0f
+ private lateinit var dotPaint: Paint
+
+ // 圆弧的相关属性
+ private var arcRadius = 0f
+ private var startAngle = -30f
+ private var sweepAngle = 240f
+ private lateinit var arcPaint: Paint
+ private lateinit var rectF: RectF
+
+ // 可自定义的属性
+ private var arcWidth = dp2px(context, 2f)
+ private var loadingColor = Color.WHITE
+ private var dotSize = 16
+ private var durationTime = 1500L
+ // 圆点每周期旋转角度
+ private var dotAngle = 90f
+
+ private var centX = 0
+ private var centY = 0
+ private var animatorValue = 0f
+ private var animator: ValueAnimator? = null
+
+ init {
+ attrs?.apply { initAttrs(this) }
+ initValue()
+ initPaint()
+ }
+
+ private fun initAttrs(attrs: AttributeSet) =
+ context.theme.obtainStyledAttributes(attrs, R.styleable.CircleLoadingView, 0, 0).apply {
+ arcWidth = getDimension(R.styleable.CircleLoadingView_arcWidth, arcWidth)
+ loadingColor = getColor(R.styleable.CircleLoadingView_loadingColor, loadingColor)
+ dotSize = getInt(R.styleable.CircleLoadingView_dotSize, dotSize)
+ durationTime =
+ getFloat(R.styleable.CircleLoadingView_durationTime, durationTime.toFloat()).toLong()
+ dotAngle = getFloat(R.styleable.CircleLoadingView_dotAngle, dotAngle)
+ }.recycle()
+
+ private fun initValue() {
+ dotPI = Math.PI.toFloat() * 2 / dotSize
+ dotRadius = arcWidth * 0.5f
+ }
+
+ private fun initPaint() {
+ dotPaint = Paint().apply {
+ color = loadingColor
+ style = Paint.Style.FILL
+ isAntiAlias = true
+ }
+
+ arcPaint = Paint().apply {
+ color = loadingColor
+ style = Paint.Style.STROKE
+ strokeCap = Paint.Cap.ROUND
+ strokeWidth = arcWidth
+ isAntiAlias = true
+ }
+ }
+
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+ initAnimator()
+ }
+
+ override fun onDetachedFromWindow() {
+ super.onDetachedFromWindow()
+ animator?.cancel()
+ }
+
+ private fun initAnimator() {
+ animator = ValueAnimator.ofFloat(0f, 1f).apply {
+ duration = durationTime
+ // 匀速运动,圆弧单独计算插值器
+ interpolator = LinearInterpolator()
+ repeatCount = -1
+ addUpdateListener { animation ->
+ animatorValue = animation.animatedValue as Float
+ // 计算环形圆点的旋转角度,再转成圆周率格式
+ dotRotatePI =
+ (dotRotateStandard + dotAngle * animatorValue) / 180f * Math.PI.toFloat()
+ // 为圆弧设置插值器,计算逻辑和设置动画插值器相同,这里为了能和环形圆点共用一个动画,计算移到内部
+ animatorValue = getInterpolation(animatorValue) * 120f
+ if (animatorValue <= 60f) {
+ startAngle = -30f + animatorValue * 5
+ sweepAngle = 240f - animatorValue * 4
+ } else {
+ startAngle = animatorValue - 150f
+ sweepAngle = (animatorValue - 60f) * 4
+ }
+ invalidate()
+ }
+ addListener(object : Animator.AnimatorListener {
+ override fun onAnimationRepeat(p0: Animator?) {
+ dotRotateStandard += dotAngle
+ if (dotRotateStandard >= 360f) dotRotateStandard -= 360f
+ }
+
+ override fun onAnimationEnd(p0: Animator?) {}
+ override fun onAnimationCancel(p0: Animator?) {}
+ override fun onAnimationStart(p0: Animator?) {}
+ })
+ start()
+ }
+ }
+
+ private fun getInterpolation(t: Float) = if (t > 0.5f) 1 - 2 * (1 - t) * t else 2 * (1 - t) * t
+
+ @SuppressLint("DrawAllocation")
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ var width = getMeasureSize(widthMeasureSpec, dp2pxInt(context, 120f))
+ var height = getMeasureSize(heightMeasureSpec, dp2pxInt(context, 120f))
+ setMeasuredDimension(width, height)
+
+ width = width - paddingLeft - paddingRight
+ height = height - paddingTop - paddingBottom
+ centX = width.shr(1) + paddingLeft
+ centY = height.shr(1) + paddingTop
+ arcRadius = (min(width, height) - arcWidth) * 0.5f
+ rectF = RectF(centX - arcRadius, centY - arcRadius, centX + arcRadius, centY + arcRadius)
+ }
+
+ private fun getMeasureSize(measureSpec: Int, defaultSize: Int): Int {
+ val specMode = MeasureSpec.getMode(measureSpec)
+ val specSize = MeasureSpec.getSize(measureSpec)
+ return when (specMode) {
+ // 确切大小,所以将得到的尺寸给view
+ MeasureSpec.EXACTLY -> specSize
+ // 默认值为 xxx px,此处要结合父控件给子控件的最多大小(要不然会填充父控件),所以采用最小值
+ MeasureSpec.AT_MOST -> min(defaultSize, specSize)
+ else -> defaultSize
+ }
+ }
+
+ override fun onDraw(canvas: Canvas?) {
+ canvas?.apply {
+ for (i in 1..dotSize) {
+ dotRealRotatePI = i * dotPI + dotRotatePI
+ drawCircle(
+ arcRadius * cos(dotRealRotatePI) + centX,
+ arcRadius * sin(dotRealRotatePI) + centY,
+ dotRadius, dotPaint
+ )
+ }
+ drawArc(rectF, startAngle, sweepAngle, false, arcPaint)
+ }
+ }
+
+}
+
+fun dp2px(context: Context, dpVal: Float) = DisplayUtils.dp2px(context, dpVal).toFloat()
+
+fun dp2pxInt(context: Context, dpVal: Float): Int = DisplayUtils.dp2px(context, dpVal)
diff --git a/example/src/main/res/layout/activity_main.xml b/example/src/main/res/layout/activity_main.xml
index 335c957..231cf96 100644
--- a/example/src/main/res/layout/activity_main.xml
+++ b/example/src/main/res/layout/activity_main.xml
@@ -7,13 +7,17 @@
android:orientation="vertical"
tools:context="com.lzf.easyfloat.example.activity.MainActivity">
+
+
+
+
\ No newline at end of file
diff --git a/example/src/main/res/layout/activity_second.xml b/example/src/main/res/layout/activity_second.xml
index 97f90d5..5ee707c 100644
--- a/example/src/main/res/layout/activity_second.xml
+++ b/example/src/main/res/layout/activity_second.xml
@@ -12,37 +12,26 @@
android:text="Show" />
+ android:text="OpenEditTextFloat" />
-
-
-
-
-
-
-
+
-
+
-
+
\ No newline at end of file
diff --git a/example/src/main/res/layout/activity_swipe_test.xml b/example/src/main/res/layout/activity_swipe_test.xml
new file mode 100644
index 0000000..bb2b493
--- /dev/null
+++ b/example/src/main/res/layout/activity_swipe_test.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/example/src/main/res/layout/float_contract.xml b/example/src/main/res/layout/float_contract.xml
new file mode 100644
index 0000000..539a8fc
--- /dev/null
+++ b/example/src/main/res/layout/float_contract.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/example/src/main/res/layout/float_swipe.xml b/example/src/main/res/layout/float_swipe.xml
new file mode 100644
index 0000000..9d5e698
--- /dev/null
+++ b/example/src/main/res/layout/float_swipe.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/example/src/main/res/layout/float_top_dialog.xml b/example/src/main/res/layout/float_top_dialog.xml
index 2d49f92..694e99b 100644
--- a/example/src/main/res/layout/float_top_dialog.xml
+++ b/example/src/main/res/layout/float_top_dialog.xml
@@ -1,25 +1,54 @@
-
+ android:layout_marginHorizontal="8dp"
+ android:layout_marginTop="6dp"
+ android:layout_marginBottom="4dp"
+ app:cardBackgroundColor="@color/translucent"
+ app:cardCornerRadius="12dp"
+ app:cardElevation="4dp">
-
-
-
+ android:gravity="center"
+ android:paddingVertical="12dp">
+
+
+
+
+
+
+
+
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/example/src/main/res/layout/popup_test.xml b/example/src/main/res/layout/popup_test.xml
new file mode 100644
index 0000000..0da4144
--- /dev/null
+++ b/example/src/main/res/layout/popup_test.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/example/src/main/res/values/attrs.xml b/example/src/main/res/values/attrs.xml
index f07acef..7610a0e 100644
--- a/example/src/main/res/values/attrs.xml
+++ b/example/src/main/res/values/attrs.xml
@@ -19,8 +19,19 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/example/src/main/res/values/colors.xml b/example/src/main/res/values/colors.xml
index 1dfa920..deb1728 100644
--- a/example/src/main/res/values/colors.xml
+++ b/example/src/main/res/values/colors.xml
@@ -21,5 +21,6 @@
#3D8EC0CD
#E6FFFFFF
#989898
+ #242424
diff --git a/example/src/main/res/values/styles.xml b/example/src/main/res/values/styles.xml
index ec93143..45e1ade 100644
--- a/example/src/main/res/values/styles.xml
+++ b/example/src/main/res/values/styles.xml
@@ -1,13 +1,19 @@
-
+
-