之前在『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);
}
}
}
最后看看最终效果: