Textview解决换行混乱问题

首先感谢MTextView.java的原作者。由于找不到原作者的博客地址。我就贴下,我下载的源码的url:http://download.csdn.net/detail/yellowcath/7421147

然后,在这个MTextView.java的基础上进行了修改,增加了Html的颜色功能。

xxx

下面是源码!

import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.text.Html;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.ForegroundColorSpan;
import android.text.style.ImageSpan;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.widget.TextView;

/**
 * @功能 图文混排TextView,请使用{@link #setMText(CharSequence)}
 * @author huangwei
 * @2014年5月27日
 * @下午5:29:27
 */
public class MTextView extends TextView {

    private static final String TAG = "MTextView";

    private Context context;
    /**
     * 用于测量字符宽度
     */
    private Paint paint = new Paint();

    private int textColor = Color.BLACK;

    //行距
    private float lineSpacing;
    private int lineSpacingDP = 2;

//  private float lineSpacingMult = 0.5f;

    /**
     * 最大宽度
     */
    private int maxWidth;
    /**
     * 只有一行时的宽度
     */
    private int oneLineWidth = -1;
    /**
     * 已绘的行中最宽的一行的宽度
     */
    private float lineWidthMax = -1;
    /**
     * 存储当前文本内容,每个item为一个字符或者一个ImageSpan
     */
    private ArrayList<Object> obList = new ArrayList<Object>();
    /**
     * 是否使用默认{@link TextView#onMeasure(int, int)}和{@link TextView#onDraw(Canvas)}
     */
    private boolean useDefault = false;
    /**
     * 存储当前文本内容,每个item为一行
     */
    ArrayList<LINE> contentList = new ArrayList<LINE>();
    /**
     * 缓存测量过的数据
     */
    private static HashMap<String, SoftReference<MeasuredData>> measuredData = new HashMap<String,  SoftReference<MeasuredData>>();
    private static int hashIndex = 0;
    private CharSequence text = "";
    /**
     * 最小高度
     */
    private int minHeight;
    /**
     * 用以获取屏幕高宽
     */
    private DisplayMetrics displayMetrics;

    public MTextView(Context context) {
        super(context);
        init(context);
    }

    public MTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public MTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public MTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(context);
    }

    private void init(Context context){
        this.context = context;
        paint.setAntiAlias(true);
        lineSpacing = dip2px(context, lineSpacingDP);
        minHeight = dip2px(context, 30);
        displayMetrics = new DisplayMetrics();
    }

    @Override
    public void setMaxWidth(int maxpixels) {
        super.setMaxWidth(maxpixels);
        maxWidth = maxpixels;
    }

    @Override
    public void setMinHeight(int minHeight)
    {
        super.setMinHeight(minHeight);
        this.minHeight = minHeight;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
        if (useDefault){
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            return;
        }

        int width = 0, height = 0;
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        switch (widthMode){
            case MeasureSpec.EXACTLY:
                width = widthSize;
                break;
            case MeasureSpec.AT_MOST:
                width = widthSize;
                break;
            case MeasureSpec.UNSPECIFIED:
                ((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
                width = displayMetrics.widthPixels;
                break;
            default:
                break;
        }
        if (maxWidth > 0) {
            width = Math.min(width, maxWidth);
        }
        paint.setTextSize(this.getTextSize());
        paint.setColor(textColor);
        int realHeight = measureContentHeight((int) width);

        //如果实际行宽少于预定的宽度,减少行宽以使其内容横向居中
        int leftPadding = getCompoundPaddingLeft();
        int rightPadding = getCompoundPaddingRight();
        width = Math.min(width, (int) lineWidthMax + leftPadding+ rightPadding);
        if (oneLineWidth > -1) {
            width = oneLineWidth;
        }
        switch (heightMode) {
            case MeasureSpec.EXACTLY:
                height = heightSize;
                break;
            case MeasureSpec.AT_MOST:
                height = realHeight;
                break;
            case MeasureSpec.UNSPECIFIED:
                height = realHeight;
                break;
            default:
                break;
        }
        height += getCompoundPaddingTop() + getCompoundPaddingBottom();
        height = Math.max(height,minHeight);
        setMeasuredDimension(width, height);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        invalidate();
        if (useDefault){
            super.onDraw(canvas);
            return;
        }
        int width;
        Object ob;
        int leftPadding = getCompoundPaddingLeft();
        int topPadding = getCompoundPaddingTop();
        float height = 0 + topPadding + lineSpacing;
        //只有一行时
        if(oneLineWidth != -1){
            height = getMeasuredHeight() /2 - contentList.get(0).height/2;
        }

        for (int i = 0; i < contentList.size(); i++){
            //绘制一行
            float realDrawedWidth = 0 + leftPadding;
            LINE line = contentList.get(i);
            for (int j = 0; j < line.line.size(); j++){
                ob = line.line.get(j);
                width = line.widthList.get(j);
                if (ob instanceof ImageSpan){
                    ImageSpan is = (ImageSpan) ob;
                    Drawable d = is.getDrawable();
                    int left = (int) (realDrawedWidth);
                    int top = (int) height;
                    int right = (int) (realDrawedWidth + width);
                    int bottom = (int) (height + line.height);
                    d.setBounds(left, top, right, bottom);
                    d.draw(canvas);
                    realDrawedWidth += width;
                }else if (ob instanceof String || ob instanceof StringWithColor){
                    String textStr = ob.toString();
                    paint.setColor(textColor);
                    if(ob instanceof StringWithColor){
                        paint.setColor(((StringWithColor)ob).color);
                    }
                    canvas.drawText(textStr, realDrawedWidth, height + line.height, paint);
                    realDrawedWidth += width;
                }
            }
            height += line.height + lineSpacing;
        }
    }

    @Override
    public void setTextColor(int color){
        super.setTextColor(color);
        textColor = color;
    }

    /**
     * 用于带ImageSpan的文本内容所占高度测量
     * @param width 预定的宽度
     * @return 所需的高度
     */
    private int measureContentHeight(int width) {
        int cachedHeight = getCachedData(text.toString(), width);

        if(cachedHeight > 0) {
            return cachedHeight;
        }

        // 已绘的宽度
        float obWidth = 0;
        float obHeight = 0;

        float textSize = this.getTextSize();
        //行高
        float lineHeight = textSize;
        //计算出的所需高度
        float height = lineSpacing;

        int leftPadding = getCompoundPaddingLeft();
        int rightPadding = getCompoundPaddingRight();

        float drawedWidth = 0;

        width = width - leftPadding - rightPadding;
        oneLineWidth = -1;
        contentList.clear();
        StringBuilder sb;
        LINE line = new LINE();
        for (int i = 0; i < obList.size(); i++) {
            Object ob = obList.get(i);
            if (ob instanceof ImageSpan) {
                Rect r = ((ImageSpan) ob).getDrawable().getBounds();
                obWidth = r.right - r.left;
                obHeight = r.bottom - r.top;
                if (obHeight > lineHeight) {
                    lineHeight = obHeight;
                }
            }else if (ob instanceof String) {
                obWidth = paint.measureText((String) ob);
                obHeight = textSize;
            }else if(ob instanceof StringWithColor){
                obWidth = paint.measureText( ((StringWithColor) ob).str );
                obHeight = textSize;
            }

            //这一行满了,存入contentList,新起一行
            if (width - drawedWidth < obWidth) {
                contentList.add(line);
                if (drawedWidth > lineWidthMax){
                    lineWidthMax = drawedWidth;
                }
                drawedWidth = 0;
                height += line.height + lineSpacing;
                lineHeight = obHeight;
                line = new LINE();
            }

            drawedWidth += obWidth;

            if ((ob instanceof String || ob instanceof StringWithColor)
                    && line.line.size() > 0
                    && (line.line.get(line.line.size() - 1) instanceof String)){
                if(ob instanceof StringWithColor){
                    ob = ob.toString();
                }
                int size = line.line.size();
                sb = new StringBuilder();
                sb.append(line.line.get(size - 1));
                sb.append(ob);
                ob = sb.toString();
                obWidth = obWidth + line.widthList.get(size - 1);
                line.line.set(size - 1, ob);
                line.widthList.set(size - 1, (int) obWidth);
                line.height = (int) lineHeight;
            }else{
                line.line.add(ob);
                line.widthList.add((int) obWidth);
                line.height = (int) lineHeight;
            }

        }
        if (line != null && line.line.size() > 0) {
            contentList.add(line);
            height += lineHeight + lineSpacing;
        }
        if (contentList.size() <= 1) {
            oneLineWidth = (int) drawedWidth + leftPadding + rightPadding;
            height = lineSpacing + lineHeight + lineSpacing;
        }
        cacheData(width,(int) height);
        return (int) height;
    }
    /**
     * 获取缓存的测量数据,避免多次重复测量
     * @param text
     * @param width
     * @return height
     */
    @SuppressWarnings("unchecked")
    private int getCachedData(String text, int width){
        SoftReference<MeasuredData> cache = measuredData.get(text);
        if(cache == null) {
            return -1;
        }
        MeasuredData md = cache.get();
        if (md != null && md.textSize == this.getTextSize() && width == md.width) {
            lineWidthMax = md.lineWidthMax;
            contentList = (ArrayList<LINE>) md.contentList.clone();
            oneLineWidth = md.oneLineWidth;
            StringBuilder sb = new StringBuilder();
            for(int i=0;i<contentList.size();i++) {
                LINE line = contentList.get(i);
                sb.append(line.toString());
            }
            return md.measuredHeight;
        }else {
            return -1;
        }
    }

    /**
     * 缓存已测量的数据
     * @param width
     * @param height
     */
    @SuppressWarnings("unchecked")
    private void cacheData(int width, int height)
    {
        MeasuredData md = new MeasuredData();
        md.contentList = (ArrayList<LINE>) contentList.clone();
        md.textSize = this.getTextSize();
        md.lineWidthMax = lineWidthMax;
        md.oneLineWidth = oneLineWidth;
        md.measuredHeight = height;
        md.width = width;
        md.hashIndex = ++hashIndex;

        StringBuilder sb = new StringBuilder();
        for(int i=0;i<contentList.size();i++) {
            LINE line = contentList.get(i);
            sb.append(line.toString());
        }

        SoftReference<MeasuredData> cache = new SoftReference<MeasuredData>(md);
        measuredData.put(text.toString(),cache);
    }
    /**
     * 用本函数代替{@link #setText(CharSequence)}
     * @param textStr
     */
    public void setMText(CharSequence textStr) {
        text = textStr;
        obList.clear();
        //  contentList.clear();
        ArrayList<SpanBean> isList = new ArrayList<>();
        useDefault = false;
        if (textStr instanceof SpannableString) {
            SpannableString ss = (SpannableString) textStr;
            ForegroundColorSpan[] spanneds = ss.getSpans(0, ss.length(), ForegroundColorSpan.class);
            for (int i = 0; i < spanneds.length; i++) {
                int s = ss.getSpanStart(spanneds[i]);
                int e = ss.getSpanEnd(spanneds[i]);
                ForegroundColorSpanBean spanBean = new ForegroundColorSpanBean();
                spanBean.fcspan = spanneds[i];
                spanBean.start = s;
                spanBean.end = e;
                isList.add(spanBean);
            }
            ImageSpan[] imageSpans = ss.getSpans(0, ss.length(), ImageSpan.class);
            for (int i = 0; i < imageSpans.length; i++) {
                int s = ss.getSpanStart(imageSpans[i]);
                int e = ss.getSpanEnd(imageSpans[i]);
                IS iS = new IS();
                iS.is = imageSpans[i];
                iS.start = s;
                iS.end = e;
                isList.add(iS);
            }
        }

        //对isList重新排序,按start最小的开始排序
        if(isList.size() > 1){
            for(int i = 0 ; i < isList.size()-1;i++){
                ///
                for(int j = (i+1) ; j < isList.size();j++){
                    SpanBean temp01 = isList.get(i);
                    SpanBean temp02 = isList.get(j);
                    if(temp01.start > temp02.start){
                        isList.set(i,temp02);
                        isList.set(j,temp01);
                    }
                }
                ///
            }
        }

        String str = textStr.toString();

        for (int i = 0, j = 0; i < textStr.length();) {
            if (j < isList.size()) {
                SpanBean spanBean = isList.get(j);
                if (i < spanBean.start) {
                    Integer cp = str.codePointAt(i);
                    //支持增补字符
                    if (Character.isSupplementaryCodePoint(cp)) {
                        i += 2;
                    }else {
                        i++;
                    }
                    obList.add(new String(Character.toChars(cp)));
                }else if (i >= spanBean.start) {
                    if(spanBean instanceof IS){
                        obList.add(((IS)spanBean).is);
                        j++;
                        i = spanBean.end;
                    }else if(spanBean instanceof ForegroundColorSpanBean){
                        Integer cp = str.codePointAt(i);
                        if (Character.isSupplementaryCodePoint(cp)){
                            i += 2;
                        }else{
                            i++;
                        }
                        obList.add(new StringWithColor(new String(Character.toChars(cp)),
                                ((ForegroundColorSpanBean)spanBean).fcspan.getForegroundColor()));
                        if(i >= spanBean.end){
                            j++;
                        }
                    }else{
                        Log.e(TAG , "--- geolo 异常情况,需要排查 ---");
                    }
                }
            }else{
                Integer cp = str.codePointAt(i);
                if (Character.isSupplementaryCodePoint(cp)){
                    i += 2;
                }else{
                    i++;
                }
                obList.add(new String(Character.toChars(cp)));
            }
        }
        requestLayout();
    }

    public void setUseDefault(boolean useDefault) {
        this.useDefault = useDefault;
        if (useDefault) {
            this.setText(text);
            this.setTextColor(textColor);
        }
    }

    public static int px2sp(Context context, float pxValue)
    {
        final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
        return (int) (pxValue / fontScale + 0.5f);
    }

    /**
     * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
     */
    public static int dip2px(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    /**
     * @功能: 所有Span的结构体
     */
    class SpanBean{
        public int start;
        public int end;
    }

    /**
     * @功能: 存储ImageSpan及其开始结束位置
     */
    class IS extends SpanBean{
        public ImageSpan is;
    }

    /**
     * @功能: 存储ForegroundColorSpan及其开始结束位置
     * 带颜色的字符串(Html)
     */
    class ForegroundColorSpanBean extends SpanBean{
        public ForegroundColorSpan fcspan;
        public String str;
    }

    /**
     * @功能: 包含颜色值的字符串
     */
    class StringWithColor{
        int color = 0;
        String str;
        StringWithColor(String str , int color){
            this.str = str;
            this.color = color;
        }

        @Override
        public String toString() {
            return str;
        }
    }

    /**
     * @功能: 存储测量好的一行数据
     * @author huangwei
     * @2014年5月27日
     * @下午5:22:12
     */
    class LINE {
        public ArrayList<Object> line = new ArrayList<Object>();
        public ArrayList<Integer> widthList = new ArrayList<Integer>();
        public int height;
        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder("height:"+height+"   ");
            for(int i=0;i<line.size();i++) {
                sb.append(line.get(i)+":"+widthList.get(i));
            }
            return sb.toString();
        }


    }
    /**
     * @功能: 缓存的数据
     * @author huangwei
     * @2014年5月27日
     * @下午5:22:25
     */
    class MeasuredData {
        public int measuredHeight;
        public float textSize;
        public int width;
        public float lineWidthMax;
        ArrayList<LINE> contentList;
        public int oneLineWidth;
        public int hashIndex;
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值