Android Window 二 可移动悬浮窗口 WindowManager



一、有图有真相



二、如何创建悬浮窗口

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

    @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.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

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方法,用于监听图片视图的触摸事件

	@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轴参数达到移动效果

	/**
	 * 更新窗口参数,控制浮动窗口移动
	 */
	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涉及到的生命周期释放等问题,也不用考虑单例促使对象的生命周期一样很长。

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

	/**
	 * 设置监听器,用于向当前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中实现接口并设置

	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时清理悬浮窗口

    @Override
    public void onDestroy(){
    	super.onDestroy();
    	//  删除视图
    	mWindowManager.removeView(mImageView);
    }



四、参考资料

WindowManager ---implements---> ViewManager

WindowManager  API


WindowManager.LayoutParams type

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


五、 下载源码

点击下载


待补充 : 参数 mWindowParams.flags


转载请注明出处:http://blog.csdn.net/love_world_/article/details/8785835



六、View.getWindowToken()方法

Class View
public IBinder getWindowToken ()

Added in API level 1
Retrieve a unique token identifying the window this view is attached to.

Returns
Return the window's token for use in WindowManager.LayoutParams.token.

WindowManager.LayoutParams.token = View.getWindowToke();  设置附着的对象Token值


七、创建问题
可以创建应用窗口与系统窗口,前者是仅能创建,后者Activity与Service都可创建Activity
在小米手机使用Service或者非Activity的Context无法启动系统窗口


2013-04-11   更新需要权限,退出时清理,待补充参数

2015-04-26 更新六、七


你可能感兴趣的:(Android,窗口与View管理)