8

Android 9.0 SystemUI NavigationBar

 3 years ago
source link: http://wuxiaolong.me/2019/09/12/SystemUI5/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

导航栏有返回(back),桌面(home),最近任务(recent),本篇主要学习这三个是如何加载的,点击事件在哪里写的?基于 AOSP 9.0 分析。

NavigationBar 创建是从 StatusBar#makeStatusBarView 开始的。

StatusBar#makeStatusBarView

protected void makeStatusBarView() {
//省略其他代码
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);

再进入 NavigationBarFragment#create 看看。

NavigationBarFragment#create

public static View create(Context context, FragmentListener listener) {
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
| WindowManager.LayoutParams.FLAG_SLIPPERY,
PixelFormat.TRANSLUCENT);
lp.token = new Binder();
lp.setTitle("NavigationBar");
lp.accessibilityTitle = context.getString(R.string.nav_bar);
lp.windowAnimations = 0;
View navigationBarView = LayoutInflater.from(context).inflate(
R.layout.navigation_bar_window, null);
if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + navigationBarView);
if (navigationBarView == null) return null;
context.getSystemService(WindowManager.class).addView(navigationBarView, lp);
FragmentHostManager fragmentHost = FragmentHostManager.get(navigationBarView);
NavigationBarFragment fragment = new NavigationBarFragment();
fragmentHost.getFragmentManager().beginTransaction()
.replace(R.id.navigation_bar_frame, fragment, TAG)
.commit();
fragmentHost.addTagListener(TAG, listener);
return navigationBarView;

这里 WindowManager addView 了导航栏的布局,最终 add NavigationBarFragment,接下来看 NavigationBarFragment#onCreateView

NavigationBarFragment#onCreateView

@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.navigation_bar, container, false);

看下布局文件 navigation_bar.xml,这是导航栏的真正根布局。

navigation_bar.xml

位于 SystemUI\res\layout\navigation_bar.xml

<com.android.systemui.statusbar.phone.NavigationBarView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:background="@drawable/system_bar_background">
<com.android.systemui.statusbar.phone.NavigationBarInflaterView
android:id="@+id/navigation_inflater"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.android.systemui.statusbar.phone.NavigationBarView>

NavigationBarInflaterView 继承自 FrameLayout,直接看 onFinishInflate() 方法,这个方法是每个 view 被 inflate 之后都会回调。

NavigationBarInflaterView#onFinishInflate

@Override
protected void onFinishInflate() {
super.onFinishInflate();
inflateChildren();
clearViews();
//关键方法
inflateLayout(getDefaultLayout());

这里调用了 getDefaultLayout 方法,加载资源文件。

NavigationBarInflaterView#getDefaultLayout

protected String getDefaultLayout() {
final int defaultResource = mOverviewProxyService.shouldShowSwipeUpUI()
? R.string.config_navBarLayoutQuickstep
: R.string.config_navBarLayout;
return mContext.getString(defaultResource);

config_navBarLayoutQuickstep 和 config_navBarLayout 位于 AOSP/frameworks/base/packages/SystemUI/res/values/config.xml。

<string name="config_navBarLayout" translatable="false">left[.5W],back[1WC];home;recent[1WC],right[.5W]</string>
<string name="config_navBarLayoutQuickstep" translatable="false">back[1.7WC];home;contextual[1.7WC]</string>

回头再看 NavigationBarInflaterView#onFinishInflate 方法调用 NavigationBarInflaterView#inflateLayout 方法。

NavigationBarInflaterView#inflateLayout

protected void inflateLayout(String newLayout) {
mCurrentLayout = newLayout;
if (newLayout == null) {
newLayout = getDefaultLayout();
//根据“;”号分割成长度为3的数组
String[] sets = newLayout.split(GRAVITY_SEPARATOR, 3);
if (sets.length != 3) {
Log.d(TAG, "Invalid layout.");
newLayout = getDefaultLayout();
sets = newLayout.split(GRAVITY_SEPARATOR, 3);
//根据“,”号分割,包含 left[.5W]和back[1WC]
String[] start = sets[0].split(BUTTON_SEPARATOR);
//包含home
String[] center = sets[1].split(BUTTON_SEPARATOR);
//包含recent[1WC]和right[.5W]
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);
updateButtonDispatchersCurrentView();

再看 inflateButtons() 方法,遍历加载 inflateButton。

NavigationBarInflaterView#inflateButtons

private void inflateButtons(String[] buttons, ViewGroup parent, boolean landscape,
boolean start) {
for (int i = 0; i < buttons.length; i++) {
inflateButton(buttons[i], parent, landscape, start);
@Nullable
protected View inflateButton(String buttonSpec, ViewGroup parent, boolean landscape,
boolean start) {
LayoutInflater inflater = landscape ? mLandscapeInflater : mLayoutInflater;
View v = createView(buttonSpec, parent, inflater);
if (v == null) return null;
v = applySize(v, buttonSpec, landscape, start);
parent.addView(v);
addToDispatchers(v);
View lastView = landscape ? mLastLandscape : mLastPortrait;
View accessibilityView = v;
if (v instanceof ReverseRelativeLayout) {
accessibilityView = ((ReverseRelativeLayout) v).getChildAt(0);
if (lastView != null) {
accessibilityView.setAccessibilityTraversalAfter(lastView.getId());
if (landscape) {
mLastLandscape = accessibilityView;
} else {
mLastPortrait = accessibilityView;
return v;

来看 NavigationBarInflaterView#createView 方法。

private View createView(String buttonSpec, ViewGroup parent, LayoutInflater inflater) {
View v = null;
String button = extractButton(buttonSpec);
//省略其他代码
if (HOME.equals(button)) {
v = inflater.inflate(R.layout.home, parent, false);
} else if (BACK.equals(button)) {
v = inflater.inflate(R.layout.back, parent, false);
} else if (RECENT.equals(button)) {
v = inflater.inflate(R.layout.recent_apps, parent, false);
} else if (MENU_IME_ROTATE.equals(button)) {
v = inflater.inflate(R.layout.menu_ime, parent, false);
} //省略其他代码
return v;

以 home 按键为例,加载了 home.xml 布局。

<com.android.systemui.statusbar.policy.KeyButtonView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:id="@+id/home"
android:layout_width="@dimen/navigation_key_width"
android:layout_height="match_parent"
android:layout_weight="0"
systemui:keyCode="3"
android:scaleType="center"
android:contentDescription="@string/accessibility_home"
android:paddingStart="@dimen/navigation_key_padding"
android:paddingEnd="@dimen/navigation_key_padding"
/>

从 KeyButtonView#sendEvent() 方法来看,home 等 view 的点击 touch 事件不是自己处理的,而是交由系统以实体按键(keycode)的形式处理的,不细看了。

那 NavigationBar icon 是具体如何加载的?看 NavigationBarView 构造方法。

NavigationBarView#构造方法

public NavigationBarView(Context context, AttributeSet attrs) {
super(context, attrs);
mDisplay = ((WindowManager) context.getSystemService(
Context.WINDOW_SERVICE)).getDefaultDisplay();
mVertical = false;
mShowMenu = false;
mShowAccessibilityButton = false;
mLongClickableAccessibilityButton = false;
mOverviewProxyService = Dependency.get(OverviewProxyService.class);
mRecentsOnboarding = new RecentsOnboarding(context, mOverviewProxyService);
mConfiguration = new Configuration();
mConfiguration.updateFrom(context.getResources().getConfiguration());
//加载 icon
reloadNavIcons();
//mButtonDispatchers 是维护这些home back recent图标view的管理类
mBarTransitions = new NavigationBarTransitions(this);
mButtonDispatchers.put(R.id.back, new ButtonDispatcher(R.id.back));
mButtonDispatchers.put(R.id.home, new ButtonDispatcher(R.id.home));
mButtonDispatchers.put(R.id.recent_apps, new ButtonDispatcher(R.id.recent_apps));
mButtonDispatchers.put(R.id.menu, new ButtonDispatcher(R.id.menu));
mButtonDispatchers.put(R.id.ime_switcher, new ButtonDispatcher(R.id.ime_switcher));
mButtonDispatchers.put(R.id.accessibility_button,
new ButtonDispatcher(R.id.accessibility_button));
mButtonDispatchers.put(R.id.rotate_suggestion,
new ButtonDispatcher(R.id.rotate_suggestion));
mButtonDispatchers.put(R.id.menu_container,
new ButtonDispatcher(R.id.menu_container));
mDeadZone = new DeadZone(this);

NavigationBarView#reloadNavIcons

private void reloadNavIcons() {
updateIcons(mContext, Configuration.EMPTY, mConfiguration);
private void updateIcons(Context ctx, Configuration oldConfig, Configuration newConfig) {
int dualToneDarkTheme = Utils.getThemeAttr(ctx, R.attr.darkIconTheme);
int dualToneLightTheme = Utils.getThemeAttr(ctx, R.attr.lightIconTheme);
//亮色的icon资源
Context lightContext = new ContextThemeWrapper(ctx, dualToneLightTheme);
//暗色的icon资源
Context darkContext = new ContextThemeWrapper(ctx, dualToneDarkTheme);
if (oldConfig.orientation != newConfig.orientation
|| oldConfig.densityDpi != newConfig.densityDpi) {
mDockedIcon = getDrawable(lightContext, darkContext, R.drawable.ic_sysbar_docked);
mHomeDefaultIcon = getHomeDrawable(lightContext, darkContext);
if (oldConfig.densityDpi != newConfig.densityDpi
|| oldConfig.getLayoutDirection() != newConfig.getLayoutDirection()) {
mBackIcon = getBackDrawable(lightContext, darkContext);
mRecentIcon = getDrawable(lightContext, darkContext, R.drawable.ic_sysbar_recent);
mMenuIcon = getDrawable(lightContext, darkContext, R.drawable.ic_sysbar_menu);
mAccessibilityIcon = getDrawable(lightContext, darkContext,
R.drawable.ic_sysbar_accessibility_button, false /* hasShadow */);
mImeIcon = getDrawable(lightContext, darkContext, R.drawable.ic_ime_switcher_default,
false /* hasShadow */);
updateRotateSuggestionButtonStyle(mRotateBtnStyle, false);
if (ALTERNATE_CAR_MODE_UI) {
updateCarModeIcons(ctx);
public KeyButtonDrawable getBackDrawable(Context lightContext, Context darkContext) {
KeyButtonDrawable drawable = chooseNavigationIconDrawable(lightContext, darkContext,
R.drawable.ic_sysbar_back, R.drawable.ic_sysbar_back_quick_step);
orientBackButton(drawable);
return drawable;
private void orientBackButton(KeyButtonDrawable drawable) {
final boolean useAltBack =
(mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
drawable.setRotation(useAltBack
? -90 : (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) ? 180 : 0);

这里看到 NavigationBar icon 加载,点击事件在哪里写了呢?看 NavigationBarFragment#onViewCreated。

NavigationBarFragment#onViewCreated

@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mNavigationBarView = (NavigationBarView) view;
//省略其他代码
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);
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);
ButtonDispatcher rotateSuggestionButton = mNavigationBarView.getRotateSuggestionButton();
rotateSuggestionButton.setOnClickListener(this::onRotateSuggestionClick);
rotateSuggestionButton.setOnHoverListener(this::onRotateSuggestionHover);
updateScreenPinningGestures();

从 mButtonDispatchers 获得 recents、back、home,然后设置点击、长按等事件,比如 onRecentsClick 方法:

private void onRecentsClick(View v) {
if (LatencyTracker.isEnabled(getContext())) {
LatencyTracker.getInstance(getContext()).onActionStart(
LatencyTracker.ACTION_TOGGLE_RECENTS);
mStatusBar.awakenDreams();
mCommandQueue.toggleRecentApps();

至此,SystemUI NavigationBar 模块代码流程分析完毕。

 我的微信公众号:吴小龙同学,欢迎关注交流,公号回复关键字「1024」有惊喜哦。
code?username=MrWuXiaolong

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK