从Android9开始,android系统就添加了系统导航手势,对于现在高版本的手机,有些系统默认就是开启导航手势的,有些还是沿用以前导航栏,但是也是可以在设置中开启导航手势,导航栏会自动消失,这篇文章主要讲解的是系统导航收拾是如何实现的。
对于系统导航手势的实现,android10还是在SystemUI中实现的,在以后的版本中可能就是在Launcher3中,SystemUI是一个系统应用,在SystemServer中启动,先来看下是如何启动的:
private static void startSystemUi(Context context, WindowManagerService windowManager) {
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.android.systemui",
"com.android.systemui.SystemUIService"));
intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);
//Slog.d(TAG, "Starting service: " + intent);
context.startServiceAsUser(intent, UserHandle.SYSTEM);
windowManager.onSystemUiStarted();
}
通过开启SystemUIService服务来拉起SystemUI,这里就直接来看下SystemUIService:
public class SystemUIService extends Service {
@Override
public void onCreate() {
super.onCreate();
((SystemUIApplication) getApplication()).startServicesIfNeeded();
... ...
}
}
没做啥,就是继续调用SystemUIApplication.startServicesIfNeeded(),在说这个方法前,可以先了解下Android10 AppComponentFactory源码梳理,这里先来看下Manifest中对application的配置:
这里用SystemUIAppComponentFactory替换掉了android系统中默认的appComponentFactory,先来看看SystemUIAppComponentFactory:
public class SystemUIAppComponentFactory extends CoreComponentFactory {
@Inject
public ContextComponentHelper mComponentHelper;
public SystemUIAppComponentFactory() {
super();
}
@Override
public Application instantiateApplication(ClassLoader cl, String className)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
Application app = super.instantiateApplication(cl, className);
if (app instanceof SystemUIApplication) {
((SystemUIApplication) app).setContextAvailableCallback(
context -> {
SystemUIFactory.createFromConfig(context);
SystemUIFactory.getInstance().getRootComponent().inject(
SystemUIAppComponentFactory.this);
}
);
}
return app;
}
}
根据上面的配置,这里的if语句为true,会执行下面的回调语句,这里主要说下这个回调语句是做什么的,在SystemUI应用中,使用了Dagger对很多的实例进行初始化,这里就是对Dagger注入对象进行初始化,这里对appComponentFactory的作用就先到这了,接着上面的开启的服务,就进入到SystemUIApplication.startServicesIfNeeded():
public class SystemUIApplication extends Application implements SysUiServiceProvider {
public static final String TAG = "SystemUIService";
private static final boolean DEBUG = false;
/**
* Hold a reference on the stuff we start.
*/
//预先定义的SystemUI组件服务
private SystemUI[] mServices;
private boolean mServicesStarted;
private boolean mBootCompleted;
//SystemUI组件集合
private final Map, Object> mComponents = new HashMap<>();
private ContextAvailableCallback mContextAvailableCallback;
public SystemUIApplication() {
super();
Log.v(TAG, "SystemUIApplication constructed.");
}
@Override
public void onCreate() {
super.onCreate();
... ...
//在SystemUIAppComponentFactory中设置的回调立马就在这调用了,感觉是多此一举
//在android 11中就没有通过这个回调了,而是直接在这里初始化的Dagger
mContextAvailableCallback.onContextAvailable(this);
... ...
}
/**
* Makes sure that all the SystemUI services are running. If they are already running, this is a
* no-op. This is needed to conditinally start all the services, as we only need to have it in
* the main process.
* This method must only be called from the main thread.
*/
//开启SystemUI组件服务,组件服务预先定义在config_systemUIServiceComponents
public void startServicesIfNeeded() {
String[] names = getResources().getStringArray(R.array.config_systemUIServiceComponents);
startServicesIfNeeded(/* metricsPrefix= */ "StartServices", names);
}
private void startServicesIfNeeded(String metricsPrefix, String[] services) {
//服务已经开始就直接返回
if (mServicesStarted) {
return;
}
mServices = new SystemUI[services.length];
... ...
//遍历定义的SystemUI组件服务,通过反射创建服务
final int N = services.length;
for (int i = 0; i < N; i++) {
String clsName = services[i];
if (DEBUG) Log.d(TAG, "loading: " + clsName);
log.traceBegin(metricsPrefix + clsName);
long ti = System.currentTimeMillis();
Class cls;
try {
cls = Class.forName(clsName);
Object o = cls.newInstance();
if (o instanceof SystemUI.Injector) {
o = ((SystemUI.Injector) o).apply(this);
}
mServices[i] = (SystemUI) o;
} catch(ClassNotFoundException ex){
throw new RuntimeException(ex);
} catch (IllegalAccessException ex) {
throw new RuntimeException(ex);
} catch (InstantiationException ex) {
throw new RuntimeException(ex);
}
mServices[i].mContext = this;
//将全局存放SystemUI组件的集合赋值给服务,服务创建好后会存放到集合中
mServices[i].mComponents = mComponents;
if (DEBUG) Log.d(TAG, "running: " + mServices[i]);
mServices[i].start();
log.traceEnd();
// Warn if initialization of component takes too long
ti = System.currentTimeMillis() - ti;
if (ti > 1000) {
Log.w(TAG, "Initialization of " + cls.getName() + " took " + ti + " ms");
}
if (mBootCompleted) {
mServices[i].onBootCompleted();
}
}
... ...
mServicesStarted = true;
}
@SuppressWarnings("unchecked")
public T getComponent(Class interfaceType) {
return (T) mComponents.get(interfaceType);
}
public SystemUI[] getServices() {
return mServices;
}
void setContextAvailableCallback(ContextAvailableCallback callback) {
mContextAvailableCallback = callback;
}
interface ContextAvailableCallback {
void onContextAvailable(Context context);
}
}
整体逻辑还是比较清晰的,startServicesIfNeeded()主要做了三件事:
接下来看下定义的组件服务frameworks/base/packages/SystemUI/res/values/config.xml:
- com.android.systemui.util.NotificationChannels
- com.android.systemui.statusbar.CommandQueue$CommandQueueStart
- com.android.systemui.keyguard.KeyguardViewMediator
- com.android.systemui.recents.Recents
- com.android.systemui.volume.VolumeUI
- com.android.systemui.stackdivider.Divider
- com.android.systemui.SystemBars
- com.android.systemui.usb.StorageNotification
- com.android.systemui.power.PowerUI
- com.android.systemui.media.RingtonePlayer
- com.android.systemui.keyboard.KeyboardUI
- com.android.systemui.pip.PipUI
- com.android.systemui.shortcut.ShortcutKeyDispatcher
- @string/config_systemUIVendorServiceComponent
- com.android.systemui.util.leak.GarbageMonitor$Service
- com.android.systemui.LatencyTester
- com.android.systemui.globalactions.GlobalActionsComponent
- com.android.systemui.ScreenDecorations
- com.android.systemui.biometrics.BiometricDialogImpl
- com.android.systemui.SliceBroadcastRelayHandler
- com.android.systemui.SizeCompatModeActivityController
- com.android.systemui.statusbar.notification.InstantAppNotifier
- com.android.systemui.theme.ThemeOverlayController
这里主要来看下SystemBars,这里主要定义的是系统状态栏以及导航栏:
public class SystemBars extends SystemUI {
private static final String TAG = "SystemBars";
private static final boolean DEBUG = false;
private static final int WAIT_FOR_BARS_TO_DIE = 500;
// in-process fallback implementation, per the product config
private SystemUI mStatusBar;
@Override
public void start() {
if (DEBUG) Log.d(TAG, "start");
createStatusBarFromConfig();
}
private void createStatusBarFromConfig() {
if (DEBUG) Log.d(TAG, "createStatusBarFromConfig");
final String clsName = mContext.getString(R.string.config_statusBarComponent);
if (clsName == null || clsName.length() == 0) {
throw andLog("No status bar component configured", null);
}
Class> cls = null;
try {
cls = mContext.getClassLoader().loadClass(clsName);
} catch (Throwable t) {
throw andLog("Error loading status bar component: " + clsName, t);
}
try {
mStatusBar = (SystemUI) cls.newInstance();
} catch (Throwable t) {
throw andLog("Error creating status bar component: " + clsName, t);
}
mStatusBar.mContext = mContext;
mStatusBar.mComponents = mComponents;
if (mStatusBar instanceof StatusBar) {
SystemUIFactory.getInstance().getRootComponent()
.getStatusBarInjector()
.createStatusBar((StatusBar) mStatusBar);
}
mStatusBar.start();
if (DEBUG) Log.d(TAG, "started " + mStatusBar.getClass().getSimpleName());
}
}
SystemBars继承自SystemUI,所有定义的组件服务都是继承自SystemUI,一路看下来,和之前的逻辑类似,去xml中获取配置的类文件,反射获取,调用是start()方法启动,先来看下xml中定义的启动的类:
com.android.systemui.statusbar.phone.StatusBar
所以这里就直接进入到了StatusBar.start()方法:
@Override
public void start() {
... ...
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
... ...
//在创建SystemUI组件的时候,有传进来一个集合,这里就是将创建的组建存放到集合中
putComponent(StatusBar.class, this);
... ...
//这里就是通过WindowManager去创建系统状态栏和导航栏
createAndAddWindows(result);
... ...
}
继承自SystemUI的都传进了同一个集合,putComponent()将所有创建的组件都存放到同一个集合中了,这样组建之间都是可以互相获取的,这里主要来看下createAndAddWindows():
public void createAndAddWindows(@Nullable RegisterStatusBarResult result) {
//这里会去创建状态栏view已经导航view
makeStatusBarView(result);
//Dependency这里面的类是通过dagger初始化的,上面有说到但是没有跟下去看,感兴趣的可以自己去看下
mStatusBarWindowController = Dependency.get(StatusBarWindowController.class);
//这里将状态栏view添加到window
mStatusBarWindowController.add(mStatusBarWindow, getStatusBarHeight());
}
public int getStatusBarHeight() {
if (mNaturalBarHeight < 0) {
final Resources res = mContext.getResources();
mNaturalBarHeight =
res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
}
return mNaturalBarHeight;
}
先来看下这个状态栏高度的定义,这个是定义在系统资源里面,并没有定义在当前SystemUI应用里面,系统资源统一定义在包名是android的应用里面frameworks/base/core/res/res:
@dimen/status_bar_height_portrait
24dp
@dimen/status_bar_height_portrait
当系统不需要状态栏的时候,这里直接设置为0就ok了,说完状态栏的高度,再来看看StatusBarWindowController.add()是如何添加状态栏的:
public void add(ViewGroup statusBarView, int barHeight) {
// Now that the status bar window encompasses the sliding panel and its
// translucent backdrop, the entire thing is made TRANSLUCENT and is
// hardware-accelerated.
mLp = new LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
barHeight,
LayoutParams.TYPE_STATUS_BAR,
LayoutParams.FLAG_NOT_FOCUSABLE
| LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
| LayoutParams.FLAG_SPLIT_TOUCH
| LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
| LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
PixelFormat.TRANSLUCENT);
mLp.token = new Binder();
mLp.gravity = Gravity.TOP;
mLp.softInputMode = LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
mLp.setTitle("StatusBar");
mLp.packageName = mContext.getPackageName();
mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
mStatusBarView = statusBarView;
mBarHeight = barHeight;
mWindowManager.addView(mStatusBarView, mLp);
mLpChanged.copyFrom(mLp);
onThemeChanged();
}
通过WindowManager的addView()方法将状态栏view添加进去,回到上面的createAndAddWindows()方法,在来看下里面调用的makeStatusBarView(),这里就会去创建状态栏view:
// ================================================================================
// Constructing the view
// ================================================================================
protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {
... ...
//这里就是去创建mStatusBarWindow,类型是StatusBarWindowView,继承自FrameLayout
inflateStatusBarWindow(context);
... ...
FragmentHostManager.get(mStatusBarWindow)
.addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> {
CollapsedStatusBarFragment statusBarFragment =
(CollapsedStatusBarFragment) fragment;
statusBarFragment.initNotificationIconArea(mNotificationIconAreaController);
PhoneStatusBarView oldStatusBarView = mStatusBarView;
mStatusBarView = (PhoneStatusBarView) fragment.getView();
... ...
}).getFragmentManager()
.beginTransaction()
.replace(R.id.status_bar_container, new CollapsedStatusBarFragment(),
CollapsedStatusBarFragment.TAG)
.commit();
... ...
//这里就是去创建导航栏
createNavigationBar(result);
... ...
}
protected void inflateStatusBarWindow(Context context) {
mStatusBarWindow = (StatusBarWindowView) mInjectionInflater.injectable(
LayoutInflater.from(context)).inflate(R.layout.super_status_bar, null);
}
先是通过super_status_bar.xml创建了状态栏的根布局,然后使用Fragment (CollapsedStatusBarFragment)替换布局里面的status_bar_container,对状态栏感兴趣的可以沿着这里继续往下看,我们这里主要是来看下导航栏相关的,也就是createNavigationBar():
mNavigationBarController = Dependency.get(NavigationBarController.class);
protected void createNavigationBar(@Nullable RegisterStatusBarResult result) {
mNavigationBarController.createNavigationBars(true /* includeDefaultDisplay */, result);
}
只是简单调用NavigationBarController.createNavigationBars(),这个方法内部又是去调用了NavigationBarFragment.create():
public static View create(Context context, FragmentListener listener) {
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
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.token = new Binder();
lp.setTitle("NavigationBar" + context.getDisplayId());
lp.accessibilityTitle = context.getString(R.string.nav_bar);
lp.windowAnimations = 0;
lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
View navigationBarView = LayoutInflater.from(context).inflate(
R.layout.navigation_bar_window, null);
if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + navigationBarView);
if (navigationBarView == null) return null;
final NavigationBarFragment fragment = FragmentHostManager.get(navigationBarView)
.create(NavigationBarFragment.class);
navigationBarView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
final FragmentHostManager fragmentHost = FragmentHostManager.get(v);
fragmentHost.getFragmentManager().beginTransaction()
.replace(R.id.navigation_bar_frame, fragment, TAG)
.commit();
fragmentHost.addTagListener(TAG, listener);
}
@Override
public void onViewDetachedFromWindow(View v) {
FragmentHostManager.removeAndDestroy(v);
}
});
context.getSystemService(WindowManager.class).addView(navigationBarView, lp);
return navigationBarView;
}
将创建的navigationBarView添加进window,这样导航栏就可以显示出来,注意上面的fragment,使用NavigationBarFragment创建的view替换了navigaiton_bar_frame,所有这里直接NavigationBarFragment所创建的view:
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.navigation_bar, container, false);
}
实际导航栏的布局就是这里的navigation_bar.xml了,这里来看下这个布局:
关于导航栏的布局,定义在NavigationBarInflaterView中,在NavigationBarInflaterView布局填充完会调用到onFinishInflate(),在这里会去添加横屏、竖屏的真正的显示的导航栏view,这里就不跟进去看了,这里来看看NavigationBarView的构造方法:
//默认导航模式,屏幕底部三个按钮显示
private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
public NavigationBarView(Context context, AttributeSet attrs) {
super(context, attrs);
... ...
mNavBarMode = Dependency.get(NavigationModeController.class).addListener(this);
//当前是否是手势导航
boolean isGesturalMode = isGesturalMode(mNavBarMode);
... ...
// 这个类会与launcher3进行通信,一些逻辑的判断需要借助这个类,在以后的版本中也是借助这个类
// 来处理返回事件等
mOverviewProxyService = Dependency.get(OverviewProxyService.class);
... ...
//手势导航就是通过下面这个类来控制
mEdgeBackGestureHandler = new EdgeBackGestureHandler(context, mOverviewProxyService);
... ...
}
@Override
public void onNavigationModeChanged(int mode) {
... ...
mBarTransitions.onNavigationModeChanged(mNavBarMode);
mEdgeBackGestureHandler.onNavigationModeChanged(mNavBarMode, curUserCtx);
... ...
}
当mNavBarMode的值是NAV_BAR_MODE_GESTURAL,这个时候就是启用手势导航,屏幕底部的三个按钮的导航就会消失,这个时候EdgeBackGestureHandler就该其作用了,导航模式改变的时候会调用到onNavigationModeChanged(),这里假设启用系统手势,那这里传进去的就是NAV_BAR_MODE_GESTURAL,这里就来看下EdgeBackGestureHandler.onNavigationModeChanged():
public void onNavigationModeChanged(int mode, Context currentUserContext) {
mIsGesturalModeEnabled = QuickStepContract.isGesturalMode(mode);
//更新手势模式
updateIsEnabled();
updateCurrentUserResources(currentUserContext.getResources());
}
private void updateIsEnabled() {
boolean isEnabled = mIsAttached && mIsGesturalModeEnabled;
//手势模式已经可用,直接返回
if (isEnabled == mIsEnabled) {
return;
}
mIsEnabled = isEnabled;
disposeInputChannel();
//判断之前的手势view是否还在,还在的话就先清理掉
if (mEdgePanel != null) {
mWm.removeView(mEdgePanel);
mEdgePanel = null;
mRegionSamplingHelper.stop();
mRegionSamplingHelper = null;
}
//这里假设开启手势,mIsEnable=true
if (!mIsEnabled) {
... ...
} else {
... ...
try {
... ...
// 监听页面设置手势排除的区域范围,当开启手势后,有可能会与当前页面定义的手势
// 产生冲突,这是时候就可以通过View的setSystemGestureExclusionRects()
// 来设置页面将系统手势排除在外
WindowManagerGlobal.getWindowManagerService()
.registerSystemGestureExclusionListener(
mGestureExclusionListener, mDisplayId);
} catch (RemoteException e) {
Log.e(TAG, "Failed to register window manager callbacks", e);
}
// 注册输入事件监听,最终会调用到当前类的onInputEvent()方法
mInputMonitor = InputManager.getInstance().monitorGestureInput(
"edge-swipe", mDisplayId);
mInputEventReceiver = new SysUiInputEventReceiver(
mInputMonitor.getInputChannel(), Looper.getMainLooper());
// 创建手势监听的View,并添加到window
mEdgePanel = new NavigationBarEdgePanel(mContext);
mEdgePanelLp = new WindowManager.LayoutParams(
mContext.getResources()
.getDimensionPixelSize(R.dimen.navigation_edge_panel_width),
mContext.getResources()
.getDimensionPixelSize(R.dimen.navigation_edge_panel_height),
WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
PixelFormat.TRANSLUCENT);
mEdgePanelLp.privateFlags |=
WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
mEdgePanelLp.setTitle(TAG + mDisplayId);
mEdgePanelLp.accessibilityTitle = mContext.getString(R.string.nav_bar_edge_panel);
mEdgePanelLp.windowAnimations = 0;
mEdgePanel.setLayoutParams(mEdgePanelLp);
mWm.addView(mEdgePanel, mEdgePanelLp);
... ...
}
}
class SysUiInputEventReceiver extends InputEventReceiver {
SysUiInputEventReceiver(InputChannel channel, Looper looper) {
super(channel, looper);
}
public void onInputEvent(InputEvent event) {
EdgeBackGestureHandler.this.onInputEvent(event);
finishInputEvent(event, true);
}
}
这里的逻辑可以分为两部分:
当有输入事件时,EdgeBackGestureHandler的onInputEvent()就会接受到输入事件:
private void onInputEvent(InputEvent ev) {
if (ev instanceof MotionEvent) {
onMotionEvent((MotionEvent) ev);
}
}
private void onMotionEvent(MotionEvent ev) {
int action = ev.getActionMasked();
if (action == MotionEvent.ACTION_DOWN) {
//校验当前点击区域是否允许系统手势
int stateFlags = mOverviewProxyService.getSystemUiStateFlags();
mIsOnLeftEdge = ev.getX() <= mEdgeWidth + mLeftInset;
mInRejectedExclusion = false;
mAllowGesture = !QuickStepContract.isBackGestureDisabled(stateFlags)
&& isWithinTouchRegion((int) ev.getX(), (int) ev.getY());
if (mAllowGesture) {
mEdgePanelLp.gravity = mIsOnLeftEdge
? (Gravity.LEFT | Gravity.TOP)
: (Gravity.RIGHT | Gravity.TOP);
mEdgePanel.setIsLeftPanel(mIsOnLeftEdge);
//将事件传递给手势view
mEdgePanel.handleTouch(ev);
updateEdgePanelPosition(ev.getY());
//刷新window的layout属性
mWm.updateViewLayout(mEdgePanel, mEdgePanelLp);
mRegionSamplingHelper.start(mSamplingRect);
mDownPoint.set(ev.getX(), ev.getY());
mThresholdCrossed = false;
}
} else if (mAllowGesture) {
//允许后续的手势事件,下面的语句只会执行一次
if (!mThresholdCrossed) {
//如果是多指就取消手势事件
if (action == MotionEvent.ACTION_POINTER_DOWN) {
// We do not support multi touch for back gesture
cancelGesture(ev);
return;
} else if (action == MotionEvent.ACTION_MOVE) {
//按下到第一次滑动的时间超过规定时间,取消手势事件
if ((ev.getEventTime() - ev.getDownTime()) > mLongPressTimeout) {
cancelGesture(ev);
return;
}
float dx = Math.abs(ev.getX() - mDownPoint.x);
float dy = Math.abs(ev.getY() - mDownPoint.y);
//竖向滑动距离大于横向滑动距离,取消手势
if (dy > dx && dy > mTouchSlop) {
cancelGesture(ev);
return;
} else if (dx > dy && dx > mTouchSlop) {
mThresholdCrossed = true;
// 捕获输入,这一次输入的后续所有事件都传过来
mInputMonitor.pilferPointers();
}
}
}
// 手势view处理touch事件,mEdgePanel就可以去绘制手势动画了
mEdgePanel.handleTouch(ev);
boolean isUp = action == MotionEvent.ACTION_UP;
if (isUp) {
boolean performAction = mEdgePanel.shouldTriggerBack();
// 手抬起来的时候触发返回事件
if (performAction) {
// Perform back
sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
}
... ...
}
... ...
}
}
关于手势NavigationBarEdgePanel这里就不再去看了,是一个继承自view的自定义view,到这里,系统手势导航的整个流程就走完了。
整一个流程下来,不管是状态栏还是导航栏,其实现方式都是通过window添加view的方式,只是window的type类型有区别,导航栏这一部分,新增了手势导航,手势部分只是多加输入事件的监听以及处理。