Перехватить кнопку возврата с мягкой клавиатуры

84

У меня есть активность с несколькими полями ввода. Когда активность началась, отображается мягкая клавиатура. При нажатии кнопки «Назад» мягкая клавиатура закрывается, и для закрытия активности мне нужно нажать кнопку «Назад» еще раз.

Итак, вопрос: можно ли перехватить кнопку возврата, чтобы закрыть программную клавиатуру и завершить действие одним нажатием кнопки возврата без создания настраиваемого InputMethodService?

PS Я знаю, как в других случаях перехватить кнопку возврата: onKeyDown()или, onBackPressed()но в этом случае не работает: перехватывается только второе нажатие кнопки возврата.

Сергей Глотов
источник

Ответы:

78

Да, есть возможность отображать и скрывать клавиатуру и перехватывать вызовы на кнопку возврата. Это требует дополнительных усилий, поскольку, как уже упоминалось, прямого способа сделать это в API нет. Ключевым моментом является переопределение boolean dispatchKeyEventPreIme(KeyEvent)в макете. Что мы делаем, так это создаем наш макет. Я выбрал RelativeLayout, так как он был основой моей деятельности.

<?xml version="1.0" encoding="utf-8"?>
<com.michaelhradek.superapp.utilities.SearchLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res/com.michaelhradek.superapp"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@color/white">

Внутри нашей деятельности мы настраиваем поля ввода и вызываем setActivity(...)функцию.

private void initInputField() {
    mInputField = (EditText) findViewById(R.id.searchInput);        

    InputMethodManager imm = 
        (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); 
    imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 
            InputMethodManager.HIDE_IMPLICIT_ONLY);

    mInputField.setOnEditorActionListener(new OnEditorActionListener() {

        @Override
        public boolean onEditorAction(TextView v, int actionId,
                KeyEvent event) {
            if (actionId == EditorInfo.IME_ACTION_SEARCH) {
                performSearch();
                return true;
            }

            return false;
        }
    });

    // Let the layout know we are going to be overriding the back button
    SearchLayout.setSearchActivity(this);
}

Очевидно, initInputField()функция настраивает поле ввода. Он также позволяет клавише ввода выполнять функции (в моем случае поиск).

@Override
public void onBackPressed() {
    // It's expensive, if running turn it off.
    DataHelper.cancelSearch();
    hideKeyboard();
    super.onBackPressed();
}

Итак, когда onBackPressed()вызывается в нашем макете, мы можем делать все, что захотим, например, скрывать клавиатуру:

private void hideKeyboard() {
    InputMethodManager imm = (InputMethodManager) 
        getSystemService(Context.INPUT_METHOD_SERVICE);
    imm.hideSoftInputFromWindow(mInputField.getWindowToken(), 0);
}

В любом случае, вот мое переопределение RelativeLayout.

package com.michaelhradek.superapp.utilities;

import android.app.Activity;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.widget.RelativeLayout;

/**
 * The root element in the search bar layout. This is a custom view just to 
 * override the handling of the back button.
 * 
 */
public class SearchLayout extends RelativeLayout {

    private static final String TAG = "SearchLayout";

    private static Activity mSearchActivity;;

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

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

    public static void setSearchActivity(Activity searchActivity) {
        mSearchActivity = searchActivity;
    }

    /**
     * Overrides the handling of the back key to move back to the 
     * previous sources or dismiss the search dialog, instead of 
     * dismissing the input method.
     */
    @Override
    public boolean dispatchKeyEventPreIme(KeyEvent event) {
        Log.d(TAG, "dispatchKeyEventPreIme(" + event + ")");
        if (mSearchActivity != null && 
                    event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
            KeyEvent.DispatcherState state = getKeyDispatcherState();
            if (state != null) {
                if (event.getAction() == KeyEvent.ACTION_DOWN
                        && event.getRepeatCount() == 0) {
                    state.startTracking(event, this);
                    return true;
                } else if (event.getAction() == KeyEvent.ACTION_UP
                        && !event.isCanceled() && state.isTracking(event)) {
                    mSearchActivity.onBackPressed();
                    return true;
                }
            }
        }

        return super.dispatchKeyEventPreIme(event);
    }
}

К сожалению, я не могу отдать должное. Если вы проверите исходный код Android для быстрого диалога SearchDialog, вы увидите, откуда пришла идея.

Mhradek
источник
2
Это сработало очень хорошо, и реализовать его было несложно (несмотря на то, что выглядело немного устрашающе).
Cesar
Большое вам спасибо за это, я просто хотел бы иметь способ заставить что-то подобное работать со старыми версиями Android.
zidarsk8 09
3
Код можно упростить, просто применив его getContext()к Activity. Разумеется, при условии, что контекст макета является рассматриваемым действием. Но я не знаю, могло ли это быть.
mxcl
3
static mSearchActivity - проблема не в том. Что, если у вас есть два таких макета? Вместо этого используйте нестатическую переменную.
Pointer Null,
Работает как шарм. Но я думаю, что лучше использовать локальный статический интерфейс и установить его реализацию изнутри активности, а не использовать статическую активность ...
SpiralDev
81

onKeyDown () и onBackPressed () в этом случае не работают. Вы должны использовать onKeyPreIme .

Первоначально вам нужно создать настраиваемый текст редактирования, расширяющий EditText. А затем вам нужно реализовать метод onKeyPreIme, который управляет KeyEvent.KEYCODE_BACK . После этого достаточно одного нажатия назад, чтобы решить вашу проблему. У меня это решение отлично работает.

CustomEditText.java

public class CustomEditText extends EditText {

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

    @Override
    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            // User has pressed Back key. So hide the keyboard
            InputMethodManager mgr = (InputMethodManager)         

           getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
            mgr.hideSoftInputFromWindow(this.getWindowToken(), 0);
            // TODO: Hide your view as you do it in your activity
        }
        return false;
}

В вашем XML

<com.YOURAPP.CustomEditText
     android:id="@+id/CEditText"
     android:layout_height="wrap_content"
     android:layout_width="match_parent"/> 

В вашей деятельности

public class MainActivity extends Activity {
   private CustomEditText editText;

   @Override
   public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      editText = (CustomEditText) findViewById(R.id.CEditText);
   }
}
Аликан Темель
источник
4
Мне так нравится этот ответ. Используя это также с фрагментом (в строке поиска, которая даже не всегда отображается), и предлагая переопределить editText, это легко инкапсулирует это поведение только тогда, когда это необходимо
Jawad
2
Всем на заметку: добавление быстрого интерфейса к этому делает жизнь намного лучше, и, к счастью, это довольно легко сделать
Джавад
как вызвать метод в onKeyPreIme (), который находится в mainActivity.java?
Abhigyan
Вам не нужно вызывать onKeyPreIme в действии. Если вы используете CustomEditText в своей деятельности, метод запускается при нажатии любой кнопки на клавиатуре.
Аликан Темел
1
Я думаю, что это лучший ответ, потому что его гораздо проще реализовать. на самом деле вам просто нужно скопировать класс CustomEditText (и переименовать текст редактирования в XML) и сделать то, что вы хотите, в области // TODO. И просто добавьте «return true» в область TODO, если вы не хотите закрывать свою деятельность. Благодаря !
Chrysotribax
10

Я выяснил, что переопределение метода dispatchKeyEventPreIme класса Layout также хорошо работает. Просто установите основное действие как атрибут и запустите предопределенный метод.

public class LinearLayoutGradient extends LinearLayout {
    MainActivity a;

    public void setMainActivity(MainActivity a) {
        this.a = a;
    }

    @Override
    public boolean dispatchKeyEventPreIme(KeyEvent event) {
        if (a != null) {
            InputMethodManager imm = (InputMethodManager) a
                .getSystemService(Context.INPUT_METHOD_SERVICE);

            if (imm.isActive() && event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
                a.launchMethod;
            }
        }

        return super.dispatchKeyEventPreIme(event);
    }
}
Кирилл Рахман
источник
вы можете показать мне свой xml для этого макета? Я пытаюсь проверить этот подход, и у меня есть вид поверхности в моем макете custum, но он ничего не перехватывает
ZZZ
В основном настраиваемое представление - это окружающий элемент в xml. <?xml version="1.0" encoding="utf-8"?> <view class="package.LinearLayoutGradient"...
Кирилл Рахман
он также работает, если мы просто расширим одно представление и переопределим его dispatchKeyEventPreIme (). Саму раскладку расширять не требуется.
faizal
7

Мне удалось переопределить dispatchKeyEvent :

@Override
public boolean dispatchKeyEvent(KeyEvent event) {
    if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
        finish();
        return true;
    }
    return super.dispatchKeyEvent(event);
}

Он скрывает клавиатуру и завершает действие.

Jaseelder
источник
похоже, это работает только на определенных устройствах, таких как HTC Desire.
Abhijit
5
не работает на samsung note 2. dispatchKeyEvent () не вызывается при включенной программной клавиатуре.
faizal
Ну, я пробовал это с эмулятором Nexus 6 (Lollipop 5.0) -> работал и Note 4 (CM12 Lollipop 5.0.2) -> работал не так, не совсем лучшее решение - но в любом случае .. Спасибо :)
Мартин Пфеффер
Нажатие кнопки потребляется при отключении клавиатуры
slott
4

Как у вас мягкая клавиатура показывает?

Если вы используете InputMethodManager.showSoftInput(), вы можете попробовать передать ResultReceiverи реализовать onReceiveResult()для обработкиRESULT_HIDDEN

http://developer.android.com/reference/android/view/inputmethod/InputMethodManager.html

суперпользователь
источник
Звучит хорошо, но showSoftInput()на моем телефоне и в эмуляторе не отображается клавиатура, поэтому я используюtoggleSoftInput()
Сергей Глотов
1
У меня не работает. onReceiveResult () вызывается с RESULT_SHOWN, но никогда позже с RESULT_HIDDEN
Юлия Роговая
2

У меня была та же проблема, но я обошел ее, перехватив нажатие клавиши возврата. В моем случае (HTC Desire, Android 2.2, Application API Level 4) он закрывает клавиатуру и немедленно завершает действие. Не знаю, почему это не должно работать и для вас:

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_BACK) {
        return true;
    }
    return super.onKeyDown(keyCode, event);
}

@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_BACK) {
        onBackPressed();
        return true;
    }
    return super.onKeyUp(keyCode, event);
}

/**
 * Called when the activity has detected the user's press of the back key
 */
private void onBackPressed() {
    Log.e(TAG, "back pressed");
    finish();
}
белый
источник
это не работает на samsung galaxy note 2. onkeydown () или onbackpressed () не срабатывают, когда отображается программная клавиатура и нажата кнопка назад
faizal
1

Используйте onKeyPreIme(int keyCode, KeyEvent event)метод и проверьте KeyEvent.KEYCODE_BACKсобытие. Это очень просто, без сложного кодирования.

Умайр
источник
0

Попробуйте этот код в своей реализации BackPressed ( кнопка блокировки возврата в android ):

InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(myEditText.getWindowToken(), 0);

Предлагаю вам взглянуть на @ Close / Hide the Android Soft Keyboard

100раб
источник
1
Я уже пробовал это, но все еще нужно два нажатия кнопки возврата. Как я понял первый щелчок перехватывает софт клавиатура так чтоonBackPressed() не работает. И только после второго пресса программа попадает в onBackPressed().
Сергей Глотов
@Sergey Glotov Я пробовал много предложений по проблеме и, наконец, пришел с не очень хорошим решением, в котором мне нужно реализовать собственную SoftKeyBoard. Но я надеюсь, что сообщество Android скоро придет с гораздо лучшим решением.
100rabh,
0

моя версия решения @mhradek:

Макет

class BazingaLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
    ConstraintLayout(context, attrs, defStyleAttr) {

var activity: Activity? = null

override fun dispatchKeyEventPreIme(event: KeyEvent): Boolean {
    activity?.let {
        if (event.keyCode == KeyEvent.KEYCODE_BACK) {
            val state = keyDispatcherState
            if (state != null) {
                if (event.action == KeyEvent.ACTION_DOWN
                    && event.repeatCount == 0) {
                    state.startTracking(event, this)
                    return true
                } else if (event.action == KeyEvent.ACTION_UP && !event.isCanceled && state.isTracking(event)) {
                    it.onBackPressed()
                    return true
                }
            }
        }
    }
    return super.dispatchKeyEventPreIme(event)
}

}

xml файл

<com... BazingaLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/grey">
 </com... BazingaLayout>

Фрагмент

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        (view as BazingaLayout).activity = activity
        super.onViewCreated(view, savedInstanceState)
}
Тасьяпр
источник