【CustomView】Android MTextView 以解决提前换行,并配置结尾样式

 

TextView在换行时常常会出现参差不齐的情况,MTextView就是为了解决这样的问题而来的。 

Update @2020/06/21:(感谢评论区)

1.解决评论反馈bug;
2.优化简化自定义MTextView;
 

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;

import com.example.mtestone.R;

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class MTextView extends android.support.v7.widget.AppCompatTextView {
    private TextPaint mPaint;
    private Layout mLayout;
    // 行高
    private int mTextHeight;
    // 当前行对应对纵坐标
    private float mLineY;
    // view的内容宽度
    private int mContentWidth;

    //结尾样式
    private String ellipsizeEnd = "";
    // 结尾样式的内容宽度
    private float ellipsizeEndWidth;
    private boolean isToEllipsize = false;


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

    private void init(Context context, AttributeSet attrs) {

        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MTextView);
        ellipsizeEnd = ta.getString(ta.getIndex(R.styleable.MTextView_ellipsize_end));
        ta.recycle();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        mPaint = getPaint();
        mPaint.setColor(getCurrentTextColor());
        mPaint.drawableState = getDrawableState();

        if (getText() == null || getText().toString().isEmpty()) {
            return;
        }
        String text = getText().toString();
        mLayout = getLayout();
        // layout.getLayout()在4.4.3出现NullPointerException
        if (mLayout == null) {
            return;
        }

        Paint.FontMetrics fm = mPaint.getFontMetrics();
        mTextHeight = (int) (Math.ceil(fm.descent - fm.ascent) + getLineSpacingExtra());

        mTextHeight = (int) (mTextHeight * mLayout.getSpacingMultiplier() + mLayout.getSpacingAdd());

        mContentWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
        
        ellipsizeEndWidth = StaticLayout.getDesiredWidth(ellipsizeEnd, getPaint());

        drawSBCLines(canvas, text);
    }

    /**
     * by Finn update
     * time @2020
     */
    private void drawSBCLines(Canvas canvas, String text) {
        int line = 1;  //当前绘制行数
        mLineY = getTextSize() + getPaddingTop();
        float x = getPaddingLeft();
        for (int i = 0; i < text.length(); i++) {
            String c = String.valueOf(text.charAt(i));
            float cw = StaticLayout.getDesiredWidth(c, getPaint());
            
            if (line == getMaxLines()) {
                //如果已最大行,需判断当前字符绘制上去,此时宽度是否超过行宽度(行宽度:指去除绘制结尾样式的内容)
                // 如果超过,就替换结尾的内容,并告知绘制已结束;
                if (x + cw - getLetterSpacing() >= mContentWidth - ellipsizeEndWidth) {
                    c = ellipsizeEnd;//替换结尾字符部分
                    isToEllipsize = true;
                }
            }
            canvas.drawText(c, x, mLineY, getPaint());
            x += cw + getLetterSpacing();
            if (x + cw - getLetterSpacing() > mContentWidth + getPaddingLeft() || c.equals("\n")) {
                line++;
                mLineY += mTextHeight;
                x = getPaddingLeft();
            }
            // 一行中的最后一个字符能挤就挤,(●'◡'●)
            else if (x + cw > mContentWidth + getPaddingLeft()
                    && x - getLetterSpacing() + cw <= mContentWidth + getPaddingLeft()) {
                x -= getLetterSpacing();
            }

            if (isToEllipsize) {
                //绘制结束
                break;
            }
        }
    }
}

添加attrs.xml:(可定义结尾内容样式)

<declare-styleable name="MTextView">
        <attr name="ellipsize_end" format="string" />
</declare-styleable>

使用参考:

<com.*.*.view.MTextView
        android:id="@+id/mtextview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:maxLines="4"
        app:ellipsize_end="---"/>
tv.setText(testStr);

感谢🙏...

 

##############################################################################################################

 

来源于参考:JustifyTextView;

由于网上找了好久,都是只解决了参差不齐的情况,并未适应设置行数限制,JustifyTextView是有继承TextView的行数限制,并未给结尾样式做处理;

闲话不说了

 

简单说明下:
//结尾样式
private String ellipStr = "...";//可以自己设置
//调用方法
MTextView.setEllipStr("....");

另外一些方法:
    // 字符间距
    public void setmCharacterSpace(float mCharacterSpace) {
        this.mCharacterSpace = mCharacterSpace;
    }
    // 行间距
    public void setmLineSpace(int mLineSpace) {
        this.mLineSpace = mLineSpace;
    }
    // 是否对齐
    public void setToAlignChars(boolean toAlignChars) {
        isToAlignChars = toAlignChars;
    }

layout:
  <com.****.****.view.MTextView
                android:id="@+id/tv_desc"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:gravity="left"
                android:maxLines="2"
                android:text="@string/test" />

//结尾样式
private String ellipStr = "...";//可以自己设置
//调用方法
MTextView.setEllipStr("....");

另外一些方法:
    // 字符间距
    public void setmCharacterSpace(float mCharacterSpace) {
        this.mCharacterSpace = mCharacterSpace;
    }
    // 行间距
    public void setmLineSpace(int mLineSpace) {
        this.mLineSpace = mLineSpace;
    }
    // 是否对齐
    public void setToAlignChars(boolean toAlignChars) {
        isToAlignChars = toAlignChars;
    }

layout:
  <com.****.****.view.MTextView
                android:id="@+id/tv_desc"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:gravity="left"
                android:maxLines="2"
                android:text="@string/test" />

 

 

 

直接上代码:

 

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.widget.TextView;

import com.ruitukeji.ruituebusiness.R;

/**
 * Created by Administrator on 2016/12/9.
 */

public class MTextView extends TextView {
    private TextPaint mPaint;
    private Layout mLayout;
    // 行高
    private int mTextHeight;
    // 纵坐标
    private float mLineY;
    // view的内容宽度
    private int mContentWidth;
    // 字符间距
    private float mCharacterSpace = 0;
    // 字符间距的一半
    private float mGapWidth;
    // 行间距
    private int mLineSpace = 0;
    // 全角字符宽度
    private float mSBCwith;
    // 是否对齐
    private boolean isToAlignChars = false;
    /**
     * @by Bugzhu修改
     * @time 2016年12月9日15:04:27
     * @action 设置属性参数
     */
    //监听行数
    private int maxLine = 0;
    //结尾样式
    private String ellipStr = "...";//可以自己设置

    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
    public MTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    /**
     * @by Bugzhu 修改
     * @time 2016年12月9日15:04:27
     * @action 设置属性参数
     */
    // 字符间距
    public void setmCharacterSpace(float mCharacterSpace) {
        this.mCharacterSpace = mCharacterSpace;
    }
    // 行间距
    public void setmLineSpace(int mLineSpace) {
        this.mLineSpace = mLineSpace;
    }
    // 是否对齐
    public void setToAlignChars(boolean toAlignChars) {
        isToAlignChars = toAlignChars;
    }
    //结尾样式
    public void setEllipStr(String ellipStr) {
        this.ellipStr = ellipStr;
    }

    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
    private void init(Context context, AttributeSet attrs) {

//        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MTextView);
//        mCharacterSpace = ta.getDimensionPixelSize(ta.getIndex(R.styleable.MTextView_character_space)
//                , 0);
//        mLineSpace = ta.getDimensionPixelSize(ta.getIndex(R.styleable.MTextView_line_space), 0);
//        isToAlignChars = ta.getBoolean(ta.getIndex(R.styleable.MTextView_align_chars), false);
//        ta.recycle();

        mGapWidth = mCharacterSpace / 2;
        String text = getText().toString();
        maxLine = getMaxLines();  //监听行数
        if (text.charAt(text.length() - 1) == 10) {
            text = text.substring(0, text.length() - 1);
            setText(text);
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        mPaint = getPaint();
        mPaint.setColor(getCurrentTextColor());
        mPaint.drawableState = getDrawableState();

        String text = getText().toString();
        mLayout = getLayout();
        // layout.getLayout()在4.4.3出现NullPointerException
        if (mLayout == null) {
            return;
        }

        Paint.FontMetrics fm = mPaint.getFontMetrics();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            mTextHeight = (int) (Math.ceil(fm.descent - fm.ascent) + getLineSpacingExtra());
        } else {
            mTextHeight = (int) (Math.ceil(fm.descent - fm.ascent) + mLineSpace);
        }
        mTextHeight = (int) (mTextHeight * mLayout.getSpacingMultiplier() + mLayout.getSpacingAdd());

        // view内容的宽度
        mContentWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
        // 全角字符宽度
        mSBCwith = getSBCwidth(text);

        if (mSBCwith > 0) {
            if (isToAlignChars) {
                drawAligmentLines(canvas, text);
            } else {
                drawSBCLines(canvas, text);
            }
        } else {
            drawDBCLines(canvas, text);
        }
    }

    private void drawSBCLines(Canvas canvas, String text) {
        int line = 1;  //当前绘制行数
        mLineY = getTextSize() + getPaddingTop();
        float x = getPaddingLeft();
        for (int i = 0; i < text.length(); i++) {
            String c = String.valueOf(text.charAt(i));
            if (c.equals(" ")) {
                continue;
            }
            float cw = StaticLayout.getDesiredWidth(c, getPaint());
            /**
             * by bugzhu添加
             * time 2016年12月9日14:46:59
             */
            if (line == maxLine){
                //判断是否为该行的倒数第二个字符(判断有些粗略)
                if (x + cw * 2 - mCharacterSpace > mContentWidth + getPaddingLeft()){
                    c = ellipStr;//替换结尾字符部分
                }
            }
            //
            canvas.drawText(c, x, mLineY, getPaint());
            x += cw + mCharacterSpace;
            if (x + cw - mCharacterSpace > mContentWidth + getPaddingLeft() || c.equals("\n")) {
                line ++;//by bugzhu添加
                mLineY += mTextHeight;
                x = getPaddingLeft();
            }
            // 一行中的最后一个字符能挤就挤,(●'◡'●)
            else if (x + cw > mContentWidth + getPaddingLeft()
                    && x - mCharacterSpace + cw <= mContentWidth + getPaddingLeft()) {
                x -= mCharacterSpace;
            }

        }
    }

    private void drawAligmentLines(Canvas canvas, String text) {
        mLineY = getTextSize() + getPaddingTop();
        float x = getPaddingLeft();
        for (int i = 0; i < text.length(); i++) {
            String c = String.valueOf(text.charAt(i));
            if (c.equals(" ")) {
                continue;
            }
            float cw = StaticLayout.getDesiredWidth(c, getPaint());
            float currentGapWidth = getGapWidth(text.charAt(i));
            canvas.drawText(c, x + currentGapWidth, mLineY, getPaint());
            if (i < text.length() - 1) {
                x += cw + 2 * currentGapWidth;
                float nextCw = StaticLayout.getDesiredWidth(String.valueOf(text.charAt(i + 1)), getPaint());
                float nextGapWidth = getGapWidth(text.charAt(i + 1));
                if (x + nextCw + 2 * nextGapWidth > mContentWidth + getPaddingLeft() || c.equals("\n")) {
                    mLineY += mTextHeight;
                    x = getPaddingLeft();
                }
            }
        }
    }

    private void drawDBCLines(Canvas canvas, String text) {
        boolean isLine = false;
        mLineY = getTextSize() + getPaddingTop();
        float x = getPaddingLeft();

        int lineCount = mLayout.getLineCount();
        if (lineCount > 1) {
            for (int i = 0; i < lineCount; i++) {
                int lineStart = mLayout.getLineStart(i);
                int lineEnd = mLayout.getLineEnd(i);
                float lineWidth = mLayout.getLineWidth(i);
                String line = text.substring(lineStart, lineEnd);
                float wordSpace = (mContentWidth - lineWidth) / (getBlankCount(line) - 1);
                for (int j = 0; j < line.length(); j++) {
                    String c = String.valueOf(line.charAt(j));
                    float cw = StaticLayout.getDesiredWidth(c, getPaint());
                    canvas.drawText(c, x, mLineY, getPaint());
                    if (i < lineCount - 1 && line.charAt(j) == 32) {
                        x += cw + wordSpace;
                    } else {
                        x += cw;
                    }
                }
                mLineY += mTextHeight;
                x = getPaddingLeft();
            }
        } else {
            int lineStart = mLayout.getLineStart(0);
            int lineEnd = mLayout.getLineEnd(0);
            String line = text.substring(lineStart, lineEnd);
            for (int j = 0; j < line.length(); j++) {
                String c = String.valueOf(line.charAt(j));
                float cw = StaticLayout.getDesiredWidth(c, getPaint());
                canvas.drawText(c, x, mLineY, getPaint());
                x += cw;
            }
        }
    }

    private int getBlankCount(String line) {
        int count = 0;
        for (char c : line.toCharArray()) {
            if (c == 32) {
                count++;
            }
        }
        return count;
    }

    private float getSBCwidth(String text) {
        for (int i = 0; i < text.length(); i++) {
            String c = String.valueOf(text.charAt(i));
            if (Character.UnicodeBlock.of(text.charAt(i)) == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS) {
                return StaticLayout.getDesiredWidth(c, getPaint());
            }
        }
        return -1;
    }

    private float getGapWidth(char c) {
        float gapWidth = (mSBCwith - StaticLayout.getDesiredWidth(String.valueOf(c)
                , getPaint())) / 2 + mGapWidth;
        return gapWidth;
    }

}

 

 

 

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值