24

I have an EditText called myTextview. I want the soft keyboard to show when I click on the EditText but then dismiss if I click outside of the EditText. So I use the method below. But the keyboard does not dismiss when I click outside the view (I click a TextView). How do I fix this code?

myTextview.setOnFocusChangeListener(new OnFocusChangeListener() {

        @Override
        public void onFocusChange(View v, boolean hasFocus) {
            if (hasFocus) {
                getActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
            } else {
                InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
                imm.hideSoftInputFromWindow(myTextview.getWindowToken(), 0);
            }

        }
    });
Lavekush Agrawal
  • 6,040
  • 7
  • 52
  • 85
user3093402
  • 1,419
  • 4
  • 20
  • 28
  • 1
    possible duplicate of [how to hide soft keyboard on android after clicking outside EditText?](http://stackoverflow.com/questions/4165414/how-to-hide-soft-keyboard-on-android-after-clicking-outside-edittext) – Blo Apr 12 '14 at 07:08

7 Answers7

68

I found a better solution:

Override the dispatchTouchEvent method in your Activity.

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    View v = getCurrentFocus();

    if (v != null && (ev.getAction() == MotionEvent.ACTION_UP || ev.getAction() == MotionEvent.ACTION_MOVE) &&
            v instanceof EditText &&
            !v.getClass().getName().startsWith("android.webkit.")) {
        int[] sourceCoordinates = new int[2];
        v.getLocationOnScreen(sourceCoordinates);
        float x = ev.getRawX() + v.getLeft() - sourceCoordinates[0];
        float y = ev.getRawY() + v.getTop() - sourceCoordinates[1];

        if (x < v.getLeft() || x > v.getRight() || y < v.getTop() || y > v.getBottom()) {
            hideKeyboard(this);
        }

    }
    return super.dispatchTouchEvent(ev);
}

private void hideKeyboard(Activity activity) {
    if (activity != null && activity.getWindow() != null) {
        activity.getWindow().getDecorView();
        InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
        if (imm != null) {
            imm.hideSoftInputFromWindow(activity.getWindow().getDecorView().getWindowToken(), 0);
        }
    }
}

This is applicable even if you are working with webview.

UPDATE 10th July 2020

The above method works perfectly but the EditText is still has the focus and typing cursor is still visible.

To solve the described issue. Do this

  1. Add findViewById(android.R.id.content).setFocusableInTouchMode(true); in your onCreate() method

  2. Add findViewById(android.R.id.content).clearFocus(); in the hideKeyboard() method

Credit to Eddwhis's comment

Danish Ansari
  • 451
  • 1
  • 6
  • 22
Felipe R. Saruhashi
  • 1,709
  • 16
  • 22
  • 2
    Neat , Clean and best solution, Saved my big time – Bhavik Mehta Oct 30 '15 at 09:53
  • What happens if another EditText is clicked? – Simas Jan 04 '16 at 09:40
  • I can confirm that - If another EditText is clicked, the focus just shifts to that EditText. This solution worked perfectly for me. – karthik prasad Jul 12 '16 at 16:00
  • 2
    Perfect solution! Just added `findViewById(android.R.id.content).setFocusableInTouchMode(true);` to `onCreate` and `findViewById(android.R.id.content).clearFocus();` to `hideKeyboard` for more clean keyboard hiding. – Eddwhis Jun 16 '17 at 08:12
  • 1
    This is working great. Putting this on my BaseActivity will make all my views compatable for this action. Thanks a lot. – March3April4 Oct 15 '19 at 08:20
  • Why does activity.getWindow().getDecorView() gets called inside the hideKeyboard() method after the activity null check? – Petermonteer Aug 08 '22 at 10:22
17

Maybe a little bit easier:

Set a focusChangedListener on your edit text and then just hide the keyboard if you don't have focus.

yourEditText.setOnFocusChangeListener(new OnFocusChangeListener() {

    @Override
    public void onFocusChange(View v, boolean hasFocus) {
        if(!hasFocus){
            hideKeyboard();
        }               
    }
});

private void hideKeyboard() {
    InputMethodManager imm = (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE);
    imm.hideSoftInputFromWindow(yourEditText.getWindowToken(), 0);
}
MysticMagicϡ
  • 28,593
  • 16
  • 73
  • 124
pat
  • 1,005
  • 4
  • 12
  • 29
3

This way, the keyboard will only disappear when you touch a view that can gain focus. I suggest you to do the following:

Create a custom ViewGroup like this:

public class TouchLayout extends LinearLayout {

    private OnInterceptTouchEventListener mListener;

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

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        if(mListener != null) {
            return mListener.onInterceptTouchEvent(event);
        }
        return super.onInterceptTouchEvent(event);
    }

    public void setOnInterceptTouchEventListener(OnInterceptTouchEventListener listener) {
        mListener = listener;
    }

    public interface OnInterceptTouchEventListener {
        public boolean onInterceptTouchEvent(MotionEvent event);
    }
}

Then add the custom View as a root of your xml layout:

<com.example.TouchLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

        <EditText
            android:id="@+id/text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

And in your Activity you should do the following:

final TouchLayout root = (TouchLayout) findViewById(R.id.root);
final EditText text = (EditText) findViewById(R.id.text);
final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);

root.setOnInterceptTouchEventListener(new OnInterceptTouchEventListener() {

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        final View v = getCurrentFocus();
        if(v != null && v.equals(text)) {
            final int screenCords[] = new int[2];
            text.getLocationOnScreen(screenCords);
            final Rect textRect = new Rect(screenCords[0], screenCords[1], screenCords[0] + text.getWidth(), screenCords[1] + text.getHeight());
            if(!textRect.contains(event.getRawX(), event.getRawY() {
                imm.hideSoftInputFromWindow(myTextview.getWindowToken(), 0);
                // Optionally you can also do the following:
                text.setCursorVisible(false);
                text.clearFocus(); 
            }
        }
        return false;
    }
};
diegocarloslima
  • 1,346
  • 14
  • 16
2

Plea: I recognize I have no clout, but please take my answer seriously.

Problem: Dismiss soft keyboard when clicking away from keyboard or edit text with minimal code.

Solution: External library known as Butterknife.

One Line Solution:

@OnClick(R.id.activity_signup_layout) public void closeKeyboard() { ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0); }

More Readable Solution:

@OnClick(R.id.activity_signup_layout) 
public void closeKeyboard() {
        InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
}

Explanation: Bind OnClick Listener to the activity's XML Layout parent ID, so that any click on the layout (not on the edit text or keyboard) will run that snippet of code which will hide the keyboard.

Example: If your layout file is R.layout.my_layout and your layout id is R.id.my_layout_id, then your Butterknife bind call should look like:

(@OnClick(R.id.my_layout_id) 
public void yourMethod {
    InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
    imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
}

Butterknife Documentation Link: http://jakewharton.github.io/butterknife/

Plug: Butterknife will revolutionize your android development. Consider it.

Charles Woodson
  • 682
  • 1
  • 8
  • 22
  • You gave that very same answer at least once more. And it smells like spam. Sorry, but that will not work out. – GhostCat Apr 05 '17 at 19:43
  • 1
    @GhostCat Why won't it work or why is it a bad solution? You have a lot of clout, very respected by this community for good reason I'm sure. This hackish solution worked for me simply when many other attempts did not. I'd appreciate an explanation of why it won't work out rather than vague slander. I'm just trying to learn. – Charles Woodson Apr 05 '17 at 20:41
  • Repeating the exact same answer is not a good practice here. That's all. – GhostCat Apr 05 '17 at 20:49
  • 1
    @GhostCat I'm new to this. So even if there are two questions that can both be answered with the same solution, I'm not supposed to post the same solution for both? And it's clearly not spam, but hey, thanks for that comment bro!!!! – Charles Woodson Apr 05 '17 at 20:54
  • 1
    When people go "here great external product" they sometimes advertise their very own stuff. That is okay when saying so, but is regarded as spam when left out. And for the duplicate... One puts up one answer, and then other questions can be declared duplicates. Thing is: don't just enter a community and push out content. Or how much time did you spend at the [help] before writing your answer? To understand how this community works? – GhostCat Apr 05 '17 at 21:02
  • 1
    I looked through all answers, didn't see this solution on any of them. So I posted on both in an attempt to help. If this is considered pushing out too much content, then that's on me, but I don't think I'm out here doing too much. I'm self taught. And open source butterknife changed my development. I hope it does the same for others. But I'll be sure to be more cautious in the future. – Charles Woodson Apr 05 '17 at 21:10
  • ButterKnife is a shorter way to write the same in Android. So, it doesn't help at all. In your case it is a usual ClickListener. Farewell, ButterKnife, if you use Kotlin. – CoolMind Feb 04 '19 at 11:16
1

For all those who are looking for a Xamarin Android code for this :

  public override bool DispatchTouchEvent(MotionEvent ev)
    {
        try
        {
            View view = CurrentFocus;
            if (view != null && (ev.Action == MotionEventActions.Up || ev.Action == MotionEventActions.Move) && view is EditText && !view.Class.Name.StartsWith("android.webkit."))
            {
                int[] Touch = new int[2];
                view.GetLocationOnScreen(Touch);
                float x = ev.RawX + view.Left - Touch[0];
                float y = ev.RawY + view.Top - Touch[1];
                if (x < view.Left || x > view.Right || y < view.Top || y > view.Bottom)
                    ((InputMethodManager)GetSystemService(InputMethodService)).HideSoftInputFromWindow((Window.DecorView.ApplicationWindowToken), 0);
            }
        }
        catch (System.Exception ex)
        {

        }

        return base.DispatchTouchEvent(ev);
    }
FreakyAli
  • 13,349
  • 3
  • 23
  • 63
0

Touch event on a View will start with ACTION_DOWN from being passed from top to this View in view hierachy. Override dispatchTouchEvent to do additional things. Here's an example to extend from FrameLayout in kotlin:

class TLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet?=null):
        FrameLayout(context, attrs){

    var preDispatchTouchEvent: ((ev: MotionEvent?)->Boolean)? = null

    override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
        if (preDispatchTouchEvent==null || !preDispatchTouchEvent!!.invoke(ev)) {
            super.dispatchTouchEvent(ev)
        }
        return true
    }

}

Wrap your EditText and other relative Views under this layout, and set preDispatchTouchEvent to dismiss EditText when event is not above it.

Check this OS question and official doc to have deeper understanding of touch event delivering.

Lym Zoy
  • 951
  • 10
  • 16
0

In Kotlin, I have used bellow code which is working fine

override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
    val v = currentFocus
    if (v != null && (ev.action == MotionEvent.ACTION_UP || ev.action == MotionEvent.ACTION_MOVE)
        && v is EditText
        && !v.javaClass.name.startsWith("android.webkit.")
    ) {
        val scrcoords = IntArray(2)
        v.getLocationOnScreen(scrcoords)
        val x = ev.rawX + v.getLeft() - scrcoords[0]
        val y = ev.rawY + v.getTop() - scrcoords[1]
        if (x < v.getLeft() || x > v.getRight() || y < v.getTop() || y > v.getBottom()
        ) hideKeyboard(this)
    }
    return super.dispatchTouchEvent(ev)
}

fun hideKeyboard(activity: Activity?) {
    if (activity != null && activity.window != null && activity.window.decorView != null
    ) {
        val imm = activity
            .getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
        imm.hideSoftInputFromWindow(
            activity.window.decorView
                .windowToken, 0
        )
    }
}

In Java, I have used bellow code which is working fine

  @Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    View v = getCurrentFocus();

    if (v != null
            && (ev.getAction() == MotionEvent.ACTION_UP || ev.getAction() == MotionEvent.ACTION_MOVE)
            && v instanceof EditText
            && !v.getClass().getName().startsWith("android.webkit.")) {
        int scrcoords[] = new int[2];
        v.getLocationOnScreen(scrcoords);
        float x = ev.getRawX() + v.getLeft() - scrcoords[0];
        float y = ev.getRawY() + v.getTop() - scrcoords[1];

        if (x < v.getLeft() || x > v.getRight() || y < v.getTop()
                || y > v.getBottom())
            hideKeyboard(this);
    }
    return super.dispatchTouchEvent(ev);
}

public static void hideKeyboard(Activity activity) {
    if (activity != null && activity.getWindow() != null
            && activity.getWindow().getDecorView() != null) {
        InputMethodManager imm = (InputMethodManager) activity
                .getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.hideSoftInputFromWindow(activity.getWindow().getDecorView()
                .getWindowToken(), 0);
    }
}
Enamul Haque
  • 4,789
  • 1
  • 37
  • 50