之前的项目中写了个音乐播放器,这里记录下悬浮窗相关的东西。
废话不多说,先上个效果图
根据效果图可以看到悬浮窗 布局由一张封面图,一个圆形进度条以及4个按钮组成。
布局内容就不写了,很简单。
悬浮窗控件 :FloatLayout继承自FrameLayout,引入布局文件。
初始化 windowManager
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
- DisplayMetrics dm = new DisplayMetrics();
- mWindowManager.getDefaultDisplay().getMetrics(dm);
- //获取屏幕的宽度,用于后边计算吸边操作
- screenWidth = dm.widthPixels;
然后通过 onInterceptTouchEvent 去处理按下、移动和抬起事件的分发拦截。这个是悬浮窗点击、移动的重点
刚开始打算是用onTouchEvent去处理触摸事件,经过验证发现并不能解决父布局和子View的点击冲突,于是试用了onInterceptTouchEvent 方法,发现这才是解决这里点击冲突正确的姿势。
- public boolean onInterceptTouchEvent(MotionEvent ev) {
-
- //左上角为原点,获取相对屏幕的坐标
- x = (int) ev.getRawX();
- y = (int) ev.getRawY();
-
- //区分拖动和点击事件 ,判断是否需要拦截
- int action = ev.getAction();
- switch (action) {
-
- case MotionEvent.ACTION_DOWN:
- //获取相对于父view的坐标点
- mTouchStartX = ev.getX();
- mTouchStartY = ev.getY();
- break;
-
- case MotionEvent.ACTION_MOVE:
- //处理移动逻辑
- float moveStartX = ev.getX();
- float moveStartY = ev.getY();
-
- //移动的距离大于3,移动距位置
- if (Math.abs(mTouchStartX - moveStartX) > 3 && Math.abs(mTouchStartY - moveStartY) > 3) {
- mParams.x = (int) (x - mTouchStartX);
- mParams.y = (int) (y - mTouchStartY);
-
- mWindowManager.updateViewLayout(this, mParams);
- return super.onInterceptTouchEvent(ev);
- }
- break;
-
- case MotionEvent.ACTION_UP:
-
- float endX = ev.getRawX();
- //根据最终手指停留的地方,设置吸边效果
- if (endX > screenWidth / 2) {
- mParams.x = screenWidth - getWidth();
- } else {
- mParams.x = 0;
- }
- mWindowManager.updateViewLayout(this, mParams);
-
- float upX = ev.getX();
- float upY = ev.getY();
-
- if (Math.abs(upX - mTouchStartX) > 30 && Math.abs(upY - mTouchStartY) > 30){
- return true;
- }
- break;
- }
-
- return super.onInterceptTouchEvent(ev);
- }
接下来就是正常处理按钮的点击事件了。因为设计的是当 菜单按钮显示的时候,点击封面图是跳转其他页面,所以需要写一个计时器 把菜单布局收起来。并且在菜单布局收起来的时候,注意调用吸边操作
- /**
- * 吸边操作
- */
- private void adscrop() {
-
- if (isWindowLive){
-
- if (x > screenWidth / 2){
- mParams.x = screenWidth;
- }else{
- mParams.x = 0;
- }
-
- mWindowManager.updateViewLayout(this,mParams);
- }
- }
提供了一个悬浮窗的管理类,需要给悬浮窗定义一些公共方法
- /**
- * 将小悬浮窗的参数传入,用于更新小悬浮窗的位置。
- * @param params
- */
- public void setParams(WindowManager.LayoutParams params){
- this.mParams = params;
- }
-
- /**
- * 设置最大进度条
- * @param maxLength
- */
- public void setMaxProgress(int maxLength){
- mFloatSeekbar.setMax(maxLength);
- }
-
- /**
- * 更新进度条
- * @param progress
- */
- public void upDataProgress(int progress){
- mFloatSeekbar.setProgress(progress);
- }
-
- /**
- *隐藏
- */
- public void hide(){
- mView.setVisibility(GONE);
- }
-
- /**
- * 显示
- */
- public void show(){
- mView.setVisibility(VISIBLE);
- }
-
- /**
- * 开始/暂停
- * @param isStart
- */
- public void startOrStop(boolean isStart){
- if (isStart){
- isPlaying = true;
- mIvStart.setImageResource(R.mipmap.float_image_stop);
- }else{
- isPlaying = false;
- mIvStart.setImageResource(R.mipmap.float_image_start);
- }
- }
- /**
- * 设置悬浮窗是否被销毁,如果被销毁,移除计时器
- * @param isLive
- */
- public void windowState(boolean isLive){
- isWindowLive = isLive;
- if (isWindowLive){
- handler.removeCallbacks(runnable);
- }
- }
- /**
- * 创建悬浮窗
- * @param context
- */
- public static void creatFloatWindow(Context context){
- mParams = new WindowManager.LayoutParams();
- mWindowManager = getWindowManager(context);
- mFloatLayout = new FloatLayout(context);
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- mParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
- } else {
- mParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
- }
-
- //设置图片格式,透明背景效果
- mParams.format = PixelFormat.RGBA_8888;
- //设置不可聚焦
- mParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
- //设置位置左上方
- mParams.gravity = Gravity.START | Gravity.TOP;
-
- DisplayMetrics dm = new DisplayMetrics();
- //取得窗口属性
- mWindowManager.getDefaultDisplay().getMetrics(dm);
- //窗口的宽度
- int screenWidth = dm.widthPixels;
- //窗口高度
- int screenHeight = dm.heightPixels;
- //以屏幕左上角为原点,设置x、y初始值,相对于gravity
- mParams.x = screenWidth;
- mParams.y = screenHeight / 2 + screenWidth / 3;
-
- //设置悬浮窗口长宽数据
- mParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
- mParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
- mFloatLayout.setParams(mParams);
- mWindowManager.addView(mFloatLayout, mParams);
- }
- /**
- * 移除悬浮窗
- */
- public static void removeFloatWindowManager() {
-
- boolean isAttach = true;
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
- isAttach = mFloatLayout.isAttachedToWindow();
- }
- if (mHasShown && isAttach && mWindowManager != null)
- mWindowManager.removeView(mFloatLayout);
-
- mHasShown = false;
- mFloatLayout.windowState(false);
- }
- /**
- *返回已创建的WindowManager。
- * @param context
- * @return
- */
- private static WindowManager getWindowManager(Context context) {
- if (mWindowManager == null){
- mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
- }
- return mWindowManager;
- }
- 因为设计的是音乐播放器,,所以播放功能应该放在service,悬浮窗的控制也放在service中,所以需要提供公共方法
- public static void hide() {
- if (mHasShown)
- mFloatLayout.hide();
- mHasShown = false;
- }
-
- public static void show() {
- if (!mHasShown)
- mFloatLayout.show();
- mHasShown = true;
- mFloatLayout.windowState(true);
-
- }
-
- public static void setMaxProgress(int length){
- if (mHasShown || mFloatLayout != null)
- mFloatLayout.setMaxProgress(length);
- }
-
- public static void updataProgress(int progress) {
- if (mHasShown || mFloatLayout != null)
- mFloatLayout.upDataProgress(progress);
- }
-
- public static void startMusic(boolean isStart){
- mFloatLayout.startOrStop(isStart);
- }