Consider that:
- Al2TextView inherits from TextView
- Al2TextInputLayout inherits from TextInputLayout
- Al2TextInputEditText inherits from TextInputEditText
When entering text into the second field (textinput_phone), the "Clear Text" icon (the X) appears and adjusts the height of the component, misaligning it with the first field (textinput_areacode).
`<?xml version="1.0" encoding="utf-8"?>
<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:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.rga.altwo.wallet.system.common.component.Al2TextView
android:id="@+id/textinput_placeholder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Teléfono celular"
android:textAppearance="@style/TextEyebrowInput"
android:textColor="@color/al2_black"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:textAllCaps="true"
tools:ignore="HardcodedText" />
<com.rga.altwo.wallet.system.common.component.Al2TextInputLayout
android:id="@+id/textinput_areacode"
android:layout_width="100dp"
android:layout_height="wrap_content"
app:boxStrokeColor="@color/al2_light_grey"
app:hintEnabled="false"
app:layout_constraintEnd_toStartOf="@id/textinput_phone"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textinput_placeholder"
app:prefixTextAppearance="@style/Text2">
<com.rga.altwo.wallet.system.common.component.Al2TextInputEditText
android:id="@+id/edittext_areacode"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:backgroundTint="@color/al2_light_grey"
android:focusable="true"
android:focusableInTouchMode="true"
android:focusedByDefault="false"
android:inputType="numberDecimal"
android:digits="123456789"
android:maxLength="5" />
</com.rga.altwo.wallet.system.common.component.Al2TextInputLayout>
<com.rga.altwo.wallet.system.common.component.Al2TextInputLayout
android:id="@+id/textinput_phone"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
app:boxStrokeColor="@color/al2_light_grey"
app:hintEnabled="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/textinput_areacode"
app:layout_constraintTop_toBottomOf="@id/textinput_placeholder">
<com.rga.altwo.wallet.system.common.component.Al2TextInputEditText
android:id="@+id/edittext_phone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:backgroundTint="@color/al2_light_grey"
android:focusable="true"
android:focusableInTouchMode="true"
android:focusedByDefault="false"
android:inputType="numberDecimal"
android:maxLength="9" />
</com.rga.altwo.wallet.system.common.component.Al2TextInputLayout>
</androidx.constraintlayout.widget.ConstraintLayout>`
Al2TextInputLayout class ->
typealias OnTextLinkClickListener = () -> Unit
@Suppress("MagicNumber")
enum class Type(val type: Int) {
STANDARD(0),
SEARCH(1),
USER(2),
PASSWORD(3),
}
@Suppress("MagicNumber")
enum class CaptionType(val type: Int) {
STANDARD(0),
SUCCESS(1),
ERROR(2),
}
class Al2TextInputLayout : TextInputLayout {
private var passwordVisible = false
private var textLink: Al2TextView? = null
private var textLabel: Al2TextView? = null
private var onTextLinkClickListener: OnTextLinkClickListener? = null
private val viewEnable: Boolean
get() = isEnabled
constructor(context: Context) : super(context) {
init(null)
}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
init(attrs)
}
constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(
context,
attrs,
defStyle
) {
init(attrs)
}
var defaultBoxStrokeColor: Int = 0
private fun init(attrs: AttributeSet?) {
defaultBoxStrokeColor = boxStrokeColor
ContextCompat.getColorStateList(context, R.color.textinput_background)?.let {
setBoxBackgroundColorStateList(
it
)
}
setupEndIcon(END_ICON_CLEAR_TEXT) //always set to clear_text except those that specify it (textinput_areacode e.g)
isExpandedHintEnabled = false
setPrefixTextAppearance(R.style.Text2)
val prefixColor = ContextCompat.getColorStateList(context, R.color.al2_grey)
prefixColor?.let { setPrefixTextColor(it) }
prefixTextView.updateLayoutParams {
height = ViewGroup.LayoutParams.MATCH_PARENT
}
prefixTextView.gravity = Gravity.CENTER
if (attrs != null) {
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.Al2TextInputLayout)
// label
val label = typedArray.getString(R.styleable.Al2TextInputLayout_textLabel)
if (!label.isNullOrEmpty()) {
setLabel(label)
}
// caption
val caption = typedArray.getString(R.styleable.Al2TextInputLayout_textCaption)
if (caption != null) {
setCaption(caption)
}
// link
val link = typedArray.getString(R.styleable.Al2TextInputLayout_textLink)
if (link != null) {
setLink(link)
}
// type
val type = typedArray.getInt(
R.styleable.Al2TextInputLayout_textInputLayoutType,
Type.STANDARD.type
)
when (type) {
Type.SEARCH.type -> {
isHintEnabled = false
}
Type.USER.type -> {
// leave empty for now
}
Type.PASSWORD.type -> {
editText?.transformationMethod = DotPasswordTransformationMethod
endIconMode = END_ICON_CUSTOM
endIconDrawable = ContextCompat.getDrawable(context, R.drawable.ic_view)
endIconDrawable?.setTint(ContextCompat.getColor(context, R.color.al2_grey))
setEndIconOnClickListener {
if (passwordVisible) {
endIconDrawable = ContextCompat.getDrawable(context, R.drawable.ic_view)
editText?.transformationMethod = DotPasswordTransformationMethod
} else {
endIconDrawable = ContextCompat.getDrawable(context, R.drawable.ic_hide)
editText?.transformationMethod = null
}
endIconDrawable?.setTint(ContextCompat.getColor(context, R.color.al2_grey))
editText?.text?.length?.let { editText?.setSelection(it) }
passwordVisible = !passwordVisible
}
}
}
typedArray.recycle()
}
}
fun setupEndIcon(iconMode: Int) {
endIconMode = iconMode
if (iconMode != END_ICON_NONE) {
endIconDrawable = ContextCompat.getDrawable(context, R.drawable.ic_close_small)
setEndIconTintList(ContextCompat.getColorStateList(context, R.color.al2_grey))
}
}
// placeholder/hint on the top of input field
fun setLabel(label: String) {
textLabel?.let {
removeView(textLabel)
}
textLabel = Al2TextView(context)
textLabel?.isEnabled = viewEnable
textLabel?.setTextAppearance(R.style.TextEyebrowInput)
textLabel?.isAllCaps = true
textLabel?.setTextColor(ContextCompat.getColorStateList(context, R.color.textinput_label))
textLabel?.text = label
addView(textLabel, 0)
}
// caption text on the bottom of input field
fun setCaption(
text: String?,
captionType: CaptionType? = CaptionType.STANDARD
) {
val lineStroke = when (captionType) {
CaptionType.SUCCESS -> ContextCompat.getColor(context, R.color.textinput_bottomline_success)
CaptionType.ERROR -> ContextCompat.getColor(context, R.color.textinput_bottomline_error)
else -> defaultBoxStrokeColor
}
// bottom line stroke for unfocused state
setDefaultStrokeColor(lineStroke)
// bottom line stroke for focused state
boxStrokeColor = lineStroke
text?.let {
val colorText: Int = when (captionType) {
CaptionType.SUCCESS -> R.color.al2_success_green
CaptionType.ERROR -> R.color.al2_error_red
else -> R.color.al2_grey
}
helperText = text
helperText.apply {
setHelperTextTextAppearance(R.style.Text2)
val captionColor = if (viewEnable) colorText else R.color.al2_light_grey
setHelperTextColor(ColorStateList.valueOf(ContextCompat.getColor(context, captionColor)))
}
}
textLink?.let {
it.visibility = if (captionType == CaptionType.STANDARD) VISIBLE else GONE
}
}
private fun setLink(text: String, onTextLinkClicked: OnTextLinkClickListener? = null) {
textLink?.let {
removeView(it)
}
textLink = Al2TextView(context)
textLink?.text = text
textLink?.apply {
setTextAppearance(R.style.TextLink1)
paintFlags = paintFlags or Paint.UNDERLINE_TEXT_FLAG
val color = if (viewEnable) R.color.al2_blue else R.color.al2_light_grey
setTextColor(ContextCompat.getColor(context, color))
layoutParams = LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.WRAP_CONTENT
).apply {
setMargins(0, 10f.toDp(context), 0, 0)
}
}
onTextLinkClicked?.let { setLinkClickListener(it) }
addView(textLink)
}
fun setLinkEnabled(enabled: Boolean) {
textLink?.isEnabled = enabled
}
fun setLinkClickListener(onTextLinkClicked: OnTextLinkClickListener) {
onTextLinkClickListener = onTextLinkClicked
onTextLinkClickListener?.let { onClick ->
textLink?.setOnClickListener {
onClick()
}
}
}
private fun setDefaultStrokeColor(color: Int) {
try {
val defaultStrokeColor = TextInputLayout::class.java.getDeclaredField("defaultStrokeColor")
defaultStrokeColor.isAccessible = true
defaultStrokeColor.set(this, color)
} catch (e: NoSuchFieldException) {
// failed to change the color
}
}
}
I tried:
- Use LinearLayout instead of ConstraintLayout
- Use minHeight on both TextInputLayout to force equal heights
Source: View source