闲来无事想自己手写一个类似微信视频聊天悬浮窗的效果,由于笔者是第一次写技术类的文章,不足的地方,还请各位多多谅解哈。另外由于篇幅有限,在此只展示关键代码。首先我们定义二个按钮,一个是创建悬浮窗,一个是让悬浮窗消失,如下所示:
this.button_open=findViewById(R.id.button_open)
this.button_close=findViewById(R.id.button_close)
this.button_open.setOnClickListener(this)
this.button_close.setOnClickListener(this)
overridefunonClick(v:View){
when(v.getId()){
R.id.button_open->
showFloatingView(this)
R.id.button_close->
if(this::mFloatingViewManager.isInitialized){
this.mFloatingViewManager.removeAllFloatingView()
我们来屡一下创建悬浮窗的逻辑,在创建悬浮窗之前,我们需要申请到系统的权限,如果没有获取到权限系统是会报错的,首先我们在AndroidManifest.xml文件中加上一条
<uses-permissionandroid:name="android.permission.SYSTEM_ALERT_WINDOW"/>,这还不够,我们还需要在代码中检测是否成功获取到了悬浮窗的权限,如果没有被授权还要动态申请,其代码如下所示:
if(!Settings.canDrawOverlays(this@MainActivity)){
showDialog()
}
else{
show()
}
其中showDialog方法的关键代码如下所示:
val intent=Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:"+this@MainActivity.getPackageName()))
startActivityForResult(intent,OVERLAY_PERMISSION_REQUEST_CODE)
其主要作用为获取权限,获取权限后我们就可以展示悬浮窗啦,以下为展示悬浮窗的代码
//获取悬浮窗布局
valfloatView=LayoutInflater.from(this).inflate(R.layout.call_float_view,null,false)
valdm=DisplayMetrics()
valwindowManager=getSystemService(Context.WINDOW_SERVICE)asWindowManager
windowManager.getDefaultDisplay().getMetrics(dm)//获取本机像素数据
this.mFloatingViewManager=FloatViewManager(this)
valconfigs=FloatViewManager.Configs()
configs.floatingViewX=dm.widthPixels/2//悬浮窗所在的初始位置的x坐标
configs.floatingViewY=dm.heightPixels/3//悬浮窗所在的初始位置的y坐标
configs.overMargin=(-(8*dm.density)).toInt()//设置悬浮窗的margin
this.mFloatingViewManager.addFloatingView(floatView,configs)
其中比较关键的一句代码就是this.mFloatingViewManager.addFloatingView(floatView,configs)了,我们来看一下这个方法都干了什么,
fun addFloatingView(view:View,configs:Configs){
valfloatingView=FloatView(this.mContext,configs.floatingViewX,configs.floatingViewY)//初始化一个FloatView
floatingView.setOnTouchListener(this)
floatingView.setOverMargin(configs.overMargin)//设置Margin
floatingView.setAnimateInitialMove(configs.animateInitialMove)//是否展示动画
valtargetParams=FrameLayout.LayoutParams(configs.floatingViewWidth,configs.floatingViewHeight)
view.setLayoutParams(targetParams)//设置视图的宽高
floatingView.addView(view)
this.mFloatingViewList.add(floatingView)
this.mWindowManager.addView(floatingView,floatingView.getWindowLayoutParams())//显示悬浮窗
},
可见,这个addView方法大致只是设置了一下视图的参数,然后把它加到了WindowManager里面,那么又有小伙伴问了,你写的这个FloatView是个什么东西呀,其实他是继承了FrameLayout的一个ViewGroup。我们只需要关注移动视图事件的方法即可,该方法的代码如下
elseif(action==MotionEvent.ACTION_MOVE){
this.mIsMoveAccept=true
this.updateViewPosition(getXByTouch(),getYByTouch())
}
其大致意思就是更新视图的位置,这样一来我们的悬浮窗就可以随着手指移动啦.
我们再设置当手指抬起的时间,让悬浮窗滑动到屏幕边缘,ACTION_UP事件的代码如下
if(this.mIsMoveAccept){
moveToEdge(true)
}
funmoveToEdge(withAnimation:Boolean){
valcurrentX=getXByTouch()
valcurrentY=getYByTouch()
moveToEdge(currentX,currentY,withAnimation)
}
Fun moveToEdge(startX:Int,startY:Int,withAnimation:Boolean){
vargoalPositionX=startX
valgoalPositionY=startY
valisMoveRightEdge=startX>(this.mMetrics.widthPixels-getWidth())/2
if(isMoveRightEdge){
goalPositionX=this.mPositionLimitRect.right
}else{
goalPositionX=this.mPositionLimitRect.left
}
moveTo(startX,startY,goalPositionX,goalPositionY,withAnimation)
}
funmoveTo(currentX:Int,currentY:Int,goalPositionX:Int,goalPositionY:Int,withAnimation:Boolean){
valgoalPositionX=Math.min(
Math.max(this.mPositionLimitRect.left,goalPositionX),
this.mPositionLimitRect.right
)
valgoalPositionY=Math.min(
Math.max(this.mPositionLimitRect.top,goalPositionY),
this.mPositionLimitRect.bottom
)
if(withAnimation){
this.mParams.y=goalPositionY
this.mMoveEdgeAnimator=ValueAnimator.ofInt(currentX,goalPositionX)
this.mMoveEdgeAnimator.addUpdateListener{listener->
mParams.x=mMoveEdgeAnimator.getAnimatedValue()asInt
mWindowManager.updateViewLayout(this@FloatView,mParams)
}
this.mMoveEdgeAnimator.setDuration(MOVE_TO_EDGE_DURATION)
this.mMoveEdgeAnimator.setInterpolator(this.mMoveEdgeInterpolator)
this.mMoveEdgeAnimator.start()
}
else{
if(this.mParams.x!=goalPositionX||this.mParams.y!=goalPositionY){
this.mParams.x=goalPositionX
this.mParams.y=goalPositionY
this.mWindowManager.updateViewLayout(this@FloatView,this.mParams)
}
}
this.mViewTouchX=0f
this.mViewTouchY=0f
this.mScreenTouchDownX=0f
this.mScreenTouchDownY=0f
this.mIsMoveAccept=false
}
从代码可以看出,我们先要判断悬浮窗是滑动到左边缘还是滑动到右边缘,然后判断是否需要执行动画,如果需要执行动画则执行,否则直接将悬浮窗拉到边缘,这样我们的悬浮窗就完成啦,是不是很简单呢,我们再来看一下销毁悬浮窗的逻辑:
funremoveAllFloatingView(){
if(this::mFloatingViewList!=null){
for(floatingViewinmFloatingViewList){
this.mWindowManager.removeViewImmediate(floatingView)
}
this.mFloatingViewList.clear()
}
}
只需要几句代码就可以完成销毁,这样我们的工作就完成啦
其实现的效果如下所示:
最后附上源码,想详细学习的朋友可以下载,看完的小伙伴还请给我赞呦