您的位置:首页 > 移动开发 > Android开发

Android Window 二 可移动悬浮窗口 WindowManager

2017-07-21 15:19 351 查看
一、效果演示



二、如何创建悬浮窗口

     比较简单,主要是使用WindowManager API,以下是使用方法

[java] view
plain copy

 print?

@Override  

protected void onCreate(Bundle savedInstanceState) {  

    super.onCreate(savedInstanceState);  

    setContentView(R.layout. activity_main);  

     

    // 获取Service  

    WindowManager mWindowManager = (WindowManager) getSystemService("window" );  

     

    ImageView imageView = new ImageView(this);  

    imageView.setImageResource(R.drawable. ic_launcher);  

     

    // 设置窗口类型,一共有三种Application windows, Sub-windows, System windows  

    // API中以TYPE_开头的常量有23个  

    mWindowParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT ;  

    // 设置期望的bitmap格式  

    mWindowParams.format = PixelFormat.RGBA_8888;  

     

    // 以下属性在Layout Params中常见重力、坐标,宽高  

    mWindowParams.gravity = Gravity.LEFT | Gravity. TOP;  

    mWindowParams.x = 100;  

    mWindowParams.y = 100;  

     

    mWindowParams .width = WindowManager.LayoutParams. WRAP_CONTENT;  

    mWindowParams .height = WindowManager.LayoutParams. WRAP_CONTENT;  

     

    // 添加指定视图  

    mWindowManager.addView(imageView, mWindowParams);  

}  

需要添加权限

[java] view
plain copy

 print?

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />  

如果没有以上权限,会出现如下异常:

[java] view
plain copy

 print?

java.lang.RuntimeException: Unable to start activity ComponentInfo{loveworld.floatview/loveworld.floatview.MainActivity}: android.view.WindowManager$BadTokenException: Unable to add window android.view.ViewRoot$W@40513b60 -- permission denied for this window type  

     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1768)  

     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1784)  

     at android.app.ActivityThread.access$1500(ActivityThread.java:123)  

     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:939)  

     at android.os.Handler.dispatchMessage(Handler.java:99)  

     at android.os.Looper.loop(Looper.java:130)  

     at android.app.ActivityThread.main(ActivityThread.java:3835)  

     at java.lang.reflect.Method.invokeNative(Native Method)  

     at java.lang.reflect.Method.invoke(Method.java:507)  

     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:847)  

     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:605)  

     at dalvik.system.NativeStart.main(Native Method)  

Caused by: android.view.WindowManager$BadTokenException: Unable to add window android.view.ViewRoot$W@40513b60 -- permission denied for this window type  

     at android.view.ViewRoot.setView(ViewRoot.java:552)  

     at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:177)  

     at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:91)  

     at android.view.Window$LocalWindowManager.addView(Window.java:465)  

     at loveworld.floatview.MainActivity.onCreate(MainActivity.java:55)  

     at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)  

     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1722)  

     ... 11 more  

三、如何使悬浮窗口可移动

     窗口随手指移动需要获取先获取手指的点击事件,而点击事件通常都是通过覆写视图的onTouchEvent获得,当前例子也看不到悬浮窗口仅能看到其中包含的图片,所以选择在ImageView获取并处理触摸事件,获取手指轨迹X,Y坐标,更新窗口位置达到随手指移动效果。

     首先自定义ImageView

[java] view
plain copy

 print?

public class MoveImageView extends ImageView {  

  

    public MoveImageView(Context context) {  

        super(context);  

    }  

  

    public MoveImageView(Context context, AttributeSet attrs) {  

        super(context, attrs);  

    }  

  

    public MoveImageView(Context context, AttributeSet attrs, int defStyle) {  

        super(context, attrs, defStyle);  

    }  

  

      

}  

覆写onTouchEvent方法,用于监听图片视图的触摸事件

[java] view
plain copy

 print?

@Override  

public boolean onTouchEvent(MotionEvent event) {  

  

    int titleHeight = 0;  

    if (mListener != null) {  

        titleHeight = mListener.getTitleHeight();  

    }  

      

    // 当前值以屏幕左上角为原点  

    mRawX = event.getRawX();  

    mRawY = event.getRawY() - titleHeight;  

      

    final int action = event.getAction();  

      

    switch (action) {  

    case MotionEvent.ACTION_DOWN:  

        // 以当前父视图左上角为原点  

        mStartX = event.getX();  

        mStartY = event.getY();  

          

        break;  

  

    case MotionEvent.ACTION_MOVE:  

        updateWindowPosition();  

        break;  

          

    case MotionEvent.ACTION_UP:  

        updateWindowPosition();  

        break;    

    }  

      

    // 消耗触摸事件  

    return true;  

}  

最后通过更新窗口X,Y轴参数达到移动效果

[java] view
plain copy

 print?

/** 

 * 更新窗口参数,控制浮动窗口移动 

 */  

private void updateWindowPosition() {  

    if (mListener != null) {  

        // 更新坐标  

        LayoutParams layoutParams = mListener.getLayoutParams();  

        layoutParams.x = (int)(mRawX - mStartX);  

        layoutParams.y = (int)(mRawY - mStartY);  

          

        // 使参数生效  

        mWindowManager.updateViewLayout(this, layoutParams);  

    }  

}  

    其中当前自定义视图要用到两个变量,只有在Activity中可以获取到,这就涉及到如何在自定义视图中获取到这两个变量。 可以把变量保存到Application中,也可以使用单例对象在Activity中初始化并赋值在自定义视图中获取,当前是使用接口回调方法,在Activity中实现接口通过这种方法使自定义视图获取变量,感觉这种方式比较好,不用考虑把变量放到Application涉及到的生命周期释放等问题,也不用考虑单例促使对象的生命周期一样很长。

    先来看看在自定义视图中如何定义

[java] view
plain copy

 print?

/** 

 * 设置监听器,用于向当前ImageView传递参数 

 *  

 * @param listener 

 */  

public void setFloatViewParamsListener(FloatViewParamsListener listener) {  

    mListener = listener;  

}  

  

/** 

 *  当前视图用于获取参数 

 */  

public interface FloatViewParamsListener {  

      

    /** 

     * 获取标题栏高度 

     *      因为需要通过Window对象获取,所以使用此办法 

     *  

     * @return 

     */  

    public int getTitleHeight();  

      

      

    /** 

     * 获取当前WindowManager.LayoutParams 对象 

     *  

     * @return 

     */  

    public WindowManager.LayoutParams getLayoutParams();  

}  

    在Activity中实现接口并设置

[java] view
plain copy

 print?

private class FloatViewListener implements FloatViewParamsListener {  

    @Override  

    public int getTitleHeight() {  

        // 获取状态栏高度。不能在onCreate回调方法中获取  

        Rect frame = new Rect();  

        getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);  

        int statusBarHeight = frame.top;   

          

        int contentTop = getWindow().findViewById(Window.ID_ANDROID_CONTENT).getTop();  

        int titleBarHeight = contentTop - statusBarHeight;  

          

        return titleBarHeight;  

    }  

  

    @Override  

    public android.view.WindowManager.LayoutParams getLayoutParams() {  

        return mWindowParams;  

    }     

}  

记得在退出Activity时清理悬浮窗口

[java] view
plain copy

 print?

@Override  

public void onDestroy(){  

    super.onDestroy();  

    //  删除视图  

    mWindowManager.removeView(mImageView);  

}  

四、参考资料

WindowManager ---implements---> ViewManager

WindowManager  API

WindowManager.LayoutParams type

Android中可自由移动悬浮窗口的Demo

五、 下载源码

点击下载

待补充 : 参数 mWindowParams.flags
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息