EditText, xóa tiêu điểm khi chạm bên ngoài


100

Bố cục của tôi chứa ListView, SurfaceViewEditText. Khi tôi nhấp vào EditText, nó nhận được tiêu điểm và bàn phím trên màn hình bật lên. Khi tôi nhấp vào một nơi nào đó bên ngoài EditText, nó vẫn có tiêu điểm (không nên). Tôi đoán tôi có thể thiết lập OnTouchListenercác chế độ xem khác trong bố cục và xóa tiêu điểm theo cách thủ công EditText. Nhưng có vẻ quá hack ...

Tôi cũng gặp trường hợp tương tự trong chế độ xem danh sách bố cục khác với các loại mục khác nhau, một số trong số đó có EditTextbên trong. Họ hành động giống như tôi đã viết ở trên.

Nhiệm vụ là làm cho EditTextmất tập trung khi người dùng chạm vào thứ gì đó bên ngoài nó.

Tôi đã thấy các câu hỏi tương tự ở đây, nhưng không tìm thấy giải pháp nào ...

Câu trả lời:


66

Tôi đã thử tất cả các giải pháp này. edc598's là gần nhất để hoạt động, nhưng các sự kiện chạm không kích hoạt trên các ứng Viewdụng khác có trong bố cục. Trong trường hợp bất kỳ ai cần hành vi này, đây là những gì tôi đã làm:

Tôi đã tạo một (ẩn) FrameLayoutđược gọi là touchInterceptorView ở vị trí cuối cùng trong bố cục để nó phủ lên mọi thứ ( chỉnh sửa: bạn cũng phải sử dụng a RelativeLayoutlàm bố cục mẹ và cung cấp các thuộc tính touchInterceptor fill_parent ). Sau đó, tôi sử dụng nó để chặn các lần chạm và xác định xem lần chạm có ở trên EditTexthay không:

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

Trả về false để thực hiện thao tác chạm.

Thật khó hiểu, nhưng đó là thứ duy nhất phù hợp với tôi.


20
Bạn có thể làm điều tương tự mà không cần thêm một bố cục bổ sung bằng cách ghi đè DispTouchEvent (MotionEvent ev) trong hoạt động của mình.
quét

cảm ơn cho clearFocus () gợi ý, nó cũng làm việc cho tôi trên SearchView
Jimmy Ilenloa

1
Tôi muốn gợi ý câu trả lời của @ zMan bên dưới. Nó dựa trên điều này, nhưng không cần chế độ xem stackoverflow.com/a/28939113/969016
Boy

Ngoài ra, nếu cuộc gặp gỡ bất cứ ai với một tình huống mà bàn phím không hề giấu giếm nhưng trọng tâm thanh toán bù trừ, invoke đầu tiên hideSoftInputFromWindow()sau đó mục tiêu rõ ràng
Ahmet Gokdayi

229

Dựa trên câu trả lời của Ken, đây là giải pháp sao chép và dán mô-đun nhất.

Không cần XML.

Đặt nó trong Hoạt động của bạn và nó sẽ áp dụng cho tất cả các EditTexts bao gồm cả những bảng trong các đoạn trong hoạt động đó.

@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
Trông có vẻ ổn. Tôi vẫn gặp một vấn đề, Tất cả văn bản chỉnh sửa của tôi đều nằm trong chế độ xem cuộn và văn bản chỉnh sửa trên cùng luôn hiển thị con trỏ. Ngay cả khi tôi nhấp chuột ra ngoài, tiêu điểm bị mất và bàn phím không còn nữa, nhưng con trỏ vẫn hiển thị trong văn bản chỉnh sửa trên cùng.
Vaibhav Gupta

1
Giải pháp tốt nhất mà tôi đã xem qua !! Trước đó, tôi đã sử dụng @Override public boolean onTouch (View v, sự kiện MotionEvent) {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 (); } trả về false; } trả về false; }
Pradeep Kumar Kushwaha

Đơn giản, ngắn gọn, chức năng! Giải pháp tốt nhất chắc chắn. Cảm ơn
Aleksandar

13
Giải pháp tốt nhất mà tôi chưa từng thấy trước đây! Tôi sẽ lưu mã này vào ý chính của mình để chuyển cho con trai và cháu trai của tôi.
Người hâm mộ Applelin vào

2
Nếu người dùng nhấp vào một EditText khác thì bàn phím sẽ đóng và mở lại ngay lập tức. Để giải quyết vấn tôi này đã thay đổi MotionEvent.ACTION_DOWNđến MotionEvent.ACTION_UPtrên mã của bạn. Câu trả lời tuyệt vời btw!
Thanasis1101

35

Đối với chế độ xem gốc của EditText, hãy để 3 thuộc tính sau là " true ":
clickable , focusable , focusableInTouchMode .

Nếu một khung nhìn muốn nhận được tiêu điểm thì nó phải thỏa mãn 3 điều kiện này.

Xem android.view :

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

Hy vọng nó giúp.



2
Để xóa tiêu điểm, chế độ xem có thể lấy tiêu điểm khác phải là nguồn gốc của chế độ xem tiêu điểm. Nó sẽ không hoạt động nếu chế độ xem cần lấy nét nằm trên một hệ thống phân cấp khác.
AxeEffect

Về mặt kỹ thuật, tuyên bố của bạn là không chính xác. Chế độ xem phụ huynh phải được (clickable) *OR* (focusable *AND* focusableInTouchMode);)
Martin Marconcini

24

Chỉ cần đặt các thuộc tính này trong top cha mẹ nhất.

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

cảm ơn anh bạn. tôi đã đấu tranh vấn đề này từ hai giờ qua. Thêm những dòng này vào dòng cha trên cùng đã hoạt động.
Däñish Shärmà

FYI: Mặc dù nó hoạt động trong hầu hết các trường hợp, nhưng nó không hoạt động đối với một số yếu tố bên trong của bố cục.
SV Madhava Reddy

16

Câu trả lời của Ken hoạt động, nhưng nó là hacky. Khi quét ám chỉ đến nhận xét của câu trả lời, điều tương tự cũng có thể được thực hiện với DispatchTouchEvent. Giải pháp này rõ ràng hơn vì nó tránh phải hack XML bằng FrameLayout giả, minh bạch. Đây là những gì trông giống như:

@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

Đối với tôi Dưới đây mọi thứ đã hoạt động -

1. Bổ sung android:clickable="true"android:focusableInTouchMode="true"đến parentLayoutcủa EditTextví dụ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. Ghi đè dispatchTouchEventtrong lớp Hoạt động và hideKeyboard()chức năng chèn

@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. Thêm setOnFocusChangeListenercho EditText

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

5

Bạn có thể đã tìm thấy câu trả lời cho vấn đề này nhưng tôi đang tìm cách giải quyết vấn đề này và vẫn không thể thực sự tìm thấy chính xác những gì tôi đang tìm kiếm vì vậy tôi nghĩ rằng tôi sẽ đăng nó ở đây.

Những gì tôi đã làm là như sau (điều này rất tổng quát, mục đích là để cung cấp cho bạn ý tưởng về cách tiếp tục, sao chép và dán tất cả mã sẽ không hoạt động O: D):

Trước tiên hãy có EditText và bất kỳ dạng xem nào khác mà bạn muốn trong chương trình của mình được bao bọc bởi một dạng xem duy nhất. Trong trường hợp của tôi, tôi đã sử dụng LinearLayout để gói mọi thứ.

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

Sau đó, trong mã của bạn, bạn phải đặt Trình nghe cảm ứng thành LinearLayout chính của bạn.

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

Tôi hy vọng điều này sẽ giúp một số người. Hoặc ít nhất là giúp họ bắt đầu giải quyết vấn đề của họ.


Vâng, tôi cũng đã đi đến giải pháp tương tự. Sau đó, tôi đã chuyển một tập hợp con của hệ thống xem android sang c ++ trên opengl và xây dựng tính năng này trong đó)
note173 23/12/11

Đây thực sự là một ví dụ rất gọn gàng. Tôi đang tìm kiếm một đoạn mã như thế này nhưng tất cả những gì tôi có thể tìm thấy là các giải pháp phức tạp. Tôi đã sử dụng cả tọa độ XY để chính xác hơn vì tôi cũng có một vài EditText nhiều dòng. Cảm ơn!
Sumitk

3

Đơn giản chỉ cần xác định hai thuộc tính của cha của nó EditTextlà:

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

Vì vậy, khi người dùng chạm vào bên ngoài EditTextkhu vực, tiêu điểm sẽ bị loại bỏ vì tiêu điểm sẽ được chuyển sang chế độ xem chính.


2

Tôi có một ListViewsố EditTextlượt xem. Kịch bản nói rằng sau khi chỉnh sửa văn bản trong một hoặc nhiều hàng, chúng ta nên nhấp vào một nút gọi là "kết thúc". Tôi đã sử dụng onFocusChangedEditTextchế độ xem bên trong listViewnhưng sau khi nhấp vào kết thúc, dữ liệu không được lưu. Vấn đề đã được giải quyết bằng cách thêm

listView.clearFocus();

bên trong onClickListenernút "kết thúc" và dữ liệu đã được lưu thành công.


Cảm ơn. Bạn đã tiết kiệm thời gian của tôi, tôi đã gặp trường hợp này trong chế độ xem lại và đang mất giá trị editText cuối cùng sau khi nhấp vào nút, vì tiêu điểm editText cuối cùng không thay đổi và do đó, FocusChanged không được gọi. Tôi muốn tìm một giải pháp làm cho editText cuối cùng sẽ mất tiêu điểm khi ở bên ngoài nó, cùng một nút bấm ... và tìm giải pháp của bạn. Nó, s hoạt động tuyệt vời!
Reyhane Farshbaf

2

Tôi thực sự nghĩ rằng đó là một cách mạnh mẽ hơn để sử dụng getLocationOnScreenhơn getGlobalVisibleRect. Bởi vì tôi gặp một vấn đề. Có một Listview chứa một số Edittext và và thiết lập ajustpantrong hoạt động. Tôi thấy getGlobalVisibleRecttrả về một giá trị giống như bao gồm scrollY trong đó, nhưng event.getRawY luôn ở trên màn hình. Đoạn mã dưới đây hoạt động tốt.

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

cảm ơn bạn rất nhiều! Nó hoạt động cho tất cả các kịch bản bố cục như RecyclerView, v.v. Tôi đã thử giải pháp khác nhưng giải pháp này phù hợp với tất cả! :) Bạn đã làm rất tốt!
Woppi

1

Để mất tiêu điểm khi chế độ xem khác bị chạm vào, cả hai chế độ xem phải được đặt thành view.focusableInTouchMode (true).

Nhưng có vẻ như việc sử dụng lấy nét ở chế độ cảm ứng không được khuyến khích. Vui lòng xem tại đây: http://android-developers.blogspot.com/2008/12/touch-mode.html


Vâng, tôi đã đọc nó, nhưng nó không chính xác những gì tôi muốn. Tôi không cần góc nhìn khác để trở nên tập trung. Mặc dù tôi không thấy bất kỳ cách nào thay thế ngay bây giờ ... Có thể, bằng cách nào đó làm cho chế độ xem gốc để lấy nét khi người dùng nhấp vào con không thể lấy nét?
note173

Miễn là tôi biết, tiêu điểm không thể được quản lý bởi một lớp, nó được quản lý bởi android ... không có ý tưởng :(
forumercio

0

Cách tốt nhất là sử dụng phương pháp mặc định clearFocus()

Bạn biết làm thế nào để giải mã trong onTouchListenerquyền?

Chỉ cần gọi EditText.clearFocus(). Nó sẽ rõ ràng tiêu điểm trong last EditText.


0

Như @pcans đã đề xuất, bạn có thể thực hiện việc ghi đè này dispatchTouchEvent(MotionEvent event)trong hoạt động của mình.

Ở đây chúng tôi lấy tọa độ cảm ứng và so sánh chúng để xem giới hạn. Nếu thao tác chạm được thực hiện bên ngoài khung nhìn thì hãy làm gì đó.

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

Ngoài ra, không cần thiết phải kiểm tra sự tồn tại và khả năng hiển thị của chế độ xem nếu bố cục hoạt động của bạn không thay đổi trong thời gian chạy (ví dụ: bạn không thêm các phân đoạn hoặc thay thế / xóa các chế độ xem khỏi bố cục). Nhưng nếu bạn muốn đóng (hoặc làm điều gì đó tương tự) menu ngữ cảnh tùy chỉnh (như trong Cửa hàng Google Play khi sử dụng menu mục bổ sung của mục) thì cần phải kiểm tra sự tồn tại của chế độ xem. Nếu không, bạn sẽ nhận được một NullPointerException.


0

Đoạn mã đơn giản này thực hiện những gì bạn muốn

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

Trong Kotlin

hidekeyboard () là một phần mở rộng Kotlin

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

Trong hoạt động, hãy thêm 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)
}

Thêm các thuộc tính này vào trang gốc nhiều nhất

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

0

Đây là Phiên bản của tôi dựa trên mã của zMan. Nó sẽ không ẩn bàn phím nếu chế độ xem tiếp theo cũng là một văn bản chỉnh sửa. Nó cũng sẽ không ẩn bàn phím nếu người dùng chỉ cuộn màn hình.

@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);
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.