本文主要分为两个部分

一.NavigationBar的加载流程

二.Android P上如何去除NavigationBar

 

一 NavigationBar的加载流程

NavigationBar就是我们常说的导航栏,今天我们来探究一下NavigationBar的加载流程。

frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/Statusbar.java

首先在makeStatusBarView中进行判断是否有navigationBar

protected void makeStatusBarView() {        try {            //判断是否有navigationbar            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);        });    }

通过上面的方法我们可以看见调用NavigationBarFragment去进行初始化的,我们继续看这个方法。

frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java

先构造一个继承于Fragment的NavigationBarFragment,然后inflate了一个FrameLayout布局"R.layout.navigation_bar_window",然后通过FragmentManager把NavigationBarFragment对象给替换进去,触发Fragment生命周期里的onCreateView(),再inflate布局"R.layout.navigation_bar"加载真正需要显示的NavigationBarView。

    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,                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.token = new Binder();        lp.setTitle("NavigationBar");        lp.windowAnimations = 0;        //加载布局文件        View navigationBarView = LayoutInflater.from(context).inflate(                R.layout.navigation_bar_window, null);        if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + navigationBarView);        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)                .commit();        fragmentHost.addTagListener(TAG, listener);        return navigationBarView;    }}
    @Override    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,            Bundle savedInstanceState) {        return inflater.inflate(R.layout.navigation_bar, container, false);    }

这里面我们看一下navigation_bar.xml

    

我们进入frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java中去查看

    public NavigationBarInflaterView(Context context, AttributeSet attrs) {        super(context, attrs);        createInflaters();        Display display = ((WindowManager)                context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();        Mode displayMode = display.getMode();        isRot0Landscape = displayMode.getPhysicalWidth() > displayMode.getPhysicalHeight();    }    private void createInflaters() {       //根据屏幕旋转角度去写子view        mLayoutInflater = LayoutInflater.from(mContext);        Configuration landscape = new Configuration();        landscape.setTo(mContext.getResources().getConfiguration());        landscape.orientation = Configuration.ORIENTATION_LANDSCAPE;        mLandscapeInflater = LayoutInflater.from(mContext.createConfigurationContext(landscape));    }    @Override    protected void onFinishInflate() {        //该方法是view的生命周期,每一个view被inflate后都会加载调用该方法        super.onFinishInflate();        inflateChildren();        clearViews();        inflateLayout(getDefaultLayout());//加载了三个按钮的关键方法    }    private void inflateChildren() {        removeAllViews();        mRot0 = (FrameLayout) mLayoutInflater.inflate(R.layout.navigation_layout, this, false);        mRot0.setId(R.id.rot0);        addView(mRot0);        mRot90 = (FrameLayout) mLayoutInflater.inflate(R.layout.navigation_layout_rot90, this,                false);        mRot90.setId(R.id.rot90);        addView(mRot90);        updateAlternativeOrder();    }

关键添加方法

   protected String getDefaultLayout() {        return mContext.getString(R.string.config_navBarLayout);    }
.//res/values-sw372dp/config.xml:    left[.25W],back[.5WC];home;recent[.5WC],right[.25W]

 

解析xml文件中的string

 protected void inflateLayout(String newLayout) {        mCurrentLayout = newLayout;        if (newLayout == null) {            newLayout = getDefaultLayout();        }        String[] sets = newLayout.split(GRAVITY_SEPARATOR, 3);//根据分号区分,变成3个数组        String[] start = sets[0].split(BUTTON_SEPARATOR);//根据逗号区分,包含 left[.25W]和back[.5WC]        String[] center = sets[1].split(BUTTON_SEPARATOR);//包含home        String[] end = sets[2].split(BUTTON_SEPARATOR);//包含recent[.5WC]和right[.25W]        // 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);    }

我们接着看inflateButtons的方法

    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);//调用inflateButton方法        }    }

调用inflatebutton方法

 @Nullable    protected View inflateButton(String buttonSpec, ViewGroup parent, boolean landscape,            boolean start) {        LayoutInflater inflater = landscape ? mLandscapeInflater : mLayoutInflater;        View v = createView(buttonSpec, parent, inflater); //创建view        if (v == null) return null;        v = applySize(v, buttonSpec, landscape, start);        parent.addView(v);        addToDispatchers(v);        View lastView = landscape ? mLastLandscape : mLastPortrait;        View accessibilityView = v;        if (v instanceof ReverseFrameLayout) {            accessibilityView = ((ReverseFrameLayout) v).getChildAt(0);        }        if (lastView != null) {            accessibilityView.setAccessibilityTraversalAfter(lastView.getId());        }        if (landscape) {            mLastLandscape = accessibilityView;        } else {            mLastPortrait = accessibilityView;        }        return v;    }

到这里我们基本就介绍完了NavigationBar的基本加载流程了。

我们在多说一下上面的createView方法:

这里我们可以看见会根据String去加载对应layout

 private View createView(String buttonSpec, ViewGroup parent, LayoutInflater inflater) {        View v = null;        String button = extractButton(buttonSpec);        if (LEFT.equals(button)) {            String s = Dependency.get(TunerService.class).getValue(NAV_BAR_LEFT, NAVSPACE);            button = extractButton(s);        } else if (RIGHT.equals(button)) {            String s = Dependency.get(TunerService.class).getValue(NAV_BAR_RIGHT, MENU_IME);            button = extractButton(s);        }        // Let plugins go first so they can override a standard view if they want.        for (NavBarButtonProvider provider : mPlugins) {            v = provider.createView(buttonSpec, parent);            if (v != null) return v;        }        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.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 (button.startsWith(KEY)) {            String uri = extractImage(button);            int code = extractKeycode(button);            v = inflater.inflate(R.layout.custom_key, parent, false);            ((KeyButtonView) v).setCode(code);            if (uri != null) {                if (uri.contains(":")) {                    ((KeyButtonView) v).loadAsync(Icon.createWithContentUri(uri));                } else if (uri.contains("/")) {                    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;    }

这里面我们以home.xml来探究

这里面的KeyButtonView 包含这个按钮的点击事件等

public boolean onTouchEvent(MotionEvent ev) {        final int action = ev.getAction();        int x, y;        if (action == MotionEvent.ACTION_DOWN) {            mGestureAborted = false;        }        if (mGestureAborted) {            return false;        }        switch (action) {            case MotionEvent.ACTION_DOWN:                mDownTime = SystemClock.uptimeMillis();                mLongClicked = false;                setPressed(true);                if (mCode != 0) {                    sendEvent(KeyEvent.ACTION_DOWN, 0, mDownTime);                } else {                    // Provide the same haptic feedback that the system offers for virtual keys.                    performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);                }                playSoundEffect(SoundEffectConstants.CLICK);                removeCallbacks(mCheckLongPress);                postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout());                break;            case MotionEvent.ACTION_MOVE:                x = (int)ev.getX();                y = (int)ev.getY();                setPressed(x >= -mTouchSlop                        && x < getWidth() + mTouchSlop                        && y >= -mTouchSlop                        && y < getHeight() + mTouchSlop);                break;            case MotionEvent.ACTION_CANCEL:                setPressed(false);                if (mCode != 0) {                    sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED);                }                removeCallbacks(mCheckLongPress);                break;            case MotionEvent.ACTION_UP:                final boolean doIt = isPressed() && !mLongClicked;                setPressed(false);                // Always send a release ourselves because it doesn't seem to be sent elsewhere                // and it feels weird to sometimes get a release haptic and other times not.                if ((SystemClock.uptimeMillis() - mDownTime) > 150 && !mLongClicked) {                    performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY_RELEASE);                }                if (mCode != 0) {                    if (doIt) {                        sendEvent(KeyEvent.ACTION_UP, 0);                        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);                    } else {                        sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED);                    }                } else {                    // no key code, just a regular ImageView                    if (doIt && mOnClickListener != null) {                        mOnClickListener.onClick(this);                        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);                    }                }                removeCallbacks(mCheckLongPress);                break;        }        return true;    }

上面有一个很重要的方法就是sendEvent()方法,实质是调用InputManager的injectInputEvent模拟发送来实现与物理按键相同的功能。

    public void sendEvent(int action, int flags) {        sendEvent(action, flags, SystemClock.uptimeMillis());    }    void sendEvent(int action, int flags, long when) {        mMetricsLogger.write(new LogMaker(MetricsEvent.ACTION_NAV_BUTTON_EVENT)                .setType(MetricsEvent.TYPE_ACTION)                .setSubtype(mCode)                .addTaggedData(MetricsEvent.FIELD_NAV_ACTION, action)                .addTaggedData(MetricsEvent.FIELD_FLAGS, flags));        final int repeatCount = (flags & KeyEvent.FLAG_LONG_PRESS) != 0 ? 1 : 0;        final KeyEvent ev = new KeyEvent(mDownTime, when, action, mCode, repeatCount,                0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,                flags | KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,                InputDevice.SOURCE_KEYBOARD);        //模拟物理按键的方法去实现的        InputManager.getInstance().injectInputEvent(ev,                InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);    }

这里面最后在补充一下,对于icon的加载是通过NavigationBarView.java这个方法来实现的。

二、Android P上如何去除NavigationBar

/**     *  NavigationBarFragment.java 新增方法    *  用于StatusBar里删除导航栏时调用     */    public static void removeFragment(Context context, NavigationBarFragment fragment, View view) {        if (fragment != null && view != null) {            ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).removeView(view);            FragmentHostManager fragmentHost = FragmentHostManager.get(view);            fragmentHost.getFragmentManager().beginTransaction()                    .remove(fragment)                    .commit();        }    }

 

更多相关文章

  1. 浅谈Java中Collections.sort对List排序的两种方法
  2. Python list sort方法的具体使用
  3. python list.sort()根据多个关键字排序的方法实现
  4. Android中如何像 360 一样优雅的杀死后台服务而不启动
  5. android调用第三方软件打开下载的附件
  6. 自定义View系列教程07--详解ViewGroup分发Touch事件
  7. Android(安卓)Menu学习
  8. android:configChanges属性
  9. Android(安卓)Studio快捷键以及使用技巧——诺诺"涂鸦"记忆

随机推荐

  1. 2011.09.14(2)——— android tabhost位于
  2. Android中对媒体的使用
  3. Android(安卓)Studio 编译常见问题解决办
  4. revoke_permission 实现过程
  5. Android线性LinearLayout布局xml属性介绍
  6. 关于 Android 进程保活,你所需要知道的一
  7. Andriod是什么?
  8. Android动态获取json解析后显示到Recycle
  9. Android内存泄漏查找
  10. 学习TimePicker和DataPicker