TextInputLayout嵌套XML定义TextInputEditText属性异常咨询
我在布局中放置了4个TextInputEditText控件,创建方式分别如下:
- A:XML中定义在
TextInputLayout内部的TextInputEditText,对应布局代码:
<com.google.android.material.textfield.TextInputLayout android:id="@+id/til_from_xml_2_elements2" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:layout_marginHorizontal="16dp" android:hint="w input from_xml_2_elements" app:endIconMode="clear_text" android:descendantFocusability="beforeDescendants" android:focusableInTouchMode="true"> <com.google.android.material.textfield.TextInputEditText android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/et_from_xml_2_elements2" android:text = "text" android:maxLines="1" android:inputType="textCapSentences" android:imeOptions="actionDone" tools:ignore="TextContrastCheck"> </com.google.android.material.textfield.TextInputEditText> </com.google.android.material.textfield.TextInputLayout>
- B:XML中独立定义、无
TextInputLayout包裹的TextInputEditText,对应布局代码:
<com.google.android.material.textfield.TextInputEditText android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/et_from_xml2" android:layout_marginTop="16dp" android:layout_marginHorizontal="16dp" android:text = "w input edit text from xml" android:maxLines="1" android:inputType="textCapSentences" android:imeOptions="actionDone" tools:ignore="TextContrastCheck"> </com.google.android.material.textfield.TextInputEditText>
- C:通过构造函数动态创建后添加到
TextInputLayout内的TextInputEditText,TextInputLayout布局代码:
<com.google.android.material.textfield.TextInputLayout android:id="@+id/til_only_group_w_input" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:layout_marginHorizontal="16dp" android:hint="w input only til" app:endIconMode="clear_text" android:descendantFocusability="beforeDescendants" android:focusableInTouchMode="true"> </com.google.android.material.textfield.TextInputLayout>
- D:通过构造函数动态独立创建、无
TextInputLayout包裹的TextInputEditText。
C、D控件创建的代码如下:
private fun getEditText(): View { return TextInputEditText( this, null, androidx.appcompat.R.attr.editTextStyle ).apply { val layoutParams = LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT ) val margin16dp = this@MainActivity.dipToPixels(16f).toInt() layoutParams.setMargins( margin16dp, margin16dp, margin16dp, 0 ) this@apply.layoutParams = layoutParams this@apply.maxLines = 1 this@apply.imeOptions = EditorInfo.IME_ACTION_DONE this@apply.inputType = android.text.InputType.TYPE_CLASS_TEXT or android.text.InputType.TYPE_TEXT_VARIATION_NORMAL this.setText("from constructor") } }
测试项目地址:NestedVewsAttributeSet
从配置逻辑上看4个TextInputEditText的核心参数应当一致,但实际渲染表现存在明显差异:B、D控件表现一致符合预期,A、C控件表现异常。我首先针对TextInputEditText的paddingLeft参数展开排查,日志输出结果为:
- et_from_xml_2_elements(A控件):44
- et_from_xml(B控件):11
- 构造函数创建添加到TIL的控件(C控件):11
- 构造函数独立创建的控件(D控件):11
其中XML定义在TextInputLayout内的A控件paddingLeft值远大于其余控件。我逐一排查属性设置来源:布局文件未直接设置padding属性、样式配置中无padding设置、默认主题属性(TextInputLayout对应R.attr.editTextStyle)及其父样式无padding配置、构造函数也未使用携带默认padding的样式。顺着TextInputEditText构造函数调用栈追踪,发现paddingLeft=44的赋值发生在View的四参构造函数View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)中,是在执行以下代码行时获取到的属性值:
final TypedArray a = context.obtainStyledAttributes( attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
由于obtainStyledAttributes方法内部包含JNI调用,我无法通过调试器深入追踪内部执行逻辑。现咨询两个问题:
- 为什么View的属性取值会受到视图嵌套层级的影响?
- 是否存在可行方案调试上述JNI调用?
1. 为什么View的属性取值会受到视图嵌套层级的影响?
根本不是嵌套层级本身影响属性取值,是TextInputLayout在XML填充阶段主动给内部定义的TextInputEditText注入了专属默认样式。
你测到的44px左padding,是Material组件库预设给TextInputLayout内部输入框的默认内边距,专门用来给前缀、起始图标预留显示空间。
翻TextInputLayout源码就能看到,它在inflate子View的流程里,会检测直接子节点是不是TextInputEditText,如果是,就不会直接用子View原本声明的样式属性,而是会给它套上一层R.attr.textInputEditTextStyle的默认样式,这个样式里就预设了默认padding值。
你测的动态创建后加入TIL的C控件padding是11,是因为TextInputLayout的样式注入逻辑只在XML inflate阶段生效,手动addView进去的控件它不会做样式包装,所以C用的还是构造时传的androidx.appcompat.R.attr.editTextStyle对应的样式,padding和独立创建的D一致。C表现异常本质也是因为没被注入TIL要求的编辑框样式,和TIL的交互逻辑(比如hint浮动、图标位置适配)无法正常匹配。
A控件是直接写在TextInputLayout标签内部的,inflate时被自动套了专属样式,所以paddingLeft是样式里预设的44,和你自己有没有在XML、主题、构造函数里配padding没有关系。
2. 是否存在可行方案调试上述JNI调用?
没必要硬啃obtainStyledAttributes的JNI逻辑,有几个成本极低的调试方案:
- 直接用Android Studio自带的Layout Inspector,选中目标控件后在属性面板搜
paddingLeft,面板会直接标注这个属性值的来源:是布局里直接写的、来自哪个样式、还是来自主题配置,直接就能定位到赋值源头。 - 在
obtainStyledAttributes返回TypedArray的位置打断点,直接读取TypedArray中对应padding属性的资源ID,顺着资源ID查framework或者Material库的资源文件,就能看到具体的赋值。 - 如果确实要追native层逻辑,可以下载对应测试机型版本的AOSP源码,配置好Android Studio的Native调试环境,attach到应用进程后给JNI层的
NativeObtainStyledAttributes方法打断点单步调试,但这个方案投入产出比极低,前两种方法完全够用。
内容的提问来源于stack exchange,提问作者Kubick

