Android Textview supports partial click, ClickableSpan (rich text), and resolves LinkMovementMethod sliding conflicts

TextView contains partially clickable links, and there are corresponding click events in other parts of the link. The format is defined in the text, public void setSpan(Object what, int start, int end, int flags) {} Click to know where Which words in the text need to be clicked.

Examples 格式:Hello Android <b>Click Detail # https://www.baidu.com</b>

Requirements:
(1) <b> --- # Display: Clickable font in blue.
(2) # --- </b> Do not display: Clickable URL in blue font.
(3) The default font is black.

1. According to the process, first obtain the text from <b> to </b>, intercept it and splice it into the original text. The URL uses setSpan (new ClickableSpan()) to set the click event to jump. Some experts have said, substring(

) It's so easy to intercept clicks through ClickableSpan, you can't say it, take a look below:

If the format of Examples is not fixed:<b>ClickableSpan allows us to respond to click events when we click on the corresponding text of TextView# https://www.baidu.com </b>, such as the commonly used URLSpan, will open the corresponding link when clicked. In order for TextView to respond to ClickableSpan clicks, <b>we need to set LinkMovementMethod for it, # https://www.baidu.com</b> But this\n\n" <b>LinkMovementMethod has a lot of Pit# https://www.csdn.net</b> <b>LinkMovementMethod has another big pit# https://www.baidu.com</b>How do you solve it? There are many, setMovementMethod(LinkMovementMethod. getInstance()) sliding, space, newline.

Rendering:


//Set the hyperlink in TextView to be clickable spannableTextView.setMovementMethod(LinkMovementMethod.getInstance());
*Support Textview to set rich text clicks
*Set the middle text setting in Textview to be clickable and change color,
*Set setMovementMethod(LinkMovementMethod.getInstance()) There is a conflict between click and Textview sliding.
* Solve the problem of clicking on the blank area and still having click events.

//1. 截取并设置点击 Help class
public class MyDataHelp {
    public static final String TAG = MyDataHelp.class.getSimpleName();
    private static String start_label = "<b>", sub_label = "#", end_label = "</b>", http = "http";//设置截取的格式

    //处理数据
    public static void dealWithContent(String with_content, final CallBack callBack) {
        Log.i(TAG, "获取到的content: " + with_content);
        try {
            final ArrayList<String> list = new ArrayList<>();
            //按照</b>截取,看看有几个可以点击需要进行数据处理
            String[] split = with_content.split(end_label);
            for (String split_content : split) {
                if (split_content.contains(start_label) && split_content.contains(sub_label)) {
                    String substring = split_content.substring(split_content.indexOf(start_label), split_content.length());
                    Log.i(TAG, "substring : " + substring);
                    list.add(substring);
                }
            }
            ArrayList<String> list_title = new ArrayList<>(); //截取<b> --- # 显示的文字 list
            final ArrayList<String> list_url = new ArrayList<>(); //截取<b> --- # URL
            for (int i = 0; i < list.size(); i++) {
                if (with_content.contains(list.get(i))) {
                    String list_content = list.get(i);
                    String sub_title = list_content.substring(list_content.indexOf(start_label) + 3, list_content.indexOf(sub_label));
                    final String url;
                    if (list_content.contains(http)) {
                        url = list_content.substring(list_content.indexOf(http), list_content.length());
                    } else {
                        url = list_content.substring(list_content.indexOf(sub_label) + 2, list_content.length());
                    }
                    String start = with_content.substring(0, with_content.indexOf(start_label));
                    String end = with_content.substring(with_content.indexOf(end_label) + 4, with_content.length());
                    String all_text = start + sub_title + end;
                    with_content = all_text;
                    Log.i(TAG, "all_text : " + all_text);
                    list_title.add(sub_title);
                    list_url.add(url);
                    Log.i(TAG, "sub_title : " + sub_title + " url: " + url);
                }
            }
            SpannableStringBuilder ssb = new SpannableStringBuilder(with_content);//进行拼接和设置点击事件
            int indexOf = 0;
            for (int i = 0; i < list_title.size(); i++) {
                String list_text = list_title.get(i);
                list_text.replaceAll(" ", "");//替换中间的空格
                final String URL = list_url.get(i);
                Log.i(TAG, "list_text : " + list_text + " URL: " + URL);
                //为了解决重复<b>Click Detail # https://www.baidu.com</b> 好多相同的indexOf默认只取第一个,接着往下遍历.
                indexOf = with_content.indexOf(list_text, i == 0 ? 0 : indexOf + list_text.length());
                if (indexOf == -1) {//找不到会返回-1,只好再重新查找
                    indexOf = with_content.indexOf(list_text);
                }
                //设置点击文字的click和颜色
                ssb.setSpan(new ForegroundColorSpan(Color.parseColor("#2497df")), indexOf, indexOf + list_text.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
                ssb.setSpan(new ClickableSpan() {
                    @Override
                    public void onClick(@NonNull View view) {
                        Log.i(TAG, "click url: " + URL);
                        callBack.dealUrl(URL);
                    }

                    @Override
                    public void updateDrawState(@NonNull TextPaint ds) {
                        ds.setUnderlineText(false);    //去除超链接的下划线
                    }
                }, indexOf, indexOf + list_text.length() - 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); //list_text.length() - 1为了解决点击空白区域依然有点击事件
                Log.i(TAG, "ssb : " + ssb);
            }
            callBack.dealText(ssb);
        } catch (Exception e) {
            Log.i(TAG, "e.getMessage(): " + e.getMessage());//必要时走try
        }
    }

    public interface CallBack {
        void dealText(SpannableStringBuilder text);

        void dealUrl(String URL);
    }

}
// 2. 解决布局clcik与TextView本身滑动冲突
public class MyTextView extends TextView {
    private static String TAG = "MyTextView";

    public MyTextView(Context context) {
        super(context);
    }

    public MyTextView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public MyTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public boolean linkHit;//内部富文本是否被点击
    private static long lastClickTime;

    private static final long CLICK_DELAY = 500l;


    @Override
    public boolean performClick() {   //最后响应3
        if (linkHit) {
            return true;    //这样textView的OnClick事件不会响应
        }
        return super.performClick();  //调用监听的onClick方法
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {   //textView的OnClick事件响应,首先响应1
        linkHit = false;
        return super.onTouchEvent(event);
    }

    public static class CustomLinkMovementMethod extends LinkMovementMethod { //次之2

        static CustomLinkMovementMethod sInstance;
        //手指按下的点为(x1, y1)手指离开屏幕的点为(x2, y2)
        float x1 = 0;
        float x2 = 0;
        float y1 = 0;
        float y2 = 0;

        @Override
        public boolean onTouchEvent(TextView widget, Spannable buffer,
                                    MotionEvent event) {
            int action = event.getAction();

            if (action == MotionEvent.ACTION_UP ||
                    action == MotionEvent.ACTION_DOWN) {
                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);
                //解决拦截滑动时触发Textview link 点击事件
                if (action == MotionEvent.ACTION_UP) {
                    //当手指离开的时候
                    x2 = event.getX();
                    y2 = event.getY();
                    if (y1 - y2 > 50) {
                        Log.i(TAG, "向上滑");
                        link = new ClickableSpan[0];
                    } else if (y2 - y1 > 50) {
                        Log.i(TAG, "向下滑");
                        link = new ClickableSpan[0];
                    }
                } else {
                    //当手指按下的时候
                    x1 = event.getX();
                    y1 = event.getY();
                }
                //不是滑动、link.length != 0,拦截处理 Textview link 点击事件 ,证明点击了
                if (link.length != 0) {
                    if (action == MotionEvent.ACTION_UP) {
                        Selection.setSelection(buffer,
                                buffer.getSpanStart(link[0]),
                                buffer.getSpanEnd(link[0]));
                        link[0].onClick(widget);
                        return true;
                    }
                } else {
                    Selection.removeSelection(buffer);
                }

            }
            return Touch.onTouchEvent(widget, buffer, event);
        }

        public static CustomLinkMovementMethod getInstance() {
            if (sInstance == null) {
                sInstance = new CustomLinkMovementMethod();
            }
            return sInstance;
        }
    }
}
// 3. Textview 赋值  
MyDataHelp.dealWithContent(content, new MyDataHelp.CallBack() {//处理富文本格式数据
            @Override
            public void dealText(SpannableStringBuilder ssb) {
                tv_content.setText(ssb);
                tv_content.setMovementMethod(MyTextView.CustomLinkMovementMethod.getInstance());//解决点击事件与Textview本身滑动冲突
                tv_content.setHighlightColor(ContextCompat.getColor(getContext(), R.color.white));//去掉点击后的背景颜色为透明
            }

            @Override
            public void dealUrl(String URL) {
                clickListenerInterface.doLink(URL);
            }
        });

  If you have other solutions, please tell me

 the download address of the Demo.

Guess you like

Origin blog.csdn.net/zyy_give/article/details/105576800