导航栏便是屏幕下方或许两边几个按键,曾经是物理按键,现在都虚拟的。常见的便是后退键,home键,recents键。
当然了,如果有输入法的显现的话,或许会有个箭头按钮用来躲藏输入法。现在还支持手势操作,便是滑屏关闭页面,这时候导航栏或许就显现一个按钮线条。
1.创立
创立还是在CentralSurfacesImpl.java里面
// TODO(b/117478341): This was left such that CarStatusBar can override this method.
// Try to remove this.
protected void createNavigationBar(@Nullable RegisterStatusBarResult result) {
mNavigationBarController.createNavigationBars(true /* includeDefaultDisplay */, result);
}
能够看到是经过Controller来创立的,那就具体看下。
2.NavigationBarController
首先判别是否需要显现导航栏,需要的话再创立
public void createNavigationBars(final boolean includeDefaultDisplay,
RegisterStatusBarResult result) {
updateAccessibilityButtonModeIfNeeded();
// Don't need to create nav bar on the default display if we initialize TaskBar.
final boolean shouldCreateDefaultNavbar = includeDefaultDisplay
&& !initializeTaskbarIfNecessary();
Display[] displays = mDisplayManager.getDisplays();
for (Display display : displays) {
if (shouldCreateDefaultNavbar || display.getDisplayId() != DEFAULT_DISPLAY) {
createNavigationBar(display, null /* savedState */, result);
}
}
}
void createNavigationBar(Display display, Bundle savedState, RegisterStatusBarResult result) {
//...
final int displayId = display.getDisplayId();
final boolean isOnDefaultDisplay = displayId == DEFAULT_DISPLAY;
// We may show TaskBar on the default display for large screen device. Don't need to create
// navigation bar for this case.
if (isOnDefaultDisplay && initializeTaskbarIfNecessary()) {
return;
}
final IWindowManager wms = WindowManagerGlobal.getWindowManagerService();
try {
if (!wms.hasNavigationBar(displayId)) {
return;
}
} catch (RemoteException e) {
// Cannot get wms, just return with warning message.
Log.w(TAG, "Cannot get WindowManager.");
return;
}
//..
NavigationBarComponent component = mNavigationBarComponentFactory.create(
context, savedState);
//navBar是经过注解生成的
NavigationBar navBar = component.getNavigationBar();
navBar.init();
mNavigationBars.put(displayId, navBar);
navBar.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
if (result != null) {
navBar.setImeWindowStatus(display.getDisplayId(), result.mImeToken,
result.mImeWindowVis, result.mImeBackDisposition,
result.mShowImeSwitcher);
}
}
@Override
public void onViewDetachedFromWindow(View v) {
v.removeOnAttachStateChangeListener(this);
}
});
}
2.1NavigationBar实例化
>结构办法
@Inject
NavigationBar(
NavigationBarView navigationBarView,
NavigationBarFrame navigationBarFrame,
@Nullable Bundle savedState,
//...
super(navigationBarView);
mFrame = navigationBarFrame;
注解生成的,如下,
>NavigationBarComponent
@Subcomponent(modules = { NavigationBarModule.class })
@NavigationBarComponent.NavigationBarScope
public interface NavigationBarComponent {
/** Factory for {@link NavigationBarComponent}. */
@Subcomponent.Factory
interface Factory {
NavigationBarComponent create(
@BindsInstance @DisplayId Context context,
@BindsInstance @Nullable Bundle savedState);
}
/** */
NavigationBar getNavigationBar();
>NavigationBarModule
这里有NavigationBar结构办法里用到的一些view
/** Module for {@link com.android.systemui.navigationbar.NavigationBarComponent}. */
@Module
public interface NavigationBarModule {
/** A Layout inflater specific to the display's context. */
@Provides
@NavigationBarScope
@DisplayId
static LayoutInflater provideLayoutInflater(@DisplayId Context context) {
return LayoutInflater.from(context);
}
/** */
@Provides
@NavigationBarScope
static NavigationBarFrame provideNavigationBarFrame(@DisplayId LayoutInflater layoutInflater) {
return (NavigationBarFrame) layoutInflater.inflate(R.layout.navigation_bar_window, null);
}
/** */
@Provides
@NavigationBarScope
static NavigationBarView provideNavigationBarview(
@DisplayId LayoutInflater layoutInflater, NavigationBarFrame frame) {
View barView = layoutInflater.inflate(R.layout.navigation_bar, frame);
return barView.findViewById(R.id.navigation_bar_view);
}
/** */
@Provides
@NavigationBarScope
static EdgeBackGestureHandler provideEdgeBackGestureHandler(
EdgeBackGestureHandler.Factory factory, @DisplayId Context context) {
return factory.create(context);
}
/** A WindowManager specific to the display's context. */
@Provides
@NavigationBarScope
@DisplayId
static WindowManager provideWindowManager(@DisplayId Context context) {
return context.getSystemService(WindowManager.class);
}
}
相关的两个布局如下
>>navigation_bar_window.xml
<com.android.systemui.navigationbar.NavigationBarFrame
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:id="@+id/navigation_bar_frame"
android:theme="@style/Theme.SystemUI"
android:layout_height="match_parent"
android:layout_width="match_parent">
</com.android.systemui.navigationbar.NavigationBarFrame>
>>navigation_bar.xml
<com.android.systemui.navigationbar.NavigationBarView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/navigation_bar_view"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
android:background="@drawable/system_bar_background">
<com.android.systemui.navigationbar.NavigationBarInflaterView
android:id="@+id/navigation_inflater"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false" />
</com.android.systemui.navigationbar.NavigationBarView>
2.2init办法
init办法是父类ViewController里的
@NavigationBarScope
public class NavigationBar extends ViewController<NavigationBarView> implements Callbacks {
ViewController.java调用了onInit办法,给view增加attach,detach监听.
// 依据子类结构办法里”super(navigationBarView);”可知,这里的view是navigationBarView
protected ViewController(T view) {
mView = view;
}
public void init() {
if (mInited) {
return;
}
onInit();
mInited = true;
if (isAttachedToWindow()) {
mOnAttachStateListener.onViewAttachedToWindow(mView);
}
addOnAttachStateChangeListener(mOnAttachStateListener);
}
2.3onInit()
mView(NavigationBarView)是mFrame(NavigationBarFrame)的child,具体看上边Module里的加载进程以及相关的布局。
@Override
public void onInit() {
mView.setBarTransitions(mNavigationBarTransitions);
mView.setTouchHandler(mTouchHandler);
setNavBarMode(mNavBarMode);//把mode传递给mView,mode是啥以及咋来的后边讲
mEdgeBackGestureHandler.setStateChangeCallback(mView::updateStates);
mNavigationBarTransitions.addListener(this::onBarTransition);
mView.updateRotationButton();
mView.setVisibility(
mStatusBarKeyguardViewManager.isNavBarVisible() ? View.VISIBLE : View.INVISIBLE);
//增加导航栏的控件mFrame(NavigationBarFrame)
mWindowManager.addView(mFrame,//依据旋转视点获取布局参数
getBarLayoutParams(mContext.getResources().getConfiguration().windowConfiguration
.getRotation()));
mDisplayId = mContext.getDisplayId();
mIsOnDefaultDisplay = mDisplayId == DEFAULT_DISPLAY;
// Ensure we try to get currentSysuiState from navBarHelper before command queue callbacks
// start firing, since the latter is source of truth
parseCurrentSysuiState();
mCommandQueue.addCallback(this);
mLongPressHomeEnabled = mNavBarHelper.getLongPressHomeEnabled();
mNavBarHelper.init();
mHomeButtonLongPressDurationMs = Optional.of(mDeviceConfigProxy.getLong(
DeviceConfig.NAMESPACE_SYSTEMUI,
HOME_BUTTON_LONG_PRESS_DURATION_MS,
/* defaultValue = */ 0
)).filter(duration -> duration != 0);
mDeviceConfigProxy.addOnPropertiesChangedListener(
DeviceConfig.NAMESPACE_SYSTEMUI, mHandler::post, mOnPropertiesChangedListener);
//...
// Respect the latest disabled-flags.
mCommandQueue.recomputeDisableFlags(mDisplayId, false);
mNotificationShadeDepthController.addListener(mDepthListener);
}
>getBarLayoutParams
private WindowManager.LayoutParams getBarLayoutParams(int rotation) {
WindowManager.LayoutParams lp = getBarLayoutParamsForRotation(rotation);
//顺道保存了4种旋转视点对应的layoutParam
lp.paramsForRotation = new WindowManager.LayoutParams[4];
for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) {
lp.paramsForRotation[rot] = getBarLayoutParamsForRotation(rot);
}
return lp;
}
>getBarLayoutParamsForRotation
private WindowManager.LayoutParams getBarLayoutParamsForRotation(int rotation) {
int width = WindowManager.LayoutParams.MATCH_PARENT;
int height = WindowManager.LayoutParams.MATCH_PARENT;
int insetsHeight = -1;
int gravity = Gravity.BOTTOM;
boolean navBarCanMove = true;
final Context userContext = mUserContextProvider.createCurrentUserContext(mContext);
if (mWindowManager != null && mWindowManager.getCurrentWindowMetrics() != null) {
Rect displaySize = mWindowManager.getCurrentWindowMetrics().getBounds();
//读取装备
navBarCanMove = displaySize.width() != displaySize.height()
&& userContext.getResources().getBoolean(
com.android.internal.R.bool.config_navBarCanMove);
}
if (!navBarCanMove) {
//读取装备里写好的高度,手机平板高度不一样
height = userContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_frame_height);
insetsHeight = userContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_height);
} else {
switch (rotation) {
case ROTATION_UNDEFINED:
case Surface.ROTATION_0:
case Surface.ROTATION_180://默认的竖屏,导航栏在底部,所以约束的是高度
height = userContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_frame_height);
insetsHeight = userContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_height);
break;
case Surface.ROTATION_90://90/270导航栏在左右两边,所以约束的是宽度
gravity = Gravity.RIGHT;
width = userContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_width);
break;
case Surface.ROTATION_270:
gravity = Gravity.LEFT;
width = userContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_width);
break;
}
}
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
width,
height,
WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
//...
PixelFormat.TRANSLUCENT);
//...
return lp;
}
阐明:
- config_navBarCanMove: 查了下values-sw600dp下是false,values下是true,所以对于手机来说应该是true了
- android 13里横屏的时候导航键是在左右显现的,不像曾经在底部
- 导航栏里面是加载了两种线性布局的,一种是横向的,一种是竖的,依据旋转视点显现不同的布局
2.4.NavBar相关view
>NavigationBarView
public class NavigationBarView extends FrameLayout {
public void onFinishInflate() {
super.onFinishInflate();
mNavigationInflaterView = findViewById(R.id.navigation_inflater);
mNavigationInflaterView.setButtonDispatchers(mButtonDispatchers);
updateOrientationViews();
reloadNavIcons();
}
>NavigationBarInflaterView
public class NavigationBarInflaterView extends FrameLayout
implements NavigationModeController.ModeChangedListener {
protected void onFinishInflate() {
super.onFinishInflate();
inflateChildren();//手动增加children容器控件
clearViews();
inflateLayout(getDefaultLayout());//手动加载按钮
}
//增加了 横屏和竖屏两种布局,依据实际情况显现
private void inflateChildren() {
removeAllViews();
mHorizontal = (FrameLayout) mLayoutInflater.inflate(R.layout.navigation_layout,
this /* root */, false /* attachToRoot */);
addView(mHorizontal);
mVertical = (FrameLayout) mLayoutInflater.inflate(R.layout.navigation_layout_vertical,
this /* root */, false /* attachToRoot */);
addView(mVertical);
updateAlternativeOrder();
}
>getDefaultLayout
导航栏都显现哪些按钮?读取string
protected String getDefaultLayout() {
final int defaultResource = QuickStepContract.isGesturalMode(mNavBarMode)
? R.string.config_navBarLayoutHandle
: mOverviewProxyService.shouldShowSwipeUpUI()
? R.string.config_navBarLayoutQuickstep
: R.string.config_navBarLayout;
return getContext().getString(defaultResource);
}
分号离隔左中右,然后逗号离隔,w表明比重,A表明绝对值,C表明居中,具体的的逻辑看代码怎么解析这些字符串
<!-- Nav bar button default ordering/layout -->
<string name="config_navBarLayout" translatable="false">left[.5W],back[1WC];home;recent[1WC],right[.5W]</string>
<string name="config_navBarLayoutQuickstep" translatable="false">back[1.7WC];home;contextual[1.7WC]</string>
<string name="config_navBarLayoutHandle" translatable="false">back[70AC];home_handle;ime_switcher[70AC]</string>
left和right,最终是被替换成space 和 menu_ime了
View createView(String buttonSpec, ViewGroup parent, LayoutInflater inflater) {
View v = null;
String button = extractButton(buttonSpec);
if (LEFT.equals(button)) {
button = extractButton(NAVSPACE);
} else if (RIGHT.equals(button)) {
button = extractButton(MENU_IME_ROTATE);
}
>navigation_layout.xml
里面是2个horizontal的线性布局,显现在底部的导航栏
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="@dimen/rounded_corner_content_padding"
android:layout_marginEnd="@dimen/rounded_corner_content_padding"
android:paddingStart="@dimen/nav_content_padding"
android:paddingEnd="@dimen/nav_content_padding"
android:clipChildren="false"
android:clipToPadding="false"
android:id="@+id/horizontal">
<com.android.systemui.navigationbar.buttons.NearestTouchFrame
android:id="@+id/nav_buttons"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
systemui:isVertical="false">
<LinearLayout
android:id="@+id/ends_group"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:clipToPadding="false"
android:clipChildren="false" />
<LinearLayout
android:id="@+id/center_group"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:orientation="horizontal"
android:clipToPadding="false"
android:clipChildren="false" />
</com.android.systemui.navigationbar.buttons.NearestTouchFrame>
</FrameLayout>
>navigation_layout_vertical.xml
里面是两个vertical的线性布局,显现在两边的导航栏,横屏的时候
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="@dimen/rounded_corner_content_padding"
android:layout_marginBottom="@dimen/rounded_corner_content_padding"
android:paddingTop="@dimen/nav_content_padding"
android:paddingBottom="@dimen/nav_content_padding"
android:id="@+id/vertical">
<com.android.systemui.navigationbar.buttons.NearestTouchFrame
android:id="@+id/nav_buttons"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
systemui:isVertical="true">
<com.android.systemui.navigationbar.buttons.ReverseLinearLayout
android:id="@+id/ends_group"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:clipToPadding="false"
android:clipChildren="false" />
<com.android.systemui.navigationbar.buttons.ReverseLinearLayout
android:id="@+id/center_group"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:clipToPadding="false"
android:clipChildren="false" />
</com.android.systemui.navigationbar.buttons.NearestTouchFrame>
</FrameLayout>
3.NavigationModeController.java
>获取mode
public int addListener(ModeChangedListener listener) {
mListeners.add(listener);
return getCurrentInteractionMode(mCurrentUserContext);
}
private int getCurrentInteractionMode(Context context) {
int mode = context.getResources().getInteger(
com.android.internal.R.integer.config_navBarInteractionMode);
return mode;
}
>mode
model有如下三种,能够看到能够有3个按钮,2个按钮,以及1个按钮
<!-- Controls the navigation bar interaction mode:
0: 3 button mode (back, home, overview buttons)
1: 2 button mode (back, home buttons + swipe up for overview)
2: gestures only for back, home and overview -->
<integer name="config_navBarInteractionMode">0</integer>
4.总结
这里简略整理了下导航栏的创立进程。
- 是由NavigationBarController实例化NavigationBar
- NavigationBar里又经过注解获取要用到的View,然后在onInit办法里经过WindowManager把View增加到窗口上
- 显现几个按钮是装备里决定的,具体看mode的阐明
- 具体的按钮的增加逻辑都是在NavigationBarView,NavigationBarinflaterView里,至于按钮的显现巨细啥的能够看getDefaultLayout里的装备string