일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- rxjava disposable
- 멤버변수
- Rxjava Observable
- 스택 큐 차이
- 서비스 vs 쓰레드
- ar vr 차이
- android ar 개발
- rxjava cold observable
- ANR이란
- 안드로이드 유닛테스트란
- jvm이란
- android retrofit login
- 플러터 설치 2022
- jvm 작동 원리
- 2022 플러터 안드로이드 스튜디오
- 큐 자바 코드
- 스택 자바 코드
- 객체
- 클래스
- 안드로이드 유닛 테스트
- 안드로이드 os 구조
- 안드로이드 유닛 테스트 예시
- 안드로이드 라이선스 종류
- 안드로이드 레트로핏 crud
- 2022 플러터 설치
- 안드로이드 라이선스
- 자바 다형성
- 서비스 쓰레드 차이
- rxjava hot observable
- 안드로이드 레트로핏 사용법
- Today
- Total
나만을 위한 블로그
[Android] 텍스트에 밑줄 추가하는 법 본문
프로젝트에 저장된 문자열에 밑줄을 추가해서 표시하는 거라면 아래처럼 할 수 있다.
<string name="string">여긴 밑줄 안 됨 <u>여기만 밑줄됨</u> 여긴 밑줄 안 됨</string>
그리고 액티비티, 프래그먼트에서 아래처럼 사용한다.
binding.tv1.text = Html.fromHtml(getString(R.string.string), Html.FROM_HTML_MODE_LEGACY)
또는 아래처럼 써도 상관없다.
binding.tv1.text = Html.fromHtml(getString(R.string.string), HtmlCompat.FROM_HTML_MODE_LEGACY)
binding.tv1.text = HtmlCompat.fromHtml(getString(R.string.string), HtmlCompat.FROM_HTML_MODE_LEGACY)
아니면 Paint 클래스의 UNDERLINE_TEXT_FLAG를 텍스트뷰의 paintFlags 설정값으로 넘기는 방법도 있다.
val underlineText = "밑줄 처리할 텍스트"
binding.tv1.paintFlags = Paint.UNDERLINE_TEXT_FLAG
binding.tv1.text = underlineText
조금 더 세밀한 제어를 원한다면 UnderlineSpan을 고려할 수 있다.
맨 처음의 <u> 태그를 사용한 것과 같은 효과를 낸다면 아래처럼 할 수 있다. 서버에서 받아온 문자열 일부를 밑줄 처리하고 싶을 때 쓸 수 있을 것이다.
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
val fullText = "여긴 밑줄 안 됨 여기만 밑줄됨 여긴 밑줄 안 됨"
val startIndex = fullText.indexOf("여기만 밑줄됨")
val endIndex = startIndex + "여기만 밑줄됨".length
val spannableString = SpannableString(fullText)
spannableString.setSpan(
UnderlineSpan(),
startIndex,
endIndex,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
binding.tv1.text = spannableString
}
하지만 이렇게 기본 제공되는 밑줄이 아닌 색깔과 두께, 위치를 바꾼 밑줄을 구현해야 할 수 있다.
그냥 XML에 View 하나 추가하면 되는 거 아니냐고 생각할 수 있지만 한 줄이었던 텍스트가 2줄, 3줄로 나눠서 표시되더라도 모든 텍스트 밑에 밑줄이 표시돼야 한다면 View 하나 추가하는 것으론 부족하다.
아래의 클래스를 사용하면 별 문제 없이 밑줄 처리를 할 수 있다.
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatTextView
class UnderlineTextView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : AppCompatTextView(context, attrs, defStyleAttr) {
var underlineColor: Int = 0
var underlineHeight: Float = 0f
var underlineMarginTop: Float = 0f
private val underlinePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
style = Paint.Style.STROKE
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
underlinePaint.color = underlineColor
underlinePaint.strokeWidth = underlineHeight
val layout = layout ?: return
val lineCount = layout.lineCount
val fm = paint.fontMetrics
for (i in 0 until lineCount) {
val lineLeft = layout.getLineLeft(i)
val lineRight = layout.getLineRight(i)
val baseline = layout.getLineBaseline(i)
val textDescent = fm.descent
val y = baseline + textDescent + underlineMarginTop + underlineHeight
canvas.drawLine(lineLeft, y, lineRight, y, underlinePaint)
}
}
}
XML에선 TextView 대신 이 클래스의 전체 경로를 적어준다.
그리고 paddingBottom을 1dp라도 넣어줘야 한다. 밑줄 굵기가 굵다면 2dp, 3dp 정도로 크기를 조절해야 밑줄이 완전히 표시된다.
lineSpacingExtra는 안 넣어도 상관없다. 텍스트 줄 구분을 위해 추가한 속성이다.
<?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/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".presentation.MainActivity">
<com.example.regacyviewpractice.presentation.underline.UnderlineTextView
android:id="@+id/tv1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="2dp"
android:lineSpacingExtra="10dp"
android:textSize="20dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="테스트" />
</androidx.constraintlayout.widget.ConstraintLayout>
액티비티, 프래그먼트에선 텍스트를 set해주면서 underlineColor, Height, MarginTop의 값을 원하는 대로 설정하면 된다.
val fullText = "이 텍스트에 전부 밑줄 칠 것 이 텍스트에 전부 밑줄 칠 것 이 텍스트에 전부 밑줄 칠 것"
binding.tv1.apply {
text = fullText
underlineColor = Color.RED
underlineHeight = 3f
underlineMarginTop = 1f
}
폴드 에뮬레이터에서 확인해 본다. 펼친 상태라면 아래처럼 표시된다.
접은 상태면 아래처럼 표시된다.
이렇게 하면 폴드를 접거나 가로 폭이 좁은 기기에서도 텍스트뷰 하나로 여러 밑줄을 표시할 수 있다.
그럼 어떻게 저렇게 작동하는 건가? 작동 원리는 아래와 같다.
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatTextView
class UnderlineTextView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : AppCompatTextView(context, attrs, defStyleAttr) {
var underlineColor: Int = 0
var underlineHeight: Float = 0f
var underlineMarginTop: Float = 0f
private val underlinePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
style = Paint.Style.STROKE
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
underlinePaint.color = underlineColor
underlinePaint.strokeWidth = underlineHeight
val layout = layout ?: return
val lineCount = layout.lineCount
val fm = paint.fontMetrics
for (i in 0 until lineCount) {
val lineLeft = layout.getLineLeft(i)
val lineRight = layout.getLineRight(i)
val baseline = layout.getLineBaseline(i)
val textDescent = fm.descent
val y = baseline + textDescent + underlineMarginTop + underlineHeight
canvas.drawLine(lineLeft, y, lineRight, y, underlinePaint)
}
}
}
전역 변수들은 뷰에서 호출할 때 원하는 값을 넣기 위해 전부 var로 선언했다.
underlinePaint에는 ANTI_ALIAS_FLAG를 썼는데 이건 안티 앨리어싱을 활성화하는 Paint 플래그다. 고사양 게임을 해봤다면 그래픽 옵션 화면에서 자주 본 단어인데 네모 형태인 픽셀로 이뤄진 곡선, 원 등의 이미지가 매끄럽게 표시되도록 해 준다. 고작 밑줄 긋는데 과한 옵션이라고 생각되지만 기왕 표시되는 거 선명하게 표시되면 좋을 거 같아서 썼다.
이후 layout 객체를 통해 텍스트의 각 줄 시작, 끝, 기준선(baseline) 정보를 가져온다. layout 객체는 텍스트뷰가 완전히 그려저서 상호작용할 준비가 끝난 후에 접근할 수 있기 때문에, 텍스트뷰가 아직 다 그려지지 않았다면 layout 객체에 접근할 수 없어서 밑줄을 그리지 않고 넘어간다.
참고로 baseline이 뭔지 모른다면 아래 그림을 참고하자.
그 다음 반복문으로 줄 수(lineCount)만큼 반복하며 getLineLeft(i), getLineRight(i)로 실제로 텍스트가 그려지는 영역의 시작과 끝의 x 좌표값을 구하고, paint.fontMetrics로 표시될 텍스트에서 글자가 실제로 기준선(baseline) 아래로 얼마나 내려갈지에 대한 값인 descent를 가져온다. 이 값은 반복문 안에서 기준선과 underlineMarginTop, underlineHeight를 합쳐서 밑줄과 밑줄 위에 표시되는 글자 사이 간격을 결정하는 y 좌표값이 된다. 이를 통해 x, y 좌표값을 얻었으니 텍스트 밑에 밑줄을 그려나가다 마지막 글자까지 그린 후 종료되는 것이다.
'Android' 카테고리의 다른 글
[Android] Hilt + Retrofit + Flow + Coil + 멀티 모듈 구조 프로젝트 - 2 - (0) | 2025.01.02 |
---|---|
[Android] 안드로이드 스튜디오와 Cursor AI 연동하는 법 (0) | 2025.01.01 |
[Android] Hilt + Retrofit + Flow + Coil + 멀티 모듈 구조 프로젝트 - 1 - (0) | 2024.12.23 |
[Android] 내 위치 정보 가져와서 사용하는 법 (with. Hilt) (0) | 2024.12.18 |
[Android] 현재 내 위치 가져오는 법 (0) | 2024.11.13 |