Кнопка перехоплення назад із м'якої клавіатури


84

У мене активність із кількома полями введення. Коли активність розпочата, відображається м'яка клавіатура. Коли натиснута кнопка "Назад", м'яка клавіатура закривається, і щоб закрити активність, мені потрібно ще раз натиснути кнопку "Назад".

Тож питання: чи можна перехопити кнопку назад, щоб закрити м’яку клавіатуру і закінчити діяльність одним натисканням кнопки назад, не створюючи спеціальних InputMethodService?

PS Я знаю, як перехопити кнопку повернення в інших випадках: onKeyDown()або onBackPressed()в цьому випадку це не працює: перехоплюється лише друге натискання кнопки назад.

Відповіді:


76

Так, цілком можливо показати та приховати клавіатуру та перехопити дзвінки на кнопку назад. Це трохи додаткових зусиль, як уже зазначалося, в 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()функція встановлює поле введення. Це також дозволяє клавіші Enter виконувати функціонал (у моєму випадку пошук).

@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, ви побачите, звідки виникла ідея.


2
Це спрацювало дуже добре, і не надто складно було реалізувати (незважаючи на те, що це було трохи страшно).
Cesar

Щиро дякую за це, я просто хотів би, щоб у мене був спосіб отримати щось подібне для роботи зі старими версіями Android.
zidarsk8

3
Код можна спростити, просто перекинувши getContext()на Activity. Звичайно, за умови, що контекстом макета є діяльність, про яку йдеться. Але я не знаю, чи не могло цього бути.
mxcl

3
static mSearchActivity - це неприємно пахне проблемою. Що робити, якщо у вас є два з них у макеті? Замість цього використовуйте нестатичну змінну.
Нуль покажчика

Працює як шарм. Але, я вважаю, що краще використовувати локальний статичний інтерфейс і встановити його реалізацію з внутрішньої діяльності, а не використовувати статичний актікт ...
SpiralDev

80

onKeyDown () та onBackPress () для цього випадку не працюють. Ви повинні використовувати 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?
Абхігян

Вам не потрібно викликати onKeyPreIme в активності. Якщо ви використовуєте CustomEditText у своїй діяльності, метод запускається при натисканні будь-якої кнопки на клавіатурі.
Alican Temel

1
Я вважаю, що це найкраща відповідь, оскільки набагато простіше у реалізації. насправді вам просто потрібно скопіювати клас "CustomEditText" (і перейменувати ваш edittext у 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 для цього макета? я намагаюся перевірити цей підхід, і у мене є вид поверхні в моєму масті, але це нічого не перехоплює
ZZZ

В основному спеціальним видом є оточуючий елемент у xml. <?xml version="1.0" encoding="utf-8"?> <view class="package.LinearLayoutGradient"...
Kirill Rakhman

це також працює, якщо ми просто розширюємо одне представлення та перевизначаємо його dispatchKeyEventPreIme (). Розширювати сам макет не потрібно.
faizal

7

Я мав успіх, замінивши dispatchKeyEvent :

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

Він приховує клавіатуру та завершує дію.


здається, це працює лише на певних пристроях, таких як HTC Desire.
Абхіджіт

5
не працює на Samsung Note 2. DispatchKeyEvent () не викликається, коли клавіатура працює.
faizal

Ну, я спробував це за допомогою емулятора Nexus 6 (Lollipop 5.0) -> спрацював та Примітка 4 (CM12 Lollipop 5.0.2) -> працював не так, насправді не найкраще рішення - але все одно .. Дякую :)
Мартін Пфеффер

Натискання кнопки споживається під час відмови від клавіатури
слот

4

Як ви показуєте м'яку клавіатуру?

Якщо ви використовуєте InputMethodManager.showSoftInput(), ви можете спробувати передати a 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

Спробуйте цей код у своїй реалізації BackPress ( Кнопка блокування назад в android ):

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

Пропоную вам поглянути @ Закрити / приховати програмну клавіатуру Android


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)
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.