锁屏界面弹出视图实践---类似QQ锁屏界面弹出消息提示框

按一般思维理解手机锁屏了,手机窗体应该是处于非活动状态的。而视图的显示 必须依赖活动的窗体。然而在使用市面应用的时候,很多应用却都可以再锁屏界面上活动自己的视图------比如QQ在锁屏时有消息到达,可以弹出对话框;再比如音乐应用在锁屏界面上有自己的歌词等等。

一开始觉得应该就是一个dialog/或者window上再加一个view。

我的猜测是对得,系统中确实有一个属性setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);然而可惜的是,外部应用似乎用不了该属性, 一直提示没有对应的权限。在翻阅WindowManager的其他属性时,当我看到了这个属性时简直是喜出望外WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED。一般我们知道FLAG的标记可以在activity窗体上使用。换句话说,如果给某个activity加上WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED,似乎看起来可以在锁屏时正常打开。经过试验确实可行,以下简单记录之。

最终效果:

锁屏界面弹出视图实践---类似QQ锁屏界面弹出消息提示框_第1张图片

1 创建在锁屏时能打开的activity

最终的实现很是简单,只需要在activity的onCreate里面简单增加几个属性即可。

//MainActivity.java
final Window win = getWindow();
win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
        | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
        | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
        | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
);
罗列下WindowManager.LayoutParams的一些属性值,方便查阅
应用程序窗口。
public static final int FIRST_APPLICATION_WINDOW = 1 ;
所有程序窗口的“基地”窗口,其他应用程序窗口都显示在它上面。
public static final int TYPE_BASE_APPLICATION = 1 ;
普通应用功能程序窗口。token必须设置为Activity的token,以指出该窗口属谁。
public static final int TYPE_APPLICATION = 2 ;
用于应用程序启动时所显示的窗口。应用本身不要使用这种类型。
它用于让系统显示些信息,直到应用程序可以开启自己的窗口。
public static final int TYPE_APPLICATION_STARTING = 3 ;
应用程序窗口结束。
public static final int LAST_APPLICATION_WINDOW = 99 ;
子窗口。子窗口的Z序和坐标空间都依赖于他们的宿主窗口。
public static final int FIRST_SUB_WINDOW = 1000 ;
面板窗口,显示于宿主窗口上层。
public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;
媒体窗口,例如视频。显示于宿主窗口下层。
public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW+ 1 ;
应用程序窗口的子面板。显示于所有面板窗口的上层。(GUI的一般规律,越“子”越靠上)
public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2 ;
对话框。类似于面板窗口,绘制类似于顶层窗口,而不是宿主的子窗口。
public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3 ;
媒体信息。显示在媒体层和程序窗口之间,需要实现透明(半透明)效果。(例如显示字幕)
public static final int TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW + 4 ;
子窗口结束。( End of types of sub-windows )
public static final int LAST_SUB_WINDOW = 1999 ;
系统窗口。非应用程序创建。
public static final int FIRST_SYSTEM_WINDOW = 2000 ;
状态栏。只能有一个状态栏;它位于屏幕顶端,其他窗口都位于它下方。
public static final int TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW;
搜索栏。只能有一个搜索栏;它位于屏幕上方。
public static final int TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+ 1 ;
电话窗口。它用于电话交互(特别是呼入)。它置于所有应用程序之上,状态栏之下。
public static final int TYPE_PHONE = FIRST_SYSTEM_WINDOW+ 2 ;
系统提示。它总是出现在应用程序窗口之上。
public static final int TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW + 3 ;
锁屏窗口。
public static final int TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW + 4 ;
信息窗口。用于显示toast。
public static final int TYPE_TOAST = FIRST_SYSTEM_WINDOW + 5 ;
系统顶层窗口。显示在其他一切内容之上。此窗口不能获得输入焦点,否则影响锁屏。
public static final int TYPE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW + 6 ;
电话优先,当锁屏时显示。此窗口不能获得输入焦点,否则影响锁屏。
public static final int TYPE_PRIORITY_PHONE = FIRST_SYSTEM_WINDOW + 7 ;
系统对话框。(例如音量调节框)。
public static final int TYPE_SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW + 8 ;
锁屏时显示的对话框。
public static final int TYPE_KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW + 9 ;
系统内部错误提示,显示于所有内容之上。
public static final int TYPE_SYSTEM_ERROR = FIRST_SYSTEM_WINDOW + 10 ;
内部输入法窗口,显示于普通UI之上。应用程序可重新布局以免被此窗口覆盖。
public static final int TYPE_INPUT_METHOD = FIRST_SYSTEM_WINDOW + 11 ;
内部输入法对话框,显示于当前输入法窗口之上。
public static final int TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW + 12 ;
墙纸窗口。
public static final int TYPE_WALLPAPER = FIRST_SYSTEM_WINDOW + 13 ;
状态栏的滑动面板。
public static final int TYPE_STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW + 14 ;
系统窗口结束。
public static final int LAST_SYSTEM_WINDOW = 2999 ;
2 获取手机当前壁纸
 我们知道QQ在锁屏弹窗的时候其背景跟我们的壁纸是一致的,这里准确说来是和桌面上的壁纸一致。
 这里就需要用到壁纸管理器:
//MainActivity.java  
public Bitmap getWallPaper() {
        WallpaperManager wallpaperManager = WallpaperManager
                .getInstance(MainActivity.this);
        // 获取当前壁纸
        Drawable wallpaperDrawable = wallpaperManager.getDrawable();
        Bitmap bm = ((BitmapDrawable) wallpaperDrawable).getBitmap();

        int heightPixels = getResources().getDisplayMetrics().heightPixels;
        int widthPixels = getResources().getDisplayMetrics().widthPixels;
        int with = bm.getHeight()*widthPixels/heightPixels > bm.getWidth() ? bm.getWidth():bm.getHeight()*widthPixels/heightPixels;
        Bitmap pbm = Bitmap.createBitmap(bm, 0, 0, with, bm.getHeight());
        // 设置 背景
        return pbm;
    }
获取到的壁纸直接加载到当前的activity上,作为其背景图片:
//MainActivity.java 
@Override
    public void onAttachedToWindow() {
        super.onAttachedToWindow();

        if (true) {
            final View view = getWindow().getDecorView();
            final WindowManager.LayoutParams lp = (WindowManager.LayoutParams) view.getLayoutParams();
            lp.gravity = Gravity.CENTER;
// 可以通过布局属性来控制activity的大小,现在市面上的一些半截activity(特别是一些支付页面等)都可以通过在这里调节属性来实现
//            lp.width = getResources().getDisplayMetrics().widthPixels / 2;
//            lp.height = getResources().getDisplayMetrics().heightPixels / 3;
            if (Build.VERSION.SDK_INT >= 16) {
                view.setBackground(new BitmapDrawable(getWallPaper()));
            } else {
                view.setBackgroundColor(Color.WHITE);
            }
            getWindowManager().updateViewLayout(view, lp);
        }
    }

3 监听屏幕状态/锁屏/解锁/亮屏
 
  
这里使用到了系统广播,然而有一个很坑爹的地方  配置静态广播的时候锁屏/亮屏广播无法生效只有用户手动解锁广播可以生效,原因一直没搞懂因此采用了在广播中注册的方式

//FloatService.java  
@Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("ndh--","onStartCommand");
                /* 注册屏幕唤醒时的广播 */
        IntentFilter intentenfilter = new IntentFilter();
        intentenfilter.addAction(Intent.ACTION_SCREEN_ON);
        intentenfilter.addAction(Intent.ACTION_SCREEN_OFF);
        intentenfilter.addAction(Intent.ACTION_USER_PRESENT);
        receiver = new ScreenStateReceiver();
        registerReceiver(receiver, intentenfilter);
        return super.onStartCommand(intent, flags, startId);
    }
// ScreenStateReceiver.java  
@Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        Log.d("ndh--", "action=" + action);
        if (Intent.ACTION_SCREEN_ON.equals(action)) { // 开屏
            ScreenStateManager.screenState=1;
            Log.d("ndh--", "screen on");
        } else if (Intent.ACTION_SCREEN_OFF.equals(action)) { // 锁屏
            ScreenStateManager.screenState=-1;
            Log.d("ndh--", "screen off");
            Intent intent1 = new Intent(context, MainActivity.class);
            intent1.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            context.startActivity(intent1);
        } else if (Intent.ACTION_USER_PRESENT.equals(action)) { // 解锁
            ScreenStateManager.screenState=0;
            Log.d("ndh--", "user_present");
        }
    }
}


PS 可能真正的场景是,有一条通知到来,然后我们判断一下当前的屏幕活动状态,如果是亮屏,挂载普通界面,如果是锁屏则挂载特制的锁屏Activity界面。这里判断屏幕是否处于活动状态
除了这里的广播外,系统的PowerManager同样可以使用:
//ScreenStateManager.java   
//判断当时屏幕是否处于打开状态
    public boolean isScreenOn(Context context) {
        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
        if (pm.isScreenOn()) {
            return true;
        }
        return false;
    }
事例代码地址:https://github.com/killer8000/popup-window-on-lock-screen
 
  
 
  

你可能感兴趣的:(android开发)