关键字 : WindowManager,浮动窗体,工具面板,SystemUI状态栏
在群里面久了,经常会听到很多人说了做一个什么窗体,什么弹出框,或者上拉,下拉的框体.下面大致介绍一下.这里面先大致说一下SystemUI(状态栏/通知栏)工程的一些相似特点,SystemUI这个系统APP基本上没有Activity,完全通过WindowManager这个管理器动态添加视图.包括屏幕顶部的状态栏,下拉的通知栏,功能设置面板:
从SystemUI的工程来看它的视窗布局,基本上是Service搭配被WindowManager添加的小视窗口,比如一条通知过来,它就对应添加一个Notification的View提供给使用者拉下来的时候可以看见.这里也是参照SystemUI工程,用Service作为载体,使用WindowManager加载自己的视窗到屏幕上面显示.
<1> : 新建一个android studio工程:PumpKinWindows:
主体程序:
package org.pumpkin.pumpkinwindows;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import org.pumpkin.pumpkinwindows.services.WindowManagerService;
public class PumpKinMainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pump_kin_main);
Intent intent=new Intent(PumpKinMainActivity.this, WindowManagerService.class);
startService(intent);
}
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
}
}
新建一个承载的Service:
package org.pumpkin.pumpkinwindows.services;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import org.pumpkin.pumpkinwindows.wm.PumpKinWindowManager;
public class WindowManagerService extends Service {
private PumpKinWindowManager pumpKinWindowManager;
public WindowManagerService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if(pumpKinWindowManager==null){
pumpKinWindowManager=new PumpKinWindowManager();
pumpKinWindowManager.CreateWindowManagerGroupView(this);
// pumpKinWindowManager.CreateWindowManagerMoveView(this);
// pumpKinWindowManager.CreateWindowManagerOverView(this);
// pumpKinWindowManager.CreateWindowManagerDialogView(this);
}
return super.onStartCommand(intent, flags, startId);
}
}
Andriod studio会自动将这个组件添加到manifest文件中.
下面是WindowManager创建视窗类:
package org.pumpkin.pumpkinwindows.wm;
import android.content.Context;
import android.graphics.PixelFormat;
import android.util.Log;
import android.view.Window;
import android.view.WindowManager;
import org.pumpkin.pumpkinwindows.view.PumpKinGroupView;
import org.pumpkin.pumpkinwindows.view.PumpKinMoveView;
import org.pumpkin.pumpkinwindows.view1.PumpKinDialogView;
import org.pumpkin.pumpkinwindows.view1.PumpKinOverlayView;
/**
* Project name : PumpKinWindows
* Created by zhibao.liu on 2016/5/3.
* Time : 16:42
* Email warden_sprite@foxmail.com
* Action : durian
*/
public class PumpKinWindowManager {
private final static String TAG="PumpKinWindowManager";
private static PumpKinGroupView mPumpKinGroupView;
private static PumpKinMoveView mPumpKinMoveView;
private static PumpKinOverlayView mPumpKinOverlayView;
private static PumpKinDialogView mPumpKinDialogView;
private WindowManager.LayoutParams pumpKinParams;
private static WindowManager.LayoutParams pumpKinMoveParams;
private static WindowManager.LayoutParams pumpKinOverParams;
private static WindowManager.LayoutParams pumpKinDialogParams;
private Context mContext;
private int ScreenWidth;
private int ScreenHeight;
private static int ScreenLevelX=0;
private static int ScreenLevelY=0;
public void CreateWindowManagerGroupView(Context context){
mContext=context;
WindowManager windowManager=(WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
ScreenWidth=windowManager.getDefaultDisplay().getWidth();
ScreenHeight=windowManager.getDefaultDisplay().getHeight();
ScreenLevelX=0;
ScreenLevelY=ScreenHeight/2;
Log.i(TAG,"ScreenWidth : "+ScreenWidth+" ScreenHeight : "+ScreenHeight);
if(mPumpKinGroupView==null){
mPumpKinGroupView=new PumpKinGroupView(context);
pumpKinParams=new WindowManager.LayoutParams();
pumpKinParams.width=ScreenWidth;
pumpKinParams.height=ScreenHeight/2;
pumpKinParams.type= WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
pumpKinParams.format= PixelFormat.RGBA_8888;
pumpKinParams.flags= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
pumpKinParams.x=0;
pumpKinParams.y=0;
pumpKinParams.packageName=context.getPackageName();
windowManager.addView(mPumpKinGroupView,pumpKinParams);
}
}
public void CreateWindowManagerMoveView(Context context){
mContext=context;
WindowManager windowManager=(WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
ScreenWidth=windowManager.getDefaultDisplay().getWidth();
ScreenHeight=windowManager.getDefaultDisplay().getHeight();
ScreenLevelX=0;
ScreenLevelY=ScreenHeight/2;
Log.i(TAG,"ScreenWidth : "+ScreenWidth+" ScreenHeight : "+ScreenHeight);
if(mPumpKinMoveView==null){
mPumpKinMoveView=new PumpKinMoveView(context);
pumpKinMoveParams=new WindowManager.LayoutParams();
pumpKinMoveParams.width=ScreenWidth;
pumpKinMoveParams.height=ScreenHeight/2;
pumpKinMoveParams.type= WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
pumpKinMoveParams.format= PixelFormat.RGBA_8888;
pumpKinMoveParams.flags= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
pumpKinMoveParams.x=0;
pumpKinMoveParams.y=0;
pumpKinMoveParams.packageName=context.getPackageName();
windowManager.addView(mPumpKinMoveView,pumpKinMoveParams);
}
}
public void CreateWindowManagerOverView(Context context){
mContext=context;
WindowManager windowManager=(WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
ScreenWidth=windowManager.getDefaultDisplay().getWidth();
ScreenHeight=windowManager.getDefaultDisplay().getHeight();
ScreenLevelX=0;
ScreenLevelY=ScreenHeight/2;
Log.i(TAG,"ScreenWidth : "+ScreenWidth+" ScreenHeight : "+ScreenHeight);
if(mPumpKinOverlayView==null){
mPumpKinOverlayView=new PumpKinOverlayView(context);
pumpKinOverParams=new WindowManager.LayoutParams();
pumpKinOverParams.width=ScreenWidth;
pumpKinOverParams.height=ScreenHeight/2;
pumpKinOverParams.type= WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
pumpKinOverParams.format= PixelFormat.RGBA_8888;
pumpKinOverParams.flags= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
pumpKinOverParams.x=0;
pumpKinOverParams.y=0;
pumpKinOverParams.packageName=context.getPackageName();
windowManager.addView(mPumpKinOverlayView,pumpKinOverParams);
}
}
public void CreateWindowManagerDialogView(Context context){
mContext=context;
WindowManager windowManager=(WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
ScreenWidth=windowManager.getDefaultDisplay().getWidth();
ScreenHeight=windowManager.getDefaultDisplay().getHeight();
ScreenLevelX=0;
ScreenLevelY=ScreenHeight/2;
Log.i(TAG,"ScreenWidth : "+ScreenWidth+" ScreenHeight : "+ScreenHeight);
if(mPumpKinDialogView==null){
mPumpKinDialogView=new PumpKinDialogView(context);
pumpKinDialogParams=new WindowManager.LayoutParams();
pumpKinDialogParams.width=ScreenWidth;
pumpKinDialogParams.height=ScreenHeight/2;
pumpKinDialogParams.type= WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
pumpKinDialogParams.format= PixelFormat.RGBA_8888;
pumpKinDialogParams.flags= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
pumpKinDialogParams.x=0;
pumpKinDialogParams.y=0;
pumpKinDialogParams.packageName=context.getPackageName();
windowManager.addView(mPumpKinDialogView,pumpKinDialogParams);
}
}
public void CreateWindowManagerPanelView(){
//todo nothing
}
public static void updateMovePositionView(Context context,int x,int y){
Log.i(TAG,"X : "+x+" Y : "+y);
pumpKinMoveParams.x=x;
pumpKinMoveParams.y=y-ScreenLevelY;
Log.i(TAG,"Params.x : "+pumpKinMoveParams.x+" Params.y : "+pumpKinMoveParams.y);
WindowManager windowManager=(WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
if(mPumpKinMoveView!=null) {
windowManager.updateViewLayout(mPumpKinMoveView,pumpKinMoveParams);
}
}
public static void removeWindows(Context context){
WindowManager windowManager=(WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
if(mPumpKinGroupView!=null) {
windowManager.removeView(mPumpKinGroupView);
mPumpKinGroupView=null;
}
}
public static void removeMoveWindows(Context context){
WindowManager windowManager=(WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
if(mPumpKinMoveView!=null) {
windowManager.removeView(mPumpKinMoveView);
mPumpKinMoveView=null;
}
}
}
下面是所有的View:
package org.pumpkin.pumpkinwindows.view;
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.Toast;
import org.pumpkin.pumpkinwindows.wm.PumpKinWindowManager;
/**
* Project name : PumpKinWindows
* Created by zhibao.liu on 2016/5/3.
* Time : 16:47
* Email warden_sprite@foxmail.com
* Action : durian
*/
public class PumpKinGroupView extends ViewGroup {
private final static String TAG="PumpKinGroupView";
private PumpKinView pumpKinView;
private Button button;
public PumpKinGroupView(final Context context) {
super(context);
pumpKinView=new PumpKinView(context);
addView(pumpKinView);
button=new Button(context);
button.setText("button");
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(context,"click button !",Toast.LENGTH_SHORT).show();
PumpKinWindowManager.removeWindows(context);
}
});
addView(button);
setBackgroundColor(Color.GRAY);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
Log.i(TAG,"l : "+l+" t : "+t+" r : "+r+" b : "+b);
int childnum=getChildCount();
for(int i=0;i<childnum;i++){
View view=getChildAt(i);
if(view instanceof Button){
view.layout(l,t+200,r,300);
}else{
view.layout(l,t,r,b-300);
}
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(1200,1824/2);
}
}
package org.pumpkin.pumpkinwindows.view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.MotionEvent;
import android.view.View;
import org.pumpkin.pumpkinwindows.wm.PumpKinWindowManager;
/**
* Project name : PumpKinWindows
* Created by zhibao.liu on 2016/5/4.
* Time : 15:35
* Email warden_sprite@foxmail.com
* Action : durian
*/
public class PumpKinMoveView extends View{
private Paint mPaint;
private Context mContext;
public PumpKinMoveView(Context context) {
super(context);
initView(context);
}
private void initView(Context context){
mContext=context;
mPaint=new Paint();
mPaint.setStrokeWidth(4);
mPaint.setTextSize(48);
mPaint.setColor(Color.GREEN);
setBackgroundColor(Color.RED);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawText("move view",100,100,mPaint);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(800,300);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
//PumpKinWindowManager.updateMovePositionView(mContext,(int)event.getX(),(int)event.getY());
break;
case MotionEvent.ACTION_MOVE:
PumpKinWindowManager.updateMovePositionView(mContext,(int)event.getX(),(int)event.getY());
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
return true;//super.onTouchEvent(event);
}
}
package org.pumpkin.pumpkinwindows.view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
/**
* Project name : PumpKinWindows
* Created by zhibao.liu on 2016/5/3.
* Time : 16:46
* Email warden_sprite@foxmail.com
* Action : durian
*/
public class PumpKinView extends View {
private Paint mPaint;
public PumpKinView(Context context) {
super(context);
mPaint=new Paint();
mPaint.setColor(Color.GREEN);
mPaint.setStrokeWidth(4.0f);
mPaint.setTextSize(64);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawText("hello windowmanager",100,100,mPaint);
}
}
package org.pumpkin.pumpkinwindows.view1;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.View;
/**
* Project name : PumpKinWindows
* Created by zhibao.liu on 2016/5/5.
* Time : 10:39
* Email warden_sprite@foxmail.com
* Action : durian
*/
public class PumpKinDialogView extends View {
private Paint mPaint;
public PumpKinDialogView(Context context) {
super(context);
initView(context);
}
private void initView(Context context){
mPaint=new Paint();
mPaint.setStrokeWidth(4);
mPaint.setTextSize(48);
mPaint.setColor(Color.GREEN);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
}
package org.pumpkin.pumpkinwindows.view1;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.View;
/**
* Project name : PumpKinWindows
* Created by zhibao.liu on 2016/5/4.
* Time : 19:11
* Email warden_sprite@foxmail.com
* Action : durian
*/
public class PumpKinOverlayView extends View {
private Paint mPaint;
public PumpKinOverlayView(Context context) {
super(context);
initView(context);
}
public void initView(Context context){
mPaint=new Paint();
mPaint.setStrokeWidth(4);
mPaint.setTextSize(48);
mPaint.setColor(Color.RED);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawText("overlay view",100,100,mPaint);
}
}
(*@ο@*) 哇~,新建了5个视窗,其实都是差不多的.
程序的基本构成是Activity启动Service,然后在Service中通过PumpKinWindowManager创建各种类型的视图窗体.
下面基本上谈一下下面的:
pumpKinParams=new WindowManager.LayoutParams();
pumpKinParams.width=ScreenWidth;
pumpKinParams.height=ScreenHeight/2;
pumpKinParams.type= WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
pumpKinParams.format= PixelFormat.RGBA_8888;
pumpKinParams.flags= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
pumpKinParams.x=0;
pumpKinParams.y=0;
pumpKinParams.packageName=context.getPackageName();
windowManager.addView(mPumpKinGroupView,pumpKinParams);
首先看看参数:
pumpKinParams.type= WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
这个参数是最重要的,不同的类型就决定了用户创建的视窗所在的层级,这个层级通俗的来说,可以举例,比如锁屏的视窗就可以遮盖整个屏幕(或者说遮盖所有的其他视窗),状态栏的下拉视窗又可以遮盖桌面的视窗,这是个视窗层级的概念,基本上所有的视窗系统都有.即视窗平面垂直于Z轴.设置TYPE_SYSTEM_ALERT类型需要APP添加权限:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
实际上面添加任何种类都要需要对应添加相应的权限,这个其实很常见,很多安全APP就可以设置系统是否允许弹框.
这种类型属于系统提示,并且出现在应用程序窗口之上.
pumpKinParams.flags= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
这个参数的设置主要是影响交互方面的,比如是否支持按键,触摸,截屏,装饰,窗体size等方面.上面参数是抢占设备的触摸点击事件,事件不会再传到这个视图下面的桌面上去.
pumpKinParams.x=0;
pumpKinParams.y=0;
这个是视窗显示的位置,但是这个位置有点特别,如果是设置TYPE_SYSTEM_ALERT的类型,可能是由于它是系统提示框,默认位置将是屏幕居中的位置,而设置上面0,0,那么其实就将生成的视窗屏幕居中,也就是说屏幕坐标系移动到屏幕中间位置,而图形显示并不是从0,0开始,这个0,0只是图形的居中位置.使用这个可以调节这两个参数值就会发现,特别是将y值调节为负数,但是负数操作屏幕的时候,设置再负视窗位置也不会变化了.这个位置在开发过程特别需要注意.可以将上面demo中的:
pumpKinWindowManager.CreateWindowManagerMoveView(this);
这一句程序注释去掉,然后注释其他的创建,然后运行移动屏幕上面的图框,就知道了.调整位置如下:
public static void updateMovePositionView(Context context,int x,int y){
Log.i(TAG,"X : "+x+" Y : "+y);
pumpKinMoveParams.x=x;
pumpKinMoveParams.y=y-ScreenLevelY;
Log.i(TAG,"Params.x : "+pumpKinMoveParams.x+" Params.y : "+pumpKinMoveParams.y);
WindowManager windowManager=(WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
if(mPumpKinMoveView!=null) {
windowManager.updateViewLayout(mPumpKinMoveView,pumpKinMoveParams);
}
}
pumpKinOverParams.width=ScreenWidth;
pumpKinOverParams.height=ScreenHeight/2;
这个很简单理解了,就是视窗的高度和宽度大小.
pumpKinOverParams.format= PixelFormat.RGBA_8888;
说明是期望的位图格式,这个我不是太清楚.
pumpKinOverParams.packageName=context.getPackageName();
这个是视图属于后面报名的应用.
配置好所有的参数后,既可以添加到WindowManager管理器中.
windowManager.addView(mPumpKinOverlayView,pumpKinOverParams);
添加了以后即可以生产出来.
当然首先要获取:
WindowManager windowManager=(WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
一般TYPE_SYSTEM_ALERT类型都可以触摸点击,常见的应用有手机360助手,屏幕上面常挂着它的一个圆形球,显示这加速度**%.
普通开发级别还有:
pumpKinOverParams.type= WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
添加权限:
<uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" />
这个类型相比前面,这个无法接受点击触摸事件,点击这种视窗,触摸事件直接到这是视图下面的应用上去了,就当做是透明的.所以一般只能作为显示信息,不影响交互功能.
一般能够给普通开发的基本上只有上面两种.
查看API:http://developer.android.com/reference/android/view/WindowManager.LayoutParams.html#packageName
会看到还有很多种类,但是经常用的并不是很多.
这个又要回到文章开头,为什么要看SystemUI的那一套,因为这个东西往往是系统应用或者定制系统的人用的多,因为除了上面两种,其他的基本上都需要被用在系统的app上面,否则都会报权限问题(即使添加了权限),所以如果是开发系统的,并且对视图层级没什么太大的要求,建议推荐使用:
<1> : TYPE_STATUS_BAR
这一种就是所有android手机屏幕上方的状态栏类型;
<2> : TYPE_STATUS_BAR_PANEL
这一种就是下来状态栏显示wifi设置的那个面板类型;
<3>TYPE_KEYGUARD_DIALOG
这个层级非常的高,可以盖过所有视窗,至于系统的顶层视窗.
当然全部要在manifest中配置上权限.
至于其他的配置完全和上面普通开发者使用的其他配置基本上可以差不多.创建,移动位置都是一样的方式,由于需要添加到系统应用中,感兴趣可以将上面的程序添加到SystemUI中,然后在里面:
public class SystemUIApplication extends Application {
private static final String TAG = "SystemUIService";
private static final boolean DEBUG = false;
/**
* The classes of the stuff to start.
*/
private final Class<?>[] SERVICES = new Class[] {
com.android.systemui.tuner.TunerService.class,
com.android.systemui.keyguard.KeyguardViewMediator.class,
com.android.systemui.recents.Recents.class,
com.android.systemui.volume.VolumeUI.class,
com.android.systemui.statusbar.SystemBars.class,
com.android.systemui.usb.StorageNotification.class,
com.android.systemui.power.PowerUI.class,
com.android.systemui.media.RingtonePlayer.class,
com.android.systemui.keyboard.KeyboardUI.class,
};
在上面添加进去启动.demo中Activity都不需要了(依葫芦画瓢,也继承SystemUI类),
也可以不用这么麻烦,在SystemUI创建过程中将:
Intent intent=new Intent(PumpKinMainActivity.this, WindowManagerService.class);
startService(intent);
去启动运行.同样也可以显示出来.
这个窗体在灵活做一些浮动窗口或者设置面板,或者浮动菜单经常被使用,基本上所有的手机系统都存在这些用法.