2

I add a scrollview( with textview inside) to fill whole screen. I add a very long text(book) to a textview. Textview is nice that it know how to fill the text to the whole screen, i.e. handle newline and know how many words to put each line, including handling non-english words.

Based on the total line, scrollview height and line height, i can use scrollto of scrollview to quick move to a page.

However, it is very slow and block UI thread when the text is very long. Some suggested to use lazy load method, i.e. appendto of textview. However, by doing so, i cannot know how many page there is, and cannot long use scrollto to "change" page.

I think for days but cannot get a better solution. Can anyone give me some ideas and suggestion? thanks

manhon
  • 683
  • 7
  • 27
  • Maybe use a asynchronous task to load data sequentially? Google android A.sych task, it is specifically there to unblock the UI thread and throws tasks in the background so the user doesn't notice hangups on the UI level. – SeahawksRdaBest Oct 23 '14 at 16:43

2 Answers2

0

Essentially, a giant ScrollView will hang up at points and will become cumbersome to switch between. A better idea is to split up the giant text/book into pages, and then display each page. In other words:

  1. Get how much text will fit in one page/TextView
  2. Split up the text between the correct two points [ie, first point is start of page and second point is calculated via how much text will fit in one page]
  3. Display the text however you like, although I'd personally prefer a dynamic ViewPager which I would make calculate the previous and next page every time

This will prevent hang ups, as long as you strategically calculate the previous and next pages at the right times [ie, lazy loading or async tasks].

This should get you on your way for pagination: Pagination in Android TextView

And for examples of lazy loading [would be simpler than these, as you would just work on the other thread for splitting up the text into pages and then giving back those however formatted pages]:

Community
  • 1
  • 1
DragonJawad
  • 1,846
  • 3
  • 20
  • 28
  • so far i could not find an efficient and accurate way to do your pt 1 to pt 3, even slower than adding all text to the textview.. – manhon Oct 24 '14 at 14:30
0

I ve been looking for same damn thing over a year ago. And ended up implementing it by myself.

The width of the line is the sum of intersymbol space(kerning), widths of whitespaces between symbols, widths of bounds for your view.

intersymbol space(kerning) is different for each pair of symbols you want to use. WA and DA have different space if your font is not MONOSPACE.

same for whitespaces , it is different for each pair of symbols. same for bounds on the left and right, it is different for each symbol.

so what i did - prepare an array of each possible symbol for your app (not all, but those that possibly would occur in the text you feeding to the view) then calculate width of each symbol.

HashMap<String, Integer[]> charMap = new HashMap<>();

        String defaultSet = "ҢңӨөөҮүёЁQWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm\"'[]{}\\./`~!@#$%^&**()_+`12\uFEFF3456«;789»0—-=:|<>?йцуке…нгшщзхъфывапролджэячсмитьбюЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮ,,–";
        String[] charArray = defaultSet.split("");
        Rect bounds = new Rect();
        for (String mChar : charArray) {
            if (mChar.length() == 0) {
                continue;
            }
            //textView.setText(mChar);
            textPaint.getTextBounds(String.valueOf(mChar), 0, 1, bounds);
            int height = bounds.height();
            int width = bounds.width();
            Integer[] size = {width, height};
            charMap.put(mChar, size);
        }

The next thing is to get kerning, whitespace and bounds values for each pair of symbols. The last parameter in each method is the charMap you calculated in the first codebox.

  /**
 * Returns Bounds map for each symbol
 * @param charArray
 * @param textPaint
 * @param mCharMap
 * @return
 */

private HashMap<String, Integer> generateBoundsLeftMap(String[] charArray, TextPaint textPaint, HashMap<String, Integer[]> mCharMap){
    //individual letterspacing, and whitespace width.
    HashMap<String, Integer>  boundsMap= new HashMap<>();
    Rect bounds = new Rect();

    for(int i=1;i<charArray.length;i++){
        String mKeyChar=charArray[i];

        String testLine="skajd fnфыв";
        textPaint.getTextBounds(testLine,0,11,bounds);
        int testwidth=bounds.width();
        String checkLine= mKeyChar.concat(testLine).concat(mKeyChar);
        textPaint.getTextBounds(checkLine, 0, 13, bounds);
        boundsMap.put(charArray[i], bounds.left);
        MLog.sout("boundsleft", "" + bounds.left, true);
        MLog.sout("measuretext",""+textPaint.measureText(checkLine),true);
        MLog.sout("boundsright",""+(textPaint.measureText(checkLine)-testwidth-(mCharMap.get(mKeyChar)[0]*2)-bounds.left),true);

    }
    return boundsMap;
}

/**
 * Returns Bounds map for each symbol
 * @param charArray
 * @param textPaint
 * @param mCharMap
 * @return
 */

private HashMap<String, Integer> generateBoundsRightMap(String[] charArray, TextPaint textPaint,HashMap<String, Integer[]> mCharMap){
    //individual letterspacing, and whitespace width.
    HashMap<String, Integer>  boundsMap= new HashMap<>();
    Rect bounds=new Rect();

    for(int i=1;i<charArray.length;i++){
        String mKeyChar=charArray[i];

        String testLine="aba";
        String checkLine= mKeyChar.concat(testLine).concat(mKeyChar);
        textPaint.getTextBounds(checkLine, 0, 5, bounds);
        int testwidth=bounds.width();
        int boundsright =(int) textPaint.measureText(checkLine)-testwidth-bounds.left;
        boundsMap.put(charArray[i], boundsright);
    }
    return boundsMap;
}

/**
 * returns Pairs map. HashMap<String, HashMap<String,   Integer > >
 *                            char1           char2   letterspacing
 * @param charArray
 * @param textPaint
 * @return
 */
private HashMap<String, HashMap<String, Integer>> generateKerningPairs(String[] charArray, TextPaint textPaint, HashMap<String,Integer[]> mCharMap){
    //individual letterspacing, and whitespace width.
    HashMap<String, HashMap<String, Integer> > pairMap= new HashMap<>();
    Rect bounds=new Rect();
    int lineWidth;
    int interspaceWidth;
    for(int i=1;i<charArray.length;i++){
        String mKeyChar=charArray[i];
        HashMap<String , Integer> spaceMap=new HashMap<>();
        for(int z=1; z<charArray.length;z++){
            //getletterspacing
            String checkLine=mKeyChar.concat(charArray[z]);
            textPaint.getTextBounds(checkLine,0,2,bounds);
            lineWidth = bounds.width();
            interspaceWidth=lineWidth-(mCharMap.get(mKeyChar))[0] - (mCharMap.get(charArray[z]))[0];
            spaceMap.put(charArray[z], interspaceWidth);
        }
        pairMap.put(mKeyChar,spaceMap);
    }
    return pairMap;
}

/**
 * returns Pairs map. HashMap<String, HashMap<String,   Integer > >
 *                            char1           char2   letterspacing
 * @param charArray
 * @param textPaint
 * @return
 */
private HashMap<String, HashMap<String, Integer>> generateSpacePairs(String[] charArray, TextPaint textPaint, HashMap<String,Integer[]> mCharMap){
    //individual letterspacing, and whitespace width.
    HashMap<String, HashMap<String, Integer> > pairMap= new HashMap<>();
    Rect bounds=new Rect();
    int lineWidth;
    int spaceWidth;
    for(int i=1;i<charArray.length;i++){
        String mKeyChar=charArray[i];
        HashMap<String , Integer> spaceMap=new HashMap<>();
        for(int z=1; z<charArray.length;z++){
            //getletterspacing
            String checkLine=mKeyChar.concat(" ").concat(charArray[z]);
            textPaint.getTextBounds(checkLine,0,3,bounds);
            lineWidth = bounds.width();
            spaceWidth=lineWidth-(mCharMap.get(mKeyChar))[0] - (mCharMap.get(charArray[z]))[0];
            spaceMap.put(charArray[z], spaceWidth);
        }
        pairMap.put(mKeyChar,spaceMap);
    }
    return pairMap;
}

Calculating these values take a LOOOONG time so I did it once for each DPI (using genymotion and changing screen density) and saved into a JSON in txt files. kernmap.txt spacemap.txt ... and so on.

You put these files into your assets folder , and the structure looks like this

assets/dpi/160/kernmap.txt ... assets/dpi/240/kernmap.txt ... and so on.

From this point - you cannot use symbols that you didnt put in default set string in the beginning

String defaultSet = "ҢңӨөөҮүёЁQWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm\"'[]{}\\./`~!@#$%^&**()_+`12\uFEFF3456«;789»0—-=:|<>?йцуке…нгшщзхъфывапролджэячсмитьбюЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮ,,–";

even having all the maps prepared you will have problems with performance. I used jni to calculate the page length.

What next?

you get your string, lets say its

"This is a test line provided by blabla"

take "T" and "h" (the first 2 characters)

linewidth = boundsLeftmap.get("T") (you have it in a map) + widthofchars.get("T") + kerningbetween.get("T").get("h") + and so on.

Using JNI it takes 3-4 seconds to process and break a string of 80000 characters into pages that perfectly fit your view.