Android EditText光标位置变化的监听器

54

我有一个带有EditText的对话框,当创建时EditText已经被填充。 当用户将光标放在文本的某些部分附近时,会弹出Toast。

我的问题是如何监听光标位置的变化。 另一篇文章问了同样的问题,被接受的解决方案为:

您可以重写onSelectionChanged(int selStart,int selEnd)以便得到选择更改的通知。 如果移动光标,则也会调用此方法(在此情况下selStart == selEnd)

onSelectionChanged(int selStart,int selEnd)是TextView类的一个受保护的方法。 如何覆盖它?


1
我在我的项目中已经成功地做到了这一点,但是我刚刚发现,在某些移动设备上,onSelectionChanged()被调用了,而在其他设备上却没有被调用。两者都是Android 4.0以上的版本。 - Andrew Mackenzie
9个回答

40
只需继承或扩展EditText类,并将以下代码添加到新创建的类中:
@Override 
protected void onSelectionChanged(int selStart, int selEnd) {
    super.onSelectionChanged(selStart, selEnd); // Always call
    // the super implementation, which informs the accessibility 
    // subsystem about the selection change.

    // Do ur task here.
}

别忘了在子类中添加构造函数。 :)

1
谢谢大师,很好地解决了问题。如果有人感兴趣,我已经在原来的问题中附加了我的操作细节。 - Mel
1
当我把这段代码放入我的程序中时,出现了一个android.view.InflateException: Binary XML file的错误。你有任何想法我是做错了什么吗? - Kamran Ahmed
如果您无法使用子类(例如,在“SearchView”中监听光标位置的更改,这是一个嵌套有“EditText”的复杂布局),则可能无法正常工作。 - Ted Hopp
@TedHopp 请看一下这个答案:https://dev59.com/v2025IYBdhLWcg3whGSx#36389070 - Michael
对于任何遇到 @KamranAhmed 问题的人:您是否实现了三个必需的构造函数?您的 XML 元素是否指向项目中的正确包? - Xavier L.
太好了,这样做没有可靠的方法?深入研究TextView代码(EditText从中继承),答案中的所有方法都是相同的东西,这意味着它们都有相同的问题,并且可能在某些设备上无法正常工作。我完成了。 - cmak

18

你可以不用子类化 EditText 来监听选择更改。虽然有点复杂,但仍然可行。要做到这一点,您需要向文本添加一个 SpanWatcher 并处理选择跨度的更改。

final SpanWatcher watcher = new SpanWatcher() {
  @Override
  public void onSpanAdded(final Spannable text, final Object what,
      final int start, final int end) {
    // Nothing here.
  }

  @Override
  public void onSpanRemoved(final Spannable text, final Object what, 
      final int start, final int end) {
    // Nothing here.
  }

  @Override
  public void onSpanChanged(final Spannable text, final Object what, 
      final int ostart, final int oend, final int nstart, final int nend) {
    if (what == Selection.SELECTION_START) {
      // Selection start changed from ostart to nstart. 
    } else if (what == Selection.SELECTION_END) {
      // Selection end changed from ostart to nstart. 
    }
  }
};

editText.getText().setSpan(watcher, 0, 0, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);

1
SpanWatcher只会被通知其范围内的更改吗?为什么是INCLUSIVE_EXCLUSIVE? - natario
5
不,它会通知所有更改。它是“包容性的”,因为当你清除“EditText”时,它不应该被移除。 - Michael
3
请注意,如果您调用了 setText,则监视器将会丢失。 - mhsmith
1
您可以通过在TextWatcherafterTextChanged方法中重新添加它来避免这种情况。 - mhsmith
在 OnePlus 7,API 31 上移动光标但未选择任何内容时不会触发。 - Jure Sencar

17

如果仍在寻找一种不需子类化EditText的解决方案:

(代码使用 Kotlin 编写)

    editText.setAccessibilityDelegate(object : View.AccessibilityDelegate() {
        override fun sendAccessibilityEvent(host: View?, eventType: Int) {
            super.sendAccessibilityEvent(host, eventType)
            if (eventType == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED){
                // here you can access selection of the editText 
                // with `editText.selectionStart`
                // and `editText.selectionEnd``
            }
        }
    })

哇,它完美地工作了...但文档说:“如果未启用辅助功能,则此方法无效。” - mifthi
2
@mifthi 你可以使用sendAccessibilityEventUnchecked,因为它不检查辅助功能是否已启用。 - Saeed Entezari
@SaeedEntezari 是的,我都试过了。对于原生 Android,会调用 sendAccessibilityEvent,但对于三星手机 - 两者都不行。 - Kirill Bubochkin
在安卓9系统的三星设备上经过测试,sendAccessibilityEvent方法没有被调用。 - vovahost
如果有人需要Java,可以使用以下代码:editor.setAccessibilityDelegate(new View.AccessibilityDelegate(){
@Override public void sendAccessibilityEvent(View host, int eventType) { super.sendAccessibilityEvent(host, eventType); if (eventType == android.view.accessibility.AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED){ } else { } } });
- Hasni
显示剩余2条评论

13

我在不同版本的Android上调试了一个相关问题(欢迎评论您的发现,我将把它们添加到列表中)。

发现总结:


4.1.6(三星设备)

onSelectionChanged() 仅在文本编辑时被调用。


4.0.6(HTC设备)

4.0.4(由Samsung Note 1设备的用户Arch1tect报告)

onSelectionChanged() 在光标更改(单击、移动等),但不在文本编辑时被调用。


在上述情况中,您没有收到关于文本编辑的通知(在某些Android版本中),您将不得不使用TextWatcher来完成这个操作,例如在afterTextChanged()方法中。


1
我发现了一个相同的问题:三星4.04(Note 1):在光标更改(点击,移动等)时会调用onSelectionChanged(),但不会在文本编辑时调用。 - Arch1tect

11

哦,太感谢您提供这个想法了。这个功能没有理由不应该在SDK中。我有一个快速子类实现了这个思路,并添加了选择更改时的监听器功能。希望它能有用。

public class EditTextSelectable extends EditText {

    public interface OnSelectionChangedListener {
         public void onSelectionChanged(int selStart, int selEnd);
    }

    private List<onSelectionChangedListener> listeners
        = new ArrayList<onSelectionChangedListener>();

    public void addOnSelectionChangedListener(OnSelectionChangedListener l) {
        listeners.add(l);
    }

    public void removeOnSelectionChangedListener(OnSelectionChangedListener l) {
        listeners.remove(l);
    }

    @Override
    protected void onSelectionChanged(int selStart, int selEnd) {
        for (onSelectionChangedListener l : listeners)
            l.onSelectionChanged(selStart, selEnd);     
        }
    }

}

1
你好,能帮我解释一下如何在Activity中设置OnSelectionChangedListener吗? - SWAppDev
@SWAppDev editText.addOnSelectionChangedListener(this) 不要忘记从EditTextSelectable.onSelectionChangedListener中实现您的活动。 - AlexS

4
上述答案的Java版本:
mEtEditor.setAccessibilityDelegate(new View.AccessibilityDelegate(){
        /**
         * Sends an accessibility event of the given type. If accessibility is not
         * enabled this method has no effect.
         * <p>
         * The default implementation behaves as {@link View#sendAccessibilityEvent(int)
         * View#sendAccessibilityEvent(int)} for the case of no accessibility delegate
         * been set.
         * </p>
         *
         * @param host      The View hosting the delegate.
         * @param eventType The type of the event to send.
         * @see View#sendAccessibilityEvent(int) View#sendAccessibilityEvent(int)
         */
        @Override
        public void sendAccessibilityEvent(View host, int eventType) {
            super.sendAccessibilityEvent(host, eventType);
            if (eventType == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED){
                // here you can access selection of the editText
                // with `editText.selectionStart`
                // and `editText.selectionEnd``                    
            }
        }
    });

3
这是一个关于 @Saeed Entezari 发布的 内容的 扩展版
"最初的回答"
fun EditText.setSelectionChangedListener(onSelectionChangedListener: (editText: EditText, selectionStart: Int, selectionEnd: Int) -> Unit) {
    setAccessibilityDelegate(object : View.AccessibilityDelegate() {
        override fun sendAccessibilityEvent(host: View?, eventType: Int) {
            super.sendAccessibilityEvent(host, eventType)
            if (eventType == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED){
                val editText = this@setSelectionChangedListener
                onSelectionChangedListener.invoke(editText, editText.selectionStart, editText.selectionEnd)
            }
        }
    })
}

1
问题提问者最初将他们的答案发布到了问题中。我把它移动到了答案中,以保持问题和答案的分离。 第一步:创建子类
package com.example;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.EditText;
import android.widget.Toast;

public class EditTextCursorWatcher extends EditText {

    public EditTextCursorWatcher(Context context, AttributeSet attrs,
            int defStyle) {
        super(context, attrs, defStyle);

    }

    public EditTextCursorWatcher(Context context, AttributeSet attrs) {
        super(context, attrs);

    }

    public EditTextCursorWatcher(Context context) {
        super(context);

    }


     @Override   
     protected void onSelectionChanged(int selStart, int selEnd) { 
        Toast.makeText(getContext(), "selStart is " + selStart + "selEnd is " + selEnd, Toast.LENGTH_LONG).show();
         } 
}

步骤二:在布局文件中引用类(例如main.xml(尽管我的是自定义对话框布局))。不要忘记使用完整的包名(在这种情况下是com.example.EditTextCursorWatcher,例如

    <com.example.EditTextCursorWatcher
     android:id="@+id/etEdit"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:gravity="top"
    android:minLines="5"
    android:inputType="textMultiLine"/> 

0
editText.doAfterTextChanged {
    doSomething(editText.selectionStart , editText.selectionEnd)  
}

editText.setOnClickListener {
    doSomething(editText.selectionStart , editText.selectionEnd)  
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接