web-dev-qa-db-fra.com

Android - ne peut pas capturer le retour arrière / supprimer appuyez sur dans le clavier logiciel.

Je remplace la méthode onKeyDown de la vue (vue de surface openGL) pour capturer toutes les pressions sur les touches. Le problème est que sur plusieurs appareils le KEYCODE_DEL n'est pas capturé. J'ai essayé d'ajouter un onKeyListener à la vue, et cela a tout capturé sauf la touche de retour arrière.

Il doit y avoir un moyen d'écouter cet événement de presse clé, mais comment?

33
Vladimir Gazbarov

MISE À JOUR DU 11/12/2014: Changement de la portée du correctif pour ne pas se limiter au <niveau API 19, car sur un clavier tiers, le bogue a toujours au-delà de 19 .

MISE À JOUR DU 1/9/2014: J'ai conçu une approche, avec du code, pour résoudre tous les problèmes KEYCODE_DEL de Google Keyboard (LatinIME), en particulier les problèmes 42904 et 62306.

L'amélioration de la réponse de Turix a été incorporée, avec permission, dans mon propre code ici . Les améliorations de Turix supprimées doivent injecter des caractères inutiles dans le tampon modifiable en trouvant à la place un moyen incrémentiel pour garantir qu'un seul caractère se trouve toujours dans ce tampon.

J'ai utilisé du code (similaire) dans une application déployée que vous pouvez tester:
https://play.google.com/store/apps/details?id=com.goalstate.WordGames.FullBoard.trialsuite]

INTRODUCTION:

La solution de contournement présentée ci-dessous est destinée à fonctionner pour toutes les versions du clavier Google, passées et futures, en ce qui concerne ces deux bogues. Cette solution de contournement nécessite pas qu'une application reste bloquée en ciblant le niveau d'API 15 ou inférieur, auquel certaines applications se sont limitées afin de tirer parti du code de compatibilité qui contourne le problème 42904.

Ces problèmes sont uniquement présents comme bogues pour une vue qui a implémenté le remplacement de onCreateInputConnection (), et qui renvoie TYPE_NULL à l'IME appelant (dans le membre inputType de l'argument EditorInfo passé à cette méthode par l'IME). Ce n'est qu'en faisant cela qu'une vue peut raisonnablement attendre que les événements clés (y compris KEYCODE_DEL) lui seront renvoyés à partir d'un clavier virtuel. Par conséquent, la solution de contournement présentée ici nécessite le TYPE_NULL InputType.

Pour les applications n'utilisant pas TYPE_NULL, il existe plusieurs remplacements dans l'objet dérivé de BaseInputConnection renvoyé par une vue de sa substitution onCreateInputConnection (), qui sont invoqués par l'IME lorsque l'utilisateur effectue des modifications, à la place de l'IME générant des événements clés. Cette approche (non TYPE_NULL) est généralement supérieure, car les capacités du clavier logiciel s'étendent désormais bien au-delà du simple tapotement des touches, à des choses comme la saisie vocale, l'achèvement, etc. Les événements clés sont une méthode plus ancienne, et ceux qui implémentent LatinIME chez Google ont dit qu'ils aimeraient voir l'utilisation de TYPE_NULL (et des événements clés) disparaître.

Si l'arrêt de l'utilisation de TYPE_NULL est une option, je vous exhorte à continuer avec l'approche recommandée d'utiliser les méthodes de remplacement InputConnection au lieu des événements clés (ou, plus simplement, en utilisant une classe dérivée de EditText, qui le fait pour vous ).

Néanmoins, le comportement TYPE_NULL n'est pas officiellement interrompu, et donc l'échec de LatinIME à générer des événements KEYCODE_DEL dans certaines circonstances est en effet un bogue. J'offre la solution de contournement suivante pour résoudre ce problème.

APERÇU:

Les problèmes rencontrés par les applications lors de la réception de KEYCODE_DEL de LatinIME sont dus à DEUX bogues connus, comme indiqué ici :

https://code.google.com/p/Android/issues/detail?id=42904 (répertorié comme WorkingAsIntended, mais le problème est, je le maintiens, un bogue dans la mesure où il provoque un échec de prennent en charge la génération d'événements KEYCODE_DEL pour les applications ciblant l'API de niveau 16 et supérieur qui ont spécifiquement répertorié un InputType de TYPE_NULL. Le problème est résolu dans les dernières versions de LatinIME, mais il existe des versions antérieures dans la nature qui présentent toujours ce bogue, et donc les applications utilisant TYPE_NULL et l'API de ciblage de niveau 16 ou supérieur auront toujours besoin d'une solution de contournement pouvant être effectuée à partir de l'application.

et ici :

http://code.google.com/p/Android/issues/detail?id=62306 (actuellement répertorié comme fixe mais pas encore publié - FutureRelease - mais même une fois qu'il sera publié, nous continuerons besoin d'une solution de contournement qui peut être effectuée à partir de l'application pour gérer les versions antérieures qui persisteront "dans la nature").

Conformément à cette thèse (que les problèmes rencontrés avec les événements KEYCODE_DEL sont dus à des bogues dans LatinIME), j'ai constaté que lors de l'utilisation d'un clavier matériel externe, et également lors de l'utilisation du clavier logiciel SwiftKey tiers, ces problèmes ne se produisent pas, alors qu'ils do se produit pour des versions spécifiques de LatinIME.

L'un ou l'autre (mais pas les deux à la fois) de ces problèmes est présent dans certaines versions de LatinIME. Par conséquent, il est difficile pour les développeurs de savoir pendant les tests s'ils ont résolu tous les problèmes KEYCODE_DEL, et parfois lorsqu'une mise à jour Android (ou clavier Google)) est effectuée, un problème ne sera plus reproductible Néanmoins, les versions LatinIME qui causent le problème seront présentes sur un certain nombre de périphériques utilisés. Cela m'a obligé à creuser dans le référentiel git AOSP LatinIME pour déterminer la portée exacte de chacun des deux problèmes (c'est-à-dire, les versions spécifiques de LatinIME et Android pour lesquelles chacun des deux problèmes peut être présent.) Le code de contournement ci-dessous a été limité à ces versions spécifiques.

Le code de contournement présenté ci-dessous comprend des commentaires détaillés qui devraient vous aider à comprendre ce qu'il tente d'accomplir. Après la présentation du code, je fournirai quelques discussions supplémentaires, qui comprendront les validations spécifiques Android Open Source Project (AOSP) au cours desquelles chacun des deux bogues a été introduit, et au cours desquelles il disparu, ainsi que les versions Android qui pourraient inclure les versions de Google Keyboard concernées.

Je voudrais avertir quiconque envisage d'utiliser cette approche pour effectuer ses propres tests afin de vérifier qu'elle fonctionne pour son application particulière. Je pense que cela fonctionnera en général et je l'ai testé sur un certain nombre d'appareils et de versions LatinIME, mais le raisonnement est compliqué, alors soyez prudent. Si vous rencontrez des problèmes, veuillez poster un commentaire ci-dessous.

[~ # ~] code [~ # ~] :

Voici donc ma solution de contournement pour les deux problèmes, avec une explication incluse dans les commentaires du code:

Tout d'abord, incluez la classe suivante (modifiée à votre goût) dans votre application, dans son propre fichier source InputConnectionAccomodatingLatinIMETypeNullIssues.Java:

import Android.view.KeyEvent;
import Android.view.View;
import Android.view.inputmethod.BaseInputConnection;

/**
 * 
 * @author Carl Gunther
 * There are bugs with the LatinIME keyboard's generation of KEYCODE_DEL events 
 * that this class addresses in various ways.  These bugs appear when the app 
 * specifies TYPE_NULL, which is the only circumstance under which the app 
 * can reasonably expect to receive key events for KEYCODE_DEL.
 * 
 * This class is intended for use by a view that overrides 
 * onCreateInputConnection() and specifies to the invoking IME that it wishes 
 * to use the TYPE_NULL InputType.  This should cause key events to be returned 
 * to the view.
 * 
 */
public class InputConnectionAccomodatingLatinIMETypeNullIssues extends BaseInputConnection {

    //This holds the Editable text buffer that the LatinIME mistakenly *thinks* 
    // that it is editing, even though the views that employ this class are 
    // completely driven by key events.
    Editable myEditable = null;

    //Basic constructor
    public InputConnectionAccomodatingLatinIMETypeNullIssues(View targetView, boolean fullEditor) {
        super(targetView, fullEditor);
    }

    //This method is called by the IME whenever the view that returned an 
    // instance of this class to the IME from its onCreateInputConnection() 
    // gains focus.
    @Override
    public Editable getEditable() {
      //Some versions of the Google Keyboard (LatinIME) were delivered with a 
      // bug that causes KEYCODE_DEL to no longer be generated once the number 
      // of KEYCODE_DEL taps equals the number of other characters that have 
      // been typed.  This bug was reported here as issue 62306.
      //
      // As of this writing (1/7/2014), it is fixed in the AOSP code, but that 
      // fix has not yet been released.  Even when it is released, there will 
      // be many devices having versions of the Google Keyboard that include the bug
      // in the wild for the indefinite future.  Therefore, a workaround is required.
      // 
      //This is a workaround for that bug which just jams a single garbage character 
      // into the internal buffer that the keyboard THINKS it is editing even 
      // though we have specified TYPE_NULL which *should* cause LatinIME to 
      // generate key events regardless of what is in that buffer.  We have other
      // code that attempts to ensure as the user edites that there is always 
      // one character remaining.
      // 
      // The problem arises because when this unseen buffer becomes empty, the IME
      // thinks that there is nothing left to delete, and therefore stops 
      // generating KEYCODE_DEL events, even though the app may still be very 
      // interested in receiving them.
      //
      //So, for example, if the user taps in ABCDE and then positions the 
      // (app-based) cursor to the left of A and taps the backspace key three 
      // times without any evident effect on the letters (because the app's own 
      // UI code knows that there are no letters to the left of the 
      // app-implemented cursor), and then moves the cursor to the right of the 
      // E and hits backspace five times, then, after E and D have been deleted, 
      // no more KEYCODE_DEL events will be generated by the IME because the 
      // unseen buffer will have become empty from five letter key taps followed 
      // by five backspace key taps (as the IME is unaware of the app-based cursor 
      // movements performed by the user).  
      //
      // In other words, if your app is processing KEYDOWN events itself, and 
      // maintaining its own cursor and so on, and not telling the IME anything 
      // about the user's cursor position, this buggy processing of the hidden 
      // buffer will stop KEYCODE_DEL events when your app actually needs them - 
      // in whatever Android releases incorporate this LatinIME bug.
      //
      // By creating this garbage characters in the Editable that is initially
      // returned to the IME here, we make the IME think that it still has 
      // something to delete, which causes it to keep generating KEYCODE_DEL 
      // events in response to backspace key presses.
      //
      // A specific keyboard version that I tested this on which HAS this 
      // problem but does NOT have the "KEYCODE_DEL completely gone" (issue 42904)
      // problem that is addressed by the deleteSurroundingText() override below 
      // (the two problems are not both present in a single version) is 
      // 2.0.19123.914326a, tested running on a Nexus7 2012 tablet.  
      // There may be other versions that have issue 62306.
      // 
      // A specific keyboard version that I tested this on which does NOT have 
      // this problem but DOES have the "KEYCODE_DEL completely gone" (issue 
      // 42904) problem that is addressed by the deleteSurroundingText() 
      // override below is 1.0.1800.776638, tested running on the Nexus10 
      // tablet.  There may be other versions that also have issue 42904.
      // 
      // The bug that this addresses was first introduced as of AOSP commit tag 
      // 4.4_r0.9, and the next RELEASED Android version after that was 
      // Android-4.4_r1, which is the first release of Android 4.4.  So, 4.4 will 
      // be the first Android version that would have included, in the original 
      // RELEASED version, a Google Keyboard for which this bug was present.
      //
      // Note that this bug was introduced exactly at the point that the OTHER bug
      // (the one that is addressed in deleteSurroundingText(), below) was first 
      // FIXED.
      //
      // Despite the fact that the above are the RELEASES associated with the bug, 
      // the fact is that any 4.x Android release could have been upgraded by the 
      // user to a later version of Google Keyboard than was present when the 
      // release was originally installed to the device.  I have checked the 
      // www.archive.org snapshots of the Google Keyboard listing page on the Google 
      // Play Store, and all released updates listed there (which go back to early 
      // June of 2013) required Android 4.0 and up, so we can be pretty sure that 
      // this bug is not present in any version earlier than 4.0 (ICS), which means
      // that we can limit this fix to API level 14 and up.  And once the LatinIME 
      // problem is fixed, we can limit the scope of this workaround to end as of 
      // the last release that included the problem, since we can assume that 
      // users will not upgrade Google Keyboard to an EARLIER version than was 
      // originally included in their Android release.
      //
      // The bug that this addresses was FIXED but NOT RELEASED as of this AOSP 
      // commit:
      //https://Android.googlesource.com/platform/packages/inputmethods/LatinIME/+
      // /b41bea65502ce7339665859d3c2c81b4a29194e4/Java/src/com/Android
      // /inputmethod/latin/LatinIME.Java
      // so it can be assumed to affect all of KitKat released thus far 
      // (up to 4.4.2), and could even affect beyond KitKat, although I fully 
      // expect it to be incorporated into the next release *after* API level 19.
      //
      // When it IS released, this method should be changed to limit it to no 
      // higher than API level 19 (assuming that the fix is released before API 
      // level 20), just in order to limit the scope of this fix, since poking 
      // 1024 characters into the Editable object returned here is of course a 
      // kluge.  But right now the safest thing is just to not have an upper limit 
      // on the application of this kluge, since the fix for the problem it 
      // addresses has not yet been released (as of 1/7/2014).
      if(Build.VERSION.SDK_INT >= 14) {
        if(myEditable == null) {
      myEditable = new EditableAccomodatingLatinIMETypeNullIssues(
            EditableAccomodatingLatinIMETypeNullIssues.ONE_UNPROCESSED_CHARACTER);
          Selection.setSelection(myEditable, 1);
        }
    else {
          int myEditableLength = myEditable.length(); 
          if(myEditableLength == 0) {
          //I actually HAVE seen this be zero on the Nexus 10 with the keyboard 
          // that came with Android 4.4.2
          // On the Nexus 10 4.4.2 if I tapped away from the view and then back to it, the 
          // myEditable would come back as null and I would create a new one.  This is also 
          // what happens on other devices (e.g., the Nexus 6 with 4.4.2,
          // which has a slightly later version of the Google Keyboard).  But for the 
          // Nexus 10 4.4.2, the keyboard had a strange behavior
          // when I tapped on the rack, and then tapped Done on the keyboard to close it, 
          // and then tapped on the rack AGAIN.  In THAT situation,
          // the myEditable would NOT be set to NULL but its LENGTH would be ZERO.  So, I 
          // just append to it in that situation.
          myEditable.append(
            EditableAccomodatingLatinIMETypeNullIssues.ONE_UNPROCESSED_CHARACTER);
          Selection.setSelection(myEditable, 1);
        }
      }
      return myEditable;
    }
    else {
      //Default behavior for keyboards that do not require any fix
      return super.getEditable();
    }
  }

  //This method is called INSTEAD of generating a KEYCODE_DEL event, by 
  // versions of Latin IME that have the bug described in Issue 42904.
  @Override
  public boolean deleteSurroundingText(int beforeLength, int afterLength) {
    //If targetSdkVersion is set to anything AT or ABOVE API level 16 
    // then for the GOOGLE KEYBOARD versions DELIVERED 
    // with Android 4.1.x, 4.2.x or 4.3.x, NO KEYCODE_DEL EVENTS WILL BE 
    // GENERATED BY THE GOOGLE KEYBOARD (LatinIME) EVEN when TYPE_NULL
    // is being returned as the InputType by your view from its 
    // onCreateInputMethod() override, due to a BUG in THOSE VERSIONS.  
    //
    // When TYPE_NULL is specified (as this entire class assumes is being done 
    // by the views that use it, what WILL be generated INSTEAD of a KEYCODE_DEL 
    // is a deleteSurroundingText(1,0) call.  So, by overriding this 
    // deleteSurroundingText() method, we can fire the KEYDOWN/KEYUP events 
    // ourselves for KEYCODE_DEL.  This provides a workaround for the bug.
    // 
    // The specific AOSP RELEASES involved are 4.1.1_r1 (the very first 4.1 
    // release) through 4.4_r0.8 (the release just prior to Android 4.4).  
    // This means that all of KitKat should not have the bug and will not 
    // need this workaround.
    //
    // Although 4.0.x (ICS) did not have this bug, it was possible to install 
    // later versions of the keyboard as an app on anything running 4.0 and up, 
    // so those versions are also potentially affected.
    //
    // The first version of separately-installable Google Keyboard shown on the 
    // Google Play Store site by www.archive.org is Version 1.0.1869.683049, 
    // on June 6, 2013, and that version (and probably other, later ones) 
    // already had this bug.
    //
    //Since this required at least 4.0 to install, I believe that the bug will 
    // not be present on devices running versions of Android earlier than 4.0.  
    //
    //AND, it should not be present on versions of Android at 4.4 and higher,
    // since users will not "upgrade" to a version of Google Keyboard that 
    // is LOWER than the one they got installed with their version of Android 
    // in the first place, and the bug will have been fixed as of the 4.4 release.
    //
    // The above scope of the bug is reflected in the test below, which limits 
    // the application of the workaround to Android versions between 4.0.x and 4.3.x.
    //
    //UPDATE:  A popular third party keyboard was found that exhibits this same issue.  It
    // was not fixed at the same time as the Google Play keyboard, and so the bug in that case
    // is still in place beyond API LEVEL 19.  So, even though the Google Keyboard fixed this
    // as of level 19, we cannot take out the fix based on that version number.  And so I've
    // removed the test for an upper limit on the version; the fix will remain in place ad
    // infinitum - but only when TYPE_NULL is used, so it *should* be harmless even when
    // the keyboard does not have the problem...
    if((Build.VERSION.SDK_INT >= 14) // && (Build.VERSION.SDK_INT < 19) 
      && (beforeLength == 1 && afterLength == 0)) {
      //Send Backspace key down and up events to replace the ones omitted 
      // by the LatinIME keyboard.
      return super.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL))
        && super.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL));
    }
    else {
      //Really, I can't see how this would be invoked, given that we're using 
      // TYPE_NULL, for non-buggy versions, but in order to limit the impact 
      // of this change as much as possible (i.e., to versions at and above 4.0) 
      // I am using the original behavior here for non-affected versions.
      return super.deleteSurroundingText(beforeLength, afterLength);
    }
  }
}

Ensuite, prenez chaque classe dérivée de View qui doit recevoir des événements clés du clavier virtuel LatinIME et modifiez-la comme suit:

Tout d'abord, créez une substitution à onCreateInputConnection () dans la vue qui doit recevoir les événements clés comme suit:

 @Override
 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
  //Passing FALSE as the SECOND ARGUMENT (fullEditor) to the constructor 
  // will result in the key events continuing to be passed in to this 
  // view.  Use our special BaseInputConnection-derived view
  InputConnectionAccomodatingLatinIMETypeNullIssues baseInputConnection = 
    new InputConnectionAccomodatingLatinIMETypeNullIssues(this, false);

   //In some cases an IME may be able to display an arbitrary label for a 
   // command the user can perform, which you can specify here.  A null value
   // here asks for the default for this key, which is usually something 
   // like Done.
   outAttrs.actionLabel = null;

   //Special content type for when no explicit type has been specified. 
   // This should be interpreted (by the IME that invoked 
   // onCreateInputConnection())to mean that the target InputConnection 
   // is not rich, it can not process and show things like candidate text 
   // nor retrieve the current text, so the input method will need to run 
   // in a limited "generate key events" mode.  This disables the more 
   // sophisticated kinds of editing that use a text buffer.
   outAttrs.inputType = InputType.TYPE_NULL;

   //This creates a Done key on the IME keyboard if you need one
   outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE;

   return baseInputConnection;
}

Ensuite, apportez les modifications suivantes à votre gestionnaire onKey () pour la vue:

 this.setOnKeyListener(new OnKeyListener() {   
   @Override   public
   boolean onKey(View v, int keyCode, KeyEvent event) {
     if(event.getAction() != KeyEvent.ACTION_DOWN) {
       //We only look at ACTION_DOWN in this code, assuming that ACTION_UP is redundant.  
       // If not, adjust accordingly.
       return false;
     }
     else if(event.getUnicodeChar() == 
       (int)EditableAccomodatingLatinIMETypeNullIssues.ONE_UNPROCESSED_CHARACTER.charAt(0))
     {
       //We are ignoring this character, and we want everyone else to ignore it, too, so 
       // we return true indicating that we have handled it (by ignoring it).   
       return true; 
     }

     //Now, just do your event handling as usual...
     if(keyCode == KeyEvent.KEYCODE_ENTER) {
       //Trap the Done key and close the keyboard if it is pressed (if that's what you want to do)
       InputMethodManager imm = (InputMethodManager)
         mainActivity.getSystemService(Context.INPUT_METHOD_SERVICE));
       imm.hideSoftInputFromWindow(LetterRack.this.getWindowToken(), 0);
       return true;
     }
     else if(keyCode == KeyEvent.KEYCODE_DEL) {
       //Backspace key processing goes here...                      
       return true;
     }
     else if((keyCode >= KeyEvent.KEYCODE_A) && (keyCode <= KeyEvent.KEYCODE_Z)) {
       //(Or, use event.getUnicodeChar() if preferable to key codes).
       //Letter processing goes here...
       return true;
     }
     //Etc.   } };

Enfin, nous devons définir une classe pour notre modifiable qui garantit qu'il y a toujours au moins un caractère dans notre tampon modifiable:

import Android.text.SpannableStringBuilder;

public class EditableAccomodatingLatinIMETypeNullIssues extends SpannableStringBuilder {
  EditableAccomodatingLatinIMETypeNullIssues(CharSequence source) {
    super(source);
  }

  //This character must be ignored by your onKey() code.    
  public static CharSequence ONE_UNPROCESSED_CHARACTER = "/";

  @Override
  public SpannableStringBuilder replace(final int 
    spannableStringStart, final int spannableStringEnd, CharSequence replacementSequence, 
    int replacementStart, int replacementEnd) {
    if (replacementEnd > replacementStart) {
      //In this case, there is something in the replacementSequence that the IME 
      // is attempting to replace part of the editable with.
      //We don't really care about whatever might already be in the editable; 
      // we only care about making sure that SOMETHING ends up in it,
      // so that the backspace key will continue to work.
      // So, start by zeroing out whatever is there to begin with.
      super.replace(0, length(), "", 0, 0);

      //We DO care about preserving the new stuff that is replacing the stuff in the 
      // editable, because this stuff might be sent to us as a keydown event.  So, we 
      // insert the new stuff (typically, a single character) into the now-empty editable, 
      // and return the result to the caller.
      return super.replace(0, 0, replacementSequence, replacementStart, replacementEnd);
    }
    else if (spannableStringEnd > spannableStringStart) {
      //In this case, there is NOTHING in the replacementSequence, and something is 
      // being replaced in the editable.
      // This is characteristic of a DELETION.
      // So, start by zeroing out whatever is being replaced in the editable.
      super.replace(0, length(), "", 0, 0);

      //And now, we will place our ONE_UNPROCESSED_CHARACTER into the editable buffer, and return it. 
      return super.replace(0, 0, ONE_UNPROCESSED_CHARACTER, 0, 1);
    }

    // In this case, NOTHING is being replaced in the editable.  This code assumes that there 
    // is already something there.  This assumption is probably OK because in our 
    // InputConnectionAccomodatingLatinIMETypeNullIssues.getEditable() method 
    // we PLACE a ONE_UNPROCESSED_CHARACTER into the newly-created buffer.  So if there 
    // is nothing replacing the identified part
    // of the editable, and no part of the editable that is being replaced, then we just 
    // leave whatever is in the editable ALONE,
    // and we can be confident that there will be SOMETHING there.  This call to super.replace() 
    // in that case will be a no-op, except
    // for the value it returns.
    return super.replace(spannableStringStart, spannableStringEnd, 
      replacementSequence, replacementStart, replacementEnd);
   }
 }

Cela termine les modifications de source que j'ai trouvées semblent gérer les deux problèmes.

REMARQUES SUPPLÉMENTAIRES :

Le problème décrit par le problème 42904 a été introduit dans la version LatinIME livrée avec le niveau d'API 16. Auparavant, les événements KEYCODE_DEL étaient générés, que TYPE_NULL ait été utilisé ou non. Dans le LatinIME publié avec Jelly bean, cette génération a été interrompue, mais aucune exception n'a été faite pour TYPE_NULL, et donc le comportement TYPE_NULL a été effectivement désactivé pour les applications ciblées au-dessus du niveau 16 de l'API. Cependant, un code de compatibilité a été ajouté qui permettait aux applications qui avaient un targetSdkVersion <16 pour continuer à recevoir les événements KEYCODE_DEL, même sans TYPE_NULL. Voir cet engagement AOSP à la ligne 1493:

https://Android.googlesource.com/platform/packages/inputmethods/LatinIME/+/Android-4.1.1_r1/Java/src/com/Android/inputmethod/latin/LatinIME.Java

Par conséquent, vous pouvez contourner ce problème en définissant targetSdkVersion dans votre application sur 15 ou moins.

Depuis la validation 4.4_r0.9 (juste avant la version 4.4), ce problème a été résolu en ajoutant un test pour isTypeNull () aux conditions protégeant la génération KEYCODE_DEL. Malheureusement, un nouveau bogue (62306) a été introduit exactement à ce point, ce qui a fait sauter la génération KEYCODE_DEL de la clause entière si l'utilisateur a tapé le retour arrière autant de fois qu'il a tapé d'autres caractères. Cela a conduit à un échec de génération de KEYCODE_DEL dans ces circonstances, même avec TYPE_NULL, et même avec targetSdkVersion <= 15. Cela a provoqué des applications qui avaient précédemment pu obtenir un comportement KEYCODE_DEL correct via un code de compatibilité (targetSdkVersion <= 15) pour faire soudainement l'expérience problème lorsque les utilisateurs mettaient à jour leurs copies de Google Keyboard (ou effectuaient un OTA contenant une nouvelle version de Google Keyboard). Voir ce fichier git AOSP à la ligne 2146 (la clause incluant "NOT_A_CODE"):

https://Android.googlesource.com/platform/packages/inputmethods/LatinIME/+/Android-4.4_r0.9/Java/src/com/Android/inputmethod/latin/LatinIME.Java

Ce problème a persisté dans les versions publiées de Google Keyboard jusqu'à aujourd'hui (1/7/2014). Il a été corrigé dans le dépôt, mais à ce jour, il n'a pas été publié.

Ce commit non libéré peut être trouvé ici (le commit git contenant ceci fusionne un commit intitulé "Send backspace as an event when TYPE_NULL"), à la ligne 2110 (vous pouvez voir que la clause "NOT_A_CODE" qui nous empêchait d'atteindre la clause qui génère KEYCODE_DEL a été supprimé):

https://Android.googlesource.com/platform/packages/inputmethods/LatinIME/+/b41bea65502ce7339665859d3c2c81b4a29194e4/Java/src/com/Android/inputmethod/latin/LatinIME.Java

Lorsque ce correctif sera publié, cette version du clavier Google n'aura plus aucun de ces deux problèmes affectant TYPE_NULL. Cependant, il y aura toujours des versions plus anciennes installées sur des appareils particuliers pour un avenir indéfini. Par conséquent, le problème nécessitera toujours une solution de contournement. Finalement, à mesure que davantage de personnes passeront à un niveau supérieur au dernier sans le correctif, cette solution de contournement sera de moins en moins nécessaire. Mais il est déjà défini pour se retirer progressivement (une fois que vous avez apporté les modifications indiquées pour mettre la limite finale sur la portée, lorsque le correctif final a été publié afin que vous sachiez ce qu'il est réellement).

51
Carl

Ressemble à un bug avec Android:

Problème 42904 : l'événement KEYCODE_DEL n'est pas remis à EditText dans le SDK 16 et supérieur.

problème 42904 @ code.google.com

20
Umair

INTRODUCTION:

Après avoir testé les solutions @ Carl et @ Turix, j'ai remarqué que:

  1. La solution de Carl ne fonctionne pas bien avec les caractères ou séquences de caractères Unicode, car ceux-ci semblent être fournis avec l'événement ACTION_MULTIPLE, ce qui rend difficile la distinction entre les caractères "factices" et le caractère réel.

  2. Je n'ai pas pu faire fonctionner deleteSurroundingText dans la dernière version de Android sur mon Nexus 5 (4.4.2). J'ai testé le ciblage de plusieurs versions de sdk différentes, mais aucune d'entre elles Peut-être que Google a décidé de changer encore une fois la logique derrière la touche DEL ...

Par conséquent, j'ai trouvé la solution combinée suivante, en utilisant les réponses de Carl et de Turix. Ma solution fonctionne en combinant l'idée de Carl d'un long préfixe de caractère factice pour faire fonctionner DEL, mais en utilisant la solution de Turix pour un Editable personnalisé pour générer des événements clés appropriés.

RÉSULTATS:

J'ai testé cette solution sur plusieurs appareils avec différentes versions de Android et différents claviers. Tous les cas de test ci-dessous fonctionnent pour moi. Je n'ai pas trouvé de cas où cette solution ne fonctionne pas.

  • Nexus 5 (4.4.2) avec clavier Google standard
  • Nexus 5 (4.4.2) avec SwiftKey
  • HTC One (4.2.2) avec clavier HTC standard
  • Nexus One (2.3.6) avec clavier Google standard
  • Samsung Galaxy S3 (4.1.2) avec clavier Samsung standard

J'ai également testé le ciblage de différentes versions de sdk:

  • Cible 16
  • Cible 19

Si cette solution fonctionne également pour vous, alors ple

LA VUE:

public class MyInputView extends EditText implements View.OnKeyListener {
    private String DUMMY;

    ...

    public MyInputView(Context context) {
        super(context);
        init(context);
    }

    private void init(Context context) {
        this.context = context;
        this.setOnKeyListener(this);

        // Generate a dummy buffer string
        // Make longer or shorter as desired.
        DUMMY = "";
        for (int i = 0; i < 1000; i++)
            DUMMY += "\0";
    }

    @Override
    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
        MyInputConnection ic = new MyInputConnection(this, false);
        outAttrs.inputType = InputType.TYPE_NULL;
        return ic;
    }

    @Override
    public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
        int action = keyEvent.getAction();

        // Catch unicode characters (even character sequeneces)
        // But make sure we aren't catching the dummy buffer.
        if (action == KeyEvent.ACTION_MULTIPLE) {
            String s = keyEvent.getCharacters();
            if (!s.equals(DUMMY)) {
                listener.onSend(s);
            }
        }

        // Catch key presses...
        if (action == KeyEvent.ACTION_DOWN) {
            switch (keyCode) {
                case KeyEvent.KEYCODE_DEL:
                    ...
                    break;
                case KeyEvent.KEYCODE_ENTER:
                    ...
                    break;
                case KeyEvent.KEYCODE_TAB:
                    ...
                    break;
                default:
                    char ch = (char)keyEvent.getUnicodeChar();
                    if (ch != '\0') {
                        ...
                    }
                    break;
            }
        }

        return false;
    }
}

LA CONNEXION D'ENTRÉE:

public class MyInputConnection extends BaseInputConnection {
    private MyEditable mEditable;

    public MyInputConnection(View targetView, boolean fullEditor) {
        super(targetView, fullEditor);
    }

    private class MyEditable extends SpannableStringBuilder {
        MyEditable(CharSequence source) {
            super(source);
        }

        @Override
        public SpannableStringBuilder replace(final int start, final int end, CharSequence tb, int tbstart, int tbend) {
            if (tbend > tbstart) {
                super.replace(0, length(), "", 0, 0);
                return super.replace(0, 0, tb, tbstart, tbend);
            }
            else if (end > start) {
                super.replace(0, length(), "", 0, 0);
                return super.replace(0, 0, DUMMY, 0, DUMMY.length());
            }
            return super.replace(start, end, tb, tbstart, tbend);
        }
    }

    @Override
    public Editable getEditable() {
        if (Build.VERSION.SDK_INT < 14)
            return super.getEditable();
        if (mEditable == null) {
            mEditable = this.new MyEditable(DUMMY);
            Selection.setSelection(mEditable, DUMMY.length());
        }
        else if (mEditable.length() == 0) {
            mEditable.append(DUMMY);
            Selection.setSelection(mEditable, DUMMY.length());
        }
        return mEditable;
    }

    @Override
    public boolean deleteSurroundingText(int beforeLength, int afterLength) {
        // Not called in latest Android version...
        return super.deleteSurroundingText(beforeLength, afterLength);
    }
}
5
pqvst

(Cette réponse est conçue comme un addendum à la réponse acceptée publiée ici par Carl.)

Bien que très reconnaissant de la recherche et de la compréhension des deux bogues, j'ai eu quelques problèmes avec la solution de contournement publiée ici par Carl. Le problème principal que j'avais était que, bien que le bloc de commentaires de Carl indique que le chemin KeyEvent.ACTION_MULTIPLE Dans onKey() ne serait pris que "le premier événement reçu après avoir sélectionné le casier à lettres", pour moi, chaque événement clé a pris cette voie. (J'ai découvert en regardant le code BaseInputConnection.Java Pour l'API-niveau-18 que c'est parce que tout le texte Editable est utilisé à chaque fois dans sendCurrentText(). Je ne suis pas vous savez pourquoi cela a fonctionné pour Carl mais pas pour moi.)

Alors, inspiré par la solution de Carl, je l'ai adaptée pour ne pas avoir ce problème. Ma solution au problème 62306 (lié à la réponse de Carl) tente d'obtenir le même effet de base de "tromper" l'IME en pensant qu'il y a toujours plus de texte qui peut être reculé. Cependant, il le fait en s'assurant que le modifiable contient exactement un caractère. Pour ce faire, vous devez étendre la classe sous-jacente qui implémente l'interface Editable, SpannedStringBuilder, d'une manière similaire à la suivante:

    private class MyEditable extends SpannableStringBuilder
    {
        MyEditable(CharSequence source) {
            super(source);
        }

        @Override
        public SpannableStringBuilder replace(final int start, final int end, CharSequence tb, int tbstart, int tbend) {
            if (tbend > tbstart) {
                super.replace(0, length(), "", 0, 0);
                return super.replace(0, 0, tb, tbstart, tbend);
            }
            else if (end > start) {
                super.replace(0, length(), "", 0, 0);
                return super.replace(0, 0, DUMMY_CHAR, 0, 1);
            }
            return super.replace(start, end, tb, tbstart, tbend); 
        }
    }

Fondamentalement, chaque fois que l'IME tente d'ajouter un caractère à l'élément modifiable (en appelant replace()), ce caractère remplace le caractère singleton qui s'y trouve. Pendant ce temps, si l'IME tente de supprimer ce qui est là, la substitution replace() remplace à la place ce qui est là par un caractère "factice" singleton (qui devrait être quelque chose que votre application ignorera) pour maintenir la longueur de 1.

Cela signifie que les implémentations de getEditable() et onKey() peuvent être légèrement plus simples que ce que Carl a posté ci-dessus. Par exemple, en supposant que la classe MyEditable ci-dessus est implémentée en tant que classe interne, getEditable() devient quelque chose comme:

    @Override
    public Editable getEditable() { 
        if (Build.VERSION.SDK_INT < 14)
            return super.getEditable();
        if (mEditable == null) {
            mEditable = this.new MyEditable(DUMMY_CHAR);
            Selection.setSelection(mEditable, 1);
        }
        else if (m_editable.length() == 0) {
            mEditable.append(DUMMY_CHAR);
            Selection.setSelection(mEditable, 1);
        }
        return mEditable;
    } 

Notez qu'avec cette solution, il n'est pas nécessaire de conserver une chaîne longue de 1024 caractères. Il n'y a pas non plus de danger de "backspacing too much" (comme discuté dans les commentaires de Carl sur le maintien de la touche de retour arrière).

Pour être complet, onKey() devient quelque chose comme:

    @Override
    public boolean onKey(View v, int keyCode, KeyEvent event)
    {
        if (event.getAction() != KeyEvent.ACTION_DOWN)
            return false;

        if ((int)DUMMY_CHAR.charAt(0) == event.getUnicodeChar())
            return true;

        // Handle event/keyCode here as normal...
    }

Enfin, je dois noter que tout ce qui précède est conçu comme une solution de contournement pour émettre 62306 niquement. Je n'ai eu aucun problème avec la solution à l'autre problème, 42904, tel que publié par Carl (remplaçant deleteSurroundingText()) et je recommanderais de l'utiliser comme il l'a publié.

3
Turix

Grâce aux pensées de @ Carl, je suis parvenu à une solution qui fonctionne correctement pour tout type d'entrée. Ci-dessous, je donne un exemple d'application complet comprenant 2 classes: MainActivity et CustomEditText:

package com.example.edittextbackspace;

import Android.app.Activity;
import Android.os.Bundle;
import Android.text.InputType;
import Android.view.ViewGroup.LayoutParams;

public class MainActivity extends Activity
{
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);

        CustomEditText edittext = initEditText();
        setContentView(edittext);
    }

    private CustomEditText initEditText()
    {
        CustomEditText editText = new CustomEditText(this)
        {
            @Override
            public void backSpaceProcessed()
            {
                super.backSpaceProcessed();
                editTextBackSpaceProcessed(this);
            }
        };

        editText.setInputType(InputType.TYPE_CLASS_NUMBER);
        editText.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
        editText.setText("1212");

        return editText;
    }

    private void editTextBackSpaceProcessed(CustomEditText customEditText)
    {
        // Backspace event is called and properly processed
    }
}

package com.example.edittextbackspace;   

import Android.content.Context;
import Android.text.Editable;
import Android.text.Selection;
import Android.text.TextWatcher;
import Android.util.Log;
import Android.view.KeyEvent;
import Android.view.View;
import Android.view.inputmethod.BaseInputConnection;
import Android.view.inputmethod.CorrectionInfo;
import Android.view.inputmethod.EditorInfo;
import Android.view.inputmethod.InputConnection;
import Android.widget.EditText;

import Java.util.ArrayList;
import Java.util.List;

public class CustomEditText extends EditText implements View.OnFocusChangeListener, TextWatcher
{
    private String              LOG                     = this.getClass().getName();
    private int                 _inputType              = 0;
    private int                 _imeOptions             = 5 | EditorInfo.IME_FLAG_NO_EXTRACT_UI;
    private List<String>        _lastComposingTextsList = new ArrayList<String>();
    private BaseInputConnection _inputConnection        = null;
    private String              _lastComposingText      = "";
    private boolean             _commitText             = true;
    private int                 _lastCursorPosition     = 0;
    private boolean             _isComposing            = false;
    private boolean             _characterRemoved       = false;
    private boolean             _isTextComposable       = false;

    public CustomEditText(Context context)
    {
        super(context);
        setOnFocusChangeListener(this);
        addTextChangedListener(this);
    }

    @Override
    public InputConnection onCreateInputConnection(final EditorInfo outAttrs)
    {
        CustomEditText.this._inputConnection = new BaseInputConnection(this, false)
        {
            @Override
            public boolean deleteSurroundingText(int beforeLength, int afterLength)
            {
                handleEditTextDeleteEvent();
                return super.deleteSurroundingText(beforeLength, afterLength);
            }

            @Override
            public boolean setComposingText(CharSequence text, int newCursorPosition)
            {
                CustomEditText.this._isTextComposable = true;
                CustomEditText.this._lastCursorPosition = getSelectionEnd();

                CustomEditText.this._isComposing = true;

                if (text.toString().equals(CustomEditText.this._lastComposingText))
                    return true;
                else
                    CustomEditText.this._commitText = true;

                if (text.length() < CustomEditText.this._lastComposingText.length())
                {
                    CustomEditText.this._lastComposingText = text.toString();

                    try
                    {
                        if (text.length() > 0)
                        {
                            if (CustomEditText.this._lastComposingTextsList.size() > 0)
                            {
                                if (CustomEditText.this._lastComposingTextsList.size() > 0)
                                {
                                    CustomEditText.this._lastComposingTextsList.remove(CustomEditText.this._lastComposingTextsList.size() - 1);
                                }
                            }
                            else
                            {
                                CustomEditText.this._lastComposingTextsList.add(text.toString().substring(0, text.length() - 1));
                            }
                        }
                        int start = Math.max(getSelectionStart(), 0) - 1;
                        int end = Math.max(getSelectionEnd(), 0);

                        CustomEditText.this._characterRemoved = true;

                        getText().replace(Math.min(start, end), Math.max(start, end), "");

                    }
                    catch (Exception e)
                    {
                        Log.e(LOG, "Exception in setComposingText: " + e.toString());
                    }
                    return true;
                }
                else
                {
                    CustomEditText.this._characterRemoved = false;
                }

                if (text.length() > 0)
                {
                    CustomEditText.this._lastComposingText = text.toString();

                    String textToInsert = Character.toString(text.charAt(text.length() - 1));

                    int start = Math.max(getSelectionStart(), 0);
                    int end = Math.max(getSelectionEnd(), 0);

                    CustomEditText.this._lastCursorPosition++;
                    getText().replace(Math.min(start, end), Math.max(start, end), textToInsert);

                    CustomEditText.this._lastComposingTextsList.add(text.toString());
                }
                return super.setComposingText("", newCursorPosition);
            }

            @Override
            public boolean commitText(CharSequence text, int newCursorPosition)
            {
                CustomEditText.this._isComposing = false;
                CustomEditText.this._lastComposingText = "";
                if (!CustomEditText.this._commitText)
                {
                    CustomEditText.this._lastComposingTextsList.clear();
                    return true;
                }

                if (text.toString().length() > 0)
                {
                    try
                    {
                        String stringToReplace = "";
                        int cursorPosition = Math.max(getSelectionStart(), 0);

                        if (CustomEditText.this._lastComposingTextsList.size() > 1)
                        {
                            if (text.toString().trim().isEmpty())
                            {
                                getText().replace(cursorPosition, cursorPosition, " ");
                            }
                            else
                            {
                                stringToReplace = CustomEditText.this._lastComposingTextsList.get(CustomEditText.this._lastComposingTextsList.size() - 2) + text.charAt(text.length() - 1);
                                getText().replace(cursorPosition - stringToReplace.length(), cursorPosition, text);
                            }
                            CustomEditText.this._lastComposingTextsList.clear();
                            return true;
                        }
                        else if (CustomEditText.this._lastComposingTextsList.size() == 1)
                        {
                            getText().replace(cursorPosition - 1, cursorPosition, text);
                            CustomEditText.this._lastComposingTextsList.clear();
                            return true;
                        }
                    }
                    catch (Exception e)
                    {
                        Log.e(LOG, "Exception in commitText: " + e.toString());
                    }
                }
                else
                {
                    if (!getText().toString().isEmpty())
                    {
                        int cursorPosition = Math.max(getSelectionStart(), 0);
                        CustomEditText.this._lastCursorPosition = cursorPosition - 1;
                        getText().replace(cursorPosition - 1, cursorPosition, text);

                        if (CustomEditText.this._lastComposingTextsList.size() > 0)
                        {
                            CustomEditText.this._lastComposingTextsList.remove(CustomEditText.this._lastComposingTextsList.size() - 1);
                        }

                        return true;
                    }
                }

                return super.commitText(text, newCursorPosition);
            }

            @Override
            public boolean sendKeyEvent(KeyEvent event)
            {
                int keyCode = event.getKeyCode();
                CustomEditText.this._lastComposingTextsList.clear();

                if (keyCode > 60 && keyCode < 68 || !CustomEditText.this._isTextComposable || (CustomEditText.this._lastComposingTextsList != null && CustomEditText.this._lastComposingTextsList.size() == 0))
                {
                    return super.sendKeyEvent(event);
                }
                else
                    return false;

            }

            @Override
            public boolean finishComposingText()
            {
                if (CustomEditText.this._lastComposingTextsList != null && CustomEditText.this._lastComposingTextsList.size() > 0)
                    CustomEditText.this._lastComposingTextsList.clear();

                CustomEditText.this._isComposing = true;
                CustomEditText.this._commitText = true;

                return super.finishComposingText();
            }

            @Override
            public boolean commitCorrection(CorrectionInfo correctionInfo)
            {
                CustomEditText.this._commitText = false;
                return super.commitCorrection(correctionInfo);
            }
        };

        outAttrs.actionLabel = null;
        outAttrs.inputType = this._inputType;
        outAttrs.imeOptions = this._imeOptions;

        return CustomEditText.this._inputConnection;
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent keyEvent)
    {
        if (keyCode == KeyEvent.KEYCODE_DEL)
        {
            int cursorPosition = this.getSelectionEnd() - 1;

            if (cursorPosition < 0)
            {
                removeAll();
            }
        }

        return super.onKeyDown(keyCode, keyEvent);
    }

    @Override
    public void setInputType(int type)
    {
        CustomEditText.this._isTextComposable = false;
        this._inputType = type;
        super.setInputType(type);
    }

    @Override
    public void setImeOptions(int imeOptions)
    {
        this._imeOptions = imeOptions | EditorInfo.IME_FLAG_NO_EXTRACT_UI;
        super.setImeOptions(this._imeOptions);
    }

    public void handleEditTextDeleteEvent()
    {
        int end = Math.max(getSelectionEnd(), 0);

        if (end - 1 >= 0)
        {
            removeChar();
            backSpaceProcessed();
        }
        else
        {
            removeAll();
        }
    }

    private void removeAll()
    {
        int startSelection = this.getSelectionStart();
        int endSelection = this.getSelectionEnd();

        if (endSelection - startSelection > 0)
            this.setText("");
        else
            nothingRemoved();
    }

    private void removeChar()
    {
        KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL);
        super.onKeyDown(event.getKeyCode(), event);
    }

    public void nothingRemoved()
    {
        // Backspace didn't remove anything. It means, a cursor of the editText was in the first position. We can use this method, for example, to switch focus to a previous view
    }

    public void backSpaceProcessed()
    {
        // Backspace is properly processed
    }

    @Override
    protected void onSelectionChanged(int selStart, int selEnd)
    {
        if (CustomEditText.this._isComposing)
        {
            int startSelection = this.getSelectionStart();
            int endSelection = this.getSelectionEnd();

            if (((CustomEditText.this._lastCursorPosition != selEnd && !CustomEditText.this._characterRemoved) || (!CustomEditText.this._characterRemoved && CustomEditText.this._lastCursorPosition != selEnd)) || Math.abs(CustomEditText.this._lastCursorPosition - selEnd) > 1 || Math.abs(endSelection - startSelection) > 1)
            {
                // clean autoprediction words
                CustomEditText.this._lastComposingText = "";
                CustomEditText.this._lastComposingTextsList.clear();
                CustomEditText.super.setInputType(CustomEditText.this._inputType);
            }
        }
    }

    @Override
    public void onFocusChange(View v, boolean hasFocus)
    {
        if (!hasFocus) {
            CustomEditText.this._lastComposingText = "";
            CustomEditText.this._lastComposingTextsList.clear();
        }
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after)
    {
        int startSelection = getSelectionStart();
        int endSelection = getSelectionEnd();
        if (Math.abs(endSelection - startSelection) > 0)
        {
            Selection.setSelection(getText(), endSelection);
        }
    }

    @Override
    public void afterTextChanged(Editable s)
    {

    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count)
    {
        super.onTextChanged(s, start, before, count);
    }
}

PDATE: J'ai mis à jour le code, car il ne fonctionnait pas correctement lorsque Text Prediction est activé sur certains appareils comme le Samsung Galaxy S6 (merci @Jonas qu'il a informé de ce problème dans le commentaire ci-dessous) et en utilisant InputType .TYPE_TEXT_FLAG_NO_SUGGESTIONS n'aide pas dans ce cas. J'ai testé cette solution sur de nombreux appareils, mais je ne sais toujours pas si elle fonctionne correctement pour tous. J'espère que j'obtiendrai quelques critiques de votre part en cas de comportement inapproprié de l'EditText.

3
Ayaz Alifov

J'ai rencontré des problèmes similaires où KEYCODE_DEL n'était pas reçu au toucher de la touche de retour arrière. Cela dépend du clavier Soft Input, je pense, car mon problème ne se produisait que dans le cas de certains claviers tiers (swype je pense) et non avec le clavier Google par défaut.

3
Ayush Chaturvedi

Je pense que vous pouvez constater que vous pouvez intercepter la clé si vous remplacez la méthode dispatchKeyEvent de la vue/activité appropriée (dans mon cas, l'activité principale était correcte).

Par exemple, je développe une application pour un appareil doté de touches de défilement matériel, et j'ai été surpris de découvrir que les méthodes onKeyUp/onKeyDown ne sont jamais appelées à leur place. Au lieu de cela, par défaut, la pression de touche passe par un tas de dispatchKeyEvents jusqu'à ce qu'elle invoque une méthode de défilement quelque part (dans mon cas, bizarrement, une pression de touche appelle une méthode de défilement sur chacune des deux vues déroulantes distinctes - comment énervant).

2
Merk

Que faire si vous avez vérifié comme le nombre décimal pour le caractère de retour arrière?

Je pense que c'est comme '/ r' (nombre décimal 7) ou quelque chose, au moins pour ASCII.

EDIT: Je suppose que Android utilise UTF-8, donc ce nombre décimal serait 8. http://www.fileformat.info/info/unicode/char/0008/index .htm

0
Synergy807