SystemUI之NavigationBar加载流程

前面介绍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值。

你可能感兴趣的:(SystemUI之NavigationBar加载流程)