In my app I have a webview that displays some content. One of these screen has a text box to fill in. I want to capture when the user presses the done button on the keyboard but there's no edit text to add a listener too. How can I capture the action regardless of device & keyboard?
I've tried these with little luck.
EditText with textPassword inputType, but without Softkeyboard
android: Softkeyboard perform action when Done key is pressed
#Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_ENTER:
// code here
break;
default:
return super.onKeyUp(keyCode, event);
}
return true;
}
My class overrides KeyEvent.Callback but the above funtion onKeyDown is never called.
Create a custom webview, override onCreateInputConnection to set ime option and input type to keyboard, override dispatchKeyEvent to get the key events filter it out
Example :
class MyWeb#JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0) : WebView(context, attrs, defStyleAttr) {
override fun onCreateInputConnection(outAttrs: EditorInfo): InputConnection {
val inputConnection = BaseInputConnection(this, false)
return inputConnection
}
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
super.dispatchKeyEvent(event)
val dispatchFirst = super.dispatchKeyEvent(event)
if (event.action == KeyEvent.ACTION_UP) {
when (event.keyCode) {
KeyEvent.KEYCODE_ENTER -> {
Toast.makeText(context,"Hii",Toast.LENGTH_LONG).show()
//callback?.onEnter()
}
}
}
return dispatchFirst
}
}
and XML
<com.example.MyWeb
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusable="true"
android:focusableInTouchMode="true"
android:id="#+id/web"
/>`
Source : https://medium.com/#elye.project/managing-keyboard-on-webview-d2e89109d106
Key events are almost never sent from a soft keyboard, they use more direct methods.
The way a keyboard on Android works is its bound to a view. That view has to implement getInputConnection() returning an object that will allow functions to be called (via AIDL) by the keyboard app. One of these functions is called for the "action key" (the done button). In the default InputConnection implementation, that will call a listener registered to the bound view.
Since you're dealing with a webview here- I don't think there is a way to capture it directly. What you can try is to subclass WebView to ActionKeyWebView. Add a function to register an action key listener interface.
Override getInputConnection to return your own InputConnectionWrapper subclass, and wrap super.getInputConnection(). THen override performEditorAction to call any listener registered for the webview. Its a fair amount of code but it should work.
Related
I'm a french developer , i'm creating an android tv application about cloud Gaming in a webview with java.
My application start a gaming stream direcly in the webview, on fullscreen, the physical buttons of controlers are working with the game, eccept the "view button" the "back button'.
This "view button" or if your prefer "select button" is for android tv a back button to the homescreen. So i have to overiding this back button, and i want replace it by a "select button" that it can interact with the games for displaying maps and inventory like in rpg games.
I know that the name will be "button_select" for interact with the pc game. So in android tv for now i will always redirected to the home page.
this is a sample of my code.
public class MainActivity extends AppCompatActivity {
#Override
public void onBackPressed() {
return;
}
With this overide, the back button is completly disabled for now, i want replace it or call the "button_select". I readed something about "handler" perhaps this is the solution.
Edit 16/06/2021
I tested much coded, but nothing work.
this one
#Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if(keyCode == KeyEvent.KEYCODE_BACK){
keyCode = KeyEvent.KEYCODE_BUTTON_SELECT;
}
return super.onKeyDown(keyCode, event);
}
And its variants
#Override
public boolean dispatchKeyEvent(KeyEvent event) {
int action = event.getAction();
int keyCode = event.getKeyCode();
switch (keyCode) {
case KeyEvent.KEYCODE_BACK:
if (action == KeyEvent.ACTION_DOWN ){
//Do something in the back button
keyCode = KeyEvent.KEYCODE_BUTTON_SELECT;
}
return true;
default:
return super.dispatchKeyEvent(event);
}
}
In game, the back button is disabled but, the" keycode_button_select "not interact with the game. There is nothing. I test with the "keycode button_start" the same things.
I tested the app button mapper for android tv, there is an option for custom keycodes with adb, and nothing is working in game.
https://play.google.com/store/apps/details?id=flar2.homebutton&hl=fr&gl=US
So by the code or by an app, nothing is working for now, i don't want to forcing the "root mode" for this things. Perhaps i'll just have to implementing a gamepad Plugin in java. I don't know...
Edit 18/06/21
I can capture the "back button" and display a dialog alert that appear on middle of the screen. But the actions after has no effect for now. I tested "dispatchEvent" with no success for now. I will testing the functions "robots". Perhaps "robot" and "dispatchEvent" will working together.
Thank you for your help.
I am assuming that you want to call button_select key when user presses back button.
In this case, create a method to call upon pressing back button. And call that method from the onBackPressed method.
For example:
public void callButtonSelectKey() {
//This could be done in either ways.
//1.
val i = Instrumentation()
i.sendKeyDownUpSync(KeyEvent.KEYCODE_BUTTON_SELECT)
//2.
val keyEventDown = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BUTTON_SELECT)
val keyEventUp = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BUTTON_SELECT)
dispatchKeyEvent(keyEventDown)
dispatchKeyEvent(keyEventUp)
}
#Override
public void onBackPressed() {
methodToCallUponBackPress();
return;
}
Now when the user presses the back button, it will call callButtonSelectKey method invoke "button_select" key. (i.e. remapped backbutton to button_select)
Let me know if you have any more questions.
EDIT
In an Object Oriented Language, overriding the class's (in this case KeyEvent) field (in this case, keycode) will do nothing. Like the code below:
//Do something in the back button
keyCode = KeyEvent.KEYCODE_BUTTON_SELECT;
To call button_select when you receive the event of KEYCODE_BACK, you should manually invoke the method to call Key Event, in this case the method I gave you earlier dispatchKeyEvent .
So you should make your change into something like this:
#Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if(keyCode == KeyEvent.KEYCODE_BACK){
//This could be done in either ways.
//1.
val i = Instrumentation()
i.sendKeyDownUpSync(KeyEvent.KEYCODE_BUTTON_SELECT)
//2.
val keyEventDown = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BUTTON_SELECT)
val keyEventUp = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BUTTON_SELECT)
dispatchKeyEvent(keyEventDown)
dispatchKeyEvent(keyEventUp)
}
return super.onKeyDown(keyCode, event); //Edit this return statement if you want to ignore a keypress.
}
My OnClickListener gets only called on the second click. The OnLongClickListener for the same View works as expected.
I tried using OnTouchListener instead, but that gets obviously triggered when swiping.
My listeners are abstract methods of an Interface that I implement in my activity:
interface OnVocableFlashcardFragmentInteractionListener {
fun onEditTextLongClick(view: View): Boolean
fun onEditTextClick(view: View)
}
I set the listeners of my View like this in my RecyclerViewAdapter Class:
init{
setHasStableIds(true)
mEditTextOnClickListener = View.OnClickListener {
mListener.onEditTextClick(it)
}
mEditTextOnLongClickListener = View.OnLongClickListener {
mListener.onEditTextLongClick(it)
}
}
override fun onBindViewHolder(holder: FlashcardViewHolder, position: Int) {
...
editText.let { it.tag = it.keyListener; it.keyListener = null; }
editText.setOnClickListener(mEditTextOnClickListener)
editText.setOnLongClickListener(mEditTextOnLongClickListener)
...
}
The implementation of the listeners in my activity looks like following:
override fun onEditTextClick(view: View) {
//-- only show toast if view is not editable (becomes editable on LongClick)
if ((view as EditText).keyListener == null) {
if (mToast != null) {
mToast!!.cancel()
}
//-- inform user to long press to edit entry
mToast = Toast.makeText(this, resources.getString(R.string.long_click_to_edit), Toast.LENGTH_LONG)
mToast!!.show()
}
}
override fun onEditTextLongClick(view: View): Boolean {
//-- I saved the KeyListener in the editTexts tag attribute
//-- to make it clickable again when needed
(view as EditText).keyListener = view.getTag() as KeyListener
showSoftKeyboard(view)
return true
}
The XML of my View looks like following:
<EditText
android:id="#+id/et_vocable_word"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:background="#null"
android:textStyle="bold"
android:hint="#string/enter_word"
android:imeOptions="actionNext"
android:inputType="textNoSuggestions"
android:maxLines="1"
android:singleLine="true" />
the view's parents and its parents parents are not declared as android:focusable="true" or android:clickable="true"
In my AndroidManifest.xml I have set android:windowSoftInputMode="stateHidden" for my activity to prevent the SoftInput from showing up when the activity starts.
Am I doing something utterly wrong or why does the OnClickListener only get called on the second click? Does anyone have an Idea how I could solve the problem?
you must make edit text focusable attribute false, because its default value is auto and this means is framework determine it must be true and false.when it be true at your first touch it has been focused by keyboard.
https://developer.android.com/reference/android/R.styleable#View_focusable
I was able to achieve the desired behavior by using OnTouchListener instead of OnClickListener like so:
override fun onEditTextTouch(editText: EditText, event: MotionEvent): Boolean {
//-- if the pressed gesture has finished
if (event.action == MotionEvent.ACTION_UP)
//-- only show toast if view is not editable (becomes editable on LongClick)
if (editText.keyListener == null) {
if (mToast != null) {
mToast!!.cancel()
}
//-- inform user to long press to edit entry
mToast = Toast.makeText(this, resources.getString(R.string.long_click_to_edit), Toast.LENGTH_LONG)
mToast!!.show()
}
return false
}
In my previous attempts at replacing my clickListener with a touchListener I forgot to check for (event.action == MotionEvent.ACTION_UP) so my code was also executed when swiping (which i did not want)
I get this warning (from the question title) in a custom Android view I am developing.
Why do I get warned? What's the logic behind it i.e. why is it a good
practice to also override performClick when you override onTouchEvent?
What's the purpose?
In some of the other answers you can see ways to make the warning go away, but it is important to understand why the system wants you to override performClick() in the first place.
There are millions of blind people in the world. Maybe you don't normally think about them much, but you should. They use Android, too. "How?" you might ask. One important way is through the TalkBack app. It is a screen reader that gives audio feedback. You can turn it on in your phone by going to Settings > Accessibility > TalkBack. Go through the tutorial there. It is really interesting. Now try to use your app with your eyes closed. You'll probably find that your app is extremely annoying at best and completely broken at worst. That's a fail for you and a quick uninstall by anyone's who's visually impaired.
Watch this excellent video by Google for an introduction into making your app accessible.
Developing Accessible Apps for Blind and Visually-Impaired Users
How to override performClick()
Let's look at a example custom view to see how overriding performClick() actually works. We'll make a simple missile launching app. The custom view will be the button to fire it.
It sounds a lot better with TalkBack enabled, but animated gifs don't allow audio, so you will just have to try it yourself.
Code
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<net.example.customviewaccessibility.CustomView
android:layout_width="200dp"
android:layout_height="200dp"
android:contentDescription="Activate missile launch"
android:layout_centerInParent="true"
/>
</RelativeLayout>
Notice that I set the contentDescription. This allows TalkBack to read out what the custom view is when the user feels over it.
CustomView.java
public class CustomView extends View {
private final static int NORMAL_COLOR = Color.BLUE;
private final static int PRESSED_COLOR = Color.RED;
public CustomView(Context context) {
super(context);
init();
}
public CustomView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public CustomView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
setBackgroundColor(NORMAL_COLOR);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
setBackgroundColor(PRESSED_COLOR);
return true;
case MotionEvent.ACTION_UP:
setBackgroundColor(NORMAL_COLOR);
// For this particular app we want the main work to happen
// on ACTION_UP rather than ACTION_DOWN. So this is where
// we will call performClick().
performClick();
return true;
}
return false;
}
// Because we call this from onTouchEvent, this code will be executed for both
// normal touch events and for when the system calls this using Accessibility
#Override
public boolean performClick() {
super.performClick();
launchMissile();
return true;
}
private void launchMissile() {
Toast.makeText(getContext(), "Missile launched", Toast.LENGTH_SHORT).show();
}
}
Notes
The documentation also uses an mDownTouch variable which appears to be used to filter out extra touch up events, but since it isn't well explained or strictly necessary for our app, I left it out. If you make a real missile launcher app, I suggest you look more into this.
The primary method that launches the missile (launchMissile()) is just called from performClick(). Be careful not to call it twice if you also have it in onTouchEvent. You will need to decide exactly how and when to call your business logic method depending on the specifics of your custom view.
Don't override performClick() and then do nothing with it just to get rid of the warning. If you want to ignore the millions of blind people in the world, then you can suppress the warning. At least that way you are honest about your heartlessness.
#SuppressLint("ClickableViewAccessibility")
#Override
public boolean onTouchEvent(MotionEvent event) { ... }
Further study
Accessibility overview
Build accessible custom views (especially the Handle custom touch events section)
Make apps more accessible
This warning tells you to override performClick
#Override
public boolean performClick() {
// Calls the super implementation, which generates an AccessibilityEvent
// and calls the onClick() listener on the view, if any
super.performClick();
// Handle the action for the custom click here
return true;
}
But it is not compulsory. As I have created a custom knobView and it is working quite good where I am also facing this warning.
The onTouchEvent is not called by some Accessibility services, as explained by clicking the "more..." link in the warning details.
It recommends that you override performClick for your desired action, or at least override it alongside your onTouchEvent.
If your code is more fitting for the touch event, you can use something similar to:
#Override
public boolean performClick() {
if (actionNotAlreadyExecuted) {
MotionEvent myEvent = MotionEvent.obtain(long downTime, long eventTime, int action, float x, float y, int metaState);
onTouch(myView, myEvent);
}
return true; // register it has been handled
}
More information on accessing touch events through code is available at trigger ontouch event programmatically
I want to catch the press of any key of the softkeyboard. I don't want a EditView or TextView in my Activity, the event must be handled from a extended View inside my Activity.
I just tried this:
1) Override the onKeyUp(int keyCode, KeyEvent event) Activity method. This don't work with softkeyboard, it just catch few hardkeyboard.
2) Create my OnKeyListener and register that in my View that contains a registered and working OnTouchListener. This doesn't work at all with softkeyboard.
3) Override the onKeyUp(int keyCode, KeyEvent event) View method. This not work at all neither if I set my OnKeyListener nor if I don't set it.
4) With the InputMethodManager object Call the method showSoftInput and passing it my View. This don't work neither for raise up the keyboard, indeed i have to call toggleSoftInput; nor to catch the key events.
I tested all only in the emulator but i think it's enough. Why it's so complicate take a simple key event from a keyboard ?
For handling hardware keys and Back key you could use dispatchKeyEvent(KeyEvent event) in your Activity
#Override
public boolean dispatchKeyEvent(KeyEvent event) {
Log.i("key pressed", String.valueOf(event.getKeyCode()));
return super.dispatchKeyEvent(event);
}
UPD: unfortunately you can't handle soft keyboard events (see Handle single key events), unless you develop your own custom keyboard (follow the link to learn how Creating input method).
With the hint of vasart i can get the KeyPress event. To make the keycode printable i have used the function getUnicodeChar passing it the meta button state then just a char cast solve the problem.
This is the working code:
#Override
public boolean dispatchKeyEvent(KeyEvent KEvent)
{
int keyaction = KEvent.getAction();
if(keyaction == KeyEvent.ACTION_DOWN)
{
int keycode = KEvent.getKeyCode();
int keyunicode = KEvent.getUnicodeChar(KEvent.getMetaState() );
char character = (char) keyunicode;
System.out.println("DEBUG MESSAGE KEY=" + character + " KEYCODE=" + keycode);
}
return super.dispatchKeyEvent(KEvent);
}
Of course this work only with ASCII character.
There are no option to handling key press events on soft keyboard (an on-screen keyboard) only from a hardware keyboard.
for more details: Handling Keyboard Actions
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).
When Keyboard is opened in activity your activity actually becomes foreground...
All TextArea or TextFields have their own mechanism to get keypressed from onScreen keyboard...
if you want to use onKeyDown() listner for virtual keyboard make sure that you set in AndroidManifest File under your activity android:windowSoftInputMode="stateAlwaysVisible" then onkeyDown() will work it did worked for me ...
I have an application that is just a surfaceview. All i do is draw stuff on the surfaceview and whatnot. So one functionality i want is if the user touches a corner of the surfaceview it shows the keyboard and then they can type into it. Note that there are no EditTexts or Textboxes in my app anywhere. How do i call the keyboard to pop up and then how do i get all the keys that the user is pressing? I don't want the keys necessarily, i just want the string that they typed. How do i go about accomplishing this?
This is not a trivial task.
First of all, you'll need to override the method onCreateInputConnection()
#Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
return new MyInputConnection(this, surfaceView, false);
}
Then you'll have to implement this input connection class, deriving from BaseInputConnection.
class MyInputConnection extends BaseInputConnection{
private MyActivity activity;
public MyInputConnection(MyActivity activity, View targetView, boolean fullEditor)
{
super( targetView, fullEditor );
mActivity = activity;
}
public boolean commitText(CharSequence text, int newCursorPosition){
myActivity.drawText((String) text);
return true;
}
There are more methods you'll want to override (see the reference), but start by focusing on commitText(). DrawText() is a method that renders the text on your surface, you'll have to come up with an implementation that suits your needs.