I have a long text which I want to break into multiple pages. Also I need a way to style this text.
1 Answers
UPDATE: I created sample application, that shows how to use PageSplitter.
How it works? Example application (Russian) - Cleverum. You need only PageSplitter
class. Other code shows you how to use this class.
import android.graphics.Typeface;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.TextPaint;
import android.text.style.StyleSpan;
import java.util.ArrayList;
import java.util.List;
public class PageSplitter {
private final int pageWidth;
private final int pageHeight;
private final float lineSpacingMultiplier;
private final int lineSpacingExtra;
private final List<CharSequence> pages = new ArrayList<CharSequence>();
private SpannableStringBuilder currentLine = new SpannableStringBuilder();
private SpannableStringBuilder currentPage = new SpannableStringBuilder();
private int currentLineHeight;
private int pageContentHeight;
private int currentLineWidth;
private int textLineHeight;
public PageSplitter(int pageWidth, int pageHeight, float lineSpacingMultiplier, int lineSpacingExtra) {
this.pageWidth = pageWidth;
this.pageHeight = pageHeight;
this.lineSpacingMultiplier = lineSpacingMultiplier;
this.lineSpacingExtra = lineSpacingExtra;
}
public void append(String text, TextPaint textPaint) {
textLineHeight = (int) Math.ceil(textPaint.getFontMetrics(null) * lineSpacingMultiplier + lineSpacingExtra);
String[] paragraphs = text.split("\n", -1);
int i;
for (i = 0; i < paragraphs.length - 1; i++) {
appendText(paragraphs[i], textPaint);
appendNewLine();
}
appendText(paragraphs[i], textPaint);
}
private void appendText(String text, TextPaint textPaint) {
String[] words = text.split(" ", -1);
int i;
for (i = 0; i < words.length - 1; i++) {
appendWord(words[i] + " ", textPaint);
}
appendWord(words[i], textPaint);
}
private void appendNewLine() {
currentLine.append("\n");
checkForPageEnd();
appendLineToPage(textLineHeight);
}
private void checkForPageEnd() {
if (pageContentHeight + currentLineHeight > pageHeight) {
pages.add(currentPage);
currentPage = new SpannableStringBuilder();
pageContentHeight = 0;
}
}
private void appendWord(String appendedText, TextPaint textPaint) {
int textWidth = (int) Math.ceil(textPaint.measureText(appendedText));
if (currentLineWidth + textWidth >= pageWidth) {
checkForPageEnd();
appendLineToPage(textLineHeight);
}
appendTextToLine(appendedText, textPaint, textWidth);
}
private void appendLineToPage(int textLineHeight) {
currentPage.append(currentLine);
pageContentHeight += currentLineHeight;
currentLine = new SpannableStringBuilder();
currentLineHeight = textLineHeight;
currentLineWidth = 0;
}
private void appendTextToLine(String appendedText, TextPaint textPaint, int textWidth) {
currentLineHeight = Math.max(currentLineHeight, textLineHeight);
currentLine.append(renderToSpannable(appendedText, textPaint));
currentLineWidth += textWidth;
}
public List<CharSequence> getPages() {
List<CharSequence> copyPages = new ArrayList<CharSequence>(pages);
SpannableStringBuilder lastPage = new SpannableStringBuilder(currentPage);
if (pageContentHeight + currentLineHeight > pageHeight) {
copyPages.add(lastPage);
lastPage = new SpannableStringBuilder();
}
lastPage.append(currentLine);
copyPages.add(lastPage);
return copyPages;
}
private SpannableString renderToSpannable(String text, TextPaint textPaint) {
SpannableString spannable = new SpannableString(text);
if (textPaint.isFakeBoldText()) {
spannable.setSpan(new StyleSpan(Typeface.BOLD), 0, spannable.length(), 0);
}
return spannable;
}
}
First, you have to create PageSplitter
object with pageWidth
and pageHeight
(in pixels) which you can get from View.getWidth()
and View.getHeight()
:
ViewPager pagesView = (ViewPager) findViewById(R.id.pages);
PageSplitter pageSplitter = new PageSplitter(pagesView.getWidth(), pagesView.getHeight(), 1, 0);
lineSpacingMultiplier
and lineSpacingExtra
must have same values as lineSpacingMultiplier
and lineSpacingExtra
attributes of TextView
s which will keep page texts.
Using PageSplitter.append()
method you can append text
which will be measured with textPaint
:
TextPaint textPaint = new TextPaint();
textPaint.setTextSize(getResources().getDimension(R.dimen.text_size));
for (int i = 0; i < 1000; i++) {
pageSplitter.append("Hello, ", textPaint);
textPaint.setFakeBoldText(true);
pageSplitter.append("world", textPaint);
textPaint.setFakeBoldText(false);
pageSplitter.append("! ", textPaint);
if ((i + 1) % 200 == 0) {
pageSplitter.append("\n", textPaint);
}
}
Then by using PageSplitter.getPages()
method you can get original text splitted to pages and put each of them into TextView
:
pagesView.setAdapter(new TextPagerAdapter(getSupportFragmentManager(), pageSplitter.getPages()));
TextPagerAdapter:
public class TextPagerAdapter extends FragmentPagerAdapter {
private final List<CharSequence> pageTexts;
public TextPagerAdapter(FragmentManager fm, List<CharSequence> pageTexts) {
super(fm);
this.pageTexts = pageTexts;
}
@Override
public Fragment getItem(int i) {
return PageFragment.newInstance(pageTexts.get(i));
}
@Override
public int getCount() {
return pageTexts.size();
}
}
PageFragment:
public class PageFragment extends Fragment {
private final static String PAGE_TEXT = "PAGE_TEXT";
public static PageFragment newInstance(CharSequence pageText) {
PageFragment frag = new PageFragment();
Bundle args = new Bundle();
args.putCharSequence(PAGE_TEXT, pageText);
frag.setArguments(args);
return frag;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
CharSequence text = getArguments().getCharSequence(PAGE_TEXT);
TextView pageView = (TextView) inflater.inflate(R.layout.page, container, false);
pageView.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimension(R.dimen.text_size));
pageView.setText(text);
return pageView;
}
}
where R.layout.page
is
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textSize="@dimen/text_size"
android:lineSpacingMultiplier="1"
android:lineSpacingExtra="0sp">
</TextView>
PageSplitter.renderToSpannable()
method wraps text
to SpannableString
according to textPaint
settings. In current method implementation I consider only TextPaint.isFakeBoldText()
property, but you can also apply other properties. For example, you can apply TextPaint.getTextSize()
property with AbsoluteSizeSpan
.

- 25,177
- 13
- 126
- 165
-
will this ensure that the pages are an exact fit on any display screen? I am trying something similar using textpaint but my issue is in fitting the text exactly in the screen so as no vertical scroll is needed and also that word wrapping happens so as no word is split between pages. You can see my solution in the thread http://stackoverflow.com/questions/20830160/string-processing-very-slow-in-new-android-version4-3 – user1938357 Dec 30 '13 at 23:00
-
I fixed few bugs and updated source code to latest version. This code works perfectly in application https://play.google.com/store/apps/details?id=com.codeoverdrive.cleverum.philosophy – mixel Jan 13 '14 at 02:36
-
have you try with big html file (1.4mb), how long it take?? – dreamfighter May 22 '14 at 06:12
-
@dreamfighter No, I think it would take significant time. What you can do is to split big file to pages with PageSplitter and save them in application folder when you open your application at first time. And next time you open application use these pages. – mixel May 22 '14 at 10:21
-
Would it be possible to get this as a project so that I can see all the parts? – hugocarlmartin Sep 23 '14 at 09:15
-
@hugocarlmartin Yes, will be soon. – mixel Sep 23 '14 at 12:01
-
While waiting for the project I was trying to implement the code above. I got "The method getObjectStorage() is undefined for the type PageFragment". What am I doing wrong? – hugocarlmartin Sep 24 '14 at 09:42
-
@hugocarlmartin See update. I created GitHub repo. And forget about getObjectStorage() - that was a newbie crap. – mixel Sep 25 '14 at 11:47
-
Thx, I look into it straight away. – hugocarlmartin Sep 25 '14 at 12:06
-
Do you know if its possible to use this with html? A Spanned text or something? – hugocarlmartin Sep 25 '14 at 12:51
-
@hugocarlmartin I think that it is possible but you need to adapt my solution to your case. I will try to do this when I will have a time. – mixel Sep 26 '14 at 07:17
-
I've tried but as soon as I use
tags it gets crazy. – hugocarlmartin Sep 26 '14 at 07:46 -
2@hugocarlmartin You can use Html.fromHtml() method to get Spanned string from html markup. Then you can write your own PageSplitter that has append(Spanned) method instead of append(String, TextPaint). But it's not trivial task. – mixel Sep 26 '14 at 12:09
-
I have downloaded the sample application, compiled it without any problems, however it crashes when running it. Can anybody help me? Here is a screenshot https://www.dropbox.com/s/17ps8ih35bkkk47/pagesplitter.jpg?dl=0 – Ivan May 11 '15 at 02:47
-
I had to create a new android project and add all the files manually to solve this problem as I don't understand what happened on the screenshot and how to resolve it. – Ivan May 11 '15 at 05:25
-
@Ivan I've updated project to use latest Gradle Android plugin and Android SDK. Try to open it in Android Studio. It should compile and run without any issues. – mixel May 11 '15 at 09:42
-
I dont have studio cause I use eclipse. Also I managed to run the sample already. Do you have updated PageSplitter to handle Html? Would be so helpful to find such code... – Ivan May 11 '15 at 13:00
-
@Ivan No, but you are free to contribute. I'll be waiting for your pull request :-) – mixel May 11 '15 at 16:05
-
Good solution! I have a [similar one](http://stackoverflow.com/questions/31837840/paginating-text-in-android/32096884#32096884), just a bit "shorter") – Onik Mar 30 '17 at 20:54
-
@mixel Great sample, thanks a lot, but unfortunately it don't break the last line to another page if it's shorter than TextView width( – Acuna Feb 25 '19 at 09:48