Android10 SystemUI系统手势导航

1、Android10 源码编译相关问题

2、Android10 系统进程Zygote启动

3、Android10 系统进程SystemServer

4、Android10 launcher启动流程

5、Android10 系统发送开机广播时机

6、Android10 AppComponentFactory源码梳理

7、Android10 InputManagerService事件输入输出

8、Android10 InputManagerService本地实现

9、Android10 SystemUI系统手势导航


        从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的配置:

    <application
        android:name=".SystemUIApplication"
        ... ...
        tools:replace="android:appComponentFactory"
        android:appComponentFactory=".SystemUIAppComponentFactory">

这里用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<Class<?>, 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.
     * <p>This method must only be called from the main thread.</p>
     */
    //开启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> T getComponent(Class<T> interfaceType) {
        return (T) mComponents.get(interfaceType);
    }

    public SystemUI[] getServices() {
        return mServices;
    }

    void setContextAvailableCallback(ContextAvailableCallback callback) {
        mContextAvailableCallback = callback;
    }

    interface ContextAvailableCallback {
        void onContextAvailable(Context context);
    }
}

整体逻辑还是比较清晰的,startServicesIfNeeded()主要做了三件事:

  1. 获取事先定义在xml中的SystemUI组件服务类;
  2. 遍历服务,通过反射机制创建SystemUI组件服务;
  3. 调用SystemUI的start()方法启动服务;

接下来看下定义的组件服务frameworks/base/packages/SystemUI/res/values/config.xml:

    <!-- SystemUI Services: The classes of the stuff to start. -->
    <string-array name="config_systemUIServiceComponents" translatable="false">
        <item>com.android.systemui.util.NotificationChannels</item>
        <item>com.android.systemui.statusbar.CommandQueue$CommandQueueStart</item>
        <item>com.android.systemui.keyguard.KeyguardViewMediator</item>
        <item>com.android.systemui.recents.Recents</item>
        <item>com.android.systemui.volume.VolumeUI</item>
        <item>com.android.systemui.stackdivider.Divider</item>
        <item>com.android.systemui.SystemBars</item>
        <item>com.android.systemui.usb.StorageNotification</item>
        <item>com.android.systemui.power.PowerUI</item>
        <item>com.android.systemui.media.RingtonePlayer</item>
        <item>com.android.systemui.keyboard.KeyboardUI</item>
        <item>com.android.systemui.pip.PipUI</item>
        <item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item>
        <item>@string/config_systemUIVendorServiceComponent</item>
        <item>com.android.systemui.util.leak.GarbageMonitor$Service</item>
        <item>com.android.systemui.LatencyTester</item>
        <item>com.android.systemui.globalactions.GlobalActionsComponent</item>
        <item>com.android.systemui.ScreenDecorations</item>
        <item>com.android.systemui.biometrics.BiometricDialogImpl</item>
        <item>com.android.systemui.SliceBroadcastRelayHandler</item>
        <item>com.android.systemui.SizeCompatModeActivityController</item>
        <item>com.android.systemui.statusbar.notification.InstantAppNotifier</item>
        <item>com.android.systemui.theme.ThemeOverlayController</item>
    </string-array>

这里主要来看下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中定义的启动的类:

    <!-- Component to be used as the status bar service.  Must implement the IStatusBar
     interface.  This name is in the ComponentName flattened format (package/class)  -->
    <string name="config_statusBarComponent" translatable="false">com.android.systemui.statusbar.phone.StatusBar</string>

所以这里就直接进入到了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:

    <!-- Height of the status bar -->
    <dimen name="status_bar_height">@dimen/status_bar_height_portrait</dimen>
    <!-- Height of the status bar in portrait -->
    <dimen name="status_bar_height_portrait">24dp</dimen>
    <!-- Height of the status bar in landscape -->
    <dimen name="status_bar_height_landscape">@dimen/status_bar_height_portrait</dimen>

当系统不需要状态栏的时候,这里直接设置为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了,这里来看下这个布局:

<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>

关于导航栏的布局,定义在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);
        }
    }

这里的逻辑可以分为两部分:

  1. 注册事件输入通道,可以接收系统的输入事件;
  2. 创建一个支持手势的mEdgePanel view,并添加到window中;

当有输入事件时,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类型有区别,导航栏这一部分,新增了手势导航,手势部分只是多加输入事件的监听以及处理。

  • 4
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值