Android实现悬浮窗的简单⽅法实例
⽬录
1. 前⾔
2.原理
3.具体实现
3.1浮窗布局
3.2 悬浮窗的实现土豆丝饼的做法
1. 使⽤服务Service
2. 获取WindowManager并设置LayoutParams
3. 创建View并添加到WindowManager
4. 实现悬浮窗的拖拽和关闭功能
5. 利⽤⼴播进⾏通信
6. 设置权限
3.3 完整代码
4. 总结
1. 前⾔
现在很多应⽤都有⼩悬浮窗的功能,⽐如看直播的时候,通过Home键返回桌⾯,直播的⼩窗⼝仍可以在屏幕上显⽰。下⾯将介绍下悬浮窗的的⼀种简单实现⽅式。
2.原理
Window我们应该很熟悉,它是⼀个接⼝类,具体的实现类为PhoneWindow,它可以对View进⾏管理。WindowManager是⼀个接⼝类,继承⾃ViewManager,从名称就知道它是⽤来管理Window的,它的实现类是WindowManagerImpl。如果我们想要对Window(View)进⾏添加、更新和删除操作就可以使⽤WindowManager,WindowManager会将具体的⼯作交由WindowManagerService处理。这⾥我们只需要知道WindowManager能⽤来管理Window就好。
WindowManager是⼀个接⼝类,继承⾃ViewManager,ViewManager中定义了3个⽅法,分布⽤来添加、更新和删除View,如下所⽰:
public interface ViewManager {
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
WindowManager也继承了这些⽅法,⽽这些⽅法传⼊的参数都是View类型,说明了Window是以View的形式存在的。3.具体实现
3.1浮窗布局
露奶门悬浮窗的简易布局如下的可参考下⾯的layout_l⽂件。顶层深⾊部分的FrameLayout布局是⽤来实现悬浮窗的拖拽功能的,点击右上⾓ImageView可以实现关闭悬浮窗,剩下区域显⽰内容,这⾥只是简单地显⽰⽂本内容,不做复杂的东西,故只设置TextView。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="schemas.android/apk/res/android"邱泽经纪人回应
xmlns:app="schemas.android/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:id="@+id/layout_drag"
android:layout_width="match_parent"
android:layout_height="15dp"
android:background="#dddddd">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_close"
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_gravity="end"
android:src="@drawable/img_delete"/>
</FrameLayout>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tv_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
android:background="#eeeeee"
android:scrollbars="vertical"/>
</LinearLayout>
3.2 悬浮窗的实现
1. 使⽤服务Service
Service 是⼀种可在后台执⾏长时间运⾏操作⽽不提供界⾯的应⽤组件,可由其他应⽤组件启动,⽽且即使⽤户切换到其他应⽤,仍将在后台继续运⾏。要保证应⽤在后台时,悬浮窗仍然可以正常显⽰,所以这⾥可以使⽤Service。
2. 获取WindowManager并设置LayoutParams
private lateinit var windowManager: WindowManager
private lateinit var layoutParams: WindowManager.LayoutParams
override fun onCreate() {
// 获取WindowManager
windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
layoutParams = WindowManager.LayoutParams().apply {
// 实现在其他应⽤和窗⼝上⽅显⽰浮窗
type = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
} else {
WindowManager.LayoutParams.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 = 600
height = 600
x = 300
y = 300
}
}
3. 创建View并添加到WindowManager
private lateinit var floatingView: View
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (Settings.canDrawOverlays(this)) {
floatingView = LayoutInflater.from(this).inflate(R.layout.layout_l, null)
windowManager.addView(floatingView, layoutParams)
}
范冰冰的绯闻StartCommand(intent, flags, startId)
}
4. 实现悬浮窗的拖拽和关闭功能
// 浮窗的坐标
private var x = 0
private var y = 0
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (Settings.canDrawOverlays(this)) {
floatingView = LayoutInflater.from(this).inflate(R.layout.layout_l, null)
windowManager.addView(floatingView, layoutParams)
// 点击浮窗的右上⾓关闭按钮可以关闭浮窗
floatingView.findViewById<AppCompatImageView>(R.id.iv_close).setOnClickListener {
}
// 实现浮窗的拖动功能, 通过改变layoutParams来实现
floatingView.findViewById<AppCompatImageView>(R.id.layout_drag).setOnTouchListener { v, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
x = Int()
y = Int()
}
MotionEvent.ACTION_MOVE -> {
val currentX = Int()
val currentY = Int()
val offsetX = currentX - x
val offsetY = currentY - y
x = currentX
y = currentY
layoutParams.x = layoutParams.x + offsetX
layoutParams.y = layoutParams.y + offsetY
// 更新floatingView
windowManager.updateViewLayout(floatingView, layoutParams)
}
}
true
}
StartCommand(intent, flags, startId)
}
5. 利⽤⼴播进⾏通信
private var receiver: MyReceiver? = null
override fun onCreate() {
// 注册⼴播
receiver = MyReceiver()
val filter = IntentFilter()
filter.addAction("android.intent.action.MyReceiver")
registerReceiver(receiver, filter)
}
inner class MyReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val content = StringExtra("content") ?: ""
// 通过Handler更新UI
val message = Message.obtain()
message.what = 0
message.obj = content
handler.sendMessage(message)
}
}
val handler = Handler(this.mainLooper) { msg ->
< = msg.obj as String
false
}
郭美美怎么那么有钱
可以在Activity中通过⼴播给Service发送信息
fun sendMessage(view: View?) {
Intent("android.intent.action.MyReceiver").apply {
putExtra("content", "Hello, World!")
sendBroadcast(this)
}
}
6. 设置权限
悬浮窗的显⽰需要权限,在l中添加:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
此外,还要通过Settings.ACTION_MANAGE_OVERLAY_PERMISSION来让动态设置权限,在Activity中设置。// MainActivity.kt
fun startWindow(view: View?) {
if (!Settings.canDrawOverlays(this)) {
startActivityForResult(Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:$packageName")), 0)
} else {
startService(Intent(this@MainActivity, FloatingWindowService::class.java))
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == 0) {
if (Settings.canDrawOverlays(this)) {
Toast.makeText(this, "悬浮窗权限授权成功", Toast.LENGTH_SHORT).show()
startService(Intent(this@MainActivity, FloatingWindowService::class.java))
}
}
}
3.3 完整代码
class FloatingWindowService : Service() {
private lateinit var windowManager: WindowManager
private lateinit var layoutParams: WindowManager.LayoutParams
private lateinit var tvContent: AppCompatTextView
private lateinit var handler: Handler
private var receiver: MyReceiver? = null
private var floatingView: View? = null
private val stringBuilder = StringBuilder()
private var x = 0
private var y = 0
// ⽤来判断floatingView是否attached 到 window manager,防⽌⼆次removeView导致崩溃
private var attached = false
override fun onCreate() {
/
/ 注册⼴播
receiver = MyReceiver()
val filter = IntentFilter()
filter.addAction("android.intent.action.MyReceiver")
registerReceiver(receiver, filter);
// 获取windowManager并设置layoutParams
windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
layoutParams = WindowManager.LayoutParams().apply {
type = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
} else {
WindowManager.LayoutParams.TYPE_PHONE
}
format = PixelFormat.RGBA_8888
//            format = PixelFormat.TRANSPARENT
gravity = Gravity.START or Gravity.TOP
flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE            width = 600
height = 600
x = 300
y = 300
}
handler = Handler(this.mainLooper) { msg ->
< = msg.obj as String
// 当⽂本超出屏幕⾃动滚动,保证⽂本处于最底部
val offset = tvContent.lineCount * tvContent.lineHeight
floatingView?.apply {
机械迷城第四关攻略if (offset > height) {
tvContent.scrollTo(0, offset - height)
}
}
false
}
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
@SuppressLint("ClickableViewAccessibility")
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (Settings.canDrawOverlays(this)) {
floatingView = LayoutInflater.from(this).inflate(R.layout.layout_show_log, null)
tvContent = floatingView!!.findViewById(R.id.tv_log)
floatingView!!.findViewById<AppCompatImageView>(R.id.iv_close).setOnClickListener {
stringBuilder.clear()
attached = false
}
// 设置TextView滚动
floatingView!!.findViewById<FrameLayout>(R.id.layout_drag).setOnTouchListener { v, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
x = Int()
y = Int()
}
MotionEvent.ACTION_MOVE -> {
val currentX = Int()
val currentY = Int()
val offsetX = currentX - x
val offsetY = currentY - y
x = currentX
y = currentY
layoutParams.x = layoutParams.x + offsetX
layoutParams.y = layoutParams.y + offsetY
windowManager.updateViewLayout(floatingView, layoutParams)
}
}
true
}
windowManager.addView(floatingView, layoutParams)
attached = true
}
StartCommand(intent, flags, startId)
}
override fun onDestroy() {
// 注销⼴播并删除浮窗
unregisterReceiver(receiver)
receiver = null
if (attached) {
}
}
inner class MyReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val content = StringExtra("content") ?: ""
stringBuilder.append(content).append("\n")
val message = Message.obtain()
message.what = 0
message.obj = String()
handler.sendMessage(message)
}
}
}
4. 总结
以上就是Android悬浮窗的⼀个简单实现⽅式。如果需要实现其他复杂⼀点的功能,⽐如播放视频,也可以在此基础上完成。
到此这篇关于Android实现悬浮窗的简单⽅法的⽂章就介绍到这了,更多相关Android实现悬浮窗内容请搜索以前的⽂章或继续浏览下⾯的相关⽂章希望⼤家以后多多⽀持!