1. 程式人生 > >問題 — ClickableSpan事件和View.onClick()事件衝突

問題 — ClickableSpan事件和View.onClick()事件衝突

一、概述

需求:

如下圖的一行文字中,有部分文字可點選,執行操作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

由上表可知:

  1. 新增富文字點選事件 和 TextView點選事件時,點選富文字時,會觸發 富文字的點選事件TextView的點選事件
  2. 新增富文字點選事件 和 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;
        }
    }

四、參考