1. Introduction
Many apps now have the function of a small floating window. For example, when you watch a live broadcast, you can press the Home button to return to the desktop, and the small window of the live broadcast can still be displayed on the screen. The following will introduce a simple implementation of the suspension window.
Principle 2.
Window is a familiar interface class. The implementation class is PhoneWindow, which manages views. WindowManager is an interface class that inherits from ViewManager and is used to manage Windows. Its implementation class is WindowManagerImpl. If we want to add, update, or delete a Window (View), we can use WindowManager, which leaves the work to WindowManagerService. All we need to know is that WindowManager can be used to manage Windows.
WindowManager is an interface class derived from ViewManager, which defines three methods for adding, updating, and deleting views, as shown below:
public interface ViewManager {
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
Copy the code
WindowManager also inherits these methods, and these methods pass in arguments of type View, indicating that the Window exists as a View.
3. Implementation
3.1 Floating window layout
See the layout_floating_window.xml file below for a simple layout of suspension Windows. The FrameLayout layout of the dark part of the top layer is used to implement the drag and drop function of the hover window. Click ImageView in the upper right corner to close the hover window and display the content in the remaining area. Here, it simply displays the text content and does not do complicated things, so it only sets the TextView.
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/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>
Copy the code
3.2 Realization of suspension window
1. Use the serviceService
A Service is an application component that can run operations in the background for a long time without providing an interface, can be started by other application components, and will continue to run in the background even if the user switches to another application. To ensure that the hover window still works when the application is in the background, use Service.
2. To obtainWindowManager
And set theLayoutParams
private lateinit var windowManager: WindowManager
private lateinit var layoutParams: WindowManager.LayoutParams
override fun onCreate(a) {
/ / get WindowManager
windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
layoutParams = WindowManager.LayoutParams().apply {
// Implement floating Windows over other applications and Windows
type = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
} else {
WindowManager.LayoutParams.TYPE_PHONE
}
format = PixelFormat.RGBA_8888
// Set the size and position of the floating window
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}}Copy the code
3. Create a View and add it toWindowManager
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_floating_window.xml, null)
windowManager.addView(floatingView, layoutParams)
}
return super.onStartCommand(intent, flags, startId)
}
Copy the code
4. The suspension window can be dragged and closed
// Float window coordinates
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_floating_window.xml, null)
windowManager.addView(floatingView, layoutParams)
// To close the floating window, click the close button in the upper right corner
floatingView.findViewById<AppCompatImageView>(R.id.iv_close).setOnClickListener {
windowManager.removeView(floatingView)
}
// Implement the float window drag function by changing layoutParams
floatingView.findViewById<AppCompatImageView>(R.id.layout_drag).setOnTouchListener { v, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
x = event.rawX.toInt()
y = event.rawY.toInt()
}
MotionEvent.ACTION_MOVE -> {
val currentX = event.rawX.toInt()
val currentY = event.rawY.toInt()
val offsetX = currentX - x
val offsetY = currentY - y
x = currentX
y = currentY
layoutParams.x = layoutParams.x + offsetX
layoutParams.y = layoutParams.y + offsetY
/ / update the floatingView
windowManager.updateViewLayout(floatingView, layoutParams)
}
}
true
}
return super.onStartCommand(intent, flags, startId)
}
Copy the code
5. Using radio for communication
private var receiver: MyReceiver? = null
override fun onCreate(a) {
// Register a broadcast
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 = intent.getStringExtra("content") ?: ""
// Update the UI with Handler
val message = Message.obtain()
message.what = 0
message.obj = content
handler.sendMessage(message)
}
}
val handler = Handler(this.mainLooper) { msg ->
tvContent.text = msg.obj as String
false
}
Copy the code
You can broadcast information to a Service in an Activity
fun sendMessage(view: View?). {
Intent("android.intent.action.MyReceiver").apply {
putExtra("content"."Hello, World!")
sendBroadcast(this)}}Copy the code
6. Set permissions
Hover window display requires permissions, add to AndroidManefest. XML:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
Copy the code
In addition, Settings.ACTION_MANAGE_OVERLAY_PERMISSION allows permissions to be set dynamically in the 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?). {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == 0) {
if (Settings.canDrawOverlays(this)) {
Toast.makeText(this."Suspension window permission granted successfully.", Toast.LENGTH_SHORT).show()
startService(Intent(this@MainActivity, FloatingWindowService::class.java))
}
}
}
Copy the code
3.3 Complete Code
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
// Check whether floatingView is attached to Window Manager to prevent secondary removeView crashes
private var attached = false
override fun onCreate(a) {
super.onCreate()
// Register a broadcast
receiver = MyReceiver()
val filter = IntentFilter()
filter.addAction("android.intent.action.MyReceiver")
registerReceiver(receiver, filter);
// Get windowManager and set 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 ->
tvContent.text = msg.obj as String
// When the text goes out of the screen, it scrolls automatically, keeping the text at the bottom
valoffset = 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() windowManager.removeView(floatingView) attached =false
}
// Set TextView scrolltvContent.movementMethod = ScrollingMovementMethod.getInstance() floatingView!! .findViewById<FrameLayout>(R.id.layout_drag).setOnTouchListener { v, event ->when (event.action) {
MotionEvent.ACTION_DOWN -> {
x = event.rawX.toInt()
y = event.rawY.toInt()
}
MotionEvent.ACTION_MOVE -> {
val currentX = event.rawX.toInt()
val currentY = event.rawY.toInt()
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
}
return super.onStartCommand(intent, flags, startId)
}
override fun onDestroy(a) {
// Unlog the broadcast and delete the floating window
unregisterReceiver(receiver)
receiver = null
if (attached) {
windowManager.removeView(floatingView)
}
}
inner class MyReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val content = intent.getStringExtra("content") ?: ""
stringBuilder.append(content).append("\n")
val message = Message.obtain()
message.what = 0
message.obj = stringBuilder.toString()
handler.sendMessage(message)
}
}
}
Copy the code
4. To summarize
So that’s a simple implementation of the Android hover window. If you need to do something a little more complicated, like play a video, you can do it on top of that.
I am a white person who has just started blogging. If the article has the inadequacy place, ask each big guy to point out, thank!