android framework13 您所在的位置:网站首页 android商城下拉菜单 android framework13

android framework13

2024-02-24 20:06| 来源: 网络整理| 查看: 265

这里讲的是下拉状态栏看到的东西,包括statusBar,quick setting,以及下边的通知。当然了,上边的statusBar还有一种完全展开的状态,下边还显示一个屏幕亮度控制的控件。quick setting面板也可以完全展开。

image.png

下边是完全拉开的状态,通知栏不见了,quick setting面板完全可见

image.png

1.NotificationShadeWindowView

这个控件就是下拉通知栏看到的根容器了,分析下如何添加到窗口

CentralSurfacesImpl.java

private void inflateStatusBarWindow() { //... ## 注解获取,通过inflate加载布局,返回根布局控件 mNotificationShadeWindowView = mCentralSurfacesComponent.getNotificationShadeWindowView(); ## 这个controller是通过注解实例化的,构造方法里包含上边的view mNotificationShadeWindowViewController = mCentralSurfacesComponent.getNotificationShadeWindowViewController(); ## 把mNotificationShadeWindowView赋值给controller mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView); ## 就是方法名字,处理expanded status bar mNotificationShadeWindowViewController.setupExpandedStatusBar(); public void createAndAddWindows(@Nullable RegisterStatusBarResult result) { makeStatusBarView(result); mNotificationShadeWindowController.attach();//作用是addView(mNotificationShadeView) mStatusBarWindowController.attach(); } 1.1.通过注解实例化

StatusBarViewModule.java

@Provides @CentralSurfacesComponent.CentralSurfacesScope public static NotificationShadeWindowView providesNotificationShadeWindowView( LayoutInflater layoutInflater) { NotificationShadeWindowView notificationShadeWindowView = (NotificationShadeWindowView) layoutInflater.inflate(R.layout.super_notification_shade, /* root= */ null); //... return notificationShadeWindowView; } 1.2.添加到窗口

NotificationShadeWindowControllerImpl.java 的attach方法,通过windowManager添加的,

public void attach() { //... mWindowManager.addView(mNotificationShadeView, mLp); //... } @Override public void setNotificationShadeView(ViewGroup view) { mNotificationShadeView = view; } 1.3.super_notification_shade.xml

下边看下相关的布局,简单了解下层级结构

#FrameLayout ## 展开的状态栏 ## FrameLayout ##textview >status_bar_expanded.xml # 用户切换头像,可点击,显示与否有条件,具体见后边分析 ## FrameLayout ,match/wrap ## ConstraintLayout ## 空的 # 通知相关的都在这个容器里,这是个自定义的ViewGroup ## 空 >>keyguard_qs_user_switch.xml

这个显示是有条件的,具体逻辑后边有分析,下图贴出了显示的效果,那个头像点击以后就会弹出一个对话框,可以切换用户,效果和qs面板底部那个用户头像的点击效果一样。

image.png

>>keyguard_status_view

锁屏界面那个钟表的界面

image.png

>>keyguard_status_bar

锁屏页面的状态栏

image.png

# 图上右侧部分 # 这个好像是左侧的文字提示,比如没有sim卡,手机没信号啥的 >keyguard_bottom_area

这个是锁屏页面,底部展示的内容,比如提示文字,左右2侧可以展示2个图标快速启动某些app,不过都需要在设置里配置的,默认是没有的。

image.png

比如提示文字hello,默认是空的,可以在settings里,自己输入自己想显示的内容,还可以选择锁屏界面是否显示通知,

image.png

看布局有2行文字,一行是我们在设置里的自己定义的,还有一行是系统提示,比如swipe up to open(这个一般点击一下屏幕就会提示的)

## FrameLayout ##空的 #空的 >>keyguard_user_switcher

下图就是显示的位置,这种和上边那个keyguard_qs_user_switcher的区别是,这种点击那个头像,不是弹个对话框,而是展开列表,效果见图 image.png

image.png

>user_switcher

上边布局里有2种user_switcher,具体显示哪个,还是都不显示,研究下代码里咋处理的。

首先设置里要开启多用户,如下图

image.png NotificationPanelViewController.java

## 这个方法是在构造方法末尾调用的 void onFinishInflate() { loadDimens(); mKeyguardStatusBar = mView.findViewById(R.id.keyguard_header); FrameLayout userAvatarContainer = null; KeyguardUserSwitcherView keyguardUserSwitcherView = null; # 根据配置里的boolean值,决定加载哪个布局还是都不加载 if (mKeyguardUserSwitcherEnabled && mUserManager.isUserSwitcherEnabled( mResources.getBoolean(R.bool.qs_show_user_switcher_for_single_user))) { if (mKeyguardQsUserSwitchEnabled) { ViewStub stub = mView.findViewById(R.id.keyguard_qs_user_switch_stub); userAvatarContainer = (FrameLayout) stub.inflate(); } else { ViewStub stub = mView.findViewById(R.id.keyguard_user_switcher_stub); keyguardUserSwitcherView = (KeyguardUserSwitcherView) stub.inflate(); } } mKeyguardStatusBarViewController = mKeyguardStatusBarViewComponentFactory.build( mKeyguardStatusBar, mNotificationPanelViewStateProvider) .getKeyguardStatusBarViewController(); mKeyguardStatusBarViewController.init(); mNotificationContainerParent = mView.findViewById(R.id.notification_container_parent); # 2种view,里边对应两种Controller来加载头像。 updateViewControllers( mView.findViewById(R.id.keyguard_status_view), userAvatarContainer, #先判断这个不为null就使用这个,if keyguardUserSwitcherView); #再判断这个是否为null,else if

配置读取: qs_show_user_switcher_for_single_user 【sw600dp 是true,默认的是false】 config_keyguardUserSwitcher【values-sw600dp下是true,values下是false】

private void updateUserSwitcherFlags() { mKeyguardUserSwitcherEnabled = mResources.getBoolean( com.android.internal.R.bool.config_keyguardUserSwitcher); mKeyguardQsUserSwitchEnabled = mKeyguardUserSwitcherEnabled && mFeatureFlags.isEnabled(Flags.QS_USER_DETAIL_SHORTCUT); }

还有一个reInflateViews方法用到,showQsUserSwitch为true,显示的是qs_user_switch那个布局,showKeyguardUserSwitcher为true,显示的是user_switch那个布局。

void reInflateViews() { //... // Re-inflate the keyguard user switcher group. updateUserSwitcherFlags(); boolean isUserSwitcherEnabled = mUserManager.isUserSwitcherEnabled( mResources.getBoolean(R.bool.qs_show_user_switcher_for_single_user)); boolean showQsUserSwitch = mKeyguardQsUserSwitchEnabled && isUserSwitcherEnabled; boolean showKeyguardUserSwitcher = !mKeyguardQsUserSwitchEnabled && mKeyguardUserSwitcherEnabled && isUserSwitcherEnabled; FrameLayout userAvatarView = (FrameLayout) reInflateStub( R.id.keyguard_qs_user_switch_view /* viewId */, R.id.keyguard_qs_user_switch_stub /* stubId */, R.layout.keyguard_qs_user_switch /* layoutId */, showQsUserSwitch /* enabled */); // 测试效果可以直接把这里改为true或false KeyguardUserSwitcherView keyguardUserSwitcherView = (KeyguardUserSwitcherView) reInflateStub( R.id.keyguard_user_switcher_view /* viewId */, R.id.keyguard_user_switcher_stub /* stubId */, R.layout.keyguard_user_switcher /* layoutId */, showKeyguardUserSwitcher /* enabled */); // 测试效果可以直接把这里改为true或false updateViewControllers(mView.findViewById(R.id.keyguard_status_view), userAvatarView, keyguardUserSwitcherView);

Flags.QS_USER_DETAIL_SHORTCUT 的值

false >NotificationsQuickSettingsContainer

下拉通知栏的核心内容都在这个布局里了

## ConstraintLayout #quick setting 内容,另一篇介绍 # 通知相关的都在这个容器里,这是个自定义的ViewGroup >NotificationStackScrollLayout

自定义的容器,通知相关的都在这里,就是开头第一张图,下边那块白色的部分。

/** * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack. */ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable { 1.4.TouchEvent

触摸事件优先交给小节2.1里的handler处理,没有处理的话自己处理。

public boolean dispatchTouchEvent(MotionEvent ev) { //分发事件前先自己处理,见2.2小节 Boolean result = mInteractionEventHandler.handleDispatchTouchEvent(ev); //自己没有处理才交给下层 result = result != null ? result : super.dispatchTouchEvent(ev); mInteractionEventHandler.dispatchTouchEventComplete(); return result; } public boolean onInterceptTouchEvent(MotionEvent ev) { boolean intercept = mInteractionEventHandler.shouldInterceptTouchEvent(ev); if (!intercept) { //交给自己下层处理 intercept = super.onInterceptTouchEvent(ev); } if (intercept) { mInteractionEventHandler.didIntercept(ev); } return intercept; } @Override public boolean onTouchEvent(MotionEvent ev) { //先自己处理 boolean handled = mInteractionEventHandler.handleTouchEvent(ev); if (!handled) { //自己没处理,交给下层处理 handled = super.onTouchEvent(ev); } if (!handled) { mInteractionEventHandler.didNotHandleTouchEvent(ev); } return handled; } 1.5.InteractionEventHandler

触摸交互事件的接口,具体实现在2.2

interface InteractionEventHandler { /** * Returns a result for {@link ViewGroup#dispatchTouchEvent(MotionEvent)} or null to defer * to the super method. */ Boolean handleDispatchTouchEvent(MotionEvent ev); /** * Called after all dispatching is done. */ void dispatchTouchEventComplete(); /** * Returns if the view should intercept the touch event. * * The touch event may still be interecepted if * {@link ViewGroup#onInterceptTouchEvent(MotionEvent)} decides to do so. */ boolean shouldInterceptTouchEvent(MotionEvent ev); /** * Called when the view decides to intercept the touch event. */ void didIntercept(MotionEvent ev); boolean handleTouchEvent(MotionEvent ev); void didNotHandleTouchEvent(MotionEvent ev); boolean interceptMediaKey(KeyEvent event); boolean dispatchKeyEvent(KeyEvent event); boolean dispatchKeyEventPreIme(KeyEvent event); } 2.NotificationShadeWindowViewController.java

这个类核心方法是setupExpandedStatusBar(),处理了NotificationShadeWindowView的touch事件,listener方法太多,这里就贴下截图 mView就是NotificationShadeWindowView

image.png

2.1.setupExpandedStatusBar

下边这行是设置下拉的的手势操作

mView.setInteractionEventHandler(new NotificationShadeWindowView.InteractionEventHandler() {//见2.2详细分析 //... //dragHelper在上边的InteractionEventHandler里用到 setDragDownHelper(mLockscreenShadeTransitionController.getTouchHelper()); 2.2.InteractionEventHandler >shouldInterceptTouchEvent

是否拦截触摸事件,一层层判断

public boolean shouldInterceptTouchEvent(MotionEvent ev) { if (mStatusBarStateController.isDozing() && !mService.isPulsing() && !mDockManager.isDocked()) { // Capture all touch events in always-on. return true; } if (mStatusBarKeyguardViewManager.isShowingAlternateBouncer()) { // capture all touches if the alt auth bouncer is showing return true; } //先判断锁屏icon if (mLockIconViewController.onInterceptTouchEvent(ev)) { // immediately return true; don't send the touch to the drag down helper return true; } boolean intercept = false; //这里就是锁屏界面了,交给mDragDownHelper处理 if (mNotificationPanelViewController.isFullyExpanded() && mDragDownHelper.isDragDownEnabled() && !mService.isBouncerShowing() && !mStatusBarStateController.isDozing()) { intercept = mDragDownHelper.onInterceptTouchEvent(ev); } return intercept; } >didIntercept

上边的intercept返回true,那么执行这个,发送一个cancel的事件

public void didIntercept(MotionEvent ev) { MotionEvent cancellation = MotionEvent.obtain(ev); cancellation.setAction(MotionEvent.ACTION_CANCEL); //这个是通知栏 mStackScrollLayout.onInterceptTouchEvent(cancellation); //这个是整个下拉的状态栏,包括qs mNotificationPanelViewController.sendInterceptTouchEventToView(cancellation); cancellation.recycle(); } >handleTouchEvent public boolean handleTouchEvent(MotionEvent ev) { boolean handled = false; if (mStatusBarStateController.isDozing()) { handled = !mService.isPulsing(); } if (mStatusBarKeyguardViewManager.isShowingAlternateBouncer()) { // eat the touch handled = true; } if ((mDragDownHelper.isDragDownEnabled() && !handled) || mDragDownHelper.isDraggingDown()) { //交给mDragDownHelper处理 handled = mDragDownHelper.onTouchEvent(ev); } return handled; } >didNotHandleTouchEvent

没人处理touch事件

public void didNotHandleTouchEvent(MotionEvent ev) { final int action = ev.getActionMasked(); if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { mService.setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false); } } >handleDispatchTouchEvent

这个是直接不处理,也就是shouldInterceptTouchEvent返回false以后,往下分发事件,里边会先调用这个方法,见1.4小节调用。没人处理的话返回null

public Boolean handleDispatchTouchEvent(MotionEvent ev) { if (mStatusBarViewController == null) { // Fix for b/192490822 Log.w(TAG, "Ignoring touch while statusBarView not yet set."); return false; } boolean isDown = ev.getActionMasked() == MotionEvent.ACTION_DOWN; boolean isUp = ev.getActionMasked() == MotionEvent.ACTION_UP; boolean isCancel = ev.getActionMasked() == MotionEvent.ACTION_CANCEL; if(isDown && mNotificationPanelViewController.shouldIgnorTouch(ev)){ //这个是自己加的拦截事件。 return true; } boolean expandingBelowNotch = mExpandingBelowNotch; if (isUp || isCancel) { mExpandingBelowNotch = false; } if (!isCancel && mService.shouldIgnoreTouch()) { return false; } if (isDown) { mTouchActive = true; mTouchCancelled = false; } else if (ev.getActionMasked() == MotionEvent.ACTION_UP || ev.getActionMasked() == MotionEvent.ACTION_CANCEL) { mTouchActive = false; } if (mTouchCancelled || mExpandAnimationRunning) { return false; } if (mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) { //正在进行解锁动画,取消触摸事件 cancelCurrentTouch(); return true; } mFalsingCollector.onTouchEvent(ev); mPulsingWakeupGestureHandler.onTouchEvent(ev); mStatusBarKeyguardViewManager.onTouch(ev); if (mBrightnessMirror != null && mBrightnessMirror.getVisibility() == View.VISIBLE) { // 亮度调节条正在显示 if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) { return false; } } if (isDown) { //点击的是通知列表意外的地方,关闭展开的通知栏。 mNotificationStackScrollLayoutController.closeControlsIfOutsideTouch(ev); } if (mStatusBarStateController.isDozing()) { mService.extendDozePulse(); } //交给锁屏icon处理 mLockIconViewController.onTouchEvent( ev, () -> mService.wakeUpIfDozing( SystemClock.uptimeMillis(), mView, "LOCK_ICON_TOUCH", PowerManager.WAKE_REASON_GESTURE) ); // In case we start outside of the view bounds (below the status bar), we need to // dispatch // the touch manually as the view system can't accommodate for touches outside of // the // regular view bounds. //这个没看懂,mView不是全屏的吗?这个触摸还是跑到屏幕外边? if (isDown && ev.getY() >= mView.getBottom()) { mExpandingBelowNotch = true; expandingBelowNotch = true; } if (expandingBelowNotch) { return mStatusBarViewController.sendTouchToView(ev); } if (!mIsTrackingBarGesture && isDown && mNotificationPanelViewController.isFullyCollapsed()) { float x = ev.getRawX(); float y = ev.getRawY(); if (mStatusBarViewController.touchIsWithinView(x, y)) { if (mStatusBarWindowStateController.windowIsShowing()) { mIsTrackingBarGesture = true; return mStatusBarViewController.sendTouchToView(ev); } else { // it's hidden or hiding, don't send to notification shade. return true; } } } else if (mIsTrackingBarGesture) { final boolean sendToStatusBar = mStatusBarViewController.sendTouchToView(ev); if (isUp || isCancel) { mIsTrackingBarGesture = false; } return sendToStatusBar; } return null; } 3.NotificationPanelView

status_bar_expanded.xml的根容器

public final class NotificationPanelView extends FrameLayout { 3.1.touchEvent public boolean onInterceptTouchEvent(MotionEvent event) { //调用的是4.4里的方法 return mTouchHandler.onInterceptTouchEvent(event); } @Override public void dispatchConfigurationChanged(Configuration newConfig) { super.dispatchConfigurationChanged(newConfig); mOnConfigurationChangedListener.onConfigurationChanged(newConfig); } //handler见 小节4.4 public void setOnTouchListener(NotificationPanelViewController.TouchHandler touchHandler) { super.setOnTouchListener(touchHandler); mTouchHandler = touchHandler; } 4.NotificationPanelViewController 4.1.构造方法

构造方法里有70来个参数,简单看下

@Inject public NotificationPanelViewController(NotificationPanelView view, //... keyguardStateController.addCallback(new KeyguardStateController.Callback() { @Override public void onKeyguardFadingAwayChanged() { updateExpandedHeightToMaxHeight(); } }); //... mView.addOnLayoutChangeListener(new ShadeLayoutChangeListener()); //设置触摸事件,handler见4.4 mView.setOnTouchListener(createTouchHandler()); mView.setOnConfigurationChangedListener(config -> loadDimens()); 4.2.onFinishInflate

下边这个方法是在构造方法末尾调用的

void onFinishInflate() { loadDimens(); mKeyguardStatusBar = mView.findViewById(R.id.keyguard_header); //... // 锁屏界面状态栏控制器 mKeyguardStatusBarViewController = mKeyguardStatusBarViewComponentFactory.build( mKeyguardStatusBar, mNotificationPanelViewStateProvider) .getKeyguardStatusBarViewController(); mKeyguardStatusBarViewController.init(); mNotificationContainerParent = mView.findViewById(R.id.notification_container_parent); //锁屏界面用户切换的显示逻辑 updateViewControllers( mView.findViewById(R.id.keyguard_status_view), userAvatarContainer, keyguardUserSwitcherView); NotificationStackScrollLayout stackScrollLayout = mView.findViewById( R.id.notification_stack_scroller); //attach方法里是对stackScrollLayout的设置以及初始化 mNotificationStackScrollLayoutController.attach(stackScrollLayout); // 这个就是管理下拉通知栏那部分的,这里设置各种监听 mNotificationStackScrollLayoutController.setOnHeightChangedListener( new NsslHeightChangedListener()); mNotificationStackScrollLayoutController.setOverscrollTopChangedListener( mOnOverscrollTopChangedListener); mNotificationStackScrollLayoutController.setOnScrollListener(this::onNotificationScrolled); mNotificationStackScrollLayoutController.setOnStackYChanged(this::onStackYChanged); mNotificationStackScrollLayoutController.setOnEmptySpaceClickListener( mOnEmptySpaceClickListener); addTrackingHeadsUpListener(mNotificationStackScrollLayoutController::setTrackingHeadsUp); //底部区域 setKeyguardBottomArea(mView.findViewById(R.id.keyguard_bottom_area)); //底部view处理的代码,kotlin写的,非常复杂,后边再研究 initBottomArea(); mWakeUpCoordinator.setStackScroller(mNotificationStackScrollLayoutController); mQsFrame = mView.findViewById(R.id.qs_frame); mPulseExpansionHandler.setUp(mNotificationStackScrollLayoutController); //... } 4.3.禁掉下拉状态栏

本来想着在touch事件里处理,感觉有点麻烦,后来想想这玩意下拉的时候是一点点显示的,或者是有个动画来显示,反正肯定有个中间过程动态处理这个控件的高度的,找了一下,如下.

>非锁屏界面

可以直接利用StatusBarManager处理

StatusBarManager statusBarManager = getContext().getSystemService(StatusBarManager.class); //先获取当前的disable信息 Pair pair = statusBarManager.getDisableInfo().toFlags(); int flag1 = pair.first; int flag2 = pair.second; if (mConfig.isHideQSPanel()) { //这个是隐藏QS面板 flag2 |= statusBarManager.DISABLE2_QUICK_SETTINGS; } else { //这个是取消隐藏QS面板 flag2 &= ~statusBarManager.DISABLE2_QUICK_SETTINGS; } if (mConfig.isHideStatusBar()) { //这个就无法下拉了 flag2 |= statusBarManager.DISABLE2_NOTIFICATION_SHADE; } else { //取消设置 flag2 &= ~statusBarManager.DISABLE2_NOTIFICATION_SHADE; } statusBarManager.disable2(flag2); > 没有锁屏界面的简单处理办法

把原本的参数h随便改个名字(比如h2),不用这个值,然后弄个局部变量h为0,那么这个控件就永远显示不出来了。这种有个问题,锁屏界面也都啥也看不见,只有个背景,像时钟,提示文字啥的都没了。

// NotificationPanelViewController.java private void setExpandedHeightInternal(float h2) { final float h=0; >有锁屏界面的 锁屏界面的高度不做处理 // NotificationPanelViewController.java private void setExpandedHeightInternal(float h2) { final float h; if(getKeyguardShowing()||isOnKeyguard()){ h=h2;//锁屏界面不好直接隐藏整个容器,因为还有时钟,锁屏icon等在这个容器里 }else{ h=0;//非锁屏界面,直接等于0就完事了,反正整个容器都不需要可见 }

2. 禁掉锁屏界面的drag手势

LockscreenShadeTransitionController.kt

锁屏界面状态栏以下区域确实下拉没反应,可从状态栏那里往下拉是可以的。 //这个类就是锁屏界面的下拉手势 class DragDownHelper( //... val isDragDownEnabled: Boolean//这个改成false get() = false

需要再加上如下代码,

//NotificationShadeWindowViewController.java mView.setInteractionEventHandler(new NotificationShadeWindowView.InteractionEventHandler() { @Override public Boolean handleDispatchTouchEvent(MotionEvent ev) { if (mStatusBarViewController == null) { // Fix for b/192490822 Log.w(TAG, "Ignoring touch while statusBarView not yet set."); return false; } boolean isDown = ev.getActionMasked() == MotionEvent.ACTION_DOWN; boolean isUp = ev.getActionMasked() == MotionEvent.ACTION_UP; boolean isCancel = ev.getActionMasked() == MotionEvent.ACTION_CANCEL; if(isDown && ev.getY() mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES)); //... mView.setManageButtonClickListener(v -> { if (mNotificationActivityStarter != null) { mNotificationActivityStarter.startHistoryIntent(v, mView.isHistoryShown()); } });

下边是footer布局的加载以及2个按钮的点击事件设置

//NotificationStackScrollLayout.java public void setManageButtonClickListener(@Nullable OnClickListener listener) { mManageButtonClickListener = listener; if (mFooterView != null) { mFooterView.setManageButtonClickListener(mManageButtonClickListener); } } protected void inflateFooterView() { //直接把footer的布局加载进来 FooterView footerView = (FooterView) LayoutInflater.from(mContext).inflate( R.layout.status_bar_notification_footer, this, false); //这里设置了clear按钮的功能 footerView.setClearAllButtonClickListener(v -> { if (mFooterClearAllListener != null) { mFooterClearAllListener.onClearAll(); } clearNotifications(ROWS_ALL, true /* closeShade */); footerView.setSecondaryVisible(false /* visible */, true /* animate */); }); setFooterView(footerView); } 5.3.EmptyShadeView

没有通知的时候显示这个,布局默认是gone的

protected void onFinishInflate() { super.onFinishInflate(); inflateEmptyShadeView(); inflateFooterView(); } private void inflateEmptyShadeView() { EmptyShadeView oldView = mEmptyShadeView; EmptyShadeView view = (EmptyShadeView) LayoutInflater.from(mContext).inflate( R.layout.status_bar_no_notifications, this, false); view.setOnClickListener(v -> { final boolean showHistory = mController.isHistoryEnabled(); Intent intent = showHistory ? new Intent(Settings.ACTION_NOTIFICATION_HISTORY) : new Intent(Settings.ACTION_NOTIFICATION_SETTINGS); mCentralSurfaces.startActivity(intent, true, true, Intent.FLAG_ACTIVITY_SINGLE_TOP); }); setEmptyShadeView(view); updateEmptyShadeView( oldView == null ? R.string.empty_shade_text : oldView.getTextResource(), oldView == null ? 0 : oldView.getFooterTextResource(), oldView == null ? 0 : oldView.getFooterIconResource()); } 5.4.normal 通知

有的通知是可以删除的,如下图的叉号,至于左侧的silent文字也可以点击,跳转到通知的设置页面 image.png

# NotificationStackScrollLayoutController.java 的attch方法 mSilentHeaderController.setOnClearSectionClickListener(v -> clearSilentNotifications());

SectionHeaderController.kt

@SectionHeaderScope internal class SectionHeaderNodeControllerImpl @Inject constructor( @NodeLabel override val nodeLabel: String, private val layoutInflater: LayoutInflater, @HeaderText @StringRes private val headerTextResId: Int, //左侧按钮的文字 private val activityStarter: ActivityStarter, @HeaderClickAction private val clickIntentAction: String //左侧按钮的action ) : NodeController, SectionHeaderController { private var _view: SectionHeaderView? = null private var clearAllButtonEnabled = false //右侧叉号的点击事件 private var clearAllClickListener: View.OnClickListener? = null //左侧按钮的点击事件 private val onHeaderClickListener = View.OnClickListener { activityStarter.startActivity( Intent(clickIntentAction), true /* onlyProvisioned */, true /* dismissShade */, Intent.FLAG_ACTIVITY_SINGLE_TOP) } override fun reinflateView(parent: ViewGroup) { //..没有直接添加到容器,至于哪里加的,暂时没找到 val inflated = layoutInflater.inflate( R.layout.status_bar_notification_section_header, parent, false /* attachToRoot */) as SectionHeaderView inflated.setHeaderText(headerTextResId) inflated.setOnHeaderClickListener(onHeaderClickListener) clearAllClickListener?.let { inflated.setOnClearAllClickListener(it) } if (oldPos != -1) { parent.addView(inflated, oldPos) } _view = inflated _view?.setClearSectionButtonEnabled(clearAllButtonEnabled) }

看下上边reinflateView方法的调用,NotificationSectionsManager.kt 下边一堆一样的方法,用的都是上边SectionHeaderNodeControllerImpl的实例,注解生成的,对应不同的注解

class NotificationSectionsManager @Inject internal constructor( //...下边这几个就是通过注解不同获取不同的对象的 @IncomingHeader private val incomingHeaderController: SectionHeaderController, @PeopleHeader private val peopleHeaderController: SectionHeaderController, @AlertingHeader private val alertingHeaderController: SectionHeaderController, @SilentHeader private val silentHeaderController: SectionHeaderController, //... fun reinflateViews() { silentHeaderController.reinflateView(parent) alertingHeaderController.reinflateView(parent) peopleHeaderController.reinflateView(parent) incomingHeaderController.reinflateView(parent) mediaContainerController.reinflateView(parent) keyguardMediaController.attachSinglePaneContainer(mediaControlsView) }

使用的地方除了Manager自己监听的configchange,还有NotificationStackScrollLayout.java

void reinflateViews() { inflateFooterView(); inflateEmptyShadeView(); updateFooter(); mSectionsManager.reinflateViews();//这里 } >NotificationSectionHeadersModule.kt

上边用到的SectionHeaderController实例都在这个module里,下边都是dagger2的注解语法,不会的需要先研究下,要不可能看不懂咋初始化的

@Provides @SilentHeader @SysUISingleton @JvmStatic fun providesSilentHeaderSubcomponent( builder: Provider ) = builder.get() .nodeLabel("silent header") .headerText(R.string.notification_section_header_gentle) .clickIntentAction(Settings.ACTION_NOTIFICATION_SETTINGS) .build() @Provides @SilentHeader @JvmStatic fun providesSilentHeaderNodeController( @SilentHeader subcomponent: SectionHeaderControllerSubcomponent ) = subcomponent.nodeController @Provides @SilentHeader @JvmStatic fun providesSilentHeaderController( @SilentHeader subcomponent: SectionHeaderControllerSubcomponent ) = subcomponent.headerController 6.NotificationGutsManager public class NotificationGuts extends FrameLayout {

ExpandableNotificationRowController.java 下边是通知列表的长按事件

if (mAllowLongPress) { if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_DRAG_TO_CONTENTS)) { mView.setDragController(mDragController); } mView.setLongPressListener((v, x, y, item) -> { if (mView.isSummaryWithChildren()) { //下图2 android system 通知下有多个通知,长按是展开 mView.expandNotification(); return true; }//下图1,长按就是显示alarm的通知设置了。 return mNotificationGutsManager.openGuts(v, x, y, item); }); }

image.png

image.png

7.NotificationsController

这里主要讲解下Notification row的创建以及其点击事件 ]()

@SysUISingleton @Provides static NotificationsController provideNotificationsController( Context context, Provider realController, Provider stubController) { if (context.getResources().getBoolean(R.bool.config_renderNotifications)) {//配置里是true return realController.get(); } else { return stubController.get(); } }

实现类

@SysUISingleton class NotificationsControllerImpl @Inject constructor( private val notificationListener: NotificationListener, private val commonNotifCollection: Lazy, private val notifPipeline: Lazy, private val notifLiveDataStore: NotifLiveDataStore, private val targetSdkResolver: TargetSdkResolver, private val notifPipelineInitializer: Lazy, private val notifBindPipelineInitializer: NotifBindPipelineInitializer, private val notificationLogger: NotificationLogger, private val notificationRowBinder: NotificationRowBinderImpl, private val notificationsMediaManager: NotificationMediaManager, private val headsUpViewBinder: HeadsUpViewBinder, private val clickerBuilder: NotificationClicker.Builder, private val animatedImageNotificationManager: AnimatedImageNotificationManager, private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager, private val bubblesOptional: Optional, private val fgsNotifListener: ForegroundServiceNotificationListener, private val memoryMonitor: Lazy, private val featureFlags: FeatureFlags ) : NotificationsController { override fun initialize( centralSurfaces: CentralSurfaces, presenter: NotificationPresenter, listContainer: NotificationListContainer, stackController: NotifStackController, notificationActivityStarter: NotificationActivityStarter, bindRowCallback: NotificationRowBinderImpl.BindRowCallback ) { notificationListener.registerAsSystemService() notifPipeline.get().addCollectionListener(object : NotifCollectionListener { override fun onEntryRemoved(entry: NotificationEntry, reason: Int) { listContainer.cleanUpViewStateForEntry(entry) } }) //设置通知的点击事件处理器 notificationRowBinder.setNotificationClicker( clickerBuilder.build( Optional.ofNullable(centralSurfaces), bubblesOptional, notificationActivityStarter)) notificationRowBinder.setUpWithPresenter( presenter, listContainer, bindRowCallback) headsUpViewBinder.setPresenter(presenter) notifBindPipelineInitializer.initialize() animatedImageNotificationManager.bind() notifPipelineInitializer.get().initialize( notificationListener, notificationRowBinder, listContainer, stackController) targetSdkResolver.initialize(notifPipeline.get()) notificationsMediaManager.setUpWithPresenter(presenter) notificationLogger.setUpWithContainer(listContainer) peopleSpaceWidgetManager.attach(notificationListener) fgsNotifListener.init() if (featureFlags.isEnabled(Flags.NOTIFICATION_MEMORY_MONITOR_ENABLED)) { memoryMonitor.get().init() } } 7.1NotificationRowBinderImpl

顾名思义,就是管理通知列表的,核心方法就是下边这个根据通知数据entry,生成或者更新view

frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java

>inflateView public void inflateViews( NotificationEntry entry, @NonNull NotifInflater.Params params, NotificationRowContentBinder.InflationCallback callback) throws InflationException { //这里的group就是NotificationStackScrollLayout.java ViewGroup parent = mListContainer.getViewParentForNotification(entry); if (entry.rowExists()) { mIconManager.updateIcons(entry); ExpandableNotificationRow row = entry.getRow(); row.reset(); updateRow(entry, row);//点击事件 inflateContentViews(entry, params, row, callback); } else { mIconManager.createIcons(entry); //看下布局如何加载的 mRowInflaterTaskProvider.get().inflate(mContext, parent, entry, row -> { // Setup the controller for the view. ExpandableNotificationRowComponent component = mExpandableNotificationRowComponentBuilder .expandableNotificationRow(row) .notificationEntry(entry) .onExpandClickListener(mPresenter) .listContainer(mListContainer) .build(); ExpandableNotificationRowController rowController = component.getExpandableNotificationRowController(); rowController.init(entry); entry.setRowController(rowController); bindRow(entry, row); updateRow(entry, row);//设置点击事件 inflateContentViews(entry, params, row, callback); }); } }

搜一下这个方法哪里调用的,NotifInflaterImpl.java 整理逻辑如下

public void rebindViews(@NonNull NotificationEntry entry, @NonNull Params params, @NonNull InflationCallback callback) { inflateViews(entry, params, callback); } @Override public void inflateViews(@NonNull NotificationEntry entry, @NonNull Params params, @NonNull InflationCallback callback) { try { requireBinder().inflateViews( entry, params, wrapInflationCallback(callback)); } catch (InflationException e) { mNotifErrorManager.setInflationError(entry, e); } }

继续找 PreparationCoordinator.java 方法A,B里会调用方法C,完事C里边又会调用D,E.

A 方法在attach方法里被指定给了pipeline的listener

public void attach(NotifPipeline pipeline) { mNotifErrorManager.addInflationErrorListener(mInflationErrorListener); mAdjustmentProvider.addDirtyListener( () -> mNotifInflatingFilter.invalidateList("adjustmentProviderChanged")); pipeline.addCollectionListener(mNotifCollectionListener); // Inflate after grouping/sorting since that affects what views to inflate. pipeline.addOnBeforeFinalizeFilterListener(this::inflateAllRequiredViews); pipeline.addFinalizeFilter(mNotifInflationErrorFilter); pipeline.addFinalizeFilter(mNotifInflatingFilter); } // A private void inflateAllRequiredViews(List entries) { for (int i = 0, size = entries.size(); i < size; i++) { ListEntry entry = entries.get(i); if (entry instanceof GroupEntry) { GroupEntry groupEntry = (GroupEntry) entry; inflateRequiredGroupViews(groupEntry); } else { NotificationEntry notifEntry = (NotificationEntry) entry; inflateRequiredNotifViews(notifEntry);//... } } } //B private void inflateRequiredGroupViews(GroupEntry groupEntry) { NotificationEntry summary = groupEntry.getSummary(); List children = groupEntry.getChildren(); inflateRequiredNotifViews(summary);//.. for (int j = 0; j < children.size(); j++) { NotificationEntry child = children.get(j); boolean childShouldBeBound = j < mChildBindCutoff; if (childShouldBeBound) { inflateRequiredNotifViews(child);//.. //.. //C private void inflateRequiredNotifViews(NotificationEntry entry) { NotifUiAdjustment newAdjustment = mAdjustmentProvider.calculateAdjustment(entry); if (mInflatingNotifs.contains(entry)) { // Already inflating this entry String errorIfNoOldAdjustment = "Inflating notification has no adjustments"; if (needToReinflate(entry, newAdjustment, errorIfNoOldAdjustment)) { inflateEntry(entry, newAdjustment, "adjustment changed while inflating"); } return; } @InflationState int state = mInflationStates.get(entry); switch (state) { case STATE_UNINFLATED: inflateEntry(entry, newAdjustment, "entryAdded"); break; case STATE_INFLATED_INVALID: rebind(entry, newAdjustment, "entryUpdated"); break; case STATE_INFLATED: String errorIfNoOldAdjustment = "Fully inflated notification has no adjustments"; if (needToReinflate(entry, newAdjustment, errorIfNoOldAdjustment)) { rebind(entry, newAdjustment, "adjustment changed after inflated"); } break; case STATE_ERROR: if (needToReinflate(entry, newAdjustment, null)) { inflateEntry(entry, newAdjustment, "adjustment changed after error"); } break; default: // Nothing to do. } } //D private void inflateEntry(NotificationEntry entry, NotifUiAdjustment newAdjustment, String reason) { //... mNotifInflater.inflateViews(entry, params, this::onInflationFinished); } //E private void rebind(NotificationEntry entry, NotifUiAdjustment newAdjustment, String reason) { //... mNotifInflater.rebindViews(entry, params, this::onInflationFinished); }

继续往上层找 NotifPipeline.kt

fun addOnBeforeFinalizeFilterListener(listener: OnBeforeFinalizeFilterListener) { mShadeListBuilder.addOnBeforeFinalizeFilterListener(listener) }

继续看 ShadeListBuilder.java

public void attach(NotifCollection collection) { Assert.isMainThread(); mDumpManager.registerDumpable(TAG, this); collection.addCollectionListener(mInteractionTracker); collection.setBuildListener(mReadyForBuildListener); mChoreographer.addOnEvalListener(this::buildList);//buildList方法分9步,加载数据用的 } private void buildList() { //... // Step 6: Filter out entries after pre-group filtering, grouping, promoting, and sorting // Now filters can see grouping, sectioning, and order information to determine whether // to filter or not. dispatchOnBeforeFinalizeFilter(mReadOnlyNotifList); //... } void addOnBeforeFinalizeFilterListener(OnBeforeFinalizeFilterListener listener) { mPipelineState.requireState(STATE_IDLE); mOnBeforeFinalizeFilterListeners.add(listener); } private void dispatchOnBeforeFinalizeFilter(List entries) { for (int i = 0; i < mOnBeforeFinalizeFilterListeners.size(); i++) { mOnBeforeFinalizeFilterListeners.get(i).onBeforeFinalizeFilter(entries); } } //这个方法又很多地方调用,调用的地方又都是别人的listener,不再继续研究了,没尽头了 private void rebuildListIfBefore(@PipelineState.StateName int rebuildState) { final @PipelineState.StateName int currentState = mPipelineState.getState(); if (currentState == STATE_IDLE) { scheduleRebuild(/* reentrant = */ false, rebuildState); return; } if (rebuildState > currentState) { return; } scheduleRebuild(/* reentrant = */ true, rebuildState); } private void scheduleRebuild(boolean reentrant, @PipelineState.StateName int rebuildState) { if (!reentrant) { mConsecutiveReentrantRebuilds = 0; mChoreographer.schedule();//这个会执行上边的buildList方法 return; } //... mChoreographer.schedule();//这个会执行上边的buildList方法 }

NotifPipelineChoreographerImpl也就是上边的mChoreographer对象,schedule方法会依次调用所有的listener,也就是上边的buildList方法了。

>updateRow

给view注册点击事件,具体见后边的Clicker

private void updateRow( NotificationEntry entry, ExpandableNotificationRow row) { // bind the click event to the content area requireNonNull(mNotificationClicker).register(row, entry.getSbn()); } >RowInflaterTask

加载notification row需要的布局到容器

@Inject public RowInflaterTask() { } /** * Inflates a new notificationView. This should not be called twice on this object */ public void inflate(Context context, ViewGroup parent, NotificationEntry entry, RowInflationFinishedListener listener) { mListener = listener; AsyncLayoutInflater inflater = new AsyncLayoutInflater(context); mEntry = entry; entry.setInflationTask(this); inflater.inflate(R.layout.status_bar_notification_row, parent, this); } >status_bar_notification_row.xml

我在布局上边加了test按钮,如下row,展开和非展开状态 image.png 完整布局如下,外层是个自定义的帧布局,里边还有好多层,具体看名字

7.2通知的点击事件

前边有分析过,点击事件给到了一个NotificationClicker,如下,通过注解builder生成的

notificationRowBinder.setNotificationClicker( clickerBuilder.build( Optional.ofNullable(centralSurfaces), bubblesOptional, notificationActivityStarter)) >NotificationClicker

build方法就是new了一个NotificationClicker对象

private NotificationClicker( NotificationClickerLogger logger, Optional centralSurfacesOptional, Optional bubblesOptional, NotificationActivityStarter notificationActivityStarter) { mLogger = logger; mCentralSurfacesOptional = centralSurfacesOptional; mBubblesOptional = bubblesOptional; mNotificationActivityStarter = notificationActivityStarter; }

前边notification row里说过给view绑定点击事件,就是这个类里的register方法,如下: 有的通知是有intent的,比如闹铃,点击一般会跳到闹铃的设置页面去了,这时候需要点击事件。 有的通知单纯的就是提示,不需要点击事件的

public void register(ExpandableNotificationRow row, StatusBarNotification sbn) { Notification notification = sbn.getNotification(); if (notification.contentIntent != null || notification.fullScreenIntent != null || row.getEntry().isBubble()) { row.setOnClickListener(this); row.setOnDragSuccessListener(mOnDragSuccessListener); } else { row.setOnClickListener(null); row.setOnDragSuccessListener(null); } }

对应的点击事件

public void onClick(final View v) { if (!(v instanceof ExpandableNotificationRow)) { //上边有贴notification row的布局,根容器就是这个,如果不是不做处理 return; } //... final ExpandableNotificationRow row = (ExpandableNotificationRow) v; final NotificationEntry entry = row.getEntry(); // Check if the notification is displaying the menu, if so slide notification back //下边这堆if都不处理intent的, if (isMenuVisible(row)) { mLogger.logMenuVisible(entry); row.animateResetTranslation(); return; } else if (row.isChildInGroup() && isMenuVisible(row.getNotificationParent())) { mLogger.logParentMenuVisible(entry); row.getNotificationParent().animateResetTranslation(); return; } else if (row.isSummaryWithChildren() && row.areChildrenExpanded()) { // We never want to open the app directly if the user clicks in between // the notifications. mLogger.logChildrenExpanded(entry); return; } else if (row.areGutsExposed()) { // ignore click if guts are exposed mLogger.logGutsExposed(entry); return; } // 标记一下这个row刚被点击了. row.setJustClicked(true); DejankUtils.postAfterTraversal(() -> row.setJustClicked(false)); if (!row.getEntry().isBubble() && mBubblesOptional.isPresent()) { mBubblesOptional.get().collapseStack(); } //最终intent是这里处理的 mNotificationActivityStarter.onNotificationClicked(entry, row); } >StatusBarNotificationActivityStarter class StatusBarNotificationActivityStarter implements NotificationActivityStarter { public void onNotificationClicked(NotificationEntry entry, ExpandableNotificationRow row) { if (mRemoteInputManager.isRemoteInputActive(entry) && !TextUtils.isEmpty(row.getActiveRemoteInputText())) { // We have an active remote input typed and the user clicked on the notification. // this was probably unintentional, so we're closing the edit text instead. mRemoteInputManager.closeRemoteInputs(); return; } Notification notification = entry.getSbn().getNotification(); final PendingIntent intent = notification.contentIntent != null ? notification.contentIntent : notification.fullScreenIntent; final boolean isBubble = entry.isBubble(); // This code path is now executed for notification without a contentIntent. // The only valid case is Bubble notifications. Guard against other cases // entering here. if (intent == null && !isBubble) { mLogger.logNonClickableNotification(entry); return; } boolean isActivityIntent = intent != null && intent.isActivity() && !isBubble; final boolean willLaunchResolverActivity = //... boolean showOverLockscreen =//... ActivityStarter.OnDismissAction postKeyguardAction = new ActivityStarter.OnDismissAction() { @Override public boolean onDismiss() { //具体的intent处理在这里 return handleNotificationClickAfterKeyguardDismissed( entry, row, intent, isActivityIntent, animate, showOverLockscreen); } @Override public boolean willRunAnimationOnKeyguard() { return animate; } }; //锁屏界面先解锁屏幕再执行上边的action,非锁屏界面执行action的onDismiss方法 if (showOverLockscreen) { mIsCollapsingToShowActivityOverLockscreen = true; postKeyguardAction.onDismiss(); } else { mActivityStarter.dismissKeyguardThenExecute( postKeyguardAction, null, willLaunchResolverActivity); } }

继续

private boolean handleNotificationClickAfterKeyguardDismissed( NotificationEntry entry, ExpandableNotificationRow row, PendingIntent intent, boolean isActivityIntent, boolean animate, boolean showOverLockscreen) { //核心就是这个了,反正最终都是走这里, final Runnable runnable = () -> handleNotificationClickAfterPanelCollapsed( entry, row, intent, isActivityIntent, animate); if (showOverLockscreen) { mShadeController.addPostCollapseAction(runnable); mShadeController.collapseShade(true /* animate */); } else if (mKeyguardStateController.isShowing() && mCentralSurfaces.isOccluded()) { mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable); mShadeController.collapseShade(); } else { runnable.run(); } // Always defer the keyguard dismiss when animating. return animate || !mNotificationPanel.isFullyCollapsed(); }

继续看handleNotificationClickAfterPanelCollapsed方法,

if (canBubble) { } else {//就是这个了 startNotificationIntent(intent, fillInIntent, entry, row, animate, isActivityIntent); }

继续startNotificationIntent方法

(adapter) -> { //... //这里就是跳转activity了 int result = intent.sendAndReturnResult(mContext, 0, fillInIntent, null, null, null, options); return result; }); 8.总结 简单介绍下展开状态的通知栏的布局结构以及显示ui 主要分三部分,状态栏(展开状态的),quick settings, notifiaction 主要介绍的是通知栏,这个是个自定义的viewgroup,然后研究了下通知栏列表的创建过程,以及点击事件等。


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有