前面介绍SystemUI启动流程中,有介绍到SystemBars的启动流程,其中NavigationBar属于SystemBars模块,今天与大家一同分析一下NavigationBar启动以及运行流程。
初始化
NavigationBar在Statusbar初始化的时候,即调用start()时进行初始化,代码如下:
@Override
public void start() {
......
createAndAddWindows();
......
}
public void createAndAddWindows() {
addStatusBarWindow();
}
private void addStatusBarWindow() {
makeStatusBarView();
mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class);
mRemoteInputController = new RemoteInputController(mHeadsUpManager);
mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
}
protected void makeStatusBarView() {
......
try {
boolean showNav = mWindowManagerService.hasNavigationBar();
if (DEBUG) Log.v(TAG, "hasNavigationBar=" + showNav);
if (showNav) {
createNavigationBar();
}
} catch (RemoteException ex) {
// no window manager? good luck with that
}
......
}
protected void createNavigationBar() {
mNavigationBarView = NavigationBarFragment.create(mContext, (tag, fragment) -> {
mNavigationBar = (NavigationBarFragment) fragment;
if (mLightBarController != null) {
mNavigationBar.setLightBarController(mLightBarController);
}
mNavigationBar.setCurrentSysuiVisibility(mSystemUiVisibility);
});
}
通过widowManager的校验,确认是否添加导航栏,然后进行NavigationBar初始化。从Android O开始,NavigationBar放入了Fragment中进行管理,其同样遵循Fragment的生命周期。
NavigationBarFragment
在NavigationbarFragment中onViewCreated()对NavigationBarView进行初始化,并注册button点击\长按事件监听,代码如下:
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mNavigationBarView = (NavigationBarView) view;
mNavigationBarView.setDisabledFlags(mDisabledFlags1);
mNavigationBarView.setComponents(mRecents, mDivider);
mNavigationBarView.setOnVerticalChangedListener(this::onVerticalChanged);
mNavigationBarView.setOnTouchListener(this::onNavigationTouch);
if (savedInstanceState != null) {
mNavigationBarView.getLightTransitionsController().restoreState(savedInstanceState);
}
prepareNavigationBarView();
checkNavBarModes();
IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_SCREEN_ON);
getContext().registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null);
notifyNavigationBarScreenOn();
}
在prepareNavigationBarView()注册各个按钮的点击事件的监听。代码如下:
private void prepareNavigationBarView() {
mNavigationBarView.reorient();
ButtonDispatcher recentsButton = mNavigationBarView.getRecentsButton();
recentsButton.setOnClickListener(this::onRecentsClick);
recentsButton.setOnTouchListener(this::onRecentsTouch);
recentsButton.setLongClickable(true);
recentsButton.setOnLongClickListener(this::onLongPressBackRecents);
ButtonDispatcher backButton = mNavigationBarView.getBackButton();
backButton.setLongClickable(true);
backButton.setOnLongClickListener(this::onLongPressBackRecents);
ButtonDispatcher homeButton = mNavigationBarView.getHomeButton();
homeButton.setOnTouchListener(this::onHomeTouch);
homeButton.setOnLongClickListener(this::onHomeLongClick);
ButtonDispatcher accessibilityButton = mNavigationBarView.getAccessibilityButton();
accessibilityButton.setOnClickListener(this::onAccessibilityClick);
accessibilityButton.setOnLongClickListener(this::onAccessibilityLongClick);
updateAccessibilityServicesState(mAccessibilityManager);
}
那么如何做到在不同的界面显示对应的NavigationBar Button呢?NavigationBarView可以做到对各个button的管理。
NavigationBarView
NavigationBarView在其构造方法中对每个button进行初始化。代码如下:
private void updateIcons(Context ctx, Configuration oldConfig, Configuration newConfig) {
if (oldConfig.orientation != newConfig.orientation
|| oldConfig.densityDpi != newConfig.densityDpi) {
mDockedIcon = getDrawable(ctx,
R.drawable.ic_sysbar_docked, R.drawable.ic_sysbar_docked_dark);
}
if (oldConfig.densityDpi != newConfig.densityDpi
|| oldConfig.getLayoutDirection() != newConfig.getLayoutDirection()) {
mBackIcon = getDrawable(ctx, R.drawable.ic_sysbar_back, R.drawable.ic_sysbar_back_dark);
mBackLandIcon = mBackIcon;
mBackAltIcon = getDrawable(ctx,
R.drawable.ic_sysbar_back_ime, R.drawable.ic_sysbar_back_ime_dark);
mBackAltLandIcon = mBackAltIcon;
mHomeDefaultIcon = getDrawable(ctx,
R.drawable.ic_sysbar_home, R.drawable.ic_sysbar_home_dark);
mRecentIcon = getDrawable(ctx,
R.drawable.ic_sysbar_recent, R.drawable.ic_sysbar_recent_dark);
mMenuIcon = getDrawable(ctx, R.drawable.ic_sysbar_menu, R.drawable.ic_sysbar_menu_dark);
mAccessibilityIcon = getDrawable(ctx, R.drawable.ic_sysbar_accessibility_button,
R.drawable.ic_sysbar_accessibility_button_dark);
int dualToneDarkTheme = Utils.getThemeAttr(ctx, R.attr.darkIconTheme);
int dualToneLightTheme = Utils.getThemeAttr(ctx, R.attr.lightIconTheme);
Context darkContext = new ContextThemeWrapper(ctx, dualToneDarkTheme);
Context lightContext = new ContextThemeWrapper(ctx, dualToneLightTheme);
mImeIcon = getDrawable(darkContext, lightContext,
R.drawable.ic_ime_switcher_default, R.drawable.ic_ime_switcher_default);
if (ALTERNATE_CAR_MODE_UI) {
updateCarModeIcons(ctx);
}
}
}
通过context来选择亮色或者暗色主题的icon。那么具体什么场景显示哪些icon?这个可以从源码中看到有这么一块逻辑:
public void setDisabledFlags(int disabledFlags) {
setDisabledFlags(disabledFlags, false);
}
public void setDisabledFlags(int disabledFlags, boolean force) {
if (!force && mDisabledFlags == disabledFlags) return;
mDisabledFlags = disabledFlags;
final boolean disableHome = ((disabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0);
// Always disable recents when alternate car mode UI is active.
boolean disableRecent = mUseCarModeUi
|| ((disabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0);
final boolean disableBack = ((disabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0)
&& ((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) == 0);
ViewGroup navButtons = (ViewGroup) getCurrentView().findViewById(R.id.nav_buttons);
if (navButtons != null) {
LayoutTransition lt = navButtons.getLayoutTransition();
if (lt != null) {
if (!lt.getTransitionListeners().contains(mTransitionListener)) {
lt.addTransitionListener(mTransitionListener);
}
}
}
if (inLockTask() && disableRecent && !disableHome) {
// Don't hide recents when in lock task, it is used for exiting.
// Unless home is hidden, then in DPM locked mode and no exit available.
disableRecent = false;
}
getBackButton().setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE);
getHomeButton().setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE);
getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE);
}
而setDisabledFlags的caller是NavigationBarFragment中disable(),由于NavigationBarFragment接入了CommandQueue的callback,当系统回调disable()时,NavigationBarView也会随之根据disableFlags进行判断,用来判断是否显示NavigationBar Button。
NavigationBarInflaterView
那么如何显示back、home、recent排序呢?NavigationBarInflaterView这个类就实现了该逻辑,NavigationBarInflaterView在Navigationbar布局加载时静态初始化,其逻辑如下:
public NavigationBarInflaterView(Context context, AttributeSet attrs) {
super(context, attrs);
//guchunhua,DATE20180329,modify for VBNLITEIA-2173,LINE
mContext = context;
createInflaters();
Display display = ((WindowManager)
context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
Mode displayMode = display.getMode();
isRot0Landscape = displayMode.getPhysicalWidth() > displayMode.getPhysicalHeight();
}
在布局inflate完毕后,读取系统配置,进行button排序,其逻辑如下:
protected void inflateLayout(String newLayout) {
mCurrentLayout = newLayout;
if (newLayout == null) {
newLayout = getDefaultLayout();
}
String[] sets = newLayout.split(GRAVITY_SEPARATOR, 3);
String[] start = sets[0].split(BUTTON_SEPARATOR);
String[] center = sets[1].split(BUTTON_SEPARATOR);
String[] end = sets[2].split(BUTTON_SEPARATOR);
// Inflate these in start to end order or accessibility traversal will be messed up.
inflateButtons(start, mRot0.findViewById(R.id.ends_group), isRot0Landscape, true);
inflateButtons(start, mRot90.findViewById(R.id.ends_group), !isRot0Landscape, true);
inflateButtons(center, mRot0.findViewById(R.id.center_group), isRot0Landscape, false);
inflateButtons(center, mRot90.findViewById(R.id.center_group), !isRot0Landscape, false);
addGravitySpacer(mRot0.findViewById(R.id.ends_group));
addGravitySpacer(mRot90.findViewById(R.id.ends_group));
inflateButtons(end, mRot0.findViewById(R.id.ends_group), isRot0Landscape, false);
inflateButtons(end, mRot90.findViewById(R.id.ends_group), !isRot0Landscape, false);
}
Android默认的排序是,自左向右分别是back-home-recent。开发人员可以到SystemUI工程目录下修改"config_navBarLayout"的config值。