Android 应用内悬浮球开发

news/2024/11/24 18:45:08/

悬浮窗口主要分为两类:一类是应用内悬浮窗口,一类是系统类的悬浮窗口(类似微信视频弹窗,由于会覆盖在其他应用上,需要申请额外的系统权限)。

其本质上都是一样,创建某个window,只是创建的window的type不一样,可以参考官方对不同type的描述文档。

本文主要介绍的是应用内的悬浮球如何开发

根据文档描述,我们可以知道TYPE_APPLICATION_PANEL适合用于应用内悬浮球的开发。
由于应用的悬浮球是依附在某Activity上的,这就需要在切换Activity的时候,不断切换悬浮球的token。所以我们选择在Activity的生命周期监听做处理:

class FloatWindowLifecycle : Application.ActivityLifecycleCallbacks {var weakCurrentActivity: WeakReference<Activity?>? = nullvar weakGlobalListener: WeakReference<ViewTreeObserver.OnGlobalLayoutListener>? = nulloverride fun onActivityCreated(activity: Activity?, savedInstanceState: Bundle?) {}override fun onActivityStarted(activity: Activity?) {}override fun onActivityResumed(activity: Activity?) {weakCurrentActivity = WeakReference(activity)activity?.window?.decorView?.let { decorView ->decorView.viewTreeObserver?.let { viewTree ->if (decorView.windowToken != null) {FloatWindowUtils.bindDebugPanelFloatWindow(activity, decorView.windowToken)weakGlobalListener?.get()?.let { globalListener ->decorView.viewTreeObserver.removeOnGlobalLayoutListener(globalListener)}} else {val globalListener = object : ViewTreeObserver.OnGlobalLayoutListener {override fun onGlobalLayout() {activity.window?.decorView?.windowToken?.let {FloatWindowUtils.bindDebugPanelFloatWindow(activity, it)}decorView.viewTreeObserver.removeOnGlobalLayoutListener(this)}}viewTree.addOnGlobalLayoutListener(globalListener)weakGlobalListener = WeakReference(globalListener)}}}}override fun onActivityPaused(activity: Activity?) {activity?.let {FloatWindowUtils.unbindDebugPanelFloatWindow(activity)}}override fun onActivityStopped(activity: Activity?) {}override fun onActivityDestroyed(activity: Activity?) {}override fun onActivitySaveInstanceState(activity: Activity?, outState: Bundle?) {}
}

工具类封装:
工具类主要提供了 WindowManager.LayoutParams的封装。

internal object FloatWindowUtils {fun updateLayoutParams(params: WindowManager.LayoutParams?,pToken: IBinder): WindowManager.LayoutParams {return params?.apply {token = pToken}?: WindowManager.LayoutParams().apply {type = WindowManager.LayoutParams.TYPE_APPLICATION_PANELflags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE orWindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH orWindowManager.LayoutParams.FLAG_NOT_TOUCH_MODALformat = PixelFormat.RGBA_8888gravity = Gravity.CENTER_VERTICAL or Gravity.STARTwidth = WindowManager.LayoutParams.WRAP_CONTENTheight = WindowManager.LayoutParams.WRAP_CONTENTtoken = pToken}}fun initDebugPanelFloatWindow(context: Context,clickAction: (context: Context) -> Unit) {FloatWindowManager.getInstance(context.applicationContext).addWindowLayout(object : FloatWindowLayout(context.applicationContext) {override fun stickySide(): Boolean = trueoverride fun uniqueStr(): String = FloatWindowConst.UNIQUE_STR_DEBUG}.apply {addView(ImageView(context.applicationContext).apply {setImageDrawable(ContextCompat.getDrawable(context,R.drawable.house))setOnClickListener {clickAction(it.context)}})})}fun bindDebugPanelFloatWindow(context: Context, token: IBinder) {FloatWindowManager.getInstance(context).bindWindowLayout(FloatWindowConst.UNIQUE_STR_DEBUG, token)}fun unbindDebugPanelFloatWindow(context: Context) {FloatWindowManager.getInstance(context).unbindWindowLayout(FloatWindowConst.UNIQUE_STR_DEBUG)}fun getScreenWidth(context: Context): Int {return context.resources.displayMetrics.widthPixels}}

封装FloatWindowManager 管理类:
主要用于WindowLayout管理和向WindowManager中添加以及移除某个View。

internal class FloatWindowManager private constructor(context: Context) {companion object {@Volatileprivate var instance: FloatWindowManager? = nullfun getInstance(c: Context): FloatWindowManager {if (instance == null) {synchronized(FloatWindowManager::class) {if (instance == null) {instance = FloatWindowManager(c.applicationContext)}}}return instance!!}}private var windowViewList = mutableListOf<FloatWindowLayout>()private var windowManager =context.getSystemService(Context.WINDOW_SERVICE) as WindowManagerprivate fun hasWindowLayout(key: String): Boolean {windowViewList.forEach {if (it.uniqueStr() == key) {return true}}return false}fun addWindowLayout(view: FloatWindowLayout) {if (hasWindowLayout(view.uniqueStr())) {return}windowViewList.add(view)}fun removeWindowLayout(key: String) {var target: FloatWindowLayout? = nullwindowViewList.forEach {if (it.uniqueStr() == key) {target = it}}target?.let {windowViewList.remove(it)}}fun bindWindowLayout(key: String, token: IBinder) {windowViewList.forEach {if (it.uniqueStr() == key) {val params = it.layoutParams as? WindowManager.LayoutParamsif (!it.isAddToWindowManager()) {windowManager.addView(it, FloatWindowUtils.updateLayoutParams(params, token))it.setAddToWindowManager(true)} else {windowManager.removeView(it)windowManager.addView(it, FloatWindowUtils.updateLayoutParams(params, token))it.setAddToWindowManager(true)}}}}fun unbindWindowLayout(key: String) {windowViewList.forEach {if (it.uniqueStr() == key) {if (it.isAddToWindowManager()) {windowManager.removeView(it)it.setAddToWindowManager(false)}}}}
}

抽象类:FloatWindowLayout

添加到windowManager中的的ViewGroup,继承至FeameLayout,你可以添加各种View在其中。

abstract class FloatWindowLayout : FrameLayout {private var lastX = 0fprivate var lastY = 0fprivate var downX = 0fprivate var downY = 0fprivate var startMove = falseprivate var animator: ValueAnimator? = nullprivate var isAddToWindowManager = falseconstructor(context: Context) : super(context)constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context,attrs,defStyleAttr)override fun onTouchEvent(event: MotionEvent): Boolean {when (event.action) {MotionEvent.ACTION_MOVE -> touchMove(event)MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> touchCancel()}return true}private fun touchMove(event: MotionEvent) {val rawX = event.rawXval rawY = event.rawYval offsetX = (rawX - lastX).toInt()val offsetY = (rawY - lastY).toInt()lastX = rawXlastY = rawYval params = layoutParams as WindowManager.LayoutParamsparams.x += offsetXparams.y += offsetYval windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManagerwindowManager.updateViewLayout(this, params)}private fun touchCancel() {if (stickySide()) {  //自动吸边val params = layoutParams as WindowManager.LayoutParamsval screenWidth = FloatWindowUtils.getScreenWidth(context)val currentX = params.xval destX = if (currentX + width / 2 > screenWidth / 2) {//向右screenWidth - width} else {//向左0}animator = ValueAnimator.ofInt(currentX, destX).apply {duration = 200interpolator = AccelerateInterpolator()addUpdateListener { animation ->animation?.run {val value = animation.animatedValue as Intparams.x = valueif (isAttachedToWindow) {val windowManager =context.getSystemService(Context.WINDOW_SERVICE) as WindowManagerwindowManager.updateViewLayout(this@FloatWindowLayout, params)} else {animation.cancel()}}}start()}}}override fun onInterceptTouchEvent(event: MotionEvent): Boolean {var intercept = falsewhen (event.action) {MotionEvent.ACTION_DOWN -> {animator?.also {it.cancel()}startMove = falsedownX = event.rawXdownY = event.rawYlastX = event.rawXlastY = event.rawY}MotionEvent.ACTION_MOVE -> {val offsetX = abs(event.rawX - downX)val offsetY = abs(event.rawY - downY)val minTouchSlop = ViewConfiguration.get(context).scaledTouchSlopif (startMove || (offsetX > minTouchSlop || offsetY > minTouchSlop)) {intercept = true}}MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {startMove = false}}return intercept}abstract fun uniqueStr(): Stringabstract fun stickySide(): Booleanfun isAddToWindowManager(): Boolean = isAddToWindowManagerfun setAddToWindowManager(addToWindowManager: Boolean) {isAddToWindowManager = addToWindowManager}
}

至此,应用内的悬浮球开发完成。


http://www.ppmy.cn/news/877750.html

相关文章

Qt 之悬浮球菜单

朝十晚八 Qt 之悬浮球菜单 目录 一、概述二、效果展示三、实现代码 1、菜单项2、悬浮球3、关键点四、相关文章 原文链接&#xff1a;Qt 之悬浮球菜单 一、概述 最近想做一个炫酷的悬浮式菜单&#xff0c;考虑到菜单展开和美观&#xff0c;所以考虑学习下 Qt 的动画系统和…

android 悬浮球代码,Android 悬浮球

闲来无事,搞一波悬浮球,此球: 无需权限 主要代码只有一个类,简简单单放进自己的工程 悬浮球可以用来干啥: 打开侧滑界面 打开一排小按钮 打开客服等等 功能: 显示红点(接收到信息等场景) 关闭红点(关闭消息提示) 自动贴边 显示球 隐藏球 自定义点击事件及回调 可以说你能…

悬浮球多功能_一个悬浮球,怎么可以这么贴心~

原标题&#xff1a;一个悬浮球&#xff0c;怎么可以这么贴心~ 一个悬浮球 满足你N个愿望 ※ 专题&#xff5c;图文&#xff5c;悬浮球上手指南 这个小蛋蛋是不少小朋友喜爱的零食&#xff0c;因为它能满足小朋友好几个愿望&#xff0c;能吃又能玩的零食哪个小朋友会不喜欢&…

android 悬浮球 (所有界面可用) 开发

创建 service 后台启动 public class FBService extends Service {private static final String TAG "FBService";public static final int TYPE_ADD 0;public static final int TYPE_DEL 1;private FloatBallManager mFBManager;Overridepublic void onCreate()…

自定义View:悬浮球与加速球

先来看一张动态图 昨天跟着视频学了如何自定义View并做成仿360悬浮球与加速球的样式 可以看出来&#xff0c;做成的效果有&#xff1a; 点击按钮后退出Activity&#xff0c;呈现一个圆形的悬浮球&#xff0c;可以随意拖动并会自动依靠到屏幕一侧&#xff0c;且拖动时会变成一…

移动端悬浮球示例

实现思路效果图代码 注意:此例子只适用于移动端; 实现思路 1.拖动元素 2.当拖放位置在左屏时,停靠屏幕在左边,右屏时,停靠在右边 3.当元素顶部(底部)在屏幕外时,停靠在屏幕顶部(底部); 效果图 代码 <!DOCTYPE html> <html lang"en"> <head>…

【app】1.1 悬浮球_绘制

前言 测试时需要抓取QXDM log&#xff0c;但是需要连接到电脑上&#xff0c;通过adb口下diag命令&#xff0c;打算编写apk&#xff0c;运行时显示一个悬浮球&#xff0c;可以直接通过apk的service去下diag命令。 设置悬浮球是因为录制音频软件时&#xff0c;如果退出当前录音…

android悬浮球代码,Android 仿360悬浮球与加速球

先来看一张动态图 昨天跟着视频学了如何自定义View并做成仿360悬浮球与加速球的样式 可以看出来&#xff0c;做成的效果有&#xff1a; 点击按钮后退出Activity&#xff0c;呈现一个圆形的悬浮球&#xff0c;可以随意拖动并会自动依靠到屏幕一侧&#xff0c;且拖动时会变成一张…