TextView rich text adaptation problem in Android

TextView rich text adaptation problem in Android

Question 1

Let's first look at the following code:

Spanned spanned = Html.fromHtml(text, mImageGetter, tagHandler);
if (spanned instanceof SpannableStringBuilder) {
    mBuilder = (SpannableStringBuilder) spanned;
} else {
    mBuilder = new SpannableStringBuilder(spanned);
}

ImageSpan[] imgSpans = mBuilder.getSpans(0, mBuilder.length(), ImageSpan.class);
......
setText(mBuilder);
setMovementMethod(LinkMovementMethod.getInstance()); //实现TextView中的局部点击事件交互

​ Let me explain first: this is a piece of code that needs to be used to display rich text in a TextView. To put it simply, a Spanned object is obtained through the Html.fromHtml() method, and then the object is converted into an object mBuilder of SpannableStringBuilder, and various Span attributes are set through a series of mBuilder methods to achieve the purpose of displaying rich text. .

​ This code has no problem at all when the API version is <= 23 and can run normally; however, when the code is placed on the device when the API version is > 23, there is a problem that the click event of the picture in the TextView is disordered !

​ After debugging, it was found that: on a device with API version > 23, when the Span array is obtained through mBuilder.getSpans(), it becomes an unordered array, as shown in the following figure:
ImageSpan out of order when api is greater than 23
​Why
is this happening? I haven't found the specific reason yet... If you have friends who know about this problem, please let me know. Thank you! Thank you!

​ Now that you know the cause of the problem, it is relatively easy to solve it: you only need to judge whether the current system version is greater than 23, and if it is a system version greater than API 23, reorder the obtained array. code show as below:

if (imgSpans.length > 0 && Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
    Arrays.sort(imgSpans, new Comparator<ImageSpan>() {
        @Override
        public int compare(ImageSpan lhs, ImageSpan rhs) {
            // 升序排列
            return mBuilder.getSpanStart(lhs) - mBuilder.getSpanStart(rhs);
        }
    });
}

Question 2

​ Let me first talk about the specific requirements of this problem in the project: in a piece of text with plain text content, when a specific area is clicked, the color of the text in the area is changed to achieve an interactive effect; as shown in the following figure:

When the api is greater than 23, the color is disordered

​ In order to achieve this requirement, I need to customize a SectionClickableSpan inherited from the system ClickableSpan to implement click events. code show as below:

public class SectionClickableSpan extends ClickableSpan {
    /**
     * 用以保存每个点击区域的位置信息holder
     */
    public static class SectionClickHolder {
        /**
         * 当前位置是否可点击;为false时,不可点击
         */
        public boolean isCanChange;
        /**
         * 当前位置索引
         */
        public int sectionIndex = -1;
        /**
         * 用以区分当前位置状态信息
         */
        public int sectionStatus = -1;
        /**
         * 可点击区域的开始索引
         */
        public int start;
        /**
         * 可点击区域的结束索引
         */
        public int end;
        /**
         * 颜色值
         */
        public int color;
    }

    public interface ISectionClickListener {
        /**
         * 当点击时回调该方法
         *
         * @param widget          当前被点击的view
         * @param clickableHolder 当前被点击的holder信息
         */
        void onSectionClick(View widget, SectionClickHolder clickableHolder);
    }

    private SectionClickHolder mSectionClickHolder;
    private ISectionClickListener mSectionClickListener;

    public SectionClickableSpan(SectionClickHolder sectionClickHolder) {
        mSectionClickHolder = sectionClickHolder;
    }

    @Override
    public void onClick(View widget) {
        if (mSectionClickListener != null) {
            mSectionClickListener.onSectionClick(widget, mSectionClickHolder);
        }
    }

    @Override
    public void updateDrawState(TextPaint ds) {
        super.updateDrawState(ds);
        ds.setColor(Color.parseColor("#666666"));
        ds.setUnderlineText(false);
        ds.clearShadowLayer();
    }

    public void setSectionClickListener(ISectionClickListener sectionClickListener) {
        mSectionClickListener = sectionClickListener;
    }

    public SectionClickHolder getSectionClickHolder() {
        return mSectionClickHolder;
    }
}

​ Then after clicking, perform the following operations to update the color of the specified area. code show as below:

......
int color = -1;
if (clickHolder.sectionStatus == QTConstants.SectionImgStatus.SECTION_CHECKED) {
    color = QTConstants.PenColor.BLUE;
} else if (clickHolder.sectionStatus == QTConstants.SectionImgStatus.SECTION_DEF) {
    color = QTConstants.PenColor.GRAY;
}

if (color != -1) {
    // 说明有需要更新的SectionClickableSpan,更新当前位置span颜色值
    ForegroundColorSpan fcs = new ForegroundColorSpan(color);
    mBuilder.setSpan(fcs, clickHolder.start, clickHolder.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    setText(mBuilder);
}

​ When these two pieces of code run on devices with API version <= 23, there is no problem; but when adapting to devices with API version > 23, the following problems are encountered:

  • ​ 1. ClickableSpan in TextView responds to click events in confusion;

  • ​ 2. When the rich text in the TextView is initialized, the ClickableSpan color information is confusing;

  • ​ 3. Every time the ClickableSpan is clicked, the color information is updated chaotically;

    The first problem and the above problem 1 are actually the same problem, it is relatively simple to solve, and it is also the same routine. code show as below:

SectionClickableSpan[] sectionClickableSpans = mBuilder.getSpans(0, mBuilder.length(), SectionClickableSpan.class);

if (sectionClickableSpans.length > 0 && Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
    Arrays.sort(sectionClickableSpans, new Comparator<SectionClickableSpan>() {
        @Override
        public int compare(SectionClickableSpan lhs, SectionClickableSpan rhs) {
            // 升序排列
            return lhs.getSectionClickHolder().start - rhs.getSectionClickHolder().start;
        }
    });
}

As for the second question, I really struggled for a long time, and there is no relevant description of similar problems on the Internet. After careful analysis, it is found that we can start with the updateDrawState() method in SectionClickableSpan, and finally, solve the problem of initial color confusion. code show as below:

@Override
public void updateDrawState(TextPaint ds) {
    super.updateDrawState(ds);
    /*
        在api23版本之上时,TextView中span的颜色会出现错乱问题;具体原因不明。
        该mSectionClickHolder.color用以解决该问题。
    */
    if (mSectionClickHolder != null && mSectionClickHolder.color != 0) {
        ds.setColor(mSectionClickHolder.color);
    } else {
        // error color
        ds.setColor(Color.parseColor("#666666"));
    }
    ds.setUnderlineText(false);
    ds.clearShadowLayer();
}

​ As for why the second problem occurs, no specific reason has been found; in order to fully understand it, you have to compare the source code of the two versions, and this should be put aside for now. If a friend understands this problem, please let me know! Here, thank you very much!

Then focus on solving the third problem. After continuous debugging through debug, I found the following phenomenon: when the API version is > 23, when the ForegroundColorSpan color is repeatedly set in the same position in the TextView, the previously existing ForegroundColorSpan needs to be cleared first. The modified code is as follows:

int color = -1;
if (clickHolder.sectionStatus == QTConstants.SectionImgStatus.SECTION_CHECKED) {
    color = QTConstants.PenColor.BLUE;
} else if (clickHolder.sectionStatus == QTConstants.SectionImgStatus.SECTION_DEF) {
    color = QTConstants.PenColor.GRAY;
}

// 每次点击更新颜色值
clickHolder.color = color;

if (color != -1) {
    /*
        在api23以上的版本中,重复在同一位置设置背景span样式时,会导致颜色显示错乱。具体原因不明
        每次设置背景时,移除该位置之前所有的背景span
    */
    ForegroundColorSpan[] foregroundColorSpans = mBuilder.getSpans(clickHolder.start,                                    clickHolder.end, ForegroundColorSpan.class);
    if (foregroundColorSpans != null && foregroundColorSpans.length > 0) {
        for (ForegroundColorSpan foregroundColorSpan : foregroundColorSpans) {
            mBuilder.removeSpan(foregroundColorSpan);
        }
    }

    ForegroundColorSpan foregroundColorSpan = new ForegroundColorSpan(color);
    mBuilder.setSpan(foregroundColorSpan, clickHolder.start, clickHolder.end,                   Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    setText(mBuilder);
}

​ So far, when the API version is > 23, the rich text adaptation problem in TextView has been solved. As for the deep-seated reasons behind this problem, we will leave it to the follow-up to delve into the differences in the source code of each version!

​ This record is easy to read!

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325957429&siteId=291194637