本文为 FlowTextView 项目中 FlowTextView 部分
项目地址:FlowTextView,分析的版本:815bdc3

1.功能介绍

FlowTextView 是对 TextView 的包装,可以在文本中插入图片等其他控件,并且支持 Html 格式文本。

1.1 集成指南

compile 'com.github.deano2390:FlowTextView:2.0.4'

1.2 使用指南

XML文件指定子控件,用法同RelativeLayout布局。

<uk.co.deanwild.flowtextview.FlowTextView
    android:id="@+id/ftv"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content" >

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_alignParentTop="true"
            android:padding="10dip"
            android:src="@drawable/android"/>

</uk.co.deanwild.flowtextview.FlowTextView>

Java代码中指定插入文本

FlowTextView flowTextView = (FlowTextView) findViewById(R.id.ftv);
Spanned html = Html.fromHtml("<html>Your html goes here....");
flowTextView.setText(html);

2.详细设计

2.1 models包

models包下存放一些抽象概念的模型类。其中域权限均是 public,并不需要通过get/set方法操作。

1.Area

Area 定义一段X轴上区域,表示子控件(即obstacle)占据的区间。

float x1;
float x2;
float width;

2.Line

Line 代表文本行在x轴上占据的区域。

float leftBound;
float rightBound;

3.Obstacle

Obstacle 代表布局中子控件对文本构成的障碍区,由子控件左上和右下点坐标构成。

int topLeftx;
int topLefty;
int bottomRightx;
int bottomRighty;

4.HtmlObject

HtmlObject 代表 html 文本。

String content;
int start;
int end;
float xOffset;    //代表x轴上的实际偏移,非字符偏移
TextPaint paint;
boolean recycle = false;

5.HtmlLink

HtmlLink 继承 HtmlObject,代表超链接。

float width;
float height;
float yOffset;    //代表y轴上的实际偏移,非字符偏移
String url;

2.2 OnLinkClickListener类

此接口定义了超链接被点击后的回调方法,可以自定义实现.

public void onLinkClick(String url);

2.3 helpers包

一些辅助类

1.PaintHelper

PaintHelper负责存储和管理画笔,避免重复创建画笔类

  • 主要域

ArrayList<TextPaint> mPaintHeap //维持一个画笔集合
  • 主要方法

void recyclePaint(TextPaint paint) //存储画笔
TextPaint getPaintFromHeap()       //获取画笔
void setColor(int color)

2.SpanParser

SpanParser 负责处理 html 文本。

int mTextLength          //html文本对象长度
Spannable mSpannable     //html文本对象
PaintHelper mPaintHelper //画笔辅助类
FlowTextView mFlowTextView //主控件
List<HtmlLink> mLinks    // html文本中的超链接集合

HashMap<Integer, HtmlObject> sorterMap //html文本集合

主要方法是 parseSpan,负责解析出HtmlObject对象,根据 待解析对象的不同分三种解析方法。

HtmlObject parseSpan(Object span, String content, int start, int end){

    if(span instanceof URLSpan)
        return getHtmlLink((URLSpan) span, content, start, end, 0);
    else if(span instanceof StyleSpan){
        return getStyledObject((StyleSpan) span, content, start, end, 0);
    else
        return getHtmlObject(content, start, end, 0);
    }

3.ClickHandler

ClickHandler是OnTouchListener的实现类,主要实现了对超链接点击事件的处理。

SpanParser mSpanParser; //html文本处理类,构造方法传入
OnLinkClickListener mOnLinkClickListener; //get/set方法设置
double distance         //两点距离
float x1,y1,x2,y2       //两点 

4.CollisionHelper

calculateLineSpaceForGivenYOffset 方法负责根据组件中的障碍物列表和文本行高度 计算得到使得文本与组件不冲突的 line 对象。

Line calculateLineSpaceForGivenYOffset(
        float lineYbottom, 
        int lineHeight, 
        float viewWidth, 
        ArrayList<Obstacle>)

其算法步骤如下:

  1. 建立Line 对象(0~viewWidth),即默认情况下文本行宽度等于组件宽度;

  2. 遍历 obstacles 集合,判断当前 Obstacle 是否与 文本行高度有交叉

  3. 如果有

    • 创建leftArea对象,内循环遍历 obstacles 集合以确定其边界

    • 创建rightArea对象,内循环遍历 obstacles 集合以确定其边界

  4. 在Area集合中寻找范围最大者,最终确定Line对象

这个算法写的极其诡异

2.4 FlowTextView

FlowTextView 继承自 RelativeLayout。

  • 主要域

1.PaintHelper mPaintHelper 
2.SpanParser mSpanParser    
3.ClickHandler mClickHandler
4.int mColor
5.int pageHeight        页面高度
6.TextPaint mTextPaint  文本画笔  
7.TextPaint mLinkPaint  html中超链接文本画笔
8.float mTextsize       文本字体大小
9.int mTextColor        文本字体颜色
10.Typeface typeFace    文本字体类型
11.mDesiredHeight       整个控件高度
12.boolean needsMeasure 空间是否需要重新测量
13.ArrayList<Obstacle> obstacles 布局中已存在的控件障碍列表
14.CharSequence mText   显示文本
15.boolean mIsHtml      文本是否是 html 格式  
16.float mSpacingMult; 
17.float mSpacingAdd;
18.List<HtmlObject> lineObjects
19.HtmlObject htmlLine
  • 核心方法

1.setText 方法将导致控件重绘

public void setText(CharSequence text) {
    mText = text;
    if (text instanceof Spannable) {
        mIsHtml = true;
        mSpanParser.setSpannable((Spannable) text);
    } else {
        mIsHtml = false;
    }
    this.invalidate();
}

2.onDraw方法 绘制
扼要分析

//1.初始化所有Obstacle,并获得子组件最大高度
int lowestYCoord = findBoxesAndReturnLowestObstacleYCoord();
//2.拆分文本并处理
String[] blocks = mText.toString().split("\n");
for (int block_no = 0; block_no <= blocks.length - 1; block_no++){
    String thisBlock = blocks[block_no];
    while (thisBlock.length() > 0) {
        //3.文本行数+1,并获得绘图起点和文本水平范围
        lineIndex++;
        yOffset = getPaddingTop() + lineIndex * lineHeight - (getLineHeight() + mTextPaint.getFontMetrics().ascent);
        Line thisLine = CollisionHelper.calculateLineSpaceForGivenYOffset(yOffset, lineHeight, mViewWidth, obstacles);
        xOffset = thisLine.leftBound;
        maxWidth = thisLine.rightBound - thisLine.leftBound;
        float actualWidth;

        //4.处理 actualWidth 与 maxWidth之间的矛盾

    }
}

3.总体设计

图片描述


incredible
187 声望5 粉丝