最近有個需求是修改虛擬按鍵的單擊和長按效果。所以研究了下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。
<com.android.systemui.statusbar.phone.NavigationBarView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:background="@drawable/system_bar_background">
<com.android.systemui.statusbar.phone.NavigationBarInflaterView
android:id="@+id/navigation_inflater"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.android.systemui.statusbar.phone.NavigationBarView>
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"即可。
修改前,對應 <string name="config_navBarLayout" translatable="false">space,back;home;recent,menu_ime</string>
修改後,對應 <string name="config_navBarLayout" translatable="false">space,recent;home;back,menu_ime</string>
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,這是個自定義屬性,用於指定這個按鈕的鍵值。這個主要用於點擊事件,詳見第二節。
<com.android.systemui.statusbar.policy.KeyButtonView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/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"
/>
點擊效果的實現流程請見下篇文章