最近在轉(zhuǎn)型kotlin放钦,然后又對(duì)天貓的茅臺(tái)活動(dòng)有點(diǎn)興趣色徘,于是,腦子一熱操禀,抽了十幾分鐘褂策,寫了個(gè)簡易的秒表助手。如下圖:
image.png
分析如下:
首先颓屑,要做到懸浮窗口斤寂,那么久必須要依賴Window屬性,在window中揪惦,添加自定義的view遍搞。然后腦子浮現(xiàn)出了這一幅圖:
image.png
emu,感覺完成一大半了器腋,最關(guān)鍵的就是 windowManager.addView(floatingView, layoutParams)這個(gè)操作了吧尾抑。
然后分析點(diǎn)歇父,第二點(diǎn):如何讓該應(yīng)用處于后臺(tái)也能運(yùn)行呢?沒錯(cuò)再愈,那就是Service榜苫,開一個(gè)Service不就好了嗎,于是乎翎冲,就先創(chuàng)建了一個(gè)Servie垂睬,并在AndroidManifest中完成注冊(cè)。
<service
android:name=".FloatingWindowService"
android:enabled="true" />
最后一個(gè)問題: 如何實(shí)時(shí)把系統(tǒng)時(shí)間拋給主線程抗悍? 這里我利用了kotlin的協(xié)程屬性
GlobalScope.launch(Dispatchers.IO) {
println("deal data===>");
while (state) {
val format = SimpleDateFormat("HH:mm:ss", Locale.getDefault())
val myDate: String = format.format(Date())
withContext(Dispatchers.Main) {
println("deal UI===>");
tvContent.setText(myDate.toString());
}
}
}
如上圖所示驹饺,然IO線程不斷處理日期數(shù)據(jù),處理完后缴渊,轉(zhuǎn)換為主線程赏壹,然后把控件數(shù)據(jù)更新,即完成功能衔沼。
ok蝌借,動(dòng)手操作一下吧。
import android.annotation.SuppressLint
import android.app.ActionBar
import android.app.Service
import android.content.Intent
import android.graphics.PixelFormat
import android.os.Build
import android.os.IBinder
import android.provider.Settings
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.WindowManager
import android.widget.TextView
import androidx.annotation.RequiresApi
import kotlinx.coroutines.*
import java.text.SimpleDateFormat
import java.util.*
class FloatingWindowService : Service() {
private lateinit var windowManager: WindowManager
private lateinit var layoutParams: WindowManager.LayoutParams
private lateinit var tvContent: TextView
private var floatingView: View? = null
private var state = true;
// 用來判斷floatingView是否attached 到 window manager指蚁,防止二次removeView導(dǎo)致崩潰
private var attached = false
override fun onCreate() {
super.onCreate()
// 獲取windowManager并設(shè)置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 = ActionBar.LayoutParams.WRAP_CONTENT
height = ActionBar.LayoutParams.WRAP_CONTENT
x = 300
y = 300
}
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
@RequiresApi(Build.VERSION_CODES.M)
@SuppressLint("ClickableViewAccessibility")
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (Settings.canDrawOverlays(this)) {
floatingView = LayoutInflater.from(this).inflate(R.layout.activity_main, null)
tvContent = floatingView!!.findViewById<TextView>(R.id.tv_time);
// 設(shè)置TextView滾動(dòng)
windowManager.addView(floatingView, layoutParams)
attached = true
GlobalScope.launch(Dispatchers.IO) {
println("deal data===>");
while (state) {
val format = SimpleDateFormat("HH:mm:ss", Locale.getDefault())
val myDate: String = format.format(Date())
withContext(Dispatchers.Main) {
println("deal UI===>");
tvContent.setText(myDate.toString());
}
}
}
}
return super.onStartCommand(intent, flags, startId)
}
override fun onDestroy() {
if (attached) {
windowManager.removeView(floatingView)
state = false
}
}
}
最后菩佑,注意要在一個(gè)activity里面 ,startService 哈凝化。
if (Settings.canDrawOverlays(this)) {
val service = Intent(this, FloatingWindowService::class.java);
startService(service);
} else {
startActivity(Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION));
}
ok稍坯,完成。