Sep 14

Android:文字渲染layout整理

Lrdcq , 2015/09/14 23:34 , 程序 , 閱讀(8603) , Via 本站原創
这个layout不是说的界面布局,而是文字渲染布局,基础类是一个叫Layout的抽象类。layout完成了和文本渲染几乎所有的相关工作,只要一个view调用layout来测量大小,来绘制图像,那么它就能和textview的表现几乎一模一样。textview里按照不同的判断条件使用了不同的layout继承类型,下面,我们就就layout基类(抽象类)和各个继承类的使用展开整理。



1.抽象类layout

所有的layout都是继承自基类——Layout,那么显然,textview实际持有的就是layout的对象,其中最重要的方法也是固定不变的。
textview确定与调用layout主要在这几个过程:
  1.在textview的onMeasure的时候,如果还没有layout,在makeNewLayout方法中开始选择需要的layout。
  2.如果textview设定为单行模式了,运行makeSingleLayout来选择单行情况下的layout。
  3.如果是属于Spannable的文本对象,使用动态布局DynamicLayout,否则,使用isBoring判断是不是单纯的单行布局,是则使用BoringLayout,其他情况使用StaticLayout。
  4.如果不是单行的,同样按照这个逻辑区分出BoringLayout,DynamicLayout与StaticLayout,参数略有不同。
  5.获得测量结果的高宽,参与计算。
  6.如果需要重新测量,运行view的requestLayout方法。
  7.在onDraw的时候,暂存已有的canvas。
  8.调用layout.draw在canvas上进行绘制。
  9.返回结果进行叠加。

也就是说,textview的主要实现逻辑就是这个layout了。那么layout中到底做了什么,首先构造上:
protected Layout(
    CharSequence text, 
    TextPaint paint, 
    int width, 
    Alignment align, 
    TextDirectionHeuristic textDir, 
    float spacingMult, 
    float spacingAdd)

我们传入的包括文字,文字样式,需求宽度,对齐方式,文本方向,行间距的增加和倍数,这些参数足以构成绘制一段文字的级别样式与大小判定。

而layout基础类中,主要提供了大量的文字测量计算的方法,主要用于获得这段文本占有到高度getHeight(),还有很多中间测量值和对应到span的变化值。同时,有基本的draw方法,主要包括:
  1.getLineRangeForDraw获取需要绘制的行数。
  2.drawBackground绘制背景部分,主要是识别出拥有背景的span(LineBackgroundSpan)并加以处理。
  3.drawText以行(TextLine对象)为单位绘制文本,实现预设的文本样式,同时识别各种各样和样式布局有关的span。
这样,就可以完成文本的测量与绘制。

2.BoringLayout

直译为无聊的layout?总之,这是一个超级简单的layout,它在重写layout的过程中,做了这么一些事:
一构造起来就初始化好所有测量参数
重写draw方法为这样:
// Override draw so it will be faster.
@Override
public void draw(Canvas c, Path highlight, Paint highlightpaint, int cursorOffset) {
    if (mDirect != null && highlight == null) {
        c.drawText(mDirect, 0, mBottom - mDesc, mPaint);
    } else {
        super.draw(c, highlight, highlightpaint, cursorOffset);
    }
}

嗯,显然,已经决定把大部分没必要的绘制给省略掉了。boringlayout的设计理念就是,我只显示一行~,一切从简处理。
在部分真的只需要显示一行的应用场景(比如标题等)下,还是有那么一点点作用的。
boringlayout最重要的地方是它提供了一个静态方法isBoring来判断一段文字是否能在一行放下。这个方法有广泛的使用场景。

3.StaticLayout

静态布局,这个layout的特点是,在初始化的时候进行一次计算与渲染,之后,再也不动它了。
这个对象一旦被初始化,就会运行generate内部方法开始计算。generate的运算代码几乎和layout类中的draw的计算方法一模一样,把所有需要计算的数值都在对象的属性中填写完成。最后,在调用draw方法的时候,由于各种参数意见计算完成,只需要直接开始画就ok。
staticlayout在textview中,只会在绘制hint等乱七八糟的东西时会用到,因为textview提供了settext方法改变文字,所以无法设定死为静态的,但是在实际需求中,我们很多情况下需要一个静态的layout在大段大段中的文字显示来保持高性能(比如新闻客户端类app)。因此我们需要自定义textview来实现高性能的textview。像这样
public class StaticTextView extends View {
    private Layout layout = null;
    public void setLayout(Layout layout) {
        this.layout = layout;
        requestLayout();
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.save();
        if (layout != null) {
            layout.draw(canvas, null, null, 0);
        }
        canvas.restore();
    }
}

我们可以直接通过设置这个view的Layout来绘制文本,并在onDraw方法中直接使用这个Layout对象来绘制文本。在这里我们摒弃了setText方法,直接通过Layout来绘制文本,而这里的Layout对象,我们可以通过预先创建之后才设置进去(这里可以放到单独的一个线程中创建),这样对比起普通TextView的setText方法,我们减少了setText中的许多消耗,可以大幅度的提升效率。
同样,如果我们有在其他自定义控件中绘制文本的需求,直接调用staticlayout来绘制就可以完美解决换行和样式问题了。

4.DynamicLayout

功能最为强大的layout了,也就是实现了textview所有功能。它在绘制的时候把字符拆成以块(Block)为单位进行渲染,而块的区分是按照段落和最小块长来设置的,目的是动态渲染提高性能(因为这个本来就很次性能)。
它的每一次渲染的参数其实是复用的StaticLayout的生成器来设置的,同时使用TextLayoutCache来缓存住。同时updateBlocks来更新样式。
同时,内部拥有TextWatcher, SpanWatcher来监听文本和span的变动以进行实时刷新。
logo