Android 悬浮窗口
一.创建悬浮窗口步骤
1.实现一个ViewGroup类,作为悬浮窗口的界面类,以便在里面重写onInterceptTouchEvent和onTouchEvent方法,实现移动界面的目的. 在本例中实现了一个FloatLayer类,可以作为通用的类,使用时需要传入WindowManager对象以实现移动窗口.// FloatLayer ~package com.example.hellofloatingwnd;import static com.ahai.util.DebugMessage.d;import android.content.Context;import android.graphics.PixelFormat;import android.util.AttributeSet;import android.view.Gravity;import android.view.MotionEvent;import android.view.WindowManager;import android.widget.RelativeLayout;public class FloatLayer extends RelativeLayout { // flags: 须设置成 FLAG_NOT_FOCUSABLE, 否则悬浮窗口下面的窗口不能取得焦点, 无法响应触摸事件 // type: 值低的窗口在值高的下层,相同的 type值,后创建的窗口显示在先创建的窗口上面. // 对应的type需要相应的权限,否则会报异常 BadTokenException. // 对于权限 android.permission.SYSTEM_ALERT_WINDOW 可使用以下几个值: // TYPE_PHONE, TYPE_SYSTEM_ALERT, TYPE_TOAST, TYPE_SYSTEM_OVERLAY // 其中 TYPE_TOAST, TYPE_SYSTEM_OVERLAY 不能响应触摸事件 public static class FloatLayoutParams extends WindowManager.LayoutParams { public FloatLayoutParams() { super(TYPE_PHONE, FLAG_NOT_FOCUSABLE, PixelFormat.RGBA_8888); gravity = Gravity.LEFT | Gravity.TOP; } public FloatLayoutParams(int type) { super(type, FLAG_NOT_FOCUSABLE, PixelFormat.RGBA_8888); gravity = Gravity.LEFT | Gravity.TOP; } public FloatLayoutParams(int xpos, int ypos) { super(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, xpos, ypos, TYPE_PHONE, FLAG_NOT_FOCUSABLE, PixelFormat.RGBA_8888); gravity = Gravity.LEFT | Gravity.TOP; } public FloatLayoutParams(int xpos, int ypos, int type) { super(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, xpos, ypos, type, FLAG_NOT_FOCUSABLE, PixelFormat.RGBA_8888); gravity = Gravity.LEFT | Gravity.TOP; } } private WindowManager mWindowManager; private int mStatusBarHeight; private int mMoveX; private int mMoveY; public FloatLayer(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); mStatusBarHeight = getStatusBarHeight(); } public FloatLayer(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mStatusBarHeight = getStatusBarHeight(); } public FloatLayer(Context context, AttributeSet attrs) { super(context, attrs); mStatusBarHeight = getStatusBarHeight(); } public FloatLayer(Context context) { super(context); mStatusBarHeight = getStatusBarHeight(); } public void setWindowManager(WindowManager windowManager) { mWindowManager = windowManager; } /** 取得系统状态栏的高度 */ private int getStatusBarHeight() { int statusBarHeight = 0; int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android"); if (resourceId > 0) { statusBarHeight = getResources().getDimensionPixelSize(resourceId); } // d("statusBarHeight=" + statusBarHeight); return statusBarHeight; } @Override public boolean onInterceptTouchEvent(MotionEvent event) { final int action = event.getAction(); if (action == MotionEvent.ACTION_MOVE) { if (handleMoveEvent(event)) return true; } else if (action == MotionEvent.ACTION_DOWN) { mMoveX = (int) event.getRawX(); mMoveY = (int) event.getRawY(); } return super.onInterceptTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { final int action = event.getAction(); if (action == MotionEvent.ACTION_MOVE) { if (handleMoveEvent(event)) return true; } else if (action == MotionEvent.ACTION_DOWN) { mMoveX = (int) event.getRawX(); mMoveY = (int) event.getRawY(); } return super.onTouchEvent(event); } private boolean handleMoveEvent(MotionEvent event) { try { if (mWindowManager != null) { // 通过以下消息可知getLayoutParams得到的对象即为 addView 传入的 LayoutParams 对象 // d("class:" + getLayoutParams().getClass()); final int x = (int) event.getRawX(); final int y = (int) event.getRawY(); int[] location = new int[2]; getLocationOnScreen(location); FloatLayoutParams layoutParams = (FloatLayoutParams) getLayoutParams(); layoutParams.x = location[0] + (x - mMoveX); layoutParams.y = location[1] + (y - mMoveY) - mStatusBarHeight; mWindowManager.updateViewLayout(this, layoutParams); mMoveX = x; mMoveY = y; return true; } } catch (Exception e) { d("", e); } return false; }}
3.取得WindowManager对象. 在 Activity 中可以通过以下方法取得, 其中前面的3个方法实际取得的是当前Activity的应用程序窗口对象,在Activity销毁等情况下, WindowManager对象也就不存在了,需要将悬浮窗口移除,否则会报错. WindowManager windowManager; windowManager = getWindow().getWindowManager(); windowManager = getWindowManager(); windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); windowManager = (WindowManager) getApplication().getSystemService(WINDOW_SERVICE); 在 Service 中, 以下两个方法均可, 在 onDestory 中将悬浮窗口移除即可. windowManager = (WindowManager) getApplication().getSystemService(WINDOW_SERVICE); windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); 4.创建View并显示. 通过以下两个行代码完成: mFloatView = (FloatLayer) inflater.inflate(R.layout.float_layout, null); windowManager.addView(mFloatView, layoutParams); 其中layoutParams是实现悬浮窗口的关键,窗口的配置及移动都通过其指定. LayoutParams需使用WindowManager.LayoutParams对象或继承自该类重写. 移动窗口通过下面的代码实现: mWindowManager.updateViewLayout(mFloatView, layoutParams); layoutParams最重要的两个参数是flags和type: flags: 须设置成 FLAG_NOT_FOCUSABLE, 否则悬浮窗口下面的窗口不能取得焦点,不能响应触摸事件. type: 值低的窗口在值高的下层,相同的 type值,后创建的窗口显示在先创建的窗口上面. 对应的type需要相应的权限,否则会报异常 BadTokenException. 对于权限 android.permission.SYSTEM_ALERT_WINDOW, type可使用以下几个值: TYPE_PHONE, TYPE_SYSTEM_ALERT, TYPE_TOAST, TYPE_SYSTEM_OVERLAY 其中 TYPE_TOAST, TYPE_SYSTEM_OVERLAY 不能响应触摸事件
在Service中实现
package com.example.hellofloatingwnd;import android.app.Application;import android.app.Service;import android.content.Intent;import android.os.IBinder;import android.view.LayoutInflater;import android.view.View;import android.view.View.OnClickListener;import android.view.WindowManager;import android.widget.ImageButton;import android.widget.Toast;public class FloatingService extends Service { private FloatLayer mFloatView; private WindowManager mWindowManager; private ImageButton mBtnHide; @Override public void onCreate() { super.onCreate(); Application app = getApplication(); mWindowManager = (WindowManager) app.getSystemService(WINDOW_SERVICE); LayoutInflater inflater = LayoutInflater.from(app); mFloatView = (FloatLayer) inflater.inflate(R.layout.float_layout, null); mFloatView.setWindowManager(mWindowManager); mBtnHide = (ImageButton) mFloatView.findViewById(R.id.mBtnHide); mBtnHide.setOnClickListener(mClickListener); FloatLayer.FloatLayoutParams layoutParams; layoutParams = new FloatLayer.FloatLayoutParams(10, 100); mWindowManager.addView(mFloatView, layoutParams); } @Override public void onDestroy() { super.onDestroy(); if (mFloatView != null) { mWindowManager.removeView(mFloatView); mFloatView = null; } } @Override public IBinder onBind(Intent arg0) { return null; } private OnClickListener mClickListener = new OnClickListener() { @Override public void onClick(View view) { if (view.getId() == R.id.mBtnHide) { Toast.makeText(getApplicationContext(), "on float button clicked.", Toast.LENGTH_SHORT).show(); FloatingService.this.stopSelf(); } } };}
在Activity中实现
package com.example.hellofloatingwnd;import android.app.Activity;import android.app.Application;import android.content.Intent;import android.os.Bundle;import android.view.LayoutInflater;import android.view.View;import android.view.View.OnClickListener;import android.view.WindowManager;import android.widget.Button;import android.widget.TextView;public class MainActivity extends Activity { private Button mBtnStart; private Button mBtnStop; private FloatLayer mFloatView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mBtnStart = (Button) findViewById(R.id.mBtnStart); mBtnStop = (Button) findViewById(R.id.mBtnStop); mBtnStart.setOnClickListener(mClickListener); mBtnStop.setOnClickListener(mClickListener); WindowManager windowManager; // windowManager = getWindow().getWindowManager(); windowManager = getWindowManager(); // windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); // windowManager = (WindowManager) // getApplication().getSystemService(WINDOW_SERVICE); Application application = getApplication(); LayoutInflater inflater = LayoutInflater.from(application); mFloatView = (FloatLayer) inflater.inflate(R.layout.float_layout, null); mFloatView.setWindowManager(windowManager); TextView textView = (TextView) mFloatView.findViewById(R.id.mTvHello); textView.setText("This create by activity."); FloatLayer.FloatLayoutParams layoutParams; layoutParams = new FloatLayer.FloatLayoutParams(50, 200); windowManager.addView(mFloatView, layoutParams); } @Override protected void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); if (mFloatView != null) { WindowManager windowManager = getWindow().getWindowManager(); windowManager.removeView(mFloatView); mFloatView = null; } } private OnClickListener mClickListener = new OnClickListener() { @Override public void onClick(View view) { if (view.getId() == R.id.mBtnStart) { Intent intent = new Intent(MainActivity.this, FloatingService.class); startService(intent); } else if (view.getId() == R.id.mBtnStop) { Intent intent = new Intent(MainActivity.this, FloatingService.class); stopService(intent); } } };}
5.在AndroidManifest.xml文件中添加权限,在layoutParams中配置不同的type类型需要不同的权限. <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />