一、概述
需求:
如下图的一行文本中,有部分文字可点击,执行操作A;其他的文本也可点击,执行操作B;
效果如下图:
布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:id="@+id/ll_root"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv_content"
android:layout_width="match_parent"
android:padding="15dp"
android:layout_height="wrap_content"/>
<View
android:layout_width="match_parent"
android:background="#f3f3f3"
android:layout_height="5dp"/>
</LinearLayout>
二、问题
布局文件中有两个控件:LinearLayout 和 TextView,添加事件如下(富文本事件必须添加):
事件 | 富文本事件 | TextView点击事件 | LinearLayout点击事件 |
---|---|---|---|
添加富文本点击事件 和 TextView点击事件 | Y | Y | N |
添加富文本点击事件 和 LinearLayout点击事件 | Y | N | N |
添加富文本点击事件 、TextView点击事件 和 LinearLayout点击事件 | Y | Y | N |
由上表可知:
- 添加富文本点击事件 和 TextView点击事件时,点击富文本时,会触发 富文本的点击事件 和 TextView的点击事件;
- 添加富文本点击事件 和 LinearLayout点击事件时,富文本的点击事件会拦截 TextView的父容器(LinearLayout)的点击事件;
三、代码
public class SpannableStringActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_spannable);
LinearLayout llRoot = (LinearLayout) findViewById(R.id.ll_root);
TextView tvContent = (TextView) findViewById(R.id.tv_content);
addSpanClick(tvContent, "我是文本我是文本我是文本我是文本我是文本我是文本我是文本我是文本");
llRoot.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(SpannableStringActivity.this, "文本被点击了", Toast.LENGTH_SHORT).show();
}
});
// 不能使用 tvContent 的点击事件,否则点击“我是前缀”时,也会触发tvContent的点击事件;
// tvContent .setOnClickListener(new View.OnClickListener() {
// @Override
// public void onClick(View v) {
// Toast.makeText(SpannableStringActivity.this, "TextView被点击了", Toast.LENGTH_SHORT).show();
// }
// });
}
// 添加ClickableSpan事件
private void addSpanClick(TextView tv, String content) {
SpannableString spanString = new SpannableString("我是前缀:");
ForegroundColorSpan span = new ForegroundColorSpan(Color.BLUE);
spanString.setSpan(span, 0, 5, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
spanString.setSpan(new ClickableSpan() {
@Override
public void onClick(View widget) {
Toast.makeText(SpannableStringActivity.this, "我是前缀", Toast.LENGTH_SHORT).show();
}
@Override
public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds);
ds.setAntiAlias(true);
ds.setUnderlineText(false);
}
}, 0, 5 , Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
// 这里需要添加这一行代码,否则富文本的点击事件不生效;
tv.setMovementMethod(MyLinkedMovementMethod.getInstance());
tv.setText(spanString);
tv.append(content);
}
}
重写 LinkMovementMethod.onTouchEvent()
方法,当点击非富文本区域时,让TextView的父容器去执行点击事件;
public class MyLinkedMovementMethod extends LinkMovementMethod {
private static MyLinkedMovementMethod sInstance;
public static MyLinkedMovementMethod getInstance() {
if (sInstance == null)
sInstance = new MyLinkedMovementMethod();
return sInstance;
}
@Override
public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
// 因为TextView没有点击事件,所以点击TextView的非富文本时,super.onTouchEvent()返回false;
// 此时可以让TextView的父容器执行点击事件;
boolean isConsume = super.onTouchEvent(widget, buffer, event);
if (!isConsume && event.getAction() == MotionEvent.ACTION_UP) {
ViewParent parent = widget.getParent();
if (parent instanceof ViewGroup) {
// 获取被点击控件的父容器,让父容器执行点击;
((ViewGroup) parent).performClick();
}
}
return isConsume;
}
}