SystemUI之NavigationBar加載流程

前面介紹SystemUI啟動流程中,有介紹到SystemBars的啟動流程,其中NavigationBar屬于SystemBars模塊,今天與大家一同分析一下NavigationBar啟動以及運行流程。

初始化

NavigationBar在Statusbar初始化的時候,即調(diào)用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點擊\長按事件監(jiān)聽,代碼如下:

@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()注冊各個按鈕的點擊事件的監(jiān)聽。代碼如下:

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);
    }

那么如何做到在不同的界面顯示對應(yīng)的NavigationBar Button呢?NavigationBarView可以做到對各個button的管理。

NavigationBarView

NavigationBarView在其構(gòu)造方法中對每個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,當系統(tǒng)回調(diào)disable()時,NavigationBarView也會隨之根據(jù)disableFlags進行判斷,用來判斷是否顯示NavigationBar Button。

NavigationBarInflaterView

那么如何顯示back、home、recent排序呢?NavigationBarInflaterView這個類就實現(xiàn)了該邏輯,NavigationBarInflaterView在Navigationbar布局加載時靜態(tài)初始化,其邏輯如下:

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完畢后,讀取系統(tǒng)配置,進行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。開發(fā)人員可以到SystemUI工程目錄下修改"config_navBarLayout"的config值。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,431評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,637評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,555評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,900評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,629評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,976評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,976評論 3 448
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,139評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,686評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,411評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,641評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,129評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,820評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,233評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,567評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,362評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,604評論 2 380

推薦閱讀更多精彩內(nèi)容