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

news2024/11/16 7:48:41

该系列文章总纲链接:专题分纲目录 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2120236.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

闯关leetcode——9. Palindrome Number

大纲 题目地址内容 解题代码地址 题目 地址 https://leetcode.com/problems/palindrome-number/description/ 内容 Given an integer x, return true if x is a palindrome, and false otherwise. Example 1: Input: x 121 Output: true Explanation: 121 reads as 121 f…

Windows 10/11 Raspberry Pi PICO (RP2040) VScode开发环境搭建

Windows 10/11 Raspberry Pi PICO VScode开发环境搭建 概述下载树莓派官方开发环境配置工具安装开发环境 概述 下载树莓派官方开发环境配置工具 pico-setup-windows 点击"Download the lastest release" 下载文件为&#xff1a;《pico-setup-windows-x64-standalo…

Gz会员卡检索不到充值记录的处理方法

会员卡预收款查询报表&#xff08;9508&#xff09;中输入会员卡号后检索不到该卡的充值记录&#xff0c;在MemberCard表中检索到该会员卡&#xff0c;将PriorityID值改为0即可检索。 下图为检索后的结果。

ElasticSearch7.8下载、安装教程

文章目录 前言一、Windows安装ElasticSearch7.8二、Elasticsearch-head安装三、Kibana安装 前言 本文章演示elasticSearch7.8、elasticSearch-head(插件模式)、kibana的Windows下载、安装教程。安装前&#xff0c;请检查电脑是否已经安装好node环境和JDK环境。 一、Windows安装…

【Kubernetes】K8s 的鉴权管理(二):基于属性 / 节点 / Webhook 的访问控制

K8s 的鉴权管理&#xff08;二&#xff09;&#xff1a;基于属性 / 节点 / Webhook 的访问控制 1.基于属性的访问控制&#xff08;ABAC 鉴权&#xff09;2.基于节点的访问控制&#xff08;node 鉴权&#xff09;2.1 读取操作2.2 写入操作 3.基于 Webhook 的访问控制3.1 基于 We…

tekton pipeline workspaces

tekton pipeline workspace是一种为执行中的管道及其任务提供可用的共享卷的方法。 在pipeline中定义worksapce作为共享卷传递给相关的task。在tekton中定义workspace的用途有以下几点: 存储输入和/或输出在task之间共享数据secret认证的挂载点ConfigMap中保存的配置的挂载点…

AI基础 : Adversarial Search II 对抗性搜索

Non-deterministic Transitions AND-OR Search Trees • In deterministic environments在确定性环境中&#xff0c;分支仅由智能体的选择引起。, branching only occurs due to agent’s choice (OR Nodes) • In non-deterministic environments在非确定性环境中&#xff0c…

Linux多线程编程实战:深入探索互斥锁的艺术

&#x1f351;个人主页&#xff1a;Jupiter. &#x1f680; 所属专栏&#xff1a;Linux从入门到进阶 欢迎大家点赞收藏评论&#x1f60a; 目录 &#x1f985;Linux线程互斥&#x1f40f;进程线程间的互斥相关背景概念&#x1f98c;互斥锁mutex*下面是一个&#xff1a;操作共享变…

九、外观模式

外观模式&#xff08;Facade Pattern&#xff09;是一种结构型设计模式&#xff0c;有叫门面模式&#xff0c;它为一个复杂子系统提供一个简单的接口&#xff0c;隐藏系统的复杂性。通过使用外观模式&#xff0c;客户端可以更方便地和复杂的系统进行交互&#xff0c;而无需直接…

在国产芯片上实现YOLOv5/v8图像AI识别-【4.4】RK3588网络摄像头推理后推流到RTSP更多内容见视频

本专栏主要是提供一种国产化图像识别的解决方案&#xff0c;专栏中实现了YOLOv5/v8在国产化芯片上的使用部署&#xff0c;并可以实现网页端实时查看。根据自己的具体需求可以直接产品化部署使用。 B站配套视频&#xff1a;https://www.bilibili.com/video/BV1or421T74f 前言…

机器学习TFIDF的情感分类文章

一.中文分词 当读者使用Python爬取了中文数据集之后&#xff0c;首先需要对数据集进行中文分词处理。由于英文中的词与词之间是采用空格关联的&#xff0c;按照空格可以直接划分词组&#xff0c;所以不需要进行分词处理&#xff0c;而中文汉字之间是紧密相连的&#xff0c;并且…

HTML零基础教程(超详细)

一、什么是HTML HTML&#xff0c;全称超文本标记语言&#xff08;HyperText Markup Language&#xff09;&#xff0c;是一种用于创建网页的标准标记语言。它通过一系列标签来定义网页的结构、内容和格式。HTML文档是由HTML元素构成的文本文件&#xff0c;这些元素包括标题、段…

《Nginx核心技术》第16章:实现Nginx的高可用负载均衡

作者&#xff1a;冰河 星球&#xff1a;http://m6z.cn/6aeFbs 博客&#xff1a;https://binghe.gitcode.host 文章汇总&#xff1a;https://binghe.gitcode.host/md/all/all.html 星球项目地址&#xff1a;https://binghe.gitcode.host/md/zsxq/introduce.html 沉淀&#xff0c…

[数据结构] 开散列法 闭散列法 模拟实现哈希结构(一)

标题&#xff1a;[数据结构] 开散列法 && 闭散列法 模拟实现哈希结构 个人主页&#xff1a;水墨不写bug 目录 一、闭散列法 核心逻辑的解决 i、为什么要设置位置状态&#xff1f;&#xff08;伪删除法的使用&#xff09; ii、哈希函数的设计 接口的实现 1、插入&a…

Linux 常用命令 - tail 【显示文件最后几行内容】

简介 tail 这个命令源自英文单词 “尾巴”&#xff0c;它的主要功能是显示文件的最后几行内容。通过使用 tail&#xff0c;用户可以查看文件的最新添加内容&#xff0c;特别是对于监控日志文件来说非常有用。tail 命令默认显示文件的最后 10 行&#xff0c;但这可以通过参数调…

走进低代码报表开发(一):探秘报表数据源

在前文当中&#xff0c;我们对勤研低代码平台的流程设计功能进行了介绍。接下来&#xff0c;让我们一同深入了解在企业日常运营中另一个极为常见的报表功能。在当今数字化时代&#xff0c;高效的报表生成对于企业的决策至关重要。勤研低代码开发平台能够以卓越的性能和便捷的操…

Git 学习与使用

0 认识⼯作区、暂存区、版本库 ⼯作区&#xff1a;是在电脑上你要写代码或⽂件的⽬录。 暂存区&#xff1a;英⽂叫stage或index。⼀般存放在.git ⽬录下的index⽂件&#xff08;.git/index&#xff09;中&#xff0c;我们 把暂存区有时也叫作索引&#xff08;index&#xff09;…

LAMP环境下项目部署

目录 1、创建一台虚拟机 centos 源的配置 备份源 修改源 重新加载缓存 安装软件 2、关闭防火墙和selinux 查看防火墙状态 关闭防火墙 查看SELinux的状态 临时关闭防火墙 永久关闭SELinux&#xff1a;编辑SELinux的配置文件 配置文件的修改内容 3、检查系统中是否…

NFTScan | 09.02~09.08 NFT 市场热点汇总

欢迎来到由 NFT 基础设施 NFTScan 出品的 NFT 生态热点事件每周汇总。 周期&#xff1a;2024.09.02~ 2024.09.08 NFT Hot News 01/ 数据&#xff1a;NFT 8 月销售额跌破 4 亿美元&#xff0c;创年内新低 9 月 2 日&#xff0c;数据显示&#xff0c;8 月 NFT 的月销售额仅为 …

直播相关01-录制麦克风声音,QT上 .pro 将 linux,mac和windows上配置为三种可以共享, 在.pro文件中 message 的作用

一 QT 上的 .pro 文件 将 linux&#xff0c;mac和windows上配置设置为可以共享 1. 先来看文件夹布局 2. 再来看 QT 中的 .pro文件 .pro 文件的写法 QT core guigreaterThan(QT_MAJOR_VERSION, 4): QT widgetsCONFIG c11# The following define makes your compiler …