应用上悬浮的快速启动菜单

最近在开发盒子上的一个Launcher,有一个悬浮菜单,觉得效果挺好,特意拿出来分析下~

悬浮菜单的效果用文字描述是:无论是进入在Launcher里还是退出Launcher进入到其它APP里,鼠标滑过右侧区域时显示菜单;鼠标滑离右侧区域时隐藏菜单。

开发中做弹出框的方式有很多种,有DialogPopUpWindowDialogFragmentActivity等分析之后这几种都是不能满足上述功能要求的,最后一种也就是后面提到的WindowsManagerAddView恰好就能满足~

 

开发这项功能时,很容易可以达到要么显示要么隐藏的效果,至于要达到鼠标滑过时显示滑离时隐藏的效果,个人还是思考了一两天也仔细查阅了WindowsManager的相关的资料的,它常用的API如下

WindowManager.LayoutParams params=newWindowManager.LayoutParams(int w,int h,int type,int flag,int format)

 

WindowsManager.addView()

WindowsManager.updateView()

WindowsManager.removeView()

 

添加View时需要的参数,wh可以忽略,主要说下typeflag两个参数

 

参数type常见的几种:

系统窗口。非应用程序创建。

public static final int FIRST_SYSTEM_WINDOW = 2000;

 

电话窗口。它用于电话交互(特别是呼入)。它置于所有应用程序之上,状态栏之下。

public static final int TYPE_PHONE = FIRST_SYSTEM_WINDOW+2;

 

系统提示。它总是出现在应用程序窗口之上。

public static final int TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW +3;

 

系统内部错误提示,显示于所有内容之上。

public static final int TYPE_SYSTEM_ERROR = FIRST_SYSTEM_WINDOW +10;

 

这里个人用的比较多的是第二种类型,View的位置在应用层之上,状态栏之下

 

参数flag常见的几种:

不许获得焦点。

public static final int FLAG_NOT_FOCUSABLE     = 0x00000008;

 

不接受触摸屏事件。

public static final int FLAG_NOT_TOUCHABLE     = 0x00000010;

 

当窗口可以获得焦点(没有设置 FLAG_NOT_FOCUSALBE 选项)时,仍然将窗口范围之外的点设备事件(鼠标、触摸屏)发送给后面的窗口处理。

否则它将独占所有的点设备事件,而不管它们是不是发生在窗口范围内。

public static final int FLAG_NOT_TOUCH_MODAL   = 0x00000020;   

  

反转FLAG_NOT_FOCUSABLE选项。

如果同时设置了FLAG_NOT_FOCUSABLE选项和本选项,窗口将能够与输入法交互,允许输入法窗口覆盖;

如果FLAG_NOT_FOCUSABLE没有设置而设置了本选项,窗口不能与输入法交互,可以覆盖输入法窗口。

public static final int FLAG_ALT_FOCUSABLE_IM = 0x00020000;

 

这里的Flag总体粗糙的来说分为可获取焦点的和不可获取焦点的两种

 

仔细看了几遍上面的两个参数后,开发这项功能的思路就有了:

需要添加两层View,第一层是背景层透明的,常显,不能获取焦点,窗口以外的键盘鼠标事件不处理,依旧分发到下层处理,主要是用于监听鼠标的滑过事件;第二层是菜单层,要获取焦点,主要是监听View的点击事件的,当鼠标滑过背景层时显示菜单层,当鼠标滑离菜单层时隐藏菜单层。


这里我是把这项功能写到了一个Service里面,Launcher收到开机广播之后,会启动这个服务,然后再在服务里去添加悬浮窗,具体代码如下:

public class LauncherService extends Service
{
    final static String TAG=LauncherService.class.getSimpleName();

    final static int FLAG_BACK=10000;
    final static int FLAG_HOME=10001;
    //这里的Handler可以不必管它 主要是用来处理菜单上的功能的
    Handler mHandler=new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg)
        {
            Log.i(TAG,"=======Callback()======="+msg.what);
            switch (msg.what)
            {
                case FLAG_BACK:               break;
                case FLAG_HOME:               break;
                default:break;
            }
            return false;
        }
    });

    @Override
    public void onCreate()
    {
        super.onCreate();
        Log.i(TAG,"=========onCreate()============");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId)
    {
        Log.i(TAG,"=========onStartCommand()============");
        showMenuBgView();
        return Service.START_STICKY;
    }      

    boolean menuViewFlag;//标记位 表示菜单层是显示还是隐藏
    WindowManager windowManager=null;
    View menuView=null;//菜单层
    View menuBgView=null;//背景层
     //显示背景层
    private void showMenuBgView()
    {
        Log.i(TAG,"=====showMenuBgView()========");
        if(windowManager==null)
        {
            windowManager=(WindowManager)getApplication().getSystemService(Context.WINDOW_SERVICE);
        }
        WindowManager.LayoutParams params=new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.TYPE_PHONE,
                WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
        params.gravity= Gravity.RIGHT|Gravity.CENTER_VERTICAL;
        menuBgView= LayoutInflater.from(getApplicationContext()).inflate(R.layout.launcher_menu_bg,null);
        menuBgView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v)
            {
                Log.i(TAG,"=====Windows====bg====onClick()====");
            }
        });

        menuBgView.findViewById(R.id.ll_root).setOnHoverListener(new View.OnHoverListener() {
            @Override
            public boolean onHover(View v, MotionEvent event)
            {
                Log.i(TAG,"=====Windows====bg====onHover()====");
                switch (event.getAction())
                {
                    case MotionEvent.ACTION_HOVER_ENTER:
                        Log.i(TAG,"=====Windows======bg====ACTION_HOVER_ENTER====");
                        showMenuView();
                        break;
                    case MotionEvent.ACTION_HOVER_MOVE:
                        break;
                    case MotionEvent.ACTION_HOVER_EXIT:
                        Log.i(TAG,"=====Windows=====bg=====ACTION_HOVER_EXIT====");
                        break;
                    case MotionEvent.ACTION_DOWN:
                        break;
                }
                return false;
            }
        });
        windowManager.addView(menuBgView,params);
        Log.i(TAG,"=====addMenuBgView========");
    }
    //显示菜单层
    private void showMenuView()
    {
        if(menuViewFlag)
        {
            Log.i(TAG,"======MenuView Already Show=======");
            return;
        }

        if(windowManager==null)
        {
            windowManager=(WindowManager)getApplication().getSystemService(Context.WINDOW_SERVICE);
        }
        WindowManager.LayoutParams params=new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.TYPE_PHONE+1,
                WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, PixelFormat.TRANSPARENT);
        params.gravity=Gravity.RIGHT|Gravity.CENTER_VERTICAL;

        menuView=LayoutInflater.from(getApplicationContext()).inflate(R.layout.launcher_menu,null);
        //addHoverListener(menuView.findViewById(R.id.ll_menu_back));
        menuView.findViewById(R.id.ll_menu_back).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v)
            {
                Log.i(TAG,"=====Windows=====back===onClick()====");
                try
                {
                    windowManager.removeView(menuView);
                    menuView=null;
                    menuViewFlag=false;
                }
                catch (Exception e)
                {
                    e.printStackTrace();
                }
                mHandler.sendEmptyMessage(FLAG_BACK);               
            }
        });

        addHoverListener(menuView.findViewById(R.id.ll_menu_home));
        menuView.findViewById(R.id.ll_menu_home).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v)
            {
                Log.i(TAG,"=====Windows=====home===onClick()====");
                mHandler.sendEmptyMessage(FLAG_HOME);              
            }
        });
        //addHoverListener(menuView.findViewById(R.id.ll_menu_key_1));
        //addHoverListener(menuView.findViewById(R.id.ll_menu_key_2));
        menuView.setOnKeyListener(new View.OnKeyListener() {
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event)
            {
                Log.i(TAG,"===========onKey============"+keyCode);
                switch (keyCode)
                {
                    case KeyEvent.KEYCODE_BACK:
                    case KeyEvent.KEYCODE_HOME:

                        break;
                    default:break;
                }
                return false;
            }
        });

        menuView.findViewById(R.id.ll_menu_content).setOnHoverListener(new View.OnHoverListener() {
            @Override
            public boolean onHover(View v, MotionEvent event)
            {
                Log.i(TAG,"=====Windows========onHover()====");
                switch (event.getAction())
                {
                    case MotionEvent.ACTION_HOVER_ENTER:
                        Log.i(TAG,"=====Windows========ACTION_HOVER_ENTER====");
                        break;
                    case MotionEvent.ACTION_HOVER_MOVE:

                        break;
                    case MotionEvent.ACTION_HOVER_EXIT:
                        Log.i(TAG,"=====Windows========ACTION_HOVER_EXIT====");

                        hiddenMenuView();

                        break;

                    case MotionEvent.ACTION_DOWN:

                        break;
                }
                return false;
            }
        });
        windowManager.addView(menuView,params);
        menuViewFlag=true;
        menuView.findViewById(R.id.ll_menu_back).requestFocus();
    }
    //给View追加一个鼠标滑过和滑离的特效
    private void addHoverListener(final View view)
    {
        view.setOnHoverListener(new View.OnHoverListener() {
            @Override
            public boolean onHover(View v, MotionEvent event)
            {
                switch (event.getAction())
                {
                    case MotionEvent.ACTION_HOVER_ENTER:
                        Log.i(TAG,"=====view========ACTION_HOVER_ENTER====");
                        view.findViewById(R.id.ll_menu_home).requestFocus();
                        break;
                    case MotionEvent.ACTION_HOVER_MOVE:
                        break;
                    case MotionEvent.ACTION_HOVER_EXIT:
                        Log.i(TAG,"=====view========ACTION_HOVER_EXIT====");
                        break;
                    case MotionEvent.ACTION_DOWN:
                        break;
                }
                return false;
            }
        });
    }
    //隐藏菜单层
    private void hiddenMenuView()
    {
        if(windowManager==null)
        {
            windowManager=(WindowManager)getApplication().getSystemService(Context.WINDOW_SERVICE);
        }

        try
        {
            if(menuView!=null)
            {
                windowManager.removeView(menuView);
                menuView=null;
                menuViewFlag=false;
            }
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }

    @Override
    public void onDestroy()
    {
        Log.i(TAG,"=========onDestroy()============");
        super.onDestroy();
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }
}

=============================题外扩展==============================
这些悬浮窗的效果和360手机安全卫士的加速悬浮球效果是非常类似的,都是它的延伸。另外细心的童鞋可能发现360的悬浮球是可以跟随着触摸手机屏的位置实时移动的,其实它内部的实现是监听了View的OnTouch()事件(当然咱们的开发都是基于机顶盒的这个事件用得就比较少了--)先获取到触屏的位置,再通过WindowsManager.updateView()的方法不断的更新球的位置参数做到的,感兴趣的童鞋私下可以试试咱们这里的悬浮窗的位置也是可以实时变化的~

附带上关键代码,代码中的变量需要自己定义--

view.setOnTouchListener(new OnTouchListener() {

    @Override
    public boolean onTouch(View v, MotionEvent event) {

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                x = event.getRawX();//距离屏幕原点的X轴的坐标
               y = event.getRawY();//距离屏幕原点的Y轴的坐标
               Log.i(TAG,"===RawX()======"+x+"====RawY()==="+y);
                mTouchStartX = event.getX();//距离父View左上顶点的X轴的坐标
                mTouchStartY = event.getY();//距离父View左上顶点的Y轴的坐标
               Log.i(TAG,"===x======"+mTouchStartX+"====y==="+mTouchStartY);
                startX=event.getRawX();
                startY=event.getRawY();
                break;
            case MotionEvent.ACTION_MOVE:
                x = event.getRawX();
                y = event.getRawY();

                updatePosition();

                break;
            case MotionEvent.ACTION_UP:
                if(startX==x && startY==y)
                {
                    Log.i(TAG,"======click=====");
                }             
                break;
        }
        return true;
    }
});
//更新View在屏幕上的位置

public void updatePosition() {
    mWManParams.x = (int) (x - mTouchStartX);
    mWManParams.y = (int) (y - mTouchStartY);
    mWManger.updateViewLayout(view, mWManParams);
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android 悬浮窗菜单,可在launcher或app中使用。示例代码:@Override public void onCreate() {     super.onCreate();     mFloatMenu = new FloatMenu.Builder(this)             .floatLoader(R.drawable.yw_anim_background)             .floatLogo(R.drawable.yw_image_float_logo)             .addMenuItem(android.R.color.transparent, R.drawable.yw_menu_account, Const.MENU_ITEMS[0], android.R.color.black, this)             .addMenuItem(android.R.color.transparent, R.drawable.yw_menu_favour, Const.MENU_ITEMS[1], android.R.color.black, this)             .addMenuItem(android.R.color.transparent, R.drawable.yw_menu_fb, Const.MENU_ITEMS[2], android.R.color.black, this)             .addMenuItem(android.R.color.transparent, R.drawable.yw_menu_msg, Const.MENU_ITEMS[3], android.R.color.black, this)             .addMenuItem(android.R.color.transparent, R.drawable.yw_menu_close, Const.MENU_ITEMS[4], android.R.color.black, this)             .menuBackground(R.drawable.yw_menu_bg)             .onMenuActionListner(this)             .build();     mFloatMenu.show(); } public void showFloat() {     if (mFloatMenu != null)         mFloatMenu.show(); } public void hideFloat() {     if (mFloatMenu != null) {         mFloatMenu.hide();     } } public void destroyFloat() {     hideFloat();     if (mFloatMenu != null) {         mFloatMenu.destroy();     }     mFloatMenu = null; }  private void showRed() {     if (!hasNewMsg) {         mFloatMenu.changeLogo(R.drawable.yw_image_float_logo, R.drawable.yw_menu_msg, 3);     } else {         mFloatMenu.changeLogo(R.drawable.yw_image_float_logo_red, R.drawable.yw_menu_msg_red, 3);     } }
### 回答1: 在 Unity 中创建安卓悬浮窗需要使用 Unity 的 Android 库和一些 Java 代码。 首先,在 Unity 中添加 Android 库。在 Unity 中,打开“Edit > Project Settings > Player”,然后选择“Other Settings”选项卡。在“Configuration”下,将“Scripting Runtime Version”设置为“.NET 4.x Equivalent”。然后,在“Configuration”下,将“Api Compatibility Level”设置为“.NET 4.x”。 接下来,需要创建一个 Java 类来控制悬浮窗的行为。将以下代码保存为“FloatingWindow.java”文件: ``` import android.app.Service; import android.content.Intent; import android.graphics.PixelFormat; import android.os.IBinder; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; import android.widget.ImageView; public class FloatingWindow extends Service { private WindowManager windowManager; private ImageView floatingWindow; @Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { super.onCreate(); floatingWindow = new ImageView(this); floatingWindow.setImageResource(R.drawable.floating_window_image); windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); final WindowManager.LayoutParams params = new WindowManager.LayoutParams( WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.TYPE_PHONE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT); params.gravity = Gravity.TOP | Gravity.LEFT; params.x = 0; params.y = 100; floatingWindow.setOnTouchListener(new View.OnTouchListener() { private int initialX; private int initialY; private float initialTouchX; private float initialTouchY; @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: initialX = params.x; initialY = params.y; initialTouchX = event.getRawX(); initialTouchY = event.getRawY(); return true; case MotionEvent.ACTION_UP: return true; ### 回答2: 使用Unity编写安卓应用悬浮窗可以通过以下步骤完成: 1. 配置Unity开发环境:确保已经安装了Unity开发环境,并且已经在Android设备上安装了Unity Remote应用程序。 2. 创建新的Unity项目:在Unity中创建新的项目,并选择合适的设置。确保选择了Android作为目标平台。 3. 创建悬浮窗UI:使用Unity的UI工具创建悬浮窗所需的UI元素,例如按钮、文本框等。可以根据需求自定义悬浮窗的外观和交互方式。 4. 设置悬浮窗位置和大小:可以使用Unity的RectTransform组件来设置悬浮窗的位置和大小。使用屏幕坐标系统来确定悬浮窗在屏幕上的位置。 5. 编写脚本:使用C#或Unity的脚本编写悬浮窗的逻辑。例如,可以添加按钮点击事件的监听器,实现点击按钮弹出菜单功能。 6. 打包应用程序:在Unity中进行构建,并选择Android为目标平台。导出apk文件并安装到Android设备上。 7. 启动悬浮窗:在Android设备上运行应用程序,并确保Unity Remote应用程序已连接。在Unity中运行悬浮窗的场景,并在设备上看到悬浮窗正常显示。 通过以上步骤,可以使用Unity编写一个安卓应用悬浮窗悬浮窗可以实现自定义的外观和交互方式,提供给用户更好的操作体验。同时,使用Unity开发还可以方便地进行调试和迭代,提高开发效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值