2023年12月6日发(作者:)

Android7.0虚拟按键(NavigationBar)源码分析之View的创

建流程

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

记录如下。

首先,几个重要的类:

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

frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/

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

frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/

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

frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/

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

frameworks/base/packages/SystemUI/res/layout/navigation_

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

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

下面从几个方面来分析

1、View的创建流程

2、点击效果的实现流程

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

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

【一、View的创建流程】

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

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

protected PhoneStatusBarView makeStatusBarView() {

final Context context = mContext;

...

try {

boolean showNav = igationBar();

if (showNav) {

createNavigationBarView(context);

}

} catch (RemoteException ex) {

// no window manager? good luck with that

}

...

return mStatusBarView;

}

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

protected void createNavigationBarView(Context context) {

inflateNavigationBarView(context);

abledFlags(mDisabled1);

ponents(mRecents, getComponent());

erticalChangedListener(

new icalChangedListener() {

@Override

public void onVerticalChanged(boolean isVertical) {

if (mAssistManager != null) {

igurationChanged();

}

crimEnabled(!isVertical);

}

});

ouchListener(new hListener() {

@Override

public boolean onTouch(View v, MotionEvent event) {

checkUserAutohide(v, event);

return false;

}});

}

protected void inflateNavigationBarView(Context context) {

mNavigationBarView = (NavigationBarView) e(

context, tion_bar, null);

}

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

NavigationBarInflaterView。

xmlns:android="/apk/res/android"

xmlns:systemui="/apk/res-auto"

android:layout_height="match_parent"

android:layout_width="match_parent"

android:background="@drawable/system_bar_background">

android:id="@+id/navigation_inflater"

android:layout_width="match_parent"

android:layout_height="match_parent" />

4、来到 继承自 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() {

shInflate();

inflateChildren();

clearViews();

inflateLayout(getDefaultLayout());

}

protected String getDefaultLayout() {

/// M: BMW @{

if (orted()) {

return ing(_navBarLayout_float);

}

/// @}

return ing(_navBarLayout);

}

protected void inflateLayout(String newLayout) {

mCurrentLayout = newLayout;

if (newLayout == null) {

newLayout = getDefaultLayout();

}

String[] sets = (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) ewById(_group), false);

inflateButtons(start, (ViewGroup) ewById(_group), true);

inflateButtons(center, (ViewGroup) ewById(_group), false);

inflateButtons(center, (ViewGroup) ewById(_group), true);

addGravitySpacer((LinearLayout) ewById(_group));

addGravitySpacer((LinearLayout) ewById(_group));

inflateButtons(end, (ViewGroup) ewById(_group), false);

inflateButtons(end, (ViewGroup) ewById(_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

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

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 ((button)) {

v = e(, parent, false);

if (landscape && isSw600Dp()) {

setupLandButton(v);

}

} else if ((button)) {

v = e(, parent, false);

if (landscape && isSw600Dp()) {

setupLandButton(v);

}

} else if ((button)) {

v = e(_apps, parent, false);

if (landscape && isSw600Dp()) {

setupLandButton(v);

}

}

...

return v;

}

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

Home键:SystemUI/res/layout/

返回键:SystemUI/res/layout/

最近打开的应用列表键:SystemUI/res/layout/recent_

以Home键为例,布局文件如下,都是常见的属性,不多讲了。主要是这个systemui:keyCode,这是个自定义属性,用于

指定这个按钮的键值。这个主要用于点击事件,详见第二节。

xmlns:android="/apk/res/android"

xmlns:systemui="/apk/res-auto"

android:id="@+id/home"

android:layout_width="@dimen/navigation_key_width"

android:layout_height="match_parent"

android:layout_weight="0"

android:src="@drawable/ic_sysbar_home"

systemui:keyCode="3"

android:scaleType="center"

android:contentDescription="@string/accessibility_home"

android:paddingStart="@dimen/navigation_key_padding"

android:paddingEnd="@dimen/navigation_key_padding"

/>

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