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 @@ - + -