一种扩展的Android文本显示控件实现

2015-03-02 12:03朱明东
软件导刊 2015年1期

摘要:Android自带的文本显示控件TextView往往难以满足排版要求。以两端对齐排版要求为例,实现能够两端对齐的文本显示控件ExTextView,为扩展TextView功能提供了方法和思路。

关键词:Android;文本显示;控件TextView;ExTextView

DOIDOI:10.11907/rjdk.143658

中图分类号:TP301

文献标识码:A 文章编号文章

编号:16727800(2015)001003303

基金项目基金项目:

作者简介作者简介:朱明东(1975-),男,硕士,国防信息学院一系讲师,研究方向为数据处理、数据分析、数据管理。

0 引言

TextView控件在Android开发中应用比较广泛,只要有文本显示要求时,通常都会用到它。但是TexwView控件并不十分完美,它在显示文本时,特别是有中西文混合文本时,往往显得参差不齐、不够工整,影响了排版效果,不能满足“两端对齐”这一中文显示的基本要求。Android实现文本两端对齐显示的基本方法有3种:①将文本转换为html格式,用WebView控件显示;②弃用TexwView控件,重新实现一个具有两端对齐功能的新控件;③在TexwView控件的基础上,扩展实现两端对齐功能。第一种方法需要在文本显示前把文本转换为html格式,这需要程序员对html格式相当熟悉;第二种方法是重新实现一个文本显示控件,需要对Android控件的实现机理有深入研究[1],对程序员的能力要求比较高;第三种方法相对简单,只需在现有控件的基础上,覆盖文本输出方法。本文采用第三种方法,即扩展TextView控件功能。

1 TextView控件机理

要扩展TextView控件功能,首先要对TextView控件的实现机理有一定了解。Android的每一个控件虽然实现起来相当复杂,但除了具体实现细节外,几乎所有的可视控件都包含两个要素:一个是与用户的交互界面(UI),另一个是与用户交互的用户输入事件。TextView作为Android的一个基础控件也不例外,用户界面是通过在画布上绘制UI,用户主要是键盘输入以及触摸屏输入。因此,分析TextView控件的机理就是要搞清控件界面的绘制框架及其输入过程。本文主要关注TextView控件的界面绘制步骤。控件的UI绘制操作通常分为3步,分别是测量、布局和绘制。

1.1 测量

对于一个可视控件,必须确定其所占空间的大小,所以TextView要重写父类View的成员函数onMeasure。该函数有两个参数,分别是用来描述宽度测量规范的widthMeasureSpec和高度测量规范的heightMeasureSpec。测量规范使用1个int值来表示,这个int值包含了2个分量。第1个是mode分量,使用最高2位来表示。测量模式有3种,分别是MeasureSpec.UNSPECIFIED(0)、MeasureSpec.EXACTLY(1)和MeasureSpec.AT_MOST(2);第2个是size分量,使用低30位来表示。当mode分量等于MeasureSpec.EXACTLY时,size分量的值就是父视图设置的宽度或者高度;当mode分量等于MeasureSpec.AT_MOST时,size分量的值就是父视图限定当前控件设置的最大宽度或者高度;当mode分量等于MeasureSpec.UNSPECIFIED时,父视图不限定当前控件所设置的宽度或者高度,这时候当前控件就按照实际需求来设置宽度和高度。

1.2 布局

通过测量后确定了控件的大小,但是控件的位置还未确定。控件的位置是通过布局这个操作来完成的。Android可视控件是按照树形结构组织在一起的,其中,子控件的位置由父控件来设置,也就是说,只有容器类控件才执行布局操作,通过重写父类View的成员函数onLayout来实现。由于TextView控件不是容器类控件,因此,它可以不重写父类View的成员函数onLayout。

1.3 绘制

经过测量和布局操作后,就确定了控件TextView的大小和位置,接下来绘制UI。控件为了绘制UI,必须重写父类View的成员函数onDraw。该函数只有一个参数canvas,canvas描述的是一块画布,控件的UI就是绘制在这块画布上的。画布提供了丰富的接口来绘制UI,例如画线(drawLine)、画圆(drawCircle)、输出文字(drawText)和贴图(drawBitmap)等等。有了这些UI画图接口之后,就可以随心所欲地绘制控件的UI了。

通过分析TextView控件的机理,不难发现,TextView对文本的显示是通过在画布(canvas)上输出文本实现的。因此要在TextView的基础上实现文本的两端对齐,关键是要重新安排每一行的字符数,控制字间距,在TextView的画布(canvas)上精确地输出每一个字符,从而确保每一行的第一个字符和最后一个字符是对齐的。

2 扩展TextView设计

2.1 两端对齐显示的基本要求

要实现文本的两端对齐,表面上是每行的最后一个字符在纵坐标上保持一致,其实还要考虑文本在显示格式上的要求,特别是每一行的第一个字符(行首)和最后一个字符(行尾)是否符合格式规范。需要考虑的格式规范要求有:①行首字符不能是以下字符:句号(。.)、问号(??)、叹号(!!)、逗号(,,)、冒号(::)和分号(;;)和引号(””)、括号())]})、书名号(》>)的后一半等;②行尾字符不能是以下字符:引号(“”)、括号((([{)、书名号(《<)的前一半。

2.2 ExTextView设计[2]

ExTextView继承了Android的TextView,其继承关系如图1所示,ExTextView的类图如图2所示。

ExTextView主要的属性包括文本高度(m_iTextHeight)、文本宽度(m_iTextWidth)、画笔(mPaint)、文本(text)、行间距(LineSpace)、左边距(left_Margin)、右边距(right_Margin)、上边距(top_Margin)、下边距(bottom_Margin)、字体高度(m_iFontHeight)、所有行属性(strings)。

其中,描述行属性的内部类Tlineattr包含3个成员,分别是该行所包含的字符串(linetext)、该行的字间距(extrawidth)和该行每一个字符的输出宽度(widths)。

ExTextView的机理是:确定每一行字符属性的IniLines()方法和覆盖父类的OnDraw()方法。IniLines()方法主要是确定第一行所包含的字符,保证行首字符和行尾字符符合格式规范的要求,其判断逻辑如图3所示。OnDraw()方法主要是根据每一行所包含的字符,确定字间距,保证所有行首字符的水平坐标值一致,所有行尾字符的水平坐标值一致,操作流程如图4所示。

3 扩展TextView实现

3.1 主要代码

ExTextView要能够实现两端对齐,其核心是先要分配每一行的字符,然后重写父类的成员函数onDraw ()。

分配字符的方法IniLines()实现如下:

private void IniLines() {

strings.clear();

String Text = text;//得到要输出的文本

intm_LineWidth = m_iTextWidth - left_Margin - right_Margin; //可输出的画布宽度

float[] widths = new float[Text.length()]; //保存每个字符所占宽度的数组

mPaint.getTextWidths(Text, widths); //得到每个字符输出时的宽度

final String Laststr = "((《“{[<"; //不能是行尾的字符

final String Firststr = "、,。;:)?”,.;:)》?/-]}>"; //不能是行首的字符

float curwidth = 0; //保存当前行所含字符的总的宽度

intstartindex = 0; //行开始的字符位置

intendindex = 0; //行结束的字符位置

String lastch = ""; //上一个字符

Boolean IsCalExtraWidth = false;//是否需要计算字间距

for (inti = 0; i

Boolean lineclosed = false; //当前行是否结束

String ch = Text.substring(i, i + 1); //得到当前字符

curwidth = curwidth + widths[i]; //当前行已输出字符的宽度

if (curwidth>m_LineWidth) { //超出了行宽

lineclosed = true;//当前行结束

IsCalExtraWidth = true;//需要计算字间距

if (Firststr.contains(ch)) { //如果当前字符不能为行首,则当前字符为该行的行尾

endindex = i; //当前行结束的字字符位置

curwidth = 0; //下一行的行宽

} else { //当前字符可以为行首

if (Laststr.contains(lastch)) { //上一个字符不能为行尾,则上一个字符为下一行的行首

endindex = i - 2;//当前行的行尾为当前字符的前两个字符

curwidth = widths[i - 1] + widths[i]; //下一行的行宽

} else { //上一个字符可以成为行尾

endindex = i - 1; //当前行的行尾为上一个字符

curwidth = widths[i]; //下一行的行宽

}

}

lastch = ch;

}

if (!lineclosed) {

if (ch.equals("n") || i == Text.length() - 1) { //或当前字符为换行符或文本最后一个字符,则当前行结束

lineclosed = true;

endindex = i;

IsCalExtraWidth = false;//不需要计算字间距

curwidth = 0;

}

}

if (lineclosed) { //如果当前行结束

intlen = endindex - startindex + 1;//当前行字符个数

float[] linewidths = new float[len]; //当前行每一个字符的宽度

float linewidth = 0; //当前行所有字符输出宽度之和

for (int j = startindex; j

linewidths[j - startindex] = widths[j];

linewidth = linewidth + widths[j];

}

float extrawidth = 0;//调整字间距

if (IsCalExtraWidth) { //需要计算调整字间距

extrawidth = (m_LineWidth - linewidth) / len;

}

stringlist.add(Text.substring(startindex, endindex + 1)); //当前行所包含的字符

strings.add(new Tlineattr(Text.substring(startindex, endindex + 1), extrawidth, linewidths));

startindex = endindex + 1; //下一行的起始字符的位置

}

}//结束对字符的历遍

if (endindex != (Text.length() - 1)) { //若上一行的行尾不是文本的最后一个字符,则还剩最后一行

endindex = Text.length() - 1;

intlen = endindex - startindex + 1;//当前行字符个数

float[] linewidths = new float[len]; //当前行每一个字符的宽度

float linewidth = 0; //当前行所有字符输出宽度之和

for (int j = startindex; j

linewidths[j - startindex] = widths[j];

linewidth = linewidth + widths[j];

}

stringlist.add(Text.substring(startindex, endindex + 1)); //当前行所包含的字符

strings.add(new Tlineattr(Text.substring(startindex, endindex + 1), 0, linewidths));

}

}

成员函数onDraw()的实现代码如下。

@Override

protected void onDraw(Canvas canvas) {

float drawx=0; //绘制字符的横坐标

float drawy=m_iFontHeight; //绘制字符的纵坐标

for(inti=0;i

drawx = left_Margin;

for (int j=0;j

String ch=strings.get(i).linetext.substring(j,j+1); //取一个字符

canvas.drawText(ch,drawx,drawy,mPaint); // 绘制当前字符

drawx=drawx+ strings.get(i).extrawidth+strings.get(i).widths[j]; //下一个字符的横坐标

}//结束当前行的循环

drawy=drawy+m_iFontHeight; //下一行字符的纵坐标

}//结束所有行的循环

}

3.2 应用实例

ExTextView控件的使用与TextView控件的使用是一样的,这里不作详细介绍。对于同一段文本,图5是Android自带的TextView显示效果,图6是ExTextView的显示效果。

4 结语

具有两端对齐功能的ExTextView控件,还不十分完善。比如,为防止英文单词被截断,需要加上中英文混合分词技术,等等。本文只是提供了扩展TextView功能一种思路,读者可以沿着这种思路不断扩展,实现所需功能。