什么是keyguard?
- Android在触发解锁流程的时候,会根据设置的解锁方式不同,获取不同的解锁布局,来展示解锁功能.
- (1).PIN码,纯数字不含字母
- (2).密码,数字+字母组合等
- (3).其他SIM卡PIN码等
以上解锁方式,展示以及部分逻辑都是通过systemui的keyguard模块来展示的.
相关布局举例
- 这里使用密码解锁来举例,界面如下
针对密码输入的视图是通过KeyguardPINView
来进行相关的逻辑交互. - 详细的视图层级如下图
可以看到通知的panel view
和锁屏的布局都是在
NotifycationShaodwWindowView
里面,这两个视图在手机上面用起来大家应该都不陌生
锁屏的时候会有通知展示,微信消息之类的,然后这个屏幕一上划就会进入输入密码的界面.
往往这个交互过程中都会伴随着动画,这个动画就是systemui
中的管理通知的manage
r中去实现的. - 接着回到锁屏这一块内容描述
- 解锁相关的内容是怎么被添加到屏幕上的?
- 又是怎么切换不同的解锁模式的?
- 是根据什么判定应该显示什么解锁视图?
等等一系列问题
查找或者解析当前的解锁View是谁?
KeyguardHostView
被添加到视图上面并且完成解析的时候会调用onFinishInflate()
at com.android.keyguard.KeyguardSecurityContainer.getSecurityViewIdForMode(KeyguardSecurityContainer.java:892)
at com.android.keyguard.KeyguardSecurityContainer.getSecurityView(KeyguardSecurityContainer.java:468)
at com.android.keyguard.KeyguardSecurityContainer.showSecurityScreen(KeyguardSecurityContainer.java:770)
at com.android.keyguard.KeyguardSecurityContainer.showPrimarySecurityScreen(KeyguardSecurityContainer.java:669)
at com.android.keyguard.KeyguardHostView.onFinishInflate(KeyguardHostView.java:154)
最终会调用到KeyguardSecurityContainer.getSecurityViewIdForMode()
函数
private int getSecurityViewIdForMode(SecurityMode securityMode) {
switch (securityMode) {
case Pattern: return R.id.keyguard_pattern_view;
case PIN: return R.id.keyguard_pin_view;
case Password: return R.id.keyguard_password_view;
case SimPin: return R.id.keyguard_sim_pin_view;
case SimPuk: return R.id.keyguard_sim_puk_view;
}
return 0;
}
public int getLayoutIdFor(SecurityMode securityMode) {
switch (securityMode) {
case Pattern: return R.layout.keyguard_pattern_view;
case PIN: return R.layout.keyguard_pin_view;
case Password: return R.layout.keyguard_password_view;
case SimPin: return R.layout.keyguard_sim_pin_view;
case SimPuk: return R.layout.keyguard_sim_puk_view;
default:
return 0;
}
}
这里会通过传入的模式返回固定的控件id
,然后把匹配到的view
返回给调用地方,记录下来,以方便后续做一系列操作,如果没有找到,证明这是第一次调用这个函数,view
还没有被添加过,就直接匹配layoutId
直接infalter
一个新的View
出来,并且同时会add
到mSecurityViewFlipper
控件上.
protected KeyguardSecurityView getSecurityView(SecurityMode securityMode) {
final int securityViewIdForMode = getSecurityViewIdForMode(securityMode);
KeyguardSecurityView view = null;
final int children = mSecurityViewFlipper.getChildCount();
for (int child = 0; child < children; child++) {
if (mSecurityViewFlipper.getChildAt(child).getId() == securityViewIdForMode) {
view = ((KeyguardSecurityView)mSecurityViewFlipper.getChildAt(child));
break;
}
}
int layoutId = getLayoutIdFor(securityMode);
if (view == null && layoutId != 0) {
final LayoutInflater inflater = LayoutInflater.from(mContext);
if (DEBUG) Log.v(TAG, "inflating id = " + layoutId);
View v = mInjectionInflationController.injectable(inflater)
.inflate(layoutId, mSecurityViewFlipper, false);
mSecurityViewFlipper.addView(v);
updateSecurityView(v);
view = (KeyguardSecurityView)v;
view.reset();
}
return view;
}
如何滑动控制显示?
上面的内容已经能够看到解锁相关的视图也是添加到NotificationShadeWindowView
这个父控件之下的,那被控制相关的猜测多多少少都和这个有关系,断点查看调用栈
从onTouch
这里开始,也就是NotificationPanelViewController
类中,会去调用super.onTouch
@Override
public boolean onTouch(View v, MotionEvent event) {
//...省略
handled |= super.onTouch(v, event);
return !mDozing || mPulsing || handled;
}
查看继承关系是继承自PancelViewControlller
的,继续追溯父类
在父类的touch
中当判定非误触等一系列判断值通过之后就会调用到setExpandedHeightInternal
case MotionEvent.ACTION_MOVE:
//...省略代码
if (!mJustPeeked && (!mGestureWaitForTouchSlop || mTracking)
&& !isTrackingBlocked()) {
setExpandedHeightInternal(newHeight);
}
break;
调用到PanelViewController
public void setExpandedHeightInternal(float h) {
//...省略
onHeightUpdated(mExpandedHeight);
notifyBarPanelExpansionChanged();
}
protected void notifyBarPanelExpansionChanged() {
//...省略
for (int i = 0; i < mExpansionListeners.size(); i++) {
mExpansionListeners.get(i).onPanelExpansionChanged(mExpandedFraction, mTracking);
}
}
然后就调用到锁屏相关了StatusBarKeyguardViewManager
@Override
public void onPanelExpansionChanged(float expansion, boolean tracking) {
//...省略
if (expansion != KeyguardBouncer.EXPANSION_HIDDEN && tracking
&& !mKeyguardStateController.canDismissLockScreen()
&& !mBouncer.isShowing() && !mBouncer.isAnimatingAway()) {
mBouncer.show(false /* resetSecuritySelection */, false /* scrimmed */);
}
//...省略
}
通过KeyguardBouncer
去控制锁屏视图,这里post
了一个Runnable
,在Runnable
中把当前mRoot
显示
并且根据不同条件,最终会调用Keyguard
视图的显示动画.
public void show(boolean resetSecuritySelection, boolean isScrimmed) {
//...省略
if (mKeyguardStateController.isFaceAuthEnabled() && !needsFullscreenBouncer()
&& !mKeyguardUpdateMonitor.userNeedsStrongAuth()
&& !mKeyguardBypassController.getBypassEnabled()) {
mHandler.postDelayed(mShowRunnable, BOUNCER_FACE_DELAY);
} else {
DejankUtils.postAfterTraversal(mShowRunnable);
}
//...省略
}
private final Runnable mShowRunnable = new Runnable() {
@Override
public void run() {
mRoot.setVisibility(View.VISIBLE);
//...省略代码
if (mKeyguardView.getHeight() != 0 && mKeyguardView.getHeight() != mStatusBarHeight) {
mKeyguardView.startAppearAnimation();
} else {
mKeyguardView.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
mKeyguardView.getViewTreeObserver().removeOnPreDrawListener(this);
mKeyguardView.startAppearAnimation();
return true;
}
});
mKeyguardView.requestLayout();
}
mShowingSoon = false;
if (mExpansion == EXPANSION_VISIBLE) {
mKeyguardView.onResume();
mKeyguardView.resetSecurityContainer();
showPromptReason(mBouncerPromptReason);
}
}
};
比如KeyguardPINView
的显示动画就一个进入的位移动画
public void startAppearAnimation() {
enableClipping(false);
setAlpha(1f);
setTranslationY(mAppearAnimationUtils.getStartTranslation());
AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */, 500 /* duration */,
0, mAppearAnimationUtils.getInterpolator());
mAppearAnimationUtils.startAnimation2d(mViews,
new Runnable() {
@Override
public void run() {
enableClipping(true);
}
});
}
以上就是通过manager
中去滑动屏幕显示keyguard的流程了
锁屏布局内部View解析之,玻璃效果
在文章一开始的时候有一个布局的树状图
里面能看到在NotificationShadeWindowView
包含了很多个布局,我们就看看代码中是在哪里实现的.
- 布局文件
super_notification_shadow.xml
中会使用NotificationShadeWindowView
视图,然后这个xml
文件就会包含我们一开始的布局树状图里面所有子View
了. - 这个
xml
文件是在哪里被解析使用的?- 会在
StausBar中
通过调用SuperStatusBarViewFactory
类的方法来创建得到,这样视图被解析后,那么后续这个xml
文件中所有自定义View的逻辑就会开始跑起来.
- 会在
public class StatusBar {
//...省略
protected NotificationShadeWindowView mNotificationShadeWindowView;
//...省略
private void inflateStatusBarWindow() {
mNotificationShadeWindowView = mSuperStatusBarViewFactory.getNotificationShadeWindowView();
//...省略
}
//...省略
}
public class SuperStatusBarViewFactory {
public NotificationShadeWindowView getNotificationShadeWindowView() {
//...省略
mNotificationShadeWindowView = (NotificationShadeWindowView)
mInjectionInflationController.injectable(
LayoutInflater.from(mContext)).inflate(R.layout.super_notification_shade,
/* root= */ null);
//...省略
return mNotificationShadeWindowView;
}
}
- 对应的
xml
文件代码如下,注意:以下代码简化了很多宽度高度等代码,主要是为了方便查看视图嵌套关系
<com.android.systemui.statusbar.phone.NotificationShadeWindowView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:sysui="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<com.android.systemui.statusbar.BackDropView
android:id="@+id/backdrop" >
<ImageView android:id="@+id/backdrop_back" />
<ImageView android:id="@+id/backdrop_front" />
</com.android.systemui.statusbar.BackDropView>
<!--玻璃效果背景,锁屏展示后,这个视图内部会通过动态设置Drawble的形式,展示类似于半透明黑色遮罩,或者毛玻璃效果等,视定制内容而定-->
<com.android.systemui.statusbar.ScrimView
android:id="@+id/scrim_behind" />
<include layout="@layout/status_bar_expanded" />
<include layout="@layout/brightness_mirror" />
<!--玻璃前景,也是为了视图效果-->
<com.android.systemui.statusbar.ScrimView
android:id="@+id/scrim_in_front"/>
<LinearLayout
android:id="@+id/lock_icon_container">
<com.android.systemui.statusbar.phone.LockIcon
android:id="@+id/lock_icon"/>
<com.android.keyguard.KeyguardMessageArea
android:id="@+id/keyguard_message_area" />
</LinearLayout>
</com.android.systemui.statusbar.phone.NotificationShadeWindowView>