1.简介
- 平板下用的是taskbar,在launcher3里,固定显示在底部的导航栏
- 手机模式用的是NavitionBar,在SystemUi下
- 需求,平板设备,不要taskbar,横屏的话只要3个导航键固定显示在右侧
- 源码里判断是否是平板模式,也就是isLargeScreen 都是看屏幕最小宽度是否大于600dp
2.TaskbarManager.java
2.1.recreateTaskbar
- isTaskbarEnabled为false的话就不会加载taskbar,这里可以改为false
- 三键导航模式下改为false,手势模式下还是用默认的即可。
public void recreateTaskbar() {
DeviceProfile dp = mUserUnlocked ?
LauncherAppState.getIDP(mContext).getDeviceProfile(mContext) : null;
destroyExistingTaskbar();
//是否开启taskbar
boolean isTaskbarEnabled = dp != null && isTaskbarPresent(dp);//补充1
if (!isTaskbarEnabled) {
SystemUiProxy.INSTANCE.get(mContext)
.notifyTaskbarStatus(/* visible */ false, /* stashed */ false);
return;
}
if (mTaskbarActivityContext == null) {
mTaskbarActivityContext = new TaskbarActivityContext(mContext, dp, mNavButtonController,
mUnfoldProgressProvider);
} else {
mTaskbarActivityContext.updateDeviceProfile(dp, mNavMode);
}
mTaskbarActivityContext.init(mSharedState);
if (mActivity != null) {
mTaskbarActivityContext.setUIController(
createTaskbarUIControllerForActivity(mActivity));
}
}
>1.isTaskbarPresent
- FLAG_HIDE_NAVBAR_WINDOW 是个系统属性,测试用的,就不看了
- isTaskbarPresent 这个就是判断设备的最小宽度是否大于600dp
private boolean isTaskbarPresent(DeviceProfile deviceProfile) {
return FLAG_HIDE_NAVBAR_WINDOW || deviceProfile.isTaskbarPresent;
}
3.NavigationBarController
3.1.createNavigationBar
navigationbar的创建方法
void createNavigationBar(Display display, Bundle savedState, RegisterStatusBarResult result) {
if (display == null) {
return;
}
final int displayId = display.getDisplayId();
final boolean isOnDefaultDisplay = displayId == mDisplayTracker.getDefaultDisplayId();
//参考补充1
if (isOnDefaultDisplay && initializeTaskbarIfNecessary()) {
//默认屏幕,如果加载的taskbar,则返回
return;
}
//走到这里就要加载navBar了
final IWindowManager wms = WindowManagerGlobal.getWindowManagerService();
try {//参考补充2
if (!wms.hasNavigationBar(displayId)) {
return;
}
} catch (RemoteException e) {
return;
}
final Context context = isOnDefaultDisplay
? mContext
: mContext.createDisplayContext(display);
NavigationBarComponent component = mNavigationBarComponentFactory.create(
context, savedState);
NavigationBar navBar = component.getNavigationBar();
//参考4.2
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);
}
});
}
>1.initializeTaskbarIfNecessary
- taskbarEnabled,我们需要修改成false
private boolean initializeTaskbarIfNecessary() {
//大屏的话用taskbar
boolean taskbarEnabled = mIsLargeScreen || mFeatureFlags.isEnabled(
Flags.HIDE_NAVBAR_WINDOW);
if (taskbarEnabled) {
final int displayId = mContext.getDisplayId();
mNavBarHelper.setTogglingNavbarTaskbar(mNavigationBars.contains(displayId));
//如果有navigationBar的话,移除
removeNavigationBar(displayId);
//初始化taskbar
mTaskbarDelegate.init(displayId);
mNavBarHelper.setTogglingNavbarTaskbar(false);
} else {//销毁taskbar
mTaskbarDelegate.destroy();
}
return taskbarEnabled;
}
>2.DisplayPolicy.java
构造方法里
if (mDisplayContent.isDefaultDisplay) {
mHasStatusBar = true;
//是否有导航栏,默认配置,平板没有,手机有,这个需要修改
mHasNavigationBar = mContext.getResources().getBoolean(R.bool.config_showNavigationBar);
//测试用,可以临时修改
String navBarOverride = SystemProperties.get("qemu.hw.mainkeys");
if ("1".equals(navBarOverride)) {
mHasNavigationBar = false;
} else if ("0".equals(navBarOverride)) {
mHasNavigationBar = true;
}
} else {
mHasStatusBar = false;//非默认屏幕,为false
mHasNavigationBar = mDisplayContent.supportsSystemDecorations();
}
3.2.onDisplayReady
public void onDisplayReady(int displayId) {
Display display = mDisplayManager.getDisplay(displayId);
mIsLargeScreen = isLargeScreen(mContext);
//参考2.1
createNavigationBar(display, null /* savedState */, null /* result */);
}
3.3.onConfigChanged
public void onConfigChanged(Configuration newConfig) {
boolean isOldConfigTablet = mIsTablet;
mIsTablet = isTablet2(mContext);
boolean largeScreenChanged = mIsTablet != isOldConfigTablet;
//手机平板模式切换,并且是平板模式,那么不用处理
if (largeScreenChanged && updateNavbarForTaskbar()) {
return;
}
//参考补充1,检查3种配置是否变化,是的话就需要重新创建
if (mConfigChanges.applyNewConfig(mContext.getResources())) {
for (int i = 0; i < mNavigationBars.size(); i++) {
recreateNavigationBar(mNavigationBars.keyAt(i));
}
} else {//补充1里的3种配置未发生变化,那么走这里
for (int i = 0; i < mNavigationBars.size(); i++) {
//参考4.5
mNavigationBars.valueAt(i).onConfigurationChanged(newConfig);
}
}
}
>1.mConfigChanges
private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges(
ActivityInfo.CONFIG_FONT_SCALE | ActivityInfo.CONFIG_SCREEN_LAYOUT
| ActivityInfo.CONFIG_UI_MODE);
4.NavigationBar.java
注解生成的
public class NavigationBar extends ViewController<NavigationBarView> implements Callbacks {
4.1.构造方法
参数参考小节5
NavigationBar(
NavigationBarView navigationBarView,//参考5.2,注解,加入下边的容器里
NavigationBarFrame navigationBarFrame,//这个是最外层容器,参考4.2通过wm添加
@Nullable Bundle savedState,
@DisplayId Context context,
//...
mNavBarMode = mNavigationModeController.addListener(mModeChangedListener);//补充1
}
>1.mModeChangedListener
private final ModeChangedListener mModeChangedListener = new ModeChangedListener() {
@Override
public void onNavigationModeChanged(int mode) {
//导航模式变化,需要更新布局参数,这里是新加的
//参考4.2.2,我们修改后的参数和导航模式有关的。
if(mNavBarMode != mode)
mWindowManager.updateViewLayout(mFrame, getBarLayoutParams(mContext.getResources().getConfiguration().windowConfiguration
.getRotation()));
mNavBarMode = mode;
if (!QuickStepContract.isGesturalMode(mode)) {
if (getBarTransitions() != null) {
getBarTransitions().setBackgroundOverrideAlpha(1f);
}
}
updateScreenPinningGestures();
if (!canShowSecondaryHandle()) {
resetSecondaryHandle();
}
setNavBarMode(mode);
mView.setShouldShowSwipeUpUi(mOverviewProxyService.shouldShowSwipeUpUI());
}
};
4.2.onInit
public void onInit() {
mView.setBarTransitions(mNavigationBarTransitions);
mView.setTouchHandler(mTouchHandler);
setNavBarMode(mNavBarMode);
mEdgeBackGestureHandler.setStateChangeCallback(mView::updateStates);
mNavigationBarTransitions.addListener(this::onBarTransition);
mView.updateRotationButton();
mView.setVisibility(
mStatusBarKeyguardViewManager.isNavBarVisible() ? View.VISIBLE : View.INVISIBLE);
//容器通过wm添加,布局参数参考补充1
mWindowManager.addView(mFrame,
getBarLayoutParams(mContext.getResources().getConfiguration().windowConfiguration
.getRotation()));//布局参数参考补充1
mDisplayId = mContext.getDisplayId();
mIsOnDefaultDisplay = mDisplayId == mDisplayTracker.getDefaultDisplayId();
parseCurrentSysuiState();
mCommandQueue.addCallback(this);
mHomeButtonLongPressDurationMs = Optional.of(mDeviceConfigProxy.getLong(
DeviceConfig.NAMESPACE_SYSTEMUI,
HOME_BUTTON_LONG_PRESS_DURATION_MS,
/* defaultValue = */ 0
)).filter(duration -> duration != 0);
//
mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
mDeviceConfigProxy.addOnPropertiesChangedListener(
DeviceConfig.NAMESPACE_SYSTEMUI, mHandler::post, mOnPropertiesChangedListener);
//..
// Respect the latest disabled-flags.
mCommandQueue.recomputeDisableFlags(mDisplayId, false);
mNotificationShadeDepthController.addListener(mDepthListener);
mTaskStackChangeListeners.registerTaskStackListener(mTaskStackListener);
}
>1.getBarLayoutParams
private WindowManager.LayoutParams getBarLayoutParams(int rotation) {
//先获取当前方向的布局参数
WindowManager.LayoutParams lp = getBarLayoutParamsForRotation(rotation);
//再获取4个方向的布局参数
lp.paramsForRotation = new WindowManager.LayoutParams[4];
for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) {
lp.paramsForRotation[rot] = getBarLayoutParamsForRotation(rot);
}
return lp;
}
>2.getBarLayoutParamsForRotation
- 我们要固定显示在右侧,所以这里需要修改。使用90度的那个配置即可。
- 这里是容器NavigationBarFrame的布局参数
- 需要监听导航模式的改变,更新这个参数
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;
//navBar是否可以显示在两侧
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);//补充3
}
/** 增加这样的配置,三键导航的话走else,
boolean fixedRight = android.os.SystemProperties.getBoolean("persist.sys.navbar.right", false);
if(fixedRight && !QuickStepContract.isGesturalMode(mNavBarMode)){
navBarCanMove = true;
} */
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://竖屏,显示在底部,高度固定
//最终都指向了补充4里的navigation_bar_height
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://横屏,右侧
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,
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.gravity = gravity;
if (insetsHeight != -1) {
lp.providedInsets = new InsetsFrameProvider[] {
new InsetsFrameProvider(ITYPE_NAVIGATION_BAR, Insets.of(0, 0, 0, insetsHeight))
};
} else {
lp.providedInsets = new InsetsFrameProvider[] {
new InsetsFrameProvider(ITYPE_NAVIGATION_BAR)
};
}
lp.token = new Binder();
lp.accessibilityTitle = userContext.getString(R.string.nav_bar);
lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC
| WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
lp.windowAnimations = 0;
lp.setTitle("NavigationBar" + userContext.getDisplayId());
lp.setFitInsetsTypes(0 /* types */);
lp.setTrustedOverlay();
return lp;
}
>3.config_navBarCanMove
- 控制导航栏在横屏的时候是否可以移动到两侧,只适用于非正方形的设备
- 默认是true,大于600dp的是false,手势模式下也是false
<bool name="config_navBarCanMove">true</bool>
>4.navigation_bar_height
frameworks/base/core/res/res/values/dimens.xml
<!-- Height of the bottom navigation / system bar. -->
<dimen name="navigation_bar_height">48dp</dimen>
<!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height -->
<dimen name="navigation_bar_height_landscape">48dp</dimen>
<!-- Width of the navigation bar when it is placed vertically on the screen -->
<dimen name="navigation_bar_width">48dp</dimen>
4.3.
空
4.4.导航模式变化后的日志
- 为了确认三键导航和手势导航之间切换都有哪些回调
>1.log1
代码参考3.3》4.5
at com.android.systemui.navigationbar.NavigationBar.getBarLayoutParams(NavigationBar.java:1630)
at com.android.systemui.navigationbar.NavigationBar.repositionNavigationBar(NavigationBar.java:1249)
at com.android.systemui.navigationbar.NavigationBar.onConfigurationChanged(NavigationBar.java:875)
at com.android.systemui.navigationbar.NavigationBarController.onConfigChanged(NavigationBarController.java:157)
at com.android.systemui.statusbar.phone.ConfigurationControllerImpl.onConfigurationChanged(ConfigurationControllerImpl.kt:70)
at com.android.systemui.SystemUIApplication.onConfigurationChanged(SystemUIApplication.java:359)
>2.log2
- 参考4.6.1》4.6.2
at com.android.systemui.navigationbar.NavigationBar.getBarLayoutParams(NavigationBar.java:1630)
at com.android.systemui.navigationbar.NavigationBar.-$$Nest$mgetBarLayoutParams(Unknown Source:0)
at com.android.systemui.navigationbar.NavigationBar$11.onNavigationModeChanged(NavigationBar.java:1915)
at com.android.systemui.navigationbar.NavigationModeController.updateCurrentInteractionMode(NavigationModeController.java:144)
at com.android.systemui.navigationbar.NavigationModeController$3.onThemeChanged(NavigationModeController.java:124)
at com.android.systemui.statusbar.phone.ConfigurationControllerImpl.onConfigurationChanged(ConfigurationControllerImpl.kt:129)
at com.android.systemui.SystemUIApplication.onConfigurationChanged(SystemUIApplication.java:359)
>log3
- 4.6.1注册的广播,参考4.6.3,最终同样走的4.6.2
at com.android.systemui.navigationbar.NavigationBar.getBarLayoutParams(NavigationBar.java:1630)
at com.android.systemui.navigationbar.NavigationBar.-$$Nest$mgetBarLayoutParams(Unknown Source:0)
at com.android.systemui.navigationbar.NavigationBar$11.onNavigationModeChanged(NavigationBar.java:1915)
at com.android.systemui.navigationbar.NavigationModeController.updateCurrentInteractionMode(NavigationModeController.java:144)
at com.android.systemui.navigationbar.NavigationModeController$2.onReceive(NavigationModeController.java:93)
4.5.onConfigurationChanged
- 配置发生变化后进行的操作
public void onConfigurationChanged(Configuration newConfig) {
final int rotation = newConfig.windowConfiguration.getRotation();
final Locale locale = mContext.getResources().getConfiguration().locale;
//布局方向是从右往左还是从左往右
final int ld = TextUtils.getLayoutDirectionFromLocale(locale);
//区域或者布局方向变化,刷新
if (!locale.equals(mLocale) || ld != mLayoutDirection) {
mLocale = locale;
mLayoutDirection = ld;
refreshLayout(ld);
}
repositionNavigationBar(rotation);
if (canShowSecondaryHandle()) {
if (rotation != mCurrentRotation) {
mCurrentRotation = rotation;
orientSecondaryHomeHandle();
}
}
}
>1.repositionNavigationBar
private void repositionNavigationBar(int rotation) {
if (mView == null || !mView.isAttachedToWindow()) return;
prepareNavigationBarView();
//获取新的布局参数设置
mWindowManager.updateViewLayout(mFrame, getBarLayoutParams(rotation));
}
4.6.NavigationModeController.java
- 切换导航模式,我们就是enable不同的RRO包,也就是overlay包发生了变化,会走对应的广播
- 同时修改后系统配置也发生了变化,会走对应的监听,所以补充2的方法会走2次。
>1.构造方法
//注册广播监听overlay的改变,这里就监听android包
IntentFilter overlayFilter = new IntentFilter(ACTION_OVERLAY_CHANGED);
overlayFilter.addDataScheme("package");
overlayFilter.addDataSchemeSpecificPart("android", PatternMatcher.PATTERN_LITERAL);
//补充3
mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, overlayFilter, null, null);
//监听配置的改变
configurationController.addCallback(new ConfigurationController.ConfigurationListener() {
@Override
public void onThemeChanged() {
updateCurrentInteractionMode(true /* notify */);//补充2
}
});
updateCurrentInteractionMode(false /* notify */);//补充2
}
>2.updateCurrentInteractionMode
public void updateCurrentInteractionMode(boolean notify) {
mCurrentUserContext = getCurrentUserContext();
int mode = getCurrentInteractionMode(mCurrentUserContext);
mUiBgExecutor.execute(() ->//更新settings的值
Settings.Secure.putString(mCurrentUserContext.getContentResolver(),
Secure.NAVIGATION_MODE, String.valueOf(mode)));
if (notify) {
for (int i = 0; i < mListeners.size(); i++) {
//监听是4.1的构造方法里注册的,回调参考4.1.1
mListeners.get(i).onNavigationModeChanged(mode);
}
}
}
>3.mReceiver
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
//补充2
updateCurrentInteractionMode(true /* notify */);
}
};
5.NavigationBarModule
public interface NavigationBarModule {
5.1.provideNavigationBarFrame
static NavigationBarFrame provideNavigationBarFrame(@DisplayId LayoutInflater layoutInflater) {
return (NavigationBarFrame) layoutInflater.inflate(R.layout.navigation_bar_window, null);
}
>1.navigation_bar_window.xml
就是个自定义的帧布局,没有啥特殊的
<com.android.systemui.navigationbar.NavigationBarFrame
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>
5.2.provideNavigationBarview
@Provides
@NavigationBarScope
static NavigationBarView provideNavigationBarview(
@DisplayId LayoutInflater layoutInflater, NavigationBarFrame frame) {
//可以看到,加载到5.1的容器frame里了
View barView = layoutInflater.inflate(R.layout.navigation_bar, frame);
return barView.findViewById(R.id.navigation_bar_view);//补充1
}
>1.navigation_bar.xml
<com.android.systemui.navigationbar.NavigationBarView
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>
5.3.navBar布局结构
- 最外层的布局参考5.1.1,布局参数参考4.2.2,容器是NavigationBarFrame
- 容器NavigationBarFrame里添加的布局是5.2.1
- NavigationBarInflaterView里会动态添加两套导航容器,横向的和竖向的,根据方向显示不同的布局。
6.NavigationBarView.java
6.1.onMeasure
- mIsVertical构造方法里默认设置为false
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int w = MeasureSpec.getSize(widthMeasureSpec);
int h = MeasureSpec.getSize(heightMeasureSpec);
//非手势模式的竖屏
final boolean newVertical = w > 0 && h > w
&& !isGesturalMode(mNavBarMode);
if (newVertical != mIsVertical) {
mIsVertical = newVertical;
reorient();
notifyVerticalChangedListener(newVertical);
}
if (isGesturalMode(mNavBarMode)) {
//更新导航背景范围
int height = mIsVertical
? getResources().getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_height_landscape)
: getResources().getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_height);
int frameHeight = getResources().getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_frame_height);
mBarTransitions.setBackgroundFrame(new Rect(0, frameHeight - height, w, h));
} else {
mBarTransitions.setBackgroundFrame(null);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
>1.updateCurrentView
private void updateCurrentView() {
resetViews();
mCurrentView = mIsVertical ? mVertical : mHorizontal;
mCurrentView.setVisibility(View.VISIBLE);
mNavigationInflaterView.setVertical(mIsVertical);
mNavigationInflaterView.updateButtonDispatchersCurrentView();
updateLayoutTransitionsEnabled();
updateCurrentRotation();
}
>2.resetViews
private void resetViews() {
mHorizontal.setVisibility(View.GONE);
mVertical.setVisibility(View.GONE);
}
7.NavigationBarInflaterView
public final class NavigationBarInflaterView extends FrameLayout {
7.1.构造方法
public NavigationBarInflaterView(Context context, AttributeSet attrs) {
super(context, attrs);
createInflaters();//补充1
mOverviewProxyService = Dependency.get(OverviewProxyService.class);
mNavBarMode = Dependency.get(NavigationModeController.class).addListener(this);
}
>1.createInflaters
void createInflaters() {
//默认的布局加载器
mLayoutInflater = LayoutInflater.from(mContext);
Configuration landscape = new Configuration();
landscape.setTo(mContext.getResources().getConfiguration());
//默认使用的配置,设置为横屏
landscape.orientation = Configuration.ORIENTATION_LANDSCAPE;
//横向布局加载器,用的就是这个横屏配置
mLandscapeInflater = LayoutInflater.from(mContext.createConfigurationContext(landscape));
}
7.2.onFinishInflate
protected void onFinishInflate() {
super.onFinishInflate();
inflateChildren();//补充1
clearViews();//补充2
//这个是加载按钮,参考7.4
inflateLayout(getDefaultLayout());//布局参考7.3
}
>1.inflateChildren
- 加载了横向和竖向两套布局,区别就是里边线性布局的方向
private void inflateChildren() {
removeAllViews();
mHorizontal = (FrameLayout) mLayoutInflater.inflate(R.layout.navigation_layout,//补充3
this /* root */, false /* attachToRoot */);
addView(mHorizontal);
mVertical = (FrameLayout) mLayoutInflater.inflate(R.layout.navigation_layout_vertical,//补充4
this /* root */, false /* attachToRoot */);
addView(mVertical);
updateAlternativeOrder();
}
>2.clearViews
清除旧数据
private void clearViews() {
if (mButtonDispatchers != null) {
for (int i = 0; i < mButtonDispatchers.size(); i++) {
mButtonDispatchers.valueAt(i).clear();
}
}
clearAllChildren(mHorizontal.findViewById(
com.android.internal.R.id.input_method_nav_buttons));
}
>3.navigation_layout.xml
- 竖屏用的布局,里边的线性布局容器是horizontal的
<FrameLayout
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>
>4.navigation_layout_verticalxml
- 横屏用的布局,里边的线性布局容器是vertical的
<FrameLayout
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>
7.3.getDefaultLayout
protected String getDefaultLayout() {
final int defaultResource = QuickStepContract.isGesturalMode(mNavBarMode)
? R.string.config_navBarLayoutHandle//手势导航用这个
//非三键导航并且launcher里的组件RecentActivity可用
: mOverviewProxyService.shouldShowSwipeUpUI()
? R.string.config_navBarLayoutQuickstep
: R.string.config_navBarLayout;//三键导航用这个
return getContext().getString(defaultResource);
}
>1.资源文件
<!--三键导航用这个-->
<string name="config_navBarLayout" translatable="false">left[.5W],back[1WC];home;recent[1WC],right[.5W]</string>
<!--非三键导航,这个应该用不到,也就2键导航可以用,我们默认没有这个模式-->
<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>
>2.布局分析
- 以"left[.5W],back[1WC];home;recent[1WC],right[.5W]"为例
- 参考7.2.3或者7.2.4布局,参考7.5,left替换成了sapce,right替换成了menu
- ends_group在下层,里边添加了比重为0.5的space,比重为1的back,比重为1的space,比重为1的recent,以及比重为0.5的menu容器
- center_group在上层,就添加了一个home,居中显示
7.4.inflateLayout
- 看下三键导航的字符串,参考上边的7.3.1
protected void inflateLayout(String newLayout) {
mCurrentLayout = newLayout;
if (newLayout == null) {
newLayout = getDefaultLayout();
}
String[] sets = newLayout.split(GRAVITY_SEPARATOR, 3);//冒号
if (sets.length != 3) {
newLayout = getDefaultLayout();
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, mHorizontal.findViewById(R.id.ends_group),
false /* landscape */, true /* start */);
inflateButtons(start, mVertical.findViewById(R.id.ends_group),
true /* landscape */, true /* start */);
inflateButtons(center, mHorizontal.findViewById(R.id.center_group),
false /* landscape */, false /* start */);
inflateButtons(center, mVertical.findViewById(R.id.center_group),
true /* landscape */, false /* start */);
//补充5,
addGravitySpacer(mHorizontal.findViewById(R.id.ends_group));
addGravitySpacer(mVertical.findViewById(R.id.ends_group));
inflateButtons(end, mHorizontal.findViewById(R.id.ends_group),
false /* landscape */, false /* start */);
inflateButtons(end, mVertical.findViewById(R.id.ends_group),
true /* landscape */, false /* start */);
updateButtonDispatchersCurrentView();
}
>1.inflateButtons
- 横向的导航栏,landscape是false
- 竖向的导航栏,landscape是true
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);
}
}
>2.inflateButton
protected View inflateButton(String buttonSpec, ViewGroup parent, boolean landscape,
boolean start) {
//根据方向使用不同的布局加载器
LayoutInflater inflater = landscape ? mLandscapeInflater : mLayoutInflater;
//参考7.5
View v = createView(buttonSpec, parent, inflater);
if (v == null) return null;
//补充3,调节大小
v = applySize(v, buttonSpec, landscape, start);
parent.addView(v);
addToDispatchers(v);
View lastView = landscape ? mLastLandscape : mLastPortrait;
View accessibilityView = v;
if (v instanceof ReverseRelativeLayout) {
accessibilityView = ((ReverseRelativeLayout) v).getChildAt(0);
}
if (lastView != null) {
accessibilityView.setAccessibilityTraversalAfter(lastView.getId());
}
if (landscape) {
mLastLandscape = accessibilityView;
} else {
mLastPortrait = accessibilityView;
}
return v;
}
>3.applySize
private View applySize(View v, String buttonSpec, boolean landscape, boolean start) {
//获取尺寸设置
String sizeStr = extractSize(buttonSpec);//补充4
if (sizeStr == null) return v;
//包含W或者A字符的
if (sizeStr.contains(WEIGHT_SUFFIX) || sizeStr.contains(ABSOLUTE_SUFFIX)) {
//
ReverseRelativeLayout frame = new ReverseRelativeLayout(mContext);
LayoutParams childParams = new LayoutParams(v.getLayoutParams());
// Compute gravity to apply
int gravity = (landscape) ? (start ? Gravity.TOP : Gravity.BOTTOM)
: (start ? Gravity.START : Gravity.END);
if (sizeStr.endsWith(WEIGHT_CENTERED_SUFFIX)) {//"WC"结尾的
gravity = Gravity.CENTER;
} else if (sizeStr.endsWith(ABSOLUTE_VERTICAL_CENTERED_SUFFIX)) {"C"结尾的
gravity = Gravity.CENTER_VERTICAL;
}
// Set default gravity, flipped if needed in reversed layouts (270 RTL and 90 LTR)
frame.setDefaultGravity(gravity);
frame.setGravity(gravity); // Apply gravity to root
frame.addView(v, childParams);
if (sizeStr.contains(WEIGHT_SUFFIX)) {//包含"W"
//解析比重值
float weight = Float.parseFloat(
sizeStr.substring(0, sizeStr.indexOf(WEIGHT_SUFFIX)));
frame.setLayoutParams(new LinearLayout.LayoutParams(0, MATCH_PARENT, weight));
} else {//不包含"W"的,认为是绝对值,单位是dp
int width = (int) convertDpToPx(mContext,
Float.parseFloat(sizeStr.substring(0, sizeStr.indexOf(ABSOLUTE_SUFFIX))));
frame.setLayoutParams(new LinearLayout.LayoutParams(width, MATCH_PARENT));
}
// Ensure ripples can be drawn outside bounds
frame.setClipChildren(false);
frame.setClipToPadding(false);
return frame;
}
//走到这里,传递的是个缩放比列,参考7.5里的布局,默认是固定的宽度,这里进行缩放
float size = Float.parseFloat(sizeStr);
ViewGroup.LayoutParams params = v.getLayoutParams();
params.width = (int) (params.width * size);
return v;
}
>4.extractSize
- 看是否有中括号包含的内容 ,参考7.3.1,比如 back[1WC]返回1WC
public static String extractSize(String buttonSpec) {
if (!buttonSpec.contains(SIZE_MOD_START)) {
return null;
}
final int sizeStart = buttonSpec.indexOf(SIZE_MOD_START);
return buttonSpec.substring(sizeStart + 1, buttonSpec.indexOf(SIZE_MOD_END));
}
>5.addGravitySpacer
添加一个占位符,比重是1的。
private void addGravitySpacer(LinearLayout layout) {
layout.addView(new Space(mContext), new LinearLayout.LayoutParams(0, 0, 1));
}
7.5.createView
View createView(String buttonSpec, ViewGroup parent, LayoutInflater inflater) {
View v = null;
String button = extractButton(buttonSpec);
if (LEFT.equals(button)) {
//left 换成 space
button = extractButton(NAVSPACE);
} else if (RIGHT.equals(button)) {
//right 换成 menu_ime
button = extractButton(MENU_IME_ROTATE);
}
//根据不同的字符串,加载不同的布局
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_ROTATE.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 (CONTEXTUAL.equals(button)) {
v = inflater.inflate(R.layout.contextual, parent, false);
} else if (HOME_HANDLE.equals(button)) {
v = inflater.inflate(R.layout.home_handle, parent, false);
} else if (IME_SWITCHER.equals(button)) {
v = inflater.inflate(R.layout.ime_switcher, parent, false);
} else if (button.startsWith(KEY)) {//以key开头的字符串
//自定义的,类似这种, key(3:http://xxx.xxx.xx/xxx.jpg) 或者 key(11:com.test.test/111)
String uri = extractImage(button);//补充2
int code = extractKeycode(button);//补充3
v = inflater.inflate(R.layout.custom_key, parent, false);
((KeyButtonView) v).setCode(code);
if (uri != null) {//图片地址
if (uri.contains(":")) {//这种是个uri地址
((KeyButtonView) v).loadAsync(Icon.createWithContentUri(uri));
} else if (uri.contains("/")) {//这种是个包名加图片资源id
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;
}
>1.extractButton
- left[.5W] 和 back[1WC],返回的是left和back
- home这种直接使用
public static String extractButton(String buttonSpec) {
if (!buttonSpec.contains(SIZE_MOD_START)) {
//不是以中括号[开头的,直接使用
return buttonSpec;
}
//只取中括号之前的内容
return buttonSpec.substring(0, buttonSpec.indexOf(SIZE_MOD_START));
}
>2.extractImage
- 比如 key(3:urlxxxx) 返回urlxxxx
public static String extractImage(String buttonSpec) {
if (!buttonSpec.contains(KEY_IMAGE_DELIM)) {
//不包含冒号的,返回空
return null;
}
final int start = buttonSpec.indexOf(KEY_IMAGE_DELIM);
//冒号和右括号之间的
String subStr = buttonSpec.substring(start + 1, buttonSpec.indexOf(KEY_CODE_END));
return subStr;
}
>3.extractKeycode
- 比如 key(3:urlxxxx) 返回3
public static int extractKeycode(String buttonSpec) {
if (!buttonSpec.contains(KEY_CODE_START)) {
//不包含左括号的,返回1
return 1;
}
final int start = buttonSpec.indexOf(KEY_CODE_START);
//左括号和冒号之间的
String subStr = buttonSpec.substring(start + 1, buttonSpec.indexOf(KEY_IMAGE_DELIM));
return Integer.parseInt(subStr);
}
>4.home.xml
- KeyButtonView是自定义的ImageView,主要处理了下触摸事件,布局里的keyCode属性的处理
<com.android.systemui.statusbar.policy.KeyButtonView
android:id="@+id/home"
android:layout_width="@dimen/navigation_key_width"
android:layout_height="match_parent"
android:layout_weight="0"
systemui:keyCode="3"
android:scaleType="center"
android:contentDescription="@string/accessibility_home"
android:paddingStart="@dimen/navigation_key_padding"
android:paddingEnd="@dimen/navigation_key_padding"
/>
>5.back.xml
- keyCode为4
<com.android.systemui.statusbar.policy.KeyButtonView
android:id="@+id/back"
android:layout_width="@dimen/navigation_key_width"
android:layout_height="match_parent"
android:layout_weight="0"
systemui:keyCode="4"
android:scaleType="center"
android:contentDescription="@string/accessibility_back"
android:paddingStart="@dimen/navigation_key_padding"
android:paddingEnd="@dimen/navigation_key_padding"
/>
>6.recent_apps.xml
<com.android.systemui.statusbar.policy.KeyButtonView
android:id="@+id/recent_apps"
android:layout_width="@dimen/navigation_key_width"
android:layout_height="match_parent"
android:layout_weight="0"
android:scaleType="center"
android:contentDescription="@string/accessibility_recent"
android:paddingStart="@dimen/navigation_key_padding"
android:paddingEnd="@dimen/navigation_key_padding"
/>
>7.nav_key_space
<Space
android:layout_width="@dimen/navigation_side_padding"
android:layout_height="match_parent"
android:layout_weight="0"
>
</Space>