窗体的使用,悬浮窗,仿360手机卫士2
功能:在桌面上显示一个布局,可以随着手势拖动
主活动:
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); View layout = findViewById(R.id.btn); //绕着空间系的Z抽旋转 ObjectAnimator rotationZ = ObjectAnimator.ofFloat(layout,"rotation", 0,360); //绕着空间系的X抽旋转 ObjectAnimator rotationX = ObjectAnimator.ofFloat(layout,"rotationX", 0,360); //绕着空间系的Y抽旋转 ObjectAnimator rotationY = ObjectAnimator.ofFloat(layout,"rotationY", 0,360); rotationZ.setDuration(3000); rotationX.setDuration(3000); rotationY.setDuration(3000); rotationY.start(); rotationX.start(); rotationZ.start(); } /** * 单击事件 */ public void btnOnClick(View view){ startService(new Intent(this,FloatWindowService.class)); finish(); } }
服务类,用于控制悬浮窗的创建于销毁,需要注意状态栏的高度
/** * 在服务中创建悬浮窗体,这样能一直在后台运行 */ public class FloatWindowService extends Service implements OnTouchListener { /** * 用于在线程中创建或移除悬浮窗。 */ private Handler handler = new Handler(); /** * 定时器,定时进行检测当前应该创建还是移除悬浮窗。 */ private Timer timer; private WindowManager manager; private View view; private LayoutParams windowParams;//窗体显示的位置的参数 private int screenWidth; private int screenHeight; @Override public IBinder onBind(Intent intent) { return null; } @Override public int onStartCommand(Intent intent, int flags, int startId) { // 开启定时器,每隔0.5秒刷新一次 if (timer == null) { timer = new Timer(); timer.scheduleAtFixedRate(new RefreshTask(), 0, 500); } return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { super.onDestroy(); // Service被终止的同时也停止定时器继续运行 timer.cancel(); timer = null; } class RefreshTask extends TimerTask { @Override public void run() { // 当前界面是桌面,且没有悬浮窗显示,则创建悬浮窗。 if (isHome() && view == null) { handler.post(new Runnable() { @Override public void run() { createWindow(); } }); } // 当前界面不是桌面,且有悬浮窗显示,则移除悬浮窗。 else if (!isHome() && view!=null) { handler.post(new Runnable() { @Override public void run() { System.out.println("移除"); manager.removeView(view); view=null; } }); } } } /** * 创建一个大窗口显示在屏幕上,创建系统类型的窗体需要权限: * <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> */ public void createWindow(){ manager = (WindowManager)getSystemService(Context.WINDOW_SERVICE); screenWidth = manager.getDefaultDisplay().getWidth(); screenHeight = manager.getDefaultDisplay().getHeight(); view = View.inflate(this, R.layout.item_layout, null); windowParams = new LayoutParams(); //屏幕正中间, 注意:直接view.getWidth()会为0 int w = View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED);//标记为未指明的 int h = View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED); view.measure(w, h); int height =view.getMeasuredHeight(); int width =view.getMeasuredWidth();//这样才能得到这个布局的宽高 windowParams.x = screenWidth / 2 - width / 2; windowParams.y = screenHeight / 2 - height / 2; System.out.println("屏幕宽:"+screenWidth+",x="+ windowParams.x+",view宽=" + view.getMeasuredWidth()/ 2 ); windowParams.type = LayoutParams.TYPE_PHONE;//设置类型 windowParams.format = PixelFormat.RGBA_8888; windowParams.gravity = Gravity.LEFT | Gravity.TOP;//以左上角为参考 //当这个窗体不可获取焦点,才能点击屏幕外的东西,不然焦点都在窗体上,不能点击其他东西 bigWindowParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL | LayoutParams.FLAG_NOT_FOCUSABLE; //设置窗体的宽高,以左上角为参考 windowParams.width = WindowManager.LayoutParams.WRAP_CONTENT; windowParams.height =WindowManager.LayoutParams.WRAP_CONTENT; manager.addView(view, windowParams); System.out.println("创建了窗体"); view.setOnTouchListener(this);//为当前布局设置触摸事件,切记要让显示这个布局的窗体不可被触摸,不然总个屏幕都能响应这个触摸了 } /** * 判断当前界面是否是桌面 ,先获取桌面应用的程序包名,然后判断当前显示活动包名是否包含在内 */ private boolean isHome() { ActivityManager mActivityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); List<RunningTaskInfo> rti = mActivityManager.getRunningTasks(1); return getHomes().contains(rti.get(0).topActivity.getPackageName()); } /** * 获得属于桌面的应用的应用包名称 * @return 返回包含所有桌面应用的包名的字符串列表 */ private List<String> getHomes() { List<String> names = new ArrayList<String>(); PackageManager packageManager = this.getPackageManager(); Intent intent = new Intent(Intent.ACTION_MAIN); intent.addCategory(Intent.CATEGORY_HOME); List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); for (ResolveInfo ri : resolveInfo) { names.add(ri.activityInfo.packageName); //属于桌面的应用:com.android.launcher(启动器) // System.out.println("属于桌面的应用:"+ri.activityInfo.packageName); } return names; } private float firstX; private float firstY; @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: firstX = event.getRawX(); firstY = event.getRawY(); System.out.println("按下的坐标:"+firstX+","+firstY); break; case MotionEvent.ACTION_MOVE: float moveX = event.getRawX(); float moveY = event.getRawY(); windowParams.x = (int) (moveX - firstX) + windowParams.x; windowParams.y = (int) (moveY - firstY) + windowParams.y; //考虑边界问题 if(windowParams.x<0){ windowParams.x = 0; } if(windowParams.x > screenWidth - v.getWidth()){ windowParams.x = screenWidth - v.getWidth(); } if(windowParams.y< 0){ windowParams.y = 0;//起点没有包括状态栏,但是Y抽高度需要减去状态栏的高度 } if(windowParams.y > screenHeight - v.getHeight() - getStatusBarHeight()){ windowParams.y = screenHeight - v.getHeight()- getStatusBarHeight(); } manager.updateViewLayout(view, windowParams);//更新位置 firstX = moveX; firstY = moveY; break; case MotionEvent.ACTION_UP: System.out.println("抬起"); break; default: break; } return false; } /** * 用于获取状态栏的高度。 * @return 返回状态栏高度的像素值。 */ private int getStatusBarHeight() { int statusBarHeight = 0;//状态栏的高度,注意不是标题栏 try { Class<?> c = Class.forName("com.android.internal.R$dimen"); Object o = c.newInstance(); java.lang.reflect.Field field = c.getField("status_bar_height"); int x = (Integer) field.get(o); statusBarHeight = getResources().getDimensionPixelSize(x); } catch (Exception e) { e.printStackTrace(); } return statusBarHeight; } }
效果图: