一、有图有真相
二、如何创建悬浮窗口
比较简单,主要是使用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);
}
}
@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;
}
/**
* 更新窗口参数,控制浮动窗口移动
*/
private void updateWindowPosition() {
if (mListener != null) {
// 更新坐标
LayoutParams layoutParams = mListener.getLayoutParams();
layoutParams.x = (int)(mRawX - mStartX);
layoutParams.y = (int)(mRawY - mStartY);
// 使参数生效
mWindowManager.updateViewLayout(this, layoutParams);
}
}
先来看看在自定义视图中如何定义
/**
* 设置监听器,用于向当前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();
}
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 API
五、 下载源码
点击下载
待补充 : 参数 mWindowParams.flags
转载请注明出处:http://blog.csdn.net/love_world_/article/details/8785835
六、View.getWindowToken()方法
2013-04-11 更新需要权限,退出时清理,待补充参数
2015-04-26 更新六、七