TextView自动垂直滚动

先说说它的优点吧:
1.当view的大小容不下文字的时候,这个view有循环滚动文字的能力。
2.滚动的时候轻轻点击它,会停止滚动。
3.停止滚动时轻轻点击它,又会继续滚动。
4.可以通过手指拖动文字的显示位置。
5.当view的大小能容下文字的时候,它不会滚动,也不会响应手指拖动。


适用范围:
1.扩展成小说阅读器
2.公告栏、小窗口展示消息或通知
3.滚动新闻
4.可以扩展成支持多种字体滚动播放

技术难点提要:
1.换行处理及英文切词
2.测量view的长度和高度、能否滚动的判断条件
3.循环滚动的实现
4.动画的实现
6.手指托动文字
7.手指控制滚动

用到的api:

paint.measureText(string):测量paint画String所需要的宽度
view.requestLayout():重新布局
vew.invalidate():刷新view
canvas.drawText():画文字
textview.getLineHeight():获取行高

 

先说说中文的换行算法吧:

主要是用paint.measureText(string)方法去计算要画string的长度
例如有一个句子:你好,我是小明,很高兴认识大家!
首先得知道一行的最大宽度,比如最大宽度为120;
系统会先计算第一个字符“你”的长度,然后与最大宽度对比,如果小于最大宽度就计算前两个字符“你好”的长度,如果“你好”还是小于最大宽度120,就计算“你好,”,一直循环下去,假如到了“你好,我是小明,很高”时发现刚好超过120,那第一行就是“你好,我是小明,很”;然后对剩下的字符“高兴认识大家!”进行上述处理,把切出来的行保存到lineStrings里;
以下是代码与说明(以下代码把英文字符排除在外,只考虑中文字符):

 

/** 
* 获取一行的字符 
*  
* @param MaxWidth 该行的最大长度 
* @param str 需要分行的字符串 
* @return 
*/
private String getLineText(int MaxWidth, String str) { 
  
// 真实行 
StringBuffer trueStringBuffer = new StringBuffer(); 
// 临时行 
StringBuffer tempStringBuffer = new StringBuffer(); 
  
for (int i = 0; i < str.length(); i++) { 
char c = str.charAt(i); 
String add = ""; 
  
add = "" + c; 
  
tempStringBuffer.append(add); 
String temp = tempStringBuffer.toString(); 
float width = getPaint().measureText(temp.toString()); 
  
if (width <= MaxWidth) { 
  
trueStringBuffer.append(add); 
} else { 
break; 
} 
  
} 
  
return trueStringBuffer.toString(); 
  
}


2.测量view的长度和高度、能否滚动的判断条件

/** 
* 测量高度 
*  
* @param width:宽度 
* @param heightMeasureSpec 
* @return 
*/
private int MeasureHeight(int width, int heightMeasureSpec) { 
int mode = MeasureSpec.getMode(heightMeasureSpec); 
int height = MeasureSpec.getSize(heightMeasureSpec); 
generateTextList(width); 
int lines = lineStrings.size(); 
  
absloutHeight = lines * getLineHeight() + getPaddingBottom() + getPaddingTop(); 
// 如果是wrap_content 
if (mode == MeasureSpec.AT_MOST) { 
  
height = (int)Math.min(absloutHeight, height); 
exactlyHeight = -1; 
  
} else if (mode == MeasureSpec.EXACTLY) { 
exactlyHeight = height; 
} 
return height; 
}



 

view的高度可以通过xml配置得来,也就是onMeasure的时候,而absloutHeight是需要看文字有多少行。前面已经讲过换行算法,行数不难求出:lineString.size()
那么计算文字的真实高度就不难了:
可以把lineStrings.size()*getLineheight()就能算出真实高度。
代码就是这样实现的(exactlyHeight可以先无视):

 

3.循环滚动的实现
首先需要知道什么时候才会滚动:
当view的高度低于文字的高度的时候会出现滚动,也就是:
exactlyHeight < absloutHeight
这里给一张示意图来表示exactlyHeight与absloutHeight的区别:黄色区域是文字区域,灰色区域是这个view的可见区域

注意:当xml里配置view的高度为wrap_content是不会滚动的,因为它刚好能容纳文字,只有当配置为fill_parent和具体值时,才会滚动.回顾一下exactlyHeight是如何赋值的:

/** 
* 测量高度 
*  
* @param width:宽度 
* @param heightMeasureSpec 
* @return 
*/
private int MeasureHeight(int width, int heightMeasureSpec) { 
int mode = MeasureSpec.getMode(heightMeasureSpec); 
int height = MeasureSpec.getSize(heightMeasureSpec); 
generateTextList(width); 
int lines = lineStrings.size(); 
  
absloutHeight = lines * getLineHeight() + getPaddingBottom() + getPaddingTop(); 
// 如果是wrap_content 
if (mode == MeasureSpec.AT_MOST) { 
  
height = (int)Math.min(absloutHeight, height); 
exactlyHeight = -1; 
  
} else if (mode == MeasureSpec.EXACTLY) { 
exactlyHeight = height; 
} 
return height; 
}


以上的所有准备工作做好了,就可以开始画了:
如果不考虑滚动,那么就直接一个for循环把lineStrings画完就结束了,但现在要考虑滚动,必需在它们for循环的基础上做一个y方向上的位移,而且这个位移会变化,我们可以用一个变量来定义它currentY

 这里onDraw()方法是精髓。先看一张滚动示意图,此图描述了几个滚动的关键状态:

不难看出,当y值小于exactlyHeight - absloutHeight时就得让它循环画在view的可见范围内我信就让y=y+absloutHeight,但是当y
y >=exactlyHeight - absloutHeight&& y < textSize + exactlyHeight - absloutHeight时,这个时候需要在view的最底端画出上半 部分文字
详情如图示:

另外当向下滚动时如果y >= absloutHeight时也是需要在顶端画出一部分文字

 

 

@Override
protected void onDraw(Canvas canvas) { 
  
super.onDraw(canvas); 
float x = getPaddingLeft(); 
float y = getPaddingTop(); 
  
float lineHeight = getLineHeight(); 
float textSize = getPaint().getTextSize(); 
  
for (int i = 0; i < lineStrings.size(); i++) { 
y = lineHeight * i + textSize + currentY; 
  
float min = 0; 
if (exactlyHeight > -1) { 
min = Math.min(min, exactlyHeight - absloutHeight); 
} 
if (y < min) { 
  
y = y + absloutHeight; 
  
} else if (y >= min && y < textSize + min) { 
  
//如果最顶端的文字已经到达需要循环从下面滚出的时候 
canvas.drawText(lineStrings.get(i), x, y + absloutHeight, getPaint()); 
} 
if (y >= absloutHeight) { 
//如果最底端的文字已经到达需要循环从上面滚出的时候 
canvas.drawText(lineStrings.get(i), x, y, getPaint()); 
y = y - absloutHeight; 
} 
canvas.drawText(lineStrings.get(i), x, y, getPaint()); 
} 
}


4.动画的实现
这一块简单,只需要不停的用handler发消息控制currentY自增操作就ok了,为了不让currentY越界,让它在absloutHeight与-absloutHeight之间

handler = new Handler() { 
  
@Override
public void handleMessage(Message msg) { 
if (absloutHeight <= getHeight()) { 
currentY = 0; 
stop(); 
return; 
} 
switch (msg.what) { 
  
case 0: { 
currentY = currentY - speed; 
  
resetCurrentY(); 
invalidate(); 
handler.sendEmptyMessageDelayed(0, delayTime); 
break; 
} 
case 1: { 
  
currentY += msg.arg1; 
  
resetCurrentY(); 
invalidate(); 
} 
} 
  
} 
  
/** 
* 重置currentY(当currentY超过absloutHeight时,让它重置为0) 
*/
private void resetCurrentY() { 
if (currentY >= absloutHeight || currentY <= -absloutHeight || getHeight() <= 0) { 
currentY = 0; 
} 
  
} 
};


5.手指托动文字
手指托动主要是在ontouch里写代码,在move的时候记录前一次y坐标,然后根据当前这次move事件与上次move事件的差值,得到滚动的距离。
move事件先上代码:

switch (event.getAction()) { 
case MotionEvent.ACTION_MOVE: 
float dy = event.getY() - lastY; 
lastY = event.getY(); 
// currentY = currentY + dy; 
Message msg = Message.obtain(); 
msg.what = 1; 
msg.arg1 = (int)dy; 
handler.sendMessage(msg); 
return true;


6.手指控制滚动

手指控制滚动主要在ontouch里的down和up/cancel事件里处理,当手指位移不超过performUpScrollStateDistance值时,表示手指是点击而不是拖动,那么就让它updateScrollStatus,这里updateScrollStatus就是让它更改滚动状态

/** 
* 更改滚动状态 
*/
public void updateScrollStatus() { 
  
if (scrolling) { 
stop(); 
} else { 
play(); 
} 
} 
  
/** 
* 开始滚动 
*/
public void play() { 
  
if (!scrolling) { 
handler.sendEmptyMessage(0); 
scrolling = true; 
} 
} 
  
/** 
* 停止滚动 
*/
public void stop() { 
if (scrolling) { 
handler.removeMessages(0); 
scrolling = false; 
} 
}

复制代码


复制代码
   
  
case MotionEvent.ACTION_DOWN: 
                distanceY = lastY = event.getY(); 
                distanceX = event.getX(); 
                pause(); 
  
case MotionEvent.ACTION_CANCEL: 
goOn(); 
float y = event.getY() - distanceY; 
float x = event.getX() - distanceX; 
  
if (Math.sqrt(y * y + x * x) < performUpScrollStateDistance) { 
updateScrollStatus(); 
} 
return true; 
  
}


 

你可能感兴趣的:(TextView自动垂直滚动)