返回

解决 Android BottomSheetDialogFragment 中 AutocompleteTextView 提示不显示

Android

BottomSheetDialogFragment 中 AutocompleteTextView 的提示不显示问题

在 Android 开发中,AutocompleteTextView 为用户提供便捷的输入提示功能,提升了应用的用户体验。然而,当将 AutocompleteTextView 置于 BottomSheetDialogFragment 内部时,开发者可能会遇到一个常见的问题:输入时提示下拉菜单无法正常显示。 本文分析造成此问题的原因,并提供可行的解决方案。

问题根源分析

该问题通常归咎于 BottomSheetDialogFragment 的层级结构和窗口行为。BottomSheetDialogFragment 的视图是附加在当前 Activity 窗口之上的,而不是Activity 的原生视图。这使得其层级和常规的视图层级略有不同,这种差异会导致 AutocompleteTextView 的下拉提示弹出时无法正确的显示,常常被遮挡或无法定位。 简单来说,就是弹出提示所需要的窗口位置信息没有正确的被 AutocompleteTextView获取到,从而导致下拉提示菜单无法正确展示。

解决方案

以下几种方案可用来解决 AutocompleteTextViewBottomSheetDialogFragment 内不显示提示的问题:

方案一:强制设置弹出窗口的锚点

通过代码指定 AutoCompleteTextView 的提示窗口的锚点。PopupWindow 通常根据锚点进行定位。默认情况下,提示会尝试找到锚点。当 AutoCompleteTextView 位于嵌套布局或非传统视图结构中时,它的默认锚点可能无法被识别,需手动设置锚点来辅助窗口正确弹出。

代码示例:

// 在 BottomSheetDialogFragment 的 onViewCreated 方法中
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    val bottomSheetBehavior = BottomSheetBehavior.from(view.parent as View)
    bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
    bottomSheetBehavior.peekHeight = resources.displayMetrics.heightPixels

    setupDropdowns()
    setupListeners()

    binding.orgListFilterEdt.setOnFocusChangeListener { _, hasFocus ->
       if(hasFocus){
        binding.orgListFilterEdt.showDropDown()
       }else{
           binding.orgListFilterEdt.dismissDropDown()
       }
    }
    binding.userListFilterEdt.setOnFocusChangeListener { _, hasFocus ->
        if(hasFocus){
            binding.userListFilterEdt.showDropDown()
        }else{
            binding.userListFilterEdt.dismissDropDown()
        }
    }
}

操作步骤:

  1. 获取到 AutocompleteTextView 实例。
  2. onViewCreated 中为AutoCompleteTextView 添加onFocusChangeListener,焦点获取后,显示DropDown; 焦点失去,关闭DropDown
    这样可以在任何时候都能触发DropDown,避免出现窗口定位不正确的情况。

注意: 使用showDropDown时,可能存在无法弹出, 可以配合 InputMethodManager 对其手动显示软件盘输入内容。

方案二:使用 PopupWindow 定制提示

可以放弃使用原生的 AutoCompleteTextView 的下拉列表提示机制, 使用一个 PopupWindow 来代替, 使得窗口更加灵活。

代码示例:

    private fun showAutocompleteDropdown(anchor: View, items: List<String>,onItemSelected:(String) -> Unit) {
        val popupView = LayoutInflater.from(context).inflate(R.layout.autocomplete_dropdown_layout, null)
        val recyclerView = popupView.findViewById<RecyclerView>(R.id.recyclerView)

        val adapter =  object : RecyclerView.Adapter<TextViewHolder>(){
                override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TextViewHolder {
                    val view = LayoutInflater.from(parent.context)
                        .inflate(R.layout.item_autocomplete_dropdown, parent, false)
                     return TextViewHolder(view)
                }

                override fun onBindViewHolder(holder: TextViewHolder, position: Int) {
                      val itemName = items[position]
                      holder.textView.text = itemName

                      holder.textView.setOnClickListener {
                          popupWindow?.dismiss()
                          onItemSelected(itemName)
                      }
                }

                override fun getItemCount(): Int {
                 return  items.size
                }
            }
        recyclerView.layoutManager = LinearLayoutManager(context)
        recyclerView.adapter = adapter

        popupWindow = PopupWindow(
            popupView,
            anchor.width,
            WindowManager.LayoutParams.WRAP_CONTENT,
            true
        ).apply {
            elevation = 8f // 可选:添加阴影效果
             isOutsideTouchable = true // 设置点击外部区域消失
            showAsDropDown(anchor)
        }

         popupWindow?.setOnDismissListener {
         }
    }

    private class TextViewHolder(view : View) : RecyclerView.ViewHolder(view){
        val textView =  view.findViewById<TextView>(R.id.tv_text_item)
    }
    
// 在 BottomSheetDialogFragment 的 setupListeners 方法中
 private fun setupListeners() {

     binding.orgListFilterEdt.setOnClickListener{
       showAutocompleteDropdown(binding.orgListFilterEdt,orgList) { selectedItem ->
         binding.orgListFilterEdt.setText(selectedItem)
         filterCallback?.onOrgSelected(selectedItem)
       }
     }


   binding.userListFilterEdt.setOnClickListener {
        showAutocompleteDropdown(binding.userListFilterEdt, userList){selectedItem ->
            binding.userListFilterEdt.setText(selectedItem)
            filterCallback?.onUserSelected(selectedItem)
        }
   }

        binding.cvApplyChanges.setOnClickListener {
            filterCallback?.onApplyChanges()
           dismiss()
        }
       binding.tvUpdateFilter.setOnClickListener {
            filterCallback?.onUpdateFilter()
        }

  }

操作步骤:

  1. 创建一个 RecyclerView 列表视图,用于展示选项。
  2. 在点击 AutocompleteTextView 时,弹出 PopupWindow ,并通过其显示 RecyclerView
  3. RecyclerView设置适配器,显示下拉选项。
  4. 点击选项后,PopupWindow 关闭, 同时更新 AutoCompleteTextView 显示内容, 并回调监听。

优势: 控制更加细致灵活。方便修改下拉列表的样式和动画等。

方案三:修改窗口属性

DialogFragment 的窗口属性做一些修改,可以解决部分问题。

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // 设置窗口的软输入模式
   dialog?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
    // 可选:设置背景透明度或样式
     dialog?.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) // 将背景设置为透明
    // 也可以设置 windowAnimationStyle 自定义动画

}

操作步骤:

  1. onCreate 方法中配置 DialogFragmentWindow
  2. 设置setSoftInputModeSOFT_INPUT_ADJUST_RESIZE, 有效利用可用屏幕空间。
  3. 设置setBackgroundDrawableColorDrawable(Color.TRANSPARENT),去除DialogFragment的默认背景。

提示: 此方案为可选方案, 用于部分场景下优化 DialogFragment 在屏幕上显示的方式, 与其他方案配合使用效果更佳。

安全建议

  • 确保从服务端获取的数据进行了验证和转义,防止 XSS 等安全问题。
  • 考虑缓存提示数据,避免频繁请求服务器,提高响应速度和用户体验。

通过这些方案的尝试,应该能够有效解决 AutoCompleteTextViewBottomSheetDialogFragment 中提示不显示的问题,让应用获得更完善的输入体验。