web-dev-qa-db-ja.com

EditText、外のタッチに明確な焦点

レイアウトには、ListViewSurfaceView、およびEditTextが含まれています。 EditTextをクリックすると、フォーカスが表示され、画面上のキーボードがポップアップします。 EditTextの外側のどこかをクリックしても、フォーカスがあります(そうすべきではありません)。レイアウトの他のビューにOnTouchListenerを設定し、EditTextのフォーカスを手動でクリアできると思います。しかし、あまりにもハックが多いようです...

他のレイアウトにも同じ状況があります-さまざまな種類のアイテムが含まれるリストビューで、一部にはEditTextが含まれています。上で書いたように動作します。

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

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

85
note173

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

touchInterceptorと呼ばれる(見えない)FrameLayoutをレイアウトの最後のViewとして作成し、すべてをオーバーレイするようにしました(- 編集:また、親レイアウトとしてRelativeLayoutを使用し、touchInterceptorfill_parent属性を指定する必要があります)。次に、それを使用してタッチをインターセプトし、タッチがEditTextの上にあるかどうかを判断します。

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を返すと、タッチ処理が失敗します。

それはハックですが、それは私のために働いた唯一のものです。

64
Ken

Kenの答えに基づいて、最もモジュール化されたコピーアンドペーストソリューションを紹介します。

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

EditTextの親ビューの場合、次の3つの属性を「true」にします。
クリック可能フォーカス可能focusableInTouchMode

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

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

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

それが役に立てば幸い。

32
suber

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

Android:focusableInTouchMode="true"
Android:clickable="true"
Android:focusable="true" 
18
chandan

ケンの答えはうまくいきますが、それはハッキーです。 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);
}
15
Mike Ortiz

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

私がやったことは次のことでした(これは非常に一般化されており、目的はすべてのコードをコピーして貼り付ける方法が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>

次に、コードでTouch Listenerをメインの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;
        }
    });

これが一部の人々に役立つことを願っています。または、少なくとも問題の解決を開始するのに役立ちます。

5
edc598

私の下で働いていたもの-

1 .parentLayouti.e Android:clickable="true"EditTextAndroid:focusableInTouchMode="true"および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 .アクティビティクラスでdispatchTouchEventをオーバーライドし、hideKeyboard()関数を挿入

@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);
                }
            }
        });
4
Adarsh Gumashta

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

Android:clickable="true"
Android:focusableInTouchMode="true"

したがって、ユーザーがEditTextエリアの外側をタッチすると、フォーカスが親ビューに転送されるため、フォーカスが削除されます。

2
Dhruvam Gupta

getLocationOnScreenよりもgetGlobalVisibleRectを使用する方がより堅牢な方法だと思います。問題に遭遇したからです。 Edittextを含むListviewがあり、アクティビティに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())));
}
2
Victor Choy

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

listView.clearFocus();

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

1

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

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

1
forumercio

@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
Sabre

最良の方法は、デフォルトのメソッド clearFocus() を使用することです

onTouchListenerのコードを解決する方法を知っていますか?

EditText.clearFocus()を呼び出すだけです。 last EditTextのフォーカスがクリアされます。

0
Huy Tower

このシンプルなコードスニペットは、あなたが望むことをします

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
Andrii