最近有个需求是修改虚拟按键的单击和长按效果。所以研究了下Android关于虚拟按键的实现流程。好记性不如烂笔头,记录如下。


    首先,几个重要的类:

//实现 单个虚拟按键的 自定义ImageView

    frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java

//虚拟按键的容器,实现整个 虚拟导航条的 自定义LinearLayout

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

//动态加载虚拟按键,放入NavigationBarView

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

//虚拟导航条对应的布局文件

    frameworks/base/packages/SystemUI/res/layout/navigation_bar.xml

//实现虚拟按键的点击效果

    frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java


下面从几个方面来分析

1、View的创建流程

2、点击效果的实现流程

3、客制化:给NavigationBar添加一个隐藏键,点击隐藏NavigationBar,上滑又会显示NavigationBar

注:以下源码如果没有特殊说明,都在 frameworks/base/packages/SystemUI/ 目录下。


【一、View的创建流程

跟 StatusBar 一样,NavigationBar 也是在 PhoneStatusBar.java 中初始化的。

1、通过IWindowManager,先判断是否显示 NavigationBar。如果显示,则加载。

    protected PhoneStatusBarView makeStatusBarView() {        final Context context = mContext;...        try {            boolean showNav = mWindowManagerService.hasNavigationBar();            if (showNav) {                createNavigationBarView(context);            }        } catch (RemoteException ex) {            // no window manager? good luck with that        }...        return mStatusBarView;    }

2、主要看这个  inflateNavigationBarView 方法,它加载了布局 R.layout.navigation_bar,作为虚拟按键的容器

    protected void createNavigationBarView(Context context) {        inflateNavigationBarView(context);        mNavigationBarView.setDisabledFlags(mDisabled1);        mNavigationBarView.setComponents(mRecents, getComponent(Divider.class));        mNavigationBarView.setOnVerticalChangedListener(                new NavigationBarView.OnVerticalChangedListener() {            @Override            public void onVerticalChanged(boolean isVertical) {                if (mAssistManager != null) {                    mAssistManager.onConfigurationChanged();                }                mNotificationPanel.setQsScrimEnabled(!isVertical);            }        });        mNavigationBarView.setOnTouchListener(new View.OnTouchListener() {            @Override            public boolean onTouch(View v, MotionEvent event) {                checkUserAutohide(v, event);                return false;            }});    }    protected void inflateNavigationBarView(Context context) {        mNavigationBarView = (NavigationBarView) View.inflate(                context, R.layout.navigation_bar, null);    }

3、来到 navigation_bar.xml。我们发现,它并没有直接将虚拟按键加入进来,而是添加了一个NavigationBarInflaterView。

    

4、来到 NavigationBarInflaterView.java 继承自 FrameLayout。

    可以看到,在回调方法onFinishInflate()中通过 inflateLayout(getDefaultLayout())。getDefaultLayout()返回了一个字符串:“space,back;home;recent,menu_ime”。用来表示虚拟按键载入的顺序。

    将字串传入 inflateLayout()。通过“;”作为分隔符,将字串分为三个部分。 space,back  home recent,menu_ime

再通过“,”将字串进行二次分割,分别存入 start,center,end三个数组。start表示NavigationBar左边第一个位置,这里表示后退键在第一个位置,HOME键在中间,应用列表键在最右边。然后通过 inflateButton() 去加载xml布局文件。


    protected void onFinishInflate() {        super.onFinishInflate();        inflateChildren();        clearViews();        inflateLayout(getDefaultLayout());    }    protected String getDefaultLayout() {        /// M: BMW @{        if (MultiWindowManager.isSupported()) {            return mContext.getString(R.string.config_navBarLayout_float);        }        /// @}        return mContext.getString(R.string.config_navBarLayout);    }    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, (ViewGroup) mRot0.findViewById(R.id.ends_group), false);        inflateButtons(start, (ViewGroup) mRot90.findViewById(R.id.ends_group), true);        inflateButtons(center, (ViewGroup) mRot0.findViewById(R.id.center_group), false);        inflateButtons(center, (ViewGroup) mRot90.findViewById(R.id.center_group), true);        addGravitySpacer((LinearLayout) mRot0.findViewById(R.id.ends_group));        addGravitySpacer((LinearLayout) mRot90.findViewById(R.id.ends_group));        inflateButtons(end, (ViewGroup) mRot0.findViewById(R.id.ends_group), false);        inflateButtons(end, (ViewGroup) mRot90.findViewById(R.id.ends_group), true);    }

5、字串作为加载标识:1、space:填充一段空白内容;2、back:返回键;3、home:Home键;4、recent:应用列表键

如果想要调整虚拟按键的顺序,只需要调整字符串的顺序,好像还挺方便的。

例如:我要把back键和recent键调换位置,只需要修改config_navBarLayout的值为"space,recent;home;back,menu_ime"即可。

修改前,对应 space,back;home;recent,menu_ime

Android 7.0 虚拟按键(NavigationBar)源码分析 之 View的创建流程_第1张图片

修改后,对应 space,recent;home;back,menu_ime

Android 7.0 虚拟按键(NavigationBar)源码分析 之 View的创建流程_第2张图片

    protected View inflateButton(String buttonSpec, ViewGroup parent, boolean landscape,            int indexInParent) {        LayoutInflater inflater = landscape ? mLandscapeInflater : mLayoutInflater;        float size = extractSize(buttonSpec);        String button = extractButton(buttonSpec);        View v = null;        if (HOME.equals(button)) {            v = inflater.inflate(R.layout.home, parent, false);            if (landscape && isSw600Dp()) {                setupLandButton(v);            }        } else if (BACK.equals(button)) {            v = inflater.inflate(R.layout.back, parent, false);            if (landscape && isSw600Dp()) {                setupLandButton(v);            }        } else if (RECENT.equals(button)) {            v = inflater.inflate(R.layout.recent_apps, parent, false);            if (landscape && isSw600Dp()) {                setupLandButton(v);            }        }...        return v;    }

6、每个虚拟按键都有独立的布局文件:

    Home键:SystemUI/res/layout/home.xml

    返回键:SystemUI/res/layout/back.xml

    最近打开的应用列表键:SystemUI/res/layout/recent_apps.xml

Home键为例,布局文件如下,都是常见的属性,不多讲了。主要是这个systemui:keyCode,这是个自定义属性,用于指定这个按钮的键值。这个主要用于点击事件,详见第二节。


点击效果的实现流程请见下篇文章

更多相关文章

  1. Android存储设备(U盘,SD卡)状态监测(《Android 2.3 SD卡挂载流程
  2. Android四大布局之表格布局行列位置控制
  3. 从零开始学android开发-布局中 layout_gravity、gravity、orient
  4. [Hi3751V811][Android8.0]系统按键的转换 - android键值的映射
  5. Android应用程序模拟手机按键
  6. Android开机流程分析 -- Zygote
  7. android:layout_alignParent 布局相对于父布局的位置

随机推荐

  1. Android(安卓)四种线程池
  2. (4.1.36.3)android Graphics(一):概述及基本几
  3. 通过创建一个位图的XY Chart来学习Androi
  4. Android传感器 设备坐标系到世界坐标系的
  5. ios模仿android屏幕密度控件自动适配
  6. Android(安卓)用户界面---操作栏(Action B
  7. Android(安卓)传感器 II-运动传感器
  8. Android赢家密码的观点与实践——以软硬
  9. android 自定义ScrollView实现背景图片伸
  10. 这可能是最好的 Android/Kotlin日志输出