• Android 音乐播放器悬浮窗


    之前的项目中写了个音乐播放器,这里记录下悬浮窗相关的东西。

     

    废话不多说,先上个效果图

    效果图

    1、自定义悬浮框布局

    根据效果图可以看到悬浮窗 布局由一张封面图,一个圆形进度条以及4个按钮组成。

    布局内容就不写了,很简单。

    悬浮窗控件 :FloatLayout继承自FrameLayout,引入布局文件。

    初始化 windowManager

    mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    1. DisplayMetrics dm = new DisplayMetrics();
    2. mWindowManager.getDefaultDisplay().getMetrics(dm);
    3. //获取屏幕的宽度,用于后边计算吸边操作
    4. screenWidth = dm.widthPixels;

    然后通过 onInterceptTouchEvent 去处理按下、移动和抬起事件的分发拦截。这个是悬浮窗点击、移动的重点

    刚开始打算是用onTouchEvent去处理触摸事件,经过验证发现并不能解决父布局和子View的点击冲突,于是试用了onInterceptTouchEvent 方法,发现这才是解决这里点击冲突正确的姿势。

    1. public boolean onInterceptTouchEvent(MotionEvent ev) {
    2. //左上角为原点,获取相对屏幕的坐标
    3. x = (int) ev.getRawX();
    4. y = (int) ev.getRawY();
    5. //区分拖动和点击事件 ,判断是否需要拦截
    6. int action = ev.getAction();
    7. switch (action) {
    8. case MotionEvent.ACTION_DOWN:
    9. //获取相对于父view的坐标点
    10. mTouchStartX = ev.getX();
    11. mTouchStartY = ev.getY();
    12. break;
    13. case MotionEvent.ACTION_MOVE:
    14. //处理移动逻辑
    15. float moveStartX = ev.getX();
    16. float moveStartY = ev.getY();
    17. //移动的距离大于3,移动距位置
    18. if (Math.abs(mTouchStartX - moveStartX) > 3 && Math.abs(mTouchStartY - moveStartY) > 3) {
    19. mParams.x = (int) (x - mTouchStartX);
    20. mParams.y = (int) (y - mTouchStartY);
    21. mWindowManager.updateViewLayout(this, mParams);
    22. return super.onInterceptTouchEvent(ev);
    23. }
    24. break;
    25. case MotionEvent.ACTION_UP:
    26. float endX = ev.getRawX();
    27. //根据最终手指停留的地方,设置吸边效果
    28. if (endX > screenWidth / 2) {
    29. mParams.x = screenWidth - getWidth();
    30. } else {
    31. mParams.x = 0;
    32. }
    33. mWindowManager.updateViewLayout(this, mParams);
    34. float upX = ev.getX();
    35. float upY = ev.getY();
    36. if (Math.abs(upX - mTouchStartX) > 30 && Math.abs(upY - mTouchStartY) > 30){
    37. return true;
    38. }
    39. break;
    40. }
    41. return super.onInterceptTouchEvent(ev);
    42. }

    接下来就是正常处理按钮的点击事件了。因为设计的是当 菜单按钮显示的时候,点击封面图是跳转其他页面,所以需要写一个计时器 把菜单布局收起来。并且在菜单布局收起来的时候,注意调用吸边操作

    1. /**
    2. * 吸边操作
    3. */
    4. private void adscrop() {
    5. if (isWindowLive){
    6. if (x > screenWidth / 2){
    7. mParams.x = screenWidth;
    8. }else{
    9. mParams.x = 0;
    10. }
    11. mWindowManager.updateViewLayout(this,mParams);
    12. }
    13. }

    提供了一个悬浮窗的管理类,需要给悬浮窗定义一些公共方法

    1. /**
    2. * 将小悬浮窗的参数传入,用于更新小悬浮窗的位置。
    3. * @param params
    4. */
    5. public void setParams(WindowManager.LayoutParams params){
    6. this.mParams = params;
    7. }
    8. /**
    9. * 设置最大进度条
    10. * @param maxLength
    11. */
    12. public void setMaxProgress(int maxLength){
    13. mFloatSeekbar.setMax(maxLength);
    14. }
    15. /**
    16. * 更新进度条
    17. * @param progress
    18. */
    19. public void upDataProgress(int progress){
    20. mFloatSeekbar.setProgress(progress);
    21. }
    22. /**
    23. *隐藏
    24. */
    25. public void hide(){
    26. mView.setVisibility(GONE);
    27. }
    28. /**
    29. * 显示
    30. */
    31. public void show(){
    32. mView.setVisibility(VISIBLE);
    33. }
    34. /**
    35. * 开始/暂停
    36. * @param isStart
    37. */
    38. public void startOrStop(boolean isStart){
    39. if (isStart){
    40. isPlaying = true;
    41. mIvStart.setImageResource(R.mipmap.float_image_stop);
    42. }else{
    43. isPlaying = false;
    44. mIvStart.setImageResource(R.mipmap.float_image_start);
    45. }
    46. }
    1. /**
    2. * 设置悬浮窗是否被销毁,如果被销毁,移除计时器
    3. * @param isLive
    4. */
    5. public void windowState(boolean isLive){
    6. isWindowLive = isLive;
    7. if (isWindowLive){
    8. handler.removeCallbacks(runnable);
    9. }
    10. }

    2、FloatWindowManager 管理类

    1. /**
    2. * 创建悬浮窗
    3. * @param context
    4. */
    5. public static void creatFloatWindow(Context context){
    6. mParams = new WindowManager.LayoutParams();
    7. mWindowManager = getWindowManager(context);
    8. mFloatLayout = new FloatLayout(context);
    9. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    10. mParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
    11. } else {
    12. mParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
    13. }
    14. //设置图片格式,透明背景效果
    15. mParams.format = PixelFormat.RGBA_8888;
    16. //设置不可聚焦
    17. mParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
    18. //设置位置左上方
    19. mParams.gravity = Gravity.START | Gravity.TOP;
    20. DisplayMetrics dm = new DisplayMetrics();
    21. //取得窗口属性
    22. mWindowManager.getDefaultDisplay().getMetrics(dm);
    23. //窗口的宽度
    24. int screenWidth = dm.widthPixels;
    25. //窗口高度
    26. int screenHeight = dm.heightPixels;
    27. //以屏幕左上角为原点,设置x、y初始值,相对于gravity
    28. mParams.x = screenWidth;
    29. mParams.y = screenHeight / 2 + screenWidth / 3;
    30. //设置悬浮窗口长宽数据
    31. mParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
    32. mParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
    33. mFloatLayout.setParams(mParams);
    34. mWindowManager.addView(mFloatLayout, mParams);
    35. }
    1. /**
    2. * 移除悬浮窗
    3. */
    4. public static void removeFloatWindowManager() {
    5. boolean isAttach = true;
    6. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    7. isAttach = mFloatLayout.isAttachedToWindow();
    8. }
    9. if (mHasShown && isAttach && mWindowManager != null)
    10. mWindowManager.removeView(mFloatLayout);
    11. mHasShown = false;
    12. mFloatLayout.windowState(false);
    13. }
    1. /**
    2. *返回已创建的WindowManager。
    3. * @param context
    4. * @return
    5. */
    6. private static WindowManager getWindowManager(Context context) {
    7. if (mWindowManager == null){
    8. mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    9. }
    10. return mWindowManager;
    11. }
    12. 因为设计的是音乐播放器,,所以播放功能应该放在service,悬浮窗的控制也放在service中,所以需要提供公共方法
    13. public static void hide() {
    14. if (mHasShown)
    15. mFloatLayout.hide();
    16. mHasShown = false;
    17. }
    18. public static void show() {
    19. if (!mHasShown)
    20. mFloatLayout.show();
    21. mHasShown = true;
    22. mFloatLayout.windowState(true);
    23. }
    24. public static void setMaxProgress(int length){
    25. if (mHasShown || mFloatLayout != null)
    26. mFloatLayout.setMaxProgress(length);
    27. }
    28. public static void updataProgress(int progress) {
    29. if (mHasShown || mFloatLayout != null)
    30. mFloatLayout.upDataProgress(progress);
    31. }
    32. public static void startMusic(boolean isStart){
    33. mFloatLayout.startOrStop(isStart);
    34. }

    最近借鉴了一下朋友的设计 重新 封装了一个播放器,有兴趣的朋友可以探讨一下

    XAudioPlayer: 音频播放器 (gitee.com)

  • 相关阅读:
    Java Number类
    【Verilog实战】SPI协议底层硬件接口设计和功能验证(附源码)
    一、Prometheus集成Grafana可视化监控安装详解
    mac pro M1(ARM)安装:ftp远程文件互传工具
    日志瘦身方法论
    网上零食销售系统(Java;JSP;JDBC)附源码+数据库+论文
    HTTP长连接
    Java多线程编程
    二叉树操作集锦(递归遍历,非递归遍历,求深度,结点个数,完全二叉树等)
    CentOS 7.9 安装 epel-release
  • 原文地址:https://blog.csdn.net/XuWei1213/article/details/100694460