安卓开发SpannableStringBuilder展示富文本

在安卓开发中,如果你们的项目需要展示文本,然后文本里面嵌套着图片,并且展示的文字有些字需要标记成不同的颜色,文字还需要有点击事件。如果让你按之前的思路去实现这样的一个效果,你会这样来设计实现思路:

 1)文字用TextView展示,图片用ImageView展示,然后文字需要被截取,根据后台返回的文字索引脚标。
  2)截取的文字会和图片链接进行组合布局进行展示,这样的问题就会是文字和图片的布局是不固定的,会根据后台返回的文字和图片不同而展示不同。
  3)文字如果需要添加点击事件,需要重新设计布局添加TextView控件

从上面的实现思路我们可以发现,如果按之前的TextView和ImageView组合的方式进行展示的话,就会非常麻烦。
那么我们就可以使用SpannableStringBuilder这个对象类来实现上面的需求,其原理就是把文本转成Html网页来进行展示。
安卓开发SpannableStringBuilder展示富文本_第1张图片
从上图我们可以发现这样设置没有一点的问题,选中的文字会展示不同的背景颜色,但是再点击颜色的时候会绿色的文字间距背景颜色。之前一直觉得是文本转成Html之后导致的Html文本自带背景颜色导致的,后面才发现文本转成Html文本:

Html.fromHtml(content,Html.FROM_HTML_MODE_LEGACY, mURLImageGetter, null);

返回一个Spanned对象,
安卓开发SpannableStringBuilder展示富文本_第2张图片
富文本转成Html网页显示之后,默认点击事件是需要高亮显示的,但是这是TextView里面设置的方法:

    /**
     * Sets the color used to display the selection highlight.
     *
     * @attr ref android.R.styleable#TextView_textColorHighlight
     */
    @android.view.RemotableViewMethod
    public void setHighlightColor(@ColorInt int color) {
        if (mHighlightColor != color) {
            mHighlightColor = color;
            invalidate();
        }
    }

所以,我们想要去掉我们选中的Html文本的点击背景颜色,就需要设置这个高亮显示的颜色设置成白色就行了。

 setHighlightColor(Color.parseColor("#00000000"));

富文本经常还需要设置选中文字的功能,下面就推荐一个选中文字改变背景颜色的开源框架。
下面是安卓开发,把文本直接转成富文本的工具类:

/**
 * 文章详情-富文本控件
 * @author guotianhui
 */

public class FenJRichTextView extends AppCompatTextView{

    private Spannable mSpannable;
    private ClickableSpan clickableSpan;
    private URLImageGetter mURLImageGetter;//加载图片使用的,处理html中![在这里插入图片描述]()的处理器,生成Drawable对象并返回
    private ColorUnderlineSpan mUnderlineSpan;
    private ForegroundColorSpan mFCTxtContentSpan; //文字颜色
    private FenJRichTextViewHelper mFenJRichTextViewHelper;
    private SpannableStringBuilder mSpannableStringBuilder;
    private OnRichContentClickListener mRichContentClickListener;//图片点击回调


    public FenJRichTextView(Context context) {
        this(context, null);
    }

    public FenJRichTextView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public FenJRichTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mURLImageGetter = new URLImageGetter(this);
        mFenJRichTextViewHelper = new FenJRichTextViewHelper();
    }

    /**
     * 设置富文本内容
     * @param content 文章内容(包含img标签)
     * @param markSpans 马克笔列表
     */
    public void setRichContent(String content, List analysisBeans, List markSpans) {
        try {
            //显示带图片的html的处理方法,第二个参数imageGetter是加载图片使用的,第三个参数是过滤标签使用的
            Spanned spanned = null;
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                spanned = Html.fromHtml(content,Html.FROM_HTML_MODE_LEGACY, mURLImageGetter, null);
            }else {
                spanned = Html.fromHtml(content,mURLImageGetter, null);
            }
            if (spanned instanceof SpannableStringBuilder) {
                mSpannableStringBuilder = (SpannableStringBuilder) spanned;
            } else {
                mSpannableStringBuilder = new SpannableStringBuilder(spanned);
            }
            mSpannable = null;
            setMarkPen(markSpans);
            setImageClickable();
            setHighlightColor(Color.parseColor("#00000000"));
            setTextClickableAnalysis(analysisBeans);
            setMovementMethod(LinkMovementMethod.getInstance());
            super.setText(mSpannableStringBuilder);

        }catch (Exception e){
            LogUtils.e(">>>>>>>>>>>>","设置文章内容出现异常:"+e);
        }
    }

    /**
     * 设置马克笔
     * @param markSpans
     */
    public void setMarkPen(List markSpans) {
        setMarkPenBgColor(markSpans);
        //增加马克笔点击事件
        setTextClickable(markSpans);
    }

    /**
     * 设置马克笔的背景色
     * @param markSpans 马克笔列表
     */
    private void setMarkPenBgColor(List markSpans) {
        try {
            if (ObjectUtils.isNotEmpty(markSpans)) {
                for (MarkerPenBean articleDetailMark : markSpans) {
                    int color;
                    if (TextUtils.isEmpty(articleDetailMark.getMarkColor())) {
                        //容错,颜色返回空使用默认颜色
                        color = Color.parseColor("#fbfab7");
                    } else {
                        color = Color.parseColor(articleDetailMark.getMarkColor());
                    }
                    int startPosition = articleDetailMark.getMarkStartPosition();
                    int endPosition = articleDetailMark.getMarkEndPosition();
                    int lg = mSpannableStringBuilder.length();
                    if (endPosition > 0 && lg >= endPosition) {
                        updateTxtBgColor(color, startPosition, endPosition);
                    }
                }
            }
        }catch (Exception e){
            LogUtils.e(">>>>>>>>>>>>>>>>","设置马克笔背景色异常:"+e);
        }
    }

    /**
     * 设置文字背景色
     * @param color
     * @param startPosition
     * @param endPosition
     */
    private void updateTxtBgColor(int color, int startPosition, int endPosition) {
        try {
            int contentLength = mSpannableStringBuilder.length();
            if (endPosition > contentLength) {
                endPosition = contentLength;
            }
            Log.e(">>>>>>>>>>>>>>>>","设置文字背景色color:"+color);
            mSpannableStringBuilder.setSpan(new BackgroundColorSpan(color), startPosition, endPosition,
                    Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
        }catch (Exception e){
            LogUtils.e(">>>>>>>>>>>>","设置文字背景色数组脚标越界:"+e);
        }
    }

    /**
     * 设置文字颜色
     * @param color 颜色
     * @param startPosition 开始位置(包含)
     * @param endPosition 结束位置(不包含)
     */
    public void updateTxtColor(int color, int startPosition, int endPosition) {
        try {
            if (mSpannable == null) {
                super.setText(getText(), BufferType.SPANNABLE);
                CharSequence charSequence = getText();
                if (charSequence instanceof Spannable) {
                    mSpannable = (Spannable) charSequence;
                }
            }
            if (mFCTxtContentSpan == null) {
                mFCTxtContentSpan = new ForegroundColorSpan(color);
            }
             Log.e(">>>>>>>>>>>>>>>>","设置文字颜色color:"+color);
            int articleL = getText().toString().length();
            if (endPosition > articleL) {
                endPosition = articleL;
            }
            mSpannable.setSpan(mFCTxtContentSpan, startPosition, endPosition,
                    Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        } catch (Exception e){
            LogUtils.e(">>>>>>>>>>>>更改文字颜色报错:"+e.getMessage());
        }
    }


    /**
     * 某段文字添加下划线
     * @param color
     * @param startPosition 下划线开始位置
     * @param endPosition 下划线结束位置
     */
    public void addTxtUnderline(int color, int startPosition, int endPosition) {
        try {
            if (mSpannable == null) {
                super.setText(getText(), BufferType.SPANNABLE);
                CharSequence charSequence = getText();
                if (charSequence instanceof Spannable) {
                    mSpannable = (Spannable) charSequence;
                }
            }
            if (mUnderlineSpan == null) {
                mUnderlineSpan = new ColorUnderlineSpan(color);
            }
            int contentL = getText().toString().length();
            if (endPosition < contentL) {
                mSpannable.setSpan(mUnderlineSpan, startPosition, endPosition, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
            } else if (contentL > startPosition) {
                mSpannable.setSpan(mUnderlineSpan, startPosition, contentL, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
            } else {

            }
        }catch (Exception e){
            LogUtils.e(">>>>>>>>>>>","文字添加下划线报错:"+e);
        }
    }


    /**
     * 移除文字颜色
     */
    public void removeTxtColor() {
        if (mSpannable != null && mFCTxtContentSpan != null) {
            mSpannable.removeSpan(mFCTxtContentSpan);
            mFCTxtContentSpan = null;
        }
    }

    /**
     * 移除文字下滑线
     */
    public void removeTxtUnLine() {
        if (mSpannable != null && mUnderlineSpan != null) {
            mSpannable.removeSpan(mUnderlineSpan);
            mUnderlineSpan = null;
        }
    }

    /**
     * 移除点击事件
     */
    public void removeContentClickableSpan() {
        if (mSpannable != null && clickableSpan != null) {
            mSpannable.removeSpan(clickableSpan);
            clickableSpan = null;
        }
    }


    /**
     * 设置文字背景色
     * @param content
     * @param color
     */
    public void setRichTxtBgColor(String content, int color) {
        if (TextUtils.isEmpty(content)) {
            return;
        }
        //显示带图片的html的处理方法
        Spanned spanned = Html.fromHtml(content, mURLImageGetter, null);

        if (spanned instanceof SpannableStringBuilder) {
            mSpannableStringBuilder = (SpannableStringBuilder) spanned;
        } else {
            mSpannableStringBuilder = new SpannableStringBuilder(spanned);
        }
        mSpannable = null;
        Log.e(">>>>>>>>>>>>>>>"," 设置文字背景色color:"+color);
        updateTxtBgColor(color, 0, mSpannableStringBuilder.length());
        setMovementMethod(LinkMovementMethod.getInstance());
        super.setText(spanned);
    }

    /**
     * 设置富本文内容
     * @param content 文章内容(包含img标签)
     */
    public void setRichContent(String content) {
        if (TextUtils.isEmpty(content)) {
            return;
        }
        //显示带图片的html的处理方法
        Spanned spanned = Html.fromHtml(content, mURLImageGetter, null);

        if (spanned instanceof SpannableStringBuilder) {
            mSpannableStringBuilder = (SpannableStringBuilder) spanned;
        } else {
            mSpannableStringBuilder = new SpannableStringBuilder(spanned);
        }
        mSpannable = null;
//        setMovementMethod(LinkMovementMethod.getInstance());
        super.setText(spanned);
    }

    /**
     *  处理图片的点击事件
     */
    private void setImageClickable() {
        try {
            ImageSpan[] imageSpans = mSpannableStringBuilder.getSpans(0, mSpannableStringBuilder.length(), ImageSpan.class);
            final ArrayList imageUrls = new ArrayList<>();
            if (ObjectUtils.isNotEmpty(imageSpans)) {
                for (int i = 0, size = imageSpans.length; i < size; i++) {
                    ImageSpan imageSpan = imageSpans[i];
                    String imageUrl = imageSpan.getSource();
                    int start = mSpannableStringBuilder.getSpanStart(imageSpan);
                    int end = mSpannableStringBuilder.getSpanEnd(imageSpan);
                    PictureItem pictureItem = new PictureItem();
                    pictureItem.setLevelPictureUrl(imageUrl);
                    imageUrls.add(pictureItem);
                    final int position = i;
                    ClickableSpan clickableSpan = new ClickableSpan() {
                        @Override
                        public void onClick(View widget) {
                            if (mRichContentClickListener != null) {
                                mRichContentClickListener.onImageClick(imageUrls, position);
                            }
                        }
                    };
                    //增加事件
                    mSpannableStringBuilder.setSpan(clickableSpan, start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
                }
            }
        }catch (Exception e){
            LogUtils.e(">>>>>>>>>>>>>>>>", "文章图片点击报错:"+e);
        }
    }

    /**
     *设置可以点击的解析卡文字
     * @param analysisBeans
     */
    public void setTextClickableAnalysis(final List analysisBeans) {
        try {
            if(ObjectUtils.isNotEmpty(analysisBeans)) {
                for (int i = 0, size = analysisBeans.size(); i < size; i++) {
                    LevelAnalysisBean imageSpan = analysisBeans.get(i);
                    int start = imageSpan.getLocation().getIndexClickable();
                    if (start < 0) {
                        continue;
                    }
                    int end = start + imageSpan.getLocation().getLengthClickable();
                    final int position = i;
                    ClickableSpan clickableSpan = new ClickableSpan() {
                        @Override
                        public void onClick(View widget) {
                            if (mRichContentClickListener != null) {
                                mRichContentClickListener.onTextClickAnalysis(analysisBeans, position);
                            }
                        }

                        //去除超链接下划线
                        @Override
                        public void updateDrawState(TextPaint ds) {
                            /**set textColor**/
//                    ds.setColor(ds.linkColor);
                            /**Remove the underline**/
                            ds.setUnderlineText(false);
                        }
                    };
                    //超文本点击事件
                    if (end > 0 && mSpannableStringBuilder.length() >= end) {
                        mSpannableStringBuilder.setSpan(clickableSpan, start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
                    }
                }
            }
        }catch (Exception e){
            LogUtils.e(">>>>>>>>>>>>>>>","设置文章的解析卡报错:"+e);
        }
    }

    /**
     * 设置文字点击事件
     */
    private void setTextClickable(List imageSpans) {
        try {
            if(ObjectUtils.isNotEmpty(imageSpans)) {
                final List markList = new ArrayList<>();
                for (int i = 0, size = imageSpans.size(); i < size; i++) {
                    MarkerPenBean imageSpan = imageSpans.get(i);
                    int start = imageSpan.getMarkStartPosition();
                    int end = imageSpan.getMarkEndPosition();
                    markList.add(imageSpan);
                    final int position = i;
                    ClickableSpan clickableSpan = new ClickableSpan() {
                        @Override
                        public void onClick(View widget) {
                            if (mRichContentClickListener != null) {
                                mRichContentClickListener.onTextClickMark(markList, position);
                            }
                        }
                        //去除超链接下划线
                        @Override
                        public void updateDrawState(TextPaint ds) {
                            /**set textColor**/
//                    ds.setColor(ds.linkColor);
                            /**Remove the underline**/
                            ds.setUnderlineText(false);
                        }
                    };
                    //超文本点击事件
                    if (end > 0 && mSpannableStringBuilder.length() >= end) {
                        mSpannableStringBuilder.setSpan(clickableSpan, start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
                    }
                }
            }
        }catch (Exception e){
            LogUtils.e(">>>>>>>>>>>>>>>>","设置文字的点击事件:"+e);
        }
    }

    /**
     * 修改下划线颜色
     */
    public void updateUnlineColor(final int color, int startPosition, int endPosition) {
        if (clickableSpan == null) {
            clickableSpan = new ClickableSpan() {
                @Override
                public void onClick(View widget) {
                }

                @Override
                public void updateDrawState(TextPaint ds) {
                    /**set textColor**/
                    ds.setColor(getColor_(color));
                    /**Remove the underline**/
                    ds.setUnderlineText(true);
                }
            };

        }
        if (mSpannable == null) {
            super.setText(getText(), BufferType.SPANNABLE);
            CharSequence charSequence = getText();
            if (charSequence instanceof Spannable) {
                mSpannable = (Spannable) charSequence;
            }
        }
        //增加超文本点击事件
        mSpannable.setSpan(clickableSpan, startPosition, endPosition, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
    }

    public void removeClickableSpan(int start, int end) {
        ClickableSpan[] clickableSpans = mSpannableStringBuilder.getSpans(start, end, ClickableSpan.class);
        if (clickableSpans != null && clickableSpans.length != 0) {
            for (ClickableSpan cs : clickableSpans) {
                mSpannableStringBuilder.removeSpan(cs);
            }
        }
        //super.setTag(getText());
    }

    public void onDestroy() {
        if (mURLImageGetter != null) {
            mURLImageGetter.clear();
            mURLImageGetter = null;
        }
        this.setText(null);
    }

    /**
     * 设置图片监听事件
     */
    public void setRichContentClickListener(OnRichContentClickListener mRichContentClickListener) {
        this.mRichContentClickListener = mRichContentClickListener;
    }

    public interface OnRichContentClickListener {
        /**
         * 图片被点击后的回调方法
         * @param imageUrls 本篇富文本内容里的全部图片
         * @param position  点击处图片在imageUrls中的位置
         */
        void onImageClick(ArrayList imageUrls, int position);

        /**
         * 文字被点击后的回调方法
         * @param textStrs
         * @param position
         */
        void onTextClick(List textStrs, int position);

        /**
         * 文字被点击后的回调方法
         * @param textStrs
         * @param position
         */
        void onTextClickMark(List textStrs, int position);

        void onTextClickAnalysis(List textStrs, int position);

    }

    /**
     * 图片标签
     * @param imgUrl
     * @return
     */
    public String getImgLabel(String imgUrl) {
        return FenJRichTextViewHelper.getImgLabel(imgUrl);
    }

    /**
     * 马克笔标签
     * @param marker 文字背景颜色
     * @return
     */
    public String getTextMarker(String color, String marker) {
        return mFenJRichTextViewHelper.getTextMarker(color, marker);
    }

    /**
     * 封装获取颜色
     */
    public int getColor_(int colorId) {
        return ContextCompat.getColor(getContext(), colorId);
    }

    private class ColorUnderlineSpan extends UnderlineSpan {
        private int underlineColor;

        public ColorUnderlineSpan(int underlineColor) {
            super();
            this.underlineColor = underlineColor;
        }

        @Override
        public void updateDrawState(TextPaint ds) {
            super.updateDrawState(ds);
//            ds.setColor(underlineColor);
//            ds.linkColor = underlineColor;
        }
    }

    /**
     * @param content
     * @param resId
     * @param vipW
     * @param vipH
     */
    public void setIconText(String content, int resId, int vipW, int vipH) {
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), resId);
        Bitmap localB = zoomImg(bitmap,dip2px(getContext(), bitmap.getWidth()),dip2px(getContext(), bitmap.getHeight()));
        CenterAlignImageSpan imgSpan = new CenterAlignImageSpan(localB);
        SpannableString spanString = new SpannableString("V");
        spanString.setSpan(imgSpan, 0, "V".length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        super.setText(spanString);
        append(content);
    }

    private Drawable getDrawable(int resId) {
        return ContextCompat.getDrawable(getContext(), resId);
    }

    public Html.ImageGetter getImageGetterInstance(final CharSequence content) {
        Html.ImageGetter imgGetter = new Html.ImageGetter() {
            @Override
            public Drawable getDrawable(String source) {
                int id = Integer.parseInt(source);
                int hdp = dip2px(getContext(), 19);
                int wdp = dip2px(getContext(), 42);
                Drawable drawable = new TextDrawable(getContext(),  source,hdp,10);
//                int hdp = drawable.getIntrinsicWidth();
                drawable.setBounds(0, 0, wdp, hdp);
                return drawable;

            }
        };
        return imgGetter;
    }

    public static int dip2px(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    public void clearAllMarkerPen() {
        if (mSpannableStringBuilder != null) {
            mSpannableStringBuilder.clearSpans();
        }
    }

    public void clear() {
        clearAllMarkerPen();
        if (mSpannableStringBuilder != null) {
            mSpannableStringBuilder.clear();
        }
    }

    public class CenterAlignImageSpan extends ImageSpan {

        public CenterAlignImageSpan(Drawable drawable) {
            super(drawable);

        }

        public CenterAlignImageSpan(Bitmap b) {
            super(b);
        }

        @Override
        public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x,
                         int top, int y, int bottom, @NonNull Paint paint) {

            Drawable b = getDrawable();
            Paint.FontMetricsInt fm = paint.getFontMetricsInt();
            int transY = (y + fm.descent + y + fm.ascent) / 2 - b.getBounds().bottom / 2;//计算y方向的位移
            canvas.save();
            canvas.translate(x, transY);//绘制图片位移一段距离
            b.draw(canvas);
            canvas.restore();
        }
    }

    /**
     * 放大图片
     * @param bm
     * @param newWidth
     * @param newHeight
     * @return
     */
    public Bitmap zoomImg(Bitmap bm, int newWidth ,int newHeight){
        // 获得图片的宽高   
        int width = bm.getWidth();
        int height = bm.getHeight();
        // 计算缩放比例   
        float scaleWidth = ((float) newWidth) / width;
        float scaleHeight = ((float) newHeight) / height;
        // 取得想要缩放的matrix参数   
        Matrix matrix = new Matrix();
        matrix.postScale(scaleWidth, scaleHeight);
        // 得到新的图片   www.2cto.com
        Bitmap newbm = Bitmap.createBitmap(bm, 0, 0, width, height, matrix, true);
        return newbm;
    }
}

你可能感兴趣的:(安卓技术分享,安卓富文本,安卓图片和文字,富文本,TextView)