TextView源码解析-----绘制过程

简介


看段Android官方的简介

Class Overview
Displays text to the user and optionally allows them to edit it. A TextView is a complete text editor, however the basic class is configured to not allow editing; see EditText for a subclass that configures the text view for editing.
To allow users to copy some or all of the TextView’s value and paste it somewhere else, set the XML attribute android:textIsSelectable to “true” or call setTextIsSelectable(true). The textIsSelectable flag allows users to make selection gestures in the TextView, which in turn triggers the system’s built-in copy/paste controls.

TextView主要用于给用户展示文字,并且让用户随意的可以对文字进行编辑。但是普通的TextView是不允许用来编辑的,只有EditText才可以。
如果在XML中设置了android:textIsSelectable 或者在Java代码中调用了setTextIsSelectable(true)方法,就可以允许对TextView的部分或者全部文字进行复制,然后粘贴到其他地方。textIsSelectable 标签是允许用户在TextView上使用选择手势。

顺便提下,大家如果想看API文档的话,可以在
file:///E:/AndroidEnvironment/SDK/docs/reference/android/widget/TextView.html
你安装SDK的目录下/docs/reference/android/widget/TextView.html找到你想要查看控件的API

分析思路


一般自定义view都需要满足2个条件,展示我们期望的UI,正确传递或者接收处理点击或者触摸事件。
所以对于TextView的分析也从这三个地方展开

  1. 绘制过程

    onMeasure()
    onLayout()
    onDraw()

  2. 事件接收处理

    由于TextView继承于View,所以主要分析onTouchEvent()方法就好了

  3. 一些和TextView有关的类如何实现,比如Spans,Layout,接收输入的InputConnection

本文基于Android SDK API-19的基础上分析

在分析之前,我们先来看个小彩蛋

这里写图片描述

不知道这个//TODO是某个哥们自问自答呢,还是别人在对他的代码review的时候给注上的

再分析之前,顺便抛出一个问题供大家思考下,maxEms这个属性到底是用来做什么的?
网上的答案五花八门,在下面的源码中我们可以一窥究竟。

绘制过程


首先来看onMeasure()部分代码

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    //首先接收到父容器传递过来的MeasureSpec
    //关于MeasureSpec是如果计算的,可以查看之前的博文
    //[LinearLayout源码解析](http://blog.csdn.net/wz249863091/article/details/51702980)
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int width;
        int height;

        //这里解释下什么叫作boring
        //A BoringLayout is a very simple Layout implementation for text that 
        //fits on a single line and is all left-to-right characters.
        //boring就是指布局所用的文本里面不包含任何Span,所有的文本方向都是从左到右的布局,
        //并且仅需一行就能显示完全的布局
        //这里将TextView和Hint的boring初始化
        BoringLayout.Metrics boring = UNKNOWN_BORING;
        BoringLayout.Metrics hintBoring = UNKNOWN_BORING;

        //获得文字的排序方式。一共有6种
        //FIRSTSTRONG_RTL,FIRSTSTRONG_LTR Unicode双向算法
        //ANYRTL_LTR
        //LTR,RTL 左到右或者右到左排序
        //LOCALE
        //first strong算法 有兴趣的同学可以自行研究下,一般情况下都是左到右排序
        if (mTextDir == null) {
            mTextDir = getTextDirectionHeuristic();
        }

        int des = -1;
        boolean fromexisting = false;

        //如果宽度是精确模式了,那就那父容器给的宽度当作当前TextView的宽度
        if (widthMode == MeasureSpec.EXACTLY) {
            // Parent has told us how big to be. So be it.
            width = widthSize;
        } else {
            if (mLayout != null && mEllipsize == null) {
            //首先计算下期望值,如果行数大于1就返回-1,否则返回单行宽度
            //具体代码贴在下面
                des = desired(mLayout);
            }

            //如果小于0,即行数大于1行,就去判断是否是boring
            //isBoring()这个方法也在下面有详细分析,大家可以阅读后
            //再回过头来看看
            if (des < 0) {
                boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
                //阅读过下面的方法,就知道boring是一个Metrics矩阵,
                //包含了文本样式 width, ascent, and descen等
                if (boring != null) {
                    mBoring = boring;
                }
            } else {
                fromexisting = true;
            }

            //再次判断boring是否为null
            //这里有2种情况会为null
            //1.des>0,即Textview只显示一行文字,就不会去计算boring的值了
            //2.Textview包含的内容不是boring的,多行,有缩进或者包含spann
            if (boring == null || boring == UNKNOWN_BORING) {
                //如果是多行文字的
                if (des < 0) {
                    des = (int) FloatMath.ceil(Layout.getDesiredWidth(mTransformed, mTextPaint));
                }
                width = des;
            } else {
            //如果是boring模式的就很简单了,把boring刚测量得到的width赋给TextView
            //即文字的宽度
                width = boring.width;
            }

            //这里就是加上Drawable的宽度
            final Drawables dr = mDrawables;
            if (dr != null) {
                width = Math.max(width, dr.mDrawableWidthTop);
                width = Math.max(width, dr.mDrawableWidthBottom);
            }

            //这里会再计算一次hint的宽度,流程和上面的一模一样,就不再重复了
            if (mHint != null) {
                int hintDes = -1;
                int hintWidth;

                if (mHintLayout != null && mEllipsize == null) {
                    hintDes = desired(mHintLayout);
                }

                if (hintDes < 0) {
                    hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring);
                    if (hintBoring != null) {
                        mHintBoring = hintBoring;
                    }
                }

                if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
                    if (hintDes < 0) {
                        hintDes = (int) FloatMath.ceil(Layout.getDesiredWidth(mHint, mTextPaint));
                    }
                    hintWidth = hintDes;
                } else {
                    hintWidth = hintBoring.width;
                }

                if (hintWidth > width) {
                    width = hintWidth;
                }
            }

            //这里再加上padding的值
            //顺便说一句,padding的值是在子view里自己算的
            //margin的值是在父容器里算的
            //在自定义view和viewgroup的时候,千万注意
            //width += getCompoundPaddingLeft() + getCompoundPaddingRight();

            //在这,就能解答之前的疑问,EMS这个属性到底是干嘛的
            //如果我们设置了maxEms这个属性
            //public void setMaxEms(int maxems) {
   
           // mMaxWidth = maxems;
            //mMaxWidthMode = EMS;

            //requestLayout();
            //invalidate();
                //}
            //mMaxWidth的值就是EMS的值
            //如果设置了maxLength,那么mMaxWidth的值就是maxWidth的值
            //然后再来看如果是EMS模式
            //Math.min(width, mMa
  • 13
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
sp-date-picker是一个日期选择器,它可以直接在页面上占据一块区域,并且不会自动关闭。通常情况下,我们使用已经封装好的日期选择会话框DatePickerDialog来代替DatePicker,因为DatePickerDialog相当于在AlertDialog上加载了DatePicker,只需调用构造函数设置一下当前年、月、日,然后调用show方法即可弹出日期对话框。日期选择事件由监听器OnDateSetListener负责响应,在该监听器实现的onDateSet方法中,开发者能够获得用户选择的具体日期,并做后续处理。需要注意的是,onDateSet方法的月份参数,该参数的起始值不是1而是0。 下面是一个使用sp-date-picker的例子: ```java // 引入DatePickerDialog import android.app.DatePickerDialog; import android.widget.DatePicker; import android.widget.TextView; // 获取TextView控件 TextView tvDate = findViewById(R.id.tv_date); // 获取当前日期 Calendar calendar = Calendar.getInstance(); int year = calendar.get(Calendar.YEAR); int month = calendar.get(Calendar.MONTH); int day = calendar.get(Calendar.DAY_OF_MONTH); // 创建DatePickerDialog实例 DatePickerDialog datePickerDialog = new DatePickerDialog(this, new DatePickerDialog.OnDateSetListener() { @Override public void onDateSet(DatePicker view, int year, int month, int dayOfMonth) { // 处理日期选择事件 String date = year + "-" + (month + 1) + "-" + dayOfMonth; tvDate.setText(date); } }, year, month, day); // 显示日期选择对话框 datePickerDialog.show(); ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值