版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
【移动应用开发技术】怎么在Android中实现一个悬浮窗功能
怎么在Android中实现一个悬浮窗功能?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。判断是否有悬浮窗权限点击左上角图标时,我们要先判断当前app是否有悬浮窗权限,首先我们在配置文件中添加,悬浮窗的权限。<uses-permission
android:name="android.permission.SYSTEM_ALERT_WINDOW"
/>(很多文章标题都是悬浮窗如何绕过权限,什么设置类型为TOAST或者PHONE,我想说不可能的事,TOAST类型的虽然部分机型可以显示但是就是一个普通的TOSAT会自动消失)那么我们如何判断是否有悬浮窗权限呢,这一块不同厂商处理方案可能不一样,这里我们用一种通用的处理方案,测试表明除了(vivo部分)无效,其他多数机型都ok。并且vivo部分机型微信通话也不会弹出提示(这我就放心了~)fun
zoom(v:
View)
{
if
(Build.VERSION.SDK_INT
>=
Build.VERSION_CODES.M)
{
if
(!Settings.canDrawOverlays(this))
{
Toast.makeText(this,
"当前无权限,请授权",
Toast.LENGTH_SHORT)
GlobalDialogSingle(this,
"",
"当前未获取悬浮窗权限",
"去开启",
DialogInterface.OnClickListener
{
dialog,
which
->
dialog.dismiss()
startActivityForResult(Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:"
+
packageName)),
0)
}).show()
}
else
{
moveTaskToBack(true)
val
intent
=
Intent(this@Main2Activity,
FloatWinfowServices::class.java)
hasBind
=
bindService(intent,
mVideoServiceConnection,
Context.BIND_AUTO_CREATE)
}
}
}我们通过Settings.canDrawOverlays(this)来判断当前应用是否有悬浮窗权限,如果没有,我们弹窗提示,通过startActivityForResult(Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:"
+
packageName)),
0)
跳转到开启悬浮窗权限页面。如果悬浮窗权限已开启,直接将当前任务栈置于后台,开启服务即可。其实回调方法,并没有直接告诉我们是否授权成功,所以我们需要在回调中再次判断override
fun
onActivityResult(requestCode:
Int,
resultCode:
Int,
data:
Intent)
{
if
(requestCode
==
0)
{
if
(Build.VERSION.SDK_INT
>=
Build.VERSION_CODES.M)
{
if
(!Settings.canDrawOverlays(this))
{
Toast.makeText(this,
"授权失败",
Toast.LENGTH_SHORT).show()
}
else
{
Handler().postDelayed({
val
intent
=
Intent(this@Main2Activity,
FloatWinfowServices::class.java)
intent.putExtra("rangeTime",
rangeTime)
hasBind
=
bindService(intent,
mVideoServiceConnection,
Context.BIND_AUTO_CREATE)
moveTaskToBack(true)
},
1000)
}
}
}
}这里我们可以看到回调中延迟了1秒,因为测试发现某些机型反应“过快”,收到回调的时候还以为没有授权成功,其实已经成功了。绑定Service我们需要一个ServiceConnection对象internal
var
mVideoServiceConnection:
ServiceConnection
=
object
:
ServiceConnection
{
override
fun
onServiceConnected(name:
ComponentName,
service:
IBinder)
{
//
获取服务的操作对象
val
binder
=
service
as
FloatWinfowServices.MyBinder
binder.service
}
override
fun
onServiceDisconnected(name:
ComponentName)
{}
}Main2Activity的完整代码如下所示:/**
*
@author
Huanglinqing
*/
class
Main2Activity
:
AppCompatActivity()
{
private
val
chronometer:
Chronometer?
=
null
private
var
hasBind
=
false
private
val
rangeTime:
Long
=
0
override
fun
onCreate(savedInstanceState:
Bundle?)
{
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
}
fun
zoom(v:
View)
{
if
(Build.VERSION.SDK_INT
>=
Build.VERSION_CODES.M)
{
if
(!Settings.canDrawOverlays(this))
{
Toast.makeText(this,
"当前无权限,请授权",
Toast.LENGTH_SHORT)
GlobalDialogSingle(this,
"",
"当前未获取悬浮窗权限",
"去开启",
DialogInterface.OnClickListener
{
dialog,
which
->
dialog.dismiss()
startActivityForResult(Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:"
+
packageName)),
0)
}).show()
}
else
{
moveTaskToBack(true)
val
intent
=
Intent(this@Main2Activity,
FloatWinfowServices::class.java)
hasBind
=
bindService(intent,
mVideoServiceConnection,
Context.BIND_AUTO_CREATE)
}
}
}
internal
var
mVideoServiceConnection:
ServiceConnection
=
object
:
ServiceConnection
{
override
fun
onServiceConnected(name:
ComponentName,
service:
IBinder)
{
//
获取服务的操作对象
val
binder
=
service
as
FloatWinfowServices.MyBinder
binder.service
}
override
fun
onServiceDisconnected(name:
ComponentName)
{}
}
override
fun
onActivityResult(requestCode:
Int,
resultCode:
Int,
data:
Intent)
{
if
(requestCode
==
0)
{
if
(Build.VERSION.SDK_INT
>=
Build.VERSION_CODES.M)
{
if
(!Settings.canDrawOverlays(this))
{
Toast.makeText(this,
"授权失败",
Toast.LENGTH_SHORT).show()
}
else
{
Handler().postDelayed({
val
intent
=
Intent(this@Main2Activity,
FloatWinfowServices::class.java)
intent.putExtra("rangeTime",
rangeTime)
hasBind
=
bindService(intent,
mVideoServiceConnection,
Context.BIND_AUTO_CREATE)
moveTaskToBack(true)
},
1000)
}
}
}
}
override
fun
onRestart()
{
super.onRestart()
Log.d("RemoteView",
"重新显示了")
//不显示悬浮框
if
(hasBind)
{
unbindService(mVideoServiceConnection)
hasBind
=
false
}
}
override
fun
onNewIntent(intent:
Intent)
{
super.onNewIntent(intent)
}
override
fun
onDestroy()
{
super.onDestroy()
}
}新建悬浮窗Service新建悬浮窗ServiceFloatWinfowServices,因为我们使用的BindService,我们在onBind方法中初始化service中的布局override
fun
onBind(intent:
Intent):
IBinder?
{
initWindow()
//悬浮框点击事件的处理
initFloating()
return
MyBinder()
}service中我们通过WindowManager来添加一个布局显示。/**
*
初始化窗口
*/
private
fun
initWindow()
{
winManager
=
application.getSystemService(Context.WINDOW_SERVICE)
as
WindowManager
//设置好悬浮窗的参数
wmParams
=
params
//
悬浮窗默认显示以左上角为起始坐标
wmParams!!.gravity
=
Gravity.LEFT
or
Gravity.TOP
//悬浮窗的开始位置,因为设置的是从左上角开始,所以屏幕左上角是x=0;y=0
wmParams!!.x
=
winManager!!.defaultDisplay.width
wmParams!!.y
=
210
//得到容器,通过这个inflater来获得悬浮窗控件
inflater
=
LayoutInflater.from(applicationContext)
//
获取浮动窗口视图所在布局
mFloatingLayout
=
inflater!!.inflate(R.layout.remoteview,
null)
//
添加悬浮窗的视图
winManager!!.addView(mFloatingLayout,
wmParams)
}悬浮窗的参数主要设置悬浮窗的类型为WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY8.0以下可设置为:wmParams!!.type
=
WindowManager.LayoutParams.TYPE_PHONE代码如下所示:private
//设置window
type
下面变量2002是在屏幕区域显示,2003则可以显示在状态栏之上
//设置可以显示在状态栏上
//设置悬浮窗口长宽数据
val
params:
WindowManager.LayoutParams
get()
{
wmParams
=
WindowManager.LayoutParams()
if
(Build.VERSION.SDK_INT
>=
Build.VERSION_CODES.O)
{
wmParams!!.type
=
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
}
else
{
wmParams!!.type
=
WindowManager.LayoutParams.TYPE_PHONE
}
wmParams!!.flags
=
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
or
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
or
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
or
WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
or
WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
wmParams!!.width
=
WindowManager.LayoutParams.WRAP_CONTENT
wmParams!!.height
=
WindowManager.LayoutParams.WRAP_CONTENT
return
wmParams
}当点击悬浮窗的时候回到Activity2页面,并且悬浮窗消失,所以我们只需要给悬浮窗添加点击事件linearLayout!!.setOnClickListener
{
startActivity(Intent(this@FloatWinfowServices,
Main2Activity::class.java))
}当Service走到onDestory的时候将view移除,对于Activity2页面来说当onResume的时候解绑Service,当onstop的时候绑定Service。从效果图中我们可以看到悬浮窗可以拖拽的,所以还要设置触摸事件,当移动距离超过某个值的时候让onTouch消费事件,这样就不会触发点击事件了。这个算是view比较基础的知识,相信大家都明白了。//开始触控的坐标,移动时的坐标(相对于屏幕左上角的坐标)
private
var
mTouchStartX:
Int
=
0
private
var
mTouchStartY:
Int
=
0
private
var
mTouchCurrentX:
Int
=
0
private
var
mTouchCurrentY:
Int
=
0
//开始时的坐标和结束时的坐标(相对于自身控件的坐标)
private
var
mStartX:
Int
=
0
private
var
mStartY:
Int
=
0
private
var
mStopX:
Int
=
0
private
var
mStopY:
Int
=
0
//判断悬浮窗口是否移动,这里做个标记,防止移动后松手触发了点击事件
private
var
isMove:
Boolean
=
false
private
inner
class
FloatingListener
:
View.OnTouchListener
{
override
fun
onTouch(v:
View,
event:
MotionEvent):
Boolean
{
val
action
=
event.action
when
(action)
{
MotionEvent.ACTION_DOWN
->
{
isMove
=
false
mTouchStartX
=
event.rawX.toInt()
mTouchStartY
=
event.rawY.toInt()
mStartX
=
event.x.toInt()
mStartY
=
event.y.toInt()
}
MotionEvent.ACTION_MOVE
->
{
mTouchCurrentX
=
event.rawX.toInt()
mTouchCurrentY
=
event.rawY.toInt()
wmParams!!.x
+=
mTouchCurrentX
-
mTouchStartX
wmParams!!.y
+=
mTouchCurrentY
-
mTouchStartY
winManager!!.updateViewLayout(mFloatingLayout,
wmParams)
mTouchStartX
=
mTouchCurrentX
mTouchStartY
=
mTouchCurrentY
}
MotionEvent.ACTION_UP
->
{
mStopX
=
event.x.toInt()
mStopY
=
event.y.toInt()
if
(Math.abs(mStartX
-
mStopX)
>=
1
||
Math.abs(mStartY
-
mStopY)
>=
1)
{
isMove
=
true
}
}
else
->
{
}
}
//如果是移动事件不触发OnClick事件,防止移动的时候一放手形成点击事件
return
isMove
}
}FloatWinfowServices所有代码如下所示:class
FloatWinfowServices
:
Service()
{
private
var
winManager:
WindowManager?
=
null
private
var
wmParams:
WindowManager.LayoutParams?
=
null
private
var
inflater:
LayoutInflater?
=
null
//浮动布局
private
var
mFloatingLayout:
View?
=
null
private
var
linearLayout:
LinearLayout?
=
null
private
var
chronometer:
Chronometer?
=
null
override
fun
onBind(intent:
Intent):
IBinder?
{
initWindow()
//悬浮框点击事件的处理
initFloating()
return
MyBinder()
}
inner
class
MyBinder
:
Binder()
{
val
service:
FloatWinfowServices
get()
=
this@FloatWinfowServices
}
override
fun
onCreate()
{
super.onCreate()
}
/**
*
悬浮窗点击事件
*/
private
fun
initFloating()
{
linearLayout
=
mFloatingLayout!!.findViewById<LinearLayout>(R.id.line1)
linearLayout!!.setOnClickListener
{
startActivity(Intent(this@FloatWinfowServices,
Main2Activity::class.java))
}
//悬浮框触摸事件,设置悬浮框可拖动
linearLayout!!.setOnTouchListener(FloatingListener())
}
//开始触控的坐标,移动时的坐标(相对于屏幕左上角的坐标)
private
var
mTouchStartX:
Int
=
0
private
var
mTouchStartY:
Int
=
0
private
var
mTouchCurrentX:
Int
=
0
private
var
mTouchCurrentY:
Int
=
0
//开始时的坐标和结束时的坐标(相对于自身控件的坐标)
private
var
mStartX:
Int
=
0
private
var
mStartY:
Int
=
0
private
var
mStopX:
Int
=
0
private
var
mStopY:
Int
=
0
//判断悬浮窗口是否移动,这里做个标记,防止移动后松手触发了点击事件
private
var
isMove:
Boolean
=
false
private
inner
class
FloatingListener
:
View.OnTouchListener
{
override
fun
onTouch(v:
View,
event:
MotionEvent):
Boolean
{
val
action
=
event.action
when
(action)
{
MotionEvent.ACTION_DOWN
->
{
isMove
=
false
mTouchStartX
=
event.rawX.toInt()
mTouchStartY
=
event.rawY.toInt()
mStartX
=
event.x.toInt()
mStartY
=
event.y.toInt()
}
MotionEvent.ACTION_MOVE
->
{
mTouchCurrentX
=
event.rawX.toInt()
mTouchCurrentY
=
event.rawY.toInt()
wmParams!!.x
+=
mTouchCurrentX
-
mTouchStartX
wmParams!!.y
+=
mTouchCurrentY
-
mTouchStartY
winManager!!.updateViewLayout(mFloatingLayout,
wmParams)
mTouchStartX
=
mTouchCurrentX
mTouchStartY
=
mTouchCurrentY
}
MotionEvent.ACTION_UP
->
{
mStopX
=
event.x.toInt()
mStopY
=
event.y.toInt()
if
(Math.abs(mStartX
-
mStopX)
>=
1
||
Math.abs(mStartY
-
mStopY)
>=
1)
{
isMove
=
true
}
}
else
->
{
}
}
//如果是移动事件不触发OnClick事件,防止移动的时候一放手形成点击事件
return
isMove
}
}
/**
*
初始化窗口
*/
private
fun
initWindow()
{
winManager
=
application.getSystemService(Context.WINDOW_SERVICE)
as
WindowManager
//设置好悬浮窗的参数
wmParams
=
params
//
悬浮窗默认显示以左上角为起始坐标
wmParams!!.gravity
=
Gravity.LEFT
or
Gravity.TOP
//悬浮窗的开始位置,因为设置的是从左上角开始,所以屏幕左上角是x=0;y=0
wmParams!!.x
=
winManager!!.defaultDisplay.width
wmParams!!.y
=
210
//得到容器,通过这个inflater来获得悬浮窗控件
inflater
=
LayoutInflater.from(applicationContext)
//
获取浮动窗口视图所在布局
mFloatingLayout
=
inflater!!.inflate(R.layout.remoteview,
null)
chronometer
=
mFloatingLayout!!.findViewById<Chronometer>(R.id.chronometer)
chronometer!!.start()
//
添加悬浮窗的视图
winManager!!.addView(mFloatingLayout,
wmParams)
}
private
//设置window
type
下面变量2002是在屏幕区域显示,2003则可以显示在状态栏之上
//设置可以显示在状态栏上
//设置悬浮窗口长宽数据
val
params:
WindowManager.LayoutParams
get()
{
wmParams
=
WindowManager.LayoutParams()
if
(Build.VERSION.SDK_INT
>=
Build.VERSION_CODES.O)
{
wmParams!!.type
=
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
}
else
{
wmParams!!.type
=
WindowManager.LayoutParams.TYPE_PHONE
}
wmParams!!.flags
=
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
or
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
or
WindowManager.
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 商业模式与价值提升(上)
- 社区卫生服务机构的服务内容
- 信息技术让留守儿童更快乐
- 乐山市2022年高中阶段教育学校招生统一考试语文试卷
- 2024年安徽淮北市事业单位招考聘用122人笔试参考题库附带答案详解
- 2024年北京石油化工学院招考聘用52人笔试参考题库附带答案详解
- 2024年3月福建福州高新区党群工作部招考聘用60人笔试参考题库附带答案详解
- 2024年3月浙江省农业农村厅所属部分事业单位招考聘用13人笔试参考题库附带答案详解
- 2024年3月广西河池市都安县自主招考聘用教师97人笔试参考题库附带答案详解
- 2024年3月山东第一医科大学附属内分泌与代谢病医院招考聘用7人笔试参考题库附带答案详解
- 某集团项目开发阶段性成果与标准模板
- 从自然数到有理数(1)课件浙教版数学七年级上册
- 闸门启闭力计算
- 防雷装置日常维护检查制度
- 全麻气管插管术中突发寂静肺
- 锅炉工岗位安全风险告知卡
- 二手车市场服务站申请书
- 中国公民健康素养测试题库
- 初中英语阅读理解解题技巧攻略课件
- 电子版的离婚协议书电子版离婚协议书怎么弄
- 学校“三重一大”制度
评论
0/150
提交评论