Android UI 之居中绘制文本内容的正确方法——实现自定义一个TextView

原文地址 http://blog.csdn.net/carrey1989/article/details/10399727   


我们在自定义一个控件的时候,有时候会需要自己来绘制一些文本内容,这样就自然而然遇到确定文本的方位的问题,比如文本需要水平居中,垂直居中,居左,居右,左上。。。等等很多情况。其中最常见的就是文本位于控件的正中央了。

    既然是文本居中,那就要让文本水平居中,并且同时垂直居中,我们分开来做。

    水平居中的思路很简单,一种是得到控件的宽度A,使得文本的中心位置x坐标=A/2就可以了。还有一种思路是我们得到控件的宽度A,然后测量出要绘制的文本的宽度B,然后使得文本的左端x坐标=(A-B)/2。

    相对复杂一点的实现文本垂直居中,我们需要用到FontMetrics,这里涉及到几个概念,分别是文本的top,bottom,ascent,descent,baseline。看下面的图:


    我们在画布中绘制文本的时候,会调用Canvas.drawText(String text, float x, float y, Paint paint)这个方法,其中y的坐标就是上图中baseline的y坐标,所以,如果我们只是简单地把drawText方法中的y设置为控件高度的1/2是不准确的,如果这样做的话会发现文本整体是位于画布的上半部分的,因为baseline下面的文本部分是很小的。

    正确的做法是通过一些合理的换算使得文本的y坐标(baseline的y坐标)位于控件的下半部分区域。下面是我自己通过计算和测试得到的准确换算关系:

[java]  view plain copy
  1. float textCenterVerticalBaselineY = viewHeight / 2 - fm.descent + (fm.bottom - fm.top) / 2;  
    textCenterVerticalBaselineY就是绘制文本时候的y坐标,viewHeight是控件的高度。其实这个换算关系也不难理解,viewHeight/2-fm.descent的意思是将整个文字区域抬高到控件的1/2,然后我们再加上(fm.bottom - fm.top) / 2的意思就是将文本下沉文本top到bottom长度的一半,从而实现文本垂直居中的目的。

    有的人或许会问,为什么最后加上的是bottom到top距离的一半而不是descent到ascent的一半呢?其实这个是我测试的结果,我发现如果用bottom到top距离的一半来设置文本垂直居中,和系统控件TextView的文本居中效果是一样的,我们来看下面的效果:

    首先是我们使用(fm.bottom - fm.top) / 2:


    然后是使用(fm.descent - fm.ascent) / 2:


    左边绿色的是系统的TextView文字居中效果,右边是我们自定义控件的文字居中效果,可以看出使用(fm.bottom - fm.top) / 2与TextView的效果是一样的,当然,我们不必一定要与TextView的效果相同,所以使用(fm.descent - fm.ascent) / 2也是可以的。

    然后我们扩展一下,自定义一个可以控制内部文本绘制方位的TextView。

    先来看一下效果图:


项目代码:

MyTextView.java

[java]  view plain copy
  1. package com.example.textalignment.mytextview;  
  2.   
  3. import com.example.textalignment.util.DisplayParams;  
  4. import com.example.textalignment.util.DisplayUtil;  
  5.   
  6. import android.content.Context;  
  7. import android.graphics.Bitmap;  
  8. import android.graphics.Canvas;  
  9. import android.graphics.Color;  
  10. import android.graphics.Matrix;  
  11. import android.graphics.Paint;  
  12. import android.graphics.RectF;  
  13. import android.graphics.Paint.Align;  
  14. import android.graphics.Paint.FontMetrics;  
  15. import android.graphics.drawable.Drawable;  
  16. import android.util.AttributeSet;  
  17. import android.util.DisplayMetrics;  
  18. import android.view.View;  
  19. /** 
  20.  * 自定义文本显示控件 
  21.  * 该自定义控件中的文本可以在9个方位进行控制 
  22.  * 左上——中上——右上 
  23.  * 左中——中中——右中 
  24.  * 左下——中下——右下 
  25.  * @author carrey 
  26.  * 
  27.  */  
  28. public class MyTextView extends View {  
  29.       
  30.     /** 要显示的文字 */  
  31.     private String text;  
  32.     /** 文字的颜色 */  
  33.     private int textColor;  
  34.     /** 文字的大小 */  
  35.     private int textSize;  
  36.     /** 文字的方位 */  
  37.     private int textAlign;  
  38.       
  39. //  public static final int TEXT_ALIGN_CENTER            = 0x00000000;  
  40.     public static final int TEXT_ALIGN_LEFT              = 0x00000001;  
  41.     public static final int TEXT_ALIGN_RIGHT             = 0x00000010;  
  42.     public static final int TEXT_ALIGN_CENTER_VERTICAL   = 0x00000100;  
  43.     public static final int TEXT_ALIGN_CENTER_HORIZONTAL = 0x00001000;  
  44.     public static final int TEXT_ALIGN_TOP               = 0x00010000;  
  45.     public static final int TEXT_ALIGN_BOTTOM            = 0x00100000;  
  46.       
  47.     /** 文本中轴线X坐标 */  
  48.     private float textCenterX;  
  49.     /** 文本baseline线Y坐标 */  
  50.     private float textBaselineY;  
  51.       
  52.     /** 控件的宽度 */  
  53.     private int viewWidth;  
  54.     /** 控件的高度 */  
  55.     private int viewHeight;  
  56.     /** 控件画笔 */  
  57.     private Paint paint;  
  58.       
  59.     private FontMetrics fm;  
  60.     /** 场景 */  
  61.     private Context context;  
  62.       
  63.     public MyTextView(Context context) {  
  64.         super(context);  
  65.         this.context = context;  
  66.         init();  
  67.     }  
  68.   
  69.     public MyTextView(Context context, AttributeSet attrs) {  
  70.         super(context, attrs);  
  71.         this.context = context;  
  72.         init();  
  73.     }  
  74.   
  75.     /** 
  76.      * 变量初始化 
  77.      */  
  78.     private void init() {  
  79.         paint = new Paint();  
  80.         paint.setAntiAlias(true);  
  81.         paint.setTextAlign(Align.CENTER);  
  82.         //默认情况下文字居中显示  
  83.         textAlign = TEXT_ALIGN_CENTER_HORIZONTAL | TEXT_ALIGN_CENTER_VERTICAL;  
  84.         //默认的文本颜色是黑色  
  85.         this.textColor = Color.BLACK;  
  86.     }  
  87.       
  88.     @Override  
  89.     protected void onLayout(boolean changed, int left, int top, int right,  
  90.             int bottom) {  
  91.         viewWidth = getWidth();  
  92.         viewHeight = getHeight();  
  93.         super.onLayout(changed, left, top, right, bottom);  
  94.     }  
  95.       
  96.     @Override  
  97.     protected void onDraw(Canvas canvas) {  
  98.         //绘制控件内容  
  99.         setTextLocation();  
  100.         canvas.drawText(text, textCenterX, textBaselineY, paint);  
  101.         super.onDraw(canvas);  
  102.     }  
  103.       
  104.     /** 
  105.      * 定位文本绘制的位置 
  106.      */  
  107.     private void setTextLocation() {  
  108.         paint.setTextSize(textSize);  
  109.         paint.setColor(textColor);  
  110.         fm = paint.getFontMetrics();  
  111.         //文本的宽度  
  112.         float textWidth = paint.measureText(text);  
  113.         float textCenterVerticalBaselineY = viewHeight / 2 - fm.descent + (fm.descent - fm.ascent) / 2;  
  114.         switch (textAlign) {  
  115.         case TEXT_ALIGN_CENTER_HORIZONTAL | TEXT_ALIGN_CENTER_VERTICAL:  
  116.             textCenterX = (float)viewWidth / 2;  
  117.             textBaselineY = textCenterVerticalBaselineY;  
  118.             break;  
  119.         case TEXT_ALIGN_LEFT | TEXT_ALIGN_CENTER_VERTICAL:  
  120.             textCenterX = textWidth / 2;  
  121.             textBaselineY = textCenterVerticalBaselineY;  
  122.             break;  
  123.         case TEXT_ALIGN_RIGHT | TEXT_ALIGN_CENTER_VERTICAL:  
  124.             textCenterX = viewWidth - textWidth / 2;  
  125.             textBaselineY = textCenterVerticalBaselineY;  
  126.             break;  
  127.         case TEXT_ALIGN_BOTTOM | TEXT_ALIGN_CENTER_HORIZONTAL:  
  128.             textCenterX = viewWidth / 2;  
  129.             textBaselineY = viewHeight - fm.bottom;   
  130.             break;  
  131.         case TEXT_ALIGN_TOP | TEXT_ALIGN_CENTER_HORIZONTAL:  
  132.             textCenterX = viewWidth / 2;  
  133.             textBaselineY = -fm.ascent;  
  134.             break;  
  135.         case TEXT_ALIGN_TOP | TEXT_ALIGN_LEFT:  
  136.             textCenterX = textWidth / 2;  
  137.             textBaselineY = -fm.ascent;  
  138.             break;  
  139.         case TEXT_ALIGN_BOTTOM | TEXT_ALIGN_LEFT:  
  140.             textCenterX = textWidth / 2;  
  141.             textBaselineY = viewHeight - fm.bottom;   
  142.             break;  
  143.         case TEXT_ALIGN_TOP | TEXT_ALIGN_RIGHT:  
  144.             textCenterX = viewWidth - textWidth / 2;  
  145.             textBaselineY = -fm.ascent;  
  146.             break;  
  147.         case TEXT_ALIGN_BOTTOM | TEXT_ALIGN_RIGHT:  
  148.             textCenterX = viewWidth - textWidth / 2;  
  149.             textBaselineY = viewHeight - fm.bottom;   
  150.             break;  
  151.         }  
  152.     }  
  153.       
  154.     /** 
  155.      * 设置文本内容 
  156.      * @param text 
  157.      */  
  158.     public void setText(String text) {  
  159.         this.text = text;  
  160.         invalidate();  
  161.     }  
  162.     /** 
  163.      * 设置文本大小 
  164.      * @param textSizeSp 文本大小,单位是sp 
  165.      */  
  166.     public void setTextSize(int textSizeSp) {  
  167.         DisplayParams displayParams = DisplayParams.getInstance(context);  
  168.         this.textSize = DisplayUtil.sp2px(textSizeSp, displayParams.fontScale);  
  169.         invalidate();  
  170.     }  
  171.     /** 
  172.      * 设置文本的方位 
  173.      */  
  174.     public void setTextAlign(int textAlign) {  
  175.         this.textAlign = textAlign;  
  176.         invalidate();  
  177.     }  
  178.     /** 
  179.      * 设置文本的颜色 
  180.      * @param textColor 
  181.      */  
  182.     public void setTextColor(int textColor) {  
  183.         this.textColor = textColor;  
  184.         invalidate();  
  185.     }  
  186. }  
MainActivity.java

[java]  view plain copy
  1. package com.example.textalignment;  
  2.   
  3. import com.example.textalignment.mytextview.MyTextView;  
  4. import com.example.textalignment.util.DisplayParams;  
  5. import com.example.textalignment.util.DisplayUtil;  
  6.   
  7. import android.os.Bundle;  
  8. import android.app.Activity;  
  9. import android.graphics.Bitmap;  
  10. import android.graphics.BitmapFactory;  
  11. import android.graphics.Color;  
  12. import android.view.Menu;  
  13. import android.widget.LinearLayout;  
  14. import android.widget.ScrollView;  
  15.   
  16. public class MainActivity extends Activity {  
  17.   
  18.     @Override  
  19.     protected void onCreate(Bundle savedInstanceState) {  
  20.         super.onCreate(savedInstanceState);  
  21.         setContentView(R.layout.activity_main);  
  22.           
  23.         DisplayParams displayParams = DisplayParams.getInstance(this);  
  24.           
  25.         LinearLayout container = (LinearLayout) findViewById(R.id.container);  
  26.           
  27.         MyTextView myTextView1 = new MyTextView(this);  
  28.         myTextView1.setText("居中的文本");  
  29.         myTextView1.setTextSize(30);  
  30.         myTextView1.setTextAlign(MyTextView.TEXT_ALIGN_CENTER_HORIZONTAL | MyTextView.TEXT_ALIGN_CENTER_VERTICAL);  
  31.         myTextView1.setTextColor(Color.BLUE);  
  32.         myTextView1.setBackgroundColor(Color.RED);  
  33.         container.addView(myTextView1, LinearLayout.LayoutParams.MATCH_PARENT, DisplayUtil.dip2px(150, displayParams.scale));  
  34.           
  35.         MyTextView myTextView2 = new MyTextView(this);  
  36.         myTextView2.setText("居左的文本");  
  37.         myTextView2.setTextSize(25);  
  38.         myTextView2.setTextAlign(MyTextView.TEXT_ALIGN_CENTER_VERTICAL | MyTextView.TEXT_ALIGN_LEFT);  
  39.         myTextView2.setTextColor(Color.GREEN);  
  40.         myTextView2.setBackgroundColor(Color.YELLOW);  
  41.         container.addView(myTextView2, LinearLayout.LayoutParams.MATCH_PARENT, DisplayUtil.dip2px(150, displayParams.scale));  
  42.           
  43.         MyTextView myTextView3 = new MyTextView(this);  
  44.         myTextView3.setText("右下的文本");  
  45.         myTextView3.setTextSize(15);  
  46.         myTextView3.setTextAlign(MyTextView.TEXT_ALIGN_BOTTOM | MyTextView.TEXT_ALIGN_RIGHT);  
  47.         myTextView3.setTextColor(Color.RED);  
  48.         myTextView3.setBackgroundColor(Color.BLUE);  
  49.         container.addView(myTextView3, LinearLayout.LayoutParams.MATCH_PARENT, DisplayUtil.dip2px(150, displayParams.scale));  
  50.           
  51.         MyTextView myTextView4 = new MyTextView(this);  
  52.         myTextView4.setText("左下的文本");  
  53.         myTextView4.setTextSize(15);  
  54.         myTextView4.setTextAlign(MyTextView.TEXT_ALIGN_BOTTOM | MyTextView.TEXT_ALIGN_LEFT);  
  55.         myTextView4.setTextColor(Color.YELLOW);  
  56.         myTextView4.setBackgroundColor(Color.GREEN);  
  57.         container.addView(myTextView4, LinearLayout.LayoutParams.MATCH_PARENT, DisplayUtil.dip2px(150, displayParams.scale));  
  58.           
  59.         MyTextView myTextView5 = new MyTextView(this);  
  60.         myTextView5.setText("中下的文本");  
  61.         myTextView5.setTextSize(35);  
  62.         myTextView5.setTextAlign(MyTextView.TEXT_ALIGN_BOTTOM | MyTextView.TEXT_ALIGN_CENTER_HORIZONTAL);  
  63.         myTextView5.setTextColor(Color.GRAY);  
  64.         myTextView5.setBackgroundColor(Color.RED);  
  65.         container.addView(myTextView5, LinearLayout.LayoutParams.MATCH_PARENT, DisplayUtil.dip2px(150, displayParams.scale));  
  66.           
  67.         MyTextView myTextView6 = new MyTextView(this);  
  68.         myTextView6.setText("居右的文本");  
  69.         myTextView6.setTextSize(25);  
  70.         myTextView6.setTextAlign(MyTextView.TEXT_ALIGN_RIGHT | MyTextView.TEXT_ALIGN_CENTER_VERTICAL);  
  71.         myTextView6.setTextColor(Color.BLUE);  
  72.         myTextView6.setBackgroundColor(Color.YELLOW);  
  73.         container.addView(myTextView6, LinearLayout.LayoutParams.MATCH_PARENT, DisplayUtil.dip2px(150, displayParams.scale));  
  74.           
  75.         MyTextView myTextView7 = new MyTextView(this);  
  76.         myTextView7.setText("左上的文本");  
  77.         myTextView7.setTextSize(25);  
  78.         myTextView7.setTextAlign(MyTextView.TEXT_ALIGN_TOP | MyTextView.TEXT_ALIGN_LEFT);  
  79.         myTextView7.setTextColor(Color.GREEN);  
  80.         myTextView7.setBackgroundColor(Color.CYAN);  
  81.         container.addView(myTextView7, LinearLayout.LayoutParams.MATCH_PARENT, DisplayUtil.dip2px(150, displayParams.scale));  
  82.           
  83.         MyTextView myTextView8 = new MyTextView(this);  
  84.         myTextView8.setText("中上的文本");  
  85.         myTextView8.setTextSize(25);  
  86.         myTextView8.setTextAlign(MyTextView.TEXT_ALIGN_TOP | MyTextView.TEXT_ALIGN_CENTER_HORIZONTAL);  
  87.         myTextView8.setTextColor(Color.RED);  
  88.         myTextView8.setBackgroundColor(Color.GREEN);  
  89.         container.addView(myTextView8, LinearLayout.LayoutParams.MATCH_PARENT, DisplayUtil.dip2px(150, displayParams.scale));  
  90.           
  91.         MyTextView myTextView9 = new MyTextView(this);  
  92.         myTextView9.setText("右上的文本");  
  93.         myTextView9.setTextSize(25);  
  94.         myTextView9.setTextAlign(MyTextView.TEXT_ALIGN_TOP | MyTextView.TEXT_ALIGN_RIGHT);  
  95.         myTextView9.setTextColor(Color.YELLOW);  
  96.         myTextView9.setBackgroundColor(Color.BLUE);  
  97.         container.addView(myTextView9, LinearLayout.LayoutParams.MATCH_PARENT, DisplayUtil.dip2px(150, displayParams.scale));  
  98.           
  99.     }  
  100. }  
activity_main.xml
[html]  view plain copy
  1. <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:paddingBottom="@dimen/activity_vertical_margin"  
  6.     android:paddingLeft="@dimen/activity_horizontal_margin"  
  7.     android:paddingRight="@dimen/activity_horizontal_margin"  
  8.     android:paddingTop="@dimen/activity_vertical_margin"  
  9.     tools:context=".MainActivity" >  
  10.   
  11.     <LinearLayout   
  12.         android:id="@+id/container"  
  13.         android:layout_width="match_parent"  
  14.         android:layout_height="wrap_content"  
  15.         android:orientation="vertical"/>  
  16.   
  17. </ScrollView>  
另外,还用到了两个工具类,代码可以参考这篇文章http://blog.csdn.net/carrey1989/article/details/10360613

在进行垂直偏上和垂直偏下的设置时,关键是设置baseline的y坐标分别等于-fm.ascent和viewHeight - fm.bottom,意思就是可以让文字刚好不超过控件的边缘。

整体思路就是这样,如需转载,请注明转载地址http://blog.csdn.net/carrey1989/article/details/10399727


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值