스팬

Compose 방식 사용해 보기
Jetpack Compose는 Android에 권장되는 UI 도구 키트입니다. Compose에서 텍스트를 사용하는 방법을 알아보세요.
<ph type="x-smartling-placeholder"></ph> 텍스트 내 여러 스타일 → 를 통해 개인정보처리방침을 정의할 수 있습니다.

스팬은 강력한 마크업 객체로, 지정할 수 있습니다. 텍스트 객체에 스팬을 연결하면 텍스트를 클릭할 수 있게 하는 등 다양한 방식으로 텍스트를 입력할 수 있습니다. 텍스트 크기를 조정하고, 맞춤 방식으로 텍스트를 그릴 수 있습니다. 스팬은 또한 TextPaint 속성을 변경하거나 Canvas로 이동하여 텍스트 레이아웃을 변경합니다.

Android는 다양한 일반 텍스트 스타일 지정 패턴을 다루는 여러 유형의 스팬을 제공합니다. 자체 스팬을 만들어 맞춤 스타일 지정을 적용할 수도 있습니다.

스팬 만들기 및 적용

스팬을 만들려면 다음 표에 나열된 클래스 중 하나를 사용하면 됩니다. 클래스는 텍스트 자체의 변경 가능 여부, 텍스트가 변경 가능한지 여부에 따라 span 데이터가 포함된 기본 데이터 구조를 파악할 수 있습니다.

클래스 변경 가능한 텍스트 변경 가능한 마크업 데이터 구조
SpannedString 아니요 아니요 선형 배열
SpannableString 아니요 선형 배열
SpannableStringBuilder 간격 트리

세 클래스 모두 Spanned를 확장합니다. 인터페이스에 추가되었습니다. SpannableStringSpannableStringBuilderSpannable 인터페이스를 확장합니다.

사용할 방법을 결정하는 방법은 다음과 같습니다.

  • 만들고 나서 텍스트 또는 마크업을 수정하지 않는다면 SpannedString을 사용합니다.
  • 적은 수의 스팬을 단일 텍스트 객체에 연결해야 하고 텍스트 자체는 읽기 전용입니다. SpannableString를 사용하세요.
  • 생성 후 텍스트를 수정해야 하고 스팬을 텍스트는 SpannableStringBuilder를 사용합니다.
  • 텍스트 객체 한 개에 많은 수의 스팬을 연결해야 하는 경우에는 텍스트 자체가 읽기 전용인지 확인하려면 SpannableStringBuilder를 사용하세요.

스팬을 적용하려면 Spannable 객체의 setSpan(Object _what_, int _start_, int _end_, int _flags_)을 호출하세요. what 매개변수는 현재 있는 스팬을 나타냅니다. 적용되지 않으며 startend 매개변수는 바로 '범위'입니다

스팬의 경계 안에 텍스트를 삽입하면 스팬이 자동으로 삽입한 텍스트를 포함합니다. 스팬 텍스트를 삽입하는 경우 경계선(즉, 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
);
부분적으로 빨간색인 회색 텍스트를 보여주는 이미지입니다.
그림 1. ForegroundColorSpan

스팬은 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)");
SPAN_EXCLUSIVE_INCLUSIVE 사용 시 스팬에 추가 텍스트가 포함되는 방식을 보여주는 이미지
그림 2. 스팬은 를 사용할 때 추가 텍스트를 Spannable.SPAN_EXCLUSIVE_INCLUSIVE

여러 스팬을 동일한 텍스트에 연결할 수 있습니다. 다음 예는 를 사용하여 굵고 빨간색의 텍스트를 만듭니다.

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
);
여러 스팬(`ForegroundColorSpan(Color.RED)` 및 `StyleSpan(BOLD)`)이 있는 텍스트를 보여주는 이미지
그림 3. 여러 스팬이 있는 텍스트: ForegroundColorSpan(Color.RED)StyleSpan(BOLD)입니다.

Android 스팬 유형

Android는 android.text.style 패키지에서 스팬 유형 20개 이상을 제공합니다. Android는 두 가지 기본 방법으로 스팬을 분류합니다.

  • 스팬이 텍스트에 미치는 영향: 스팬은 텍스트 모양이나 텍스트에 영향을 미칠 수 있습니다. 측정항목입니다.
  • 스팬 범위: 일부 스팬은 개별 문자에 적용할 수 있지만 다른 스팬은 개별 문자에 적용할 수 있습니다. 전체 단락에 적용해야 합니다.
를 통해 개인정보처리방침을 정의할 수 있습니다. <ph type="x-smartling-placeholder">
</ph> <ph type="x-smartling-placeholder">다양한 스팬 카테고리를 보여주는 이미지</ph> <ph type="x-smartling-placeholder">
</ph> 그림 4. 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);
`underlineSpan`을 사용하여 텍스트에 밑줄을 긋는 방법을 보여주는 이미지
그림 5. 를 사용하여 밑줄이 표시된 텍스트 UnderlineSpan

텍스트 모양에만 영향을 미치는 스팬은 레이아웃 다시 계산을 트리거하지 않고 텍스트 다시 그리기를 트리거합니다. 이러한 스팬은 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);
relativeSizeSpan의 사용을 보여주는 이미지
그림 6. RelativeSizeSpan

텍스트 측정항목에 영향을 미치는 스팬을 적용하면 관찰 객체가 올바른 레이아웃과 렌더링을 위해 텍스트를 다시 측정(예: 텍스트 크기에 따라 단어가 다른 줄에 표시될 수 있습니다. 위의 범위는 재측정, 텍스트 레이아웃 재계산, 확인할 수 있습니다.

텍스트 측정항목에 영향을 미치는 스팬은 MetricAffectingSpan 클래스를 확장합니다. 스팬이 텍스트 측정에 미치는 영향을 서브클래스가 정의할 수 있는 추상 클래스 (TextPaint에 대한 액세스 권한을 제공하여) MetricAffectingSpanCharacterSpan: 서브클래스는 문자의 텍스트 모양에 영향을 줍니다. 있습니다.

단락에 영향을 미치는 스팬

스팬은 단락 수준에서 텍스트에 영향을 줄 수도 있습니다. 예를 들어 텍스트 블록의 여백을 조정할 수 있습니다. 전체 단락에 영향을 미치는 스팬은 ParagraphStyle을 구현합니다. 받는사람 스팬을 사용하고 끝부분을 제외한 전체 단락에 첨부합니다. 줄바꿈 문자를 사용할 수 있습니다. 단락 스팬을 Android는 스팬을 전혀 적용하지 않습니다.

그림 8은 Android가 텍스트에서 단락을 구분하는 방법을 보여줍니다.

그림 7. Android에서 단락은 줄바꿈 (\n) 문자

다음 코드 예에서는 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);
QuoteSpan의 예를 보여주는 이미지
그림 8. QuoteSpan 적용됩니다.

맞춤 스팬 만들기

기존 Android 스팬에서 제공하는 것보다 더 많은 기능이 필요하다면 맞춤 스팬을 구현하면 됩니다. 자체 스팬을 구현할 때는 스팬이 문자 수준에서 텍스트에 영향을 미치는지 또는 단락 수준에서 텍스트에 영향을 미치는지와 텍스트의 레이아웃이나 모양에도 영향을 줍니다. 이렇게 하면 확장할 수 있는 기본 클래스와 필요할 수 있는 인터페이스 결정 정의할 수 있습니다 다음 표를 참고하세요.

시나리오 클래스 또는 인터페이스
스팬이 문자 수준에서 텍스트에 영향을 미칩니다. CharacterStyle
스팬이 텍스트 모양에 영향을 미칩니다. UpdateAppearance
스팬이 텍스트 측정항목에 영향을 미칩니다. UpdateLayout
스팬이 단락 수준에서 텍스트에 영향을 미칩니다. ParagraphStyle

예를 들어 텍스트 크기 및 텍스트 크기를 수정하는 맞춤 스팬을 구현해야 하는 경우 RelativeSizeSpan를 확장합니다. 상속을 통해, RelativeSizeSpan CharacterStyle를 확장하고 두 개의 Update 인터페이스를 구현합니다. 이후 클래스는 이미 updateDrawStateupdateMeasureState 콜백을 제공합니다. 이러한 콜백을 재정의하여 맞춤형 동작을 구현할 수 있습니다. 이 다음 코드는 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);
    }
}

이 예에서는 맞춤 스팬을 만드는 방법을 보여줍니다. 사용자는 텍스트에 RelativeSizeSpanForegroundColorSpan를 적용하여 효과를 낼 수 있습니다.

테스트 스팬 사용

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()의 이 오버로드를 호출할 때 TextViewSpannable의 복사본을 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 드림 매개변수로 인해 TextViewSpannableString를 만들고 이제 TextView에 의해 보관되는 CharSequence 객체에 변경 가능한 마크업이 있으며 있습니다. 스팬을 업데이트하려면 텍스트를 Spannable로 검색한 다음 필요에 따라 스팬을 업데이트할 수 있습니다

스팬을 연결, 분리 또는 재배치할 때 TextView가 자동으로 업데이트되어 텍스트의 변경사항을 반영합니다. 내부 속성을 변경하는 경우 invalidate()를 호출하여 모양 관련 변경을 수행하거나 측정항목 관련 변경을 수행하기 위한 requestLayout()입니다.

TextView에서 텍스트를 여러 번 설정

RecyclerView.ViewHolder를 사용할 때처럼 어떤 경우에는 TextView를 재사용하여 텍스트를 여러 번 설정하는 것이 좋습니다. 작성자: 기본적으로 BufferType의 설정 여부와 관계없이 TextViewCharSequence 객체의 사본을 만들어 메모리에 보관합니다. 이렇게 하면 TextView는 의도적으로 업데이트하므로 원본은 업데이트할 수 없습니다. CharSequence 객체를 사용하여 텍스트를 업데이트합니다. 즉, 새 포드를 설정할 때마다 TextView는 새 객체를 만듭니다.

이 프로세스를 더 세밀하게 제어하고 싶은 경우 자체적인 Spannable.Factory 및 재정의 newSpannable() 새 텍스트 객체를 만드는 대신 기존 텍스트 객체를 변환하고 반환할 수 있습니다. 다음 예와 같이 CharSequenceSpannable로 설정합니다.

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)를 사용해야 합니다. 텍스트를 설정하는 것입니다. 그렇지 않으면 소스 CharSequenceSpanned로 생성됩니다. 인스턴스를 사용하고 Spannable로 변환할 수 없으므로 newSpannable()에서 ClassCastException입니다.

newSpannable()를 재정의한 후 새 Factory를 사용하도록 TextView에 지시합니다.

Kotlin

textView.setSpannableFactory(spannableFactory)

자바

textView.setSpannableFactory(spannableFactory);

Spannable.Factory 객체를 한 번 설정합니다. TextView입니다. RecyclerView를 사용 중인 경우 다음과 같은 경우에 Factory 객체를 설정합니다. 먼저 뷰를 확장합니다. 이렇게 하면 RecyclerView는 새 항목을 ViewHolder에 바인딩합니다.

내부 스팬 속성 변경

변경 가능한 스팬의 내부 속성(예: 글머리기호 색상을 사용하면 오버헤드가 발생하지 않도록 setText()가 생성될 때 스팬 참조를 유지하여 여러 번 스팬을 수정해야 하는 경우 참조를 수정한 다음 유형에 따라 TextViewinvalidate() 또는 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 패키지의 문서를 참고하세요.