窗体的使用,在当前窗体显示,在主屏幕上显示窗体,解决获取宽高为0的问题(仿360手机卫士1)
TextView view=new TextView(this); view.setText("自定义吐司"); view.setTextSize(25); view.setTextColor(Color.parseColor("#000000")); //窗体的参数设置 WindowManager wm=(WindowManager) getSystemService(WINDOW_SERVICE); //定义参数 WindowManager.LayoutParams params = new WindowManager.LayoutParams(); params.height = WindowManager.LayoutParams.WRAP_CONTENT; params.width = WindowManager.LayoutParams.WRAP_CONTENT; //与窗体左上角对齐 params.gravity=Gravity.TOP | Gravity.LEFT; //指定窗体距离左边100 上边100个像素点,可以实现跟随手指移动的效果,为控件设置触摸事件,wm.updateViewLayout(); params.x=100; params.y=100; params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE //不可有焦点 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE //不可以触摸 | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; //不锁屏 params.format = PixelFormat.TRANSLUCENT; //半透明 //能够在手机当前屏幕显示(哪怕当前屏幕的活动不是本应用),需要加权限 //<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> params.type =WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; //设置类型为系统级别的 wm.addView(view, params);//将视图和参数添加到窗体,会一直显示在该窗体所关联的活动的屏幕上 //wm.removeView(view);//调用这个方法就可以让视图消失
窗体是显示在创建这个窗体的界面上的,所以如果想显示在桌面上,需要显示桌面的时候创建窗体,那么也就只能在后台创建了
注意:获取到窗体需要显示的布局的时候,直接去获取这个布局的宽高可能会为0,
下面指定这个窗体显示在屏幕中间位置,主活动省略,只是在activity中开启这个服务,然后销毁该活动
/** * 创建悬浮窗体 */ public class FloatWindowService extends Service { /** * 用于在线程中创建或移除悬浮窗。 */ private Handler handler = new Handler(); /** * 定时器,定时进行检测当前应该创建还是移除悬浮窗。 */ private Timer timer; private WindowManager manager; private View view; @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() { createBigWindow(); } }); } // 当前界面不是桌面,且有悬浮窗显示,则移除悬浮窗。 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 createBigWindow(){ manager = (WindowManager)getSystemService(Context.WINDOW_SERVICE); int screenWidth = manager.getDefaultDisplay().getWidth(); int screenHeight = manager.getDefaultDisplay().getHeight(); view = View.inflate(this, R.layout.item_layout, null); view.findViewById(R.id.close); view.findViewById(R.id.back); LayoutParams bigWindowParams = 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();//这样才能得到这个布局的宽高 bigWindowParams.x = screenWidth / 2 - width / 2; bigWindowParams.y = screenHeight / 2 - height / 2; System.out.println("屏幕宽:"+screenWidth+",x="+ bigWindowParams.x+",view宽=" + view.getMeasuredWidth()/ 2 ); bigWindowParams.type = LayoutParams.TYPE_PHONE;//设置类型 bigWindowParams.format = PixelFormat.RGBA_8888; bigWindowParams.gravity = Gravity.LEFT | Gravity.TOP;//以左上角为参考 //当这个窗体不可获取焦点,就不能点击屏幕外的东西 //bigWindowParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL | LayoutParams.FLAG_NOT_FOCUSABLE; //设置窗体的宽高,以左上角为参考 bigWindowParams.width = WindowManager.LayoutParams.WRAP_CONTENT; bigWindowParams.height =WindowManager.LayoutParams.WRAP_CONTENT; manager.addView(view, bigWindowParams); System.out.println("创建了窗体"); } /** * 判断当前界面是否是桌面 ,先获取桌面应用的程序包名,然后判断当前显示活动包名是否包含在内 */ 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; } }
效果如下: 带拖动效果的见:窗体的使用,悬浮窗,仿360手机卫士2
为何我们调用view.getWidth和view.getHeight的时候,返回值都为0呢?????
因为在onCreate方法执行完了,我们定义的控件才会被度量(measure),所以我们在onCreate方法里面通过view.getHeight()获取控件的高度或者宽度肯定是0,因为它自己还没有被度量,也就是说他自己都不知道自己有多高,而你这时候去获取它的尺寸,肯定是不行的.
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); final ImageView imageView = (ImageView) findViewById(R.id.imageview); int w = View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED); int h = View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED); imageView.measure(w, h); int height =imageView.getMeasuredHeight(); int width =imageView.getMeasuredWidth(); textView.append("\n"+height+","+width); System.out.println("执行完毕.."+System.currentTimeMillis()); } //------------------------------------------------方法一
ViewTreeObserver vto = imageView.getViewTreeObserver(); vto.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { public boolean onPreDraw() { int height = imageView.getMeasuredHeight(); int width = imageView.getMeasuredWidth(); textView.append("\n"+height+","+width); return true; } }); <pre name="code" class="html">//-----------------------------------------------方法二
ViewTreeObserver vto2 = imageView.getViewTreeObserver(); vto2.addOnGlobalLayoutListener(new OnGlobalLayoutListener() { @Override public void onGlobalLayout() { imageView.getViewTreeObserver().removeGlobalOnLayoutListener(this); textView.append("\n\n"+imageView.getHeight()+","+imageView.getWidth()); } }); //-----------------------------------------------方法三
总结:那么需要获取控件的宽高该用那个方法呢? 上面我使用的方法一
方法一: 比其他的两个方法多了一次计算,也就是多调用了一次onMeasure()方法,该方法虽然看上去简单,但是如果要目标控件计算耗时比较大的话,不建议使用,如listView等.
方法二,它的回调方法会调用很多次,并且滑动TextView的时候任然会调用,所以不建议使用.
方法三,比较合适.
当然,实际应用的时候需要根据实际情况而定.
补充:
//获取View宽度的正确姿势,当 view layout 处理完成时,会自动发送消息,通知 UI 线程。借此机制,巧妙获取 View 的宽高属性 viewPager.post(new Runnable() { @Override public void run() { LogUtil.e("宽度=" + viewPager.getWidth()); } });
8.0悬浮窗的适配: