学习笔记:
滑动解锁相对于来说逻辑还是简单的,说白了就是对事件的处理,然后做一些事。
这里主要从锁屏的界面Layout结构、touchEvent事件分发、解锁动作逻辑几个方面进行源码的分析。
锁屏的界面Layout结构分析
StatusbarWindowView
整个锁屏界面的顶级 View 就是 StatusbarWindowView;
StatusBar#createAndAddWindows()
// StatusBar.java public void createAndAddWindows(@Nullable RegisterStatusBarResult result) { makeStatusBarView(result); mNotificationShadeWindowController.attach(); // 添加视图 mStatusBarWindowController.attach(); }
StatusBarWindowController#attach()
// StatusBarWindowController.java public void attach() { // Now that the status bar window encompasses the sliding panel and its // translucent backdrop, the entire thing is made TRANSLUCENT and is // hardware-accelerated. mLp = new WindowManager.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, mBarHeight, WindowManager.LayoutParams.TYPE_STATUS_BAR, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH | WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, PixelFormat.TRANSLUCENT); mLp.privateFlags |= PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC; mLp.token = new Binder(); mLp.gravity = Gravity.TOP; mLp.setFitInsetsTypes(0 /* types */); mLp.setTitle("StatusBar"); mLp.packageName = mContext.getPackageName(); mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; mWindowManager.addView(mStatusBarView, mLp); mLpChanged.copyFrom(mLp); }
StatusBarWindow 是在 StatusBar 的 createAndAddWindows() 流程中调用StatusBarWindowController.attach() 添加到窗口上的, type为WindowManager.LayoutParams.TYPE_STATUS_BAR
Layout结构
锁屏界面的Layout结构可以简单概括为以下结构:
- mStatusBarWindow--> R.layout.super_status_bar
- notification_panel--> R.layout.status_bar_expanded
- keyguardBouncer-->R.layout.keyguard_bouncer
mStatusBarWindow-->notification_panel-->notification_container_parent-->keyguard_header(锁屏状态栏) | | | -->keyguard_bottom_area (lock_icon和充电状态等) | | | -->keyguard_status_view (锁屏时钟日期) | | | -->keyguard_up_slide (箭头提示动画) | -->keyguardBouncer(安全锁界面)
在这里 keyguardBouncer 加载就不分析了,前面有说过的,见锁屏加载分析。
touchEvent事件分发
我们这里分析上滑解锁过程中的touchEvent事件分发
android中的事件分发概念:事件序列。
事件序列
在Android系统中,一个单独的事件基本上是没什么作用的,只有一个事件序列,才有意义。一个事件序列正常情况下,定义为 DOWN、MOVE(0或者多个)、UP/CANCEL。事件序列以DOWN事件开始,中间会有0或者多个MOVE事件,最后以UP事件或者CANCEL事件结束。
DOWN事件作为序列的开始,有一个很重要的职责,就是寻找事件序列的接受者,怎么理解呢?framework 在DOWN事件的传递过程中,需要根据View事件处理方法(onTouchEvent)的返回值来确定事件序列的接受者。如果一个View的onTouchEvent事件,在处理DOWN事件的时候返回true,说明它愿意接受并处理该事件序列。
上滑解锁
当用户移动手指时,产生touch down事件,最外层view StatusBarWindowView会执行onInterceptTouchEvent,看是否需要拦截touch事件。再一级级往子View传递,都没有被拦截,之后执行OnTouchEvent从子View开始一级级往父View传递,到PanelView这里当手指移动的距离达到一定的阈值会调用onTrackingStarted从而设置mTracking的值为true,onTouchEvent返回true,接收此touch move事件,之后的touch事件直接传到此View。
在用户滑动过程会调用setExpandedHeightInternal,进而调用NotificationPanelView的onHeightUpdated进行锁屏上的时间和通知View根据手指的移动距离进行缩小、变透明处理。
当用户抬起手指时,产生touch up事件,PanelView接收到这个事件后会调用endMotionEvent,如果手指从down到up之间移动的距离达到一定阈值会调用onTrackingStopped。
1.硬件发出指令:按下,移动,抬起
2.input接收
3.代码执行相应操作:ACTION_DOWN,ACTION_MOVE,ACTION_UP
PanelView#onInterceptTouchEvent()
// PanelView.java @Override public boolean onInterceptTouchEvent(MotionEvent event) { return mTouchHandler.onInterceptTouchEvent(event); }
PanelViewController
// PanelViewController.java public class TouchHandler implements View.OnTouchListener { public boolean onInterceptTouchEvent(MotionEvent event) { if (mInstantExpanding || !mNotificationsDragEnabled || mTouchDisabled || (mMotionAborted && event.getActionMasked() != MotionEvent.ACTION_DOWN)) { return false; } /* * If the user drags anywhere inside the panel we intercept it if the movement is * upwards. This allows closing the shade from anywhere inside the panel. * * We only do this if the current content is scrolled to the bottom, * i.e canCollapsePanelOnTouch() is true and therefore there is no conflicting scrolling * gesture * possible. */ int pointerIndex = event.findPointerIndex(mTrackingPointer); if (pointerIndex < 0) { pointerIndex = 0; mTrackingPointer = event.getPointerId(pointerIndex); } final float x = event.getX(pointerIndex); final float y = event.getY(pointerIndex); boolean canCollapsePanel = canCollapsePanelOnTouch(); switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: mStatusBar.userActivity(); mAnimatingOnDown = mHeightAnimator != null; mMinExpandHeight = 0.0f; mDownTime = SystemClock.uptimeMillis(); if (mAnimatingOnDown && mClosing && !mHintAnimationRunning || mPeekAnimator != null) { cancelHeightAnimator(); cancelPeek(); mTouchSlopExceeded = true; return true; } mInitialTouchY = y; mInitialTouchX = x; mTouchStartedInEmptyArea = !isInContentBounds(x, y); mTouchSlopExceeded = mTouchSlopExceededBeforeDown; mJustPeeked = false; mMotionAborted = false; mPanelClosedOnDown = isFullyCollapsed(); mCollapsedAndHeadsUpOnDown = false; mHasLayoutedSinceDown = false; mUpdateFlingOnLayout = false; mTouchAboveFalsingThreshold = false; addMovement(event); break; case MotionEvent.ACTION_POINTER_UP: final int upPointer = event.getPointerId(event.getActionIndex()); if (mTrackingPointer == upPointer) { // gesture is ongoing, find a new pointer to track final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; mTrackingPointer = event.getPointerId(newIndex); mInitialTouchX = event.getX(newIndex); mInitialTouchY = event.getY(newIndex); } break; case MotionEvent.ACTION_POINTER_DOWN: if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) { mMotionAborted = true; mVelocityTracker.clear(); } break; case MotionEvent.ACTION_MOVE: final float h = y - mInitialTouchY; addMovement(event); if (canCollapsePanel || mTouchStartedInEmptyArea || mAnimatingOnDown) { float hAbs = Math.abs(h); float touchSlop = getTouchSlop(event); if ((h < -touchSlop || (mAnimatingOnDown && hAbs > touchSlop)) && hAbs > Math.abs(x - mInitialTouchX)) { cancelHeightAnimator(); startExpandMotion(x, y, true /* startTracking */, mExpandedHeight); return true; } } break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: mVelocityTracker.clear(); break; } return false; } @Override public boolean onTouch(View v, MotionEvent event) { if (mInstantExpanding || (mTouchDisabled && event.getActionMasked() != MotionEvent.ACTION_CANCEL) || (mMotionAborted && event.getActionMasked() != MotionEvent.ACTION_DOWN)) { return false; } // If dragging should not expand the notifications shade, then return false. if (!mNotificationsDragEnabled) { if (mTracking) { // Turn off tracking if it's on or the shade can get stuck in the down position. onTrackingStopped(true /* expand */); } return false; } // On expanding, single mouse click expands the panel instead of dragging. if (isFullyCollapsed() && event.isFromSource(InputDevice.SOURCE_MOUSE)) { if (event.getAction() == MotionEvent.ACTION_UP) { expand(true); } return true; } /* * We capture touch events here and update the expand height here in case according to * the users fingers. This also handles multi-touch. * * If the user just clicks shortly, we show a quick peek of the shade. * * Flinging is also enabled in order to open or close the shade. */ int pointerIndex = event.findPointerIndex(mTrackingPointer); if (pointerIndex < 0) { pointerIndex = 0; mTrackingPointer = event.getPointerId(pointerIndex); } final float x = event.getX(pointerIndex); final float y = event.getY(pointerIndex); if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { mGestureWaitForTouchSlop = shouldGestureWaitForTouchSlop(); mIgnoreXTouchSlop = isFullyCollapsed() || shouldGestureIgnoreXTouchSlop(x, y); } switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: startExpandMotion(x, y, false /* startTracking */, mExpandedHeight); mJustPeeked = false; mMinExpandHeight = 0.0f; mPanelClosedOnDown = isFullyCollapsed(); mHasLayoutedSinceDown = false; mUpdateFlingOnLayout = false; mMotionAborted = false; mPeekTouching = mPanelClosedOnDown; mDownTime = SystemClock.uptimeMillis(); mTouchAboveFalsingThreshold = false; mCollapsedAndHeadsUpOnDown = isFullyCollapsed() && mHeadsUpManager.hasPinnedHeadsUp(); addMovement(event); if (!mGestureWaitForTouchSlop || (mHeightAnimator != null && !mHintAnimationRunning) || mPeekAnimator != null) { mTouchSlopExceeded = (mHeightAnimator != null && !mHintAnimationRunning) || mPeekAnimator != null || mTouchSlopExceededBeforeDown; cancelHeightAnimator(); cancelPeek(); onTrackingStarted(); } if (isFullyCollapsed() && !mHeadsUpManager.hasPinnedHeadsUp() && !mStatusBar.isBouncerShowing()) { startOpening(event); } break; case MotionEvent.ACTION_POINTER_UP: final int upPointer = event.getPointerId(event.getActionIndex()); if (mTrackingPointer == upPointer) { // gesture is ongoing, find a new pointer to track final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; final float newY = event.getY(newIndex); final float newX = event.getX(newIndex); mTrackingPointer = event.getPointerId(newIndex); startExpandMotion(newX, newY, true /* startTracking */, mExpandedHeight); } break; case MotionEvent.ACTION_POINTER_DOWN: if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) { mMotionAborted = true; endMotionEvent(event, x, y, true /* forceCancel */); return false; } break; case MotionEvent.ACTION_MOVE: addMovement(event); float h = y - mInitialTouchY; // If the panel was collapsed when touching, we only need to check for the // y-component of the gesture, as we have no conflicting horizontal gesture. if (Math.abs(h) > getTouchSlop(event) && (Math.abs(h) > Math.abs(x - mInitialTouchX) || mIgnoreXTouchSlop)) { mTouchSlopExceeded = true; if (mGestureWaitForTouchSlop && !mTracking && !mCollapsedAndHeadsUpOnDown) { if (!mJustPeeked && mInitialOffsetOnTouch != 0f) { startExpandMotion(x, y, false /* startTracking */, mExpandedHeight); h = 0; } cancelHeightAnimator(); // 向上滑动时,手指移动的距离达到一定的阈值会调用onTrackingStarted, // 设置mTracking值为true,从而接收touch事件 onTrackingStarted(); } } float newHeight = Math.max(0, h + mInitialOffsetOnTouch); if (newHeight > mPeekHeight) { if (mPeekAnimator != null) { mPeekAnimator.cancel(); } mJustPeeked = false; } else if (mPeekAnimator == null && mJustPeeked) { // The initial peek has finished, but we haven't dragged as far yet, lets // speed it up by starting at the peek height. mInitialOffsetOnTouch = mExpandedHeight; mInitialTouchY = y; mMinExpandHeight = mExpandedHeight; mJustPeeked = false; } newHeight = Math.max(newHeight, mMinExpandHeight); if (-h >= getFalsingThreshold()) { mTouchAboveFalsingThreshold = true; mUpwardsWhenThresholdReached = isDirectionUpwards(x, y); } if (!mJustPeeked && (!mGestureWaitForTouchSlop || mTracking) && !isTrackingBlocked()) { // 用户滑动过程会调用setExpandedHeightInternal setExpandedHeightInternal(newHeight); } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: addMovement(event); endMotionEvent(event, x, y, false /* forceCancel */); break; } return !mGestureWaitForTouchSlop || mTracking; } }
移动过程中:主要在调用了两个方法。
// PanelViewController.java protected void onTrackingStarted() { endClosing(); mTracking = true; mBar.onTrackingStarted(); notifyExpandingStarted(); notifyBarPanelExpansionChanged(); }
// PanelViewController.java public void setExpandedHeightInternal(float h) { if (isNaN(h)) { Log.wtf(TAG, "ExpandedHeight set to NaN"); } if (mExpandLatencyTracking && h != 0f) { DejankUtils.postAfterTraversal( () -> mLatencyTracker.onActionEnd(LatencyTracker.ACTION_EXPAND_PANEL)); mExpandLatencyTracking = false; } float fhWithoutOverExpansion = getMaxPanelHeight() - getOverExpansionAmount(); if (mHeightAnimator == null) { float overExpansionPixels = Math.max(0, h - fhWithoutOverExpansion); if (getOverExpansionPixels() != overExpansionPixels && mTracking) { setOverExpansion(overExpansionPixels, true /* isPixels */); } mExpandedHeight = Math.min(h, fhWithoutOverExpansion) + getOverExpansionAmount(); } else { mExpandedHeight = h; if (mOverExpandedBeforeFling) { setOverExpansion(Math.max(0, h - fhWithoutOverExpansion), false /* isPixels */); } } // If we are closing the panel and we are almost there due to a slow decelerating // interpolator, abort the animation. if (mExpandedHeight < 1f && mExpandedHeight != 0f && mClosing) { mExpandedHeight = 0f; if (mHeightAnimator != null) { mHeightAnimator.end(); } } mExpandedFraction = Math.min(1f, fhWithoutOverExpansion == 0 ? 0 : mExpandedHeight / fhWithoutOverExpansion); // 进行锁屏上的时间和通知View根据手指的移动距离进行缩小、变透明处理 onHeightUpdated(mExpandedHeight); notifyBarPanelExpansionChanged(); }
下面主要从:onHeightUpdated、notifyBarPanelExpansionChanged 两方法作为入口。
先看 NotificationPanelViewController#onHeightUpdated()
// NotificationPanelViewController.java @Override protected void onHeightUpdated(float expandedHeight) { if (!mQsExpanded || mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted) { // 更新时钟位置将设置顶部填充,这可能会触发新的面板高度并重新定位时钟。 // 这是一个循环依赖项,应该避免,否则会出现堆栈溢出。 if (mStackScrollerMeasuringPass > 2) { if (DEBUG) Log.d(TAG, "Unstable notification panel height. Aborting."); } else { //锁屏上的时间和通知View根据手指的移动距离进行缩小、变透明处理 positionClockAndNotifications(); } } if (mQsExpandImmediate || mQsExpanded && !mQsTracking && mQsExpansionAnimator == null && !mQsExpansionFromOverscroll) { float t; if (mKeyguardShowing) { // 在Keyguard上,将QS扩展线性插值到面板扩展 t = expandedHeight / (getMaxPanelHeight()); } else { // In Shade, interpolate linearly such that QS is closed whenever panel height is // minimum QS expansion + minStackHeight float panelHeightQsCollapsed = mNotificationStackScroller.getIntrinsicPadding() + mNotificationStackScroller.getLayoutMinHeight(); float panelHeightQsExpanded = calculatePanelHeightQsExpanded(); t = (expandedHeight - panelHeightQsCollapsed) / (panelHeightQsExpanded - panelHeightQsCollapsed); } float targetHeight = mQsMinExpansionHeight + t * (mQsMaxExpansionHeight - mQsMinExpansionHeight); setQsExpansion(targetHeight); } updateExpandedHeight(expandedHeight); updateHeader(); // 更新通知半透明 updateNotificationTranslucency(); updatePanelExpanded(); updateGestureExclusionRect(); if (DEBUG) { mView.invalidate(); } }
到这里了就一起看个滑动解锁的堆栈:
09-19 05:48:41.853 1477 1477 D yexiao : java.lang.Throwable: 09-19 05:48:41.853 1477 1477 D yexiao : at com.android.keyguard.KeyguardSecurityContainer.showNextSecurityScreenOrFinish(KeyguardSecurityContainer.java:710) 09-19 05:48:41.853 1477 1477 D yexiao : at com.android.keyguard.KeyguardHostView.dismiss(KeyguardHostView.java:214) 09-19 05:48:41.853 1477 1477 D yexiao : at com.android.keyguard.KeyguardHostView.dismiss(KeyguardHostView.java:196) 09-19 05:48:41.853 1477 1477 D yexiao : at com.android.systemui.statusbar.phone.KeyguardBouncer.show(KeyguardBouncer.java:167) 09-19 05:48:41.853 1477 1477 D yexiao : at com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.showBouncer(StatusBarKeyguardViewManager.java:434) 09-19 05:48:41.853 1477 1477 D yexiao : at com.android.systemui.statusbar.phone.StatusBar.showBouncerIfKeyguard(StatusBar.java:3959) 09-19 05:48:41.853 1477 1477 D yexiao : at com.android.systemui.statusbar.phone.StatusBar.makeExpandedInvisible(StatusBar.java:2506) 09-19 05:48:41.853 1477 1477 D yexiao : at com.android.systemui.statusbar.phone.PhoneStatusBarView$1.run(PhoneStatusBarView.java:65) 09-19 05:48:41.853 1477 1477 D yexiao : at android.os.Handler.handleCallback(Handler.java:938) 09-19 05:48:41.853 1477 1477 D yexiao : at android.os.Handler.dispatchMessage(Handler.java:99) 09-19 05:48:41.853 1477 1477 D yexiao : at android.os.Looper.loop(Looper.java:223) 09-19 05:48:41.853 1477 1477 D yexiao : at android.app.ActivityThread.main(ActivityThread.java:7945) 09-19 05:48:41.853 1477 1477 D yexiao : at java.lang.reflect.Method.invoke(Native Method) 09-19 05:48:41.853 1477 1477 D yexiao : at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:603) 09-19 05:48:41.853 1477 1477 D yexiao : at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
再看一个滑动后到密码安全锁(即密码解锁)的堆栈:
09-17 11:20:35.891 1473 1473 D yexiao: : java.lang.Throwable 09-17 11:20:35.891 1473 1473 D yexiao: : at com.android.keyguard.KeyguardSecurityContainer.showNextSecurityScreenOrFinish(KeyguardSecurityContainer.java:710) 09-17 11:20:35.891 1473 1473 D yexiao: : at com.android.keyguard.KeyguardHostView.dismiss(KeyguardHostView.java:214) 09-17 11:20:35.891 1473 1473 D yexiao: : at com.android.keyguard.KeyguardHostView.dismiss(KeyguardHostView.java:196) 09-17 11:20:35.891 1473 1473 D yexiao: : at com.android.systemui.statusbar.phone.KeyguardBouncer.show(KeyguardBouncer.java:167) 09-17 11:20:35.891 1473 1473 D yexiao: : at com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.onPanelExpansionChanged(StatusBarKeyguardViewManager.java:297) 09-17 11:20:35.891 1473 1473 D yexiao: : at com.android.systemui.statusbar.phone.PanelViewController.notifyBarPanelExpansionChanged(PanelViewController.java:1011) 09-17 11:20:35.891 1473 1473 D yexiao: : at com.android.systemui.statusbar.phone.PanelViewController.setExpandedHeightInternal(PanelViewController.java:727) 09-17 11:20:35.891 1473 1473 D yexiao: : at com.android.systemui.statusbar.phone.PanelViewController$TouchHandler.onTouch(PanelViewController.java:1338) 09-17 11:20:35.891 1473 1473 D yexiao: : at com.android.systemui.statusbar.phone.NotificationPanelViewController$18.onTouch(NotificationPanelViewController.java:3229) 09-17 11:20:35.891 1473 1473 D yexiao: : at android.view.View.dispatchTouchEvent(View.java:14385) 09-17 11:20:35.891 1473 1473 D yexiao: : at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3120) 09-17 11:20:35.891 1473 1473 D yexiao: : at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2792) 09-17 11:20:35.891 1473 1473 D yexiao: : at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3126) 09-17 11:20:35.891 1473 1473 D yexiao: : at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2806) 09-17 11:20:35.891 1473 1473 D yexiao: : at com.android.systemui.statusbar.phone.NotificationShadeWindowView.dispatchTouchEvent(NotificationShadeWindowView.java:173) 09-17 11:20:35.891 1473 1473 D yexiao: : at android.view.View.dispatchPointerEvent(View.java:14656) // 省略部分Log....