避免 EditText TextWatcher 在每次字符更改时触发事件

Avoid EditText TextWatcher firing events on every character change

我使用附加了 TextWatcher 的 EditText 文本字段。所有回调方法都会在我输入的每个字符上触发事件。在我的例子中,我调用 api on text changed,将响应放入同一片段的标签中。我无法检查特定格式的 EditText,因为它将是自由文本。

示例: 你叫什么名字?

E -> api call 
R -> api call
I _> api call
K -> api call

如何避免这种情况?我可以使用计时器吗(例如 2 秒没有文本更改 -> api 调用)?也许有一种优雅的方式可以做到这一点。

如果您使用的是 Kotlin,则可以使用协程来完成此操作。例如,如果您想 运行 在文本停止更改后 2 秒调用 API,它看起来像这样:

在 Activity 或带有 ViewModel 的片段中

val model: MainViewModel by viewModels()

binding.textInput.doAfterTextChanged { e ->
    e?.let { model.registerChange( it.toString() ) }
}

在视图模型中

private var job: Job? = null

fun registerChange(s: String) {
    job?.cancel() // cancel prior job on a new change in text
    job = viewModelScope.launch {
        delay(2_000)
        println("Do API call with $s")
    }
}

或者不使用 ViewModel

private var job: Job? = null

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val binding = ActivityMainBinding.inflate(layoutInflater)
    setContentView(binding.root)

    binding.textInput.doAfterTextChanged { e ->
        e?.let { 
            job?.cancel()
            job = lifecycleScope.launch {
                delay(1_000)
                println("Do API call with ${it}")
            }
        }
    }
}

使用 Jetpack Compose

使用 Jetpack Compose 的解决方案是相同的,只是添加到 onValueChange lambda 而不是 doAfterTextChanged(就地,或者与之前相同的 ViewModel 调用)。

private var job: Job? = null

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
        val textState = remember { mutableStateOf("") }
        TextField(
            value = textState.value,
            onValueChange = {
                textState.value = it
                job?.cancel()
                job = lifecycleScope.launch {
                    delay(2_000)
                    println("TEST: Do API call with $it")
                }
            }
        )
    }
}

Java解决方案

如果您正在使用 Java,您可以使用处理程序和 postDelayed 运行nable,但流程是相同的(在文本更改时,取消先前的任务并启动新的有延迟)。

private final Handler handler = new Handler();

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    EditText textInput = findViewById(R.id.text_input);
    textInput.addTextChangedListener(new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {}

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {}

        @Override
        public void afterTextChanged(Editable s) {
            if( s != null ) {
                handler.removeCallbacksAndMessages(null);
                handler.postDelayed(() -> {
                    System.out.println("Call API with " + s.toString());
                }, 2000);
            }
        }
    });
}