趁着今天项目收工,无事可做,记录一下自定义锁屏界面的实现
锁屏界面要具备以下特征:
1、屏幕亮后启动
2、全屏
3、屏蔽back和recent键
4、滑屏解锁
5、处理点击事件
经过网上查资料和自己实验,上面的问题得以一一解决,下面是过程
核心之一是广播监听,用来监听屏幕亮起的广播。
屏幕亮起对应的action是SCREEN_ON,这是要做的就是启动锁屏Activity,注意设置标志位ACTIVITY_NEW_TASK,否则会报错从外部启动activity。对应代码如下
case Intent.ACTION_SCREEN_ON:
Intent startIntent = new Intent(context, LockScreentActivity.class);
startIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
context.startActivity(startIntent);
break;
后台的服务用来注册广播监听者,之所以不在Activity或者Applicaion里注册广播监听,是因为后台服务很少会被销毁,除非内存实在不够用了。整个类的代码如下
public class LockScreenService extends Service {
LockScreenReceiver mReceiver;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_SCREEN_ON); // 接收屏幕亮时的广播
mReceiver = new LockScreenReceiver();
registerReceiver(mReceiver, intentFilter);
}
@Override
public void onDestroy() {
super.onDestroy();
unregisterReceiver(mReceiver);
}
}
而后在主界面里启动这个服务即可
锁屏界面就是上面的LockScreenActivity,它主要负责全屏、屏蔽recent键和back键以及滑屏解锁等
主要是隐藏导航栏、标题栏透明、取消系统锁屏等,代码如下
private void initWindow() {
final Window window = getWindow();
requestWindowFeature(Window.FEATURE_NO_TITLE); // 无标题
window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD // 取消系统锁屏
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); // 锁屏时仍显示
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE // 防止系统栏隐藏时activity大小发生变化
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY // 沉浸式
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION // 隐藏导航栏
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // 隐藏导航栏
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); // 全屏
window.setNavigationBarColor(Color.TRANSPARENT);
window.setStatusBarColor(Color.TRANSPARENT);
// 设置标题栏透明
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
} else {
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(0);
}
}
不过有时导航栏和状态栏只是透明,没有隐藏,为了避免内容和状态栏重叠,只好设置一下内容的上间距,使之略大于状态栏的高度,代码如下
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
mContentView = findViewById(R.id.lock_content);
int statusHeight = getStatusBarHeight();
Log.i("LockScreen", "onCreate: 状态栏高度:" + statusHeight);
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) mContentView.getLayoutParams();
layoutParams.topMargin = statusHeight + 5;
mContentView.setLayoutParams(layoutParams);
...
}
private int getStatusBarHeight() {
int result = 0;
int resourceId = getResources().getIdentifier("status_bar_height",
"dimen", "android");
if (resourceId > 0) {
result = getResources().getDimensionPixelSize(resourceId);
}
return result;
}
由于按下recent键界面会走到onPause()方法,也就是只是失去焦点但依旧可见,因此只需在onPause()里进行处理即可
@Override
protected void onPause() {
super.onPause();
// 屏蔽recent键
android.app.ActivityManager activityManager = (android.app.ActivityManager) getApplicationContext()
.getSystemService(Context.ACTIVITY_SERVICE);
activityManager.moveTaskToFront(getTaskId(), 0);
}
屏蔽back键很简单,覆写onBackPressed()即可
@Override
public void onBackPressed() {
// 屏蔽Back键
}
为了方便处理滑屏解锁和响应点击事件,我用自定义view来作为锁屏界面的视图主体,专门用来处理触摸事件。
本质是一个ListView,里面处理触摸事件的逻辑如下:
1、按下事件,记录横坐标
2、移动事件,根据横坐标和按下时横坐标的差,决定内容的偏移量
3、抬起事件,根据横坐标,计算滑动了多远,超过阈值则结束锁屏界面;否则内容归位
因此,相关的代码如下所示
@Override
public boolean onTouchEvent(MotionEvent ev) {
final float x = ev.getX();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mStartX = x;
break;
case MotionEvent.ACTION_MOVE:
moveContent(x);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
handleTouchResult(x);
break;
}
return super.onTouchEvent(ev);
}
private void moveContent(float x) {
float offsetX = x - mStartX;
if (offsetX < 0f) {
offsetX = 0f;
}
setTranslationX(offsetX); // 内容的偏移量
}
private void handleTouchResult(float destination) {
float offsetX = destination - mStartX;
if (offsetX > mWindowWidth * 0.4) { // 超过阈值,结束锁屏activity
handleTouchResult(mWindowWidth - this.getLeft(), true);
} else { // 否则内容回到原位
handleTouchResult(-getLeft(), false);
}
}
private void handleTouchResult(float destination, boolean finishActivity) {
ObjectAnimator animator = ObjectAnimator.ofFloat(this, "translationX", destination);
animator.setDuration(250).start();
if (finishActivity) {
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
mActivity.finish();
}
});
}
}
为了响应点击事件,我的onTouchEvent返回了父类的返回值,不能拦截。
阈值是0.4倍的窗口宽度,从锁屏界面传过来,传入时机是锁屏界面所有子view测量完成后,获取decorView的宽度,传入之。LockScreenActivity中相关代码如下:
private ViewTreeObserver.OnGlobalLayoutListener mOnGlobalLayoutListener = new ViewTreeObserver
.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
mWindowWidth = getWindow().getDecorView().getWidth();
mContentView.setmWindowWidth(mWindowWidth);
mContentView.setActivity(LockScreentActivity.this);
if (mContentView != null) {
mContentView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
...
mContentView.getViewTreeObserver().addOnGlobalLayoutListener(mOnGlobalLayoutListener);
}
mContentView就是自定义的listView。
以上就是这个简单锁屏界面的实现。代码地址:github链接
参考文献:
个人第二个项目总结:home键,recent键,back键的屏蔽
Android锁屏实现与总结