轉載請註明來源 https://blog.csdn.net/u011453163/article/details/84655641
話不多說,先上效果圖
代碼是用kotlin寫的,kotlin也是剛學的,寫的可能不怎麼好。
起因
說說爲什麼開發這個功能,首先自己公司的項目裏用到了懸浮球的功能,以前見到的懸浮球一般是作爲快捷入口,有點像狗皮膏藥,有時候很煩。一直覺得微信的用戶體驗很好,微信也用了懸浮球,但是相對剋制,給用戶選擇的空間很大,效果也挺炫的,忍不住也想嘗試的開發一個,然後就有了這份代碼了。
思路
1、 通過WindowManager 添加兩個view,一個是控制器懸浮球,一個是展開頁面的載體
fun addFloatingWindow() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.canDrawOverlays(context)) {
Log.d("FloatingWindow", "沒有懸浮窗權限")
return
}
}
if (!isAddView) {
isAddView = true
windowManager?.apply {
addView(floatingBaseView, baseLayoutParams)
addView(controller, controlLayoutParams)
}
}
}
kotlin 沒有高亮。。。。
這裏只是做了簡單的權限判斷,實際應用到項目中,權限自行管理即可
2、通過切換WindowManager.LayoutParams 的 flag 來改變兩個view的焦點問題
baseLayoutParams?.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
controlLayoutParams?.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
關於flag網上已經有很多文章寫的很明白了 這裏就不贅述了,這裏之所以要切換焦點,使用底層的view是全屏的且一直存在的,如果不切換焦點的話,觸摸事件無法傳遞。
3、縮放效果
3.1、 最開始的想法是通過改變WindowManager.LayoutParams的寬高來處理的,但是在window上使用動畫,總是出現掉幀,達不到效果
3.2、第二種也是爲什麼我要使用兩層的原因,我使用view來做這個動畫效果,使用view做縮放動畫有兩種方式 :一 、canvas.clip(…) 無法消除鋸齒(放棄) 二、使用paint的Xfermod來處理 能消除鋸齒(採用)
private fun init() {
pathRect = Path()
pathCircle = Path()
paint = Paint().apply {
xfermode=PorterDuffXfermode(PorterDuff.Mode.DST_OUT)
isAntiAlias = true
}
}
override fun draw(canvas: Canvas) {
canvas.saveLayer(0f, 0f, this.width.toFloat(), this.height.toFloat(), null, Canvas.ALL_SAVE_FLAG)
super.draw(canvas)
/**
* 取路徑區域重疊的部分
*/
canvas.drawFilter = PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG or Paint.FILTER_BITMAP_FLAG)
pathRect?.reset()
pathRect?.addRect(0f, 0f, width.toFloat(), height.toFloat(), Path.Direction.CW)
pathCircle?.reset()
pathCircle?.addCircle(circleX.toFloat(), circleY.toFloat(), circleRadius.toFloat(), Path.Direction.CW)
pathRect?.op(pathCircle, Path.Op.XOR)
canvas.drawPath(pathRect, paint)
}
4、移除區域
因爲是兩層的關係 ,所以做移除區域就很簡單,只要底層畫出相應的塊即可。
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas?.apply {
drawFilter = pfilter
if (showDelArea) {
paint.color = delAreaBgColor
if (isInside) {
drawCircle(width.toFloat(), height.toFloat(), delRadius.toFloat(), paint)
} else {
drawCircle(width.toFloat(), height.toFloat(), srcRadius.toFloat(), paint)
}
paint.color = delTextColor
paint.getTextBounds(delText, 0, delText.length, textRect)
drawText(delText, width.toFloat() - (srcRadius-textRect.width())/2-textRect.width(), height.toFloat() -(srcRadius-textRect.height())/2, paint)
}
}
}
Demo
github源碼
FloatingView
如何引入
在項目根 build.gradle 添加
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
在使用的模塊下添加
dependencies {
implementation 'com.github.zhenbinwei:KotlinFloatingView:1.0.0'
}
需要使用的權限
<!-- 顯示系統窗口權限 -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<!-- 在 屏幕最頂部顯示addview-->
<uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" />
如何使用
//創建實例對象
FloatingWindow floatingWindow=new FloatingWindow(this);
//設置展開的佈局
floatingWindow.addRealContentView(View.inflate(this,R.layout.test,null));
//設置懸浮窗圖標
floatingWindow.setMiniWindowIcon(R.mipmap.ic_launcher_round);
//顯示
floatingWindow.addFloatingWindow();
//關閉
floatingWindow.removeFloatingWindow();