导航栏便是屏幕下方或许两边几个按键,曾经是物理按键,现在都虚拟的。常见的便是后退键,home键,recents键。
当然了,如果有输入法的显现的话,或许会有个箭头按钮用来躲藏输入法。现在还支持手势操作,便是滑屏关闭页面,这时候导航栏或许就显现一个按钮线条。

1.创立

创立还是在CentralSurfacesImpl.java里面

    // TODO(b/117478341): This was left such that CarStatusBar can override this method.
    // Try to remove this.
    protected void createNavigationBar(@Nullable RegisterStatusBarResult result) {
        mNavigationBarController.createNavigationBars(true /* includeDefaultDisplay */, result);
    }

能够看到是经过Controller来创立的,那就具体看下。

2.NavigationBarController

首先判别是否需要显现导航栏,需要的话再创立

    public void createNavigationBars(final boolean includeDefaultDisplay,
            RegisterStatusBarResult result) {
        updateAccessibilityButtonModeIfNeeded();
        // Don't need to create nav bar on the default display if we initialize TaskBar.
        final boolean shouldCreateDefaultNavbar = includeDefaultDisplay
                && !initializeTaskbarIfNecessary();
        Display[] displays = mDisplayManager.getDisplays();
        for (Display display : displays) {
            if (shouldCreateDefaultNavbar || display.getDisplayId() != DEFAULT_DISPLAY) {
                createNavigationBar(display, null /* savedState */, result);
            }
        }
    }
        void createNavigationBar(Display display, Bundle savedState, RegisterStatusBarResult result) {
//...
            final int displayId = display.getDisplayId();
            final boolean isOnDefaultDisplay = displayId == DEFAULT_DISPLAY;
            // We may show TaskBar on the default display for large screen device. Don't need to create
            // navigation bar for this case.
            if (isOnDefaultDisplay && initializeTaskbarIfNecessary()) {
                return;
            }
            final IWindowManager wms = WindowManagerGlobal.getWindowManagerService();
            try {
                if (!wms.hasNavigationBar(displayId)) {
                    return;
                }
            } catch (RemoteException e) {
                // Cannot get wms, just return with warning message.
                Log.w(TAG, "Cannot get WindowManager.");
                return;
            }
//..
            NavigationBarComponent component = mNavigationBarComponentFactory.create(
                    context, savedState);
            //navBar是经过注解生成的
            NavigationBar navBar = component.getNavigationBar();
            navBar.init();
            mNavigationBars.put(displayId, navBar);
            navBar.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
                @Override
                public void onViewAttachedToWindow(View v) {
                    if (result != null) {
                        navBar.setImeWindowStatus(display.getDisplayId(), result.mImeToken,
                                result.mImeWindowVis, result.mImeBackDisposition,
                                result.mShowImeSwitcher);
                    }
                }
                @Override
                public void onViewDetachedFromWindow(View v) {
                    v.removeOnAttachStateChangeListener(this);
                }
            });
        }

2.1NavigationBar实例化

>结构办法

        @Inject
        NavigationBar(
                NavigationBarView navigationBarView,
                NavigationBarFrame navigationBarFrame,
                @Nullable Bundle savedState,
                //...
        super(navigationBarView);
        mFrame = navigationBarFrame;                

注解生成的,如下,

>NavigationBarComponent

    @Subcomponent(modules = { NavigationBarModule.class })
    @NavigationBarComponent.NavigationBarScope
    public interface NavigationBarComponent {
        /** Factory for {@link NavigationBarComponent}. */
        @Subcomponent.Factory
        interface Factory {
            NavigationBarComponent create(
                    @BindsInstance @DisplayId Context context,
                    @BindsInstance @Nullable Bundle savedState);
        }
        /** */
        NavigationBar getNavigationBar();

>NavigationBarModule

这里有NavigationBar结构办法里用到的一些view

/** Module for {@link com.android.systemui.navigationbar.NavigationBarComponent}. */
@Module
public interface NavigationBarModule {
    /** A Layout inflater specific to the display's context. */
    @Provides
    @NavigationBarScope
    @DisplayId
    static LayoutInflater provideLayoutInflater(@DisplayId Context context) {
        return LayoutInflater.from(context);
    }
    /** */
    @Provides
    @NavigationBarScope
    static NavigationBarFrame provideNavigationBarFrame(@DisplayId LayoutInflater layoutInflater) {
        return (NavigationBarFrame) layoutInflater.inflate(R.layout.navigation_bar_window, null);
    }
    /** */
    @Provides
    @NavigationBarScope
    static NavigationBarView provideNavigationBarview(
            @DisplayId LayoutInflater layoutInflater, NavigationBarFrame frame) {
        View barView = layoutInflater.inflate(R.layout.navigation_bar, frame);
        return barView.findViewById(R.id.navigation_bar_view);
    }
    /** */
    @Provides
    @NavigationBarScope
    static EdgeBackGestureHandler provideEdgeBackGestureHandler(
            EdgeBackGestureHandler.Factory factory, @DisplayId Context context) {
        return factory.create(context);
    }
    /** A WindowManager specific to the display's context. */
    @Provides
    @NavigationBarScope
    @DisplayId
    static WindowManager provideWindowManager(@DisplayId Context context) {
        return context.getSystemService(WindowManager.class);
    }
}

相关的两个布局如下

>>navigation_bar_window.xml

<com.android.systemui.navigationbar.NavigationBarFrame
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:systemui="http://schemas.android.com/apk/res-auto"
    android:id="@+id/navigation_bar_frame"
    android:theme="@style/Theme.SystemUI"
    android:layout_height="match_parent"
    android:layout_width="match_parent">
</com.android.systemui.navigationbar.NavigationBarFrame>

>>navigation_bar.xml

<com.android.systemui.navigationbar.NavigationBarView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/navigation_bar_view"
    android:layout_height="match_parent"
    android:layout_width="match_parent"
    android:clipChildren="false"
    android:clipToPadding="false"
    android:background="@drawable/system_bar_background">
    <com.android.systemui.navigationbar.NavigationBarInflaterView
        android:id="@+id/navigation_inflater"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clipChildren="false"
        android:clipToPadding="false" />
</com.android.systemui.navigationbar.NavigationBarView>

2.2init办法

init办法是父类ViewController里的

@NavigationBarScope
public class NavigationBar extends ViewController<NavigationBarView> implements Callbacks {

ViewController.java调用了onInit办法,给view增加attach,detach监听.

// 依据子类结构办法里”super(navigationBarView);”可知,这里的view是navigationBarView

        protected ViewController(T view) {
            mView = view;
        }
            public void init() {
                if (mInited) {
                    return;
                }
                onInit();
                mInited = true;
                if (isAttachedToWindow()) {
                    mOnAttachStateListener.onViewAttachedToWindow(mView);
                }
                addOnAttachStateChangeListener(mOnAttachStateListener);
            }

2.3onInit()

mView(NavigationBarView)是mFrame(NavigationBarFrame)的child,具体看上边Module里的加载进程以及相关的布局。

    @Override
    public void onInit() {
        mView.setBarTransitions(mNavigationBarTransitions);
        mView.setTouchHandler(mTouchHandler);
        setNavBarMode(mNavBarMode);//把mode传递给mView,mode是啥以及咋来的后边讲
        mEdgeBackGestureHandler.setStateChangeCallback(mView::updateStates);
        mNavigationBarTransitions.addListener(this::onBarTransition);
        mView.updateRotationButton();
        mView.setVisibility(
                mStatusBarKeyguardViewManager.isNavBarVisible() ? View.VISIBLE : View.INVISIBLE);
     //增加导航栏的控件mFrame(NavigationBarFrame)
        mWindowManager.addView(mFrame,//依据旋转视点获取布局参数
                getBarLayoutParams(mContext.getResources().getConfiguration().windowConfiguration
                        .getRotation()));
        mDisplayId = mContext.getDisplayId();
        mIsOnDefaultDisplay = mDisplayId == DEFAULT_DISPLAY;
        // Ensure we try to get currentSysuiState from navBarHelper before command queue callbacks
        // start firing, since the latter is source of truth
        parseCurrentSysuiState();
        mCommandQueue.addCallback(this);
        mLongPressHomeEnabled = mNavBarHelper.getLongPressHomeEnabled();
        mNavBarHelper.init();
        mHomeButtonLongPressDurationMs = Optional.of(mDeviceConfigProxy.getLong(
                DeviceConfig.NAMESPACE_SYSTEMUI,
                HOME_BUTTON_LONG_PRESS_DURATION_MS,
                /* defaultValue = */ 0
        )).filter(duration -> duration != 0);
        mDeviceConfigProxy.addOnPropertiesChangedListener(
                DeviceConfig.NAMESPACE_SYSTEMUI, mHandler::post, mOnPropertiesChangedListener);
//...
        // Respect the latest disabled-flags.
        mCommandQueue.recomputeDisableFlags(mDisplayId, false);
        mNotificationShadeDepthController.addListener(mDepthListener);
    }

>getBarLayoutParams

        private WindowManager.LayoutParams getBarLayoutParams(int rotation) {
            WindowManager.LayoutParams lp = getBarLayoutParamsForRotation(rotation);
            //顺道保存了4种旋转视点对应的layoutParam
            lp.paramsForRotation = new WindowManager.LayoutParams[4];
            for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) {
                lp.paramsForRotation[rot] = getBarLayoutParamsForRotation(rot);
            }
            return lp;
        }

>getBarLayoutParamsForRotation

        private WindowManager.LayoutParams getBarLayoutParamsForRotation(int rotation) {
            int width = WindowManager.LayoutParams.MATCH_PARENT;
            int height = WindowManager.LayoutParams.MATCH_PARENT;
            int insetsHeight = -1;
            int gravity = Gravity.BOTTOM;
            boolean navBarCanMove = true;
            final Context userContext = mUserContextProvider.createCurrentUserContext(mContext);
            if (mWindowManager != null && mWindowManager.getCurrentWindowMetrics() != null) {
                Rect displaySize = mWindowManager.getCurrentWindowMetrics().getBounds();
                //读取装备
                navBarCanMove = displaySize.width() != displaySize.height()
                        && userContext.getResources().getBoolean(
                        com.android.internal.R.bool.config_navBarCanMove);
            }
            if (!navBarCanMove) {
            //读取装备里写好的高度,手机平板高度不一样
                height = userContext.getResources().getDimensionPixelSize(
                        com.android.internal.R.dimen.navigation_bar_frame_height);
                insetsHeight = userContext.getResources().getDimensionPixelSize(
                        com.android.internal.R.dimen.navigation_bar_height);
            } else {
                switch (rotation) {
                    case ROTATION_UNDEFINED:
                    case Surface.ROTATION_0:
                    case Surface.ROTATION_180://默认的竖屏,导航栏在底部,所以约束的是高度
                        height = userContext.getResources().getDimensionPixelSize(
                                com.android.internal.R.dimen.navigation_bar_frame_height);
                        insetsHeight = userContext.getResources().getDimensionPixelSize(
                                com.android.internal.R.dimen.navigation_bar_height);
                        break;
                    case Surface.ROTATION_90://90/270导航栏在左右两边,所以约束的是宽度
                        gravity = Gravity.RIGHT;
                        width = userContext.getResources().getDimensionPixelSize(
                                com.android.internal.R.dimen.navigation_bar_width);
                        break;
                    case Surface.ROTATION_270:
                        gravity = Gravity.LEFT;
                        width = userContext.getResources().getDimensionPixelSize(
                                com.android.internal.R.dimen.navigation_bar_width);
                        break;
                }
            }
            WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                    width,
                    height,
                    WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
//...
                    PixelFormat.TRANSLUCENT);
//...
            return lp;
        }

阐明:

  • config_navBarCanMove: 查了下values-sw600dp下是false,values下是true,所以对于手机来说应该是true了
  • android 13里横屏的时候导航键是在左右显现的,不像曾经在底部
  • 导航栏里面是加载了两种线性布局的,一种是横向的,一种是竖的,依据旋转视点显现不同的布局

2.4.NavBar相关view

>NavigationBarView

public class NavigationBarView extends FrameLayout {
    public void onFinishInflate() {
        super.onFinishInflate();
        mNavigationInflaterView = findViewById(R.id.navigation_inflater);
        mNavigationInflaterView.setButtonDispatchers(mButtonDispatchers);
        updateOrientationViews();
        reloadNavIcons();
    }

>NavigationBarInflaterView

public class NavigationBarInflaterView extends FrameLayout
        implements NavigationModeController.ModeChangedListener {
    protected void onFinishInflate() {
        super.onFinishInflate();
        inflateChildren();//手动增加children容器控件
        clearViews();
        inflateLayout(getDefaultLayout());//手动加载按钮
    }
//增加了 横屏和竖屏两种布局,依据实际情况显现
    private void inflateChildren() {
        removeAllViews();
        mHorizontal = (FrameLayout) mLayoutInflater.inflate(R.layout.navigation_layout,
                this /* root */, false /* attachToRoot */);
        addView(mHorizontal);
        mVertical = (FrameLayout) mLayoutInflater.inflate(R.layout.navigation_layout_vertical,
                this /* root */, false /* attachToRoot */);
        addView(mVertical);
        updateAlternativeOrder();
    }        

>getDefaultLayout

导航栏都显现哪些按钮?读取string

    protected String getDefaultLayout() {
        final int defaultResource = QuickStepContract.isGesturalMode(mNavBarMode)
                ? R.string.config_navBarLayoutHandle
                : mOverviewProxyService.shouldShowSwipeUpUI()
                        ? R.string.config_navBarLayoutQuickstep
                        : R.string.config_navBarLayout;
        return getContext().getString(defaultResource);
    }

分号离隔左中右,然后逗号离隔,w表明比重,A表明绝对值,C表明居中,具体的的逻辑看代码怎么解析这些字符串

    <!-- Nav bar button default ordering/layout -->
    <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>
    <string name="config_navBarLayoutHandle" translatable="false">back[70AC];home_handle;ime_switcher[70AC]</string>

left和right,最终是被替换成space 和 menu_ime了

    View createView(String buttonSpec, ViewGroup parent, LayoutInflater inflater) {
        View v = null;
        String button = extractButton(buttonSpec);
        if (LEFT.equals(button)) {
            button = extractButton(NAVSPACE);
        } else if (RIGHT.equals(button)) {
            button = extractButton(MENU_IME_ROTATE);
        }

>navigation_layout.xml

里面是2个horizontal的线性布局,显现在底部的导航栏

<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:systemui="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginStart="@dimen/rounded_corner_content_padding"
    android:layout_marginEnd="@dimen/rounded_corner_content_padding"
    android:paddingStart="@dimen/nav_content_padding"
    android:paddingEnd="@dimen/nav_content_padding"
    android:clipChildren="false"
    android:clipToPadding="false"
    android:id="@+id/horizontal">
    <com.android.systemui.navigationbar.buttons.NearestTouchFrame
        android:id="@+id/nav_buttons"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clipChildren="false"
        android:clipToPadding="false"
        systemui:isVertical="false">
        <LinearLayout
            android:id="@+id/ends_group"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="horizontal"
            android:clipToPadding="false"
            android:clipChildren="false" />
        <LinearLayout
            android:id="@+id/center_group"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_gravity="center"
            android:gravity="center"
            android:orientation="horizontal"
            android:clipToPadding="false"
            android:clipChildren="false" />
    </com.android.systemui.navigationbar.buttons.NearestTouchFrame>
</FrameLayout>

>navigation_layout_vertical.xml

里面是两个vertical的线性布局,显现在两边的导航栏,横屏的时候

<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:systemui="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginTop="@dimen/rounded_corner_content_padding"
    android:layout_marginBottom="@dimen/rounded_corner_content_padding"
    android:paddingTop="@dimen/nav_content_padding"
    android:paddingBottom="@dimen/nav_content_padding"
    android:id="@+id/vertical">
    <com.android.systemui.navigationbar.buttons.NearestTouchFrame
        android:id="@+id/nav_buttons"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clipChildren="false"
        android:clipToPadding="false"
        systemui:isVertical="true">
        <com.android.systemui.navigationbar.buttons.ReverseLinearLayout
            android:id="@+id/ends_group"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"
            android:clipToPadding="false"
            android:clipChildren="false" />
        <com.android.systemui.navigationbar.buttons.ReverseLinearLayout
            android:id="@+id/center_group"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:orientation="vertical"
            android:clipToPadding="false"
            android:clipChildren="false" />
    </com.android.systemui.navigationbar.buttons.NearestTouchFrame>
</FrameLayout>

3.NavigationModeController.java

>获取mode

    public int addListener(ModeChangedListener listener) {
        mListeners.add(listener);
        return getCurrentInteractionMode(mCurrentUserContext);
    }
        private int getCurrentInteractionMode(Context context) {
            int mode = context.getResources().getInteger(
                    com.android.internal.R.integer.config_navBarInteractionMode);
            return mode;
        }

>mode

model有如下三种,能够看到能够有3个按钮,2个按钮,以及1个按钮

    <!-- Controls the navigation bar interaction mode:
         0: 3 button mode (back, home, overview buttons)
         1: 2 button mode (back, home buttons + swipe up for overview)
         2: gestures only for back, home and overview -->
    <integer name="config_navBarInteractionMode">0</integer>

4.总结

这里简略整理了下导航栏的创立进程。

  • 是由NavigationBarController实例化NavigationBar
  • NavigationBar里又经过注解获取要用到的View,然后在onInit办法里经过WindowManager把View增加到窗口上
  • 显现几个按钮是装备里决定的,具体看mode的阐明
  • 具体的按钮的增加逻辑都是在NavigationBarView,NavigationBarinflaterView里,至于按钮的显现巨细啥的能够看getDefaultLayout里的装备string