You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android动态创建多个AutoCompleteTextView下拉锚点错位问题

问题描述
  • 需求:动态创建多个输入项视图,每个视图包含一个AutoCompleteTextView用于展示下拉选择列表。提前定义子项布局item_new_product.xml,运行时inflate后添加到主布局activity_main.xml的viewContainer容器中。
  • 预期:每个AutoCompleteTextView的下拉列表在其父级TextInputLayout位置弹出,XML中已配置dropDownAnchor属性指向对应父级TextInputLayout的ID。
  • 实际现象:第一个添加的子项下拉弹出位置正常,后续新增的所有子项下拉列表全部锚定到第一个AutoCompleteTextView对应的TextInputLayout位置。
根本原因

所有动态inflate的布局内视图ID完全重复,AutoCompleteTextView在解析XML配置的dropDownAnchor属性时,会在全局视图树中查找第一个匹配该ID的视图,最终所有后续子项的锚点都指向第一个加载的同IDTextInputLayout,导致位置错位。

实现方案

步骤1:修改子项布局XML

删除item_new_product.xml中AutoCompleteTextView节点上静态配置的android:dropDownAnchor属性,避免XML加载阶段提前绑定错误锚点。
修改后的item_new_product.xml中AutoCompleteTextView部分代码:

<AutoCompleteTextView
    android:id="@+id/productTypeAutoCompleteTv"
    android:layout_width="match_parent"
    android:layout_height="52dp"
    android:dropDownHeight="120dp"
    android:inputType="none"
    android:maxLines="1"
    android:textSize="12sp"
    android:singleLine="true"
    android:paddingStart="16dp"
    android:paddingEnd="16dp"/>

步骤2:动态加载时手动绑定唯一锚点

在addView方法中,inflate子布局后,先获取当前子项内部的TextInputLayout实例,为其分配全局唯一ID,再调用setDropDownAnchor()方法给同子项的AutoCompleteTextView绑定该唯一ID,确保锚点是当前子项内的对应视图,完全绕开ID重复导致的全局查找错误。
修改后的核心逻辑如下:

private fun addView() {
    val rootView = binding.viewContainer
    val inflater = LayoutInflater.from(this)
    val child: View = inflater.inflate(R.layout.item_new_product, rootView, false)
    // 生成子项唯一ID用于后续移除操作
    val childId = View.generateViewId()
    child.id = childId
    IDs.add(childId)

    val items: List<String> = listOf("Beverages", "Cocktails", "Pasta", "Pizza", "Salads")
    val adapter = ArrayAdapter(this, R.layout.item_product_dropdown, items)

    // 获取当前子项内的TextInputLayout实例,分配唯一ID
    val productTypeLayout = child.findViewById<TextInputLayout>(R.id.productTypeLayout)
    val anchorId = View.generateViewId()
    productTypeLayout.id = anchorId
    // 获取当前子项内的AutoCompleteTextView,绑定对应唯一锚点ID
    val productAutoComplete = child.findViewById<AutoCompleteTextView>(R.id.productTypeAutoCompleteTv)
    productAutoComplete.setDropDownAnchor(anchorId)
    productAutoComplete.setAdapter(adapter)
    rootView.addView(child)
}

其余参考代码

activity_main.xml

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.google.android.material.card.MaterialCardView
        android:id="@+id/cardView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        android:backgroundTint="@android:color/holo_blue_dark"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">


        <com.google.android.material.button.MaterialButton
            android:id="@+id/addButton"
            android:layout_width="52dp"
            android:layout_height="52dp"
            android:layout_gravity="start"
            android:background="@color/holo_blue_bright"
            android:elevation="5dp"
            android:text="+" />

        <com.google.android.material.button.MaterialButton
            android:id="@+id/removeButton"
            android:layout_width="52dp"
            android:layout_height="52dp"
            android:layout_gravity="end"
            android:background="@color/holo_blue_bright"
            android:elevation="5dp"
            android:text="-" />

    </com.google.android.material.card.MaterialCardView>

    <LinearLayout
        android:id="@+id/viewContainer"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:layout_margin="16dp"
        android:animateLayoutChanges="true"
        android:background="@color/holo_blue_dark"
        android:orientation="vertical"
        android:padding="8dp"
        app:layout_constraintTop_toBottomOf="@id/cardView"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent">
    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

styles.xml

<resources>
    <style name="DropdownLayoutStyle" parent="Widget.MaterialComponents.TextInputLayout.FilledBox.ExposedDropdownMenu">
        <item name="endIconTint">@color/holo_blue_dark</item>
        <item name="hintTextColor">@color/holo_blue_dark</item>
    </style>
</resources>

item_product_dropdown.xml

<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="52dp"
    android:background="@color/holo_blue_bright"
    android:maxLines="1"
    android:padding="16dp"
    android:textColor="@color/white" />

MainActivity.kt 完整代码

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    
    private var IDs = mutableListOf<Int>()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        binding.addButton.setOnClickListener { addView() }
        binding.removeButton.setOnClickListener { removeView() }
        setContentView(binding.root)
    }
    
    private fun addView() {
        val rootView = binding.viewContainer
        val inflater = LayoutInflater.from(this)
        val child: View = inflater.inflate(R.layout.item_new_product, rootView, false)
        val childId = View.generateViewId()
        child.id = childId
        IDs.add(childId)

        val items: List<String> = listOf("Beverages", "Cocktails", "Pasta", "Pizza", "Salads")
        val adapter = ArrayAdapter(this, R.layout.item_product_dropdown, items)

        val productTypeLayout = child.findViewById<TextInputLayout>(R.id.productTypeLayout)
        val anchorId = View.generateViewId()
        productTypeLayout.id = anchorId
        val productAutoComplete = child.findViewById<AutoCompleteTextView>(R.id.productTypeAutoCompleteTv)
        productAutoComplete.setDropDownAnchor(anchorId)
        productAutoComplete.setAdapter(adapter)
        rootView.addView(child)
    }

    private fun removeView() {
        if (IDs.size == 0) return
        val view: View? = findViewById(IDs.last())
        IDs.removeLast()
        view?.let { (it.parent as ViewGroup).removeView(it) }
    }
}

内容的提问来源于stack exchange,提问作者AndroIdefix

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 12:51:19