版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
TextView 设置ClickableSpan之后,需要设置setMovementMethod(LinkMovementMethod.getInstance()),而LinkMovementMethod 继续ScrollingMovementMethod ,这样对于长文本有如下两个问题:
原文地址
GitHub测试Demo
1:ClickableSpan的点击和TextView的长文本滑动冲突
2:点击TextView空白区域,总选中最后一个文本
基于此实现了一个简单的英文阅读器,点击链接查看
主要实现
1:实现页面中单词点击选中
2:实现分页功能
3:实现简单的翻页动画
1分析原有的LinkMovementMethod 点击和滑动逻辑
1)根据点击位置查找当前行的ClickableSpan,若点击空白区域,则为当前行最后一个ClickableSpan;若ClickableSpan集合不为空,则拦截down和up事件。
@Override
public boolean onTouchEvent(TextView widget, Spannable buffer,
MotionEvent event) {
int action = event.getAction();
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
int x = (int) event.getX();
int y = (int) event.getY();
x -= widget.getTotalPaddingLeft();
y -= widget.getTotalPaddingTop();
x += widget.getScrollX();
y += widget.getScrollY();
Layout layout = widget.getLayout();
int line = layout.getLineForVertical(y);
int off = layout.getOffsetForHorizontal(line, x);
//根据点击位置查找当前行的ClickableSpan,若点击空白区域,则为当前行最后一个ClickableSpan
ClickableSpan[] links = buffer.getSpans(off, off, ClickableSpan.class);
//ClickableSpan集合不为空,则拦截down和up事件
if (links.length != 0) {
if (action == MotionEvent.ACTION_UP) {
links[0].onClick(widget);
} else if (action == MotionEvent.ACTION_DOWN) {
Selection.setSelection(buffer,
buffer.getSpanStart(links[0]),
buffer.getSpanEnd(links[0]));
}
return true;
} else {
Selection.removeSelection(buffer);
}
}
return super.onTouchEvent(widget, buffer, event);
}
2)调用父类onTouchEvent透传事件
@Override
public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
return Touch.onTouchEvent(widget, buffer, event);
}
3)处理拖拽事件。
ACTION_DOWN设置拖拽状态,ACTION_UP清除拖拽状态,ACTION_MOVE如果有拖拽状态,满足条件则移动内容。
/**
* Handles touch events for dragging. You may want to do other actions
* like moving the cursor on touch as well.
*/
public static boolean onTouchEvent(TextView widget, Spannable buffer,
MotionEvent event) {
DragState[] ds;
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
ds = buffer.getSpans(0, buffer.length(), DragState.class);
for (int i = 0; i < ds.length; i++) {
buffer.removeSpan(ds[i]);
}
//设置拖拽状态
buffer.setSpan(new DragState(event.getX(), event.getY(),
widget.getScrollX(), widget.getScrollY()),
0, 0, Spannable.SPAN_MARK_MARK);
return true;
case MotionEvent.ACTION_UP:
ds = buffer.getSpans(0, buffer.length(), DragState.class);
//清除拖拽状态
for (int i = 0; i < ds.length; i++) {
buffer.removeSpan(ds[i]);
}
if (ds.length > 0 && ds[0].mUsed) {
return true;
} else {
return false;
}
case MotionEvent.ACTION_MOVE:
ds = buffer.getSpans(0, buffer.length(), DragState.class);
//如果有拖拽状态,满足条件则移动内容
if (ds.length > 0) {
if (ds[0].mFarEnough == false) {
int slop = ViewConfiguration.get(widget.getContext()).getScaledTouchSlop();
if (Math.abs(event.getX() - ds[0].mX) >= slop ||
Math.abs(event.getY() - ds[0].mY) >= slop) {
ds[0].mFarEnough = true;
}
}
if (ds[0].mFarEnough) {
ds[0].mUsed = true;
boolean cap = (event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0
|| MetaKeyKeyListener.getMetaState(buffer,
MetaKeyKeyListener.META_SHIFT_ON) == 1
|| MetaKeyKeyListener.getMetaState(buffer,
MetaKeyKeyListener.META_SELECTING) != 0;
float dx;
float dy;
if (cap) {
// if we're selecting, we want the scroll to go in
// the direction of the drag
dx = event.getX() - ds[0].mX;
dy = event.getY() - ds[0].mY;
} else {
dx = ds[0].mX - event.getX();
dy = ds[0].mY - event.getY();
}
ds[0].mX = event.getX();
ds[0].mY = event.getY();
int nx = widget.getScrollX() + (int) dx;
int ny = widget.getScrollY() + (int) dy;
int padding = widget.getTotalPaddingTop() + widget.getTotalPaddingBottom();
Layout layout = widget.getLayout();
ny = Math.min(ny, layout.getHeight() - (widget.getHeight() - padding));
ny = Math.max(ny, 0);
int oldX = widget.getScrollX();
int oldY = widget.getScrollY();
scrollTo(widget, layout, nx, ny);
// If we actually scrolled, then cancel the up action.
if (oldX != widget.getScrollX() || oldY != widget.getScrollY()) {
widget.cancelLongPress();
}
return true;
}
}
}
return false;
}
2根据业务需要滑动和点击事件,做如下处理
1)如果点击当前行的区域为空白区域,返回空集合。
2)ACTION_DOWN设置拖拽状态,ACTION_UP清除拖拽状态,设置之后再拦截
@Override
public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
if (mTouchSlop == 0) {
mTouchSlop = ViewConfiguration.get(widget.getContext()).getScaledTouchSlop();
}
ClickableSpan[] links;
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
mIsMoved = false;
//设置拖拽状态
super.onTouchEvent(widget, buffer, event);
mDownY = event.getY();
mDownX = event.getX();
links = findClickableSpans(widget, buffer, event);
if (links.length != 0) {
Selection.setSelection(buffer,
buffer.getSpanStart(links[0]),
buffer.getSpanEnd(links[0]));
return true;
} else {
Selection.removeSelection(buffer);
}
break;
case MotionEvent.ACTION_MOVE:
if (Math.abs(event.getX() - mDownX) > mTouchSlop ||
Math.abs(event.getY() - mDownY) > mTouchSlop) {
mIsMoved = true;
}
break;
case MotionEvent.ACTION_UP:
//清除拖拽状态
super.onTouchEvent(widget, buffer, event);
links = findClickableSpans(widget, buffer, event);
//如果滑动,则不触发点击事件
if (!mIsMoved && links.length != 0) {
links[0].onClick(widget);
return true;
} else {
if (!mIsMoved && mClickWordListener != null) {
mClickWordListener.onClickEmpty();
}
Selection.removeSelection(buffer);
}
break;
default:
}
return super.onTouchEvent(widget, buffer, event);
}
/**
* 如果点击区域为空白区域,返回空集合
*/
private ClickableSpan[] findClickableSpans(TextView widget, Spannable buffer, MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
x -= widget.getTotalPaddingLeft();
y -= widget.getTotalPaddingTop();
x += widget.getScrollX();
y += widget.getScrollY();
Layout layout = widget.getLayout();
int line = layout.getLineForVertical(y);
int off = layout.getOffsetForHorizontal(line, x);
int lineMax = (int) layout.getLineMax(line);
//点击空白区域返回空集合
if (x > lineMax) {
return new ClickableSpan[]{};
}
return buffer.getSpans(off, off, ClickableSpan.class);
}