目录介绍
- 1.类似酷狗等锁屏页面实现步骤
- 1.1 什么是锁屏联动媒体播放器
- 1.2 如何实现锁屏页面
- 1.3 关于自定义锁屏页面左右滑动的控件
- 1.4 注意要点分析
- 1.5 具体完整代码的案例
- 1.6 效果图展示案例
- 2.自定义锁屏页的基本原理
- 2.1 基本原理
- 2.2 原理图形展示
- 2.3 讨论一些细节
- 3.锁屏Activity配置信息说明
- 3.1 去掉系统锁屏做法
- 3.2 权限问题
- 4.屏蔽物理或者手机返回键
- 4.1 为什么要这样处理
- 4.2 如何实现,实现逻辑代码
- 5.滑动屏幕解锁
- 5.1 滑动解锁原理
- 5.2 滑动控件自定义
- 6.透明栏与沉浸模式
- 6.1 透明栏与沉浸模式的概念
- 6.2 如何实现,代码展示
- 7.用户指纹识别,如何锁屏页面失效
- 8.关于其他
- 8.1 版本更新情况
- 8.2 参考案例
- 8.3 个人博客
0.备注
- 建议结合代码,看博客更加高效,项目地址:https://github.com/yangchong211/
- 博客大汇总,持续更新目录说明,记录所有开源项目和博客
1.类似酷狗等锁屏页面实现步骤
1.1 什么是锁屏联动媒体播放器
- 播放器除了播放了音乐之外什么都没做,就可以分别在任务管理、锁屏、负一屏控制播放器。
- 也可以这样通俗的解释,这个举例子说一个应用场景,我使用混沌大学听音频,然后我关闭了屏幕(屏幕灭了),当我再次打开的时候,屏幕的锁屏页面或者顶层页面便会出现一层音频播放器控制的页面,那么即使我不用解锁屏幕,也照样可以控制音频播放器的基本播放操作。如果你细心观察一下,也会发现有些APP正式这样操作的。目前我发现QQ音乐,酷狗音乐,混沌大学等是这样的
- 如何实现,逻辑思路
- 第一步:在服务中注册屏幕熄灭广播
- 第二步:处理逻辑,发现屏幕熄灭就开启锁屏页面,再次点亮屏幕时就可以看到锁屏页面
- 第三步:点击锁屏页面上的按钮,比如上一首,下一首,播放暂停可以与主程序同步信息。
- 第四步:滑动锁屏页面,锁屏页面被销毁,进入程序主界面。
1.2 如何实现锁屏页面
- 1.2.1 注册一个广播接收者监听屏幕亮了或者灭了
public class AudioBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if(action!=null && action.length()>0){
switch (action){
//锁屏时处理的逻辑
case Constant.LOCK_SCREEN:
PlayService.startCommand(context,Constant.LOCK_SCREEN);
break;
//当屏幕灭了
case Intent.ACTION_SCREEN_OFF:
PlayService.startCommand(context,Intent.ACTION_SCREEN_OFF);
break;
//当屏幕亮了
case Intent.ACTION_SCREEN_ON:
PlayService.startCommand(context,Intent.ACTION_SCREEN_ON);
break;
default:
break;
}
}
}
}
- 1.2.2 在服务中开启和注销锁屏操作
- 在oncreate方法中注册广播接收者
final IntentFilter filter = new IntentFilter();
//锁屏
filter.addAction(Constant.LOCK_SCREEN);
//当屏幕灭了
filter.addAction(Intent.ACTION_SCREEN_OFF);
//当屏幕亮了
filter.addAction(Intent.ACTION_SCREEN_ON);
registerReceiver(mAudioReceiver, filter);
- 在ondestory方法中注销广播接收者
unregisterReceiver(mAudioReceiver);
1.3 关于自定义锁屏页面左右滑动的控件
- 1.3.1 只有从左向右滑动的自定义控件
public class SlitherFinishLayout extends RelativeLayout implements OnTouchListener {
/**
* SlitherFinishLayout布局的父布局
*/
private ViewGroup mParentView;
/**
* 处理滑动逻辑的View
*/
private View touchView;
/**
* 滑动的最小距离
*/
private int mTouchSlop;
/**
* 按下点的X坐标
*/
private int downX;
/**
* 按下点的Y坐标
*/
private int downY;
/**
* 临时存储X坐标
*/
private int tempX;
/**
* 滑动类
*/
private Scroller mScroller;
/**
* SlitherFinishLayout的宽度
*/
private int viewWidth;
/**
* 记录是否正在滑动
*/
private boolean isSlither;
private OnSlitherFinishListener onSlitherFinishListener;
private boolean isFinish;
public SlitherFinishLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SlitherFinishLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
mScroller = new Scroller(context);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (changed) {
// 获取SlitherFinishLayout所在布局的父布局
mParentView = (ViewGroup) this.getParent();
viewWidth = this.getWidth();
}
}
/**
* 设置OnSlitherFinishListener, 在onSlitherFinish()方法中finish Activity
* @param onSlitherFinishListener listener
*/
public void setOnSlitherFinishListener(OnSlitherFinishListener onSlitherFinishListener) {
this.onSlitherFinishListener = onSlitherFinishListener;
}
/**
* 设置Touch的View
* @param touchView
*/
public void setTouchView(View touchView) {
this.touchView = touchView;
touchView.setOnTouchListener(this);
}
public View getTouchView() {
return touchView;
}
/**
* 滚动出界面
*/
private void scrollRight() {
final int delta = (viewWidth + mParentView.getScrollX());
// 调用startScroll方法来设置一些滚动的参数,我们在computeScroll()方法中调用scrollTo来滚动item
mScroller.startScroll(mParentView.getScrollX(), 0, -delta + 1, 0, Math.abs(delta));
postInvalidate();
}
/**
* 滚动到起始位置
*/
private void scrollOrigin() {
int delta = mParentView.getScrollX();
mScroller.startScroll(mParentView.getScrollX(), 0, -delta, 0, Math.abs(delta));
postInvalidate();
}
/**
* touch的View是否是AbsListView, 例如ListView, GridView等其子类
* @return
*/
private boolean isTouchOnAbsListView() {
return touchView instanceof AbsListView ? true : false;
}
/**
* touch的view是否是ScrollView或者其子类
* @return
*/
private boolean isTouchOnScrollView() {
return touchView instanceof ScrollView ? true : false;
}
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = tempX = (int) event.getRawX();
downY = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
int moveX = (int) event.getRawX();
int deltaX = tempX - moveX;
tempX = moveX;
if (Math.abs(moveX - downX) > mTouchSlop && Math.abs((int) event.getRawY() - downY) < mTouchSlop) {
isSlither = true;
// 若touchView是AbsListView,
// 则当手指滑动,取消item的点击事件,不然我们滑动也伴随着item点击事件的发生
if (isTouchOnAbsListView()) {
MotionEvent cancelEvent = MotionEvent.obtain(event);
cancelEvent.setAction(MotionEvent.ACTION_CANCEL
| (event.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
v.onTouchEvent(cancelEvent);
}
}
if (moveX - downX >= 0 && isSlither) {
mParentView.scrollBy(deltaX, 0);
// 屏蔽在滑动过程中ListView ScrollView等自己的滑动事件
if (isTouchOnScrollView() || isTouchOnAbsListView()) {
return true;
}
}
break;
case MotionEvent.ACTION_UP:
isSlither = false;
if (mParentView.getScrollX() <= -viewWidth / 2) {
isFinish = true;
scrollRight();
} else {
scrollOrigin();
isFinish = false;
}
break;
default:
break;
}
// 假如touch的view是AbsListView或者ScrollView 我们处理完上面自己的逻辑之后
// 再交给AbsListView, ScrollView自己处理其自己的逻辑
if (isTouchOnScrollView() || isTouchOnAbsListView()) {
return v.onTouchEvent(event);
}
// 其他的情况直接返回true
return true;
}
@Override
public void computeScroll() {
// 调用startScroll的时候scroller.computeScrollOffset()返回true,
if (mScroller.computeScrollOffset()) {
mParentView.scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
if (mScroller.isFinished()) {
if (onSlitherFinishListener != null && isFinish) {
onSlitherFinishListener.onSlitherFinish();
}
}
}
}
public interface OnSlitherFinishListener {
void onSlitherFinish();
}
}
- 1.3.2 支持向左或者向右滑动的控件,灵活处理
public class SlideFinishLayout extends RelativeLayout {
private final String TAG = SlideFinishLayout.class.getName();
/**
* SlideFinishLayout布局的父布局
*/
private ViewGroup mParentView;
/**
* 滑动的最小距离
*/
private int mTouchSlop;
/**
* 按下点的X坐标
*/
private int downX;
/**
* 按下点的Y坐标
*/
private int downY;
/**
* 临时存储X坐标
*/
private int tempX;
/**
* 滑动类
*/
private Scroller mScroller;
/**
* SlideFinishLayout的宽度
*/
private int viewWidth;
/**
* 记录是否正在滑动
*/
private boolean isSlide;
private OnSlideFinishListener onSlideFinishListener;
/**
* 是否开启左侧切换事件
*/
private boolean enableLeftSlideEvent = true;
/**
* 是否开启右侧切换事件
*/
private boolean enableRightSlideEvent = true;
/**
* 按下时范围(处于这个范围内就启用切换事件,目的是使当用户从左右边界点击时才响应)
*/
private int size ;
/**
* 是否拦截触摸事件
*/
private boolean isIntercept = false;
/**
* 是否可切换
*/
private boolean canSwitch;
/**
* 左侧切换
*/
private boolean isSwitchFromLeft = false;
/**
* 右侧侧切换
*/
private boolean isSwitchFromRight = false;
public SlideFinishLayout(Context context) {
super(context);
init(context);
}
public SlideFinishLayout(Context context, AttributeSet attrs) {
super(context, attrs, 0);
init(context);
}
public SlideFinishLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(Context context) {
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
Log.i(TAG, "设备的最小滑动距离:" + mTouchSlop);
mScroller = new Scroller(context);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (changed) {
// 获取SlideFinishLayout所在布局的父布局
mParentView = (ViewGroup) this.getParent();
viewWidth = this.getWidth();
size = viewWidth;
}
Log.i(TAG, "viewWidth=" + viewWidth);
}
public void setEnableLeftSlideEvent(boolean enableLeftSlideEvent) {
this.enableLeftSlideEvent = enableLeftSlideEvent;
}
public void setEnableRightSlideEvent(boolean enableRightSlideEvent) {
this.enableRightSlideEvent = enableRightSlideEvent;
}
/**
* 设置OnSlideFinishListener, 在onSlideFinish()方法中finish Activity
* @param onSlideFinishListener onSlideFinishListener
*/
public void setOnSlideFinishListener(OnSlideFinishListener onSlideFinishListener) {
this.onSlideFinishListener = onSlideFinishListener;
}
/**
* 是否拦截事件,如果不拦截事件,对于有滚动的控件的界面将出现问题(相冲突)
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
float downX = ev.getRawX();
Log.i(TAG, "downX =" + downX + ",viewWidth=" + viewWidth);
if(enableLeftSlideEvent && downX < size){
Log.e(TAG, "downX 在左侧范围内 ,拦截事件");
isIntercept = true;
isSwitchFromLeft = true;
isSwitchFromRight = false;
return false;
}else if(enableRightSlideEvent && downX > (viewWidth - size)){
Log.i(TAG, "downX 在右侧范围内 ,拦截事件");
isIntercept = true;
isSwitchFromRight = true;
isSwitchFromLeft = false;
return true;
}else{
Log.i(TAG, "downX 不在范围内 ,不拦截事件");
isIntercept = false;
isSwitchFromLeft = false;
isSwitchFromRight = false;
}
return super.onInterceptTouchEvent(ev);
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
//不拦截事件时 不处理
if(!isIntercept){
Log.d(TAG,"false------------");
return false;
}
Log.d(TAG,"true-----------");
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
downX = tempX = (int) event.getRawX();
downY = (int) event.getRawY();
Log.d(TAG,"downX---"+downX+"downY---"+downY);
break;
case MotionEvent.ACTION_MOVE:
int moveX = (int) event.getRawX();
int deltaX = tempX - moveX;
tempX = moveX;
if (Math.abs(moveX - downX) > mTouchSlop && Math.abs((int) event.getRawY() - downY) < mTouchSlop) {
isSlide = true;
}
Log.e(TAG, "scroll deltaX=" + deltaX);
//左侧滑动
if(enableLeftSlideEvent){
if (moveX - downX >= 0 && isSlide) {
mParentView.scrollBy(deltaX, 0);
}
}
//右侧滑动
if(enableRightSlideEvent){
if (moveX - downX <= 0 && isSlide) {
mParentView.scrollBy(deltaX, 0);
}
}
Log.i(TAG + "/onTouchEvent", "mParentView.getScrollX()=" + mParentView.getScrollX());
break;
case MotionEvent.ACTION_UP:
isSlide = false;
//mParentView.getScrollX() <= -viewWidth / 2 ==>指左侧滑动
//mParentView.getScrollX() >= viewWidth / 2 ==>指右侧滑动
if (mParentView.getScrollX() <= -viewWidth / 2 || mParentView.getScrollX() >= viewWidth / 2) {
canSwitch = true;
if(isSwitchFromLeft){
scrollToRight();
}
if(isSwitchFromRight){
scrollToLeft();
}
} else {
scrollOrigin();
canSwitch = false;
}
break;
default:
break;
}
return true;
}
/**
* 滚动出界面至右侧
*/
private void scrollToRight() {
final int delta = (viewWidth + mParentView.getScrollX());
// 调用startScroll方法来设置一些滚动的参数,我们在computeScroll()方法中调用scrollTo来滚动item
mScroller.startScroll(mParentView.getScrollX(), 0, -delta + 1, 0, Math.abs(delta));
postInvalidate();
}
/**
* 滚动出界面至左侧
*/
private void scrollToLeft() {
final int delta = (viewWidth - mParentView.getScrollX());
// 调用startScroll方法来设置一些滚动的参数,我们在computeScroll()方法中调用scrollTo来滚动item
//此处就不可用+1,也不卡直接用delta
mScroller.startScroll(mParentView.getScrollX(), 0, delta - 1, 0, Math.abs(delta));
postInvalidate();
}
/**
* 滚动到起始位置
*/
private void scrollOrigin() {
int delta = mParentView.getScrollX();
mScroller.startScroll(mParentView.getScrollX(), 0, -delta, 0, Math.abs(delta));
postInvalidate();
}
@Override
public void computeScroll(){
// 调用startScroll的时候scroller.computeScrollOffset()返回true,
if (mScroller.computeScrollOffset()) {
mParentView.scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
if (mScroller.isFinished()) {
if (onSlideFinishListener != null && canSwitch) {
//回调,左侧切换事件
if(isSwitchFromLeft){
onSlideFinishListener.onSlideBack();
}
//右侧切换事件
if(isSwitchFromRight){
onSlideFinishListener.onSlideForward();
}
}
}
}
}
public interface OnSlideFinishListener {
void onSlideBack();
void onSlideForward();
}
}
1.4 注意要点分析
- 1.4.1 在清单文件需要注册属性
- 1.4.2 程序在前台时,当从锁屏页面finish时,会有闪屏效果
- 如果加上这句话android:launchMode="singleInstance",那么程序在前台时会有闪屏效果,如果在后台时,则直接展现栈顶页面
- 如果不加这句话
1.5 具体完整代码的案例
- 可以参考具体的案例代码:https://github.com/yangchong211/YCAudioPlayer
- 可以参考的博客:https://www.jianshu.com/p/53017c3fc75d
2.自定义锁屏页的基本原理
2.1 基本原理
- Android系统实现自定义锁屏页的思路很简单,即在App启动时开启一个service,在Service中时刻监听系统SCREEN_OFF的广播,当屏幕熄灭时,Service监听到广播,开启一个锁屏页Activity在屏幕最上层显示,该Activity创建的同时会去掉系统锁屏(当然如果有密码是禁不掉的)。
2.2 原理图形展示
2.3 讨论一些细节
2.3.1 关于启动Activity时Intent的Flag问题
如果不添加FLAG_ACTIVITY_NEW_TASK的标志位,会出现“Calling startActivity() from outside of an Activity”的运行时异常,毕竟我们是从Service启动的Activity。Activity要存在于activity的栈中,而Service在启动activity时必然不存在一个activity的栈,所以要新起一个栈,并装入启动的activity。使用该标志位时,也需要在AndroidManifest中声明taskAffinity,即新task的名称,否则锁屏Activity实质上还是在建立在原来App的task栈中。
标志位FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS,是为了避免在最近使用程序列表出现Service所启动的Activity,但这个标志位不是必须的,其使用依情况而定。
2.3.2 动态注册广播接收者
IntentFilter mScreenOffFilter = new IntentFilter();
mScreenOffFilter.addAction(Intent.ACTION_SCREEN_OFF);
registerReceiver(mScreenOffReceiver, mScreenOffFilter);
3.锁屏Activity配置信息说明
3.1 去掉系统锁屏做法
- 在自定义锁屏Activity的onCreate()方法里设定以下标志位就能完全实现相同的功能:
//注意需要做一下判断
if (getWindow() != null) {
Window window = getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN |
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN |
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
// 锁屏的activity内部也要做相应的配置,让activity在锁屏时也能够显示,同时去掉系统锁屏。
// 当然如果设置了系统锁屏密码,系统锁屏是没有办法去掉的
// FLAG_DISMISS_KEYGUARD用于去掉系统锁屏页
// FLAG_SHOW_WHEN_LOCKED使Activity在锁屏时仍然能够显示
window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD |
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
window.getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE);
}
}
3.2 权限问题
- 不要忘记在Manifest中加入适当的权限:
4.屏蔽物理或者手机返回键
4.1 为什么要这样处理
- 当自定义锁屏页最终出现在手机上时,我们总希望它像系统锁屏页那样屹立不倒,所有的按键都不能触动它,只有通过划瓶或者指纹才能解锁,因此有必要对按键进行一定程度上的屏蔽。针对只有虚拟按键的手机,我们可以通过隐藏虚拟按键的方式部分解决这个问题,具体方法在后文会介绍。但是当用户在锁屏页底部滑动,隐藏后的虚拟按键还是会滑出,而且如果用户是物理按键的话就必须进行屏蔽了。
4.2 如何实现,实现逻辑代码
@Override
public void onBackPressed() {
// 不做任何事,为了屏蔽back键
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
int key = event.getKeyCode();
switch (key) {
case KeyEvent.KEYCODE_BACK: {
return true;
}
case KeyEvent.KEYCODE_MENU:{
return true;
}
default:
break;
}
return super.onKeyDown(keyCode, event);
}
5.滑动屏幕解锁
5.1 滑动解锁原理
- 当手指在屏幕上滑动时,拦截并处理滑动事件,使锁屏页面随着手指运动,当运动到达一定的阀值时,用户手指松开手指,锁屏页自动滑动到屏幕边界消失,如果没有达到运动阀值,就会自动滑动到起始位置,重新覆盖屏幕。
- 对滑动的距离与阀值进行一个比较,此处的阀值为0.5*屏幕宽度,如果低于阀值,则移动到初始位置;如果高于阀值,以同样的方式移出屏幕右边界,然后将Activity干掉
5.2 滑动控件自定义
- 具体可以看代码:项目地址https://github.com/yangchong211/YCAudioPlayer
- 可以直接看weight——other——SlideFinishLayout/SlitherFinishLayout类
6.透明栏与沉浸模式
6.1 透明栏与沉浸模式的概念
- 沉浸模式与透明栏是两个不同的概念,由于某些原因,国内一些开发或产品会把这两个概念混淆。
- 6.1.1 沉浸模式 什么是沉浸模式?
- 从4.4开始,Android 为 “setSystemUiVisibility()”方法提供了新的标记 “SYSTEM_UI_FLAG_IMMERSIVE”以及”SYSTEM_UI_FLAG_IMMERSIVE_STIKY”,就是我们所谈的沉浸模式,全称为 “Immersive Full-Screen Mode”,它可以使你的app隐藏状态栏和导航栏,实现真正意义上的全屏体验。
- 之前 Android 也是有全屏模式的,主要通过”setSystemUiVisibility()”添加两个Flag,即”SYSTEM_UI_FLAG_FULLSCREEN”,”SYSTEM_UI_FLAG_HIDE_NAVIGATION”(仅适用于使用导航栏的设备,即虚拟按键)。
- 这两个标记都存在一些问题,例如使用第一个标记的时候,除非 App 提供暂时退出全屏模式的功能(例如部分电子书软件中点击一次屏幕中央位置),用户是一直都没法看见状态栏的。这样,如果用户想去看看通知中心有什么通知,那就必须点击一次屏幕,显示状态栏,然后才能调出通知中心。
- 而第二个标记的问题在于,Google 认为导航栏对于用户来说是十分重要的,所以只会短暂隐藏导航栏。一旦用户做其他操作,例如点击一次屏幕,导航栏就会马上被重新调出。这样的设定对于看图软件,视频软件等等没什么大问题,但是对于游戏之类用户需要经常点击屏幕的 App,那就几乎是悲剧了——这也是为什么你在 Android 4.4 之前找不到什么全屏模式会自动隐藏导航栏的应用。
6.2 如何实现,代码展示
- 6.2.1 在案例中代码展示
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
window.getDecorView().setSystemUiVisibility(
// SYSTEM_UI_FLAG_LAYOUT_STABLE保持整个View稳定,使View不会因为SystemUI的变化而做layout
View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
// SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION,开发者容易被其中的HIDE_NAVIGATION所迷惑,
// 其实这个Flag没有隐藏导航栏的功能,只是控制导航栏浮在屏幕上层,不占据屏幕布局空间;
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
// SYSTEM_UI_FLAG_HIDE_NAVIGATION,才是能够隐藏导航栏的Flag;
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
// SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN,由上面可知,也不能隐藏状态栏,只是使状态栏浮在屏幕上层。
View.SYSTEM_UI_FLAG_FULLSCREEN |
View.SYSTEM_UI_FLAG_IMMERSIVE);
}
- 注意的是,这段代码除了需要加在Activity的OnCreate()方法中,也要加在重写的onWindowFocusChanged()方法中,在窗口获取焦点时再将Flag设置一遍,否则在部分手机上可能导致无法达到预想的效果。一般情况下没有问题,最后建议还是加上
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if(hasFocus && getWindow()!=null){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
getWindow().getDecorView().setSystemUiVisibility(
// SYSTEM_UI_FLAG_LAYOUT_STABLE保持整个View稳定,使View不会因为SystemUI的变化而做layout
View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
// SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION,开发者容易被其中的HIDE_NAVIGATION所迷惑,
// 其实这个Flag没有隐藏导航栏的功能,只是控制导航栏浮在屏幕上层,不占据屏幕布局空间;
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
// SYSTEM_UI_FLAG_HIDE_NAVIGATION,才是能够隐藏导航栏的Flag;
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
// SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN,由上面可知,也不能隐藏状态栏,只是使状态栏浮在屏幕上层。
View.SYSTEM_UI_FLAG_FULLSCREEN |
View.SYSTEM_UI_FLAG_IMMERSIVE);
}
}
}
8.关于其他
8.1 版本更新情况
8.2 参考案例
- Android添加锁屏界面:http://blog.csdn.net/sanjay_f/article/details/48653135
- Android 自定义锁屏:http://blog.csdn.net/macaopark/article/details/73477986
- Android锁屏实现与总结:https://www.jianshu.com/p/6c3a6b0f145e
- 浅谈Android自定义锁屏页:https://www.2cto.com/kf/201607/528329.html
8.3 个人博客
- github: https://github.com/yangchong211
- 知乎: https://www.zhihu.com/people/yang-chong-69-24/pins/posts
- : http://www.jianshu.com/u/b7b2c6ed9284
- csdn: http://my.csdn.net/m0_37700275
- 喜马拉雅听书: http://www.ximalaya.com/zhubo/71989305/
- 泡在网上的日子:http://www.jcodecraeer.com/member/content_list.php?channelid=1
- 邮箱:[email protected]
- 阿里云博客:https://yq.aliyun.com/users/article?spm=5176.100239.headeruserinfo.3.dT4bcV