Computer >> 컴퓨터 >  >> 프로그래밍 >> Android

코틀린(Kotlin)으로 안드로이드 텍스트 페이지네이션 구현하는 방법

이 예제는 Kotlin을 사용해 Android에서 텍스트 페이지네이션(pagination) 기능을 구현하는 방법을 보여줍니다. 긴 문자열을 TextView 크기에 맞게 여러 페이지로 나눈 뒤, 이전·다음 버튼을 눌러 페이지 사이를 자유롭게 이동할 수 있습니다.

프로젝트 설정 및 구현 순서

1단계 − 새 프로젝트 생성

Android Studio에서 File ⇒ New Project 메뉴를 선택하여 새 프로젝트를 만들고, 프로젝트 생성에 필요한 정보를 모두 입력합니다.

2단계 − activity_main.xml 작성

res/layout/activity_main.xml에 아래 코드를 추가합니다. 화면 전체를 채우는 TextView와 함께, 왼쪽 영역은 '이전' 버튼(buttonBack), 오른쪽 영역은 '다음' 버튼(buttonForward)으로 사용합니다.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="16dp"
    android:paddingTop="16dp"
    android:paddingRight="16dp"
    android:paddingBottom="16dp">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    <Button
        android:id="@+id/buttonBack"
        style="?android:attr/buttonBarButtonStyle"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:background="@android:color/transparent" />
    <Button
        android:id="@+id/buttonForward"
        style="?android:attr/buttonBarButtonStyle"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:background="@android:color/transparent" />
    </LinearLayout>
    <TextView
        android:id="@+id/tv"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</RelativeLayout>

3단계 − MainActivity.kt 작성

src/MainActivity.kt에 아래 코드를 추가합니다. HTML 문자열과 Spannable 스타일이 적용된 문자열을 하나로 합친 뒤, TextView의 레이아웃이 완성되는 시점(onGlobalLayout)에 Pagination 객체를 생성하고 현재 페이지를 화면에 표시합니다.

import android.graphics.Color
import android.graphics.Typeface
import android.os.Bundle
import android.text.Html
import android.text.Spannable
import android.text.SpannableString
import android.text.TextUtils
import android.text.style.ForegroundColorSpan
import android.text.style.RelativeSizeSpan
import android.text.style.StyleSpan
import android.view.ViewTreeObserver
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {
    lateinit var buttonBack: Button
    lateinit var buttonForward: Button
    private lateinit var textView: TextView
    private var pagination: Pagination? = null
    private lateinit var charSequence: CharSequence
    private var currentIndex = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        title = "KotlinApp"
        buttonBack = findViewById(R.id.buttonBack)
        buttonForward = findViewById(R.id.buttonForward)
        textView = findViewById(R.id.tv)

        val htmlString = Html.fromHtml(getString(R.string.html_string))
        val spanString = SpannableString(getString(R.string.long_string))
        spanString.setSpan(ForegroundColorSpan(Color.BLUE), 0, 24, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
        spanString.setSpan(RelativeSizeSpan(2f), 0, 24, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
        spanString.setSpan(StyleSpan(Typeface.MONOSPACE.style), 0, 24, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
        spanString.setSpan(ForegroundColorSpan(Color.BLUE), 536, spanString.length,
            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
        spanString.setSpan(RelativeSizeSpan(2f), 536, spanString.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
        spanString.setSpan(StyleSpan(Typeface.MONOSPACE.style), 536, spanString.length,
            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
        charSequence = TextUtils.concat(htmlString, spanString)

        textView.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
            override fun onGlobalLayout() {
                // 여러 번 호출되지 않도록 리스너 제거
                textView.viewTreeObserver.removeOnGlobalLayoutListener(this)
                pagination = Pagination(
                    charSequence, textView.width,
                    textView.height,
                    textView.paint,
                    textView.lineSpacingMultiplier,
                    textView.lineSpacingExtra,
                    textView.includeFontPadding
                )
                update()
            }
        })

        buttonBack.setOnClickListener {
            currentIndex = if ((currentIndex > 0)) currentIndex - 1 else 0
            update()
        }
        buttonForward.setOnClickListener {
            currentIndex = if ((currentIndex < pagination!!.size() - 1)) currentIndex + 1 else pagination!!.size() - 1
            update()
        }
    }

    private fun update() {
        val text = pagination!![currentIndex]
        if (text != null) textView.text = text
    }
}

참고: 위 코드는 strings.xml에 정의된 html_string과 long_string 리소스를 사용합니다. 테스트에 사용할 충분히 긴 일반 문자열과 HTML 문자열을 미리 등록해 두어야 합니다.

4단계 − Pagination 클래스 생성

새 Kotlin 클래스를 만들고 아래 코드를 추가합니다. 이 클래스는 StaticLayout을 활용해 전체 텍스트를 TextView의 실제 너비와 높이에 맞춰 한 줄씩 측정하고, 화면을 벗어나는 지점마다 페이지를 분리합니다.

import android.text.StaticLayout
import android.text.TextPaint

class Pagination(
    private val text: CharSequence,
    private val pageWidth: Int,
    private val pageHeight: Int,
    private val paint: TextPaint,
    private val lineSpacingMultiplier: Float,
    private val lineSpacingExtra: Float,
    private val includeFontPadding: Boolean
) {
    private val pages = mutableListOf<CharSequence>()

    init {
        paginate()
    }

    private fun paginate() {
        var start = 0
        while (start < text.length) {
            val layout = StaticLayout.Builder
                .obtain(text, start, text.length, paint, pageWidth)
                .setLineSpacing(lineSpacingExtra, lineSpacingMultiplier)
                .setIncludePad(includeFontPadding)
                .build()
            var end = text.length
            for (line in 0 until layout.lineCount) {
                if (layout.getLineBottom(line) > pageHeight) {
                    end = layout.getLineStart(line)
                    break
                }
            }
            if (end <= start) break
            pages.add(text.subSequence(start, end))
            start = end
        }
    }

    operator fun get(index: Int): CharSequence? = pages.getOrNull(index)

    fun size(): Int = pages.size
}

5단계 − AndroidManifest.xml 작성

androidManifest.xml에 아래 코드를 추가합니다.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
    package="com.example.q11">
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

앱 실행 및 결과 확인

이제 애플리케이션을 실행해 보겠습니다. 실제 Android 기기가 컴퓨터에 연결되어 있다고 가정합니다. Android Studio에서 프로젝트의 액티비티 파일 중 하나를 연 뒤 툴바의 Run 코틀린(Kotlin)으로 안드로이드 텍스트 페이지네이션 구현하는 방법 아이콘을 클릭하고, 목록에서 본인의 모바일 기기를 선택하세요. 그러면 기기 화면에 첫 번째 페이지가 표시됩니다.

왼쪽 버튼을 누르면 이전 페이지로, 오른쪽 버튼을 누르면 다음 페이지로 이동합니다. 첫 페이지에서는 인덱스가 0보다 작아지지 않고, 마지막 페이지에서는 전체 페이지 수를 초과하지 않도록 처리되어 있어 안정적으로 동작합니다.

코틀린(Kotlin)으로 안드로이드 텍스트 페이지네이션 구현하는 방법