Simple Method Example of Android to Realize Floating Window
- 2021-12-19 06:54:55
- OfStack
3.2 Implementation of Floating Window
1. Use service Service2. Get WindowManager and set LayoutParams3. Create View and add it to WindowManager4. Drag and close floating window 5. Communicate with broadcast 6. Set permissions 3.3 Complete code
4. Summary
1. Preface
Nowadays, many applications have the function of small floating window. For example, when watching live broadcast, return to the desktop through Home key, and the small window of live broadcast can still be displayed on the screen. The following will introduce a simple implementation of the lower floating window.
2. Principle
We should be familiar with Window, it is an interface class, the specific implementation class for PhoneWindow, it can manage View. WindowManager is an interface class that inherits from ViewManager. It is known from its name that it is used to manage Window, and its implementation class is WindowManagerImpl. If we want to add, update and delete Window (View), we can use WindowManager, and WindowManager will leave the specific work to WindowManagerService. Here we just need to know that WindowManager can be used to manage Window.
WindowManager is an interface class that inherits from ViewManager, and ViewManager defines three methods distributed to add, update, and delete View, as follows:
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 also inherits these methods, and the parameters passed in by these methods are of type View, indicating that Window exists in the form of View.
3. Concrete realization
3.1 Floating Window Layout
For a simple layout of the floating window, please refer to the file layout_floating_window. xml below. The FrameLayout layout of the dark part of the top layer is used to realize the drag and drop function of the floating window. Click ImageView in the upper right corner to close the floating window, and the rest area displays the content. Here, the text content is simply displayed, and no complicated things are done, so only TextView is set.
<?xml version="1.0" encoding="utf-8"?>
<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>
3.2 Implementation of Floating Window
1. Using the service Service
Service is an application component that can run for a long time in the background without providing an interface. It can be started by other application components and will continue to run in the background even if the user switches to other applications. To ensure that the application in the background, the floating window can still be displayed normally, so Service can be used here.
2. Get WindowManager and set LayoutParams
private lateinit var windowManager: WindowManager
private lateinit var layoutParams: WindowManager.LayoutParams
override fun onCreate() {
// Get WindowManager
windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
layoutParams = WindowManager.LayoutParams().apply {
// Realize the display of floating windows above 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
}
}
3. Create View and add it to 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_floating_window.xml, null)
windowManager.addView(floatingView, layoutParams)
}
return super.onStartCommand(intent, flags, startId)
}
4. Drag and close the floating window
// Coordinates of floating window
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)
// Click the Close button in the upper right corner of the floating window to close the floating window
floatingView.findViewById<AppCompatImageView>(R.id.iv_close).setOnClickListener {
windowManager.removeView(floatingView)
}
// Realize the dragging function of floating window , By changing layoutParams To realize
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 floatingView
windowManager.updateViewLayout(floatingView, layoutParams)
}
}
true
}
return super.onStartCommand(intent, flags, startId)
}
5. Communication by Broadcast
private var receiver: MyReceiver? = null
override fun onCreate() {
// Registered 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") ?: ""
// Pass Handler Update UI
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
}
Information can be broadcast to Service in Activity
fun sendMessage(view: View?) {
Intent("android.intent.action.MyReceiver").apply {
putExtra("content", "Hello, World!")
sendBroadcast(this)
}
}
6. Set permissions
The display of the floating window requires permission, add in AndroidManefest. xml:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
In addition, the permissions are set dynamically through Settings.ACTION_MANAGE_OVERLAY_PERMISSION, which is set in 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, " Suspended Window Permission Authorization Successfully ", Toast.LENGTH_SHORT).show()
startService(Intent(this@MainActivity, FloatingWindowService::class.java))
}
}
}
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
// Used to judge floatingView Whether or not attached To window manager , prevent 2 Times removeView Lead to collapse
private var attached = false
override fun onCreate() {
super.onCreate()
// Registered broadcast
receiver = MyReceiver()
val filter = IntentFilter()
filter.addAction("android.intent.action.MyReceiver")
registerReceiver(receiver, filter);
// Get windowManager And set the 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
// Automatically scroll when the text goes beyond the screen, ensuring that the text is at the bottom
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()
windowManager.removeView(floatingView)
attached = false
}
// Settings TextView Scroll
tvContent.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() {
// Unregister 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)
}
}
}
4. Summary
The above is a simple implementation of Android floating window. If you need to realize other 1-point complicated functions, such as playing video, you can also complete it on this basis.