Обнаружение, когда пользователь закрыл программную клавиатуру

114

На мой взгляд, у меня есть виджет EditText. Когда пользователь выбирает виджет EditText, я показываю некоторые инструкции, и появляется программная клавиатура.

Я использую OnEditorActionListener, чтобы определить, когда пользователь завершил ввод текста, и я отключаю клавиатуру, скрываю инструкции и выполняю некоторые действия.

Моя проблема в том, что пользователь закрывает клавиатуру, нажимая клавишу НАЗАД. ОС закрывает клавиатуру, но мои инструкции (которые мне нужно скрыть) все еще видны.

Я пробовал переопределить OnKeyDown, но, похоже, он не вызывается, когда кнопка BACK используется для закрытия клавиатуры.

Я пробовал установить OnKeyListener для виджета EditText, но, похоже, он тоже не вызывается.

Как я могу определить, когда отключается программная клавиатура?

deSelby
источник

Ответы:

160

Я знаю, как это сделать. Создайте подкласс EditText и реализуйте:

@Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
  if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
    // Do your thing.
    return true;  // So it is not propagated.
  }
  return super.dispatchKeyEvent(event);
}

Вот ссылка на то, как использовать ваши пользовательские представления (когда вы создаете подкласс EditText): http://developer.android.com/guide/topics/ui/custom-components.html

сойка
источник
2
Я получаю отчеты от пользователей Android с аппаратными клавиатурами, которые каким-то образом мешают нажатию клавиш. На данный момент у меня нет дополнительной информации.
esilver
Я искал несколько решений, это, безусловно, лучшее!
Friesgaard
11
Подожди, подожди, подожди, я только что посмотрел на это в третий раз - разве не должен быть супер-вызов onKeyPreIme? Или есть особая причина, по которой это не так?
Erhannis
Выглядит полезно, за исключением случаев, когда EditText нельзя разделить на подклассы (например, в SearchView). Это проблема при попытке скрыть SearchView, если он пуст при закрытии клавиатуры. Я должен задаться вопросом, почему люди, занимающиеся Android, просто не предоставляют несколько хороших API OSK для такого рода вещей.
tbm
2
@tbm Чтобы добиться аналогичного эффекта SearchView, обратитесь к stackoverflow.com/questions/9629313/…
Cheok Yan Cheng
124

Джей, твое решение хорошее! Спасибо :)

public class EditTextBackEvent extends EditText {

    private EditTextImeBackListener mOnImeBack;

    public EditTextBackEvent(Context context) {
        super(context);
    }

    public EditTextBackEvent(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public EditTextBackEvent(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
        if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && 
            event.getAction() == KeyEvent.ACTION_UP) {
            if (mOnImeBack != null) 
                mOnImeBack.onImeBack(this, this.getText().toString());
        }
        return super.dispatchKeyEvent(event);
    }

    public void setOnEditTextImeBackListener(EditTextImeBackListener listener) {
        mOnImeBack = listener;
    }

}

public interface EditTextImeBackListener {
    public abstract void onImeBack(EditTextBackEvent ctrl, String text);
}
olivier_sdg
источник
Есть ли конкретная причина, которую мы также хотим обнаружить KeyEvent.ACTION_UP?
Чеок Ян Ченг
2
@CheokYanCheng это потому, что действие пользователя обычно вступает в силу при отпускании кнопки, а не при начале ее нажатия.
jayeffkay
3
Обязательно продлите android.support.v7.widget.AppCompatEditTextпод тонировку.
Sanvywell 02
Продлить: AppCompatEditTextдля androidx
COYG
Большой! Я бы предложил только улучшение, чтобы обобщить ваше решение. Я бы передал аргументы из onKeyPreIme "как есть" слушателю, таким образом вы можете реализовать свою логику по-разному там, где вам нужно.
marcRDZ
17

Я немного изменил решение Джея, вызвав super.onKeyPreIme ():

_e = new EditText(inflater.getContext()) {
@Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
    if (event.getKeyCode() == KeyEvent.KEYCODE_BACK){
            cancelTextInput();
        }
        return super.onKeyPreIme(keyCode, event);
    }
};

Замечательное решение, Джей, +1!

Steelight
источник
14

Вот мой собственный EditText, чтобы определить, отображается ли клавиатура или нет

/**
 * Created by TheFinestArtist on 9/24/15.
 */
public class KeyboardEditText extends EditText {

    public KeyboardEditText(Context context) {
        super(context);
    }

    public KeyboardEditText(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public KeyboardEditText(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
        super.onFocusChanged(focused, direction, previouslyFocusedRect);
        if (listener != null)
            listener.onStateChanged(this, true);
    }

    @Override
    public boolean onKeyPreIme(int keyCode, @NonNull KeyEvent event) {
        if (event.getKeyCode() == KeyEvent.KEYCODE_BACK
                && event.getAction() == KeyEvent.ACTION_UP) {
            if (listener != null)
                listener.onStateChanged(this, false);
        }
        return super.onKeyPreIme(keyCode, event);
    }

    /**
     * Keyboard Listener
     */
    KeyboardListener listener;

    public void setOnKeyboardListener(KeyboardListener listener) {
        this.listener = listener;
    }

    public interface KeyboardListener {
        void onStateChanged(KeyboardEditText keyboardEditText, boolean showing);
    }
}
Лучший художник
источник
8

Сейчас 2019 год ...
Итак, я создал более удобное решение с Kotlin.

1. Создайте функцию расширения:

fun Activity.addKeyboardToggleListener(onKeyboardToggleAction: (shown: Boolean) -> Unit): KeyboardToggleListener? {
    val root = findViewById<View>(android.R.id.content)
    val listener = KeyboardToggleListener(root, onKeyboardToggleAction)
    return root?.viewTreeObserver?.run {
        addOnGlobalLayoutListener(listener)
        listener
    }
}

2. Где находится прослушиватель переключения:

open class KeyboardToggleListener(
        private val root: View?,
        private val onKeyboardToggleAction: (shown: Boolean) -> Unit
) : ViewTreeObserver.OnGlobalLayoutListener {
    private var shown = false
    override fun onGlobalLayout() {
        root?.run {
            val heightDiff = rootView.height - height
            val keyboardShown = heightDiff > dpToPx(200f)
            if (shown != keyboardShown) {
                onKeyboardToggleAction.invoke(keyboardShown)
                shown = keyboardShown
            }
        }
    }
}

fun View.dpToPx(dp: Float) = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, resources.displayMetrics).roundToInt()

3. Используйте его в любом простом действии :

addKeyboardToggleListener {shown ->
          // hurray! Now you know when the keyboard is shown and hidden!!
      }
Лев Droidcoder
источник
Спасибо за решение Kotlin. Хотя, когда я реализовал его, я заметил, что он запускает слушателя несколько раз для одной смены клавиатуры, а также при запуске. Мне пришлось сохранить открытое / не открытое состояние и вызывать слушателей только тогда, когда значение действительно было другим.
RandomEngy
@RandomEngy исправил это в KeyboardToggleListener. Спасибо, что заметили
Leo Droidcoder
4

Просто создайте класс, расширяющий Edittext, и используйте этот edittext в своем коде, вам нужно просто переопределить следующий метод в настраиваемом edittext:

@Override
 public boolean onKeyPreIme(int keyCode, KeyEvent event) {
 if (keyCode == KeyEvent.KEYCODE_BACK) {

    //Here it catch all back keys
    //Now you can do what you want.

} else if (keyCode == KeyEvent.KEYCODE_MENU) {
    // Eat the event
    return true;
}
return false;}
farhad.kargaran
источник
Есть ли способ определить, когда открывается клавиатура?
Powder366
3

Вот решение с ключевым слушателем. Я понятия не имею, почему это работает, но OnKeyListener работает, если вы просто переопределяете onKeyPreIme в своем пользовательском EditText.

SomeClass.java

customEditText.setOnKeyListener((v, keyCode, event) -> {
            if(event.getAction() == KeyEvent.ACTION_DOWN) {
                switch (keyCode) {
                    case KeyEvent.KEYCODE_BACK:
                        getPresenter().onBackPressed();
                        break;
                }
            }
            return false;
        }); 

CustomEditText.java

@Override
    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
        return super.dispatchKeyEvent(event);
    }
Рубин Ю
источник
3

Используя ответ @ olivier_sdg, но преобразованный в Kotlin:

class KeyboardEditText : AppCompatEditText {

    var listener: Listener? = null
  
    constructor(context: Context) : super(context)
    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
  
    override fun onKeyPreIme(keyCode: Int, event: KeyEvent): Boolean {
        if (event.keyCode == KeyEvent.KEYCODE_BACK && event.action == KeyEvent.ACTION_UP) {
            listener?.onImeBack(this)
        }
        return super.dispatchKeyEvent(event)
    }
  
    interface Listener {
        fun onImeBack(editText: KeyboardEditText)
    }

}

Использование:

keyboardEditText.listener = object : KeyboardEditText.Listener {
    override fun onImeBack(editText: KeyboardEditText) {
        //Back detected
    }
}
bmjohns
источник
2

Для всех, кто хочет сделать то же самое в Xamarin, я перевел некоторые из основных ответов, поскольку они немного отличаются. Я создал суть здесь , но подведение итогов, создать пользовательскую EditText и переопределение OnKeyPreImeкак так:

public class CustomEditText : EditText
{
    public event EventHandler BackPressed;

    // ...

    public override bool OnKeyPreIme([GeneratedEnum] Keycode keyCode, KeyEvent e)
    {
        if (e.KeyCode == Keycode.Back && e.Action == KeyEventActions.Up)
        {
            BackPressed?.Invoke(this, new EventArgs());
        }

        return base.OnKeyPreIme(keyCode, e);
    }
}

... а потом в представлении ...

editText = FindViewById<CustomEditText>(Resource.Id.MyEditText);
editText.BackPressed += (s, e) => 
{
    // <insert code here>
};
Стивен Эверс
источник
Хотя это всего лишь простой пример, я бы рекомендовал не использовать анонимные методы в обработчиках событий, так как они создают утечки памяти, и многие люди используют найденные здесь примеры и работают с ними, не осознавая этого. Источник: docs.microsoft.com/en-us/xamarin/cross-platform/deploy-test/…
Джаред
0

hideSoftInputFromWindow возвращает истину, когда клавиатура закрывается, используйте это значение для обнаружения закрытия клавиатуры в android

InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);

        if (imm.hideSoftInputFromWindow(findFocus().getWindowToken(),
                InputMethodManager.HIDE_NOT_ALWAYS)) {
            //keyboard is closed now do what you need here
        }
Бали
источник