[关闭]
@coder-pig 2015-11-12T15:13:58.000000Z 字数 7422 阅读 1897

Android基础入门教程——10.7 WindowManager(窗口管理服务)

Android基础入门教程


本节引言:

本节给大家带来的Android给我们提供的系统服务中的——WindowManager(窗口管理服务),
它是显示View的最底层,Toast,Activity,Dialog的底层都用到了这个WindowManager,
他是全局的!该类的核心无非:调用addView,removeView,updateViewLayout这几个方法
来显示View以及通过WindowManager.LayoutParams这个API来设置相关的属性!
本节我们就来探讨下这个WindowManager在实际开发中的一些应用实例吧~
官方API文档:WindowManager


1.WindowManager的一些概念:

1)WindowManager介绍

Android为我们提供的用于与窗口管理器进行交互的一个API!我们都知道App的界面都是
由一个个的Acitivty组成,而Activity又由View组成,当我们想显示一个界面的时候,
第一时间想起的是:Activity,对吧?又或者是Dialog和Toast。
但是有些情况下,前面这三者可能满足不了我们的需求,比如我们仅仅是一个简单的显示
用Activity显得有点多余了,而Dialog又需要Context对象,Toast又不可以点击...
对于以上的情况我们可以利用WindowManager这个东东添加View到屏幕上,
或者从屏幕上移除View!他就是管理Android窗口机制的一个接口,显示View的最底层!


2)如何获得WindowManager实例

获得WindowManager对象:

  1. WindowManager wManager = getApplicationContext().getSystemService(Context. WINDOW_ SERVICE);

获得WindowManager.LayoutParams对象,为后续操作做准备

  1. WindowManager.LayoutParams wmParams=new WindowManager.LayoutParams();

2.WindowManager使用实例:

实例1:获取屏幕宽高

在Android 4.2前我们可以用下述方法获得屏幕宽高:

  1. public static int[] getScreenHW(Context context) {
  2. WindowManager manager = (WindowManager)context
  3. .getSystemService(Context.WINDOW_SERVICE);
  4. Display display = manager.getDefaultDisplay();
  5. int width = display.getWidth();
  6. int height = display.getHeight();
  7. int[] HW = new int[] { width, height };
  8. return HW;
  9. }

而上述的方法在Android 4.2以后就过时了,我们可以用另一种方法获得屏幕宽高:

  1. public static int[] getScreenHW2(Context context) {
  2. WindowManager manager = (WindowManager) context.
  3. getSystemService(Context.WINDOW_SERVICE);
  4. DisplayMetrics dm = new DisplayMetrics();
  5. manager.getDefaultDisplay().getMetrics(dm);
  6. int width = dm.widthPixels;
  7. int height = dm.heightPixels;
  8. int[] HW = new int[] { width, height };
  9. return HW;
  10. }

然后我们可以再另外写两个获取宽以及高的方法,这里以第二种获得屏幕宽高为例:

  1. public static int getScreenW(Context context) {
  2. return getScreenHW2(context)[0];
  3. }
  4. public static int getScreenH(Context context) {
  5. return getScreenHW2(context)[1];
  6. }

当然,假如你不另外写一个工具类的话,你可以直接直接获取,比如:

  1. public class MainActivity extends AppCompatActivity {
  2. @Override
  3. protected void onCreate(Bundle savedInstanceState) {
  4. super.onCreate(savedInstanceState);
  5. setContentView(R.layout.activity_main);
  6. WindowManager wManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
  7. DisplayMetrics dm = new DisplayMetrics();
  8. wManager.getDefaultDisplay().getMetrics(dm);
  9. Toast.makeText(MainActivity.this, "当前手机的屏幕宽高:" + dm.widthPixels + "*" +
  10. dm.heightPixels, Toast.LENGTH_SHORT).show();
  11. }
  12. }

运行结果


实例2:设置窗口全屏显示

  1. getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
  2. WindowManager.LayoutParams.FLAG_FULLSCREEN);
  3. getSupportActionBar().hide();

运行结果


实例3:保持屏幕常亮

  1. public void setKeepScreenOn(Activity activity,boolean keepScreenOn)
  2. {
  3. if(keepScreenOn)
  4. {
  5. activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
  6. }else{
  7. activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
  8. }
  9. }

实例4:简单悬浮框的实现

运行效果图

实现代码

首先我们需要一个后台的Service在后台等待我们的操作,比如完成悬浮框的绘制移除等,
于是乎我们定义一个Service:MyService.java
我们需要一个创建悬浮框View的一个方法:

  1. private void createWindowView() {
  2. btnView = new Button(getApplicationContext());
  3. btnView.setBackgroundResource(R.mipmap.ic_launcher);
  4. windowManager = (WindowManager) getApplicationContext()
  5. .getSystemService(Context.WINDOW_SERVICE);
  6. params = new WindowManager.LayoutParams();
  7. // 设置Window Type
  8. params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
  9. // 设置悬浮框不可触摸
  10. params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
  11. | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
  12. // 悬浮窗不可触摸,不接受任何事件,同时不影响后面的事件响应
  13. params.format = PixelFormat.RGBA_8888;
  14. // 设置悬浮框的宽高
  15. params.width = 200;
  16. params.height = 200;
  17. params.gravity = Gravity.LEFT;
  18. params.x = 200;
  19. params.y = 000;
  20. // 设置悬浮框的Touch监听
  21. btnView.setOnTouchListener(new View.OnTouchListener() {
  22. //保存悬浮框最后位置的变量
  23. int lastX, lastY;
  24. int paramX, paramY;
  25. @Override
  26. public boolean onTouch(View v, MotionEvent event) {
  27. switch (event.getAction()) {
  28. case MotionEvent.ACTION_DOWN:
  29. lastX = (int) event.getRawX();
  30. lastY = (int) event.getRawY();
  31. paramX = params.x;
  32. paramY = params.y;
  33. break;
  34. case MotionEvent.ACTION_MOVE:
  35. int dx = (int) event.getRawX() - lastX;
  36. int dy = (int) event.getRawY() - lastY;
  37. params.x = paramX + dx;
  38. params.y = paramY + dy;
  39. // 更新悬浮窗位置
  40. windowManager.updateViewLayout(btnView, params);
  41. break;
  42. }
  43. return true;
  44. }
  45. });
  46. windowManager.addView(btnView, params);
  47. isAdded = true;
  48. }

然后我们只需在OnCreate( )方法中调用上述的createWindowView( )方法即可启动加载悬浮框,
但是我们发现了一点:这玩意貌似关不掉啊,卧槽,好吧,接下来我们就要分析下需求了!
当处于手机的普通界面,即桌面的时候,这玩意才显示,而当我们启动其他App时,这个悬浮框应该
消失不见,当我们推出app又回到桌面,这个悬浮框又要重新出现!
那么我们首先需要判断App是否位于桌面,于是乎我们再加上下述代码:

  1. /**
  2. * 判断当前界面是否是桌面
  3. */
  4. public boolean isHome(){
  5. if(mActivityManager == null) {
  6. mActivityManager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
  7. }
  8. List<RunningTaskInfo> rti = mActivityManager.getRunningTasks(1);
  9. return homeList.contains(rti.get(0).topActivity.getPackageName());
  10. }
  11. /**
  12. * 获得属于桌面的应用的应用包名称
  13. * @return 返回包含所有包名的字符串列表
  14. */
  15. private List<String> getHomes() {
  16. List<String> names = new ArrayList<String>();
  17. PackageManager packageManager = this.getPackageManager();
  18. // 属性
  19. Intent intent = new Intent(Intent.ACTION_MAIN);
  20. intent.addCategory(Intent.CATEGORY_HOME);
  21. List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(intent,
  22. PackageManager.MATCH_DEFAULT_ONLY);
  23. for(ResolveInfo ri : resolveInfo) {
  24. names.add(ri.activityInfo.packageName);
  25. }
  26. return names;
  27. }

好了,接下来我们需要每隔一段时间来进行一系列的判断,比如:是否在桌面,是否已加载悬浮框,
否则加载;否则,如果加载了,就将这个悬浮框移除!这里我们使用handler~,因为不能在子线程直接
更新UI,所以,你懂的,所以我们自己写一个handler来完成上述的操作:

  1. //定义一个更新界面的Handler
  2. private Handler mHandler = new Handler() {
  3. @Override
  4. public void handleMessage(Message msg) {
  5. switch(msg.what) {
  6. case HANDLE_CHECK_ACTIVITY:
  7. if(isHome()) {
  8. if(!isAdded) {
  9. windowManager.addView(btnView, params);
  10. isAdded = true;
  11. new Thread(new Runnable() {
  12. public void run() {
  13. for(int i=0;i<10;i++){
  14. try {
  15. Thread.sleep(1000);
  16. } catch (InterruptedException e) {e.printStackTrace();}
  17. Message m = new Message();
  18. m.what=2;
  19. mHandler.sendMessage(m);
  20. }
  21. }
  22. }).start();}
  23. } else {
  24. if(isAdded) {
  25. windowManager.removeView(btnView);
  26. isAdded = false;
  27. }
  28. }
  29. mHandler.sendEmptyMessageDelayed(HANDLE_CHECK_ACTIVITY, 0);
  30. break;
  31. }
  32. }
  33. };

最后要做的一件事,就是重写Service的onStartCommand( )方法了,就是做判断,取出Intent中的
数据,判断是需要添加悬浮框,还是要移除悬浮框!

  1. @Override
  2. public int onStartCommand(Intent intent, int flags, int startId) {
  3. int operation = intent.getIntExtra(OPERATION, OPERATION_SHOW);
  4. switch(operation) {
  5. case OPERATION_SHOW:
  6. mHandler.removeMessages(HANDLE_CHECK_ACTIVITY);
  7. mHandler.sendEmptyMessage(HANDLE_CHECK_ACTIVITY);
  8. break;
  9. case OPERATION_HIDE:
  10. mHandler.removeMessages(HANDLE_CHECK_ACTIVITY);
  11. break;
  12. }
  13. return super.onStartCommand(intent, flags, startId);
  14. }

好的,至此,主要的工作就完成了,接下来就是一些零碎的东西了,用一个Activity
来启动这个Service:MainActivity.java

  1. public class MainActivity extends AppCompatActivity implements View.OnClickListener {
  2. private Button btn_on;
  3. @Override
  4. protected void onCreate(Bundle savedInstanceState) {
  5. super.onCreate(savedInstanceState);
  6. setContentView(R.layout.activity_main);
  7. bindViews();
  8. }
  9. private void bindViews() {
  10. btn_on = (Button) findViewById(R.id.btn_on);
  11. btn_on.setOnClickListener(this);
  12. }
  13. @Override
  14. public void onClick(View v) {
  15. switch (v.getId()) {
  16. case R.id.btn_on:
  17. Intent mIntent = new Intent(MainActivity.this, MainService.class);
  18. mIntent.putExtra(MainService.OPERATION, MainService.OPERATION_SHOW);
  19. startService(mIntent);
  20. Toast.makeText(MainActivity.this, "悬浮框已开启~", Toast.LENGTH_SHORT).show();
  21. break;
  22. }
  23. }
  24. }

接着AndroidManifest.xml加上权限,以及为MainService进行注册:

  1. <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
  2. <uses-permission android:name="android.permission.GET_TASKS" />
  3. <service android:name=".MainService"/>

好了,逻辑还是比较容易理解的~大家自己再看看吧~


3.文献扩展:

从第四个实例中,你可能留意到了:WindowManager.LayoutParams这个东东,这是一个标记,
比如全屏~时间关系就不一一列举出来了,可以到官网或者下述链接中查看:

官方文档:WindowManager.LayoutParams
Android系统服务-WindowManager

另外,假如你对上述的悬浮框有兴趣,想更深入的研究,可见郭大叔(郭霖)的博客:

Android桌面悬浮窗效果实现,仿360手机卫士悬浮窗效果
Android桌面悬浮窗进阶,QQ手机管家小火箭效果实现


4.本节代码示例下载:

WindowManagerDemo2.zip


本节小结:

本节我们对Android系统服务中的WindowManager进行了学习,前面三个实例可能
实际开发中会用得多一点,建议将第一个示例写成一个工具类,毕竟屏幕宽高用得
蛮多的~至于悬浮框那个能看懂就看懂,没看懂耶没什么~实际开发很少叫你弄个
悬浮框吧...嗯,好的,本节就到这里,谢谢~

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注