项目需求是可以实现文章的点开查看全文和收起功能这里用到了
SpannableStringBuilder进行拼接,对文章行数以及字数进行相关的处理,然后进行点击事件的处理
结果最后出现了问题:在同一段文字中,点击可点击文字部分时,所有的文字也会响应,也就是span响应的同时,onclick也响应了
第一时间想到的就是怎么拦截它
下面奉上自定义的这个View
public class CollapsedTextView extends androidx.appcompat.widget.AppCompatTextView { /** * 收起状态下的最大行数 */ private int maxLine = 1; /** * 截取后,文本末尾的字符串 */ private static final String ELLIPSE = "..."; /** * 默认全文的Text */ private static final String EXPANDEDTEXT = "展开全文"; /** * 默认收起的text */ private static final String COLLAPSEDTEXT = "收起"; /** * 所有行数 */ private int allLines = 1; /** * 是否是收起状态,默认收起 */ private boolean collapsed = true; /** * 真实的text */ private String text; /** * 收起时实际显示的text */ private CharSequence collapsedCs; private SpannableStringBuilder downBT; private SpannableStringBuilder upBT; public CollapsedTextView(Context context) { super(context); initView(context, null); } public CollapsedTextView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); initView(context, attrs); } public CollapsedTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(context, attrs); } private void initView(Context context, AttributeSet attrs) { downBT = new SpannableStringBuilder("展开全文"); Drawable dDown = getResources().getDrawable(R.drawable.ic_launcher);//subs_descibe_up dDown.setBounds(0, 0, dDown.getIntrinsicWidth() , dDown.getIntrinsicHeight()); //创建ImageSpan ImageSpan downSpan = new ImageSpan(dDown, ImageSpan.ALIGN_BASELINE); //用ImageSpan替换文本 downBT.setSpan(EXPANDEDTEXT, 0, downBT.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE); // downBT.setSpan(downSpan, 0, downBT.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);//图标 upBT = new SpannableStringBuilder(" 收起"); Drawable dUp = getResources().getDrawable(R.drawable.ic_launcher);//subs_descibe_down dUp.setBounds(0, 0, dUp.getIntrinsicWidth(), dUp.getIntrinsicHeight()); //创建ImageSpan ImageSpan upSpan = new ImageSpan(dUp, ImageSpan.ALIGN_BASELINE); //用ImageSpan替换文本 upBT.setSpan(COLLAPSEDTEXT, 0, upBT.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE); // upBT.setSpan(upSpan, 0, upBT.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);//图标 } public void setShowText(String text) { this.text = text; if (allLines > 0) { getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { ViewTreeObserver obs = getViewTreeObserver(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { obs.removeOnGlobalLayoutListener(this); } else { obs.removeGlobalOnLayoutListener(this); } TextPaint paint = getPaint(); float measureText = paint.measureText(text); int showWidth = getWidth() - getPaddingLeft() - getPaddingRight(); int lines = (int) (measureText / showWidth); if (measureText % showWidth != 0) { lines++; } allLines = (int) (paint.measureText(text + downBT) / showWidth); if (lines > maxLine) { int expect = text.length() / lines; int end = 0; int lastLineEnd = 0; int expandedTextWidth = (int) paint.measureText(ELLIPSE + downBT); //计算每行显示文本数 for (int i = 1; i <= maxLine; i++) { int tempWidth = 0; if (i == maxLine) { tempWidth = expandedTextWidth; } end += expect; if (end > text.length()) { end = text.length(); } if (paint.measureText(text, lastLineEnd, end) > showWidth - tempWidth) { //预期的第一行超过了实际显示的宽度 end--; while (paint.measureText(text, lastLineEnd, end) > showWidth - tempWidth) { end--; } } else { end++; while (paint.measureText(text, lastLineEnd, end) < showWidth - tempWidth) { end++; } end--; } lastLineEnd = end; } setMovementMethod(MyLinkedMovementMethod.getInstance()); SpannableStringBuilder s = new SpannableStringBuilder(text, 0, end) .append(ELLIPSE); collapsedCs = addClickableSpan(s, downBT); setText(collapsedCs); append(downBT); } else { setText(text); } } }); } } private static 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; } } private CharSequence addClickableSpan(SpannableStringBuilder s, SpannableStringBuilder collapsedText) { collapsedText.setSpan(new ClickableSpan() {//这里用可以点击的部分进行setspan @Override public void onClick(@NonNull View widget) { if (collapsed) { SpannableStringBuilder s = new SpannableStringBuilder(text); setText(addClickableSpan(s, upBT)); append(upBT); } else { setText(collapsedCs); append(downBT); } collapsed = !collapsed; } @Override public void updateDrawState(TextPaint ds) { super.updateDrawState(ds); ds.setAntiAlias(true); ds.setUnderlineText(false); } },0, collapsedText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); return s; } }
借鉴文章:https://blog.csdn.net/Love667767/article/details/82903992