Updated 28 November 2019
Floating chat head is nothing but the view that is drawn over other applications. Android system allows applications to draw over other application if the application has android.permission.SYSTEM_ALERT_WINDOW permission. We are going to use the background service to add the floating widget into the view hierarchy of the current screen. So, this floating view is always on top of the application windows.
To drag the view across the screen we are going to override OnTouchListener() to listen to drag events and change the position of the view has in the screen.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.floatingchatheat"> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> <service android:name=".MyFloatingWindowService"/> </application> </manifest> |
In this manifest you can we have a user-permission android:name=”android.permission.SYSTEM_ALERT_WINDOW” that is responsible for adding the overlay layout.
As you can see we have also declared.MyFloatingWindowService in the service tag. That is the Started service that is for managing our touch events on overlay floating window when our app is in the background state.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
MainActivity.class package com.example.floatingchathead import android.app.Activity import android.content.Intent import android.net.Uri import android.os.Build import android.os.Bundle import android.provider.Settings import android.view.View import androidx.appcompat.app.AppCompatActivity class MainActivity : AppCompatActivity() { val requestCode = 201 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val btn: View = findViewById(R.id.btn) btn.setOnClickListener(View.OnClickListener { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) { val intent = Intent( Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:$packageName") ) startActivityForResult(intent,requestCode) } else { startService(Intent(this, MyFloatingWindowService::class.java)) finish() } }) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (resultCode == Activity.RESULT_OK) { startService(Intent(this, MyFloatingWindowService::class.java)) finish() } } } |
In our MainActivity, As you can see we have a button in activity_main.xml layout we are performing two tasks on click of it.
1- We check that is Build version is greater then Marshmallow and is app have the permission of overlay.
2- Simply start the service that we created in the next step and after starting the service we finish the activity.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
package com.example.floatingchatheat import android.annotation.SuppressLint import android.app.Service import android.content.Context import android.content.Intent import android.graphics.PixelFormat import android.os.Build import android.os.IBinder import android.view.* import kotlin.math.roundToInt class MyFloatingWindowService : Service() { /**Solution for handle layout flag because that devices whom Build version is * greater then Oreo that don't support WindowManager.LayoutParams.TYPE_PHONE * in that case we use WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY*/ var LAYOUT_FLAG: Int = 0 lateinit var floatingView: View lateinit var manager: WindowManager lateinit var params: WindowManager.LayoutParams override fun onBind(intent: Intent?): IBinder? { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { LAYOUT_FLAG = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY } else { WindowManager.LayoutParams.TYPE_PHONE } val params = WindowManager.LayoutParams( WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, LAYOUT_FLAG, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT ) this.params = params //Specify the view position params.gravity = Gravity.TOP or Gravity.LEFT //Initially view will be added to top-left corner params.x = 0 params.y = 100 manager = getSystemService(Context.WINDOW_SERVICE) as WindowManager floatingView = LayoutInflater.from(this).inflate(R.layout.activity_main2, null) manager.addView(floatingView, params) floatingView.findViewById<View>(R.id.chat_head)?.setOnTouchListener(object : View.OnTouchListener { var initialX: Int? = null var initialY: Int? = null var initialTouchX: Float? = null var initialTouchY: Float? = null @SuppressLint("ClickableViewAccessibility") override fun onTouch(view: View?, motionEvent: MotionEvent?): Boolean { when (motionEvent!!.action) { MotionEvent.ACTION_DOWN -> { //remember the initial position. initialX = params.x initialY = params.y //get the touch location initialTouchX = motionEvent!!.getRawX() initialTouchY = motionEvent!!.getRawY() return true } MotionEvent.ACTION_UP -> { val Xdiff = (motionEvent.getRawX() - initialTouchX!!) val Ydiff = (motionEvent.getRawY() - initialTouchY!!) return true } MotionEvent.ACTION_MOVE -> { //Calculate the X and Y coordinates of the view. params.x = initialX!!.plus((motionEvent.getRawX() - initialTouchX!!)).roundToInt() params.y = initialY!!.plus((motionEvent.getRawY() - initialTouchY!!).roundToInt()) manager.updateViewLayout(floatingView, params) return true } } return false } }) return START_NOT_STICKY } override fun onDestroy() { super.onDestroy() manager.removeView(floatingView) } } |
In our service class first thing to do is we need to initialize a window manager object because we add the layout or view on it.
We need to call the getSysytemServices method and its parameters we pass Context.WINDOW_SERVICE to initialize WindowManager object.
Also, we need to initialize the Window Manager layout params.
At this point please take a look at LAYOUT_FLAG initialization above comment because it can through Exception.
Now getting the view by the help of LayoutInflater and after getting the view we add that view in our window manger object with layout params.
Now we can achieve the overlay but for handling the touch event on that popup window we need to setOnTouchLlistner on that view or you can set any child view of that view.
In my case I have an image view in my floating view that’s id is chat_head.
I setOnTouchListener on it and manage the touch events in onTouch callback.
Now we achieved Floating Chat head like facebook in android using Kotlin.
If you have more details or questions, you can reply to the received confirmation email.
Back to Home
2 comments
are you want to see the activity_main.xml?