TextView自动垂直滚动

来源:互联网 发布:人工智能聊天机器人 编辑:程序博客网 时间:2024/05/21 06:44

先说说它的优点吧:
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时也是需要在顶端画出一部分文字

 

 

@Overrideprotected 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() {   @Overridepublic 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;   }


 

原创粉丝点击