스팬은 강력한 마크업 객체로,
지정할 수 있습니다. 텍스트 객체에 스팬을 연결하면
텍스트를 클릭할 수 있게 하는 등 다양한 방식으로 텍스트를 입력할 수 있습니다.
텍스트 크기를 조정하고, 맞춤 방식으로 텍스트를 그릴 수 있습니다. 스팬은 또한
TextPaint
속성을 변경하거나
Canvas
로 이동하여 텍스트 레이아웃을 변경합니다.
Android는 다양한 일반 텍스트 스타일 지정 패턴을 다루는 여러 유형의 스팬을 제공합니다. 자체 스팬을 만들어 맞춤 스타일 지정을 적용할 수도 있습니다.
스팬 만들기 및 적용
스팬을 만들려면 다음 표에 나열된 클래스 중 하나를 사용하면 됩니다. 클래스는 텍스트 자체의 변경 가능 여부, 텍스트가 변경 가능한지 여부에 따라 span 데이터가 포함된 기본 데이터 구조를 파악할 수 있습니다.
클래스 | 변경 가능한 텍스트 | 변경 가능한 마크업 | 데이터 구조 |
---|---|---|---|
SpannedString |
아니요 | 아니요 | 선형 배열 |
SpannableString |
아니요 | 예 | 선형 배열 |
SpannableStringBuilder |
예 | 예 | 간격 트리 |
세 클래스 모두 Spanned
를 확장합니다.
인터페이스에 추가되었습니다. SpannableString
및 SpannableStringBuilder
도 Spannable
인터페이스를 확장합니다.
사용할 방법을 결정하는 방법은 다음과 같습니다.
- 만들고 나서 텍스트 또는 마크업을 수정하지 않는다면
SpannedString
을 사용합니다. - 적은 수의 스팬을 단일 텍스트 객체에 연결해야 하고
텍스트 자체는 읽기 전용입니다.
SpannableString
를 사용하세요. - 생성 후 텍스트를 수정해야 하고 스팬을
텍스트는
SpannableStringBuilder
를 사용합니다. - 텍스트 객체 한 개에 많은 수의 스팬을 연결해야 하는 경우에는
텍스트 자체가 읽기 전용인지 확인하려면
SpannableStringBuilder
를 사용하세요.
스팬을 적용하려면 Spannable
객체의 setSpan(Object _what_, int _start_, int _end_, int
_flags_)
을 호출하세요. what 매개변수는 현재 있는 스팬을 나타냅니다.
적용되지 않으며 start 및 end 매개변수는
바로 '범위'입니다
스팬의 경계 안에 텍스트를 삽입하면 스팬이 자동으로
삽입한 텍스트를 포함합니다. 스팬 에 텍스트를 삽입하는 경우
경계선(즉, start 또는 end 색인에서)에 있는 flags는
매개변수는 스팬을 확장하여 삽입된 텍스트를 포함할지 여부를 결정합니다. 사용
Spannable.SPAN_EXCLUSIVE_INCLUSIVE
드림
플래그를 지정하여 삽입된 텍스트를 포함하고
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
삽입된 텍스트를 제외합니다.
다음 예는
ForegroundColorSpan
(으)로
문자열:
Kotlin
val spannable = SpannableStringBuilder("Text is spantastic!") spannable.setSpan( ForegroundColorSpan(Color.RED), 8, // start 12, // end Spannable.SPAN_EXCLUSIVE_INCLUSIVE )
자바
SpannableStringBuilder spannable = new SpannableStringBuilder("Text is spantastic!"); spannable.setSpan( new ForegroundColorSpan(Color.RED), 8, // start 12, // end Spannable.SPAN_EXCLUSIVE_INCLUSIVE );
스팬은 Spannable.SPAN_EXCLUSIVE_INCLUSIVE
를 사용하여 설정되므로
는
다음 예를 참고하세요.
Kotlin
val spannable = SpannableStringBuilder("Text is spantastic!") spannable.setSpan( ForegroundColorSpan(Color.RED), 8, // start 12, // end Spannable.SPAN_EXCLUSIVE_INCLUSIVE ) spannable.insert(12, "(& fon)")
자바
SpannableStringBuilder spannable = new SpannableStringBuilder("Text is spantastic!"); spannable.setSpan( new ForegroundColorSpan(Color.RED), 8, // start 12, // end Spannable.SPAN_EXCLUSIVE_INCLUSIVE ); spannable.insert(12, "(& fon)");
여러 스팬을 동일한 텍스트에 연결할 수 있습니다. 다음 예는 를 사용하여 굵고 빨간색의 텍스트를 만듭니다.
Kotlin
val spannable = SpannableString("Text is spantastic!") spannable.setSpan(ForegroundColorSpan(Color.RED), 8, 12, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) spannable.setSpan( StyleSpan(Typeface.BOLD), 8, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE )
자바
SpannableString spannable = new SpannableString("Text is spantastic!"); spannable.setSpan( new ForegroundColorSpan(Color.RED), 8, 12, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE ); spannable.setSpan( new StyleSpan(Typeface.BOLD), 8, spannable.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE );
Android 스팬 유형
Android는 android.text.style 패키지에서 스팬 유형 20개 이상을 제공합니다. Android는 두 가지 기본 방법으로 스팬을 분류합니다.
- 스팬이 텍스트에 미치는 영향: 스팬은 텍스트 모양이나 텍스트에 영향을 미칠 수 있습니다. 측정항목입니다.
- 스팬 범위: 일부 스팬은 개별 문자에 적용할 수 있지만 다른 스팬은 개별 문자에 적용할 수 있습니다. 전체 단락에 적용해야 합니다.
다음 섹션에서는 이러한 카테고리를 자세히 설명합니다.
텍스트 모양에 영향을 미치는 스팬
문자 수준에서 적용되는 일부 스팬은 텍스트 모양에 영향을 미칩니다. 예를 들면 다음과 같습니다.
텍스트나 배경 색상을 변경하고 밑줄이나 취소선을 추가할 수 있습니다. 이러한
스팬은
CharacterStyle
클래스
다음 코드 예는 UnderlineSpan
를 밑줄에 적용하는 방법을 보여줍니다.
텍스트:
Kotlin
val string = SpannableString("Text with underline span") string.setSpan(UnderlineSpan(), 10, 19, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
자바
SpannableString string = new SpannableString("Text with underline span"); string.setSpan(new UnderlineSpan(), 10, 19, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
텍스트 모양에만 영향을 미치는 스팬은 레이아웃 다시 계산을 트리거하지 않고 텍스트 다시 그리기를 트리거합니다. 이러한 스팬은 UpdateAppearance
를 구현하고 CharacterStyle
을 확장합니다.
CharacterStyle
서브클래스는 다음에 대한 액세스 권한을 제공하여 텍스트를 그리는 방법을 정의합니다.
TextPaint
업데이트
텍스트 측정항목에 영향을 미치는 스팬
문자 수준에서 적용되는 다른 범위는 텍스트 측정항목(예: 줄)에 영향을 미칩니다.
높이와 텍스트 크기를 변경할 수 있습니다. 이러한 스팬은
MetricAffectingSpan
드림
클래스에 대해 자세히 알아보세요.
다음 코드 예에서는
RelativeSizeSpan
텍스트 크기 50% 증가:
Kotlin
val string = SpannableString("Text with relative size span") string.setSpan(RelativeSizeSpan(1.5f), 10, 24, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
자바
SpannableString string = new SpannableString("Text with relative size span"); string.setSpan(new RelativeSizeSpan(1.5f), 10, 24, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
텍스트 측정항목에 영향을 미치는 스팬을 적용하면 관찰 객체가 올바른 레이아웃과 렌더링을 위해 텍스트를 다시 측정(예: 텍스트 크기에 따라 단어가 다른 줄에 표시될 수 있습니다. 위의 범위는 재측정, 텍스트 레이아웃 재계산, 확인할 수 있습니다.
텍스트 측정항목에 영향을 미치는 스팬은 MetricAffectingSpan
클래스를 확장합니다.
스팬이 텍스트 측정에 미치는 영향을 서브클래스가 정의할 수 있는 추상 클래스
(TextPaint
에 대한 액세스 권한을 제공하여) MetricAffectingSpan
가
CharacterSpan
: 서브클래스는 문자의 텍스트 모양에 영향을 줍니다.
있습니다.
단락에 영향을 미치는 스팬
스팬은 단락 수준에서 텍스트에 영향을 줄 수도 있습니다. 예를 들어
텍스트 블록의 여백을 조정할 수 있습니다. 전체 단락에 영향을 미치는 스팬은 ParagraphStyle
을 구현합니다. 받는사람
스팬을 사용하고 끝부분을 제외한 전체 단락에 첨부합니다.
줄바꿈 문자를 사용할 수 있습니다. 단락 스팬을
Android는 스팬을 전혀 적용하지 않습니다.
그림 8은 Android가 텍스트에서 단락을 구분하는 방법을 보여줍니다.
다음 코드 예에서는
QuoteSpan
을 단락으로 설정합니다. 참고:
스팬을
Android는 스타일을 전혀 적용하지 않습니다.
Kotlin
spannable.setSpan(QuoteSpan(color), 8, text.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
자바
spannable.setSpan(new QuoteSpan(color), 8, text.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
맞춤 스팬 만들기
기존 Android 스팬에서 제공하는 것보다 더 많은 기능이 필요하다면 맞춤 스팬을 구현하면 됩니다. 자체 스팬을 구현할 때는 스팬이 문자 수준에서 텍스트에 영향을 미치는지 또는 단락 수준에서 텍스트에 영향을 미치는지와 텍스트의 레이아웃이나 모양에도 영향을 줍니다. 이렇게 하면 확장할 수 있는 기본 클래스와 필요할 수 있는 인터페이스 결정 정의할 수 있습니다 다음 표를 참고하세요.
시나리오 | 클래스 또는 인터페이스 |
---|---|
스팬이 문자 수준에서 텍스트에 영향을 미칩니다. | CharacterStyle |
스팬이 텍스트 모양에 영향을 미칩니다. | UpdateAppearance |
스팬이 텍스트 측정항목에 영향을 미칩니다. | UpdateLayout |
스팬이 단락 수준에서 텍스트에 영향을 미칩니다. | ParagraphStyle |
예를 들어 텍스트 크기 및 텍스트 크기를 수정하는 맞춤 스팬을 구현해야 하는 경우
RelativeSizeSpan
를 확장합니다. 상속을 통해, RelativeSizeSpan
CharacterStyle
를 확장하고 두 개의 Update
인터페이스를 구현합니다. 이후
클래스는 이미 updateDrawState
및 updateMeasureState
콜백을 제공합니다.
이러한 콜백을 재정의하여 맞춤형 동작을 구현할 수 있습니다. 이
다음 코드는 RelativeSizeSpan
를 확장하는 맞춤 스팬을 만들고
다음과 같이 updateDrawState
콜백을 재정의하여 TextPaint
의 색상을 설정합니다.
Kotlin
class RelativeSizeColorSpan( size: Float, @ColorInt private val color: Int ) : RelativeSizeSpan(size) { override fun updateDrawState(textPaint: TextPaint) { super.updateDrawState(textPaint) textPaint.color = color } }
자바
public class RelativeSizeColorSpan extends RelativeSizeSpan { private int color; public RelativeSizeColorSpan(float spanSize, int spanColor) { super(spanSize); color = spanColor; } @Override public void updateDrawState(TextPaint textPaint) { super.updateDrawState(textPaint); textPaint.setColor(color); } }
이 예에서는 맞춤 스팬을 만드는 방법을 보여줍니다. 사용자는
텍스트에 RelativeSizeSpan
및 ForegroundColorSpan
를 적용하여 효과를 낼 수 있습니다.
테스트 스팬 사용
Spanned
인터페이스를 사용하면 스팬을 설정하고 다음에서 스팬을 검색할 수 있습니다.
있습니다. 테스트 시에는 Android JUnit를 구현합니다.
테스트를 사용하여 올바른 스팬이 추가되었는지 확인
올바른 위치에 놓으세요 텍스트 스타일 지정 샘플
앱
에는
BulletPointSpan
를 텍스트에 추가합니다. 다음 코드 예는
글머리 기호가 예상대로 표시되는지 확인하세요.
Kotlin
@Test fun textWithBulletPoints() { val result = builder.markdownToSpans("Points\n* one\n+ two") // Check whether the markup tags are removed. assertEquals("Points\none\ntwo", result.toString()) // Get all the spans attached to the SpannedString. val spans = result.getSpans<Any>(0, result.length, Any::class.java) // Check whether the correct number of spans are created. assertEquals(2, spans.size.toLong()) // Check whether the spans are instances of BulletPointSpan. val bulletSpan1 = spans[0] as BulletPointSpan val bulletSpan2 = spans[1] as BulletPointSpan // Check whether the start and end indices are the expected ones. assertEquals(7, result.getSpanStart(bulletSpan1).toLong()) assertEquals(11, result.getSpanEnd(bulletSpan1).toLong()) assertEquals(11, result.getSpanStart(bulletSpan2).toLong()) assertEquals(14, result.getSpanEnd(bulletSpan2).toLong()) }
자바
@Test public void textWithBulletPoints() { SpannedString result = builder.markdownToSpans("Points\n* one\n+ two"); // Check whether the markup tags are removed. assertEquals("Points\none\ntwo", result.toString()); // Get all the spans attached to the SpannedString. Object[] spans = result.getSpans(0, result.length(), Object.class); // Check whether the correct number of spans are created. assertEquals(2, spans.length); // Check whether the spans are instances of BulletPointSpan. BulletPointSpan bulletSpan1 = (BulletPointSpan) spans[0]; BulletPointSpan bulletSpan2 = (BulletPointSpan) spans[1]; // Check whether the start and end indices are the expected ones. assertEquals(7, result.getSpanStart(bulletSpan1)); assertEquals(11, result.getSpanEnd(bulletSpan1)); assertEquals(11, result.getSpanStart(bulletSpan2)); assertEquals(14, result.getSpanEnd(bulletSpan2)); }
더 많은 테스트 예는 다음을 참고하세요. GitHub의 MarkdownBuilderTest
커스텀 스팬 테스트
스팬을 테스트할 때 TextPaint
에 예상된
Canvas
에 올바른 요소가 표시되는지 확인합니다. 예를 들어 글머리 기호를 일부 텍스트 앞에 붙이는 맞춤 스팬 구현을 고려하세요. 글머리기호에 크기와 색상이 지정되어 있으며 공백이 있음
글머리기호 사이의 간격에 가까워집니다.
AndroidJUnit 테스트를 구현하고 다음을 확인하여 이 클래스의 동작을 테스트할 수 있습니다.
- 스팬을 올바르게 적용하면 지정된 크기의 글머리 기호와 캔버스에 색상이 표시되고 왼쪽과 조정할 수 있습니다.
- 스팬을 적용하지 않으면 맞춤 동작이 나타나지 않습니다.
이러한 테스트의 구현은 TextStyling 샘플을 참고하세요.
캔버스를 모의 처리하고 모의 처리된
객체를
drawLeadingMargin()
드림
메서드를 사용하고 올바른 메서드가 올바른 메서드로 호출되는지
매개변수입니다.
더 많은 스팬 테스트 샘플은 BulletPointSpanTest를 사용합니다.
스팬 사용 권장사항
TextView
에 텍스트를 설정하는 메모리 효율적인 방법이 몇 가지 있습니다.
선택하세요.
기본 텍스트 변경 없이 스팬 연결 또는 분리
TextView.setText()
드림
스팬을 다르게 처리하는 여러 오버로드를 포함합니다. 예를 들어
다음 코드를 사용하여 Spannable
텍스트 객체를 설정합니다.
Kotlin
textView.setText(spannableObject)
자바
textView.setText(spannableObject);
setText()
의 이 오버로드를 호출할 때 TextView
는 Spannable
의 복사본을 SpannedString
으로 만들고 CharSequence
로 메모리에 보관합니다.
즉, 텍스트와 스팬은 변경할 수 없으므로
텍스트 또는 스팬을 업데이트하고 새 Spannable
객체를 만든 다음
다시 setText()
를 호출합니다. 이렇게 하면
있습니다.
스팬이 변경 가능해야 함을 나타내려면 대신
setText(CharSequence text, TextView.BufferType
type)
님,
다음과 같습니다.
Kotlin
textView.setText(spannable, BufferType.SPANNABLE) val spannableText = textView.text as Spannable spannableText.setSpan( ForegroundColorSpan(color), 8, spannableText.length, SPAN_INCLUSIVE_INCLUSIVE )
자바
textView.setText(spannable, BufferType.SPANNABLE); Spannable spannableText = (Spannable) textView.getText(); spannableText.setSpan( new ForegroundColorSpan(color), 8, spannableText.getLength(), SPAN_INCLUSIVE_INCLUSIVE);
이 예에서
BufferType.SPANNABLE
드림
매개변수로 인해 TextView
가 SpannableString
를 만들고
이제 TextView
에 의해 보관되는 CharSequence
객체에 변경 가능한 마크업이 있으며
있습니다. 스팬을 업데이트하려면 텍스트를 Spannable
로 검색한 다음
필요에 따라 스팬을 업데이트할 수 있습니다
스팬을 연결, 분리 또는 재배치할 때 TextView
가 자동으로 업데이트되어 텍스트의 변경사항을 반영합니다. 내부 속성을 변경하는 경우
invalidate()
를 호출하여 모양 관련 변경을 수행하거나
측정항목 관련 변경을 수행하기 위한 requestLayout()
입니다.
TextView에서 텍스트를 여러 번 설정
RecyclerView.ViewHolder
를 사용할 때처럼 어떤 경우에는 TextView
를 재사용하여 텍스트를 여러 번 설정하는 것이 좋습니다. 작성자:
기본적으로 BufferType
의 설정 여부와 관계없이 TextView
는
CharSequence
객체의 사본을 만들어 메모리에 보관합니다. 이렇게 하면
TextView
는 의도적으로 업데이트하므로 원본은 업데이트할 수 없습니다.
CharSequence
객체를 사용하여 텍스트를 업데이트합니다. 즉, 새 포드를 설정할 때마다
TextView
는 새 객체를 만듭니다.
이 프로세스를 더 세밀하게 제어하고 싶은 경우
자체적인
Spannable.Factory
및 재정의
newSpannable()
새 텍스트 객체를 만드는 대신 기존 텍스트 객체를 변환하고 반환할 수 있습니다.
다음 예와 같이 CharSequence
를 Spannable
로 설정합니다.
Kotlin
val spannableFactory = object : Spannable.Factory() { override fun newSpannable(source: CharSequence?): Spannable { return source as Spannable } }
Java
Spannable.Factory spannableFactory = new Spannable.Factory(){ @Override public Spannable newSpannable(CharSequence source) { return (Spannable) source; } };
다음과 같은 경우 textView.setText(spannableObject, BufferType.SPANNABLE)
를 사용해야 합니다.
텍스트를 설정하는 것입니다. 그렇지 않으면 소스 CharSequence
가 Spanned
로 생성됩니다.
인스턴스를 사용하고 Spannable
로 변환할 수 없으므로 newSpannable()
에서
ClassCastException
입니다.
newSpannable()
를 재정의한 후 새 Factory
를 사용하도록 TextView
에 지시합니다.
Kotlin
textView.setSpannableFactory(spannableFactory)
자바
textView.setSpannableFactory(spannableFactory);
Spannable.Factory
객체를 한 번 설정합니다.
TextView
입니다. RecyclerView
를 사용 중인 경우 다음과 같은 경우에 Factory
객체를 설정합니다.
먼저 뷰를 확장합니다. 이렇게 하면
RecyclerView
는 새 항목을 ViewHolder
에 바인딩합니다.
내부 스팬 속성 변경
변경 가능한 스팬의 내부 속성(예:
글머리기호 색상을 사용하면 오버헤드가 발생하지 않도록
setText()
가 생성될 때 스팬 참조를 유지하여 여러 번
스팬을 수정해야 하는 경우 참조를 수정한 다음
유형에 따라 TextView
의 invalidate()
또는 requestLayout()
변경할 수 있습니다.
다음 코드 예에서 맞춤 글머리 기호 구현에는 버튼을 탭하면 회색으로 변경되는 기본 빨간색
Kotlin
class MainActivity : AppCompatActivity() { // Keeping the span as a field. val bulletSpan = BulletPointSpan(color = Color.RED) override fun onCreate(savedInstanceState: Bundle?) { ... val spannable = SpannableString("Text is spantastic") // Setting the span to the bulletSpan field. spannable.setSpan( bulletSpan, 0, 4, Spanned.SPAN_INCLUSIVE_INCLUSIVE ) styledText.setText(spannable) button.setOnClickListener { // Change the color of the mutable span. bulletSpan.color = Color.GRAY // Color doesn't change until invalidate is called. styledText.invalidate() } } }
자바
public class MainActivity extends AppCompatActivity { private BulletPointSpan bulletSpan = new BulletPointSpan(Color.RED); @Override protected void onCreate(Bundle savedInstanceState) { ... SpannableString spannable = new SpannableString("Text is spantastic"); // Setting the span to the bulletSpan field. spannable.setSpan(bulletSpan, 0, 4, Spanned.SPAN_INCLUSIVE_INCLUSIVE); styledText.setText(spannable); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // Change the color of the mutable span. bulletSpan.setColor(Color.GRAY); // Color doesn't change until invalidate is called. styledText.invalidate(); } }); } }
Android KTX 확장 프로그램 기능 사용
Android KTX에는 스팬을 다루는 확장 함수도 포함되어 있습니다. 더 쉽게 만들 수 있습니다 자세한 내용은 androidx.core.text 패키지의 문서를 참고하세요.