作者:沉鈺发忠谦琪伦 | 来源:互联网 | 2023-07-07 12:55
先说说它的优点吧:1.当view的大小容不下文字的时候,这个view有循环滚动文字的能力。2.滚动的时候轻轻点击它,会停止滚动。3.停止滚动时轻轻点击它,又会继续滚动。4.可以通过手指拖动文字的显示位
先说说它的优点吧:
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 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 这里给一张示意图来表示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 详情如图示:
另外当向下滚动时如果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 y = lineHeight * i + textSize + currentY;
float min = 0;
if (exactlyHeight > -1) {
min = Math.min(min, exactlyHeight - absloutHeight);
}
if (y
y = y + absloutHeight;
} else if (y >= min && y
//如果最顶端的文字已经到达需要循环从下面滚出的时候
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) updateScrollStatus();
}
return true;
}