03. SystemUI 系统导航栏 NavigationBar 分析(AndroidO)

本文详细分析了Android O系统导航栏(NavigationBar)的创建、布局加载和按键监听过程,包括NavigationBarView和NavigationBarInflaterView的作用,以及如何进行客制化操作,如导航栏的创建/隐藏和按键的增删。
摘要由CSDN通过智能技术生成

SystemUI 系统导航栏 NavigationBar 分析(AndroidO)

1 概述

导航栏是指在屏幕底端或侧边容纳了一排虚拟按键的一个窗口,这些按键包括了常用的返回键、主页键、最近任务键等。导航栏窗口作为 SystemUI 的一个重要组件,它的启动时机是在状态栏之后,同时导航栏窗口也是通过 WindowManager.addView() 方法创建的。

2 导航栏分析

2.1 导航栏窗口创建

和状态栏一样,导航栏的创建入口也是在 StatusBar.makeStatusBarView()。

// frameworks\base\packages\SystemUI\src\com\android\systemui\statusbar\phone\StatusBar.java
protected void makeStatusBarView() {
   
    // 创建状态栏...
    ...
    // 创建导航栏
    try {
   
        boolean showNav = mWindowManagerService.hasNavigationBar(); // 1 是否显示导航栏
        if (showNav) {
   
            createNavigationBar(); // 2 创建导航栏的控件树
        }
    } catch (RemoteException ex) {
   
        // no window manager? good luck with that
    }
    ...
}

通过 hasNavigationBar() 判断是否需要创建显示导航栏,首先看配置文件,然后检查系统属性 qemu.hw.mainkeys 是否设置,两个判断标志都在 PhoneWindowManager 类的 setInitialDisplaySize() 方法中获取设置。

// frameworks\base\services\core\java\com\android\server\wm\WindowManagerService.java 
public boolean hasNavigationBar() {
   
    return mPolicy.hasNavigationBar();
}

// frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.java
public class PhoneWindowManager implements WindowManagerPolicy {
   
    boolean mHasNavigationBar = false;
    public boolean hasNavigationBar() {
   
        return mHasNavigationBar; //
    }

    public void setInitialDisplaySize(Display display, int width, int height, int density) {
   
        // frameworks\base\core\res\res\values\config.xml  默认为true
        mHasNavigationBar = res.getBoolean(com.android.internal.R.bool.config_showNavigationBar);
        // 可以在模拟器中通过设置系统属性进行覆盖
        String navBarOverride = SystemProperties.get("qemu.hw.mainkeys");
        if ("1".equals(navBarOverride)) {
   
            mHasNavigationBar = false;
        } else if ("0".equals(navBarOverride)) {
   
            mHasNavigationBar = true;
        }
    } 
}

createNavigationBar() 通过调用 NavigationBarFragment.create() 来加载导航栏控件树。

// frameworks\base\packages\SystemUI\src\com\android\systemui\statusbar\phone\StatusBar.java
private NavigationBarFragment mNavigationBar;
private View mNavigationBarView;
protected void createNavigationBar() {
   
    // navigation_bar_window.xml
    mNavigationBarView = NavigationBarFragment.create(mContext, (tag, fragment) -> {
    
        // navigation_bar.xml
        mNavigationBar = (NavigationBarFragment) fragment; 
        if (mLightBarController != null) {
   
            mNavigationBar.setLightBarController(mLightBarController);
        }
        mNavigationBar.setCurrentSysuiVisibility(mSystemUiVisibility);
    });
}

create() 方法和创建状态栏相似,同样是调用 WindowManager.addView 创建导航栏窗口,而在 onCreateView() 方法中真正加载的 RootView 是 navigation_bar.xml 布局。在这里,导航栏的窗口类型是 TYPE_NAVIGATION_BAR,导航栏的宽度高度都是 MATCH_PARENT,但不会充满整个屏幕,而是充满由 WindowManager 提供的父布局 ,其大小是在 PhoneWindowManager.beginLayoutLw() 中计算的,后面会分析到。

// frameworks\base\packages\SystemUI\src\com\android\systemui\statusbar\phone\NavigationBarFragment.java
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, //窗口类型 FIRST_SYSTEM_WINDOW+19
            WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING // 接受导致设备唤醒的触摸事件
                    | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE //不接受按键事件
                    | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL // 消耗所有指针事件本身,而不管它们是否在窗口内
                    | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH // // 该窗口将接受超出其边界的触摸事件,并将其发送到也支持拆分触摸的其他窗口
                    | WindowManager.LayoutParams.FLAG_SLIPPERY, // 在手势中期,使触摸能够从窗口滑出到相邻窗口中,而不是在手势持续时间内被捕获
            PixelFormat.TRANSLUCENT);
    lp.token = new Binder();
    lp.setTitle("NavigationBar");
    lp.windowAnimations = 0; // 不使用窗口动画

    //加载导航栏控件树 frameworks\base\packages\SystemUI\res\layout\navigation_bar_window.xml
    View navigationBarView = LayoutInflater.from(context).inflate(
            R.layout.navigation_bar_window, null);
    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) // navigation_bar_window.xml
            .commit();
    fragmentHost.addTagListener(TAG, listener);
    return navigationBarView;
}

NavigationBarFragment

// frameworks\base\packages\SystemUI\src\com\android\systemui\statusbar\phone\NavigationBarFragment.java
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
                         Bundle savedInstanceState) {
   
    // 加载布局  frameworks\base\packages\SystemUI\res\layout\navigation_bar.xml
    return inflater.inflate(R.layout.navigation_bar, container, false);
}

@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);
    backButton.setOnLongClickListener(this::onLongPressBackRecents);
    ButtonDispatcher homeButton = mNavigationBarView.getHomeButton();

    homeButton.setOnTouchListener(this::onHomeTouch);
    homeButton.setOnLongClickListener(this::onHomeLongClick);
    ...
}

再来看一下 PhoneWindowManager.beginLayoutLw() 是怎么计算的导航栏位置的。

一般来说,导航栏位于屏幕底部和右侧,这是根据屏幕方向及高宽大小决定的。 这里 mNavigationBarCanMove 变量代表导航栏是否可以移动,是在PhoneWindowManager.setInitialDisplaySize() 方法中设置的。mNavigationBarPosition 变量代表导航栏最终的布局位置。

// frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.java
public void beginLayoutLw(boolean isDefaultDisplay, int displayWidth, int displayHeight,int displayRotation, int uiMode) {
   
    ...
        boolean updateSysUiVisibility = layoutNavigationBar(displayWidth, displayHeight,displayRotation, uiMode, overscanLeft, overscanRight, overscanBottom, dcf, navVisible, navTranslucent,navAllowedHidden, statusBarExpandedNotKeyguard); //
    updateSysUiVisibility |= layoutStatusBar(pf, df, of, vf, dcf, sysui, isKeyguardShowing); //
    if (updateSysUiVisibility) {
   
        updateSystemUiVisibilityLw();
    }
}

private boolean<
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值