大家好,欢迎来到IT知识分享网。
目录
三.WindowManager.LayoutParams的TYPE类型:
一.使用悬浮窗功能的原因:
在Android开发中我们想要做到提醒用户关键信息的作用的时候.例如App更新信息之类的,有很多种方式去实现,主要的话还是三种方式Dialog,AlertDialog,PopupWindow,但是他们都有一个共同的缺点那就是依赖于Activity,而悬浮窗是不依赖Activity的,甚至,App在后台运行,悬浮窗依旧会弹出来,只要App进程不被杀死,但是悬浮窗也有缺点,那就是权限问题
二.悬浮窗的具体实现步骤:
一.添加权限:
<!-- 悬浮窗需要添加该权限--> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
二.动态申请权限:
private fun initFloatWindow() { // 权限判断 if (!Settings.canDrawOverlays(applicationContext)) { // 启动Activity让用户授权 val mIntent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:${packageName}")) startActivityForResult(mIntent, 10) } else { // 已经有权限了,就去初始化对应的视图或者悬浮窗弹窗的初始化 initView() } } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (resultCode == 10) { if (Settings.canDrawOverlays(applicationContext)) { initView() } else { ToastUtils.showToast(this, "请设置对应权限") } } }
三.WindowManager.LayoutParams的TYPE类型:
为什么要将这个TYPE类型放在第三位呢,因为这个就是导致各种问题出现的罪魁祸首
type = when { Build.VERSION.SDK_INT >= Build.VERSION_CODES.O -> WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1 -> WindowManager.LayoutParams.TYPE_PHONE else -> WindowManager.LayoutParams.TYPE_TOAST }
上述这段代码是用来根据不同的Android版本设置窗口类型的,使用的Kotlin的when表达式,来判断设备的Android版本(Build.VERSION.SDK_INT),并根据版本返回不同的窗口类型(TYPE)
1. 如果 Android 版本大于或等于 Oreo(API 级别 26),则窗口类型为TYPE_APPLICATION_OVERLAY ,这通常用于应用程序的悬浮窗。
2. 如果 Android 版本大于或等于 Nougat MR1(API 级别 25),则窗口类型为 TYPE_PHONE ,这通常用于电话应用的窗口。
3. 如果以上两个条件都不满足,则使用 TYPE_TOAST ,这通常用于显示短暂的 Toast 消息。
我举一个例子,如下:
这是因为我的手机为Android7我用的是被淘汰的TYPE类型,这个情况下我们需要更换TYPE类型就能解决对应的问题
四.编写代码:
1.XML代码:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <TextView android:background="#f46d43" android:paddingVertical="10dp" android:gravity="center" android:textSize="28sp" android:textColor="#ffffff" android:text="WindowManager与悬浮框" android:layout_width="match_parent" android:layout_height="wrap_content"/> <Button android:textAllCaps="false" android:id="@+id/activityshow_btn" android:text="Activity显示悬浮框" android:layout_width="match_parent" android:layout_height="wrap_content"/> <Button android:textAllCaps="false" android:id="@+id/serviceshow_btn" android:text="Service显示悬浮框" android:layout_width="match_parent" android:layout_height="wrap_content"/> <Button android:textAllCaps="false" android:id="@+id/activityshow_btn_cancle" android:text="Activity隐藏悬浮框" android:layout_width="match_parent" android:layout_height="wrap_content"/> </LinearLayout>
2.Activity相关代码:
/ * 悬浮窗: 不依赖Activity, 当Activity销毁的时候悬浮窗仍然可以显示,注意悬浮窗需要手动获取权限以及声明权限 */ class FloatingWindowActivity : AppCompatActivity(), View.OnClickListener { private lateinit var mActivityFloatingWindow: ActivityFloatingWindow private val mServiceshowBtn by lazy { findViewById<Button>(R.id.serviceshow_btn) } private val mActivityshowBtn by lazy { findViewById<Button>(R.id.activityshow_btn) } private val m_activityshow_btn_cancle by lazy { findViewById<Button>(R.id.activityshow_btn_cancle) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_floating_window) initFloatWindow() initListener() } private fun initFloatWindow() { // 权限判断 if (!Settings.canDrawOverlays(applicationContext)) { // 启动Activity让用户授权 val mIntent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:${packageName}")) startActivityForResult(mIntent, 10) } else { // 已经有权限了 initView() } } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (resultCode == 10) { if (Settings.canDrawOverlays(applicationContext)) { initView() } else { ToastUtils.showToast(this, "请设置对应权限") } } } private fun initView() { mActivityFloatingWindow = ActivityFloatingWindow(this) } private fun initListener() { mServiceshowBtn.setOnClickListener(this) mActivityshowBtn.setOnClickListener(this) m_activityshow_btn_cancle.setOnClickListener(this) } override fun onClick(v: View?) { when(v?.id) { R.id.serviceshow_btn -> { } R.id.activityshow_btn -> { activityShowFloatingWindow() } R.id.activityshow_btn_cancle -> { mActivityFloatingWindow.remove() } } } private fun activityShowFloatingWindow() { mActivityFloatingWindow.showFloatWindow() } }
3.WindowManager相关代码
class ActivityFloatingWindow(context: Context) : View.OnTouchListener { private var mContext: Context private lateinit var mWindowParams: WindowManager.LayoutParams private lateinit var mWindowManager: WindowManager private lateinit var rootLayout: View init { mContext = context initFloatWindow() } private var mInViewX = 0f private var mInViewY = 0f private var mDownInScreenX = 0f private var mDownInScreenY = 0f private var mInScreenX = 0f private var mInScreenY = 0f var isMoving = false / * 初始化布局 */ private fun initFloatWindow() { rootLayout = LayoutInflater.from(mContext) .inflate(R.layout.floatingwidow_in_activity, null) rootLayout.setOnTouchListener(this) mWindowParams = WindowManager.LayoutParams() mWindowManager = MyApp.mApplicationContext?.getSystemService(Context.WINDOW_SERVICE) as WindowManager mWindowParams.type = WindowManager.LayoutParams.TYPE_PHONE mWindowParams.format = PixelFormat.RGBA_8888 // 设置悬浮窗不获取焦点的原因就是为了传递事件 mWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE mWindowParams.gravity = Gravity.START or Gravity.TOP mWindowParams.height = WindowManager.LayoutParams.WRAP_CONTENT mWindowParams.width = WindowManager.LayoutParams.WRAP_CONTENT defaultPosition() } fun showFloatWindow() { if (null == rootLayout.parent) { mWindowManager.addView(rootLayout, mWindowParams) } } fun remove() { if (null != rootLayout.parent) { mWindowManager.removeView(rootLayout) } } override fun onTouch(v: View?, event: MotionEvent?): Boolean { return floatLayoutTouch(event!!) } private fun floatLayoutTouch(motionEvent: MotionEvent): Boolean { when (motionEvent.action) { MotionEvent.ACTION_DOWN -> { // 获取相对View的坐标,即以此View左上角为原点 mInViewX = motionEvent.x mInViewY = motionEvent.y // 获取相对屏幕的坐标,即以屏幕左上角为原点 mDownInScreenX = motionEvent.rawX mDownInScreenY = motionEvent.rawY mInScreenX = motionEvent.rawX mInScreenY = motionEvent.rawY isMoving = true } MotionEvent.ACTION_MOVE -> { // 更新浮动窗口位置参数 mInScreenX = motionEvent.rawX mInScreenY = motionEvent.rawY mWindowParams.x = (mInScreenX - mInViewX).toInt() mWindowParams.y = (mInScreenY - mInViewY).toInt() // 手指移动的时候更新小悬浮窗的位置 mWindowManager.updateViewLayout(rootLayout, mWindowParams) isMoving = true } MotionEvent.ACTION_UP -> { isMoving = false // 如果手指离开屏幕时,xDownInScreen和xInScreen相等,且yDownInScreen和yInScreen相等,则视为触发了单击事件。 if (mDownInScreenX == mInScreenX && mDownInScreenY == mInScreenY) { Toast.makeText(mContext, "Click", Toast.LENGTH_SHORT).show() } } } return true } private fun defaultPosition() { val metrics = DisplayMetrics() // 默认固定位置,靠屏幕左边缘的中间 mWindowManager.defaultDisplay.getMetrics(metrics) mWindowParams.x = 0 mWindowParams.y = metrics.heightPixels/2 } }
!!!这里的WIndowManager相关的代码我推荐大家使用单例,我这里没有使用单例,运行效果如下:
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/141669.html