The Canvas. DrawText method is familiar to you, and TextView uses it to drawText. However, this method does not have the function of wrapping text, which means that it can only draw one line; TextView text, on the other hand, is automatically wrapped, and will be wrapped when the page is too short to show the text that follows it (with the Android :breakStrategy property to adjust the timing). After looking at the source code, WE found that TextView is used to help measure text with Layout.

Layout

Layout is an abstract class, the concrete implementation of BoringLayout, StaticLayout, DynamicLayout. A brief introduction:

  • BoringLayout BoringLayout, used for a single line of text, can be determined by calling the isBoring method if it is not sure whether a given text is sufficient
  • StaticLayout StaticLayout, as the name suggests, is text that does not change.
  • DynamicLayout DynamicLayout, text can be changed.

Here’s a look at what they do via StaticLayout.

Construction method:

	val lineSpaceadd = 0.0 f // Extra line spacing
        val lineSpacemuti = 1.0 f// Multiple of line spacing
        // Check whether to use Builder according to different versions
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
            mLayout = StaticLayout.Builder
                .obtain("Incoming String", start string, end string, TextPaint(), width).build()}else {
        	// Traditional construction method
            mLayout = StaticLayout(
                "Incoming String",
                TextPaint(),
                width,
                Layout.Alignment.ALIGN_NORMAL,
                lineSpaceadd,
                lineSpacemuti,
                false)}Copy the code

What can we do with it

  1. Layout uses the String and width passed in to calculate how much text can be displayed on each line. So we can get every line of text.
	mLayout.lineCount// Get the number of rows
        mLayout.getLineStart(0)// Get the starting position of the first line in the incoming String
        mLayout.getLineEnd(0)// Get the terminating position of the first line in the incoming String
        mLayout.getLineVisibleEnd(2)// Gets the last visible character of the specified line (without counting the text offset of Spaces)

Copy the code
  1. Layout has a draw method that draws the contents of the line directly to the view.
	val canvas = getCanvas()
	mLayout.draw(canvas)// Just pass in canvas
Copy the code

expand

demand

I developed the novel reading software, and I needed to assign the content of each chapter to each page. I need to get the number of words and content that can be displayed on each page.

The solution

In this case, you can use Layout to separate sections, and then calculate how many lines can be displayed on each page.

How to implement

A brief description of the paging tool:

  1. You need to pass in the content of the chapter, because you definitely need it for paging.
  2. Two parameters related to line spacing
  • LineSpaceAdd additional line spacing, increased for positive numbers and decreased for negative numbers. Default is 0.0F.
  • LineSpaceMutil Multiple of line spacing. There is no specific unit. The default is 1.0f.

These two parameters are important in the novel reading page. 3. Read the height and width of the page. The width allows Layout to split the content into rows. 4

  1. If you’re just passing a textView directly to calculate it it’s textView.getLineHeight ()
  2. If it is by passing textPaint, then use this calculation
    fun getLineHeight(a): Float {
    	// The formula is very simple, and also reflects the function of the two parameters of line spacing
    	return textPaint.textSize * lineSpaceMult + lineSpaceExtra
    }
    Copy the code
  3. Of course, these things are not enough. You need to know the font and size of the text to measure it. In this case, you just need to pass in a TextPaint to get the data.

The specific implementation

Pagingtool. kt I tangled for a long time and finally used the singleton mode, code foundation is not deep, welcome to point out any problems.

// Kotlin singleton, Java students should not wonder
object PagingTool{
    private var width = 0/ / width
    private var height = 0/ / height
    private var lineSpaceAdd = 0.0 f// Extra line spacing
    private var lineSpaceMutil = 1.0 f// Multiple of line spacing
    private var text:String = ""// Text content
    private var textPaint = TextPaint()
    // As for the brush parameters, since I save the configuration of the reading page in the database, I return LiveData through the Room frame to update the font size in real time; Of course, you can manually update every configuration change.
    private lateinit var mLayout:LayoutLateinit stands for lazy loading,
    
    //setter
    public fun setHeight(height: Int) {
        this.height = height
    }
    public fun setWidth(width: Int) {
        this.width = width
    }
    public fun setPaint(textPaint:TextPaint){
    	this.textPaint = textPaint
    }
    public fun setLineSpaceAdd(spaceAdd:Float){
    	lineSpaceAdd = spaceAdd
    }
    public fun setLineSpaceMutil(spaceMutil:Float){
    	lineSpaceMutil = spaceMutil
    }
    
    // Calculate the row height
    private fun getLineHeight(a):Int{
    	// The calculation method mentioned above
    	return textPaint.textSize*lineSpaceMutil+lineSpaceAdd
        //textView.getLineHeight()
    }
    
    private fun setText(str:String){
    	text = str
        mLayout = StaticLayout(
                text,
                textPaint,
                width,
                Layout.Alignment.ALIGN_NORMAL,
                lineSpaceAdd,
                lineSpaceMutil,
                false// Don't worry about this parameter)}/ / paging
    public fun paging(str:String):List<String>{
    	setText(str)// Set the content and initialize the layout
        // return the entire section if the boundary condition is 0
        if(width == 0 || height == 0)return arrayListOf(str)
        val totalLineCount = mLayout.lineCount// Total rows measured by layout
        var pageLineCount = height / getLineHeight() // Divide the page height by the row height to get the number of lines allowed to draw on the page
        if(pageLineCount < 1)pageLineCount = 1// This can only occur when the text is too large to display more than one line of text, I still set it to display one line, can delete
        var pageCount = totalLineCount / pageLineCount // Divide the total number of lines by the number of lines allowed to draw on a page to get the number of pages
        if (totalLineCount % pageLineCount > 0)// There are a few lines left to make up the last page
        	pageCount++
        val list = ArrayList<String>()
        // Now you just need to add the content to the list page by page
        for(i in 0 until pageCount){
            var temp = (i + 1) * pageLineCount
            temp--
            if (temp >= totalLineCount)
                temp = totalLineCount - 1
            val start = mLayout.getLineStart(i * pageLineCount)
            val end = mLayout.getLineEnd(temp)
            // Get the start and end coordinates of each page
            val string = text.substring(start, end)
            list.add(string)
        }
        // The size of the list is the number of pages
        return list
    }
}
Copy the code

The general idea is like this, maybe there will be a small bug, big problem should not be, see a idea is good.