首页 > 其他分享 >遥控器Tab键切换时按键音流程

遥控器Tab键切换时按键音流程

时间:2024-07-11 14:54:46浏览次数:10  
标签:java ViewRootImpl 遥控器 Tab 按键 return android event view


用遥控器进行界面的tab切换时,会有音量出现,梳理下tab切换时按键音的逻辑。基于Android U的代码。

从ViewRootImpl的processKeyEvent方法开始,这个方法用于处理按键事件。首先看下该方法的调用堆栈。

at android.view.ViewRootImpl$ViewPostImeInputStage.processKeyEvent(ViewRootImpl.java:6957)
at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:6826)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:6244)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:6301)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:6267)
at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:6432)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:6275)
at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:6489)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:6248)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:6301)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:6267)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:6275)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:6248)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:6301)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:6267)
at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:6465)
at android.view.ViewRootImpl$ImeInputStage.onFinishedInputEvent(ViewRootImpl.java:6676)
at android.view.inputmethod.InputMethodManager$PendingEvent.run(InputMethodManager.java:4278)
at android.view.inputmethod.InputMethodManager.invokeFinishedInputEventCallback(InputMethodManager.java:3715)
at android.view.inputmethod.InputMethodManager.finishedInputEvent(InputMethodManager.java:3706)
at android.view.inputmethod.InputMethodManager.-$$Nest$mfinishedInputEvent(Unknown Source:0)
at android.view.inputmethod.InputMethodManager$ImeInputEventSender.onInputEventFinished(InputMethodManager.java:4255)
at android.view.InputEventSender.dispatchInputEventFinished(InputEventSender.java:154)
at android.os.MessageQueue.nativePollOnce(Native Method)
at android.os.MessageQueue.next(MessageQueue.java:335)
at android.os.Looper.loopOnce(Looper.java:162)
at android.os.Looper.loop(Looper.java:294)
at android.app.ActivityThread.main(ActivityThread.java:8177)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:570)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)

源码分析:

frameworks/base/core/java/android/view/ViewRootImpl.java
private int processKeyEvent(QueuedInputEvent q) {
            final KeyEvent event = (KeyEvent)q.mEvent;

            if (mUnhandledKeyManager.preViewDispatch(event)) {//在event事件被分发给view层级结构之前调用,如果未处理的handler具有焦点并且消费了该事件,则返回true
                return FINISH_HANDLED;//FINISH_HANDLED表示该事件已被处理。
            }

            // Deliver the key to the view hierarchy.
            if (mView.dispatchKeyEvent(event)) {//将key事件传递给mView(Activity的顶层DecordView对象)处理
                return FINISH_HANDLED;//如果mView层级结构中任何一个view处理了该事件,返回FINISH_HANDLED表示该事件已被处理
            }

            if (shouldDropInputEvent(q)) {//是否应该放弃处理该事件
                return FINISH_NOT_HANDLED;//返回FINISH_NOT_HANDLED表示该事件没有被处理
            }

            // This dispatch is for windows that don't have a Window.Callback. Otherwise,
            // the Window.Callback usually will have already called this (see
            // DecorView.superDispatchKeyEvent) leaving this call a no-op.
            if (mUnhandledKeyManager.dispatch(mView, event)) {//是否有未处理的事件
                return FINISH_HANDLED;//表示该事件已经被处理
            }

            int groupNavigationDirection = 0;

            if (event.getAction() == KeyEvent.ACTION_DOWN
                    && event.getKeyCode() == KeyEvent.KEYCODE_TAB) {//tab键处理
                if (KeyEvent.metaStateHasModifiers(event.getMetaState(), KeyEvent.META_CTRL_ON)) {
                    groupNavigationDirection = View.FOCUS_FORWARD;//将焦点移至下一个可选的item
                } else if (KeyEvent.metaStateHasModifiers(event.getMetaState(),
                        KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) {
                    groupNavigationDirection = View.FOCUS_BACKWARD;//将焦点移至上一个可选的item
                }
            }

            // If a modifier is held, try to interpret the key as a shortcut.
            if (event.getAction() == KeyEvent.ACTION_DOWN
                    && !KeyEvent.metaStateHasNoModifiers(event.getMetaState())
                    && event.getRepeatCount() == 0
                    && !KeyEvent.isModifierKey(event.getKeyCode())
                    && groupNavigationDirection == 0) {//如果是快捷键
                if (mView.dispatchKeyShortcutEvent(event)) {//如果mView处理了快捷键
                    return FINISH_HANDLED;//表示该事件已被处理
                }
                if (shouldDropInputEvent(q)) {//该快捷键是否应该被放弃处理
                    return FINISH_NOT_HANDLED;
                }
            }

            // Apply the fallback event policy.
            if (mFallbackEventHandler.dispatchKeyEvent(event)) {//下发给PhoneFallbackEventHandler处理该事件
                return FINISH_HANDLED;
            }
            if (shouldDropInputEvent(q)) {//是否应该放弃处理该事件
                return FINISH_NOT_HANDLED;
            }

            // Handle automatic focus changes.
            //处理焦点变化,这里会进行按键音的处理
            if (event.getAction() == KeyEvent.ACTION_DOWN) {
                if (groupNavigationDirection != 0) {//
                    if (performKeyboardGroupNavigation(groupNavigationDirection)) {
                        return FINISH_HANDLED;
                    }
                } else {
                    if (performFocusNavigation(event)) {//处理焦点变化
                        return FINISH_HANDLED;
                    }
                }
            }
            return FORWARD;//如果到这里事件仍未被处理,返回FORWARD表示该事件应该被传递给下一个事件处理流程
        }

大致流程:

1.在event事件被分发到mView之前,调用mUnhandledKeyManager.preViewDispatch(event)判断是否有未处理按键事件,如果有返回FINISH_HANDLED表示该事件已被处理。


        boolean preViewDispatch(KeyEvent event) {
            mDispatched = false;
            if (mCurrentReceiver == null) {//mCurrentReceiver当前接收器。此值是瞬态的,在预分派和预览阶段之间使用,以确保其他输入阶段不会干扰跟踪。
                mCurrentReceiver = mCapturedKeys.get(event.getKeyCode());
            }
            if (mCurrentReceiver != null) {//如果存在mCurrentReceiver
                View target = mCurrentReceiver.get();
                if (event.getAction() == KeyEvent.ACTION_UP) {
                    mCurrentReceiver = null;
                }
                if (target != null && target.isAttachedToWindow()) {
                    target.onUnhandledKeyEvent(event);
                }
                // consume anyways so that we don't feed uncaptured key events to other views
                return true;//到这里的话,event事件无论如何都会消耗,返回true表示事件已被处理,这样就不会将未捕获的键事件提供给其他视图
            }
            return false;
        }

2.调用mView.dispatchKeyEvent()方法将event事件传递给mView(Activity的顶层DecordView对象)处理,如果view层级结构中任何一个view处理了该事件,返回FINISH_HANDLED表示该事件已被处理。最主要的就是这部分,大部分按键都会被mView.dispatchKeyEvent()处理。

3.判断是否应该放弃处理该事件,如果放弃处理,那么返回FINISH_NOT_HANDLED表示该事件没有被处理。看下哪些场景应该放弃处理该事件

    protected boolean shouldDropInputEvent(QueuedInputEvent q) {
            if (mView == null || !mAdded) {//mView不存在
                Slog.w(mTag, "Dropping event due to root view being removed: " + q.mEvent);
                return true;
            }

            // Find a reason for dropping or canceling the event.
            final String reason;//放弃或取消处理该事件的reason
            if (!mAttachInfo.mHasWindowFocus
                    && !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)
                    && !isAutofillUiShowing()) {
                // This is a non-pointer event and the window doesn't currently have input focus
                // This could be an event that came back from the previous stage
                // but the window has lost focus or stopped in the meantime.
                reason = "no window focus";//window没有焦点
            } else if (mStopped) {
                reason = "window is stopped";//window处于stop状态
            } else if (mIsAmbientMode
                    && !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_BUTTON)) {
                reason = "non-button event in ambient mode";//ambient mode下没有button事件
            } else if (mPausedForTransition && !isBack(q.mEvent)) {
                reason = "paused for transition";//activity处于转换阶段,暂停处理event事件
            } else {//大多会走这里,一般不会放弃处理event事件
                // Most common path: no reason to drop or cancel the event
                return false;
            }

            if (isTerminalInputEvent(q.mEvent)) {//不要删除终端输入事件,但将其标记为已取消
                // Don't drop terminal input events, however mark them as canceled.
                q.mEvent.cancel();
                Slog.w(mTag, "Cancelling event (" + reason + "):" + q.mEvent);
                return false;
            }

            // Drop non-terminal input events.//放弃处理非终端输入事件
            Slog.w(mTag, "Dropping event (" + reason + "):" + q.mEvent);
            return true;
        }

4.如果事件仍未被处理,调用mUnhandledKeyManager.dispatch来判断是否有未处理的事件。如果有,则直接返回FINISH_HANDLED表示该事件已被处理。

5.Tab键和快捷键的处理。

6.如果事件仍未被处理,调用mFallbackEventHandler.dispatchKeyEvent(event)方法继续下发给PhoneFallbackEventHandler处理。

7.调用performFocusNavigation方法处理焦点变化逻辑

frameworks/base/core/java/android/view/ViewRootImpl.java
   private boolean performFocusNavigation(KeyEvent event) {
            int direction = 0;//焦点转换的方向
            switch (event.getKeyCode()) {
                case KeyEvent.KEYCODE_DPAD_LEFT://左移
                    if (event.hasNoModifiers()) {
                        direction = View.FOCUS_LEFT;
                    }
                    break;
                case KeyEvent.KEYCODE_DPAD_RIGHT://右移
                    if (event.hasNoModifiers()) {
                        direction = View.FOCUS_RIGHT;
                    }
                    break;
                case KeyEvent.KEYCODE_DPAD_UP://上移
                    if (event.hasNoModifiers()) {
                        direction = View.FOCUS_UP;
                    }
                    break;
                case KeyEvent.KEYCODE_DPAD_DOWN://下移
                    if (event.hasNoModifiers()) {
                        direction = View.FOCUS_DOWN;
                    }
                    break;
                case KeyEvent.KEYCODE_TAB://tab键
                    if (event.hasNoModifiers()) {
                        direction = View.FOCUS_FORWARD;
                    } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
                        direction = View.FOCUS_BACKWARD;
                    }
                    break;
            }
            if (direction != 0) {
                View focused = mView.findFocus();//找到控件树中当前获取焦点的view
                if (focused != null) {
                    View v = focused.focusSearch(direction);//根据当前获取焦点的view,在direction方向上找到下一个获取焦点的view
                    if (v != null && v != focused) {
                        // do the math the get the interesting rect
                        // of previous focused into the coord system of
                        // newly focused view
                        focused.getFocusedRect(mTempRect);
                        if (mView instanceof ViewGroup) {
                            ((ViewGroup) mView).offsetDescendantRectToMyCoords(
                                    focused, mTempRect);
                            ((ViewGroup) mView).offsetRectIntoDescendantCoords(
                                    v, mTempRect);
                        }
                        if (v.requestFocus(direction, mTempRect)) {//旧焦点的移除,新焦点的建立
                            boolean isFastScrolling = event.getRepeatCount() > 0;
                            playSoundEffect(
                                    SoundEffectConstants.getConstantForFocusDirection(direction,
                                            isFastScrolling));//播放按键音的地方
                            return true;
                        }
                    }

                    // Give the focused view a last chance to handle the dpad key.
                    if (mView.dispatchUnhandledMove(focused, direction)) {
                        return true;
                    }
                } else {
                    if (mView.restoreDefaultFocus()) {
                        return true;
                    }
                }
            }
            return false;
        }

主要流程:首先计算焦点转换的方向,然后在控件树中找到当前获取焦点的view,根据当前获取焦点的view,在direction方向上找到下一个获取焦点的view,最后移除旧焦点,建立新焦点,并播放按键音。

playSoundEffect()方法就是播放按键音的地方

public void playSoundEffect(@SoundEffectConstants.SoundEffect int effectId) {
        if ((mDisplay.getFlags() & Display.FLAG_TOUCH_FEEDBACK_DISABLED) != 0) {
            return;
        }

        checkThread();

        try {
            final AudioManager audioManager = getAudioManager();

            if (mFastScrollSoundEffectsEnabled
                    && SoundEffectConstants.isNavigationRepeat(effectId)) {
                audioManager.playSoundEffect(
                        SoundEffectConstants.nextNavigationRepeatSoundEffectId());
                return;
            }

            switch (effectId) {
                case SoundEffectConstants.CLICK:
                    audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK);
                    return;
                case SoundEffectConstants.NAVIGATION_DOWN:
                case SoundEffectConstants.NAVIGATION_REPEAT_DOWN:
                    audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_DOWN);
                    return;
                case SoundEffectConstants.NAVIGATION_LEFT:
                case SoundEffectConstants.NAVIGATION_REPEAT_LEFT:
                    audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_LEFT);
                    return;
                case SoundEffectConstants.NAVIGATION_RIGHT:
                case SoundEffectConstants.NAVIGATION_REPEAT_RIGHT:
                    audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_RIGHT);
                    return;
                case SoundEffectConstants.NAVIGATION_UP:
                case SoundEffectConstants.NAVIGATION_REPEAT_UP:
                    audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_UP);
                    return;
                default:
                    throw new IllegalArgumentException("unknown effect id " + effectId +
                            " not defined in " + SoundEffectConstants.class.getCanonicalName());
            }
        } catch (IllegalStateException e) {
            // Exception thrown by getAudioManager() when mView is null
            Log.e(mTag, "FATAL EXCEPTION when attempting to play sound effect: " + e);
            e.printStackTrace();
        }
    }

根据不同的direction方向播放不同的音效,最终调用AudioService.playSoundEffect()方法播放音效。传递的effectType参数值表示音效类型,在audio_assets.xml里配置:

    <asset id="FX_KEY_CLICK" file="Effect_Tick.ogg"/>
    <asset id="FX_FOCUS_NAVIGATION_UP" file="Effect_Tick.ogg"/>
    <asset id="FX_FOCUS_NAVIGATION_DOWN" file="Effect_Tick.ogg"/>
    <asset id="FX_FOCUS_NAVIGATION_LEFT" file="Effect_Tick.ogg"/>
    <asset id="FX_FOCUS_NAVIGATION_RIGHT" file="Effect_Tick.ogg"/>
    <asset id="FX_KEYPRESS_STANDARD" file="KeypressStandard.ogg"/>
    <asset id="FX_KEYPRESS_SPACEBAR" file="KeypressSpacebar.ogg"/>
    <asset id="FX_KEYPRESS_DELETE" file="KeypressDelete.ogg"/>
    <asset id="FX_KEYPRESS_RETURN" file="KeypressReturn.ogg"/>
    <asset id="FX_KEYPRESS_INVALID" file="KeypressInvalid.ogg"/>
    <asset id="FX_BACK" file="Effect_Tick.ogg"/>

可以通过修改这个配置或者对应的ogg文件来定制按键音效。

标签:java,ViewRootImpl,遥控器,Tab,按键,return,android,event,view
From: https://blog.csdn.net/li_5033/article/details/140317209

相关文章

  • 在Linux中,ptables是否支持time时间控制用户行为,如有请写出具体操作步骤。
    在Linux中,iptables是一个非常强大的防火墙工具,用于配置网络传输相关规则。然而,iptables本身并不支持基于时间的规则控制,也就是说,它不能直接根据时间来控制用户行为或网络流量。iptables的规则是基于包的源地址、目的地址、端口号、协议类型等来决定是否允许或拒绝数据包。但是......
  • 「AI绘画Stable Diffusion 零基础入门 」AI 绘画原理与工具介绍,万字解析AI绘画的使用
    大家好,我是程序员晓晓AI绘画原理想要入门AI绘画,首先需要了解它的原理是什么样的。其实很早就已经有人基于深度学习模型展开了对图像生成的研究了,但在那时,生成的图像分辨率和内容都非常抽象。直到近两年,AI产出的图像内容的质量变高、而且有一定的艺术价值,这时它才算......
  • 深入理解 CompletableFuture 的底层原理
    引言在现代Java编程中,异步编程变得越来越重要。为了实现高效和非阻塞的代码,Java8引入了CompletableFuture,一个用于构建异步应用程序的强大工具。本文将详细探讨CompletableFuture的底层原理,展示其工作机制,并通过代码示例说明如何在实际应用中使用它。异步编程的背景......
  • cron表达式和crontab表达式
    每次写cron表达式老是迷迷糊糊不敢肯定,特此记录crontab表达式*****分时日月周域值范围域数值字符备注秒[第一位]0~59-*/,-分[第二位]0~59-*/ ,-时[第三位]0~59-*/ ,-日[第四位]1~31-*?/ ,LWC -月[第五位]1~12JAN-DEC[月份简写] -*/ ,-......
  • 蓝桥杯单片机学习总结(Day4 独立按键实现LED流水灯)
    标题一:实现独立按键输出标题二:实现按键输出的效果标题三:实验总结      如图所示,S7、S6、S5、S4是独立按键一列,需要注意的是如果你的开发板独立按键和矩阵键盘是一体的如上图需要把引脚盖接到独立键盘那儿。    P30~P33是矩阵键盘和独立按键的引脚在编......
  • 【Stable Diffusion】(基础篇三)—— 关键词和参数设置
    提示词和文生图参数设置本系列笔记主要参考B站nenly同学的视频教程,传送门:B站第一套系统的AI绘画课!零基础学会StableDiffusion,这绝对是你看过的最容易上手的AI绘画教程|SDWebUI保姆级攻略_哔哩哔哩_bilibili本文主要讲解如何正确高效地使用合适的提示词来帮助完成AI绘......
  • 【AI绘画】什么是Stable Diffusion?保姆级 Stable Diffusion 入门教程
    一、什么是StableDiffusion?StableDiffusion是一种基于深度学习的文本到图像生成模型,它能够根据用户提供的文本描述生成相应的高质量图像。支持多种任务如图像修复、绘制、文本到图像等。在当下,StableDiffusion是最为流行和受欢迎之一的AI绘画工具。二、StableDiff......
  • CompletableFuture使用详解
    文章目录一、创建CompletableFuture1.1new关键字1.2静态工厂方法二、创建异步任务2.1supplyAsync2.2runAsync2.3获取任务结果的方法三、异步回调处理3.1thenApply和thenApplyAsync3.2thenAccept和thenAcceptAsync3.3thenRun和thenRunAsync3.4whenComplete和......
  • Stable Diffusion | AI协助室内设计神器,实现令人惊叹的视觉转换
    你是否已经厌倦了传统的室内设计方式,想探索新方法来增强作品设计感?本期小编就同大家分享一个新武器,用StableDiffusion的ControlNet来打造一个室内设计全新工作流。无论你是经验丰富的室内设计师还是初学小白,都将使你的日常工作如虎添翼、告别爆肝,焕发出令甲方爸爸们赞叹的......
  • Stable Diffusion|IP-Adapter 图片风格迁移
    前段时间腾讯发布了一个新的ControlNet模型叫“IP-Adapter”,它的作用就是把上传的图像转化为图像提示词,简单的来说就是跟Midjourney的垫图功能差不多。IP-Adapter能够精准地识别参考图的风格特征,并且可以很好的适配其他的模型以及ControlNet模型。它还可以让图像提示词......