Android自定义富文本控件

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Parcel;
import android.support.annotation.IntDef;
import android.support.annotation.IntRange;
import android.support.annotation.Nullable;
import android.text.Html;
import android.text.Layout;
import android.text.Selection;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.text.method.Touch;
import android.text.style.ClickableSpan;
import android.text.style.ImageSpan;
import android.text.style.URLSpan;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;

import com.bumptech.glide.load.resource.drawable.GlideDrawable;
import com.bumptech.glide.request.Request;
import com.bumptech.glide.request.animation.GlideAnimation;
import com.bumptech.glide.request.target.SimpleTarget;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import pl.droidsonroids.gif.GifDrawable;

public class RichTextView extends GifSpanTextView implements Drawable.Callback, View.OnAttachStateChangeListener {

private static Pattern IMAGE_TAG_PATTERN = Pattern.compile("\\<img(.*?)\\>");
private static Pattern IMAGE_WIDTH_PATTERN = Pattern.compile("width=\"(.*?)\"");
private static Pattern IMAGE_HEIGHT_PATTERN = Pattern.compile("height=\"(.*?)\"");
private static Pattern IMAGE_SRC_PATTERN = Pattern.compile("src=\"(.*?)\"");
private static Pattern IMAGE_BIG_SRC_PATTERN = Pattern.compile("bigsrc=\"(.*?)\"");
private static Pattern IMAGE_HREF_PATTERN = Pattern.compile("href=\"(.*?)\"");

private Drawable placeHolder, errorImage;//占位图,错误图
private OnImageClickListener onImageClickListener;//图片点击回调
private OnURLClickListener onURLClickListener;//超链接点击回调
//    private HashSet<Target> targets;

// private HashSet targets;
private HashMap mImages;
private HashMap mBigImages;
private ImageFixListener mImageFixListener;
private int d_w = 200;
private int d_h = 100;
private boolean isScroll = true;

private String imageHost;
private boolean dontConsumeNonUrlClicks = true;
private boolean linkHit;
private Context mContext;
private GlideImageGeter glideImageGeter;

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

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

public RichTextView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);

    init(context, attrs);
}

private void init(Context context, AttributeSet attrs) {
    mContext = context;
 //   targets = new HashSet<>();
    TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RichTextView);
    placeHolder = typedArray.getDrawable(R.styleable.RichTextView_placeHolder);
    errorImage = typedArray.getDrawable(R.styleable.RichTextView_errorImage);

    d_w = typedArray.getDimensionPixelSize(R.styleable.RichTextView_defaultWidth, Utils.Dp2Px(context, d_w));
    d_h = typedArray.getDimensionPixelSize(R.styleable.RichTextView_defaultHeight, Utils.Dp2Px(context, d_h));

    isScroll = typedArray.getBoolean(R.styleable.RichTextView_isScroll, isScroll);

    if (placeHolder == null) {
        placeHolder = new ColorDrawable(Color.GRAY);
    }
    placeHolder.setBounds(0, 0, d_w, d_h);
    if (errorImage == null) {
        errorImage = new ColorDrawable(Color.GRAY);
    }
    errorImage.setBounds(0, 0, d_w, d_h);
    typedArray.recycle();
}


/**
 * 设置富文本
 *
 * @param text 富文本
 */
public void setRichText(String text) {
    try {
        glideImageGeter = new GlideImageGeter(getContext(), this);
     //   targets.clear();
        matchImages(text);

        if (text != null) {
            text = text.replaceAll(" ", "\t");  //替换空格
        }
        Spanned spanned = Html.fromHtml(text, glideImageGeter, null);
        SpannableStringBuilder spannableStringBuilder;
        if (spanned instanceof SpannableStringBuilder) {
            spannableStringBuilder = (SpannableStringBuilder) spanned;
        } else {
            spannableStringBuilder = new SpannableStringBuilder(spanned);
        }

        // 处理图片得点击事件
        ImageSpan[] imageSpans = spannableStringBuilder.getSpans(0, spannableStringBuilder.length(), ImageSpan.class);
        final List<String> imageUrls = new ArrayList<>();

        for (int i = 0, size = imageSpans.length; i < size; i++) {
            ImageSpan imageSpan = imageSpans[i];
            String imageUrl = imageSpan.getSource();
            String bigImageUrl = mBigImages.get(imageUrl);
            if (!TextUtils.isEmpty(bigImageUrl)) {
                imageUrl = bigImageUrl;
            }
            int start = spannableStringBuilder.getSpanStart(imageSpan);
            int end = spannableStringBuilder.getSpanEnd(imageSpan);
            imageUrl = imageUrl.startsWith("http") ? imageUrl : imageHost + imageUrl;
            if (imageUrl.endsWith(".gif") || imageUrl.endsWith(".bmp") || imageUrl.endsWith(".jpg") || imageUrl.endsWith(".jpeg") || imageUrl.endsWith(".JPG") || imageUrl.endsWith(".JPEG") || imageUrl.endsWith(".png") || imageUrl.endsWith(".PNG")) {
                imageUrls.add(imageUrl);
            }
            final int finalI = i;
            ClickableSpan clickableSpan = new ClickableSpan() {
                @Override
                public void onClick(View widget) {
                    if (onImageClickListener != null) {
                        onImageClickListener.imageClicked(imageUrls, finalI);
                    }
                }
            };
            ClickableSpan[] clickableSpans = spannableStringBuilder.getSpans(start, end, ClickableSpan.class);
            if (clickableSpans != null && clickableSpans.length != 0) {
                for (ClickableSpan cs : clickableSpans) {
                    spannableStringBuilder.removeSpan(cs);
                }
            }
            spannableStringBuilder.setSpan(clickableSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        }

        // 处理超链接点击事件
        URLSpan[] urlSpans = spannableStringBuilder.getSpans(0, spannableStringBuilder.length(), URLSpan.class);

        for (int i = 0, size = urlSpans == null ? 0 : urlSpans.length; i < size; i++) {
            URLSpan urlSpan = urlSpans[i];

            int start = spannableStringBuilder.getSpanStart(urlSpan);
            int end = spannableStringBuilder.getSpanEnd(urlSpan);

            spannableStringBuilder.removeSpan(urlSpan);
            spannableStringBuilder.setSpan(new CallableURLSpan(urlSpan.getURL(), onURLClickListener), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        }

        //表情转义
        final Matcher matcher = EmojiHelper.getInstance().getEmojiMatcher(spanned.toString());

        while (matcher.find()) {
            final String name = matcher.group(1);

            final GifDrawable drawable = EmojiHelper.getInstance().getGifDrawable(name);
            if (drawable != null) {
                final ImageSpan gifSpan = new GifImageSpan(drawable);
                spannableStringBuilder.setSpan(gifSpan, matcher.start(), matcher.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
        }

        super.setText(spanned);
        if (isScroll) {
            setMovementMethod(LocalLinkMovementMethod.getInstance());    //解决ListView里TextView设置LinkMovementMethod后导致其ItemClick失效的问题
        }
        setLongClickable(false);    //屏蔽长按事件,防止部分手机Spannable长按时崩溃
    } catch (Exception ex) {
        ex.printStackTrace();
    }
}

/*
private void addTarget(ImageTarget target) {
targets.add(target);
}
/ /*
* 从文本中拿到标签,并获取图片url和宽高
*/
private void matchImages(String text) {
mImages = new HashMap<>();
mBigImages = new HashMap<>();
ImageHolder holder;
Matcher imageMatcher, srcMatcher, bigSrcMatcher, widthMatcher, heightMatcher, hrefMatcher;
int position = 0;
hrefMatcher = IMAGE_HREF_PATTERN.matcher(text);
String hrefImageUrl = null;
if (hrefMatcher.find()) {
hrefImageUrl = getTextBetweenQuotation(hrefMatcher.group().trim().substring(4));
}
imageMatcher = IMAGE_TAG_PATTERN.matcher(text);
while (imageMatcher.find()) {
String image = imageMatcher.group().trim();
srcMatcher = IMAGE_SRC_PATTERN.matcher(image);
String src = null;
if (srcMatcher.find()) {
src = getTextBetweenQuotation(srcMatcher.group().trim().substring(4));
}
if (TextUtils.isEmpty(src)) {
continue;
}
bigSrcMatcher = IMAGE_BIG_SRC_PATTERN.matcher(image);
String bigSrc = null;
if (bigSrcMatcher.find()) {
bigSrc = getTextBetweenQuotation(bigSrcMatcher.group().trim().substring(4));
}
if (TextUtils.isEmpty(bigSrc)) {
if (!TextUtils.isEmpty(hrefImageUrl)) {
bigSrc = hrefImageUrl;
} else {
bigSrc = src;
}
}

        holder = new ImageHolder(src, bigSrc, position);
        widthMatcher = IMAGE_WIDTH_PATTERN.matcher(image);
        if (widthMatcher.find()) {
            holder.width = parseStringToInteger(getTextBetweenQuotation(widthMatcher.group().trim().substring(6)));
        }

        heightMatcher = IMAGE_HEIGHT_PATTERN.matcher(image);
        if (heightMatcher.find()) {
            holder.height = parseStringToInteger(getTextBetweenQuotation(heightMatcher.group().trim().substring(6)));
        }

        mImages.put(holder.src, holder);
        mBigImages.put(holder.src, holder.bigSrc);
        position++;
    }
}

private int parseStringToInteger(String integerStr) {
    int result = -1;
    if (!TextUtils.isEmpty(integerStr)) {
        try {
            result = Integer.parseInt(integerStr);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    return result;
}

/**
 * 从双引号之间取出字符串
 */
@Nullable
private static String getTextBetweenQuotation(String text) {
    Pattern pattern = Pattern.compile("\"(.*?)\"");
    Matcher matcher = pattern.matcher(text);
    if (matcher.find()) {
        return matcher.group(1);
    }
    return null;
}

  


@Override
public boolean onTouchEvent(MotionEvent event) {
linkHit = false;
boolean res = super.onTouchEvent(event);

    if (dontConsumeNonUrlClicks)
        return linkHit;
    return res;

}

public static class LocalLinkMovementMethod extends LinkMovementMethod {
    static LocalLinkMovementMethod sInstance;


    public static LocalLinkMovementMethod getInstance() {
        if (sInstance == null)
            sInstance = new LocalLinkMovementMethod();

        return sInstance;
    }


    @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);

            if (link.length != 0) {
                if (action == MotionEvent.ACTION_UP) {
                    link[0].onClick(widget);
                } else if (action == MotionEvent.ACTION_DOWN) {
                    Selection.setSelection(buffer,
                            buffer.getSpanStart(link[0]),
                            buffer.getSpanEnd(link[0]));
                }

                if (widget instanceof RichTextView) {
                    ((RichTextView) widget).linkHit = true;
                }
                return true;

            } else {
                Selection.removeSelection(buffer);
                Touch.onTouchEvent(widget, buffer, event);
                return false;
            }
        }
        return Touch.onTouchEvent(widget, buffer, event);
    }
}


private static final class URLDrawable extends Drawable {
    private Drawable drawable;

    @Override
    public void setAlpha(@IntRange(from = 0, to = 255) int alpha) {

    }

    @Override
    public void setColorFilter(@Nullable ColorFilter colorFilter) {

    }

    @Override
    public int getOpacity() {
        return 0;
    }

    @Override
    public void draw(Canvas canvas) {
        if (drawable != null)
            drawable.draw(canvas);
    }

    public void setDrawable(Drawable drawable) {
        this.drawable = drawable;
    }
}

private static class CallableURLSpan extends URLSpan {

    private OnURLClickListener onURLClickListener;

    public CallableURLSpan(String url, OnURLClickListener onURLClickListener) {
        super(url);
        this.onURLClickListener = onURLClickListener;
    }

    @SuppressWarnings("unused")
    public CallableURLSpan(Parcel src, OnURLClickListener onURLClickListener) {
        super(src);
        this.onURLClickListener = onURLClickListener;
    }

    @Override
    public void onClick(View widget) {
        if (onURLClickListener != null && onURLClickListener.urlClicked(getURL())) {
            return;
        }
        super.onClick(widget);
    }
}

public static class ImageHolder {
    public static final int DEFAULT = 0;
    public static final int CENTER_CROP = 1;
    public static final int CENTER_INSIDE = 2;

    @IntDef({DEFAULT, CENTER_CROP, CENTER_INSIDE})
    public @interface ScaleType {
    }

    private final String src;
    private final String bigSrc;
    private final int position;
    private int width = -1, height = -1;
    private int scaleType = DEFAULT;

    public ImageHolder(String src, String bigSrc, int position) {
        this.src = src;
        this.bigSrc = bigSrc;
        this.position = position;
    }

    public String getSrcUrl() {
        return src;
    }

    public String getBigSrcUrl() {
        return bigSrc;
    }

    public int getHeight() {
        return height;
    }

    public int getWidth() {
        return width;
    }

    public void setWidth(int width) {
        this.width = width;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    @ScaleType
    public int getScaleType() {
        return scaleType;
    }

    public void setScaleType(@ScaleType int scaleType) {
        this.scaleType = scaleType;
    }
}

@SuppressWarnings("unused")
public ImageHolder getImageHolder(String url) {
    return mImages.get(url);
}

@SuppressWarnings("unused")
public void setPlaceHolder(Drawable placeHolder) {
    this.placeHolder = placeHolder;
    this.placeHolder.setBounds(0, 0, d_w, d_h);
}

@SuppressWarnings("unused")
public void setErrorImage(Drawable errorImage) {
    this.errorImage = errorImage;
    this.errorImage.setBounds(0, 0, d_w, d_h);
}

public void setOnImageClickListener(OnImageClickListener onImageClickListener) {
    this.onImageClickListener = onImageClickListener;
}

public void setImageFixListener(ImageFixListener mImageFixListener) {
    this.mImageFixListener = mImageFixListener;
}

/**
 * 设置超链接点击回调事件(需在setRichText方法之前调用)
 *
 * @param onURLClickListener 回调
 */
public void setOnURLClickListener(OnURLClickListener onURLClickListener) {
    this.onURLClickListener = onURLClickListener;
}

@Override
public void onViewAttachedToWindow(View v) {

}

@Override
public void onViewDetachedFromWindow(View v) {
    glideImageGeter.recycle();
}

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

public interface OnURLClickListener {

    /**
     * 超链接点击得回调方法
     *
     * @param url 点击得url
     * @return true:已处理,false:未处理(会进行默认处理)
     */
    boolean urlClicked(String url);
}

public interface ImageFixListener {
    /**
     * 修复图片尺寸的方法
     *
     * @param holder ImageHolder对象
     */
    void onFix(ImageHolder holder);

}

public void configure(String imageHostPrefix) {
    imageHost = imageHostPrefix;
}

}

关注公众号“大模型全栈程序员”回复“小程序”获取1000个小程序打包源码。更多免费资源在http://www.gitweixin.com/?p=2627

发表评论

邮箱地址不会被公开。 必填项已用*标注