天天看点

安卓开发学习之锁屏的实现背景思路实现结语

背景

趁着今天项目收工,无事可做,记录一下自定义锁屏界面的实现

思路

锁屏界面要具备以下特征:

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键

由于按下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键

屏蔽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锁屏实现与总结

继续阅读