TextView 的文字是怎麼自動換行的

相信這個方法Canvas.drawText你們必定不陌生,TextView就是使用它將文字繪製出來。但是這個方法並無文字換行的功能,也就是說它只能繪製一行;可是TextView的文字倒是會自動換行,當頁面不足以顯示後面的文字時(經過android:breakStrategy屬性能夠調整換行時機)就會自動換行。查看源碼後發現TextView是經過Layout來幫助測量文字。java

Layout

Layout是一個抽象類,具體實現有BoringLayoutStaticLayoutDynamicLayout。 簡單介紹一下:android

  • BoringLayout 無聊的佈局,用於單行文本,若是不肯定給定的文字是否知足能夠調用isBoring方法來判斷
  • StaticLayout 靜態佈局,顧名思義就是不會變化的文本。
  • DynamicLayout 動態佈局,文字能夠被改變。

這裏經過StaticLayout來介紹一下它們的做用。數據庫

構造方法:canvas

val lineSpaceadd = 0.0f //額外的行間距
        val lineSpacemuti = 1.0f//行間距倍數
        //根據不一樣的版本確認是否使用Builder
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
            mLayout = StaticLayout.Builder
                .obtain("傳入的String", string的起始座標, string的結尾座標, TextPaint(), width)
                .build()
        } else {
        	//傳統構造方法
            mLayout = StaticLayout(
                "傳入的String",
                TextPaint(),
                width,
                Layout.Alignment.ALIGN_NORMAL,
                lineSpaceadd,
                lineSpacemuti,
                false
            )
        }
複製代碼

咱們能夠用它來幹嗎呢markdown

  1. Layout經過傳入的String和width,來計算出每行能顯示的文字數量。因此咱們可以獲取每一行的文字。
mLayout.lineCount//獲取行數
        mLayout.getLineStart(0)//獲取第一行在傳入String中的起始位置
        mLayout.getLineEnd(0)//獲取第一行在傳入String中的終止位置
        mLayout.getLineVisibleEnd(2)//獲取指定行的最後可見字符(不計算空格的文本偏移量)

複製代碼
  1. Layout有一個draw方法,能夠直接把分行的內容繪製到view上。
val canvas = getCanvas()
	mLayout.draw(canvas)//傳入canvas就行啦
複製代碼

拓展

需求框架

開發了小說的閱讀軟件,我須要將每一章的內容分配到每個頁面。我須要獲取到每一頁可以顯示的文字數量和文字內容。工具

解決方案oop

此時就能夠經過Layout把章節內容分行,而後計算每一頁可以顯示多少行,將每頁的內容傳遞過去。佈局

如何實現字體

簡單描述一下這個分頁工具:

  1. 須要傳入章節的內容,由於要進行分頁,確定是須要它的。
  2. 行間距相關的兩個參數
  • lineSpaceAdd 額外的行間距,正數則增長行間距負數則減小,默認爲0.0f。
  • lineSpaceMutil 行間距倍數,沒有具體的單位,默認爲1.0f,大於1.0f則增長行距,小於則減小。

這兩個參數在小說閱讀頁面仍是很重要的。 3. 閱讀頁面的高度和寬度,經過寬度使得Layout可以將內容分割成行,經過頁面高度和行高度就可以可以獲取每一頁可以顯示的行數。 4. 行高度lineHeight

  1. 若是你是直接傳遞的textview來計算的話就是直接textview.getLineHeight()
  2. 若是是經過傳遞textPaint,那麼就用這個計算
    fun getLineHeight(): Float {
    	//公式很簡單,也體現出了行間距這兩個參數的做用
    	return textPaint.textSize * lineSpaceMult + lineSpaceExtra
    }
    複製代碼
  3. 光有這些東西固然是不夠的,文字的測量確定須要知道文字的字體、字號,在這裏只須要傳入一個TextPaint就能夠獲取到這些數據了。

具體實現

PagingTool.kt 我糾結了好久最後仍是用了單例模式,代碼功底不深,有問題歡迎你們指出。

//kotlin中的單例,java的同窗不用納悶
object PagingTool{
    private var width = 0//寬度
    private var height = 0//高度
    private var lineSpaceAdd = 0.0f//額外的行間距
    private var lineSpaceMutil = 1.0f//行間距倍數
    private var text:String = ""//文字內容
    private var textPaint = TextPaint()
    //對於畫筆的參數,因爲我是把閱讀頁面的配置保存在數據庫中的,經過room框架返回LiveData,實時更新字體字號;固然也能夠每次配置變動就手動更新一次。
    private lateinit var mLayout:Layout//工具的核心人物,lateinit就是延遲加載的意思,
    
    //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
    }
    
    //計算行高
    private fun getLineHeight():Int{
    	//上面說到的計算方法
    	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//這個參數不用在乎
            )
    }
    
    //分頁
    public fun paging(str:String):List<String>{
    	setText(str)//設置內容,初始化layout
        //邊界條件,爲0就直接返回整個章節的內容
        if(width == 0 || height == 0)return arrayListOf(str)
        val totalLineCount = mLayout.lineCount//總行數,這個是layout測量出來的
        var pageLineCount = height / getLineHeight() //頁面高度除以行高度獲得頁面容許繪製的行數
        if(pageLineCount < 1)pageLineCount = 1//這種狀況,只可能出如今文字巨大,大到頁面高度顯示不下一行文字,那我仍是設置讓他顯示一行,能夠刪掉
        var pageCount = totalLineCount / pageLineCount //總行數除以頁面容許繪製的行數,獲得分頁數量
        if (totalLineCount % pageLineCount > 0)//還剩下有幾行,組成最後一頁
        	pageCount++
        val list = ArrayList<String>()
        //如今就只須要將內容按頁添加到這個list中
        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)
            //獲取到每一頁的起始座標,結尾座標
            val string = text.substring(start, end)
            list.add(string)
        }
        //這個時候就已經把內容分頁了,list的size就是頁數
        return list
    }
}
複製代碼

手擼的,沒有跑過大體思路是這樣,也許會有小bug,大問題應該沒有吧,看個思路就好。

相關文章
相關標籤/搜索