Android - не может захватывать нажатие клавиши "backspace/delete" в мягкой форме. клавиатура

Я переопределяю метод onKeyDown представления (вид поверхности OpenGL) для захвата все нажатия клавиш. Проблема в том, что на нескольких устройствах KEYCODE_DEL не захватили. Я попытался добавить onKeyListener в представление и захватил все, кроме ключевого слова backspace.

Должен быть способ прослушать это событие нажатия клавиши, но как?

Ответ 1

  11/12/2014 ОБНОВЛЕНИЕ: Изменена область исправления, чтобы не ограничивать & lt; Уровень API 19, поскольку на сторонней клавиатуре ошибка по-прежнему превышает 19.

1/9/2014 ОБНОВЛЕНИЕ: Я разработал подход с кодом для решения всех проблем Google Keyboard (LatinIME) KEYCODE_DEL, в частности выпуски 42904 и 62306.

Усовершенствование ответа Turix было включено, с разрешения, в мой собственный код здесь. В удаленных улучшениях Turix необходимо вводить мусорные символы в редактируемый буфер, вместо этого находя инкрементный способ, гарантирующий, что в этом буфере всегда находится только один символ.

Я использовал (похожий) код для этого в развернутом приложении, которое вы можете протестировать:
https://play.google.com/store/apps/details?id=com.goalstate.WordGames.FullBoard.trialsuite]

ВВЕДЕНИЕ:

Обходное решение, представленное ниже, предназначено для всех версий клавиатуры Google, как прошлых, так и будущих, в отношении этих двух ошибок. Этот обходной путь не требует, чтобы приложение оставалось застрявшим, ориентируясь на уровень API 15 или ниже, которым ограничивались некоторые приложения, чтобы воспользоваться преимуществами кода совместимости, который обходит проблему 42904.

Эти проблемы присутствуют только как ошибки для представления, которое реализовало переопределение для onCreateInputConnection() и которое возвращает TYPE_NULL вызывающему IME (в элементе inputType аргумента EditorInfo, переданного этому методу IME). Только делая это, представление может разумно ожидать, что ключевые события (включая KEYCODE_DEL) будут возвращены ему с программной клавиатуры. Следовательно, представленный здесь обходной путь требует TYPE_NULL InputType.

Для приложений, не использующих TYPE_NULL, существуют различные переопределения в производном от BaseInputConnection объекте, возвращаемом представлением из его переопределения onCreateInputConnection(), которые вызываются IME, когда пользователь выполняет редактирование, вместо того, чтобы IME генерировал ключевые события. Этот подход (не TYPE_NULL) обычно лучше, потому что возможности программной клавиатуры теперь выходят далеко за рамки простого нажатия клавиш, на такие вещи, как голосовой ввод, завершение и т.д. Ключевые события являются более старым методом, и те, кто реализует LatinIME в Google, заявили, что что они хотели бы, чтобы использование TYPE_NULL (и ключевые события) прекратилось.

Если прекращение использования TYPE_NULL является опцией, то я бы настоятельно рекомендовал вам продолжить рекомендуемый подход с использованием методов переопределения InputConnection вместо ключевых событий (или, проще, с помощью класса, производного от EditText, который делает это для вас).

Тем не менее, поведение TYPE_NULL официально не прекращается, и, таким образом, отказ LatinIME генерировать события KEYCODE_DEL при определенных обстоятельствах действительно является ошибкой. Я предлагаю следующий обходной путь для решения этой проблемы.

ОБЗОР:

Проблемы, с которыми сталкивались приложения при получении KEYCODE_DEL от LatinIME, связаны с ДВУМИ известными ошибками, о которых сообщалось здесь:

https://code.google.com/p/android/issues/detail?id=42904 (указано как WorkingAsIntended, но проблема, я утверждаю, является ошибкой, поскольку она приводит к сбою в поддержке генерации событий KEYCODE_DEL для приложений, ориентированных на API уровня 16 и выше, в которых конкретно указан InputType типа TYPE_NULL. Проблема исправлена в последней версии выпуски LatinIME, но есть и прошлые выпуски, которые все еще демонстрируют эту ошибку, и поэтому приложениям, использующим TYPE_NULL и предназначенным для уровня API 16 или выше, все еще потребуется обходной путь, который может быть выполнен из приложения.

и здесь:

http://code.google.com/p/android/issues/detail?id=62306 (в настоящее время перечислено как исправленное, но еще не выпущенное - FutureRelease - но даже после его выпуска нам все еще потребуется обходной путь, который можно выполнить из приложения, чтобы иметь дело с прошлыми выпусками, которые будут сохраняться "в дикой природе").

В соответствии с этим тезисом (что проблемы с событиями KEYCODE_DEL связаны с ошибками в LatinIME), я обнаружил, что при использовании внешней аппаратной клавиатуры, а также при использовании сторонней программной клавиатуры SwiftKey эти проблемы не возникают, пока они действительно происходят для определенных версий LatinIME.

Одна или другая (но не обе сразу) из этих проблем присутствует в некоторых выпусках LatinIME. Следовательно, разработчикам во время тестирования трудно узнать, работали ли они над всеми проблемами KEYCODE_DEL, и иногда, когда выполняется обновление для Android (или клавиатуры Google), проблема больше не будет воспроизводиться при тестировании. Тем не менее версии LatinIME, вызывающие проблему, будут присутствовать на многих используемых устройствах. Это заставило меня покопаться в git-репозитории AOSP LatinIME, чтобы определить точную область действия каждой из двух проблем (то есть, конкретной LatinIME и Android, версии, для которых может присутствовать каждая из двух проблем). Приведенный ниже код обхода ограничен этими конкретными версиями.

Обходной код, представленный ниже, включает в себя обширные комментарии, которые должны помочь вам понять, чего он пытается достичь. После презентации кода я проведу дополнительное обсуждение, которое будет включать в себя конкретные коммиты Android Open Source Project (AOSP), при которых была представлена каждая из двух ошибок и при которых она исчезла, а также версии Android, которые могли бы включите затронутые выпуски Google Keyboard.

Я бы предупредил любого, кто думает об использовании этого подхода, чтобы провести собственное тестирование, чтобы убедиться, что он работает для их конкретного приложения. Я думаю, что он будет работать в целом, и проверил его на нескольких устройствах и версиях LatinIME, но рассуждения сложны, поэтому действуйте с осторожностью. Если вы обнаружите какие-либо проблемы, пожалуйста, оставьте комментарий ниже.

КОД:

Вот мой обходной путь для обеих проблем, с пояснениями в комментариях к коду:

Во-первых, включите следующий класс (отредактированный по вкусу) в свое приложение в его собственный исходный файл 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 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 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 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);
    }
  }
}

Затем возьмите каждый производный от View класс, который должен получать события клавиш, от программной клавиатуры LatinIME и отредактируйте его следующим образом:

Сначала создайте переопределение для onCreateInputConnection() в представлении, которое нужно получить ключевые события следующим образом:

 @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;
}

Во-вторых, внесите следующие изменения в свой обработчик onKey() для представления:

 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 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.   } };

Наконец, нам нужно определить класс для нашего редактируемого, который гарантирует, что всегда есть хотя бы один символ в нашем редактируемом буфере:

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);
   }
 }

Это завершает исходные изменения, которые, как я обнаружил, решают обе проблемы.

ДОПОЛНИТЕЛЬНЫЕ ЗАМЕЧАНИЯ:

Проблема, описанная в выпуске 42904, была представлена в версии LatinIME, поставляемой с уровнем API 16. До этого события KEYCODE_DEL генерировались независимо от того, использовался ли TYPE_NULL. В LatinIME, выпущенном с Jelly Bean, это поколение было прекращено, но не было сделано исключения для TYPE_NULL, и поэтому поведение TYPE_NULL было эффективно отключено для приложений, нацеленных выше уровня API 16. Однако был добавлен код совместимости, позволяющий приложениям, имеющим targetSdkVersion & lt; 16, чтобы продолжать получать события KEYCODE_DEL, даже без TYPE_NULL. Смотрите этот коммит AOSP в строке 1493:

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

Поэтому вы можете обойти эту проблему, установив targetSdkVersion в своем приложении на 15 или ниже.

В коммите 4.4_r0.9 (непосредственно перед выпуском 4.4) эта проблема была исправлена путем добавления теста isTypeNull() в условия, защищающие генерацию KEYCODE_DEL. К сожалению, именно в этот момент была введена новая ошибка (62306), из-за которой было пропущено все предложение обтекания поколения KEYCODE_DEL, если пользователь вводил клавишу Backspace столько раз, сколько он вводил другие символы. Это привело к ошибке генерации KEYCODE_DEL при таких обстоятельствах, даже с TYPE_NULL и даже с targetSdkVersion & lt; = 15. Это привело к тому, что приложения, которые ранее могли получить правильное поведение KEYCODE_DEL с помощью кода совместимости (targetSdkVersion & lt; = 15), неожиданно Эта проблема возникает, когда пользователи обновляют свои копии клавиатуры Google (или выполняют OTA, содержащую новую версию клавиатуры Google). Смотрите этот файл git AOSP в строке 2146 (пункт включает в себя "NOT_A_CODE"):

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

Эта проблема сохранялась в выпущенных версиях Google Keyboard до настоящего времени (07.01.2014). Это было исправлено в репо, но на момент написания статьи не было выпущено.

Этот невыпущенный коммит можно найти здесь (коммит git, содержащий этот коммит, объединяет коммит под названием "Отправить backspace как событие, когда TYPE_NULL"), в строке 2110 (вы можете видеть, что предложение "NOT_A_CODE", которое использовалось для предотвращения нашего достижения предложения, генерирует KEYCODE_DEL был удален):

https://android.googlesource.com/platform/packages/inputmethods/LatinIME/+/b41bea65502ce7339665859d3c2c81b4a29194e4/java/src/com/android/inputmethod/latin/LatinIME.java

Когда это исправление будет выпущено, эта версия клавиатуры Google больше не будет иметь ни одной из этих двух проблем, влияющих на TYPE_NULL. Тем не менее, в течение неопределенного времени на определенных устройствах будут установлены более старые версии. Следовательно, проблема все еще нуждается в обходном пути. В конце концов, по мере того, как все больше людей переходят на более высокий уровень, чем последний, не включая исправления, этот обходной путь будет требоваться все меньше и меньше. Но он уже предназначен для постепенного отказа от себя (как только вы внесете указанные изменения, чтобы установить окончательный предел для области, когда окончательное исправление будет действительно выпущено, чтобы вы знали, что это на самом деле).

Ответ 2

Похож на ошибку с Android:

Проблема 42904: Событие KEYCODE_DEL не передается в EditText в SDK 16 и выше.

Проблема 42904 @code.google.com

Ответ 3

Введение:

После тестирования решений @Carl и @Turix я заметил, что:

  • Решение Carl не работает с символами Unicode или символьными последовательностями, поскольку они, кажется, поставляются с событием ACTION_MULTIPLE, что затрудняет различие между символами "dummy" и фактическим символом.

  • Мне не удалось получить deleteSurroundingText в последней версии Android на моем Nexus 5 (4.4.2). Я тестировал таргетинг на несколько разных версий sdk, но никто из них не работал. Возможно, Google решил еще раз изменить логику клавиши DEL...

Поэтому я придумал следующее комбинированное решение, используя ответы Carl и Turix. Мое решение работает, объединяя идею Carl о ​​длинном фиктивном префиксе символов, чтобы заставить работать DEL, но используя решение Turix для пользовательского Editable для генерации правильных ключевых событий.

Результаты:

Я тестировал это решение на нескольких устройствах с различными версиями Android и разных клавиатур. Все приведенные ниже тестовые примеры работают для меня. Я не нашел случая, когда это решение не работает.

  • Nexus 5 (4.4.2) со стандартной клавиатурой Google.
  • Nexus 5 (4.4.2) с SwiftKey
  • HTC One (4.2.2) со стандартной клавиатурой HTC
  • Nexus One (2.3.6) со стандартной клавиатурой Google.
  • Samsung Galaxy S3 (4.1.2) со стандартной клавиатурой Samsung.

Я также тестировал таргетинг на различные версии sdk:

  • Цель 16
  • Цель 19

Если это решение также работает для вас, тогда ple

ПРОСМОТР:

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;
    }
}

ВХОДНОЕ СОЕДИНЕНИЕ:

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);
    }
}

Ответ 4

У меня возникли аналогичные проблемы, когда KEYCODE_DEL не получался при нажатии клавиши backspace. Я думаю, это зависит от клавиатуры Soft Input, потому что моя проблема происходила только в случае некоторых сторонних клавиатур (swype, я думаю), а не с клавиатуры Google по умолчанию.

Ответ 5

(Этот ответ подразумевается как добавление к принятому ответу, опубликованному здесь Карлом.)

Несмотря на высокую оценку исследований и понимания этих двух ошибок, у меня было несколько проблем с обходным решением, опубликованным здесь Карлом. Основная проблема, которую я имел, заключалась в том, что, хотя блок комментариев Carl говорит, что путь KeyEvent.ACTION_MULTIPLE в onKey() будет приниматься только на "первом событии, полученном после выбора буфера для писем", для меня каждое событие ключа принимало этот путь, (Я обнаружил, посмотрев код BaseInputConnection.java для уровня API-18-го уровня, потому что весь текст Editable используется в sendCurrentText() каждый раз. Я не уверен, почему это сработало для Карла, но не для меня. )

Итак, вдохновленный решением Carl, я адаптировал его, чтобы не иметь этой проблемы. Мое решение для выпуска 62306 (связанное с ответом Carl) пытается добиться того же основного эффекта "обманывания" IME, считая, что всегда есть больше текста, который может быть пересмотрен. Однако он делает это, убедившись, что Editable имеет в нем ровно один символ. Для этого вам необходимо расширить базовый класс, который реализует интерфейс Editable, SpannedStringBuilder, таким образом, как показано ниже:

    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); 
        }
    }

В принципе, всякий раз, когда IME пытается добавить символ к редактируемому (вызывая replace()), этот символ заменяет любой символ одиночного символа. Между тем, если IME пытается удалить то, что там, переопределение replace() вместо этого заменяет то, что там, с символом singleton "dummy" (который должен быть тем, что ваше приложение будет игнорировать), чтобы поддерживать длину 1.

Это означает, что реализации getEditable() и onKey() могут быть несколько проще, чем выведенный выше Carl. Например, предполагая, что класс MyEditable выше реализован как внутренний класс, getEditable() становится чем-то вроде:

    @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;
    } 

Обратите внимание, что с этим решением нет необходимости поддерживать длинную строку длиной 1024 символа. Кроме того, существует ли какая-либо опасность "слишком много назад" (как обсуждается в комментариях Карла об удержании клавиши backspace).

Для полноты, onKey() становится чем-то вроде:

    @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...
    }

Наконец, я должен отметить, что все вышеизложенное подразумевается как обходное решение для выпуска 62306. У меня не было проблем с решением другой проблемы, 42904, как опубликовано Carl (переопределение deleteSurroundingText()) и рекомендовал бы использовать его, когда он разместил его.

Ответ 6

Благодаря соображениям @Carl я пришел к решению, которое работает правильно для любого типа ввода. Ниже я даю полное рабочее примерное приложение, состоящее из 2 классов: MainActivity и 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);
    }
}

UPDATE: Я обновил код, потому что он не работал должным образом, когда Text Prediction включен на некоторых устройствах, таких как Samsung Galaxy S6 (спасибо @Jonas, что он сообщил об этой проблеме в комментарии ниже) и используя InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS в этом случае не поможет, Я тестировал это решение на множестве устройств, но все еще не уверен, что он работает правильно для всех. Надеюсь, что я получу некоторые отзывы от вас в случае неправильного поведения EditText.

Ответ 7

Я думаю, вы можете обнаружить, что вы можете перехватить ключ, если вы переопределите метод dispatchKeyEvent соответствующего вида/активности (в моем случае основное действие было прекрасным).

Например, я разрабатываю приложение для устройства с аппаратными клавишами прокрутки, и я с удивлением обнаружил, что методы onKeyUp/onKeyDown никогда не вызываются для них. Вместо этого по умолчанию нажатие клавиши проходит через группу dispatchKeyEvent, пока она не вызовет метод прокрутки где-нибудь (в моем случае, как ни странно, одно нажатие клавиши вызывает методы прокрутки на каждом из двух отдельных прокручиваемых представлений - как раздражает).

Ответ 8

Что делать, если вы отметили десятичное число для символа обратного пространства?

Я думаю, что это как '/r' (десятичное число 7) или что-то еще, по крайней мере для ASCII.

EDIT: Я предполагаю, что Android использует UTF-8, поэтому это десятичное число будет равно 8. http://www.fileformat.info/info/unicode/char/0008/index.htm

Ответ 9

Учитывая ответ Umair, вы можете рассмотреть применение обходного пути здесь:

Захватите событие касания, которое оно НЕ является ключевым событием и происходит вокруг нижней правой части экрана, пока отображается клавиатура.

Как получить положение Touch в андроиде?

Есть ли способ узнать, отображается ли мягкая клавиатура?

Надеюсь, что поможет

Ответ 10

InputFilter вызывается для backspace и если edittext пуст.

editText.setFilters(new InputFilter[]{new InputFilter() {
        @Override
        public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
            if(source.equals("")) {
                //a backspace was entered
            }

            return source;
        }
    }});

Ответ 11

Это старое сообщение и мои предложения в случае, если кто-то нуждается в супер быстрой взломе/реализации.

Простейшая работа, с которой я столкнулся, заключается в том, чтобы реализовать TextWatcher вместе с OnKeyListener и в onTextChanged сравнить с предыдущей существующей строкой, будет ли она уменьшена на один символ.

Преимущество этого заключается в том, что он работает на любом типе клавиатуры без длительного процесса кодирования.

Например, мой editText содержит только один символ, поэтому я сравнивал characterSequence, если это пустая строка, а затем мы можем подтвердить, что нажата клавиша Удалить.

Ниже приведен код, поясняющий то же самое:

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

    if(charSequence.toString().equals("")) //Compare here for any change in existing string by single character with previous string
    { 
        //Carry out your tasks here it comes in here when Delete Key is pressed.
    }
}

Примечание: В этом случае мой edittext содержит только один символ, поэтому я сравниваю charSequesnce с пустой строкой (так как нажатие delete сделает его пустым), для ваших нужд вам нужно изменить его и сравнить (Например, после нажатия клавиши подстрока является частью исходной строки) с существующей строкой. Надеюсь, что это поможет.