转载请注明来源 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();