前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android 9.0 SystemUI NavigationBar

Android 9.0 SystemUI NavigationBar

作者头像
吴小龙同學
发布2020-10-30 10:46:10
2K0
发布2020-10-30 10:46:10
举报
文章被收录于专栏:吴小龙同學吴小龙同學

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

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

StatusBar#makeStatusBarView

代码语言:javascript
复制
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);
    });
}

再进入 NavigationBarFragment#create 看看。

NavigationBarFragment#create

代码语言:javascript
复制
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

代码语言:javascript
复制
@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

代码语言:javascript
复制
<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

代码语言:javascript
复制
@Override
protected void onFinishInflate() {
    super.onFinishInflate();
    inflateChildren();
    clearViews();
    //关键方法
    inflateLayout(getDefaultLayout());
}

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

NavigationBarInflaterView#getDefaultLayout

代码语言:javascript
复制
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。

代码语言:javascript
复制
<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

代码语言:javascript
复制
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

代码语言:javascript
复制
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 方法。

代码语言:javascript
复制
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 布局。

代码语言:javascript
复制
<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#构造方法

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
@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 方法:

代码语言:javascript
复制
private void onRecentsClick(View v) {
    if (LatencyTracker.isEnabled(getContext())) {
        LatencyTracker.getInstance(getContext()).onActionStart(
                LatencyTracker.ACTION_TOGGLE_RECENTS);
    }
    mStatusBar.awakenDreams();
    mCommandQueue.toggleRecentApps();
}

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-09-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • StatusBar#makeStatusBarView
  • NavigationBarFragment#create
  • NavigationBarFragment#onCreateView
  • navigation_bar.xml
  • NavigationBarInflaterView#onFinishInflate
  • NavigationBarInflaterView#getDefaultLayout
  • NavigationBarInflaterView#inflateLayout
  • NavigationBarInflaterView#inflateButtons
  • NavigationBarView#构造方法
  • NavigationBarView#reloadNavIcons
  • NavigationBarFragment#onViewCreated
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档