android13#navigationbar

1,716 阅读12分钟

1.简介

  • 平板下用的是taskbar,在launcher3里,固定显示在底部的导航栏
  • 手机模式用的是NavitionBar,在SystemUi下
  • 需求,平板设备,不要taskbar,横屏的话只要3个导航键固定显示在右侧
  • 源码里判断是否是平板模式,也就是isLargeScreen 都是看屏幕最小宽度是否大于600dp

2.TaskbarManager.java

2.1.recreateTaskbar

  • isTaskbarEnabled为false的话就不会加载taskbar,这里可以改为false
  • 三键导航模式下改为false,手势模式下还是用默认的即可。
    public void recreateTaskbar() {
        DeviceProfile dp = mUserUnlocked ?
                LauncherAppState.getIDP(mContext).getDeviceProfile(mContext) : null;

        destroyExistingTaskbar();
//是否开启taskbar
        boolean isTaskbarEnabled = dp != null && isTaskbarPresent(dp);//补充1
        if (!isTaskbarEnabled) {
            SystemUiProxy.INSTANCE.get(mContext)
                    .notifyTaskbarStatus(/* visible */ false, /* stashed */ false);
            return;
        }

        if (mTaskbarActivityContext == null) {
            mTaskbarActivityContext = new TaskbarActivityContext(mContext, dp, mNavButtonController,
                    mUnfoldProgressProvider);
        } else {
            mTaskbarActivityContext.updateDeviceProfile(dp, mNavMode);
        }
        mTaskbarActivityContext.init(mSharedState);

        if (mActivity != null) {
            mTaskbarActivityContext.setUIController(
                    createTaskbarUIControllerForActivity(mActivity));
        }
    }

>1.isTaskbarPresent

  • FLAG_HIDE_NAVBAR_WINDOW 是个系统属性,测试用的,就不看了
  • isTaskbarPresent 这个就是判断设备的最小宽度是否大于600dp
    private boolean isTaskbarPresent(DeviceProfile deviceProfile) {
        return FLAG_HIDE_NAVBAR_WINDOW || deviceProfile.isTaskbarPresent;
    }

3.NavigationBarController

3.1.createNavigationBar

navigationbar的创建方法

    void createNavigationBar(Display display, Bundle savedState, RegisterStatusBarResult result) {
        if (display == null) {
            return;
        }

        final int displayId = display.getDisplayId();
        final boolean isOnDefaultDisplay = displayId == mDisplayTracker.getDefaultDisplayId();
//参考补充1
        if (isOnDefaultDisplay && initializeTaskbarIfNecessary()) {
        //默认屏幕,如果加载的taskbar,则返回
            return;
        }
//走到这里就要加载navBar了
        final IWindowManager wms = WindowManagerGlobal.getWindowManagerService();

        try {//参考补充2
            if (!wms.hasNavigationBar(displayId)) {
                return;
            }
        } catch (RemoteException e) {
            return;
        }
        final Context context = isOnDefaultDisplay
                ? mContext
                : mContext.createDisplayContext(display);
        NavigationBarComponent component = mNavigationBarComponentFactory.create(
                context, savedState);
        NavigationBar navBar = component.getNavigationBar();
        //参考4.2
        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);
            }
        });
    }

>1.initializeTaskbarIfNecessary

  • taskbarEnabled,我们需要修改成false
    private boolean initializeTaskbarIfNecessary() {
        //大屏的话用taskbar
        boolean taskbarEnabled = mIsLargeScreen || mFeatureFlags.isEnabled(
                Flags.HIDE_NAVBAR_WINDOW);

        if (taskbarEnabled) {
            final int displayId = mContext.getDisplayId();
            mNavBarHelper.setTogglingNavbarTaskbar(mNavigationBars.contains(displayId));
            //如果有navigationBar的话,移除
            removeNavigationBar(displayId);
            //初始化taskbar
            mTaskbarDelegate.init(displayId);
            mNavBarHelper.setTogglingNavbarTaskbar(false);
        } else {//销毁taskbar
            mTaskbarDelegate.destroy();
        }
        return taskbarEnabled;
    }

>2.DisplayPolicy.java

构造方法里


        if (mDisplayContent.isDefaultDisplay) {
            mHasStatusBar = true;
            //是否有导航栏,默认配置,平板没有,手机有,这个需要修改
            mHasNavigationBar = mContext.getResources().getBoolean(R.bool.config_showNavigationBar);

            //测试用,可以临时修改
            String navBarOverride = SystemProperties.get("qemu.hw.mainkeys");
            if ("1".equals(navBarOverride)) {
                mHasNavigationBar = false;
            } else if ("0".equals(navBarOverride)) {
                mHasNavigationBar = true;
            }
        } else {
            mHasStatusBar = false;//非默认屏幕,为false
            mHasNavigationBar = mDisplayContent.supportsSystemDecorations();
        }

3.2.onDisplayReady

    public void onDisplayReady(int displayId) {
        Display display = mDisplayManager.getDisplay(displayId);
        mIsLargeScreen = isLargeScreen(mContext);
        //参考2.1
        createNavigationBar(display, null /* savedState */, null /* result */);
    }

3.3.onConfigChanged

    public void onConfigChanged(Configuration newConfig) {
        boolean isOldConfigTablet = mIsTablet;
        mIsTablet = isTablet2(mContext);
        boolean largeScreenChanged = mIsTablet != isOldConfigTablet;
        //手机平板模式切换,并且是平板模式,那么不用处理
        if (largeScreenChanged && updateNavbarForTaskbar()) {
            return;
        }
//参考补充1,检查3种配置是否变化,是的话就需要重新创建
        if (mConfigChanges.applyNewConfig(mContext.getResources())) {
            for (int i = 0; i < mNavigationBars.size(); i++) {
                recreateNavigationBar(mNavigationBars.keyAt(i));
            }
        } else {//补充1里的3种配置未发生变化,那么走这里
            for (int i = 0; i < mNavigationBars.size(); i++) {
            //参考4.5
                mNavigationBars.valueAt(i).onConfigurationChanged(newConfig);
            }
        }
    }

>1.mConfigChanges

    private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges(
            ActivityInfo.CONFIG_FONT_SCALE | ActivityInfo.CONFIG_SCREEN_LAYOUT
                    | ActivityInfo.CONFIG_UI_MODE);

4.NavigationBar.java

注解生成的

public class NavigationBar extends ViewController<NavigationBarView> implements Callbacks {

4.1.构造方法

参数参考小节5

    NavigationBar(
            NavigationBarView navigationBarView,//参考5.2,注解,加入下边的容器里
            NavigationBarFrame navigationBarFrame,//这个是最外层容器,参考4.2通过wm添加
            @Nullable Bundle savedState,
            @DisplayId Context context,
            //...

        mNavBarMode = mNavigationModeController.addListener(mModeChangedListener);//补充1
    }

>1.mModeChangedListener

    private final ModeChangedListener mModeChangedListener = new ModeChangedListener() {
        @Override
        public void onNavigationModeChanged(int mode) {
            //导航模式变化,需要更新布局参数,这里是新加的
            //参考4.2.2,我们修改后的参数和导航模式有关的。
            if(mNavBarMode != mode)
            mWindowManager.updateViewLayout(mFrame, getBarLayoutParams(mContext.getResources().getConfiguration().windowConfiguration
                          .getRotation()));
            mNavBarMode = mode;

            if (!QuickStepContract.isGesturalMode(mode)) {
                if (getBarTransitions() != null) {
                    getBarTransitions().setBackgroundOverrideAlpha(1f);
                }
            }
            updateScreenPinningGestures();

            if (!canShowSecondaryHandle()) {
                resetSecondaryHandle();
            }
            setNavBarMode(mode);
            mView.setShouldShowSwipeUpUi(mOverviewProxyService.shouldShowSwipeUpUI());
        }
    };

4.2.onInit

    public void onInit() {

        mView.setBarTransitions(mNavigationBarTransitions);
        mView.setTouchHandler(mTouchHandler);
        setNavBarMode(mNavBarMode);
        mEdgeBackGestureHandler.setStateChangeCallback(mView::updateStates);
        mNavigationBarTransitions.addListener(this::onBarTransition);
        mView.updateRotationButton();

        mView.setVisibility(
                mStatusBarKeyguardViewManager.isNavBarVisible() ? View.VISIBLE : View.INVISIBLE);

//容器通过wm添加,布局参数参考补充1
        mWindowManager.addView(mFrame,
                getBarLayoutParams(mContext.getResources().getConfiguration().windowConfiguration
                        .getRotation()));//布局参数参考补充1
        mDisplayId = mContext.getDisplayId();
        mIsOnDefaultDisplay = mDisplayId == mDisplayTracker.getDefaultDisplayId();


        parseCurrentSysuiState();
        mCommandQueue.addCallback(this);
        mHomeButtonLongPressDurationMs = Optional.of(mDeviceConfigProxy.getLong(
                DeviceConfig.NAMESPACE_SYSTEMUI,
                HOME_BUTTON_LONG_PRESS_DURATION_MS,
                /* defaultValue = */ 0
        )).filter(duration -> duration != 0);
        //
        mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
        mDeviceConfigProxy.addOnPropertiesChangedListener(
                DeviceConfig.NAMESPACE_SYSTEMUI, mHandler::post, mOnPropertiesChangedListener);
//..
        // Respect the latest disabled-flags.
        mCommandQueue.recomputeDisableFlags(mDisplayId, false);

        mNotificationShadeDepthController.addListener(mDepthListener);
        mTaskStackChangeListeners.registerTaskStackListener(mTaskStackListener);
    }

>1.getBarLayoutParams

    private WindowManager.LayoutParams getBarLayoutParams(int rotation) {
    //先获取当前方向的布局参数
        WindowManager.LayoutParams lp = getBarLayoutParamsForRotation(rotation);
        //再获取4个方向的布局参数
        lp.paramsForRotation = new WindowManager.LayoutParams[4];
        for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) {
            lp.paramsForRotation[rot] = getBarLayoutParamsForRotation(rot);
        }
        return lp;
    }

>2.getBarLayoutParamsForRotation

  • 我们要固定显示在右侧,所以这里需要修改。使用90度的那个配置即可。
  • 这里是容器NavigationBarFrame的布局参数
  • 需要监听导航模式的改变,更新这个参数
    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;
        //navBar是否可以显示在两侧
        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);//补充3
        }
        
/** 增加这样的配置,三键导航的话走else,
        boolean fixedRight = android.os.SystemProperties.getBoolean("persist.sys.navbar.right", false);
        if(fixedRight && !QuickStepContract.isGesturalMode(mNavBarMode)){
            navBarCanMove = true;
        } */

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://竖屏,显示在底部,高度固定
                //最终都指向了补充4里的navigation_bar_height
                    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://横屏,右侧
                    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,
                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.gravity = gravity;
        if (insetsHeight != -1) {
            lp.providedInsets = new InsetsFrameProvider[] {
                new InsetsFrameProvider(ITYPE_NAVIGATION_BAR, Insets.of(0, 0, 0, insetsHeight))
            };
        } else {
            lp.providedInsets = new InsetsFrameProvider[] {
                    new InsetsFrameProvider(ITYPE_NAVIGATION_BAR)
            };
        }
        lp.token = new Binder();
        lp.accessibilityTitle = userContext.getString(R.string.nav_bar);
        lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC
                | WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
        lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
        lp.windowAnimations = 0;
        lp.setTitle("NavigationBar" + userContext.getDisplayId());
        lp.setFitInsetsTypes(0 /* types */);
        lp.setTrustedOverlay();
        return lp;
    }

>3.config_navBarCanMove

  • 控制导航栏在横屏的时候是否可以移动到两侧,只适用于非正方形的设备
  • 默认是true,大于600dp的是false,手势模式下也是false
    <bool name="config_navBarCanMove">true</bool>

>4.navigation_bar_height

frameworks/base/core/res/res/values/dimens.xml

    <!-- Height of the bottom navigation / system bar. -->
    <dimen name="navigation_bar_height">48dp</dimen>
    <!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height -->
    <dimen name="navigation_bar_height_landscape">48dp</dimen>
    <!-- Width of the navigation bar when it is placed vertically on the screen -->
    <dimen name="navigation_bar_width">48dp</dimen>

4.3.

4.4.导航模式变化后的日志

  • 为了确认三键导航和手势导航之间切换都有哪些回调

>1.log1

代码参考3.3》4.5

		at com.android.systemui.navigationbar.NavigationBar.getBarLayoutParams(NavigationBar.java:1630)
		at com.android.systemui.navigationbar.NavigationBar.repositionNavigationBar(NavigationBar.java:1249)
		at com.android.systemui.navigationbar.NavigationBar.onConfigurationChanged(NavigationBar.java:875)
		at com.android.systemui.navigationbar.NavigationBarController.onConfigChanged(NavigationBarController.java:157)
		at com.android.systemui.statusbar.phone.ConfigurationControllerImpl.onConfigurationChanged(ConfigurationControllerImpl.kt:70)
		at com.android.systemui.SystemUIApplication.onConfigurationChanged(SystemUIApplication.java:359)

>2.log2

  • 参考4.6.1》4.6.2
		at com.android.systemui.navigationbar.NavigationBar.getBarLayoutParams(NavigationBar.java:1630)
		at com.android.systemui.navigationbar.NavigationBar.-$$Nest$mgetBarLayoutParams(Unknown Source:0)
		at com.android.systemui.navigationbar.NavigationBar$11.onNavigationModeChanged(NavigationBar.java:1915)
		at com.android.systemui.navigationbar.NavigationModeController.updateCurrentInteractionMode(NavigationModeController.java:144)
		at com.android.systemui.navigationbar.NavigationModeController$3.onThemeChanged(NavigationModeController.java:124)
		at com.android.systemui.statusbar.phone.ConfigurationControllerImpl.onConfigurationChanged(ConfigurationControllerImpl.kt:129)
		at com.android.systemui.SystemUIApplication.onConfigurationChanged(SystemUIApplication.java:359)

>log3

  • 4.6.1注册的广播,参考4.6.3,最终同样走的4.6.2
		at com.android.systemui.navigationbar.NavigationBar.getBarLayoutParams(NavigationBar.java:1630)
		at com.android.systemui.navigationbar.NavigationBar.-$$Nest$mgetBarLayoutParams(Unknown Source:0)
		at com.android.systemui.navigationbar.NavigationBar$11.onNavigationModeChanged(NavigationBar.java:1915)
		at com.android.systemui.navigationbar.NavigationModeController.updateCurrentInteractionMode(NavigationModeController.java:144)
		at com.android.systemui.navigationbar.NavigationModeController$2.onReceive(NavigationModeController.java:93)

4.5.onConfigurationChanged

  • 配置发生变化后进行的操作
    public void onConfigurationChanged(Configuration newConfig) {
        final int rotation = newConfig.windowConfiguration.getRotation();
        final Locale locale = mContext.getResources().getConfiguration().locale;
        //布局方向是从右往左还是从左往右
        final int ld = TextUtils.getLayoutDirectionFromLocale(locale);
        //区域或者布局方向变化,刷新
        if (!locale.equals(mLocale) || ld != mLayoutDirection) {

            mLocale = locale;
            mLayoutDirection = ld;
            refreshLayout(ld);
        }

        repositionNavigationBar(rotation);
        if (canShowSecondaryHandle()) {
            if (rotation != mCurrentRotation) {
                mCurrentRotation = rotation;
                orientSecondaryHomeHandle();
            }
        }
    }

>1.repositionNavigationBar

    private void repositionNavigationBar(int rotation) {
        if (mView == null || !mView.isAttachedToWindow()) return;

        prepareNavigationBarView();
//获取新的布局参数设置
        mWindowManager.updateViewLayout(mFrame, getBarLayoutParams(rotation));
    }

4.6.NavigationModeController.java

  • 切换导航模式,我们就是enable不同的RRO包,也就是overlay包发生了变化,会走对应的广播
  • 同时修改后系统配置也发生了变化,会走对应的监听,所以补充2的方法会走2次。

>1.构造方法

//注册广播监听overlay的改变,这里就监听android包
        IntentFilter overlayFilter = new IntentFilter(ACTION_OVERLAY_CHANGED);
        overlayFilter.addDataScheme("package");
        overlayFilter.addDataSchemeSpecificPart("android", PatternMatcher.PATTERN_LITERAL);
        //补充3
        mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, overlayFilter, null, null);
//监听配置的改变
        configurationController.addCallback(new ConfigurationController.ConfigurationListener() {
            @Override
            public void onThemeChanged() {

                updateCurrentInteractionMode(true /* notify */);//补充2
            }
        });

        updateCurrentInteractionMode(false /* notify */);//补充2
    }

>2.updateCurrentInteractionMode

    public void updateCurrentInteractionMode(boolean notify) {
        mCurrentUserContext = getCurrentUserContext();
        int mode = getCurrentInteractionMode(mCurrentUserContext);
        mUiBgExecutor.execute(() ->//更新settings的值
            Settings.Secure.putString(mCurrentUserContext.getContentResolver(),
                    Secure.NAVIGATION_MODE, String.valueOf(mode)));

        if (notify) {
            for (int i = 0; i < mListeners.size(); i++) {
            //监听是4.1的构造方法里注册的,回调参考4.1.1
                mListeners.get(i).onNavigationModeChanged(mode);
            }
        }
    }

>3.mReceiver

    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
        //补充2
            updateCurrentInteractionMode(true /* notify */);
        }
    };

5.NavigationBarModule

public interface NavigationBarModule {

5.1.provideNavigationBarFrame

    static NavigationBarFrame provideNavigationBarFrame(@DisplayId LayoutInflater layoutInflater) {
        return (NavigationBarFrame) layoutInflater.inflate(R.layout.navigation_bar_window, null);
    }

>1.navigation_bar_window.xml

就是个自定义的帧布局,没有啥特殊的

<com.android.systemui.navigationbar.NavigationBarFrame
    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>

5.2.provideNavigationBarview

    @Provides
    @NavigationBarScope
    static NavigationBarView provideNavigationBarview(
            @DisplayId LayoutInflater layoutInflater, NavigationBarFrame frame) {
            //可以看到,加载到5.1的容器frame里了
        View barView = layoutInflater.inflate(R.layout.navigation_bar, frame);
        return barView.findViewById(R.id.navigation_bar_view);//补充1
    }

>1.navigation_bar.xml

<com.android.systemui.navigationbar.NavigationBarView
    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>

5.3.navBar布局结构

  • 最外层的布局参考5.1.1,布局参数参考4.2.2,容器是NavigationBarFrame
  • 容器NavigationBarFrame里添加的布局是5.2.1
  • NavigationBarInflaterView里会动态添加两套导航容器,横向的和竖向的,根据方向显示不同的布局。

6.NavigationBarView.java

6.1.onMeasure

  • mIsVertical构造方法里默认设置为false
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int w = MeasureSpec.getSize(widthMeasureSpec);
        int h = MeasureSpec.getSize(heightMeasureSpec);
//非手势模式的竖屏
        final boolean newVertical = w > 0 && h > w
                && !isGesturalMode(mNavBarMode);
        if (newVertical != mIsVertical) {
            mIsVertical = newVertical;

            reorient();
            notifyVerticalChangedListener(newVertical);
        }

        if (isGesturalMode(mNavBarMode)) {
            //更新导航背景范围
            int height = mIsVertical
                    ? getResources().getDimensionPixelSize(
                            com.android.internal.R.dimen.navigation_bar_height_landscape)
                    : getResources().getDimensionPixelSize(
                            com.android.internal.R.dimen.navigation_bar_height);
            int frameHeight = getResources().getDimensionPixelSize(
                    com.android.internal.R.dimen.navigation_bar_frame_height);
            mBarTransitions.setBackgroundFrame(new Rect(0, frameHeight - height, w, h));
        } else {
            mBarTransitions.setBackgroundFrame(null);
        }

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

>1.updateCurrentView

    private void updateCurrentView() {
        resetViews();
        mCurrentView = mIsVertical ? mVertical : mHorizontal;
        mCurrentView.setVisibility(View.VISIBLE);
        mNavigationInflaterView.setVertical(mIsVertical);
        mNavigationInflaterView.updateButtonDispatchersCurrentView();
        updateLayoutTransitionsEnabled();
        updateCurrentRotation();
    }

>2.resetViews

    private void resetViews() {
        mHorizontal.setVisibility(View.GONE);
        mVertical.setVisibility(View.GONE);
    }

7.NavigationBarInflaterView

public final class NavigationBarInflaterView extends FrameLayout {

7.1.构造方法

    public NavigationBarInflaterView(Context context, AttributeSet attrs) {
        super(context, attrs);
        createInflaters();//补充1
        mOverviewProxyService = Dependency.get(OverviewProxyService.class);
        mNavBarMode = Dependency.get(NavigationModeController.class).addListener(this);
    }

>1.createInflaters

    void createInflaters() {
    //默认的布局加载器
        mLayoutInflater = LayoutInflater.from(mContext);
        Configuration landscape = new Configuration();
        landscape.setTo(mContext.getResources().getConfiguration());
        //默认使用的配置,设置为横屏
        landscape.orientation = Configuration.ORIENTATION_LANDSCAPE;
        //横向布局加载器,用的就是这个横屏配置
        mLandscapeInflater = LayoutInflater.from(mContext.createConfigurationContext(landscape));
    }

7.2.onFinishInflate

    protected void onFinishInflate() {
        super.onFinishInflate();
        inflateChildren();//补充1
        clearViews();//补充2
        //这个是加载按钮,参考7.4
        inflateLayout(getDefaultLayout());//布局参考7.3
    }

>1.inflateChildren

  • 加载了横向和竖向两套布局,区别就是里边线性布局的方向
    private void inflateChildren() {
        removeAllViews();
        mHorizontal = (FrameLayout) mLayoutInflater.inflate(R.layout.navigation_layout,//补充3
                this /* root */, false /* attachToRoot */);
        addView(mHorizontal);
        mVertical = (FrameLayout) mLayoutInflater.inflate(R.layout.navigation_layout_vertical,//补充4
                this /* root */, false /* attachToRoot */);
        addView(mVertical);
        updateAlternativeOrder();
    }

>2.clearViews

清除旧数据

    private void clearViews() {
        if (mButtonDispatchers != null) {
            for (int i = 0; i < mButtonDispatchers.size(); i++) {
                mButtonDispatchers.valueAt(i).clear();
            }
        }
        clearAllChildren(mHorizontal.findViewById(
                com.android.internal.R.id.input_method_nav_buttons));
    }

>3.navigation_layout.xml

  • 竖屏用的布局,里边的线性布局容器是horizontal的
<FrameLayout
    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>

>4.navigation_layout_verticalxml

  • 横屏用的布局,里边的线性布局容器是vertical的
<FrameLayout
    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>

7.3.getDefaultLayout

    protected String getDefaultLayout() {
        final int defaultResource = QuickStepContract.isGesturalMode(mNavBarMode)
                ? R.string.config_navBarLayoutHandle//手势导航用这个
                //非三键导航并且launcher里的组件RecentActivity可用
                : mOverviewProxyService.shouldShowSwipeUpUI()
                        ? R.string.config_navBarLayoutQuickstep
                        : R.string.config_navBarLayout;//三键导航用这个
        return getContext().getString(defaultResource);
    }

>1.资源文件

    <!--三键导航用这个-->
    <string name="config_navBarLayout" translatable="false">left[.5W],back[1WC];home;recent[1WC],right[.5W]</string>
    <!--非三键导航,这个应该用不到,也就2键导航可以用,我们默认没有这个模式-->
    <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>

>2.布局分析

  • 以"left[.5W],back[1WC];home;recent[1WC],right[.5W]"为例
  • 参考7.2.3或者7.2.4布局,参考7.5,left替换成了sapce,right替换成了menu
  • ends_group在下层,里边添加了比重为0.5的space,比重为1的back,比重为1的space,比重为1的recent,以及比重为0.5的menu容器
  • center_group在上层,就添加了一个home,居中显示

7.4.inflateLayout

  • 看下三键导航的字符串,参考上边的7.3.1
    protected void inflateLayout(String newLayout) {
        mCurrentLayout = newLayout;
        if (newLayout == null) {
            newLayout = getDefaultLayout();
        }
        String[] sets = newLayout.split(GRAVITY_SEPARATOR, 3);//冒号
        if (sets.length != 3) {
            newLayout = getDefaultLayout();
            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, mHorizontal.findViewById(R.id.ends_group),
                false /* landscape */, true /* start */);
        inflateButtons(start, mVertical.findViewById(R.id.ends_group),
                true /* landscape */, true /* start */);

        inflateButtons(center, mHorizontal.findViewById(R.id.center_group),
                false /* landscape */, false /* start */);
        inflateButtons(center, mVertical.findViewById(R.id.center_group),
                true /* landscape */, false /* start */);
//补充5,
        addGravitySpacer(mHorizontal.findViewById(R.id.ends_group));
        addGravitySpacer(mVertical.findViewById(R.id.ends_group));

        inflateButtons(end, mHorizontal.findViewById(R.id.ends_group),
                false /* landscape */, false /* start */);
        inflateButtons(end, mVertical.findViewById(R.id.ends_group),
                true /* landscape */, false /* start */);

        updateButtonDispatchersCurrentView();
    }

>1.inflateButtons

  • 横向的导航栏,landscape是false
  • 竖向的导航栏,landscape是true
    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);
        }
    }

>2.inflateButton

    protected View inflateButton(String buttonSpec, ViewGroup parent, boolean landscape,
            boolean start) {
         //根据方向使用不同的布局加载器
        LayoutInflater inflater = landscape ? mLandscapeInflater : mLayoutInflater;
        //参考7.5
        View v = createView(buttonSpec, parent, inflater);
        if (v == null) return null;
//补充3,调节大小
        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;
    }

>3.applySize

    private View applySize(View v, String buttonSpec, boolean landscape, boolean start) {
    //获取尺寸设置
        String sizeStr = extractSize(buttonSpec);//补充4
        if (sizeStr == null) return v;
//包含W或者A字符的
        if (sizeStr.contains(WEIGHT_SUFFIX) || sizeStr.contains(ABSOLUTE_SUFFIX)) {
            // 
            ReverseRelativeLayout frame = new ReverseRelativeLayout(mContext);
            LayoutParams childParams = new LayoutParams(v.getLayoutParams());

            // Compute gravity to apply
            int gravity = (landscape) ? (start ? Gravity.TOP : Gravity.BOTTOM)
                    : (start ? Gravity.START : Gravity.END);
            if (sizeStr.endsWith(WEIGHT_CENTERED_SUFFIX)) {//"WC"结尾的
                gravity = Gravity.CENTER;
            } else if (sizeStr.endsWith(ABSOLUTE_VERTICAL_CENTERED_SUFFIX)) {"C"结尾的
                gravity = Gravity.CENTER_VERTICAL;
            }

            // Set default gravity, flipped if needed in reversed layouts (270 RTL and 90 LTR)
            frame.setDefaultGravity(gravity);
            frame.setGravity(gravity); // Apply gravity to root

            frame.addView(v, childParams);

            if (sizeStr.contains(WEIGHT_SUFFIX)) {//包含"W"
                //解析比重值
                float weight = Float.parseFloat(
                        sizeStr.substring(0, sizeStr.indexOf(WEIGHT_SUFFIX)));
                frame.setLayoutParams(new LinearLayout.LayoutParams(0, MATCH_PARENT, weight));
            } else {//不包含"W"的,认为是绝对值,单位是dp
                int width = (int) convertDpToPx(mContext,
                        Float.parseFloat(sizeStr.substring(0, sizeStr.indexOf(ABSOLUTE_SUFFIX))));
                frame.setLayoutParams(new LinearLayout.LayoutParams(width, MATCH_PARENT));
            }

            // Ensure ripples can be drawn outside bounds
            frame.setClipChildren(false);
            frame.setClipToPadding(false);

            return frame;
        }
//走到这里,传递的是个缩放比列,参考7.5里的布局,默认是固定的宽度,这里进行缩放
        float size = Float.parseFloat(sizeStr);
        ViewGroup.LayoutParams params = v.getLayoutParams();
        params.width = (int) (params.width * size);
        return v;
    }

>4.extractSize

  • 看是否有中括号包含的内容 ,参考7.3.1,比如 back[1WC]返回1WC
    public static String extractSize(String buttonSpec) {
        if (!buttonSpec.contains(SIZE_MOD_START)) {
            return null;
        }
        final int sizeStart = buttonSpec.indexOf(SIZE_MOD_START);
        return buttonSpec.substring(sizeStart + 1, buttonSpec.indexOf(SIZE_MOD_END));
    }

>5.addGravitySpacer

添加一个占位符,比重是1的。

    private void addGravitySpacer(LinearLayout layout) {
        layout.addView(new Space(mContext), new LinearLayout.LayoutParams(0, 0, 1));
    }

7.5.createView

    View createView(String buttonSpec, ViewGroup parent, LayoutInflater inflater) {
        View v = null;
        String button = extractButton(buttonSpec);
        if (LEFT.equals(button)) {
        //left 换成 space
            button = extractButton(NAVSPACE);
        } else if (RIGHT.equals(button)) {
        //right 换成 menu_ime
            button = extractButton(MENU_IME_ROTATE);
        }
        //根据不同的字符串,加载不同的布局
        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);
        } else if (NAVSPACE.equals(button)) {
            v = inflater.inflate(R.layout.nav_key_space, parent, false);
        } else if (CLIPBOARD.equals(button)) {
            v = inflater.inflate(R.layout.clipboard, parent, false);
        } else if (CONTEXTUAL.equals(button)) {
            v = inflater.inflate(R.layout.contextual, parent, false);
        } else if (HOME_HANDLE.equals(button)) {
            v = inflater.inflate(R.layout.home_handle, parent, false);
        } else if (IME_SWITCHER.equals(button)) {
            v = inflater.inflate(R.layout.ime_switcher, parent, false);
        } else if (button.startsWith(KEY)) {//以key开头的字符串
        //自定义的,类似这种, key(3:http://xxx.xxx.xx/xxx.jpg) 或者 key(11:com.test.test/111)
            String uri = extractImage(button);//补充2
            int code = extractKeycode(button);//补充3
            v = inflater.inflate(R.layout.custom_key, parent, false);
            ((KeyButtonView) v).setCode(code);
            if (uri != null) {//图片地址
                if (uri.contains(":")) {//这种是个uri地址
                    ((KeyButtonView) v).loadAsync(Icon.createWithContentUri(uri));
                } else if (uri.contains("/")) {//这种是个包名加图片资源id
                    int index = uri.indexOf('/');
                    String pkg = uri.substring(0, index);
                    int id = Integer.parseInt(uri.substring(index + 1));
                    ((KeyButtonView) v).loadAsync(Icon.createWithResource(pkg, id));
                }
            }
        }
        return v;
    }

>1.extractButton

  • left[.5W] 和 back[1WC],返回的是left和back
  • home这种直接使用
    public static String extractButton(String buttonSpec) {
        if (!buttonSpec.contains(SIZE_MOD_START)) {
        //不是以中括号[开头的,直接使用
            return buttonSpec;
        }
        //只取中括号之前的内容
        return buttonSpec.substring(0, buttonSpec.indexOf(SIZE_MOD_START));
    }

>2.extractImage

  • 比如 key(3:urlxxxx) 返回urlxxxx
    public static String extractImage(String buttonSpec) {
        if (!buttonSpec.contains(KEY_IMAGE_DELIM)) {
        //不包含冒号的,返回空
            return null;
        }
        final int start = buttonSpec.indexOf(KEY_IMAGE_DELIM);
        //冒号和右括号之间的
        String subStr = buttonSpec.substring(start + 1, buttonSpec.indexOf(KEY_CODE_END));
        return subStr;
    }

>3.extractKeycode

  • 比如 key(3:urlxxxx) 返回3
    public static int extractKeycode(String buttonSpec) {
        if (!buttonSpec.contains(KEY_CODE_START)) {
        //不包含左括号的,返回1
            return 1;
        }
        final int start = buttonSpec.indexOf(KEY_CODE_START);
        //左括号和冒号之间的
        String subStr = buttonSpec.substring(start + 1, buttonSpec.indexOf(KEY_IMAGE_DELIM));
        return Integer.parseInt(subStr);
    }

>4.home.xml

  • KeyButtonView是自定义的ImageView,主要处理了下触摸事件,布局里的keyCode属性的处理
<com.android.systemui.statusbar.policy.KeyButtonView
    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"
    />

>5.back.xml

  • keyCode为4
<com.android.systemui.statusbar.policy.KeyButtonView
    android:id="@+id/back"
    android:layout_width="@dimen/navigation_key_width"
    android:layout_height="match_parent"
    android:layout_weight="0"
    systemui:keyCode="4"
    android:scaleType="center"
    android:contentDescription="@string/accessibility_back"
    android:paddingStart="@dimen/navigation_key_padding"
    android:paddingEnd="@dimen/navigation_key_padding"
    />

>6.recent_apps.xml

<com.android.systemui.statusbar.policy.KeyButtonView
    android:id="@+id/recent_apps"
    android:layout_width="@dimen/navigation_key_width"
    android:layout_height="match_parent"
    android:layout_weight="0"
    android:scaleType="center"
    android:contentDescription="@string/accessibility_recent"
    android:paddingStart="@dimen/navigation_key_padding"
    android:paddingEnd="@dimen/navigation_key_padding"
    />

>7.nav_key_space

<Space 
    android:layout_width="@dimen/navigation_side_padding"
    android:layout_height="match_parent"
    android:layout_weight="0"
    >
</Space>