简体   繁体   English

Android-无法捕获软件中的退格键/删除键。 键盘

[英]Android - cannot capture backspace/delete press in soft. keyboard

I am overriding the onKeyDown method of the view (openGL surface view) to capture all of the key presses. 我覆盖了视图(openGL表面视图)的onKeyDown方法以捕获所有按键。 The problem is that on several devices the KEYCODE_DEL is not captured. 问题是在多个设备上没有捕获KEYCODE_DEL。 I have tried adding an onKeyListener to the view, and that captured everything except backspace key. 我尝试将onKeyListener添加到视图中,并且捕获了除Backspace键之外的所有内容。

There has to be a way to listen to this key press event, but how? 必须有一种方法来监听此按键事件,但是如何?

11/12/2014 UPDATE: Changed scope of fix to not limit to < API level 19, since at a third party keyboard still has the bug beyond 19. 2014年11月12日更新:已将修复范围更改为不限于<API级别19,因为第三方键盘上的错误仍超过19。

1/9/2014 UPDATE: I've devised an approach, with code, to resolve all Google Keyboard (LatinIME) KEYCODE_DEL problems, specifically issues 42904 and 62306. 2014年1月9日更新:我已经设计出一种带有代码的方法来解决所有Google键盘(LatinIME)KEYCODE_DEL问题,尤其是问题42904和62306。

The enhancement in Turix's answer has been incorporated, with permission, into my own code here . Turix的答案的增强功能已在获得许可的情况下并入我自己的代码中 Turix's improvements removed need to inject garbage characters into the Editable buffer by instead finding an incremental way to ensure that exactly one character was always in that buffer. Turix的改进消除了将垃圾字符注入Editable缓冲区的麻烦,而改为寻找一种增量方式来确保该缓冲区中始终有一个字符。

I've used (similar) code to this in a deployed app that you're welcome to test: 我在一个已部署的应用程序中使用了与此类似的代码,欢迎您进行测试:
https://play.google.com/store/apps/details?id=com.goalstate.WordGames.FullBoard.trialsuite] https://play.google.com/store/apps/details?id=com.goalstate.WordGames.FullBoard.trialsuite]

INTRODUCTION: 介绍:

The workaround presented below is intended to work for all versions of the Google Keyboard, both past and future, so far as these two bugs are concerned. 就这两个错误而言,下面介绍的解决方法旨在适用于过去和将来的所有版本的Google Keyboard。 This workaround does not require that an app remain stuck targeting API level 15 or below, which some apps have restricted themselves to in order to take advantage of compatibility code that gets around issue 42904. 这种解决方法并不需要一个应用程序仍然卡住定位API级别15或以下,其中一些应用程序,以便采取的围绕问题42904获取兼容性代码的优势有限制自己。

These problems are only present as bugs for a view that has implemented the override for onCreateInputConnection(), and which returns TYPE_NULL to the invoking IME (in the inputType member of the EditorInfo argument passed to that method by the IME). 这些问题仅作为已实现onCreateInputConnection()重写的视图的bug出现并且该视图将TYPE_NULL返回给调用的IME(在IME传递给该方法的EditorInfo参数的inputType成员中)。 It is only by doing this that a view can reasonably expect that key events (including KEYCODE_DEL) will be returned to it from a soft keyboard. 只有这样做,视图才能合理预期键事件(包括KEYCODE_DEL)将从软键盘返回给它。 Consequently, the workaround presented here requires the TYPE_NULL InputType. 因此,此处介绍的解决方法需要TYPE_NULL InputType。

For apps not using TYPE_NULL, there are various overrides in the BaseInputConnection-derived object returned by a view from its onCreateInputConnection() override, that are invoked by the IME when the user performs edits, instead of the IME generating key events. 对于不使用TYPE_NULL的应用,视图从其onCreateInputConnection()覆盖返回的BaseInputConnection派生对象中有各种覆盖,这些覆盖由IME在用户执行编辑时调用, 而不是由IME生成键事件。 This (non TYPE_NULL) approach is usually superior, because the soft keyboard's capabilities now extend far beyond the mere tapping of keys, to things like voice input, completion, etc. Key events are an older method, and those implementing LatinIME at Google have said that they would like to see the use of TYPE_NULL (and key events) go away. 这种(非TYPE_NULL)方法通常是更好的方法,因为软键盘的功能现在已经远远超出了仅敲击按键的范围,扩展到了语音输入,完成等功能。按键事件是一种较旧的方法,谷歌实现LatinIME的人表示他们希望看到TYPE_NULL(和键事件)的使用消失了。

If discontinuing the use of TYPE_NULL is an option, then I would urge you to proceed with the recommended approach of using the InputConnection override methods instead of key events (or, more simply, by using a class derived from EditText, which does that for you). 如果可以选择不使用TYPE_NULL,那么我建议您继续使用建议的方法,即使用InputConnection覆盖方法代替键事件(或更简单地说,使用从EditText派生的类,为您完成此操作) )。

Nonetheless, TYPE_NULL behavior is not being officially discontinued, and thus the failure of LatinIME to generate KEYCODE_DEL events under certain circumstances is indeed a bug. 尽管如此,TYPE_NULL行为并未被正式终止,因此LatinIME在某些情况下未能生成KEYCODE_DEL事件确实是一个错误。 I offer the following workaround to address this problem. 我提供以下解决方法来解决此问题。

OVERVIEW: 概述:

The problems that apps have had in receiving KEYCODE_DEL from LatinIME are due to TWO known bugs, as reported here : 该应用程序不得不从LatinIME接收KEYCODE_DEL的问题是,由于两个已知的bug,报告如下

https://code.google.com/p/android/issues/detail?id=42904 (listed as WorkingAsIntended, but the problem is, I maintain, a bug inasmuch as it causes a failure to support KEYCODE_DEL event generation for apps targeting API level 16 and above that have specifically listed an InputType of TYPE_NULL. The problem is fixed in the latest releases of LatinIME, but there are past releases in the wild that still exhibit this bug, and so apps using TYPE_NULL and targeting API Level 16 or above will still need a workaround that can be performed from within the app. https://code.google.com/p/android/issues/detail?id=42904 (列为WorkingAsIntended,但我认为问题是一个错误,因为它导致无法支持针对应用定位的KEYCODE_DEL事件生成API级别16及更高版本专门列出了TYPE_NULL的InputType。问题已在最新版本的LatinIME中修复,但过去的发行版本中仍然存在此错误,因此使用TYPE_NULL并定位到API级别16或更高版本的应用以上仍然需要可以在应用程序内执行的解决方法。

and here : 在这里

http://code.google.com/p/android/issues/detail?id=62306 (presently listed as fixed but not yet released - FutureRelease - but even once it is released, we will still need a workaround that can be performed from within the app to deal with the past releases that will persist "in the wild"). http://code.google.com/p/android/issues/detail?id=62306 (目前列为固定版本,但尚未发布-FutureRelease-但即使已发布,我们仍然需要可以执行的解决方法从应用程序内部处理过去的版本,这些版本将“在野外持续”)。

Consistent with this thesis (that the problems experienced with KEYCODE_DEL events are due to bugs in LatinIME), I have found that when using an external hardware keyboard, and also when using the third party SwiftKey soft keyboard, these problems do not occur, while they do occur for specific versions of LatinIME. 与本论文一致(KEYCODE_DEL事件遇到的问题是由于LatinIME中的错误引起的),我发现当使用外部硬件键盘以及第三方SwiftKey软键盘时,这些问题不会发生。 确实会针对特定版本的LatinIME。

One or the other (but not both at once) of these problems is present in some LatinIME releases. 在某些LatinIME版本中存在一个或另一个(但不是一次)的问题。 Consequently, it is difficult for developers to know during testing whether they have worked around all KEYCODE_DEL problems, and sometimes when an Android (or Google Keyboard) update is performed, a problem will no longer be reproducible in testing. 因此,开发人员很难在测试期间知道他们是否已经解决了所有KEYCODE_DEL问题,并且有时在执行Android(或Google键盘)更新时,测试中的问题将不再重现。 Nonetheless, the LatinIME versions that cause the problem will be present on quite a number of devices in use. 但是,导致该问题的LatinIME版本仍将出现在使用中的许多设备上。 This has forced me to dig into the AOSP LatinIME git repo to determine the exact scope of each of the two problems (ie, the specific LatinIME, and Android, versions for which each of the two issues may be present). 这迫使我深入研究AOSP LatinIME git repo,以确定两个问题中每个问题的确切范围(即,可能存在两个问题的特定LatinIME和Android版本)。 The workaround code below has been restricted to those specific versions. 下面的解决方法代码已限于这些特定版本。

The workaround code presented below includes extensive comments which should help you to understand what it is attempting to accomplish. 下面显示的解决方法代码包括大量注释,这些注释应有助于您了解其要完成的工作。 Following the presentation of the code, I will provide some additional discussion, which will include the specific Android Open Source Project (AOSP) commits at which each of the two bugs was introduced, and at which it disappeared, and also the Android versions that might include the affected Google Keyboard releases. 在介绍了代码之后,我将提供一些附加的讨论,其中包括特定的Android开放源代码项目(AOSP)提交,在该提交中引入了两个错误,在此错误中又消失了,以及可能包括受影响的Google键盘版本。

I would warn anyone thinking of using this approach to perform their own testing to verify that it works for their particular app. 我会警告任何考虑使用此方法来执行自己的测试以验证它适用于其特定应用程序的人。 I think that it will work in general, and have tested it on a number of devices and LatinIME versions, but the reasoning is complicated, so proceed with caution. 我认为它可以正常运行,并且已经在许多设备和LatinIME版本上进行了测试,但是推理很复杂,因此请谨慎操作。 If you find any problems, please post a comment below. 如果发现任何问题,请在下面发表评论。

CODE : 代码

Here, then, is my workaround for both of the two problems, with an explanation included in the comments to the code: 然后,这是我针对这两个问题的解决方法,并在代码的注释中包含了解释:

First, include the following class (edited to taste) in your app, in its own source file InputConnectionAccomodatingLatinIMETypeNullIssues.java: 首先,在您的应用程序中,在其自己的源文件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);
    }
  }
}

Next, take each View-derived class that needs to receive key events from the LatinIME soft keyboard, and edit it as follows: 接下来,获取需要从LatinIME软键盘接收键事件的每个View派生类,并按如下所示进行编辑:

First, create an override to onCreateInputConnection() in the view that is to receive key events as follows: 首先,在要接收键事件的视图中创建对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;
}

Second, make the following changes to your onKey() handler for the view: 其次,对视图的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'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.   } };

Finally, we need to define a class for our editable that ensures that there is always at least one character in our editable buffer: 最后,我们需要为可编辑对象定义一个类,以确保可编辑缓冲区中始终至少有一个字符:

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

That completes the source changes that I've found seem to handle both problems. 这样就完成了我发现可以解决这两个问题的源代码更改。

ADDITIONAL NOTES : 其他说明

The problem described by Issue 42904 was introduced in the LatinIME version delivered with API level 16. Prior to that, KEYCODE_DEL events were generated regardless of whether TYPE_NULL was used. 在API级别16随附的LatinIME版本中引入了问题42904描述的问题。在此之前,无论是否使用TYPE_NULL都将生成KEYCODE_DEL事件。 In the LatinIME released with Jelly Bean, this generation was discontinued, but no exception was made for TYPE_NULL, and so TYPE_NULL behavior was effectively disabled for apps targeted above API level 16. There was, however, compatibility code added that allowed apps that had a targetSdkVersion < 16 to continue to receive KEYCODE_DEL events, even without TYPE_NULL. 在与Jelly Bean一起发布的LatinIME中,这一代产品已经停产,但是TYPE_NULL没有例外,因此,针对API级别16以上的应用有效地禁用了TYPE_NULL行为。但是,添加了兼容性代码,允许具有targetSdkVersion <16,即使没有TYPE_NULL,也继续接收KEYCODE_DEL事件。 See this AOSP commit at line 1493: 请参阅第1493行的AOSP提交:

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

Therefore, you could work around this problem by setting targetSdkVersion in your app to 15 or lower. 因此,可以通过将应用程序中的targetSdkVersion设置为15或更低的值来解决此问题。

As of commit 4.4_r0.9 (just prior to the 4.4 release), this problem was fixed by adding a test for isTypeNull() to the conditions guarding KEYCODE_DEL generation. 从commit 4.4_r0.9开始(仅在4.4版本之前),已通过在保护KEYCODE_DEL生成的条件中添加isTypeNull()测试来解决此问题。 Unfortunately, a new bug (62306) was introduced at exactly that point which caused the entire clause wrapping KEYCODE_DEL generation to be skipped if the user had typed backspace as many times as she had typed other characters. 不幸的是,正是在这一点上引入了一个新的错误(62306),如果用户键入退格次数与键入其他字符的次数相同,则会导致包装KEYCODE_DEL代的整个子句被跳过。 This led to a failure to generate KEYCODE_DEL under those circumstances, even with TYPE_NULL, and even with targetSdkVersion <= 15. This caused apps that had previously been able to get correct KEYCODE_DEL behavior via compatibility code (targetSdkVersion <= 15) to suddenly experience this problem when users upgraded their copies of Google Keyboard (or performed an OTA that contained a new version of Google Keyboard). 在这种情况下,即使使用TYPE_NULL,甚至使用targetSdkVersion <= 15,也无法生成KEYCODE_DEL。这导致以前能够通过兼容性代码(targetSdkVersion <= 15)获得正确KEYCODE_DEL行为的应用突然遇到这种情况。用户升级其Google键盘副本(或执行包含新版Google键盘的OTA)时出现问题。 See this AOSP git file at line 2146 (the clause including "NOT_A_CODE"): 请参阅第2146行的AOSP git文件(包括“ NOT_A_CODE”的子句):

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

This problem has persisted in released versions of Google Keyboard to the present time (1/7/2014). 到目前为止,此问题在发行版的Google键盘中仍然存在(2014年1月7日)。 It has been fixed in the repo, but as of this writing has not been released. 它已在存储库中修复,但截至本文撰写时尚未发布。

That unreleased commit can be found here (the git commit containing this merges a commit titled "Send backspace as an event when TYPE_NULL"), at line 2110 (you can see that the "NOT_A_CODE" clause that used to prevent our reaching the clause that generates KEYCODE_DEL has been removed): 可以在此处找到未发布的提交(包含该提交的git提交将名为“ 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 https://android.googlesource.com/platform/packages/inputmethods/LatinIME/+/b41bea65502ce7339665859d3c2c81b4a29194e4/java/src/com/android/inputmethod/latin/LatinIME.java

When this fix is released, that version of the Google Keyboard will no longer have either of these two problems affecting TYPE_NULL. 发布此修复程序后,该版本的Google键盘将不再具有影响TYPE_NULL的这两个问题。 However , there will still be older versions installed on particular devices for the indefinite future. 但是 ,在不确定的将来,仍将在特定设备上安装较旧的版本。 Hence, the problem will still need a workaround. 因此,该问题仍将需要解决方法。 Eventually, as more people upgrade to a higher level than the last one not including the fix, this workaround will be needed less and less. 最终,随着更多人升级到更高的级别而不是上一个不包含此修复程序的人,将越来越需要这种解决方法。 But it is already scoped to phase itself out (once you make the indicated changes to put the final limit on the scope, when the final fix has actually been released so that you know what it actually is). 但是它已经可以逐步淘汰(一旦您进行了指示性的更改以将最终限制放到范围上,而最终修订实际上已经发布,以便您知道它的真正含义)。

Looks like a bug with Android: 看起来像是Android的错误:

Issue 42904 : KEYCODE_DEL event is not delivered to EditText in SDK 16 and above. 问题42904 :KEYCODE_DEL事件未传递到SDK 16及更高版本中的EditText

Issue 42904 @ code.google.com 发行42904 @ code.google.com

INTRODUCTION: 介绍:

After testing both @Carl's and @Turix's solutions I noticed that: 在测试了@Carl和@Turix的解决方案之后,我注意到:

  1. Carl's solution does not work well with unicode characters or character sequences, since these seem to be delivered with the ACTION_MULTIPLE event, which makes it difficult to distinguish between the "dummy" characters and the actual character. Carl的解决方案不适用于Unicode字符或字符序列,因为它们似乎是与ACTION_MULTIPLE事件一起提供的,这使得很难区分“虚拟”字符和实际字符。

  2. I was not able to get deleteSurroundingText working in the latest version of Android on my Nexus 5 (4.4.2). 我无法在Nexus 5(4.4.2)的最新版本的Android中使用deleteSurroundingText I tested targeting several different sdk versions, but none of them worked. 我针对几种不同的sdk版本进行了测试,但没有一个起作用。 Perhaps Google has decided to yet again change the logic behind the DEL key... 也许Google已经决定再次更改DEL键背后的逻辑...

Therefore, I've come up with the following combined solution, using both Carl's and Turix's answers. 因此,我使用了卡尔和Turix的答案,提出了以下组合解决方案。 My solution works by combining Carl's idea of a long dummy character prefix to make DEL work, but using Turix's solution for a custom Editable to generate proper key events. 我的解决方案通过结合卡尔关于长假字符前缀的想法使DEL起作用,但是将Turix的解决方案用于自定义Editable来生成适当的键事件。

RESULTS: 结果:

I have tested this solution on several devices with different versions of Android and different keyboards. 我已经在具有不同版本Android和不同键盘的多种设备上测试了该解决方案。 All of the below test cases work for me. 以下所有测试用例都对我有用。 I have not found a case where this solution does not work. 我还没有发现这种解决方案不起作用的情况。

  • Nexus 5 (4.4.2) with Standard Google Keyboard 具有标准Google键盘的Nexus 5(4.4.2)
  • Nexus 5 (4.4.2) with SwiftKey 具有SwiftKey的Nexus 5(4.4.2)
  • HTC One (4.2.2) with Standard HTC Keyboard 配备标准HTC键盘的HTC One(4.2.2)
  • Nexus One (2.3.6) with Standard Google Keyboard 具有标准Google键盘的Nexus One(2.3.6)
  • Samsung Galaxy S3 (4.1.2) with Standard Samsung Keyboard 带有标准三星键盘的三星Galaxy S3(4.1.2)

I have also tested targeting different sdk versions: 我还针对不同的SDK版本进行了测试:

  • Target 16 目标16
  • Target 19 目标19

If this solution also works for you, then ple 如果此解决方案也对您有效,请

THE VIEW: 风景:

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

THE INPUT CONNECTION: 输入连接:

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

(This answer is meant as an addendum to the accepted answer posted here by Carl.) (此答案是卡尔在此处发布的公认答案的附录。)

While greatly appreciative of the research and understanding of the two bugs, I had a few troubles with the workaround posted here by Carl. 虽然非常赞赏对这两个bug的研究和理解,但我对Carl在此处发布的解决方法有一些麻烦。 The main issue I had was that, although Carl's comment block says that the KeyEvent.ACTION_MULTIPLE path in onKey() would only be taken on "the first event received after selecting the letter rack", for me, every single key event took that path. 我遇到的主要问题是,尽管卡尔的注释块说onKey()中的KeyEvent.ACTION_MULTIPLE路径仅在“选择字母架后收到的第一个事件”上采用,但对我来说,每个单个键事件都采用该路径。 (I discovered by looking at the BaseInputConnection.java code for API-level-18 that this is because the entire Editable text is used in sendCurrentText() each time. I'm not sure why it worked for Carl but not me.) (我通过查看API级别18的BaseInputConnection.java代码发现,这是因为每次都在sendCurrentText()使用了整个Editable文本。我不确定为什么它对Carl有效,但对我却不起作用。)

So, inspired by Carl's solution, I adapted it to not have this problem. 因此,受卡尔解决方案的启发,我对其进行了调整,以解决这个问题。 My solution to issue 62306 (linked to in Carl's answer) tries to achieve the same basic effect of "tricking" the IME into thinking that there is always more text that can be backspaced over. 我对问题62306的解决方案(与卡尔的答案相关联)试图实现“诱使” IME相同的基本效果,即认为总会有更多文本可以退格。 However, it does this by making sure the Editable has exactly one character in it. 但是,它是通过确保Editable中只有一个字符来实现的。 To do so, you need to extend the underlying class that implements the Editable interface, SpannedStringBuilder , in a way similar to the following: 为此,您需要以类似于以下的方式扩展实现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); 
        }
    }

Basically, whenever the IME attempts to add a character to the Editable (by calling replace() ), that character replaces whatever singleton character is there. 基本上,每当IME尝试将字符添加到Editable中时(通过调用replace() ),该字符都会替换那里的任何单例字符。 Meanwhile, if the IME attempts to remove what's there, the replace() override instead replaces what's there with a singleton "dummy" character (which should be something your app will ignore) to maintain the length of 1. 同时,如果IME尝试删除其中的内容,则replace()替代会使用单例“虚拟”字符(您的应用程序将忽略的字符replace()替换那里的内容,以保持1的长度。

This means that the implementations of getEditable() and onKey() can be slightly simpler than what Carl posted above. 这意味着getEditable()onKey()可以比Carl上面发布的实现稍微简单一些。 For example, assuming the MyEditable class above is implemented as an inner class, getEditable() becomes something like: 例如,假设上面的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;
    } 

Note that with this solution, there's no need to maintain a 1024-character long string. 请注意,使用此解决方案,无需维护1024个字符的长字符串。 Nor, is there any danger of "backspacing too much" (as discussed in Carl's comments about holding down the backspace key). 同样,也不存在“退格过多”的危险(正如卡尔在按住退格键的评论中所讨论的那样)。

For completeness, onKey() becomes something like: 为了完整onKey()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...
    }

Finally, I should note that all of the above is meant as a workaround to issue 62306 only . 最后,我应该注意,以上所有内容是发出62306的一种解决方法。 I had no problems with the solution to the other issue, 42904, as posted by Carl (overriding deleteSurroundingText() ) and would recommend using it as he posted it. 我对Carl发布的另一个问题42904的解决方案没有任何问题(覆盖deleteSurroundingText() ),并且建议在发布时使用它。

I have faced similar issues where KEYCODE_DEL was not being received on touch of backspace key. 我曾遇到过类似的问题,即在按Backspace键时未收到KEYCODE_DEL。 It depends on the Soft Input keyboard i think, because my issue was happening only in case of some third party keyboards(swype i think) and not with default google keyboard. 这取决于我认为的软输入键盘,因为我的问题仅在某些第三方键盘(我认为是Swype)的情况下发生,而不是默认的Google键盘。

Owing to @Carl's thoughts, I came to a solution which is working properly for any input type. 由于@Carl的想法,我想到了一种适用于任何输入类型的解决方案。 Below I give a complete working sample app consisting of 2 classes: MainActivity and CustomEditText : 下面,我给出一个完整的工作示例应用程序,该应用程序包含2个类: MainActivityCustomEditText

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: I updated the code, because it was not working properly when Text Prediction is enabled on some devices like Samsung Galaxy S6 (thanks @Jonas that he informed about this issue in the comment below) and using InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS doesn't help in this case. 更新:我更新了代码,因为在诸如Samsung Galaxy S6之类的某些设备上启用了文本预测时,它无法正常工作(感谢@Jonas他在下面的评论中告知了此问题)并使用InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS在这个案例。 I tested this solution on lots of devices, but still not sure whether it works properly for all. 我在许多设备上测试了该解决方案,但仍不确定该解决方案是否对所有设备都适用。 I hope I will get some reviews from you in case of any improper behaviour of the EditText. 如果EditText有任何不当行为,希望我能给您一些评论。

I think you may find that you can intercept the key if you override the dispatchKeyEvent method of the appropriate view/activity (in my case, the main activity was fine). 我认为您可能会发现,如果重写适当的视图/活动的dispatchKeyEvent方法(在我的情况下,主活动很好),则可以拦截该键。

For example I'm developing an app for a device which has hardware scroll keys, and I was surprised to discover the onKeyUp / onKeyDown methods never get called for them. 例如,我正在为具有硬件滚动键的设备开发应用程序,但我惊讶地发现onKeyUp / onKeyDown方法从未被调用过。 Instead, by default the key press goes through a bunch of dispatchKeyEvent s until it invokes a scroll method somewhere (in my case, bizarrely enough, one key press invokes a scroll methods on each of two separate scrollable views--how annoying). 取而代之的是,默认情况下,按键会经过一堆dispatchKeyEvent直到它在某个地方调用了滚动方法(在我的情况下,非常奇怪的是,一次按键在两个单独的可滚动视图中的每个上都调用了一个滚动方法-多么令人讨厌)。

What if you checked for like the decimal number for the backspace character? 如果您检查退格字符的十进制数字该怎么办?

I think its like '/r' (decimal number 7) or something, at least for ASCII. 我认为它类似于“ / r”(十进制数7)之类的东西,至少对于ASCII而言。

EDIT: I guess Android uses UTF-8, so this decimal number would be 8. http://www.fileformat.info/info/unicode/char/0008/index.htm 编辑:我想Android使用UTF-8,所以此十进制数将是8。http ://www.fileformat.info/info/unicode/char/0008/index.htm

Given the response by Umair you may consider applying a workaround here: 鉴于Umair的回应,您可以考虑在此处应用解决方法:

Capture a touch event that it's NOT a key event and happens around the lower-right part of the screen while the keyboard is shown. 捕获不是键盘事件的触摸事件,而是在显示键盘时在屏幕的右下部分附近发生。

How to get the Touch position in android? 如何在Android中获得Touch位置?

Is there a way to tell if the soft-keyboard is shown? 是否可以判断是否显示了软键盘?

Hope that helps 希望能有所帮助

InputFilter called for backspace and if edittext is empty. InputFilter要求退格,如果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;
        }
    }});

This is old post and giving my suggestions in case somebody is in need of super quick hack/implementation. 这是旧文章,如果有人需要超级快速的破解/实施 ,请提供我的建议

The simplest work around I came out with is to implement TextWatcher too along with on OnKeyListener and in onTextChanged compare with the previous existing string whether it is reduced by one character. 我想到的最简单的方法是在OnKeyListener上也实现TextWatcher,并在onTextChanged中与以前的现有字符串进行比较,以是否将其减少一个字符。

The benefit of this is it works on any type of keyboard with no long coding process easily. 这样做的好处是它可以在任何类型的键盘上工作,而无需很长时间的编码过程。

For instance my editText holds only one character, so I compared characterSequence if it is empty string, then by that we can acknowledge that Delete key is pressed. 例如,我的editText仅包含一个字符,因此我比较了characterSequence(如果它是空字符串),则可以确认按下Delete键

Below is the code explaining the same: 以下是解释相同代码的代码:

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

Note: In this case my edittext contains only single character so I'm comparing charSequesnce with empty string(since pressing delete will make it empty), for your needs you need to modify it and compare(Like after pressing key substring is part of the original string) it with existing string. 注意:在这种情况下,我的edittext仅包含单个字符,因此我将charSequesnce与空字符串进行比较(因为按delete会使它为空),根据您的需要,您需要对其进行修改和比较(就像按子字符串一样,是原始字符串)与现有字符串一起使用。 Hope it helps. 希望能帮助到你。

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

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