文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Android应用内悬浮窗的实现方案示例

2023-05-30 22:53

关注

1、悬浮窗的基本介绍

悬浮窗,大家应该也不陌生,凌驾于应用之上的一个小弹窗,实现上很简单,就是添加一个系统级别的窗口,Android中通过WindowManagerService( WMS)来管理所有的窗口,对于WMS来说,管你是Activity、Toast、Dialog,都不过是通过WindowManagerGlobal.addView()添加的一个个View。

Android中的窗口分为三个级别:

1 应用窗口,比如Activity的窗口;

2 子窗口,依赖于父窗口,比如PopupWindow;

3 系统窗口,比如状态栏、Toast,目标悬浮窗就是系统窗口.

2、根据产品需求进行设计

先了解一下大概的产品需求:

悬浮窗需要跨越整个应用
2、需要与悬浮窗进行交互
3、悬浮窗得移动
4、点击跳转特定的页面
5、消息提示的拖拽小红点

需求很简单,但是如果估算没错,不下一周产品经理会添加新的需求,所以为了更好的后续扩展,需要进行合理的设计,主要分为以下几点:

悬浮窗自定义一个FrameLayout布局FloatLayout,里面进行拖动及点击响应处理;
2、FloatMonkService,是一个服务,开启服务的时候创建悬浮窗;
3、FloatCallBack,交互接口,在FloatMonkService里面实现接口,用于交互;
4、FloatWindowManager,悬浮窗的管理,因为后续悬浮窗布局可能有好几个,可以在这里面进行切换;
5、HomeWatcherReceiver,广播接收者,因为在应用内展示,需要监听用户在点击Home键和切换键的时候隐藏悬浮窗,需要FloatMonkService里头动态注册;
6、FloatActionController,其实就是代理,其它模块需要通过它来和悬浮窗进行交互,真正干活的是实现FloatCallBack接口的FloatMonkService;
7、FloatPermissionManager,需要适配各个傻逼机型的权限,庆幸网上已有大佬分享,只需要单独对7.0系统进行一些适配就行,悬浮窗权限适配;
8、拖拽控件DraggableFlagView,直接拿来在悬浮窗上出现很奇怪的问题,所以需要改造一下下才能达到图中效果。

3、具体实现

float_littlemonk_layout.xml

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"  xmlns:dfv="http://schemas.android.com/apk/res-auto"  android:layout_width="wrap_content"  android:layout_height="wrap_content"  android:gravity="center"  android:orientation="vertical">  <RelativeLayout    android:id="@+id/monk_relative_root"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical">    <ImageView      android:id="@+id/float_id"      android:layout_width="70dp"      android:layout_height="80dp"      android:layout_gravity="center_vertical|end"      android:scaleType="center"      android:src="@drawable/little_monk" />  </RelativeLayout>  <FrameLayout    android:layout_width="match_parent"    android:layout_height="match_parent">    <floatwindow.xishuang.float_lib.view.DraggableFlagView      android:id="@+id/main_dfv"      android:layout_width="17dp"      android:layout_height="17dp"      android:layout_gravity="end"      dfv:color1="#FF3B30" />  </FrameLayout></FrameLayout>

简单的布局,就是一张图片+右上角放一个自定义的小红点。

FloatLayout.java

@Override  public boolean onTouchEvent(MotionEvent event) {    // 获取相对屏幕的坐标,即以屏幕左上角为原点    int x = (int) event.getRawX();    int y = (int) event.getRawY();    //下面的这些事件,跟图标的移动无关,为了区分开拖动和点击事件    int action = event.getAction();    switch (action) {      case MotionEvent.ACTION_DOWN:        startTime = System.currentTimeMillis();        mTouchStartX = event.getX();        mTouchStartY = event.getY();        break;      case MotionEvent.ACTION_MOVE:        //图标移动的逻辑在这里        float mMoveStartX = event.getX();        float mMoveStartY = event.getY();        // 如果移动量大于3才移动        if (Math.abs(mTouchStartX - mMoveStartX) > 3            && Math.abs(mTouchStartY - mMoveStartY) > 3) {          // 更新浮动窗口位置参数          mWmParams.x = (int) (x - mTouchStartX);          mWmParams.y = (int) (y - mTouchStartY);          mWindowManager.updateViewLayout(this, mWmParams);          return false;        }        break;      case MotionEvent.ACTION_UP:        endTime = System.currentTimeMillis();        //当从点击到弹起小于半秒的时候,则判断为点击,如果超过则不响应点击事件        if ((endTime - startTime) > 0.1 * 1000L) {          isclick = false;        } else {          isclick = true;        }        break;    }    //响应点击事件    if (isclick) {      Toast.makeText(mContext, "我是大傻叼", Toast.LENGTH_SHORT).show();    }    return true;  }

为了把悬浮窗的view操作抽离出来,自定义了这个布局,主要进行两部分功能,悬浮窗的移动和点击处理,重点是通过mWindowManager.updateViewLayout(this, mWmParams)来进行悬浮窗的位置移动,我这个Demo里面只是简单的通过时间来判断点击事件,有必要的话点击事件需要添加特定View范围判断来响应点击。

// 如果移动量大于3才移动if (Math.abs(mTouchStartX - mMoveStartX) > 3 && Math.abs(mTouchStartY - mMoveStartY) > 3)

这个判断是为了避免点击悬浮窗不在重心位置会出现移动的现象。

FloatMonkService.java

public class FloatMonkService extends Service implements FloatCallBack {    private HomeWatcherReceiver mHomeKeyReceiver;  @Override  public void onCreate() {    super.onCreate();    FloatActionController.getInstance().registerCallLittleMonk(this);    //注册广播接收者    mHomeKeyReceiver = new HomeWatcherReceiver();    final IntentFilter homeFilter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);    registerReceiver(mHomeKeyReceiver, homeFilter);    //初始化悬浮窗UI    initWindowData();  }  @Override  public IBinder onBind(Intent intent) {    return null;  }    private void initWindowData() {    FloatWindowManager.createFloatWindow(this);  }  @Override  public void onDestroy() {    super.onDestroy();    //移除悬浮窗    FloatWindowManager.removeFloatWindowManager();    //注销广播接收者    if (null != mHomeKeyReceiver) {      unregisterReceiver(mHomeKeyReceiver);    }  }  /////////////////////////////////////////////////////////实现接口////////////////////////////////////////////////////  @Override  public void guideUser(int type) {    FloatWindowManager.updataRedAndDialog(this);  }    @Override  public void hide() {    FloatWindowManager.hide();  }    @Override  public void show() {    FloatWindowManager.show();  }    @Override  public void addObtainNumer() {    FloatWindowManager.addObtainNumer(this);    guideUser(4);  }    @Override  public void setObtainNumber(int number) {    FloatWindowManager.setObtainNumber(this, number);  }}

服务开启的时候通过FloatWindowManager.createFloatWindow(this)来创建悬浮窗,实现FloatCallBack 实现需要交互的接口。下面看一下创建悬浮窗的真正操作是怎样的。

FloatWindowManager.java

  public static void createFloatWindow(Context context) {    wmParams = new WindowManager.LayoutParams();    WindowManager windowManager = getWindowManager(context);    mFloatLayout = new FloatLayout(context);    if (Build.VERSION.SDK_INT >= 24) {       wmParams.type = WindowManager.LayoutParams.TYPE_PHONE;    } else {       String packname = context.getPackageName();      PackageManager pm = context.getPackageManager();      boolean permission = (PackageManager.PERMISSION_GRANTED == pm.checkPermission("android.permission.SYSTEM_ALERT_WINDOW", packname));      if (permission) {        wmParams.type = WindowManager.LayoutParams.TYPE_PHONE;      } else {        wmParams.type = WindowManager.LayoutParams.TYPE_TOAST;      }    }    //设置图片格式,效果为背景透明    wmParams.format = PixelFormat.RGBA_8888;    //设置浮动窗口不可聚焦(实现操作除浮动窗口外的其他可见窗口的操作)    wmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;    //调整悬浮窗显示的停靠位置为左侧置顶    wmParams.gravity = Gravity.START | Gravity.TOP;    DisplayMetrics dm = new DisplayMetrics();    //取得窗口属性    mWindowManager.getDefaultDisplay().getMetrics(dm);    //窗口的宽度    int screenWidth = dm.widthPixels;    //窗口高度    int screenHeight = dm.heightPixels;    //以屏幕左上角为原点,设置x、y初始值,相对于gravity    wmParams.x = screenWidth;    wmParams.y = screenHeight;    //设置悬浮窗口长宽数据    wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT;    wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT;    mFloatLayout.setParams(wmParams);    windowManager.addView(mFloatLayout, wmParams);    mHasShown = true;    //是否展示小红点展示    checkRedDot(context);  }  private static WindowManager getWindowManager(Context context) {    if (mWindowManager == null) {      mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);    }    return mWindowManager;  }

核心代码其实就是mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE),其中的context不能是Activity的,一开始就说了,Activity会返回它专享的WindowManager,而Activity的窗口级别是属于应用层的。进行一些初始化操作之后 windowManager.addView(mFloatLayout, wmParams)把布局添加进去就ok了。

 if (Build.VERSION.SDK_INT >= 24) {       wmParams.type = WindowManager.LayoutParams.TYPE_PHONE;    } else {       String packname = context.getPackageName();      PackageManager pm = context.getPackageManager();      boolean permission = (PackageManager.PERMISSION_GRANTED == pm.checkPermission("android.permission.SYSTEM_ALERT_WINDOW", packname));      if (permission) {        wmParams.type = WindowManager.LayoutParams.TYPE_PHONE;      } else {        wmParams.type = WindowManager.LayoutParams.TYPE_TOAST;      }    }

说一下这段代码的意义,当WindowManager.LayoutParams.type设置为WindowManager.LayoutParams.TYPE_TOAST的时候,是可以跳过权限申请的,但是为毛又单独适配各个机型呢,因为我们有小米Android系统,魅族Android系统,还有华为等等Android系统,特别是产品经理的魅族,一些特殊机型上是没有效果的,所以为了更保险,得再加一份权限申请,还有一点得提一下,那就是7.0上WindowManager.LayoutParams.TYPE_TOAST,悬浮窗只能持续一秒的时间,所以7.0不设这个type,谷歌爸爸最叼,7.0以上老老实实申请权限。

FloatActionController.java

public class FloatActionController {  private FloatActionController() {  }  public static FloatActionController getInstance() {    return LittleMonkProviderHolder.sInstance;  }  // 静态内部类  private static class LittleMonkProviderHolder {    private static final FloatActionController sInstance = new FloatActionController();  }  private FloatCallBack mCallLittleMonk;    public void startMonkServer(Context context) {    Intent intent = new Intent(context, FloatMonkService.class);    context.startService(intent);  }    public void stopMonkServer(Context context) {    Intent intent = new Intent(context, FloatMonkService.class);    context.stopService(intent);  }    public void registerCallLittleMonk(FloatCallBack callLittleMonk) {    mCallLittleMonk = callLittleMonk;  }    public void show() {    if (mCallLittleMonk == null) return;    mCallLittleMonk.show();  }    public void hide() {    if (mCallLittleMonk == null) return;    mCallLittleMonk.hide();  }}

这就是暴露出来的接口,按需添加,效果大概是这样的。

大概效果如下:

Android应用内悬浮窗的实现方案示例

Demo:代码地址感兴趣可以看看完整的。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程网。

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯