私は同じ問題を抱えていて、でこれを行うための他の投稿を読んでいるときにLayoutEditText
クラスに出くわしました。キャンバスで下線を手動で描画することで、これを実現するために必要なすべてを提供します。
最初に、XML レイアウト ファイルで簡単にカスタマイズできるようにカスタム属性を定義しました。
<declare-styleable name="UnderlinedTextView" >
<attr name="underlineHeight" format="dimension" />
<attr name="underlineOffset" format="dimension" />
<attr name="underlineColor" format="color" />
<attr name="underLinePosition" format="enum">
<enum name="baseline" value="0" />
<enum name="below" value="1" />
</attr>
</declare-styleable>
そしてカスタムTextView
クラス
class UnderlinedTextView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : androidx.appcompat.widget.AppCompatTextView(context, attrs, defStyleAttr) {
@Retention(AnnotationRetention.SOURCE)
@IntDef(POSITION_BASELINE, POSITION_BELOW)
annotation class UnderLinePosition {
companion object {
const val POSITION_BASELINE = 0
const val POSITION_BELOW = 1
}
}
private val linePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
style = Paint.Style.FILL
}
var lineColor: Int
get() = linePaint.color
set(value) {
if (linePaint.color != value) {
linePaint.color = value
invalidate()
}
}
var lineHeight: Float
get() = linePaint.strokeWidth
set(value) {
if (linePaint.strokeWidth != value) {
linePaint.strokeWidth = value
updateSpacing()
}
}
var lineTopOffset = 0F
set(value) {
if (field != value) {
field = value
updateSpacing()
}
}
@UnderLinePosition
var linePosition = POSITION_BASELINE
private val rect = Rect()
private var internalAdd: Float = lineSpacingExtra
private inline val extraSpace
get() = lineTopOffset + lineHeight
init {
val density = context.resources.displayMetrics.density
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.UnderlinedTextView, defStyleAttr, 0)
lineColor = typedArray.getColor(R.styleable.UnderlinedTextView_underlineColor, currentTextColor)
lineTopOffset = typedArray.getDimension(R.styleable.UnderlinedTextView_underlineOffset, 0f)
lineHeight = typedArray.getDimension(R.styleable.UnderlinedTextView_underlineHeight, density * 1)
linePosition = typedArray.getInt(R.styleable.UnderlinedTextView_underLinePosition, POSITION_BASELINE)
typedArray.recycle()
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
setMeasuredDimension(measuredWidth, measuredHeight + (extraSpace + 0.5f).toInt())
}
override fun onDraw(canvas: Canvas?) {
canvas?.takeIf { !text.isNullOrEmpty() }?.let {
val count = lineCount
val layout = layout
var xStart: Float
var xStop: Float
var yStart: Float
var firstCharInLine: Int
var lastCharInLine: Int
var lastLine: Boolean
var offset: Int
val lineSpacing = lineSpacingExtra * lineSpacingMultiplier
for (i in 0 until count) {
val baseline = getLineBounds(i, rect)
lastLine = i == count - 1
offset = if (lastLine) 0 else 1
firstCharInLine = layout.getLineStart(i)
lastCharInLine = layout.getLineEnd(i)
xStart = layout.getPrimaryHorizontal(firstCharInLine)
xStop = layout.getPrimaryHorizontal(lastCharInLine - offset)
yStart = when (linePosition) {
POSITION_BASELINE -> baseline + lineTopOffset
POSITION_BELOW -> (rect.bottom + lineTopOffset) - if (lastLine) 0F else lineSpacing
else -> throw NotImplementedError("")
}
canvas.drawRect(xStart, yStart, xStop, yStart + lineHeight, linePaint)
}
}
super.onDraw(canvas)
}
private fun updateSpacing() {
setLineSpacing(internalAdd, 1f)
}
override fun setLineSpacing(add: Float, mult: Float) {
internalAdd = add
super.setLineSpacing(add + extraSpace, 1f)
}
}
それなら使い方は簡単
<some.package.UnderlinedTextView
android:id="@+id/tvTest"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginBottom="10dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:gravity="center"
android:text="This is a demo text"
android:textSize="16sp"
app:underlineColor="#ffc112ef"
app:underlineHeight="3dp"/>
最終結果
- 複線
- 単線