简体   繁体   English

在Android中,可以在他们到达具有焦点的子视图之前全局拦截关键事件吗?

[英]In Android, Can One Intercept Key Events Globally Before They Reach the Child View That Has Focus?

In most circumstances, I want key events to be processed by the (child) view that presently has focus, which is the default behavior, and also is what I presently have implemented. 在大多数情况下,我希望关键事件由目前具有焦点的(子)视图处理,这是默认行为,也是我目前已经实现的。

However, under certain very specific circumstances, I would like to temporarily intercept and handle all key events (including specifically those normally handled by the child views) either in my current activity or, failing that, in my root view (it doesn't matter, so long as they are processed globally - I don't care about hardware buttons like volume +/-, as these are not handled in any of my child views anyway). 但是,在某些非常具体的情况下,我想在我当前的活动中暂时拦截和处理所有关键事件(包括那些通常由子视图处理的关键事件),或者在我的根视图中失败,这是无关紧要的,只要它们在全球范围内处理 - 我不关心像音量+/-这样的硬件按钮,因为无论如何都不会在我的任何子视图中处理这些按钮)。

For touch events, we have onInterceptTouchEvent(), which allows a ViewGroup to watch touch events as they are dispatched to child views, and to block receipt of those events (when desired) by the child views for which they are intended. 对于触摸事件,我们有onInterceptTouchEvent(),它允许ViewGroup在调度到子视图时观察触摸事件,并阻止它们所针对的子视图接收这些事件(如果需要)。

Unfortunately, I can't find anything analogous to onInterceptTouchEvent() for key events. 不幸的是,我找不到与onInterceptTouchEvent()类似的关键事件。 Am I missing something obvious, or is this an actual asymmetry in the OS? 我错过了一些明显的东西,或者这是操作系统中的实际不对称?

Of course, I could just wire the current key event handler code of every child view to directly call a method on the main activity to handle the event if it wants to, and to have that activity-level method return a boolean that indicates whether it handled the event. 当然,我可以连接每个子视图的当前键事件处理程序代码,直接调用主活动上的方法来处理事件,如果它想要,并让该活动级方法返回一个布尔值,指示是否它处理了这个事件。 Then, the child view could handle the event if and only if the activity method it invoked had not handled it. 然后,当且仅当它调用的活动方法没有处理它时,子视图才能处理该事件。

But I'm hoping there's a cleaner way to simply intercept the traffic on its way "down" the hierarchy to the child view, just as onInterceptTouchEvent() does for touch events. 但我希望有一种更简洁的方法可以简单地拦截流向“层次”层次到子视图的流量,就像onInterceptTouchEvent()对触摸事件一样。

All widgets/Views seem to implement KeyEvent.Callback , which has methods to be called for key down, long press, multiple and key up. 所有小部件/视图似乎都实现了KeyEvent.Callback ,它具有调用key down,long press,multiple和key up的方法。 All of these methods return boolean values explained as: 所有这些方法都返回布尔值,解释如下:

If you handled the event, return true. 如果您处理了该事件,则返回true。 If you want to allow the event to be handled by the next receiver, return false. 如果要允许事件由下一个接收器处理,则返回false。

I think you can try overriding these methods in parent components. 我想你可以尝试在父组件中重写这些方法。

The other one is dispatchKeyEvent() of View it handles dispatching events down the focus path. 另一个是View dispatchKeyEvent() ,它处理焦点路径下的调度事件。 Overriding this can be tried also. 也可以尝试覆盖这一点。

Overriding dispatchKeyEvent in a ViewGroup which is ancestral to the View s to be disabled works. 覆盖ViewGroup中的dispatchKeyEvent ,该ViewGroup是要禁用的View的祖先。

I am using the following~. 我使用以下〜。

public static class TvPrefsContainer extends FrameLayout {
    private boolean isActive; // i.e. will dispatch keyboard events to focused children
    // snip

    @Override public boolean dispatchKeyEvent(KeyEvent event) {
        return isActive && super.dispatchKeyEvent(event);
    }
}

public static class TvPrefs extends PreferenceFragment {
    @Override public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        addPreferencesFromResource(R.xml.tv_prefs);
    }
}

private void togglePrefsVis() {
    tvPrefsContainer.animate()
            .alpha(isPrefsVisible ? 0 : 1)
            .translationX(isPrefsVisible ? tvPrefsContainer.getMeasuredWidth() : 0)
            .start();
    tvPrefsContainer.isActive = isPrefsVisible;
    isPrefsVisible = !isPrefsVisible;
}

You can add OnGlobalFocusChangeListener to your root view. 您可以将OnGlobalFocusChangeListener添加到根视图中。 Let's do that per activity: 让我们按活动做:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    findViewById(android.R.id.content).getViewTreeObserver().addOnGlobalFocusChangeListener(new ViewTreeObserver.OnGlobalFocusChangeListener() {
        @Override
        public void onGlobalFocusChanged(View oldFocus, View newFocus) {
            if (newFocus instanceof EditText) {
                ((EditText) newFocus).addTextChangedListener(new TextWatcher() {
                    @Override
                    public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
                        //process whatever you want
                    }

                    @Override
                    public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
                    }

                    @Override
                    public void afterTextChanged(Editable editable) {
                    }
                });
            }
        }
    });
}

Of course if you are interested in views other than EditText you have to figure out how to observe their changes. 当然,如果您对EditText以外的其他视图感兴趣,您必须弄清楚如何观察他们的更改。

As well if you want to add it globally to all of your activities use Application#registerActivityLifecycleCallbacks() to use that code in all of your activities. 如果要将其全局添加到所有活动中,请使用Application#registerActivityLifecycleCallbacks()在所有活动中使用该代码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM