深度揭秘!Android TextView 使用原理全解析

深度揭秘!Android TextView 使用原理全解析

一、TextView 概述

1.1 TextView 的作用与应用场景

在 Android 开发里,TextView 是一个基础且极为常用的 UI 组件,主要用于在界面上展示文本信息。它能显示各种类型的文本内容,像普通文字、数字、标点等,并且能够对文本的样式、大小、颜色等属性进行灵活设置。TextView 广泛应用于各类 Android 应用中,比如在新闻类应用里,标题和正文内容通常使用 TextView 展示;在电商应用中,商品的名称、价格等信息也会通过 TextView 呈现。

1.2 TextView 继承关系

TextView 继承自 View 类,这意味着它具备 View 类的基本特性,如测量、布局、绘制等。以下是 TextView 的继承关系代码:

java 复制代码
// TextView 类继承自 AppCompatTextView
public class TextView extends AppCompatTextView {
    // 类的具体实现
}

// AppCompatTextView 继承自 TextViewCompat.Widget
public class AppCompatTextView extends TextViewCompat.Widget<TextView> {
    // 类的具体实现
}

// TextViewCompat.Widget 继承自 android.widget.TextView
public static class Widget<T extends android.widget.TextView> extends android.widget.TextView {
    // 类的具体实现
}

从上述代码可以看出,TextView 经过多层继承,最终继承自 android.widget.TextView,这种继承结构使得 TextView 能够复用 View 类的许多功能,同时在此基础上进行扩展,以满足文本显示的特殊需求。

二、TextView 的创建与初始化

2.1 在 XML 布局文件中创建 TextView

在 Android 开发中,我们通常会在 XML 布局文件里创建 TextView 组件。以下是一个简单的 XML 布局文件示例:

xml 复制代码
<!-- 定义一个垂直方向的线性布局 -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <!-- 创建一个 TextView 组件 -->
    <TextView
        android:id="@+id/my_text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello, World!"
        android:textSize="20sp"
        android:textColor="#FF0000" />
</LinearLayout>

在这个示例中,我们创建了一个 LinearLayout 作为根布局,并在其中添加了一个 TextView。通过 android:id 属性为 TextView 指定了一个唯一的标识符,方便在 Java 代码中引用;android:layout_widthandroid:layout_height 属性分别设置了 TextView 的宽度和高度;android:text 属性设置了 TextView 要显示的文本内容;android:textSize 属性设置了文本的大小;android:textColor 属性设置了文本的颜色。

2.2 在 Java 代码中创建 TextView

除了在 XML 布局文件中创建 TextView,我们也可以在 Java 代码中动态创建 TextView。以下是一个在 Java 代码中创建 TextView 的示例:

java 复制代码
import android.os.Bundle;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // 创建一个线性布局
        LinearLayout linearLayout = new LinearLayout(this);
        linearLayout.setOrientation(LinearLayout.VERTICAL);

        // 创建一个 TextView 实例
        TextView textView = new TextView(this);
        // 设置 TextView 的文本内容
        textView.setText("Hello, Java!");
        // 设置文本大小
        textView.setTextSize(20);
        // 设置文本颜色
        textView.setTextColor(getResources().getColor(android.R.color.holo_red_dark));

        // 将 TextView 添加到线性布局中
        linearLayout.addView(textView);

        // 将线性布局设置为活动的内容视图
        setContentView(linearLayout);
    }
}

在这个示例中,我们在 MainActivityonCreate 方法中创建了一个 LinearLayout 和一个 TextView。首先,我们创建了 LinearLayout 并设置其方向为垂直方向;然后,创建了 TextView 实例,并通过 setText 方法设置文本内容,setTextSize 方法设置文本大小,setTextColor 方法设置文本颜色;最后,将 TextView 添加到线性布局中,并将线性布局设置为活动的内容视图。

2.3 TextView 的初始化过程

当 TextView 被创建时,会进行一系列的初始化操作。以下是 TextView 构造函数的部分源码:

java 复制代码
// TextView 的构造函数,接收上下文和属性集合作为参数
public TextView(Context context, @Nullable AttributeSet attrs) {
    // 调用父类的构造函数
    super(context, attrs);
    // 初始化 TextView 的属性
    initTextView(context, attrs, 0, 0);
}

// 初始化 TextView 的方法
private void initTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    // 获取属性解析器
    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TextView, defStyleAttr, defStyleRes);
    try {
        // 获取文本内容
        CharSequence text = a.getText(R.styleable.TextView_text);
        if (text != null) {
            // 设置文本内容
            setText(text);
        }
        // 获取文本大小
        float textSize = a.getDimension(R.styleable.TextView_textSize, mTextPaint.getTextSize());
        // 设置文本大小
        setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
        // 获取文本颜色
        int textColor = a.getColor(R.styleable.TextView_textColor, Color.BLACK);
        // 设置文本颜色
        setTextColor(textColor);
        // 其他属性的初始化...
    } finally {
        // 回收属性解析器
        a.recycle();
    }
}

TextView 的构造函数中,首先调用父类的构造函数,然后调用 initTextView 方法进行属性的初始化。在 initTextView 方法中,通过 TypedArray 从属性集合中获取各种属性值,如文本内容、文本大小、文本颜色等,并将这些值设置到 TextView 中。最后,回收 TypedArray 以释放资源。

三、TextView 的测量过程

3.1 测量的基本概念

在 Android 中,每个 View 都需要经过测量过程来确定其大小。测量过程由 measure 方法触发,最终会调用 onMeasure 方法来完成具体的测量操作。测量的结果会影响 View 在布局中的显示大小。

3.2 TextView 的 onMeasure 方法

以下是 TextView 的 onMeasure 方法的部分源码:

java 复制代码
// 重写 onMeasure 方法,用于测量 TextView 的大小
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // 保存原始的测量规格
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);

    // 计算文本的宽度和高度
    int desiredWidth = getDesiredWidth();
    int desiredHeight = getDesiredHeight();

    // 根据测量模式确定最终的宽度
    int measuredWidth = resolveSizeAndState(desiredWidth, widthMeasureSpec, 0);
    // 根据测量模式确定最终的高度
    int measuredHeight = resolveSizeAndState(desiredHeight, heightMeasureSpec, 0);

    // 设置测量结果
    setMeasuredDimension(measuredWidth, measuredHeight);
}

// 计算 TextView 所需的宽度
private int getDesiredWidth() {
    // 获取文本绘制的画笔
    TextPaint paint = getPaint();
    // 获取文本内容
    CharSequence text = getText();
    if (text != null) {
        // 计算文本的宽度
        return (int) paint.measureText(text.toString());
    }
    return 0;
}

// 计算 TextView 所需的高度
private int getDesiredHeight() {
    // 获取文本绘制的画笔
    TextPaint paint = getPaint();
    // 获取文本内容
    CharSequence text = getText();
    if (text != null) {
        // 获取字体的度量信息
        Paint.FontMetrics fm = paint.getFontMetrics();
        // 计算文本的高度
        return (int) (fm.bottom - fm.top);
    }
    return 0;
}

onMeasure 方法中,首先从测量规格中获取宽度和高度的测量模式以及大小。然后,调用 getDesiredWidthgetDesiredHeight 方法分别计算 TextView 所需的宽度和高度。getDesiredWidth 方法通过 TextPaintmeasureText 方法计算文本的宽度;getDesiredHeight 方法通过 Paint.FontMetrics 获取字体的度量信息,从而计算出文本的高度。最后,使用 resolveSizeAndState 方法根据测量模式确定最终的宽度和高度,并通过 setMeasuredDimension 方法设置测量结果。

3.3 测量模式的影响

测量模式分为三种:EXACTLYAT_MOSTUNSPECIFIED。不同的测量模式会影响 TextView 最终的测量结果。以下是 resolveSizeAndState 方法的源码:

java 复制代码
// 根据测量模式确定最终的大小
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
    // 获取测量模式
    final int specMode = MeasureSpec.getMode(measureSpec);
    // 获取测量大小
    final int specSize = MeasureSpec.getSize(measureSpec);
    final int result;
    switch (specMode) {
        // 精确模式,使用测量规格中的大小
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        // 最大模式,使用所需大小和测量规格中的大小的较小值
        case MeasureSpec.AT_MOST:
            if (size <= specSize) {
                result = size;
            } else {
                result = specSize;
                childMeasuredState |= MEASURED_STATE_TOO_SMALL;
            }
            break;
        // 未指定模式,使用所需大小
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        default:
            result = size;
            break;
    }
    return result | (childMeasuredState & MEASURED_STATE_MASK);
}

当测量模式为 EXACTLY 时,TextView 的大小将被设置为测量规格中指定的大小;当测量模式为 AT_MOST 时,TextView 的大小将取所需大小和测量规格中指定大小的较小值;当测量模式为 UNSPECIFIED 时,TextView 的大小将被设置为所需大小。

四、TextView 的布局过程

4.1 布局的基本概念

布局过程是指 View 在其父容器中的位置和大小的确定过程。布局过程由 layout 方法触发,最终会调用 onLayout 方法来完成具体的布局操作。

4.2 TextView 的 onLayout 方法

以下是 TextView 的 onLayout 方法的部分源码:

java 复制代码
// 重写 onLayout 方法,用于布局 TextView
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    // 调用父类的 onLayout 方法
    super.onLayout(changed, left, top, right, bottom);
    // 处理布局变化
    if (changed) {
        // 重新计算文本的布局
        layoutText();
    }
}

// 重新计算文本的布局
private void layoutText() {
    // 获取文本内容
    CharSequence text = getText();
    if (text != null) {
        // 获取文本绘制的画笔
        TextPaint paint = getPaint();
        // 创建文本布局对象
        StaticLayout layout = new StaticLayout(text, paint, getWidth(),
                Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
        // 设置文本布局
        setTextLayout(layout);
    }
}

onLayout 方法中,首先调用父类的 onLayout 方法,然后判断布局是否发生变化。如果布局发生变化,则调用 layoutText 方法重新计算文本的布局。在 layoutText 方法中,创建一个 StaticLayout 对象,该对象用于处理文本的布局,然后将其设置到 TextView 中。

4.3 布局参数的影响

布局参数会影响 TextView 在父容器中的位置和大小。例如,在 XML 布局文件中设置的 android:layout_marginandroid:layout_gravity 等属性都会影响 TextView 的布局。以下是一个设置了布局参数的 XML 示例:

xml 复制代码
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/my_text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello, World!"
        android:textSize="20sp"
        android:textColor="#FF0000"
        android:layout_marginTop="20dp"
        android:layout_gravity="center_horizontal" />
</LinearLayout>

在这个示例中,android:layout_marginTop 属性设置了 TextView 距离顶部的边距,android:layout_gravity 属性设置了 TextView 在父容器中的对齐方式。

五、TextView 的绘制过程

5.1 绘制的基本概念

绘制过程是指将 View 的内容绘制到屏幕上的过程。绘制过程由 draw 方法触发,最终会调用 onDraw 方法来完成具体的绘制操作。

5.2 TextView 的 onDraw 方法

以下是 TextView 的 onDraw 方法的部分源码:

java 复制代码
// 重写 onDraw 方法,用于绘制 TextView
@Override
protected void onDraw(Canvas canvas) {
    // 调用父类的 onDraw 方法
    super.onDraw(canvas);
    // 获取文本内容
    CharSequence text = getText();
    if (text != null) {
        // 获取文本绘制的画笔
        TextPaint paint = getPaint();
        // 获取文本布局
        Layout layout = getTextLayout();
        if (layout != null) {
            // 保存画布状态
            canvas.save();
            // 移动画布到文本的起始位置
            canvas.translate(getPaddingLeft(), getPaddingTop());
            // 绘制文本
            layout.draw(canvas, paint, null, 0);
            // 恢复画布状态
            canvas.restore();
        }
    }
}

onDraw 方法中,首先调用父类的 onDraw 方法,然后获取文本内容和文本布局。如果文本布局不为空,则保存画布状态,将画布移动到文本的起始位置,调用 Layoutdraw 方法绘制文本,最后恢复画布状态。

5.3 绘制顺序和优化

TextView 的绘制过程中,会按照一定的顺序进行绘制。例如,先绘制背景,再绘制文本。为了提高绘制效率,可以对绘制过程进行优化。例如,避免在 onDraw 方法中进行频繁的对象创建和计算,尽量复用已有的对象。以下是一个简单的优化示例:

java 复制代码
// 重写 onDraw 方法,进行绘制优化
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    // 获取文本内容
    CharSequence text = getText();
    if (text != null) {
        // 获取文本绘制的画笔
        TextPaint paint = getPaint();
        // 获取文本布局
        Layout layout = getTextLayout();
        if (layout != null) {
            // 保存画布状态
            canvas.save();
            // 移动画布到文本的起始位置
            canvas.translate(getPaddingLeft(), getPaddingTop());
            // 绘制文本
            layout.draw(canvas, paint, null, 0);
            // 恢复画布状态
            canvas.restore();
        }
    }
    // 避免在 onDraw 方法中创建新的对象
    // 例如:不要在这里创建新的 Paint 对象
    // Paint newPaint = new Paint(); 
}

在这个示例中,避免在 onDraw 方法中创建新的 Paint 对象,而是复用已有的 TextPaint 对象,从而提高绘制效率。

六、TextView 的文本处理

6.1 文本的设置与获取

TextView 中,可以通过 setText 方法设置文本内容,通过 getText 方法获取文本内容。以下是示例代码:

java 复制代码
// 获取 TextView 实例
TextView textView = findViewById(R.id.my_text_view);
// 设置文本内容
textView.setText("New Text");
// 获取文本内容
CharSequence text = textView.getText();

setText 方法有多个重载版本,可以接受不同类型的参数,如 StringCharSequence 等。以下是 setText 方法的部分源码:

java 复制代码
// 设置文本内容,接受 CharSequence 类型的参数
public void setText(CharSequence text) {
    // 调用设置文本的核心方法
    setText(text, BufferType.NORMAL);
}

// 设置文本内容,接受 CharSequence 类型和 BufferType 类型的参数
public void setText(CharSequence text, BufferType type) {
    // 处理文本设置的逻辑
    // ...
}

setText 方法中,会根据传入的文本内容和 BufferType 进行相应的处理。

6.2 文本样式的设置

可以通过 setTextSizesetTextColorsetTypeface 等方法设置文本的样式。以下是示例代码:

java 复制代码
// 获取 TextView 实例
TextView textView = findViewById(R.id.my_text_view);
// 设置文本大小
textView.setTextSize(24);
// 设置文本颜色
textView.setTextColor(Color.BLUE);
// 设置字体样式
Typeface typeface = Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD_ITALIC);
textView.setTypeface(typeface);

setTextSize 方法用于设置文本的大小,setTextColor 方法用于设置文本的颜色,setTypeface 方法用于设置字体样式。以下是 setTextSize 方法的部分源码:

java 复制代码
// 设置文本大小,接受单位和大小值作为参数
public void setTextSize(int unit, float size) {
    // 获取资源对象
    Context c = getContext();
    Resources r;
    if (c == null) {
        r = Resources.getSystem();
    } else {
        r = c.getResources();
    }
    // 根据单位转换大小值
    setRawTextSize(TypedValue.applyDimension(unit, size, r.getDisplayMetrics()));
}

// 设置原始文本大小
private void setRawTextSize(float size) {
    if (size != mTextPaint.getTextSize()) {
        // 更新文本大小
        mTextPaint.setTextSize(size);
        // 重新计算文本布局
        requestLayout();
        // 重绘 TextView
        invalidate();
    }
}

setTextSize 方法中,会根据传入的单位和大小值进行转换,然后调用 setRawTextSize 方法更新文本大小。在 setRawTextSize 方法中,会更新 TextPaint 的文本大小,并调用 requestLayout 方法重新计算文本布局,调用 invalidate 方法重绘 TextView

6.3 文本的换行与截断

TextView 中,可以通过 android:maxLinesandroid:ellipsize 等属性设置文本的换行和截断方式。以下是一个 XML 示例:

xml 复制代码
<TextView
    android:id="@+id/my_text_view"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="This is a long text that may need to be wrapped or truncated."
    android:maxLines="2"
    android:ellipsize="end" />

在这个示例中,android:maxLines 属性设置了文本最多显示的行数,android:ellipsize 属性设置了文本截断的方式。当文本超过指定的行数时,会根据 android:ellipsize 属性的值进行截断。以下是 TextView 处理文本截断的部分源码:

java 复制代码
// 处理文本截断
private void applyEllipsize(Layout layout) {
    // 获取最大行数
    int maxLines = getMaxLines();
    if (maxLines > 0) {
        // 获取文本布局的行数
        int lineCount = layout.getLineCount();
        if (lineCount > maxLines) {
            // 获取截断方式
            TextUtils.TruncateAt ellipsize = getEllipsize();
            if (ellipsize != null) {
                // 处理文本截断
                CharSequence truncatedText = TextUtils.ellipsize(getText(), getPaint(),
                        layout.getWidth(), ellipsize);
                // 设置截断后的文本
                setText(truncatedText);
            }
        }
    }
}

applyEllipsize 方法中,会根据 maxLinesellipsize 属性的值处理文本截断。如果文本的行数超过 maxLines,则根据 ellipsize 属性的值对文本进行截断,并设置截断后的文本。

七、TextView 的事件处理

7.1 点击事件处理

可以为 TextView 设置点击事件监听器,当用户点击 TextView 时,会触发相应的事件处理逻辑。以下是示例代码:

java 复制代码
// 获取 TextView 实例
TextView textView = findViewById(R.id.my_text_view);
// 设置点击事件监听器
textView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // 处理点击事件
        Toast.makeText(MainActivity.this, "TextView clicked!", Toast.LENGTH_SHORT).show();
    }
});

在这个示例中,通过 setOnClickListener 方法为 TextView 设置了一个点击事件监听器。当用户点击 TextView 时,会弹出一个 Toast 提示框。以下是 setOnClickListener 方法的部分源码:

java 复制代码
// 设置点击事件监听器
public void setOnClickListener(@Nullable OnClickListener l) {
    if (!isClickable()) {
        // 设置为可点击
        setClickable(true);
    }
    // 设置点击事件监听器
    getListenerInfo().mOnClickListener = l;
}

setOnClickListener 方法中,会先判断 TextView 是否可点击,如果不可点击则将其设置为可点击,然后将传入的点击事件监听器保存到 ListenerInfo 对象中。

7.2 长按事件处理

可以为 TextView 设置长按事件监听器,当用户长按 TextView 时,会触发相应的事件处理逻辑。以下是示例代码:

java 复制代码
// 获取 TextView 实例
TextView textView = findViewById(R.id.my_text_view);
// 设置长按事件监听器
textView.setOnLongClickListener(new View.OnLongClickListener() {
    @Override
    public boolean onLongClick(View v) {
        // 处理长按事件
        Toast.makeText(MainActivity.this, "TextView long clicked!", Toast.LENGTH_SHORT).show();
        return true;
    }
});

在这个示例中,通过 setOnLongClickListener 方法为 TextView 设置了一个长按事件监听器。当用户长按 TextView 时,会弹出一个 Toast 提示框。以下是 setOnLongClickListener 方法的部分源码:

java 复制代码
// 设置长按事件监听器
public void setOnLongClickListener(@Nullable OnLongClickListener l) {
    if (!isLongClickable()) {
        // 设置为可长按
        setLongClickable(true);
    }
    // 设置长按事件监听器
    getListenerInfo().mOnLongClickListener = l;
}

setOnLongClickListener 方法中,会先判断 TextView 是否可长按,如果不可长按则将其设置为可长按,然后将传入的长按事件监听器保存到 ListenerInfo 对象中。

7.3 触摸事件处理

可以为 TextView 设置触摸事件监听器,当用户触摸 TextView 时,会触发相应的事件处理逻辑。以下是示例代码:

java 复制代码
// 获取 TextView 实例
TextView textView = findViewById(R.id.my_text_view);
// 设置触摸事件监听器
textView.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        // 处理触摸事件
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // 处理按下事件
                break;
            case MotionEvent.ACTION_MOVE:
                // 处理移动事件
                break;
            case MotionEvent.ACTION_UP:
                // 处理抬起事件
                break;
        }
        return false;
    }
});

在这个示例中,通过 setOnTouchListener 方法为 TextView 设置了一个触摸事件监听器。当用户触摸 TextView 时,会根据触摸事件的类型进行相应的处理。以下是 setOnTouchListener 方法的部分源码:

java 复制代码
// 设置触摸事件监听器
public void setOnTouchListener(@Nullable OnTouchListener l) {
    // 设置触摸事件监听器
    getListenerInfo().mOnTouchListener = l;
}

setOnTouchListener 方法中,会将传入的触摸事件监听器保存到 ListenerInfo 对象中。

八、TextView 的高级特性

8.1 富文本显示

TextView 支持富文本显示,可以通过 SpannableStringHtml 类来实现。以下是一个使用 SpannableString 实现富文本显示的示例:

java 复制代码
// 获取 TextView 实例
TextView textView = findViewById(R.id.my_text_view);
// 创建 SpannableString 对象
SpannableString spannableString = new SpannableString("This is a bold and colored text.");
// 设置部分文本为粗体
spannableString.setSpan(new StyleSpan(Typeface.BOLD), 0, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
// 设置部分文本为红色
spannableString.setSpan(new ForegroundColorSpan(Color.RED), 10, 14, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
// 设置富文本内容
textView.setText(spannableString);

在这个示例中,创建了一个 SpannableString 对象,并通过 setSpan 方法设置了部分文本的样式,最后将 SpannableString 对象设置到 TextView 中。以下是 SpannableString 的部分源码:

java 复制代码
// SpannableString 类继承自 SpannableStringInternal 并实现了 Spannable 接口
public class SpannableString extends SpannableStringInternal implements Spannable {
    // 构造函数,接受 CharSequence 类型的参数
    public SpannableString(CharSequence source) {
        super(source, 0, source.length());
    }

    // 设置跨度
    @Override
    public void setSpan(Object what, int start, int end, int flags) {
        super.setSpan(what, start, end, flags);
    }
}

SpannableString 类中,通过 setSpan 方法设置文本的跨度,从而实现富文本显示。

8.2 文本选择与复制

TextView 支持文本选择和复制功能。可以通过 setMovementMethod 方法设置 MovementMethod 来启用文本选择功能。以下是示例代码:

java 复制代码
// 获取 TextView 实例
TextView textView = findViewById(R.id.my_text_view);
// 设置文本选择功能
textView.setMovementMethod(LinkMovementMethod.getInstance());
// 设置可复制
textView.setTextIsSelectable(true);

在这个示例中,通过 setMovementMethod 方法设置 LinkMovementMethod 来启用文本选择功能,通过 setTextIsSelectable 方法设置 TextView 可复制。以下是 setMovementMethod 方法的部分源码:

java 复制代码
// 设置移动方法
public void setMovementMethod(MovementMethod movement) {
    if (mMovement != movement) {
        // 移除之前的移动方法
        if (mMovement != null) {
            mMovement.onDetachedFromWindow(this);
        }
        // 设置新的移动方法
        mMovement = movement;
        if (mMovement != null) {
            mMovement.onAttachedToWindow(this);
        }
        // 重绘 TextView
        invalidate();
    }
}

setMovementMethod 方法中,会先移除之前的 MovementMethod,然后设置新的 MovementMethod,并调用 onAttachedToWindow 方法进行初始化,最后重绘 TextView

8.3 文本滚动

可以通过设置 android:scrollbars 属性和 setMovementMethod 方法来实现文本滚动功能。以下是一个 XML 示例:

xml 复制代码
<TextView
    android:id="@+id/my_text_view"
    android:layout_width="wrap_content"
    android:layout_height="200dp"
    android:text="This is a long text that may need to be scrolled."
    android:scrollbars="vertical"
    android:movementMethod="scrolling" />

在这个示例中,android:scrollbars 属性设置了滚动条的方向,android:movementMethod 属性设置了移动方法为滚动。以下是 TextView 处理文本滚动的部分源码:

java 复制代码
// 处理文本滚动
private void handleScrolling() {
    // 获取滚动条方向
    int scrollbars = getVerticalScrollbarPosition();
    if (scrollbars != 0) {
        // 设置滚动条可见
        setVerticalScrollBarEnabled(true);
        // 处理滚动逻辑
        // ...
    } else {
        // 设置滚动条不可见
        setVerticalScrollBarEnabled(false);
    }
}

handleScrolling 方法中,会根据滚动条的方向设置滚动条的可见性,并处理滚动逻辑。

九、TextView 的性能优化

9.1 避免频繁重绘

频繁重绘会影响 TextView 的性能。可以通过避免在 onDraw 方法中进行频繁的对象创建和计算,以及合理使用 invalidatepostInvalidate 方法来减少重绘次数。以下是一个优化示例:

java 复制代码
// 避免在 onDraw 方法中创建新的对象
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    // 获取文本内容
    CharSequence text = getText();
    if (text != null) {
        // 获取文本绘制的画笔
        TextPaint paint = getPaint();
        // 获取文本布局
        Layout layout = getTextLayout();
        if (layout != null) {
            // 保存画布状态
            canvas.save();
            // 移动画布到文本的起始位置
            canvas.translate(getPaddingLeft(), getPaddingTop());
            // 绘制文本
            layout.draw(canvas, paint, null, 0);
            // 恢复画布状态
            canvas.restore();
        }
    }
    // 不要在这里创建新的 Paint 对象
    // Paint newPaint = new Paint(); 
}

// 合理使用 invalidate 和 postInvalidate 方法
public void updateText(String newText) {
    // 设置新的文本内容
    setText(newText);
    // 仅在必要时调用 invalidate 或 postInvalidate 方法
    if (needInvalidate()) {
        if (isInEditMode()) {
            invalidate();
        } else {
            postInvalidate();
        }
    }
}

// 判断是否需要重绘
private boolean needInvalidate() {
    // 根据具体情况判断是否需要重绘
    return true;
}

在这个示例中,避免在 onDraw 方法中创建新的 Paint 对象,同时在 updateText 方法中,仅在必要时调用 invalidatepostInvalidate 方法进行重绘。

9.2 优化文本布局

合理设置 TextView 的属性,如 maxLinesellipsize 等,可以减少文本布局的计算量。以下是一个优化示例:

xml 复制代码
<TextView
    android:id="@+id/my_text_view"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="This is a long text that may need to be wrapped or truncated."
    android:maxLines="2"
    android:ellipsize="end" />

在这个示例中,通过设置 maxLinesellipsize 属性,避免了不必要的文本布局计算。

9.3 减少内存占用

可以通过复用 PaintSpannableString 等对象来减少内存占用。以下是一个复用 SpannableString 对象的示例:

java 复制代码
// 复用 SpannableString 对象
private SpannableString reusableSpannableString;

public void updateTextWithSpannable(String text) {
    if (reusableSpannableString == null) {
        reusableSpannableString = new SpannableString(text);
    } else {
        reusableSpannableString.clearSpans();
        reusableSpannableString = new SpannableString(text);
    }
    // 设置部分文本为粗体
    reusableSpannableString.setSpan(new StyleSpan(Typeface.BOLD), 0, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    // 设置部分文本为红色
    reusableSpannableString.setSpan(new ForegroundColorSpan(Color.RED), 10, 14, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    // 设置富文本内容
    TextView textView = findViewById(R.id.my_text_view);
    textView.setText(reusableSpannableString);
}

在这个示例中,通过复用 SpannableString 对象,减少了内存的分配和回收,从而降低了内存占用。

十、总结与展望

10.1 总结

通过对 Android TextView 使用原理的深入分析,我们了解到 TextView 作为 Android 开发中最基础且常用的 UI 组件,其功能丰富且灵活。从创建与初始化,到测量、布局、绘制过程,再到文本处理、事件处理以及高级特性和性能优化,每个环节都涉及到众多的源码逻辑和细节。

在创建与初始化方面,TextView 既可以在 XML 布局文件中定义,也可以在 Java 代码中动态创建,并且会在初始化过程中根据属性集合设置各种属性值。测量过程根据测量模式和文本内容确定 TextView 的大小,布局过程确定其在父容器中的位置,绘制过程

相关推荐
_一条咸鱼_2 小时前
揭秘 Android TextInputLayout:从源码深度剖析其使用原理
android·java·面试
_一条咸鱼_2 小时前
揭秘!Android VideoView 使用原理大起底
android·java·面试
_一条咸鱼_3 小时前
深度剖析:Android Canvas 使用原理全揭秘
android·java·面试
_一条咸鱼_3 小时前
深度剖析!Android TextureView 使用原理全揭秘
android·java·面试
_一条咸鱼_3 小时前
揭秘!Android CheckBox 使用原理全解析
android·java·面试
_一条咸鱼_3 小时前
深度揭秘:Android Toolbar 使用原理的源码级剖析
android·java·面试
_一条咸鱼_3 小时前
揭秘 Java ArrayList:从源码深度剖析其使用原理
android·java·面试