Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DispatchKeyEvent to listen for Spacebar being pressed

Tags:

android

I am working on an Android project and I need to check when the spacebar is pressed sos I can execute a certain function.

The problem is, it was working on the emulator but not on my actual device. I think it might be because my emulator was using a physical keyboard, not the onscreen virtual keyboard but when testing on an actual device, its using a virtual keyboard.

I'm trying the dispatch keyevent

@Override
    public boolean dispatchKeyEvent(KeyEvent event)
    {
        
        if (event.getKeyCode() == KeyEvent.KEYCODE_SPACE
                && event.getAction() == KeyEvent.ACTION_UP)
        {
            QueryEditor queryEditor = (QueryEditor)getSupportFragmentManager().findFragmentById(R.id.fragment_queryEditor);
            queryEditor.formatQueryText();
            return true;
        }
        }

I've also tried the on key down

@Override
    public boolean onKeyDown(int keyCode, KeyEvent event)
    {
        if (keyCode == KeyEvent.KEYCODE_BACK)
        {
            disconnectDatabase();
        }
        else if (keyCode == KeyEvent.KEYCODE_DEL)
        {
            QueryEditor queryEditor = (QueryEditor)getSupportFragmentManager().findFragmentById(R.id.fragment_queryEditor);
            queryEditor.formatQueryText();
        }
        
        return super.onKeyDown(keyCode, event);
    }

Neither of these get fired though unless the back button is pressed but I need the spacebar to trigger the event.

Update

Below is my code for how QueryEditor Fragment is created and the event handler is created

@Override
    public void onActivityCreated(Bundle savedInstanceState)
    {
        super.onActivityCreated(savedInstanceState);
        
        iQueryEditor = (IQueryEditor)this;
        iErrorHandler = (IErrorHandler)this;
        
        txtQueryEditor = (EditText)getActivity().findViewById(R.id.query_txtQueryEditor);
        
        btnSubmitQuery = (ImageButton)getActivity().findViewById(R.id.query_btnPerformQuery);
        btnClearQuery = (ImageButton)getActivity().findViewById(R.id.query_btnDeleteQuery);
        
        txtQueryEditor.addTextChangedListener(new QueryTextChanged(getActivity(), txtQueryEditor, iQueryEditor));
        setDatabaseUsed();
        txtQueryEditor.setOnEditorActionListener(new OnEditorActionListener() {
            
            @Override
            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
                if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER))
                        || actionId == EditorInfo.IME_ACTION_DONE)
                {
                    executeQuery();
                    return true;
                }
                else if (event.getKeyCode() == KeyEvent.KEYCODE_SPACE && event.getAction() == KeyEvent.ACTION_UP)
                {
                    Toast.makeText(getActivity(), "Space Bar Pressed", Toast.LENGTH_SHORT).show();
                    return true;
                }
                return false;
            }
        });
        
        btnSubmitQuery.setOnClickListener(mBtnSubmitQueryListener);
        btnClearQuery.setOnClickListener(mBtnDeleteQueryListener);
    }

txtQueryEditor is the EditText that I am trying to receive the space bar event on.

like image 544
Boardy Avatar asked May 12 '14 20:05

Boardy


2 Answers

Part of the answer lies in the KeyEvent API documentation:

As soft input methods can use multiple and inventive ways of inputting text, there is no guarantee that any key press on a soft keyboard will generate a key event: this is left to the IME's discretion, and in fact sending such events is discouraged. You should never rely on receiving KeyEvents for any key on a soft input method. In particular, the default software keyboard will never send any key event to any application targetting Jelly Bean or later, and will only send events for some presses of the delete and return keys to applications targetting Ice Cream Sandwich or earlier. Be aware that other software input methods may never send key events regardless of the version. Consider using editor actions like IME_ACTION_DONE if you need specific interaction with the software keyboard, as it gives more visibility to the user as to how your application will react to key presses.

Here is a comment left by a Google Engineer:

The answer is quite simple. Applications should never rely on key events to manage user input. The reason for this is, it results in a very poor user experience.

To start with, there are too many cases where it does not work. It does not work appropriately for CJK languages. It does not work correctly with gesture input, or with voice input, or with toggle input, or any new inventive input method developers may come up with in the future. In fact, the only case where it works is for only restricted cases of the poorest input experience : a legacy hardware-keyboard-like input method, which is a poor fit for the mobile era.

This is why Android defines a rich API through which IMEs communicate to the applications. It is method agnostic and language agnostic, and all software input methods are implementing it. Applications are supposed to make use of this API, and using EditText is the simplest way of doing this (more on this below).

Please stop relying on legacy key events for text entry -- AT ALL. It is good to support them, but requiring them is bad for everyone. It breaks consistency, it locks some languages out of your app, and it forces users into a much poorer experience than they expect on Android. And as noted in comment #14, input methods are explicitly under no obligation at all to send key events, though some may choose to do it.

There are two simple ways of implementing the rich text input APIs. Both will require some of this is done in Java land. The first one is to simply use EditText. If your app needs to do its own rendering, you can either subclass it if that suits you, or make it hidden while you render any changes to the text. You could also use DynamicLayout and Editable to achieve the same effect. Another possibility is to implement directly the InputConnection API by extending BaseInputConnection, however this is a LOT of hard work and should generally only need to be done by applications that have very specific needs with regards to text edition, like an IDE or a word processor.

like image 163
Manish Mulimani Avatar answered Oct 15 '22 12:10

Manish Mulimani


This implementation in your Activity should work:

@Override
public boolean dispatchKeyEvent(KeyEvent event) {
    if (event.getKeyCode() == KeyEvent.KEYCODE_SPACE && event.getAction() == KeyEvent.ACTION_UP) {
        Log.d("test", "[Dispatch] Space bar pressed! " + event);
        return true;
    }
    return super.dispatchKeyEvent(event);
}

The difference to your code is that I am calling super.dispatchKeyEvent() for all other keys than SPACE_BAR. If dispatchKeyEvent returns true onKeyUp() won't be triggered. So if you wish to just monitor space key event simply comment line //return true;

It would also works fine if you use onKeyUp() method. Do not use onKeyDown(), it may be called few times if user holds his finger too long.

@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_SPACE) {
        Log.d("test", "Space bar pressed!");
        return true;
    }
    return super.onKeyUp(keyCode, event);
}

Here I use similar approach. If your if statement is true handle event and return true. For the rest of your keys call super.onKeyUp();

At least, but most important, if you have view(e.g. EditText) that owns current focus and keyboard is shown for that view then above code won't be called at all(in Activity). If this is the case you have to implement listener TextView.OnEditorActionListener and register for that view calling setOnEditorActionListener(TextView.OnEditorActionListener l).

viewWithFocus.setOnEditorActionListener(new TextView.OnEditorActionListener() {
    @Override
    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
        if (event.getKeyCode() == KeyEvent.KEYCODE_SPACE && event.getAction() == KeyEvent.ACTION_UP) {
            Log.d("test", "[Editor] Space bar pressed! " + event);
            //TODO implement your action here
            return true;//remove this line if you want edit text to create space
        }
        return false;
    }
});

As alternative, you can override this view and implement onKepUp() like above.

Update

Above solution works for hardware keyboard. Sorry for not checking this more carefully.

From Android Documentation

Note: When handling keyboard events with the KeyEvent class and related APIs, you should expect that such keyboard events come only from a hardware keyboard. You should never rely on receiving key events for any key on a soft input method (an on-screen keyboard).

However, I found how to overcome this problem. I have based my research on SearchView component and found that following code will do the job:

editText.addTextChangedListener(new TextWatcher() {
    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        char last = s.charAt(s.length()-1);
        if (' ' == last) {
            Log.i("test", "space pressed");
        }
    }

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

For IME actions keep using TextView.OnEditorActionListener.

like image 40
Damian Petla Avatar answered Oct 15 '22 11:10

Damian Petla