之前在『Android 使 TextView 支持长按选择』一文中介绍了使用 android:textIsSelectable 属性就能简单实现 TextView 可选择的小技巧,文末也提到实际使用的时候往往会有一些坑。
比如,当你为 TextView 设置了 android:textIsSelectable 属性的同时还设置了点击事件,那么你会发现,当首次点击 TextView 时,TextView 是不会响应的:

不难猜到,TextView 在设置了 android:textIsSelectable 属性后,对原有的 View 触摸事件监听有入侵。
我们可以继承 TextView,重写 onTouchEvent() 方法,对触摸事件重新做处理:
public class SelectableTextView extends AppCompatTextView {
private long mActionDownTime = 0L;
public SelectableTextView(@NonNull Context context) {
super(context);
}
public SelectableTextView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public SelectableTextView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean click = false;
boolean textSelectable = isTextSelectable();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mActionDownTime = System.currentTimeMillis();
break;
case MotionEvent.ACTION_UP:
long actionUpTime = System.currentTimeMillis();
if (actionUpTime - mActionDownTime < ViewConfiguration.getLongPressTimeout()) {
setTextIsSelectable(false);
performClick();
click = true;
}
break;
}
setTextIsSelectable(textSelectable);
return click || super.onTouchEvent(event);
}
@Override
public boolean performClick() {
return super.performClick();
}
}
代码的逻辑非常简单,首先拿到 TextView 有没有设置为可选择,并声明一个点击标识,当监听到 ACTION_DOWN 事件时,记录事件产生的时间,而当监听到 ACTION_UP 事件时,计算两次事件的时间差,假如不为长按,则将当前 TextView 设置为不可选择,再执行点击事件,并将点击标识设置为 true,不要忘了将 TextView 是否可选择的属性重置回来;假如为长按,则不需要执行我们的操作,交还给父类处理即可。最后 onTouchEvent() 需要一个返回值,当执行点击时,我们接管了事件的处理逻辑,返回 true 即可,当执行长按时,交还给父类处理。
有几个地方需要解释一下。判断是否为长按是由 ViewConfiguration 的 getLongPressTimeout() 返回值进行比较的,默认为 400ms,由 Android 系统定义,我们也可以根据实际情况自行判定。
执行点击时我先将是否可选择的属性设置为 false,是因为如果不设置的话,执行点击事件时,TextView 会莫名选中点击区域的文字:

当事件响应完之后我必须要将该属性重置回来,所以一开始就必须获取该属性的值。
最后返回值这行代码我用了简写,可能会看得有些莫名奇妙,其实只要熟悉逻辑运算符 || 就不难理解,当点击标识为 true 时,该表达式必为 true,不需要再计算后面的值,onTouchEvent() 就会直接返回 true,而当点击标识为 false 时,则会执行 super.onTouchEvent(event) 并返回其值,当然你也可以用更加易读的写法:
public class SelectableTextView extends AppCompatTextView {
...
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean click = false;
...
if (click) {
return true;
} else {
return super.onTouchEvent(event);
}
}
}
最后看看最终效果:

