Onderschep terug-knop van zacht toetsenbord

Ik heb de activiteit met verschillende invoervelden. Wanneer de activiteit is gestart, wordt het zachte toetsenbord weergegeven. Wanneer de terug-knop wordt ingedrukt, wordt het zachte toetsenbord gesloten en om de activiteit te sluiten, moet ik nog een keer op de terug-knop drukken.

Dus de vraag: is het mogelijk om de terugknop te onderscheppen om het zachte toetsenbord te sluiten en de activiteit te beëindigen met één druk op de terugknop zonder aangepaste InputMethodServicete maken?

P.S. Ik weet hoe ik de terugknop in andere gevallen moet onderscheppen: onKeyDown()of onBackPressed()maar in dit geval werkt het niet: alleen de tweede druk op de terugknop wordt onderschept .


Antwoord 1, autoriteit 100%

onKeyDown()en onBackPressed()werken in dit geval niet. U moet onKeyPreImegebruiken.

In eerste instantie moet u aangepaste tekst voor bewerken maken die EditText uitbreidt. En dan moet je de onKeyPreIme-methode implementeren die KeyEvent.KEYCODE_BACKbestuurt. Hierna is één druk op de achterkant voldoende om uw probleem op te lossen. Deze oplossing werkt voor mij perfect.

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

In uw XML

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

In je activiteit

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

Antwoord 2, autoriteit 93%

Ja, het is heel goed mogelijk om het toetsenbord weer te geven en te verbergen en de oproepen naar de terugknop te onderscheppen. Het is een beetje extra inspanning, aangezien er is gezegd dat er geen directe manier is om dit in de API te doen. De sleutel is om boolean dispatchKeyEventPreIme(KeyEvent)binnen een lay-out te negeren. Wat we doen is onze lay-out maken. Ik heb RelativeLayout gekozen omdat dit de basis van mijn activiteit was.

<?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">

Binnen onze Activiteit stellen we onze invoervelden in en roepen we de functie setActivity(...)op.

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

Het is duidelijk dat de functie initInputField()het invoerveld instelt. Het stelt ook de enter-toets in staat om de functionaliteit uit te voeren (in mijn geval een zoekopdracht).

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

Dus wanneer de onBackPressed()wordt aangeroepen in onze lay-out, kunnen we doen wat we willen, zoals het toetsenbord verbergen:

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

Hoe dan ook, hier is mijn overschrijving van de 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);
    }
}

Helaas kan ik niet alle eer opeisen. Als u de Android bron voor de snelle SearchDialog boxje zult zien waar het idee vandaan kwam.


Antwoord 3, autoriteit 15%

Ik ontdekte dat het negeren van de dispatchKeyEventPreImemethode van de Layout Class ook goed werkt. Stel gewoon uw hoofdactiviteit in als een attribuut en start een vooraf gedefinieerde methode.

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

Antwoord 4

Hier is mijn variant van de oplossing van @kirill-rakhman.

Ik moest weten wanneer de hardware- of bewegingsterug-knop werd ingedrukt terwijl het toetsenbord zichtbaar was, zodat ik kon reageren en een knop kon tonen die eerder verborgen was wanneer een van de bewerkingstekstweergaven de focus had gekregen.

  1. Verklaar eerst de terugbelinterface die u nodig heeft
interface KeyboardEventListener {
   fun onKeyBoardDismissedIme()
}
  1. Maak vervolgens de aangepaste weergave met de luisteraar voor pre ime belangrijke gebeurtenissen
class KeyboardAwareConstraintLayout(context: Context, attrs: AttributeSet) :
    ConstraintLayout(context, attrs) {
    var listener: KeyboardEventListener? = null
    override fun dispatchKeyEventPreIme(event: KeyEvent?): Boolean {
        val imm: InputMethodManager =
            context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
        if (imm.isActive && event?.keyCode == KeyEvent.KEYCODE_BACK) {
            listener?.onKeyBoardDismissedIme()
        }
        return super.dispatchKeyEventPreIme(event)
    }
}
  1. Wikkel uw lay-out in met de aangepaste lay-outweergave
<com.package_name.KeyboardAwareLayout 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:id="@+id/keyBoardAwareLayout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
{Your layout children here}    
</com.package_name.KeyboardAwareLayout>
  1. Implementeer vervolgens de callback-interface in uw activiteit of fragment en stel de toetsenbordbewuste lay-out in
class MyFragment : Fragment, KeyboardEventListener {
    // TODO: Setup some viewbinding to retrieve the view reference
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding.keyBoardAwareLayout.listener = this
    }
    override fun onKeyBoardDismissedIme() {
        // TODO: React to keyboard hidden with back button
    }
}

Other episodes