产品要求实现一个类似于下图的功能
其中红框部分需要实现点击,且左右有Padding。
并且整体的文本需要有点击事件 和 长按事件,蓝字处要实现另一个点击事件
这个不再赘述了,Android内本身支持很多类型的Span,例如ClickSpan、ForgroundColorSpan等等。具体实现方式大家可以自行查阅。
此处使用ClickSpan实现蓝字点击、使用自定义的ReplacementSpan实现UI层样式和Padding。
ClickSpan需要搭配LinkMovementMethod实现点击的具体逻辑。本身系统LinkMovementMethod并不支持去除下划线,并且在点击后会有background变化等问题。此处也许要自定义一个。
自定义Method的具体代码如下
public class MyLinkMovementMethod extends LinkMovementMethod {
private static LinkMovementMethod sInstance;
private long downTime;
public static MovementMethod getInstance() {
if (sInstance == null)
sInstance = new ReaderQuoterMovementMethod();
return sInstance;
}
@Override
public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN) {
downTime = System.currentTimeMillis();
}
if (action == MotionEvent.ACTION_UP) {
long interval = System.currentTimeMillis() - downTime;
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[] link = buffer.getSpans(off, off, ClickableSpan.class);
if (link.length != 0) {
if (interval < ViewConfiguration.getLongPressTimeout()) {
link[0].onClick(widget);
buffer.setSpan(new BackgroundColorSpan(Color.TRANSPARENT),
buffer.getSpanStart(link[0]), buffer.getSpanEnd(link[0]),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
return true;
}
}
自定义ReplacementSpan的具体代码如下:
public class QuoterSpan extends ReplacementSpan {
private static final int DEFAULT_RIGHT_PADDING = 6;
private Context mContext;
private Rect mRect;
public QuoterSpan(Context context) {
mContext = context;
mRect = new Rect();
}
@Override
public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, @Nullable Paint.FontMetricsInt fontMetricsInt) {
//获取原本文本的宽度,并在此基础上添加padding宽度
paint.getTextBounds(text.subSequence(0, text.length()).toString(), start, end, mRect);
return mRect.width() + Math.round(((TextPaint) paint).density * DEFAULT_LEFT_PADDING * 2);
}
@Override
public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint) {
if (mContext == null) {
return;
}
paint.setColor(ThemeSettingsHelper.getInstance().getThemeColor(mContext, R.color.milk_Blue).getDefaultColor());
paint.setAntiAlias(true);
//文本开始绘制区域为最初X值加左边padding
int textBegin = Math.round(x + ((TextPaint) paint).density * DEFAULT_LEFT_PADDING);
canvas.drawText(text.subSequence(start, end).toString(), textCenter, y, paint);
}
}
1、单机:如果textView本身有点击事件,此时又添加了ClickSpan,必须搭配MovenmentMethod才能将点击事件拦截在ClickSpan。这一点在源码中可以找到:
public boolean onTouchEvent(MotionEvent event) {
final int action = event.getActionMasked();
if (mEditor != null) {
mEditor.onTouchEvent(event);
if (mEditor.mSelectionModifierCursorController != null
&& mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) {
return true;
}
}
// 先交由父类决定是否拦截
final boolean superResult = super.onTouchEvent(event);
// .......
// 判断是否由MovementMethod处理
if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
&& mText instanceof Spannable && mLayout != null) {
boolean handled = false;
if (mMovement != null) {
handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
}
// .......
}
2、长按:如果当前textView设置了longClick事件。而我们的MovementMethod又多在MotionEvent = Up 的时候处理点击逻辑。(放down里可能会误触)。
这就会导致一个问题,当textView的长按被触发后抬起手指,MovementMethod的Up事件也会被触发。
解决:如本文中第一段代码中,在MovementMethod的Down事件时记录一个事件,如果Up和down的间隔时间大于系统内置的长按间隔(500ms默认,各厂商可能会改这个时间,例如小米手机的tap间隔小于系统默认的100ms)。则不做处理。