Androidでページ送り(ページネーション)テキストを実装する方法
このチュートリアルでは、Androidアプリでページ送り(ページネーション)対応のテキスト表示を実装する方法を、サンプルコードとともにわかりやすく解説します。
ページネーションテキストとは
電子書籍リーダーのように、長文テキストを画面の高さに合わせて複数ページに分割し、「前へ」「次へ」の操作でページを切り替えられる表示方式です。Androidでは StaticLayout クラスを活用することで、テキストの行数や改行位置を事前に計算し、ページごとに文字列を自動的に分割できます。
実装手順
ステップ1:新規プロジェクトを作成する
Android Studioで「File」→「New Project」を選択し、必要な項目をすべて入力して新しいプロジェクトを作成します。
ステップ2:レイアウトファイル(activity_main.xml)にコードを追加する
res/layout/activity_main.xml に以下のコードを記述します。このレイアウトは、テキストを表示する TextView と、ページを移動するための「戻る」「進む」ボタンで構成されています。
<?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/btn_back" 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/btn_forward" 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.java にコードを追加する
src/MainActivity.java に以下のコードを記述します。ここでは、HTML形式の文字列とスタイル付き文字列(Spannable)を連結したテキストを用意し、レイアウトの描画が完了したタイミングで Pagination クラスによるページ分割を行っています。また、ボタンのクリックイベントで現在のページ番号を更新し、表示内容を切り替えます。
package app.com.sample;
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.Spanned;
import android.text.TextUtils;
import android.text.style.ForegroundColorSpan;
import android.text.style.RelativeSizeSpan;
import android.text.style.StyleSpan;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private TextView mTextView;
private Pagination mPagination;
private CharSequence mText;
private int mCurrentIndex = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = findViewById(R.id.tv);
Spanned htmlString = Html.fromHtml(getString(R.string.html_string));
Spannable spanString = new SpannableString(getString(R.string.long_string));
spanString.setSpan(new ForegroundColorSpan(Color.BLUE), 0, 24, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
spanString.setSpan(new RelativeSizeSpan(2f), 0, 24, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
spanString.setSpan(new StyleSpan(Typeface.MONOSPACE.getStyle()), 0, 24, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
spanString.setSpan(new ForegroundColorSpan(Color.BLUE), 700, spanString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
spanString.setSpan(new RelativeSizeSpan(2f), 700, spanString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
spanString.setSpan(new StyleSpan(Typeface.MONOSPACE.getStyle()), 700, spanString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
mText = TextUtils.concat(htmlString, spanString);
mTextView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
// Removing layout listener to avoid multiple calls
mTextView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
mPagination = new Pagination(
mText, mTextView.getWidth(),
mTextView.getHeight(),
mTextView.getPaint(),
mTextView.getLineSpacingMultiplier(),
mTextView.getLineSpacingExtra(),
mTextView.getIncludeFontPadding());
update();
}
});
findViewById(R.id.btn_back).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mCurrentIndex = (mCurrentIndex > 0) ? mCurrentIndex - 1 : 0;
update();
}
});
findViewById(R.id.btn_forward).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mCurrentIndex = (mCurrentIndex < mPagination.size() - 1) ? mCurrentIndex + 1 : mPagination.size() - 1;
update();
}
});
}
private void update() {
final CharSequence text = mPagination.get(mCurrentIndex);
if (text != null) mTextView.setText(text);
}
}
ステップ4:Pagination.java を作成する
src/Pagination.java に以下のコードを記述します。このクラスがページネーション処理の中核となる部分です。StaticLayout を使って各行の高さを計算し、TextView の高さを超えた時点でテキストを区切ってページとして登録します。
package app.com.sample;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import java.util.ArrayList;
import java.util.List;
class Pagination {
private final boolean mIncludePad;
private final int mWidth;
private final int mHeight;
private final float mSpacingMult;
private final float mSpacingAdd;
private final CharSequence mText;
private final TextPaint mPaint;
private final List<CharSequence> mPages;
Pagination(CharSequence text, int pageW, int pageH, TextPaint paint, float spacingMult, float spacingAdd, boolean inclidePad) {
this.mText = text;
this.mWidth = pageW;
this.mHeight = pageH;
this.mPaint = paint;
this.mSpacingMult = spacingMult;
this.mSpacingAdd = spacingAdd;
this.mIncludePad = inclidePad;
this.mPages = new ArrayList<>();
layout();
}
private void layout() {
final StaticLayout layout = new StaticLayout(mText, mPaint, mWidth, Layout.Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, mIncludePad);
final int lines = layout.getLineCount();
final CharSequence text = layout.getText();
int startOffset = 0;
int height = mHeight;
for (int i = 0; i < lines; i++) {
if (height < layout.getLineBottom(i)) {
// When the layout height has been exceeded
addPage(text.subSequence(startOffset, layout.getLineStart(i)));
startOffset = layout.getLineStart(i);
height = layout.getLineTop(i) + mHeight;
}
if (i == lines - 1) {
// Put the rest of the text into the last page
addPage(text.subSequence(startOffset, layout.getLineEnd(i)));
return;
}
}
}
private void addPage(CharSequence text) {
mPages.add(text);
}
int size() {
return mPages.size();
}
CharSequence get(int index) {
return (index >= 0 && index < mPages.size()) ? mPages.get(index) : null;
}
}
ステップ5:AndroidManifest.xml にコードを追加する
androidManifest.xml に以下のコードを記述します。
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="https://schemas.android.com/apk/res/android" package="app.com.sample"> <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」アイコンをクリックしてください。実行デバイスとして接続済みのモバイル端末を選択すると、端末上に以下のような初期画面が表示されます。


まとめ
本記事では、StaticLayout を利用して長文テキストを自動的にページ分割し、ボタン操作で前後のページに移動できる実装方法を紹介しました。TextView の幅・高さ・行間などの情報をもとにページを計算する仕組みは、電子書籍アプリや記事閲覧アプリなど、長文コンテンツを扱う場面で非常に役立ちます。ぜひ自分のプロジェクトにも応用してみてください。
-
Androidアプリでテキストファイルを簡単に読み込む方法【サンプルコード付き】
この記事では、Androidアプリでシンプルなテキストファイルを読み込む方法を、実際のサンプルコードとともにステップごとに解説します。res/rawフォルダに配置したテキストファイルを読み込み、画面に表示するまでの一連の流れを学ぶことができます。 ステップ1:プロジェクトの作成とテキストファイルの配置 まず、Android Studioで新しいプロジェクトを作成します。メニューから「File」→「New Project」を選択し、必要な項目をすべて入力してプロジェクトを作成しましょう。 続いて、新しいAndroidリソースディレクトリとして「raw」フォルダを作成し、読み込みたいテキストファ
-
TkinterのTextウィジェットでハイパーリンクを作成する方法を解説
TkinterのTextウィジェットは、複数行にわたるユーザー入力を受け付けるために一般的に使用されるウィジェットです。しかし、テキストドキュメントの中には、ユーザーを別のページへ誘導したい場合など、ハイパーリンクを含めたいケースもあります。本記事では、PythonのHyperLinkManagerスニペットを使って、Textウィジェット内にハイパーリンクを作成する方法を詳しく解説します。HyperLinkManagerとはHyperLinkManagerは、Textウィジェット内の特定のキーワードにハイパーリンク機能を追加するためのコードスニペットです。このスニペットを利用することで、クリッ