画BaseLine平齐的文本

作者: 砂砾han | 来源:发表于2017-06-02 00:25 被阅读235次

    最近在项目中遇到好几处类似下方这样的带单位、左大右小的布局


    1.png 2.png

    看起来很平常,两个不同size的TextView左右布局,同时它们的baseline是平齐的,有人马上就想到了在RelativeLayout中的子View可以使用layout_alignBaseline属性完成这个布局,这里贴下xml文件:

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        >
    
        <TextView
            android:id="@+id/tv_value"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:includeFontPadding="false"
            android:textColor="@color/white"
            android:textSize="16sp" />
    
    
        <TextView
            android:id="@+id/tv_unit"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignBaseline="@id/tv_value"
            android:layout_toRightOf="@id/tv_value"
            android:includeFontPadding="false"
            android:textColor="@color/white"
            android:textSize="14sp" />
    </RelativeLayout>
    

    本来以为到这里就结束了,但是设计告诉我说,这个底边并没有平齐,那个kg的g明明下沉了,我要的效果不是这样的……然后我就解释了一下因为这个g比较特殊,你要是m就齐了,设计大哥就问了句那我要的能实现不……
    这里就不得不说一下TextView的绘制了


    Text的绘制

    熟悉自定义view的同学应该知道,自定义view其实就是绘制文字和图像,这就涉及到canvas的drawText()方法了,这里有好几个重载的方法,看看我们经常常用的这个方法的源码:

    /**
         * Draw the text, with origin at (x,y), using the specified paint. The
         * origin is interpreted based on the Align setting in the paint.
         *
         * @param text  The text to be drawn
         * @param x     The x-coordinate of the origin of the text being drawn
         * @param y     The y-coordinate of the baseline of the text being drawn
         * @param paint The paint used for the text (e.g. color, size, style)
         */
        public void drawText(@NonNull String text, float x, float y, @NonNull Paint paint) {
            native_drawText(mNativeCanvasWrapper, text, 0, text.length(), x, y, paint.mBidiFlags,
                    paint.getNativeInstance(), paint.mNativeTypeface);
        }
    

    这四个参数除了y其他的都很好理解,text是要绘制的文本内容,x是x轴方向的开始绘制的值,y参数说明里的baseline又是什么东东……
    先上图:


    3.png

    这张图里有四根线,其中3就是baseline,可以看到汉字和英文字母都会超出这个线,阿拉伯数字就不会超出这个baseline。也就是说drawtext在垂直方向是以3为基准的,所以当我们想把文本垂直居中绘制在某一个view里,y的值是不能直接设为getHeight()/2的,也就是图中的2号线,y值应该向下偏移到3的位置。
    这里就说下3号线距离1号线和4号线分别对应两个值,他们是FontMetrics这个类中的两个值,ascent(负数)和descent的绝对值。在FontMetrics有五个float类型值:

    • leading 留给文字音标符号的距离

    • ascent 从baseline线到最高的字母顶点到距离,负值

    • top 从baseline线到字母最高点的距离加上ascent, |top|=|ascent|+|leading|

    • descent 从baseline线到字母最低点到距离

    • bottom 和top类似,系统为一些极少数符号留下的空间,top和bottom总会比ascent和descent大一点的就是这些少到忽略的特殊符号。

    想要了解详情的可以看看FontMetrics这个类的源码。


    所以到这里,实现平齐效果的思路就很明了,自定义一个view,继承自View,画两个text即可实现,关键在于第二个text的baseline设置成多少。
    先贴上完整的类

    import android.content.Context;
    import android.content.res.TypedArray;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.text.TextUtils;
    import android.util.AttributeSet;
    import android.view.View;
    
    
    /**
     * Description:
     * Created by hdz on 2017/6/1.
     */
    
    public class AlignmentView extends View {
    
        //数值的画笔
        private Paint valuePaint;
    
        //单位的画笔
        private Paint unitPaint;
    
        private int valueColor = Color.BLACK;
    
        private int unitColor = Color.BLACK;
    
        private float valueTextSize = 30;
    
        private float unitTextSize = 24;
    
        //数值和单位之间的padding
        private float space = 0;
    
        /**
         * 类型
         * 0:下方未超出baseline
         * 1:下方超出baseline
         */
        private int type;
    
        private String value;
    
        private String unit;
    
        //数值对应baseline的y值
        private float valueDrawY;
        
        private Paint.FontMetrics valueMetrics;
    
        private Paint.FontMetrics unitMetrics;
    
        public AlignmentView(Context context) {
            this(context, null, 0);
        }
    
        public AlignmentView(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public AlignmentView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init(context, attrs, defStyleAttr);
        }
    
        private void init(Context context, AttributeSet attrs, int defStyleAttr) {
    
            if (attrs == null) {
                return;
            }
    
            TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.AlignmentView, defStyleAttr, 0);
            value = array.getString(R.styleable.AlignmentView_value);
            unit = array.getString(R.styleable.AlignmentView_unit);
            space = array.getDimension(R.styleable.AlignmentView_space, 0);
            unitColor = array.getColor(R.styleable.AlignmentView_unitColor, Color.BLACK);
            valueColor = array.getColor(R.styleable.AlignmentView_valueColor, Color.BLACK);
            valueTextSize = array.getDimension(R.styleable.AlignmentView_valueSize, 30);
            unitTextSize = array.getDimension(R.styleable.AlignmentView_unitSize, 24);
            type = array.getInt(R.styleable.AlignmentView_type, 0);
    
            if (TextUtils.isEmpty(value)) {
                value = "数值";
            }
    
            if (TextUtils.isEmpty(unit)) {
                unit = "单位";
            }
    
            valuePaint = new Paint();
            valuePaint.setAntiAlias(true);
            valuePaint.setTextSize(valueTextSize);
            valuePaint.setColor(valueColor);
            valuePaint.setTextAlign(Paint.Align.LEFT);
    
            unitPaint = new Paint();
            unitPaint.setAntiAlias(true);
            unitPaint.setTextSize(unitTextSize);
            unitPaint.setColor(unitColor);
            unitPaint.setTextAlign(Paint.Align.LEFT);
    
            array.recycle();
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            valueMetrics = valuePaint.getFontMetrics();
            unitMetrics = unitPaint.getFontMetrics();
    
            //获取文字的宽高
            float valueH = valueMetrics.descent - valueMetrics.ascent;
            float valueW = valuePaint.measureText(value);
            float unitH = unitMetrics.descent - unitMetrics.ascent;
            float unitW = unitPaint.measureText(unit);
    
            int realH = (int) (Math.max(valueH, unitH) + getPaddingBottom() + getPaddingTop());
            int realW = (int) (valueW + unitW + getPaddingLeft() + getPaddingRight() + space);
    
            int width = measureSize(realW, widthMeasureSpec);
            int height = measureSize(realH, heightMeasureSpec);
            setMeasuredDimension(width, height);
        }
    
        private int measureSize(int defaultSize, int measureSpec) {
            int resultSize = defaultSize;
            int mode = MeasureSpec.getMode(measureSpec);
            int size = MeasureSpec.getSize(measureSpec);
    
            switch (mode) {
                case MeasureSpec.AT_MOST:
                case MeasureSpec.UNSPECIFIED:
                    break;
                case MeasureSpec.EXACTLY:
                    resultSize = size;
                    break;
            }
            return resultSize;
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            drawValue(canvas);
            drawUnit(canvas);
            canvas.drawLine(getPaddingLeft(), valueDrawY, getWidth(), valueDrawY, unitPaint);
        }
    
    //    画左侧的数值
        private void drawValue(Canvas canvas) {
           //y值的计算,向下偏移
            valueDrawY = getHeight() / 2 + (Math.abs(valueMetrics.ascent) - valueMetrics.descent) / 2;
            canvas.drawText(value, getPaddingLeft(), valueDrawY, valuePaint);
        }
    
    //    画右侧的单位
        private void drawUnit(Canvas canvas) {
            float valueWidth = valuePaint.measureText(value);
            float x = getPaddingLeft() + valueWidth + space;
            float y = valueDrawY;
            if (type == 1) {
    //            当底部超出baseline的时候,应该向上偏移单位对应的descent值
                y = valueDrawY - unitMetrics.descent;
            }
            canvas.drawText(unit, x, y, unitPaint);
        }
    
    //    向外部提供设置值的方法
        public void setValue(String value) {
            this.value = value;
            invalidate();
        }
    
        public void setUnit(String unit) {
            this.unit = unit;
            invalidate();
        }
    }
    

    attrs:

    <declare-styleable name="AlignmentView">
            <attr name="value" format="string"/>
            <attr name="unit" format="string"/>
            <attr name="valueColor" format="color" />
            <attr name="unitColor" format="color" />
            <attr name="valueSize" format="dimension" />
            <attr name="unitSize" format="dimension" />
            <attr name="space" format="dimension"/>
    
            <attr name="type">
                <enum name="NORMAL" value="0"/>
                <enum name="DESCENT_BEYOND" value="1"/>
            </attr>
        </declare-styleable>
    

    这里需要说下就是drawUnit方法中drawText中的y值,当y = valueDrawY时也就达到了上面layout_alignBaseline的效果,如果还要调整,就需要
    y = valueDrawY - unitMetrics.descent了,这里descent应该是unit所对应的。

    布局文件

     <com.example.handezhao.align.AlignmentView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:background="#57df87"
            android:padding="10dp"
            app:space="5dp"
            app:unitColor="#00f"
            app:unitSize="20sp"
            app:valueColor="#454545"
            app:valueSize="40sp"
            app:value="1234567890"
            app:unit="abcdefghijk"
            android:layout_marginTop="20dp"
            app:type="DESCENT_BEYOND"
            />
    

    最后看看两种type的效果:

    type0.png type1.png

    😆😆😆,这里的type可以根据不同的需求增加,比如右上角之类的……

    相关文章

      网友评论

        本文标题:画BaseLine平齐的文本

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