Android - 实现竖排文字

作者: 穿越平行宇宙 | 来源:发表于2019-07-07 19:46 被阅读40次
image.png

1. 自定义View 描绘出效果

VerticalText.java

public class VerticalText extends View {

    private String mLiBai = "世间所有的相遇,都是久别重逢";

    private String mText = "静夜思,唐代 李白,床前明月光,疑是地上霜,,举头望明月,低头思故乡。";

    //分割符号
    private String mSubStringStr = ",";
    private Paint mTextPaint;
    private float mTextSize;
    //屏幕宽高
    private int mWidth;
    private int mHeight;
    //单个字符高度
    private float mCharHeight;
    //单个字符宽度
    private int mCharWidth;
    //上下字间距
    private float mTopBottomSpacing;
    //左右行间距
    private float mLeftRightSpacing;
    //每列需要绘制的文字列表
    private List<String> mFormatTexts = new ArrayList<>();
    //计算每行可容纳文字数目后得出的上下边距
    private float mCalculatePaddingTopBottom;
    //计算总列数后得出的左右边距
    private float mCalculatePaddingLeftRight;
    //一列最多可容纳的字数
    private int mOneLineTextNum;
    //列数
    private int mLineNum;

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

    public VerticalText(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public VerticalText(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mTextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 15f, getResources().getDisplayMetrics());
        mLeftRightSpacing = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8f, getResources().getDisplayMetrics());
        mTopBottomSpacing = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8f, getResources().getDisplayMetrics());

        mTextPaint = new Paint();
        mTextPaint.setAntiAlias(true);
        mTextPaint.setColor(Color.BLACK);
        mTextPaint.setTextSize(mTextSize);

        //测量文字宽度
        mCharWidth = (int) mTextPaint.measureText("一");
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int wMode = MeasureSpec.getMode(widthMeasureSpec);
        int wSize = MeasureSpec.getSize(widthMeasureSpec);
        int hMode = MeasureSpec.getMode(heightMeasureSpec);
        int hSize = MeasureSpec.getSize(heightMeasureSpec);

        if (hMode == MeasureSpec.EXACTLY) {
            mHeight = hSize;
            setCalculatePaddingTopBottom();
        } else {
            if (!mSubStringStr.isEmpty()) {
                //有分割符号
                String[] split = mText.split(mSubStringStr);
                for (String s : split) {
                    mHeight = Math.max(mHeight, getPaddingTop() + getTotalHeight(s) + getPaddingBottom());
                }
            } else {
                mHeight = getPaddingTop() + getTotalHeight(mText) + getPaddingBottom();
            }
            //防止超出父容器
            mHeight = mHeight > hSize ? hSize : mHeight;
        }

        mFormatTexts.clear();
        if (!mSubStringStr.isEmpty()) {
            //有分割符号
            String[] split = mText.split(mSubStringStr);
            for (String s : split) {
                getFormatTexts(s);
            }
        } else {
            getFormatTexts(mText);
        }

        if (wMode == MeasureSpec.EXACTLY) {
            mWidth = wSize;
            setCalculatePaddingLeftRight();
        } else {
            if (!mSubStringStr.isEmpty()) {
                //有分割符号
                mWidth = (int) (getPaddingLeft() + mFormatTexts.size() * (mCharWidth + mLeftRightSpacing) - mLeftRightSpacing + getPaddingRight());
            } else {
                //控件可容纳内容的高度
                int num = getOneLineTextNum();
                //总共的列数
                int lineNum = getLineNum(num);
                mWidth = (int) (getPaddingLeft() + lineNum * (mCharWidth + mLeftRightSpacing) - mLeftRightSpacing + getPaddingRight());
            }
            mWidth = mWidth > wSize ? wSize : mWidth;
        }

        setMeasuredDimension(mWidth, mHeight);
    }

    /**
     * 所有字符竖排的高度(包括与下一个文字的边距)
     *
     * @param str
     * @return
     */
    private int getTotalHeight(String str) {
        return (int) ((getCharHeight() + mTopBottomSpacing) * str.length() - mTopBottomSpacing);
    }

    /**
     * 单个字符的高度
     *
     * @return
     */
    private float getCharHeight() {
        if (mCharHeight == 0) {
            mCharHeight = Math.abs(mTextPaint.ascent());
        }
        return mCharHeight;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        float x = mWidth - getPaddingRight() - mCalculatePaddingLeftRight;
        float y;
        for (int i = 0; i < mFormatTexts.size(); i++) {
            //遍历写列表中的文字
            String thisLineText = mFormatTexts.get(i);
            x = i == 0 ? x - mCharWidth : x - mCharWidth - mLeftRightSpacing;
            //换行后,y轴坐标重置
            y = getCharHeight() + mCalculatePaddingTopBottom;
            for (int j = 0; j < thisLineText.length(); j++) {
                y = j == 0 ? y + getPaddingTop() : y + getCharHeight() + mTopBottomSpacing;
                //绘制text中 j 到 j+1 的字,y坐标是baseline的高度
                canvas.drawText(thisLineText, j, j + 1, x, y, mTextPaint);
            }
        }
    }

    /**
     * 根据列数分割每列要绘制的字符串
     *
     * @param str
     * @return
     */
    public List<String> getFormatTexts(String str) {
        //控件可容纳内容的高度
        int num = getOneLineTextNum();
        //总共的列数
        int lineNum = getLineNum(num);
        if (lineNum > 1) {
            //大于一列
            for (int i = 0; i <= str.length(); i += num) {
                if (i + num >= str.length()) {
                    //超出边界,直接切割至字符串末位
                    String substring = str.substring(i);
                    if (!substring.isEmpty()) {
                        mFormatTexts.add(substring);
                    }
                } else {
                    mFormatTexts.add(str.substring(i, i + num));
                }
            }
        } else {
            mFormatTexts.add(str);
        }
        return mFormatTexts;
    }

    /**
     * 获取列数
     *
     * @param num
     * @return
     */
    private int getLineNum(int num) {
        if (mLineNum == 0) {
            int result = mText.length() % num;
            mLineNum = result == 0 ? mText.length() / num : mText.length() / num + 1;
        }
        return mLineNum;
    }

    /**
     * 获得一列可容纳的文字数
     *
     * @return
     */
    private int getOneLineTextNum() {
        if (mOneLineTextNum == 0) {
            int oneLineHeight = mHeight - getPaddingTop() - getPaddingBottom();
            //粗略计算一列可容纳多少字
            mOneLineTextNum = (int) (oneLineHeight / getCharHeight());
            while ((int) (mOneLineTextNum * (getCharHeight() + mTopBottomSpacing) - mTopBottomSpacing) > oneLineHeight) {
                //有可能加上上下间距后超出可容纳内容高度
                mOneLineTextNum--;
            }
        }
        return mOneLineTextNum;
    }

    /**
     * 给定控件宽高情况下,计算每列可容纳文字数目后得出的上下边距
     */
    private void setCalculatePaddingTopBottom() {
        if (mCalculatePaddingTopBottom == 0) {
            mCalculatePaddingTopBottom = (mHeight - getPaddingTop() - getPaddingBottom() - (getOneLineTextNum() * (getCharHeight() + mTopBottomSpacing) - mTopBottomSpacing)) / 2;
        }
    }

    /**
     * 给定控件宽高情况下,计算列数后得出的左右边距
     */
    private void setCalculatePaddingLeftRight() {
        if (mCalculatePaddingLeftRight == 0) {
            mCalculatePaddingLeftRight = (mWidth - getPaddingLeft() - getPaddingRight() - (getLineNum(getOneLineTextNum()) * (mCharWidth + mLeftRightSpacing) - mLeftRightSpacing)) / 2;
        }
    }
}

2. UI 布局中定义

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.example.administrator.vertical_text.VerticalText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#dddddd"
        android:padding="15dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintRight_toRightOf="parent"/>

</android.support.constraint.ConstraintLayout>

源GitHub下载地址

相关文章

  • [Android]实现文字竖排

    最终效果:文字竖排,英文字母是横着的,中文字是竖的,如下图 分析:英文是一个完整的单词旋转了90度,中文一字一行 ...

  • Android - 实现竖排文字

    1. 自定义View 描绘出效果 VerticalText.java 2. UI 布局中定义 activity_m...

  • CSS3实现文本竖排排列且两端对齐

    最近需要写一个公交站牌的效果,需要达到的效果如图(网上找的图) 但是不知道如何实现文字竖排两端对齐,文字竖排我相信...

  • 竖排显示文字

  • 3个Excel小技巧,你都会吗?

    1、横排文字转为竖排文字 方法一: 选中单元格,单击【开始】,在对齐方式功能区单击“方向”,选择“竖排文字”即可。...

  • 网页文字竖排的几种实现方式

    古时候的书籍里面文字的书写方式都是从上到下从右向左书写的,我们可不可以在网页上实现这种文字排版效果,虽然现在竖排在...

  • iOS 文字横竖排切换

    使用YYText 的YYLabel 实现文字横竖排版时发现一个问题,当对verticalForm 设置为YES时,...

  • CorelDRAW如何设置竖排文字

    很多同学反映,在CorelDRAW中找不到竖排文本工具在哪,或者在设置竖排文本的时候,遇到数字和字母,竖排文字就会...

  • Android控制文字水平间距android:letterSpa

    Android控制文字水平间距android:letterSpacing 附录文章1实现的是Android的文字在...

  • 竖排文字的学问

    中国古代书籍上千年来都是从右到左,竖排排版。难道古人不知道横排可以节约纸张,且容易阅读么?我们的祖先当然知道这其中...

网友评论

    本文标题:Android - 实现竖排文字

    本文链接:https://www.haomeiwen.com/subject/goeihctx.html