小动画之“悬浮窗”(WindowManager)

用WindowManager 实现悬浮窗小动画

  • 前言
  • Window 与 WindowManager
  • 实例
  • 重写 onTouch() 方法

前言

先看效果图,添加悬浮窗,悬浮窗可以移动,并且可以用于桌面,这里用到了 WindowManager 以及 GestureDetector;没有对点击事件进行处理;

Window 与 WindowManager

  1. 伪代码使用WindowManager 来添加一个 Window 。

    WindowManager manager= (WindowManager) getSystemService(Context .WINDOWSERVICE);
    WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(width,height,type,flags,format);
    manager . addView(btn, layoutParams) ;
    
  2. flag 参数意义

    flags 用来控制Window 的显示特性;

    public static final int FLAG NOT FOCUSABLE = 0x00000008;
    表示此 Window 不需要获取焦点,不接收各种输入事件,此标记会同时启用 FLAG_NOT_TOUCH MODAL,最终事件会直接传递给下层具有焦点的 Window 。

    public stat 工c final int FLAG NOT TOUCH MODAL = 0x00000020 ;
    自己 Window 区域内的事件自己处理:自己 Window 区域外的事件传递给底层Window 处理。一般这个选项会默认开启, 否则其他Window 无法收到事件。

    public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;
    可以让此Window 显示在锁屏上。

  3. type 参数意义

    type 参数表示Window 的类型。Window 有三种类型:应用Window 、子Window 和系统Window 。

    应用Window 对应着一个Activity 。子Window 不能独立存在,它需要附属在特定的父Window 中,比如Dialog 就是一个子Window 。

    系统Window 是需要声明权限才能创建的,比如Toast 和系统状态栏都是系统Window 。
    Window 是分层的,层级大的Window 会覆盖在层级小的Window 上面。

    1. 应用Window 的层级范围:1 ~99
    2. 子Window 的层级范围:1000~ 1999
    3. 系统Window 的层级范围:2000~29990

    如果想让 Window 置于顶层,则采用较大的层级即可;如果
    是系统类型的 Window ,则需要在 AndroidMenifest.xml 中配置如下权限声明,否则会报权限不足的错误。

    <uses-permission android:name="android.permission.ACTION_MANAGE_OVERLAY_PERMISSION" />
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
    <uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" />
    
  4. Window Manager 提供的常用的只有三个方法,即添加View 、更新 VieW 和删除 View 。这三个方法定义在ViewManager 中,而Window Manager 继承自 ViewManager 。

    public interface WindowManager extends ViewManager {
    	public interface ViewManager {
    		public void addView(View view, ViewGroup.LayoutParams params) ;
    		public void updateViewLayout(View view , ViewGroup . LayoutParams params) ;
    		public void removeView(View view) ;
    		}
    }
    

实例

  1. 添加布局,两个 button,添加、移除;

  2. 在 onCreate() 方法中初始化:

    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_float_windows);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            //参考:https://blog.csdn.net/zxm317122667/article/details/52685492
            if (!Settings.canDrawOverlays(this)) {
                Intent mIntent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
                mIntent.setData(Uri.parse("package:" + getPackageName()));
                mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                startActivityForResult(mIntent, REQUESTCODE);
            } else {
                initView();
            }
        } else {
            initView();
        }
    }
    
    private void initView() {
    //初始化布局,并添加点击事件监听
    ……
    mWindowManager = (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
    }
    

    注意:

    1. 在 SDKAPI ≥ 23时,需要在代码中动态判断版本,并通过 Settings.ACTION_MANAGE_OVERLAY_PERMISSION 申请;
    2. 通过 Settings.canDrawOverlays(this) 判断设备是否可以显示,避免每次打开都要去验证权限;该方法自参考:https://blog.csdn.net/zxm317122667/article/details/52685492;
  3. 添加点击事件

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_add_float_win:
                if (iconCount > 0) {
                    //只填加一个
                    break;
                }
                mImageView = new ImageView(this);
                mImageView.setBackgroundResource(R.drawable.icon52);
                //设置图片尺寸:WindowManager.LayoutParams.WRAP_CONTENT,
                //WindowManager.LayoutParams.WRAP_CONTENT,
                mLayoutParams = new WindowManager.LayoutParams(
                        150, 150, 2003,
                        WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
                                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
                                WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED,
                        PixelFormat.TRANSPARENT);
                mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
                mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
                mLayoutParams.x = 200;
                mLayoutParams.y = 800;
                mWindowManager.addView(mImageView, mLayoutParams);
                mImageView.setOnTouchListener(this);
                iconCount++;
                break;
            case R.id.btn_remove_float_win:
                if (iconCount < 1) {
                    break;
                }
                mWindowManager.removeViewImmediate(mImageView);
                iconCount--;
                break;
            default:
                break;
        }
    }
    

注意:

  1. 这里添加了 mImageView.setOnTouchListener(this); 应将 Activity 实现 View.OnTouchListener;需要重写 onTouch() 方法;
  2. 在添加 Window 时,其实我们只是将 ImageView 利用 WindowManager 的
    LayoutParams 添加到 Window Manager 中,但 Window 只是一个虚拟概念,真正添加到Window Manager 中的其实是 View 。

重写 onTouch() 方法

@Override
public boolean onTouch(View v, MotionEvent event) {
    int rawX = (int) event.getRawX();
    int rawY = (int) event.getRawY();
    switch (event.getAction()) {
        case MotionEvent.ACTION_MOVE:
            mLayoutParams.x = rawX - mImageView.getWidth() / 2;
            mLayoutParams.y = rawY - mImageView.getHeight();
            mWindowManager.updateViewLayout(mImageView, mLayoutParams);
            break;
        default:
            break;
    }
    return false;
}

注:这里让 ImageView 随手指移动;使用了 如下代码;不要问我为什么上面减一半下面减去整个,因为不这样会出现不同步现象;别问,问就不会……

mLayoutParams.x = rawX - mImageView.getWidth() / 2;
mLayoutParams.y = rawY - mImageView.getHeight();

最后,附上悬浮窗的原图:
小动画之“悬浮窗”(WindowManager)_第1张图片

你可能感兴趣的:(Android学习总结,java,android,安卓)