Android SystemUI组件(06)导航栏创建分析&虚拟按键

124 篇文章 95 订阅

该系列文章总纲链接:专题分纲目录 Android SystemUI组件


本章关键点总结 & 说明:

说明:本章节持续迭代之前章节的思维导图,主要关注左侧SystemBars分析中导航栏部分即可。

1 导航栏创建之makeStatusBarView

通过上一篇文章的分析,我们知道 addNavigationBar是在addStatusBarWindow之后执行的,addStatusBarWindow代码实现如下:

private void addStatusBarWindow() {
    makeStatusBarView();//创建statusbar视图
    mStatusBarWindowManager = new StatusBarWindowManager(mContext);
    mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
}

addStatusBarWindow中makeStatusBarView也执行了导航栏相关逻辑,相关代码如下:

protected PhoneStatusBarView makeStatusBarView() {
    final Context context = mContext;
	//...
    try {
        //关键点1:导航栏显示与否
        boolean showNav = mWindowManagerService.hasNavigationBar();
		//是否显示导航栏
        if (showNav) {
		    //关键点2:加载导航栏布局
            mNavigationBarView =
                (NavigationBarView) View.inflate(context, R.layout.navigation_bar, null);
            mNavigationBarView.setDisabledFlags(mDisabled);
            mNavigationBarView.setBar(this);
            mNavigationBarView.setOnVerticalChangedListener(
                    new NavigationBarView.OnVerticalChangedListener() {
                @Override
                public void onVerticalChanged(boolean isVertical) {
                    if (mSearchPanelView != null) {
                        mSearchPanelView.setHorizontal(isVertical);
                    }
                    mNotificationPanel.setQsScrimEnabled(!isVertical);
                }
            });
			//设置导航栏触摸事件
            mNavigationBarView.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    checkUserAutohide(v, event);
                    return false;
                }});
        }
    } catch (RemoteException ex) {
        // no window manager? good luck with that
    }
	//...
    startGlyphRasterizeHack();
    return mStatusBarView;
}

这里主要分析两个部分:navigationBar显示与否 和 导航栏Layout相关。

1.1 navigationBar显示与否

导航栏显示与否关键在于方法mWindowManagerService.hasNavigationBar的实现,这里最终是掉用到了PhoneWindowManager中的hasNavigationBar方法。代码如下所示:

public class PhoneWindowManager implements WindowManagerPolicy {
    //...
    public boolean hasNavigationBar() {
        return mHasNavigationBar;
    }
    //...
}

这里变量mHasNavigationBar的赋值操作为:

mHasNavigationBar = res.getBoolean(com.android.internal.R.bool.config_showNavigationBar);

这里可以看到 导航栏的配置主要是在/res/res/values中名为config_showNavigationBar的标识,即为默认的配置,如下所示:

    <!-- Whether a software navigation bar should be shown. NOTE: in the future this may be
         autodetected from the Configuration. -->
    <bool name="config_showNavigationBar">false</bool>

因此,这里可以根据需求修改该配置。

1.2 导航栏Layout相关

加载导航栏布局的语句为:

NavigationBarView = (NavigationBarView) View.inflate(context, R.layout.navigation_bar, null);

从布局文件(res\layout\navigation_bar.xml)中来来看,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"
    >
    <!--横向导航栏-->
    <FrameLayout android:id="@+id/rot0"
        android:layout_height="match_parent"
        android:layout_width="match_parent"
        >

        <LinearLayout
            android:layout_height="match_parent"
            android:layout_width="match_parent"
            android:orientation="horizontal"
            android:clipChildren="false"
            android:clipToPadding="false"
            android:id="@+id/nav_buttons"
            android:animateLayoutChanges="true"
            >

            <!-- navigation controls -->
            <View
                android:layout_width="@dimen/navigation_side_padding"
                android:layout_height="match_parent"
                android:layout_weight="0"
                android:visibility="invisible"
                />
			<!--back按钮-->
            <com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/back"
                android:layout_width="@dimen/navigation_key_width"
                android:layout_height="match_parent"
                android:src="@drawable/ic_sysbar_back"
                systemui:keyCode="4"
                android:layout_weight="0"
                android:scaleType="center"
                android:contentDescription="@string/accessibility_back"
                />
            <View 
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:visibility="invisible"
                />
			<!--home按钮-->
            <com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/home"
                android:layout_width="@dimen/navigation_key_width"
                android:layout_height="match_parent"
                android:src="@drawable/ic_sysbar_home"
                systemui:keyCode="3"
                systemui:keyRepeat="false"
                android:layout_weight="0"
                android:scaleType="center"
                android:contentDescription="@string/accessibility_home"
                />
            <View 
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:visibility="invisible"
                />
			<!--recent按钮-->
            <com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/recent_apps"
                android:layout_width="@dimen/navigation_key_width"
                android:layout_height="match_parent"
                android:src="@drawable/ic_sysbar_recent"
                android:layout_weight="0"
                android:scaleType="center"
                android:contentDescription="@string/accessibility_recent"
                />
            <FrameLayout
                android:layout_width="@dimen/navigation_side_padding"
                android:layout_height="match_parent"
                android:layout_weight="0" >
                <com.android.systemui.statusbar.policy.KeyButtonView
                    android:id="@+id/menu"
                    android:layout_width="@dimen/navigation_extra_key_width"
                    android:layout_height="match_parent"
                    android:contentDescription="@string/accessibility_menu"
                    android:src="@drawable/ic_sysbar_menu"
                    android:visibility="invisible"
                    android:scaleType="centerInside"
                    android:layout_gravity="end"
                    systemui:keyCode="82" />

                <com.android.systemui.statusbar.policy.KeyButtonView
                    android:id="@+id/ime_switcher"
                    android:layout_width="@dimen/navigation_extra_key_width"
                    android:layout_height="match_parent"
                    android:contentDescription="@string/accessibility_ime_switch_button"
                    android:scaleType="centerInside"
                    android:src="@drawable/ic_ime_switcher_default"
                    android:visibility="invisible"
                    android:layout_gravity="end" />
            </FrameLayout>

        </LinearLayout>

        <!-- lights out layout to match exactly -->
        <LinearLayout
            android:layout_height="match_parent"
            android:layout_width="match_parent"
            android:orientation="horizontal"
            android:id="@+id/lights_out"
            android:visibility="gone"
            >
            <ImageView
                android:layout_width="@dimen/navigation_key_width"
                android:layout_height="match_parent"
                android:layout_marginStart="@dimen/navigation_side_padding"
                android:src="@drawable/ic_sysbar_lights_out_dot_small"
                android:scaleType="center"
                android:layout_weight="0"
                />
            <View 
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:visibility="invisible"
                />
            <ImageView
                android:layout_width="@dimen/navigation_key_width"
                android:layout_height="match_parent"
                android:src="@drawable/ic_sysbar_lights_out_dot_large"
                android:scaleType="center"
                android:layout_weight="0"
                />
            <View 
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:visibility="invisible"
                />
            <ImageView
                android:layout_width="@dimen/navigation_key_width"
                android:layout_marginEnd="@dimen/navigation_side_padding"
                android:layout_height="match_parent"
                android:src="@drawable/ic_sysbar_lights_out_dot_small"
                android:scaleType="center"
                android:layout_weight="0"
                />
        </LinearLayout>

        <com.android.systemui.statusbar.policy.DeadZone
            android:id="@+id/deadzone"
            android:layout_height="match_parent"
            android:layout_width="match_parent"
            systemui:minSize="@dimen/navigation_bar_deadzone_size"
            systemui:maxSize="@dimen/navigation_bar_deadzone_size_max"
            systemui:holdTime="@integer/navigation_bar_deadzone_hold"
            systemui:decayTime="@integer/navigation_bar_deadzone_decay"
            systemui:orientation="horizontal"
            android:layout_gravity="top"
            />
    </FrameLayout>
	<!--纵向显示-->
    <FrameLayout android:id="@+id/rot90"
        android:layout_height="match_parent"
        android:layout_width="match_parent"
        android:visibility="gone"
        android:paddingTop="0dp"
        >

        <LinearLayout 
            android:layout_height="match_parent"
            android:layout_width="match_parent"
            android:orientation="vertical"
            android:clipChildren="false"
            android:clipToPadding="false"
            android:id="@+id/nav_buttons"
            android:animateLayoutChanges="true"
            >

            <!-- navigation controls -->
            <FrameLayout
                android:layout_weight="0"
                android:layout_width="match_parent"
                android:layout_height="@dimen/navigation_side_padding" >
                <com.android.systemui.statusbar.policy.KeyButtonView
                    android:id="@+id/ime_switcher"
                    android:layout_width="match_parent"
                    android:layout_height="@dimen/navigation_extra_key_width"
                    android:contentDescription="@string/accessibility_ime_switch_button"
                    android:scaleType="centerInside"
                    android:src="@drawable/ic_ime_switcher_default"
                    android:layout_gravity="top"
                    android:visibility="invisible" />

                <com.android.systemui.statusbar.policy.KeyButtonView
                    android:id="@+id/menu"
                    android:layout_width="match_parent"
                    android:layout_height="40dp"
                    android:contentDescription="@string/accessibility_menu"
                    android:src="@drawable/ic_sysbar_menu_land"
                    android:scaleType="centerInside"
                    android:layout_gravity="top"
                    android:visibility="invisible"
                    systemui:keyCode="82" />
            </FrameLayout>

			<!--recent按钮-->
            <com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/recent_apps"
                android:layout_height="@dimen/navigation_key_width"
                android:layout_width="match_parent"
                android:src="@drawable/ic_sysbar_recent_land"
                android:scaleType="center"
                android:layout_weight="0"
                android:contentDescription="@string/accessibility_recent"
                />
            <View 
                android:layout_height="match_parent"
                android:layout_width="match_parent"
                android:layout_weight="1"
                android:visibility="invisible"
                />
			<!--home按钮-->
            <com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/home"
                android:layout_height="@dimen/navigation_key_width"
                android:layout_width="match_parent"
                android:src="@drawable/ic_sysbar_home_land"
                android:scaleType="center"
                systemui:keyCode="3"
                systemui:keyRepeat="false"
                android:layout_weight="0"
                android:contentDescription="@string/accessibility_home"
                />
            <View 
                android:layout_height="match_parent"
                android:layout_width="match_parent"
                android:layout_weight="1"
                android:visibility="invisible"
                />
			<!--back按钮-->
            <com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/back"
                android:layout_height="@dimen/navigation_key_width"
                android:layout_width="match_parent"
                android:src="@drawable/ic_sysbar_back_land"
                android:scaleType="center"
                systemui:keyCode="4"
                android:layout_weight="0"
                android:contentDescription="@string/accessibility_back"
                />
            <View
                android:layout_height="@dimen/navigation_side_padding"
                android:layout_width="match_parent"
                android:layout_weight="0"
                android:visibility="invisible"
                />
        </LinearLayout>

        <!-- lights out layout to match exactly -->
        <LinearLayout 
            android:layout_height="match_parent"
            android:layout_width="match_parent"
            android:orientation="vertical"
            android:id="@+id/lights_out"
            android:visibility="gone"
            >
            <ImageView
                android:layout_height="@dimen/navigation_key_width"
                android:layout_marginTop="@dimen/navigation_side_padding"
                android:layout_width="match_parent"
                android:src="@drawable/ic_sysbar_lights_out_dot_small"
                android:scaleType="center"
                android:layout_weight="0"
                />
            <View 
                android:layout_height="match_parent"
                android:layout_width="match_parent"
                android:layout_weight="1"
                android:visibility="invisible"
                />
            <ImageView
                android:layout_height="@dimen/navigation_key_width"
                android:layout_width="match_parent"
                android:src="@drawable/ic_sysbar_lights_out_dot_large"
                android:scaleType="center"
                android:layout_weight="0"
                />
            <View 
                android:layout_height="match_parent"
                android:layout_width="match_parent"
                android:layout_weight="1"
                android:visibility="invisible"
                />
            <ImageView
                android:layout_height="@dimen/navigation_key_width"
                android:layout_marginBottom="@dimen/navigation_side_padding"
                android:layout_width="match_parent"
                android:src="@drawable/ic_sysbar_lights_out_dot_small"
                android:scaleType="center"
                android:layout_weight="0"
                />
        </LinearLayout>

        <com.android.systemui.statusbar.policy.DeadZone
            android:id="@+id/deadzone"
            android:layout_height="match_parent"
            android:layout_width="match_parent"
            systemui:minSize="@dimen/navigation_bar_deadzone_size"
            systemui:maxSize="@dimen/navigation_bar_deadzone_size_max"
            systemui:holdTime="@integer/navigation_bar_deadzone_hold"
            systemui:decayTime="@integer/navigation_bar_deadzone_decay"
            systemui:orientation="vertical"
            android:layout_gravity="top"
            />
    </FrameLayout>

</com.android.systemui.statusbar.phone.NavigationBarView>

可以看到,横向和纵向是加载两个不同的FrameLayout配置文件。由于文件过长,这里使用简图来描述,如下:

简单解读说明如下:

  • nav_buttons:4个控件,back,home,recent,menu。
  • lights_out:多数情况不可见,当处于低辨识度模式下,nav_buttons隐藏且lights_out显示,显示为三个不明显的小灰点,降低对用户视线的干扰。
  • search_light:多数情况不可见,当HOME按键被禁后serach功能还可用,此时会变成可见,用于提示用户该功能可使用。
  • deadzone:防止边界误触操作。

导航栏显示以及布局由屏幕的方向来决定,而导航栏有两种不同的显示方式,横向显示和竖向显示,同时 我从mRotatedViews变量 分析:

public class NavigationBarView extends LinearLayout {
	//...
	View[] mRotatedViews = new View[4];
	//...
    //布局加载完成后,会回调onFinishInflate方法
	@Override
	public void onFinishInflate() {
        //屏幕方位0和180方向显示的导航栏为rot0,90和270显示的导航栏为rot90
		mRotatedViews[Surface.ROTATION_0] =
		mRotatedViews[Surface.ROTATION_180] = findViewById(R.id.rot0);
		mRotatedViews[Surface.ROTATION_90] = findViewById(R.id.rot90);
		mRotatedViews[Surface.ROTATION_270] = mRotatedViews[Surface.ROTATION_90];
		mCurrentView = mRotatedViews[Surface.ROTATION_0];
		getImeSwitchButton().setOnClickListener(mImeSwitcherClickListener);
		updateRTLOrder();
	}
}

这里也可以看到,在加载完xml文件后,会根据不同的旋转角度加载不同的layout布局文件。

2 导航栏创建之addNavigationBar 入口分析

addNavigationBar 代码实现如下:

// For small-screen devices (read: phones) that lack hardware navigation buttons
private void addNavigationBar() {
    if (mNavigationBarView == null) return;
    //关键点1
    prepareNavigationBarView();
    //关键点2: getNavigationBarLayoutParams分析
    mWindowManager.addView(mNavigationBarView, getNavigationBarLayoutParams());
}

2.1 prepareNavigationBarView分析

继续分析prepareNavigationBarView,代码实现如下:

private void prepareNavigationBarView() {
    mNavigationBarView.reorient();
    //设置导航栏三个图标的点击事件
    mNavigationBarView.getRecentsButton().setOnClickListener(mRecentsClickListener);
    mNavigationBarView.getRecentsButton().setOnTouchListener(mRecentsPreloadOnTouchListener);
    mNavigationBarView.getRecentsButton().setLongClickable(true);
    mNavigationBarView.getRecentsButton().setOnLongClickListener(mLongPressBackRecentsListener);
    mNavigationBarView.getBackButton().setLongClickable(true);
    mNavigationBarView.getBackButton().setOnLongClickListener(mLongPressBackRecentsListener);
    mNavigationBarView.getHomeButton().setOnTouchListener(mHomeActionListener);
    updateSearchPanel();
}

导航栏布局的明确显示在prepareNavigationBarView中的mNavigationBarView.reorient();来决定,我们查看reorient方法,代码实现如下:

public void reorient() {
	//获取屏幕旋转方向
    final int rot = mDisplay.getRotation();
	
	//隐藏导航栏布局
    for (int i=0; i<4; i++) {
        mRotatedViews[i].setVisibility(View.GONE);
    }
	
	//根据屏幕方向显示导航栏布局
    mCurrentView = mRotatedViews[rot];
    mCurrentView.setVisibility(View.VISIBLE);

    getImeSwitchButton().setOnClickListener(mImeSwitcherClickListener);

    mDeadZone = (DeadZone) mCurrentView.findViewById(R.id.deadzone);

    //初始化导航栏的转换效果,这些效果可能包括动画和过渡。
    mBarTransitions.init(mVertical);
    //根据 mDisabledFlags 设置导航栏的禁用状态
    setDisabledFlags(mDisabledFlags, true /* force */);
    //设置菜单按钮的可见性
    setMenuVisibility(mShowMenu, true /* force */);

    //如果导航栏处于横屏/垂直模式,mDelegateHelper 对象交换 X 和 Y 坐标,以适应横屏布局。
    if (mDelegateHelper != null) {
        mDelegateHelper.setSwapXY(mVertical);
    }
    updateTaskSwitchHelper();

    setNavigationIconHints(mNavigationIconHints, true);
}

2.2 getNavigationBarLayoutParams分析

我们回到PhoneStatusBar的addNavigationBar继续分析最后一个导航栏的LayoutParameters,它决定了导航栏在窗体上的显示位置,getNavigationBarLayoutParams代码实现如下:

private WindowManager.LayoutParams getNavigationBarLayoutParams() {
	/*初始化参数说明如下:
	FLAG_TOUCHABLE_WHEN_WAKING:当手机处于睡眠状态时,如果屏幕被按下,那么该window将第一个收到事件
	FLAG_NOT_FOCUSABLE:不获取焦点
	FLAG_NOT_TOUCH_MODAL:即使该window在可获得焦点情况下,仍然把该window之外的任何event发送到该window之后的其他window
	FLAG_WATCH_OUTSIDE_TOUCH:不接受事件,转发到其他window
	FLAG_SPLIT_TOUCH:当window设置这个flag,window会接收来自window边界之外发送给其他window的点击事件,支持多点触控.当这个flag没有设置的时候,第一下点击则决定了哪个window会接收整个点击事件,直到手指拿开.当设置了这个flag,这每一个点击事件(不一定是第一个)都决定了那个window来接收剩下的点击事件,直到手指拿开.点击事件会被分开传递给多个window.
	*/
    WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
            LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
            WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
                0
                | 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,
            PixelFormat.TRANSLUCENT);
    // this will allow the navbar to run in an overlay on devices that support this
    if (ActivityManager.isHighEndGfx()) {
		//硬件加速参数
        lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
    }

    lp.setTitle("NavigationBar");//窗口名称
    lp.windowAnimations = 0;//不设置窗口动画
    return lp;
}

上面的LayoutParames决定了导航栏在窗体的大小和显示的位置效果,当然这也是受父窗口影响的,当我们有变更导航栏的显示需求时就可以同各国修正LayoutParames参数来解决。

3 导航栏虚拟按键工作原理

虚拟按键是用来替代物理按键的,而这也是导航栏最重要的工作,输入子系统(IMS)中有一个关键的方法:injectInputEvent,即直接模拟物理按键上报输入事件,它是虚拟按键的实现基础。导航栏中的KeyButtonView就是该接口使用者之一,KeyButtonView中最重要的字段是mCode,用于指示其生成的按键事件的键值。

导航栏中有4个KeyButtonView,其中back、home、menu分别产生 KEY_BACK、KEY_HOME 、KEY_MENU 三种按键事件,recent不产生按键事件。

接下来关注KeyButtonView的两个部分:从触屏事件转换到按键事件、键盘事件发送。

3.1 从触屏事件转换到按键事件

这里从KeyButtonView的onTouchEvent()方法(该方法是触屏的回调方法)开始分析,代码实现如下:

public boolean onTouchEvent(MotionEvent ev) {
    final int action = ev.getAction();
    int x, y;

    switch (action) {
		//触屏事件-按下
        case MotionEvent.ACTION_DOWN:
            //Log.d("KeyButtonView", "press");
            mDownTime = SystemClock.uptimeMillis();
            setPressed(true);
			//如果mCode被设置有值,则发送按键事件KeyEvent.ACTION_DOWN
            if (mCode != 0) {
                sendEvent(KeyEvent.ACTION_DOWN, 0, mDownTime);
            } else {
                // Provide the same haptic feedback that the system offers for virtual keys.
                performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
            }
            if (mSupportsLongpress) {
                removeCallbacks(mCheckLongPress);
                postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout());
            }
            break;
		//触屏事件-移动
        case MotionEvent.ACTION_MOVE:
            x = (int)ev.getX();
            y = (int)ev.getY();
            setPressed(x >= -mTouchSlop
                    && x < getWidth() + mTouchSlop
                    && y >= -mTouchSlop
                    && y < getHeight() + mTouchSlop);
            break;
		//触屏事件-取消
        case MotionEvent.ACTION_CANCEL:
            setPressed(false);
			//如果mCode被设置有值,则发送按键事件KeyEvent.ACTION_UP,带标记KeyEvent.FLAG_CANCELED
            if (mCode != 0) {
                sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED);
            }
            if (mSupportsLongpress) {
                removeCallbacks(mCheckLongPress);
            }
            break;
		//触屏事件-抬起
        case MotionEvent.ACTION_UP:
            final boolean doIt = isPressed();
            setPressed(false);
			//如果mCode被设置有值,则发送按键事件KeyEvent.ACTION_UP
            if (mCode != 0) {
                if (doIt) {
                    sendEvent(KeyEvent.ACTION_UP, 0);
                    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
                    playSoundEffect(SoundEffectConstants.CLICK);
                } else {
                    sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED);
                }
            } else {
			//如果没设置,则此时触发OnClickListener
			//导航栏会采用这种方式来处理事件
                // no key code, just a regular ImageView
                if (doIt) {
                    performClick();
                }
            }
            if (mSupportsLongpress) {
                removeCallbacks(mCheckLongPress);
            }
            break;
    }

    return true;
}

整个过程就是 触摸事件转换成按键事件的一个过程。

3.2 键盘事件发送

接下来专注分析KeyButtonView的sendEvent方法,代码实现如下:

public void sendEvent(int action, int flags) {
    sendEvent(action, flags, SystemClock.uptimeMillis());
}

继续分析,代码实现如下:

void sendEvent(int action, int flags, long when) {
	//计算重复次数repeatCount
    final int repeatCount = (flags & KeyEvent.FLAG_LONG_PRESS) != 0 ? 1 : 0;
	//根据参数构建KeyEvent事件
    final KeyEvent ev = new KeyEvent(mDownTime, when, action, mCode, repeatCount,
            0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
            flags | KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
            InputDevice.SOURCE_KEYBOARD);
	//将KeyEvent事件加入到InputDispatcher的派发队列。
	//说明:INJECT_INPUT_EVENT_MODE_ASYNC表示加入派发队列后立刻返回,不阻塞,也不等待事件派发的成功与否。
    InputManager.getInstance().injectInputEvent(ev,
            InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
}

接下来最关键的就是执行InputManager的injectInputEvent方法了。这一部分属于输入子系统了,感兴趣的伙伴可查看这篇文章的后半部分:

Android Framework 输入子系统 (10)Input命令解读_input swipe

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

图王大胜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值