天天看点

自定义VerticalTextView

最近做需求,网上找不到链接,于是自己做一个简陋版的VerticalTextView

GitHub下载链接

[email protected]:13540634851/PreferenceDemo.git

效果图

渐变纵向中英文混合.png

自定义VerticalTextView

注意:

  1. 由于是自定义View,TextView’的属性在这里不管用
  2. 只有3个方法可用

    设置显示的字符串,

    public void setText(String text) ;

    设置对齐方式

    Gravity.START

    Gravity.CENTER

    Gravity.END

    public void setGravity(int gravity) ;

    设置是否垂直显示

    public void setHorizontal(boolean orientation) ;

  3. 支持warp_content属性
  4. 支持中英文混合显示(英文旋转,中文不旋转)

由于中英文在FontMetrics显示的问题,建议根据需求再调整一下

可用方法

//设置颜色渐变
    public void setLinearGradientColor(int[] color, int angle) {
        mLinearGradientColor = color;
        mAngle = angle;
        update();
    }

    //设置字符串
    public void setText(String text) {
        this.mText = text;
        update();
    }

    //设置对齐方式
    public void setGravity(int mGravity) {
        this.mGravity = mGravity;
        update();
    }

    //设置显示方式
    public void setHorizontal(boolean orientation) {
        this.mIsHorizontal = orientation;
        update();
    }

    public boolean isHorizontal() {
        return mIsHorizontal;
    }

    public void update() {
        requestLayout();
        invalidate();
    }
           

代码

VerticalTextView.java

package com.can.testpreference.view;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;

import androidx.annotation.Nullable;

import java.util.HashMap;

public class VerticalTextView extends View {
    private Paint mPaint;
    private Paint mDebugPaint;
    private float mSpacing;
    private String mText;
    private boolean mIsHorizontal = false;
    private Paint.FontMetrics mFontMetrics;
    private String[] mDrawText;
    private float mWordHeight;
    private float mVerticalwordHeight;
    private int mGravity = Gravity.START;
    private int mViewWidth, mViewHeight;
    private HashMap<Integer, Integer> mLengthRecord;
    private float mChineseWordWdth = -1;
    private static final boolean DEBUG = true;
    private int[] mLinearGradientColor;
    private int mAngle;

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

    public VerticalTextView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

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

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

    //设置颜色渐变
    public void setLinearGradientColor(int[] color, int angle) {
        mLinearGradientColor = color;
        mAngle = angle;
        update();
    }

    //设置字符串
    public void setText(String text) {
        this.mText = text;
        update();
    }

    //设置对齐方式
    public void setGravity(int mGravity) {
        this.mGravity = mGravity;
        update();
    }

    //设置显示方式
    public void setHorizontal(boolean orientation) {
        this.mIsHorizontal = orientation;
        update();
    }

    public boolean isHorizontal() {
        return mIsHorizontal;
    }

    public void update() {
        requestLayout();
        invalidate();
    }


    private void init() {
        mPaint = new Paint();
        mPaint.setTextSize(60);
        mPaint.setColor(Color.RED);

        mDebugPaint = new Paint();
        mDebugPaint.setStyle(Paint.Style.STROKE);
        mDebugPaint.setColor(Color.GRAY);
    }

    //根据水平和垂直显示测量视图大小,特别是warp_content
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (!TextUtils.isEmpty(mText)) {
            mDrawText = mText.split("\n");
        }

        mFontMetrics = mPaint.getFontMetrics();
        mWordHeight = mFontMetrics.descent - mFontMetrics.ascent;
        mVerticalwordHeight = mFontMetrics.descent - mFontMetrics.ascent;
        mSpacing = 0;

        if (mIsHorizontal) {
            measureHorizontal(widthMeasureSpec, heightMeasureSpec);
        } else {
            measureVertical(widthMeasureSpec, heightMeasureSpec);
        }
        setMeasuredDimension(mViewWidth, mViewHeight);
    }

    //水平测量
    public void measureHorizontal(int widthMeasureSpec, int heightMeasureSpec) {
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        mViewHeight = heightSpecSize;
        mViewWidth = widthSpecSize;

        int lineCount = 1;
        if (mDrawText != null) {
            lineCount = Math.max(1, mDrawText.length);
        }

        if (widthSpecMode == MeasureSpec.AT_MOST) {
            mViewWidth = 0;
            float maxHeight = 0;
            if (!TextUtils.isEmpty(mText)) {
                for (String dtxt : mDrawText) {
                    float height = mPaint.measureText(dtxt);
                    if (maxHeight < height) {
                        maxHeight = height;
                    }
                }
                maxHeight += 2 * (mSpacing);
            }
            mViewWidth += maxHeight;
        }

        if (heightSpecMode == MeasureSpec.AT_MOST) {
            mViewHeight = (int) ((mWordHeight + mSpacing) * lineCount + mSpacing);
        }
        if (widthSpecSize < mViewWidth) {
            mViewWidth = widthSpecSize;
        }
        if (heightSpecSize < mViewHeight) {
            mViewHeight = heightSpecSize;
        }
    }

    //垂直测量
    public void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);

        mViewHeight = heightSpecSize;
        mViewWidth = widthSpecSize;

        if (mLengthRecord == null) {
            mLengthRecord = new HashMap<>();
        }
        if (widthSpecMode == MeasureSpec.AT_MOST) {
            int widthLine = 1;
            mViewWidth = 0;
            if (mDrawText != null && mDrawText.length > 0) {
                widthLine = mDrawText.length;
            }
            mViewWidth = (int) (mWordHeight * widthLine + mSpacing);
        }

        mChineseWordWdth = -1;
        mViewHeight = 0;
        int maxHeight = 0;
        if (mDrawText != null && mDrawText.length > 0) {
            ChineseWordsWatch chineseWordsWatch = new ChineseWordsWatch();
            for (int i = mDrawText.length - 1; i >= 0; i--) {
                chineseWordsWatch.setString(mDrawText[i]);
                int height = 0;
                String readStr;
                while (null != (readStr = chineseWordsWatch.nextString())) {
                    if (chineseWordsWatch.isChineseWord()) {
                        if (mChineseWordWdth == -1) {
                            mChineseWordWdth = mPaint.measureText(readStr);
                        }
                        height += mVerticalwordHeight;
                    } else {
                        height += mPaint.measureText(readStr);
                    }
                }
                mLengthRecord.put(i, height);
                if (maxHeight < height) {
                    maxHeight = height;
                }
            }
        }

        if (mChineseWordWdth != -1) {
            mViewWidth = (int) (mViewWidth - (mWordHeight - mChineseWordWdth));
        }


        if (heightSpecMode == MeasureSpec.AT_MOST) {
            mViewHeight = (int) (maxHeight + 2 * mSpacing);
        }

        if (widthSpecSize < mViewWidth) {
            mViewWidth = widthSpecSize;
        }
        if (heightSpecSize < mViewHeight) {
            mViewHeight = heightSpecSize;
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (TextUtils.isEmpty(mText)) {
            return;
        }
        if (mIsHorizontal) {
            drawHorizontal(canvas);
        } else {
            drawVertical(canvas);
        }
    }

    //垂直绘制
    private void drawVertical(Canvas canvas) {
        int startX = (int) mSpacing;
        int startY = (int) mSpacing;

        ChineseWordsWatch chineseWordsWatch = new ChineseWordsWatch();
        for (int i = mDrawText.length - 1; i >= 0; i--) {
            chineseWordsWatch.setString(mDrawText[i]);
            String readStr;
            int currentheight = startY;
            if (mLengthRecord != null && mLengthRecord.containsKey(i)) {
                int popHight = mLengthRecord.get(i);
                if (mGravity == Gravity.CENTER) {
                    currentheight = (mViewHeight - popHight) >> 1;
                } else if (mGravity == Gravity.END) {
                    currentheight = (int) (mViewHeight - 2 * mSpacing - popHight);
                } else {
                    currentheight = startY;
                }
            }

            LinearGradient baseLinearGradient = null;
            if (mLinearGradientColor != null) {
                baseLinearGradient = LinearGradientTool.getLinearGradient(0, 0, mViewWidth, mViewHeight, mLinearGradientColor, mAngle);
            }


            while (null != (readStr = chineseWordsWatch.nextString())) {
                if (chineseWordsWatch.isChineseWord()) {
                    currentheight += mVerticalwordHeight;
                    if (DEBUG) {
                        drawDebugLine(canvas, startX, currentheight - mFontMetrics.descent, (int) mPaint.measureText(readStr));
                    }

                    if (baseLinearGradient != null) {
                        mPaint.setShader(baseLinearGradient);
                    }
                    canvas.drawText(readStr, startX, currentheight - mFontMetrics.descent, mPaint);
                } else {
                    float marginFixed = 0;
                    if (mChineseWordWdth != -1) {
                        marginFixed = (float) ((mVerticalwordHeight - mChineseWordWdth) * 0.5);
                    }
                    canvas.save();
                    canvas.rotate(90, startX, currentheight);

                    if (DEBUG) {
                        drawDebugLine(canvas, startX, currentheight - mFontMetrics.descent + marginFixed, (int) mPaint.measureText(readStr));
                    }


                    if (mLinearGradientColor != null) {
                        mPaint.setShader(LinearGradientTool.getRotate90LinearGradient(startX, currentheight,
                                0, 0,
                                mViewWidth, mViewHeight,
                                mLinearGradientColor, mAngle));
                    }

                    canvas.drawText(readStr, startX, currentheight - mFontMetrics.descent + marginFixed, mPaint);
                    canvas.restore();
                    currentheight += mPaint.measureText(readStr);
                }
            }
            startX += mWordHeight;
        }
    }

    //水平绘制
    private void drawHorizontal(Canvas canvas) {
        int startX;
        int startY = (int) (mSpacing - mFontMetrics.ascent);


        if (mLinearGradientColor != null) {
            mPaint.setShader(LinearGradientTool.getLinearGradient(0, 0, mViewWidth, mViewHeight, mLinearGradientColor, mAngle));
        }
        for (String dtxt : mDrawText) {
            float textWidth = mPaint.measureText(dtxt);
            if (mGravity == Gravity.START) {
                startX = (int) mSpacing;
            } else if (mGravity == Gravity.END) {
                startX = (int) (mViewWidth - mSpacing - textWidth);
            } else {
                startX = ((int) (mViewWidth - textWidth)) >> 1;
            }

            canvas.drawText(dtxt, startX, startY, mPaint);
            if (DEBUG) {
                drawDebugLine(canvas, startX, startY, (int) mPaint.measureText(dtxt));
            }
            startY += mWordHeight + mSpacing;
        }
    }

    private void drawDebugLine(Canvas canvas, float offx, float offy, float length) {
        canvas.drawRect(offx, offy + mFontMetrics.ascent, offx + length, offy + mFontMetrics.descent, mDebugPaint);
    }
}


           

ChineseWordsWatch.java

package com.can.testpreference.view;

public class ChineseWordsWatch {
    private char[] mCharArray;
    private int mStartX, mStopX;
    private boolean mIsChinese;

    public ChineseWordsWatch() {

    }

    public void setString(String doString) {
        this.mCharArray = doString.toCharArray();
        mStartX = 0;
        mStopX = 0;
    }

    public static boolean isChinese(char c) {
        return c >= 0x4E00 && c <= 0x9FA5;
    }

    public String nextString() {
        mStopX = mStartX;
        mIsChinese = false;
        boolean containNonChineseWord = false;
        while (mStopX < mCharArray.length) {
            if (isChinese(mCharArray[mStopX])) {
                if (containNonChineseWord) {
                    mIsChinese = false;
                } else {
                    mStopX++;
                    mIsChinese = true;
                }
                break;
            } else {
                containNonChineseWord = true;
                mStopX++;
            }
        }

        String ret;
        if (mStartX == mStopX) {
            ret = null;
        } else {
            ret = new String(mCharArray, mStartX, mStopX - mStartX);
        }
        mStartX = mStopX;
        return ret;
    }

    public boolean isChineseWord() {
        return mIsChinese;
    }
}

           

LinearGradientTool.java

package com.can.testpreference.view;

import android.graphics.LinearGradient;
import android.graphics.Shader;

public class LinearGradientTool {


    /***
     * 根据旋转90度渐变区域和渐变角度生产一个渐变器
     * @param rotateX 旋转中心横坐标
     * @param rotateY 旋转中心纵坐标
     * @param x0      渐变起始横坐标
     * @param y0      渐变结束纵坐标
     * @param x1      渐变结束横坐标
     * @param y1      渐变起始纵坐标
     * @param color   渐变颜色数组  至少两种颜色
     * @param angle   渐变角度  0~360
     * @return 根据旋转渐变区域和渐变角度生产一个渐变器
     */
    public static LinearGradient getRotate90LinearGradient(float rotateX, float rotateY, float x0, float y0, float x1, float y1, int[] color, int angle) {


        float width = x1 - x0;
        float height = y1 - y0;
        float colorstartX = rotateX - rotateY;
        float colorStarty = (rotateY + rotateX) - width;

        return getLinearGradient(colorstartX
                , colorStarty
                , colorstartX + height
                , colorStarty + width
                , color, angle + 90);
    }

    /**
     * 根据渐变区域和渐变角度生产一个渐变器
     *
     * @param x0    渐变起始横坐标
     * @param y0    渐变结束纵坐标
     * @param x1    渐变结束横坐标
     * @param y1    渐变起始纵坐标
     * @param color 渐变颜色数组  至少两种颜色
     * @param angle 渐变角度  0~360
     *              <p>
     *              <p>
     *              angle角度为例
     *              (x0,y0)           渐变结束
     *              -----------------
     *              |              /|
     *              |             / |
     *              |            /  |
     *              |           /   |
     *              |          /    |
     *              |         /     |
     *              |        /      |
     *              |       /       |
     *              |      /        |
     *              |     /         |
     *              |    /          |
     *              |   /           |
     *              |  /            |
     *              | / \ angle     |
     *              |/  )           |
     *              -----------------
     *              渐变开始         (x1,y1)
     * @return 生产一个渐变器
     */
    public static LinearGradient getLinearGradient(float x0, float y0, float x1, float y1, int[] color, int angle) {
        if (angle > 0) {
            angle = angle % 360;
        } else if (angle < 0) {
            angle = 360 + angle % 360;
        }

        float centerX = 0.5f * (x0 + x1);
        float centerY = 0.5f * (y0 + y1);
        float radius = (float) (Math.sqrt((x1 - x0) * (x1 - x0) + (y1 - x0) * (y1 - x0)) * 0.5);
        float offx = (float) (radius * Math.cos(angle * Math.PI / 180));
        float offY = (float) (radius * Math.sin(angle * Math.PI / 180));

        int colorLen = color.length;
        float[] postion = new float[colorLen];
        postion[0] = 0;
        postion[colorLen - 1] = 1;
        float dx = 1.0f / (colorLen - 1);
        for (int i = 1; i < colorLen - 1; i++) {
            postion[i] = i * dx;
        }

        LinearGradient linearGradient = new LinearGradient(centerX - offx,
                centerY + offY,
                centerX + offx,
                centerY - offY,
                color,
                postion,
                Shader.TileMode.CLAMP);
        return linearGradient;
    }
}

           

继续阅读