【移动应用开发技术】怎么在Android中实现一个悬浮窗功能_第1页
【移动应用开发技术】怎么在Android中实现一个悬浮窗功能_第2页
【移动应用开发技术】怎么在Android中实现一个悬浮窗功能_第3页
【移动应用开发技术】怎么在Android中实现一个悬浮窗功能_第4页
【移动应用开发技术】怎么在Android中实现一个悬浮窗功能_第5页
已阅读5页,还剩9页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

【移动应用开发技术】怎么在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. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论