EditText、外のタッチに明確にフォーカス


100

私のレイアウトが含まれているListViewSurfaceViewEditText。をクリックするEditTextと、フォーカスが移動し、画面キーボードがポップアップします。の外のどこかをクリックしEditTextても、フォーカスがあります(フォーカスされません)。OnTouchListenerレイアウトの他のビューにをセットアップし、手動でEditTextのフォーカスをクリアできると思います。しかし、あまりにもハッキリすぎるようです...

私は他のレイアウトでも同じ状況にあります-いくつかのアイテムが中にあるさまざまなタイプのアイテムを含むリストビューEditText。彼らは私が上で書いたように行動します。

タスクはEditText、ユーザーがその外側の何かに触れたときにフォーカスを失うようにすることです。

ここで同様の質問を見たことがありますが、解決策は見つかりません...

回答:


66

私はこれらすべての解決策を試しました。edc598は作業に最も近いものでしたViewが、レイアウトに含まれる他のでタッチイベントがトリガーされませんでした。誰かがこの動作を必要とする場合、これは私がやったことです:

(非表示)を作成しました FrameLayoutすべてをオーバーレイするように、レイアウトの最後にtouchInterceptorと呼ばれる編集: a を親レイアウトとして使用し、touchInterceptor属性を指定する必要もあります)。次に、それを使用してタッチをインターセプトし、タッチがの上にあるかどうかを判断しました。ViewRelativeLayout fill_parentEditText

FrameLayout touchInterceptor = (FrameLayout)findViewById(R.id.touchInterceptor);
touchInterceptor.setOnTouchListener(new OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            if (mEditText.isFocused()) {
                Rect outRect = new Rect();
                mEditText.getGlobalVisibleRect(outRect);
                if (!outRect.contains((int)event.getRawX(), (int)event.getRawY())) {
                    mEditText.clearFocus();
                    InputMethodManager imm = (InputMethodManager) v.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 
                    imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
                }
            }
        }
        return false;
    }
});

タッチ処理を無効にするには、falseを返します。

それはハッキーですが、私のために働いたのはそれだけです。


20
アクティビティでdispatchTouchEvent(MotionEvent ev)をオーバーライドすることにより、レイアウトを追加せずに同じことを行うことができます。
pcans 2013年

clearFocus()ヒントをありがとう、それはSearchViewでも私に役立ちました
Jimmy Ilenloa 14

1
以下の@zManの答えを教えてください。これはこれに基づいていますが、ビューは必要ありません。stackoverflow.com
Boy

また、キーボードが非表示ではなくフォーカスをクリアするという状況に遭遇した場合は、最初に起動してhideSoftInputFromWindow()からフォーカスをクリアしてください
Ahmet Gokdayi

229

ケンの答えに基づいて、これが最もモジュラーなコピーアンドペーストソリューションです。

XMLは必要ありません。

それをアクティビティに配置すると、そのアクティビティ内のフラグメント内のものを含むすべてのEditTextに適用されます。

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
        View v = getCurrentFocus();
        if ( v instanceof EditText) {
            Rect outRect = new Rect();
            v.getGlobalVisibleRect(outRect);
            if (!outRect.contains((int)event.getRawX(), (int)event.getRawY())) {
                v.clearFocus();
                InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
                imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
            }
        }
    }
    return super.dispatchTouchEvent( event );
}

12
元気そうです。それでも問題が1つあります。すべての編集テキストはスクロールビュー内にあり、上部の編集テキストには常にカーソルが表示されています。外側をクリックしても、フォーカスが失われ、キーボードがなくなりますが、カーソルは依然として上部の編集テキストに表示されています。
Vaibhav Gupta、

1
私が出会った最高のソリューション!! 以前は、@ Override public boolean onTouch(View v、MotionEvent event){if(v.getId()!= search_box.getId()){if(search_box.hasFocus()){InputMethodManager imm =(InputMethodManager)getSystemService( Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(search_box.getWindowToken()、0); search_box.clearFocus(); } falseを返します。} falseを返します。}
Pradeep Kumar Kushwaha

シンプル、簡潔、機能的!確かに最善の解決策。ありがとう
Aleksandar

13
これまでに見たことのない最高のソリューション!このコードを要旨に保存して、息子と孫に渡します。
ファンApplelin、2018年

2
ユーザーが別のEditTextをクリックすると、キーボードが閉じてすぐに再び開きます。これを解決するために、私はあなたのコードを変更MotionEvent.ACTION_DOWNMotionEvent.ACTION_UPました。素晴らしい答えです!
Thanasis1101

35

EditTextの親ビューの場合は、以下の3つの属性が「なりましょう:」
クリッカブルフォーカス可能focusableInTouchMode

ビューがフォーカスを受け取りたい場合、これらの3つの条件を満たす必要があります。

android.viewを参照してください:

public boolean onTouchEvent(MotionEvent event) {
    ...
    if (((viewFlags & CLICKABLE) == CLICKABLE || 
        (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
        ...
        if (isFocusable() && isFocusableInTouchMode()
            && !isFocused()) {
                focusTaken = requestFocus();
        }
        ...
    }
    ...
}

それが役に立てば幸い。



2
フォーカスをクリアするには、他のフォーカス可能なビューがフォーカスされたビューの親でなければなりません。フォーカスするビューが別の階層にある場合は機能しません。
AxeEffect 2014

技術的には、ステートメントは正しくありません。親ビューは(clickable) *OR* (focusable *AND* focusableInTouchMode);)でなければなりません
Martin Marconcini

24

これらのプロパティを最上位の親に配置するだけです。

android:focusableInTouchMode="true"
android:clickable="true"
android:focusable="true" 

ありがとう。私はこの問題を最後の2時間から苦労していました。これらの行を最上位の親に追加するとうまくいきました。
デニッシュシェル

参考:それはほとんどの場合に機能しますが、レイアウトのいくつかの内部要素には機能しません。
SVマダバレディ

16

ケンの答えは機能しますが、ハックです。回答のコメントでpcansが言及しているように、dispatchTouchEventでも同じことができます。このソリューションは、透明なダミーのFrameLayoutでXMLをハッキングする必要がないため、よりクリーンです。これは次のようになります。

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
    EditText mEditText = findViewById(R.id.mEditText);
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
        View v = getCurrentFocus();
        if (mEditText.isFocused()) {
            Rect outRect = new Rect();
            mEditText.getGlobalVisibleRect(outRect);
            if (!outRect.contains((int)event.getRawX(), (int)event.getRawY())) {
                mEditText.clearFocus();
                //
                // Hide keyboard
                //
                InputMethodManager imm = (InputMethodManager) v.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 
                imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
            }
        }
    }
    return super.dispatchTouchEvent(event);
}

7

私のために以下のことがうまくいった-

1. 追加android:clickable="true"android:focusableInTouchMode="true"parentLayoutEditTextすなわちandroid.support.design.widget.TextInputLayout

<android.support.design.widget.TextInputLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:clickable="true"
    android:focusableInTouchMode="true">
<EditText
    android:id="@+id/employeeID"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:ems="10"
    android:inputType="number"
    android:hint="Employee ID"
    tools:layout_editor_absoluteX="-62dp"
    tools:layout_editor_absoluteY="16dp"
    android:layout_marginTop="42dp"
    android:layout_alignParentTop="true"
    android:layout_alignParentRight="true"
    android:layout_alignParentEnd="true"
    android:layout_marginRight="36dp"
    android:layout_marginEnd="36dp" />
    </android.support.design.widget.TextInputLayout>

2. ActivityクラスでのオーバーライドdispatchTouchEventhideKeyboard()関数の挿入

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            View view = getCurrentFocus();
            if (view != null && view instanceof EditText) {
                Rect r = new Rect();
                view.getGlobalVisibleRect(r);
                int rawX = (int)ev.getRawX();
                int rawY = (int)ev.getRawY();
                if (!r.contains(rawX, rawY)) {
                    view.clearFocus();
                }
            }
        }
        return super.dispatchTouchEvent(ev);
    }

    public void hideKeyboard(View view) {
        InputMethodManager inputMethodManager =(InputMethodManager)getSystemService(Activity.INPUT_METHOD_SERVICE);
        inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
    }

3. EditTextの追加setOnFocusChangeListener

EmployeeId.setOnFocusChangeListener(new View.OnFocusChangeListener() {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                if (!hasFocus) {
                    hideKeyboard(v);
                }
            }
        });

5

あなたはおそらくこの問題の答えをすでに見つけたでしょうが、私はこれを解決する方法を探していましたが、それでも私が探していたものを正確に見つけることができないので、ここに投稿すると思いました。

私がしたことは次のとおりです(これは非常に一般化されています。目的は、続行する方法のアイデアを与えることです。すべてのコードをコピーして貼り付けるとO:Dは機能しません):

最初に、EditTextとプログラムで必要なその他のビューを単一のビューでラップします。私の場合、LinearLayoutを使用してすべてをラップしました。

<LinearLayout 
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/mainLinearLayout">
 <EditText
  android:id="@+id/editText"/>
 <ImageView
  android:id="@+id/imageView"/>
 <TextView
  android:id="@+id/textView"/>
 </LinearLayout>

次に、コードでタッチリスナーをメインのLinearLayoutに設定する必要があります。

final EditText searchEditText = (EditText) findViewById(R.id.editText);
mainLinearLayout.setOnTouchListener(new View.OnTouchListener() {

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            // TODO Auto-generated method stub
            if(searchEditText.isFocused()){
                if(event.getY() >= 72){
                    //Will only enter this if the EditText already has focus
                    //And if a touch event happens outside of the EditText
                    //Which in my case is at the top of my layout
                    //and 72 pixels long
                    searchEditText.clearFocus();
                    InputMethodManager imm = (InputMethodManager) v.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
                    imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
                }
            }
            Toast.makeText(getBaseContext(), "Clicked", Toast.LENGTH_SHORT).show();
            return false;
        }
    });

これが一部の人の役に立てば幸いです。または、少なくとも彼らが問題を解決し始めるのを助けます。


はい、私も同様の解決策を見つけました。次に、android表示システムのサブセットをopenglのc ++に移植し、この機能をビルドしました)
note173

これは本当に非常にきちんとした例です。このようなコードを探していましたが、複雑な解決策しか見つかりませんでした。複数行のEditTextもほとんどなかったので、XY座標をより正確に使用しました。ありがとう!
Sumitk 2012

3

その親の2つのプロパティを次のように定義するだけEditTextです。

android:clickable="true"
android:focusableInTouchMode="true"

したがって、ユーザーがEditText領域の外側に触れると、フォーカスが親ビューに転送されるため、フォーカスが削除されます。


2

私は意見でListView構成されていEditTextます。このシナリオでは、1つ以上の行のテキストを編集した後、「終了」というボタンをクリックする必要があると述べています。私onFocusChangedEditText内部のビューで使用しましたlistViewが、完了をクリックした後、データは保存されていません。追加することで問題は解決しました

listView.clearFocus();

onClickListener「完了」ボタンの内部にあり、データは正常に保存されました。


ありがとう。あなたは私の時間を節約しました、私はこのシナリオをrecyclerviewに持っていて、ボタンをクリックした後に最後のeditText値を失っていました、最後のeditTextフォーカスが変更されなかったため、onFocusChangedが呼び出されなかったためです。最後のeditTextが外側にあるときにフォーカスを失う、同じボタンクリックで解決策を見つけたい...そしてあなたの解決策を見つけてください。それは素晴らしい働きをしました!
Reyhane Farshbaf

2

私は実際にそれを使用するための、より堅牢な方法だと思うgetLocationOnScreenよりgetGlobalVisibleRect。問題に遭遇したからです。Edittextを含みajustpan、アクティビティに設定されたリストビューがあります。私は見つけるgetGlobalVisibleRectこと、それにscrollYを含むように見えるが値を返しますが、event.getRawYは、画面によって常にあります。以下のコードはうまくいきます。

public boolean dispatchTouchEvent(MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
        View v = getCurrentFocus();
        if ( v instanceof EditText) {
            if (!isPointInsideView(event.getRawX(), event.getRawY(), v)) {
                Log.i(TAG, "!isPointInsideView");

                Log.i(TAG, "dispatchTouchEvent clearFocus");
                v.clearFocus();
                InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
                imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
            }
        }
    }
    return super.dispatchTouchEvent( event );
}

/**
 * Determines if given points are inside view
 * @param x - x coordinate of point
 * @param y - y coordinate of point
 * @param view - view object to compare
 * @return true if the points are within view bounds, false otherwise
 */
private boolean isPointInsideView(float x, float y, View view) {
    int location[] = new int[2];
    view.getLocationOnScreen(location);
    int viewX = location[0];
    int viewY = location[1];

    Log.i(TAG, "location x: " + location[0] + ", y: " + location[1]);

    Log.i(TAG, "location xWidth: " + (viewX + view.getWidth()) + ", yHeight: " + (viewY + view.getHeight()));

    // point is inside view bounds
    return ((x > viewX && x < (viewX + view.getWidth())) &&
            (y > viewY && y < (viewY + view.getHeight())));
}

どうもありがとうございます!RecyclerViewなどのすべてのレイアウトシナリオで機能しました。他の解決策を試しましたが、これはすべてに当てはまります。:) よくやった!
Woppi 2017年

1

他のビューがタッチされたときにフォーカスを失うには、両方のビューをview.focusableInTouchMode(true)として設定する必要があります。

ただし、タッチモードでのフォーカスの使用は推奨されないようです。こちらをご覧ください:http : //android-developers.blogspot.com/2008/12/touch-mode.html


はい、これを読みましたが、私が望んでいるものではありません。集中するために他の視点は必要ありません。現在、別の方法は表示されませんが...たぶん、ユーザーがフォーカスできない子をクリックしたときに、どういうわけかルートビューをフォーカスを取得するようにしますか?
note173

私が知っている限り、フォーカスはクラスによって管理することはできません、それはアンドロイドによって管理されます...アイデアはありません:(
forumercio

0

最善の方法は、デフォルトの方法を使用することです clearFocus()

あなたはコードを解決する方法を知っています onTouchListener正しくか?

電話するだけEditText.clearFocus()。でフォーカスがクリアされlast EditTextます。


0

@pcansが提案したように、これをオーバーライドすることができます dispatchTouchEvent(MotionEvent event)に、アクティビティでます。

ここで、タッチ座標を取得し、それらをビューの境界と比較します。ビューの外でタッチが実行された場合は、何かを行います。

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
        View yourView = (View) findViewById(R.id.view_id);
        if (yourView != null && yourView.getVisibility() == View.VISIBLE) {
            // touch coordinates
            int touchX = (int) event.getX();
            int touchY = (int) event.getY();
            // get your view coordinates
            final int[] viewLocation = new int[2];
            yourView.getLocationOnScreen(viewLocation);

            // The left coordinate of the view
            int viewX1 = viewLocation[0];
            // The right coordinate of the view
            int viewX2 = viewLocation[0] + yourView.getWidth();
            // The top coordinate of the view
            int viewY1 = viewLocation[1];
            // The bottom coordinate of the view
            int viewY2 = viewLocation[1] + yourView.getHeight();

            if (!((touchX >= viewX1 && touchX <= viewX2) && (touchY >= viewY1 && touchY <= viewY2))) {

                Do what you want...

                // If you don't want allow touch outside (for example, only hide keyboard or dismiss popup) 
                return false;
            }
        }
    }
    return super.dispatchTouchEvent(event);
}

また、実行時にアクティビティのレイアウトが変更されない場合(たとえば、フラグメントを追加したり、レイアウトからビューを置き換えたり削除したりしない場合)は、ビューの存在と可視性を確認する必要はありません。ただし、(アイテムのオーバーフローメニューを使用するときのGoogle Playストアのように)カスタムコンテキストメニューを閉じる(または同様のことを行う)場合は、ビューの存在を確認する必要があります。それ以外の場合はを取得しNullPointerExceptionます。


0

この簡単なコードのスニペットはあなたが望むことをします

GestureDetector gestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
            @Override
            public boolean onSingleTapConfirmed(MotionEvent e) {
                KeyboardUtil.hideKeyboard(getActivity());
                return true;
            }
        });
mScrollView.setOnTouchListener((v, e) -> gestureDetector.onTouchEvent(e));

0

コトリンで

hidekeyboard()はKotlin拡張機能です

fun Activity.hideKeyboard() {
    hideKeyboard(currentFocus ?: View(this))
}

アクティビティに追加 dispatchTouchEventをします

override fun dispatchTouchEvent(event: MotionEvent): Boolean {
    if (event.action == MotionEvent.ACTION_DOWN) {
        val v: View? = currentFocus
        if (v is EditText) {
            val outRect = Rect()
            v.getGlobalVisibleRect(outRect)
            if (!outRect.contains(event.rawX.toInt(), event.rawY.toInt())) {
                v.clearFocus()
                hideKeyboard()
            }
        }
    }
    return super.dispatchTouchEvent(event)
}

これらのプロパティを最上位の親に追加します

android:focusableInTouchMode="true"
android:focusable="true"

0

これは、zManのコードに基づく私のバージョンです。次のビューも編集テキストの場合、キーボードは非表示になりません。また、ユーザーが画面をスクロールするだけの場合、キーボードは非表示になりません。

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
        downX = (int) event.getRawX();
    }

    if (event.getAction() == MotionEvent.ACTION_UP) {
        View v = getCurrentFocus();
        if (v instanceof EditText) {
            int x = (int) event.getRawX();
            int y = (int) event.getRawY();
            //Was it a scroll - If skip all
            if (Math.abs(downX - x) > 5) {
                return super.dispatchTouchEvent(event);
            }
            final int reducePx = 25;
            Rect outRect = new Rect();
            v.getGlobalVisibleRect(outRect);
            //Bounding box is to big, reduce it just a little bit
            outRect.inset(reducePx, reducePx);
            if (!outRect.contains(x, y)) {
                v.clearFocus();
                boolean touchTargetIsEditText = false;
                //Check if another editText has been touched
                for (View vi : v.getRootView().getTouchables()) {
                    if (vi instanceof EditText) {
                        Rect clickedViewRect = new Rect();
                        vi.getGlobalVisibleRect(clickedViewRect);
                        //Bounding box is to big, reduce it just a little bit
                        clickedViewRect.inset(reducePx, reducePx);
                        if (clickedViewRect.contains(x, y)) {
                            touchTargetIsEditText = true;
                            break;
                        }
                    }
                }
                if (!touchTargetIsEditText) {
                    InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
                    imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
                }
            }
        }
    }
    return super.dispatchTouchEvent(event);
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.