android - 如何使用嵌套在 TextInputLayout 内的 TextInputEditText 约束视图
问题描述
我试图将 MaterialButton 的高度限制为与 TextInputEditText 的高度相同,方法是将它们从上到下和从下到下对齐。
但是,我无法做到这一点,因为 TextInputEditText 嵌套在 TextInputLayout 中。有了这个,我只能用 TextInputLayout 约束 MaterialButton,它给出了以下内容:
我不想删除计数器(或 TextInputEditText 下方的任何帮助文本)。
有没有办法用嵌套的TextInputEditText (从上到下和从下到下)约束 MaterialButton ,使它们以相同的高度出现?
这是我的代码:
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/chat_panel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/finish_panel">
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:id="@+id/chat_text_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@id/chat_send_button"
app:endIconDrawable="@drawable/ic_clear"
app:endIconMode="clear_text"
app:helperTextEnabled="true"
app:counterMaxLength="200"
app:counterEnabled="true"
app:errorEnabled="true"
android:hint="Chat">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/chat_text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:inputType="textNoSuggestions"
android:maxHeight="200dp"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/chat_send_button"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="wrap_content"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="@id/chat_text_container"
app:layout_constraintBottom_toBottomOf="@id/chat_text_container"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toRightOf="@id/chat_text_container"
android:minWidth="0dp"
android:insetBottom="0dp"
app:icon="@drawable/ic_send"
app:iconPadding="0dp"
app:iconGravity="textStart"/>
</androidx.constraintlayout.widget.ConstraintLayout>
解决方案
ConstraintLayout只能将约束应用于直接子级。由于要将按钮约束到的TextInputEditText视图是TextInputLayout的子视图,而不是 ConstraintLayout 的直接子视图,因此在按钮和TextInputEditText之间应用约束的任何尝试都会失败。
我们可以在ConstraintLayout上设置一个布局监听器,捕获TextInputEditText的大小和位置,并修改按钮视图以设置其大小和位置。这将需要特殊编码来进行更改,并且是一个有效的解决方案。
但是,如果我们有一个可重用类型的视图,它可以引用嵌入在子ViewGroup中的视图并将嵌入视图的大小和位置复制为ConstraintLayout的直接子级,该怎么办?然后我们可以将按钮约束到这种新类型的视图并获得我们想要的位置。
这样的自定义视图可以基于View类,但也可以基于扩展View的ConstraintHelper类。此类是ConstraintLayout中其他“帮助”类的基础,例如Group、GuideLine等。从我的角度来看,ConstraintHelper非常适合自定义视图。
这是这样一个自定义视图:
ViewSurrogate.kt
class ViewSurrogate @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : ConstraintHelper(context, attrs, defStyleAttr) {
private lateinit var mContainer: ConstraintLayout
private var mTargetId: Int = NO_ID
private lateinit var mTargetView: View
private var mDrawOutline = false
private var mPaint: Paint? = null
init {
context.theme.obtainStyledAttributes(
attrs, R.styleable.ViewSurrogate, 0, 0
).apply {
try {
mDrawOutline = getBoolean(R.styleable.ViewSurrogate_drawOutline, false)
mTargetId = getResourceId(R.styleable.ViewSurrogate_targetView, NO_ID)
} finally {
recycle()
}
}
if (mTargetId == NO_ID) throw InvalidTarget("app:targetId was not specified.")
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
if (this::mContainer.isInitialized) return
mContainer = (parent as ConstraintLayout).also { container ->
mTargetView = container.findViewById(mTargetId)
?: throw InvalidTarget("Can't find target view in layout.")
}
}
override fun draw(canvas: Canvas) {
super.draw(canvas)
if (!mDrawOutline) return
val paint = mPaint
?: Paint().apply {
style = Paint.Style.STROKE
color = Color.RED
strokeWidth = 10f
pathEffect = DashPathEffect(floatArrayOf(15f, 15f, 15f, 15f), 0f)
}
canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
setDimensionsFromTarget(mTargetView)
}
override fun updatePostLayout(container: ConstraintLayout?) {
super.updatePostLayout(container)
mTargetView.let { targetView ->
val pos = getPositionInCommonAncestor(targetView)
val lp = layoutParams as ViewGroup.MarginLayoutParams
if (lp.marginStart != pos.x || lp.topMargin != pos.y) {
lp.setMargins(pos.x, pos.y, 0, 0)
requestLayout()
} else if (width != targetView.width || height != targetView.height) {
requestLayout()
}
}
}
override fun getBaseline() = mTargetView.baseline
private fun getPositionInCommonAncestor(targetView: View): TargetViewOffset {
var viewY = targetView.y
var viewX = targetView.x
var targetAncestor: ViewGroup? = targetView.parent as ViewGroup
while (targetAncestor != null && targetAncestor != parent) {
viewY += targetAncestor.y
viewX += targetAncestor.x
targetAncestor = targetAncestor.parent as ViewGroup
}
if (targetAncestor == null) {
throw WrongParentException("ViewSurrogate must be a direct child of an ancestor of the target view.")
}
return TargetViewOffset(viewX.toInt(), viewY.toInt())
}
private fun setDimensionsFromTarget(targetView: View) {
if (width == targetView.width && height == targetView.height) return
setMeasuredDimension(targetView.width, targetView.height)
}
private data class TargetViewOffset(val x: Int, val y: Int)
private class WrongParentException(msg: String) : Exception(msg)
private class InvalidTarget(msg: String) : Exception(msg)
}
将以下内容添加到布局并将按钮约束到此视图的顶部和底部:
<[your package name].ViewSurrogate
android:id="@+id/surrogateView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:drawOutline="true"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:targetView="@id/chat_text"
tools:layout_marginTop="359dp" />
drawOutline
是一个布尔值,用于确定是否绘制视图的轮廓以帮助调试。
targetView
是要复制的视图的 ID。这是TextInputEditText。
代理视图的定位将通过设置代理视图的顶部和左侧边距来完成,因此我们将视图限制在父视图的顶部和左侧。
布局编辑器并没有完全复制 Android 系统的布局处理,因此tools:layout_marginTop
需要在 Android Studio 设计器中近似代理视图的位置。如果按钮的位置在我们的应用程序中是硬编码的,我们也需要这样做。
以下是可样式化的定义:
<resources>
<declare-styleable name="ViewSurrogate">
<attr name="drawOutline" format="boolean" />
<attr name="targetView" format="reference" />
</declare-styleable>
</resources>
这是在模拟器中显示的布局。红色轮廓显示代理视图的范围。注意按钮如何以TextInputEditText为中心。(红色轮廓显示代理视图的范围。)
如果我们希望按钮看起来延伸到TextInputEditText的顶部和底部,我们需要将顶部和底部插入设置为0dp
.
android:insetTop="0dp"
android:insetBottom="0dp"
所以,它现在看起来像这样:
我认为这可能是一个通用的解决方案,可以到达嵌入在ConstraintLayout中的任何ViewGroup以拉出视图以应用约束。
(使用 ConstraintLayout 2.0.4 进行了轻微测试)
推荐阅读
- javascript - 如何在具有对象的深度嵌套数组中获取最后一个孩子
- python - Kivy - 在代码中的 TabbedPanelItems 之间切换
- r - 是否可以在 igraph R 库中使用多线程?
- gcc - printf 与 utf-8 编码字符串的兼容性
- android-studio - 我想用我的 android 手机作为模拟器在 windows 7 上运行颤振。在我的调试模式下进行了调整。如何解决颤振医生命令的问题
- microsoft-graph-api - Microsoft Graph:NextLink...上一个链接呢?
- android - AppCompatDialogs 在使用 alpha 版本的各种操作系统版本上崩溃 Material Components
- python - Python 2.7 的 input() 和 raw_input() 问题。用户输入的数据未正确读取。到底是怎么回事?
- shell - sqlplus 命令在 linux 中从 crontab 执行时返回帮助页面
- r - 清理具有获得姓氏和名字的分隔空间的列,以便我可以从我的数据框中过滤它