TextView基础

TextView 基本设置

基础设置

TextView 设置字体粗细

  1. 布局里设置
android:textStyle="bold"
  1. 代码设置
//android中为textview动态设置字体为粗体
TextView textView = (TextView)findViewById(R.id.textView);
textView .setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
//设置不为加粗
textView.setTypeface(Typeface.defaultFromStyle(Typeface.NORMAL));
  1. strings.xml 设置
<string name="styled_text">Plain, <b>bold</b>, <i>italic</i>, <b><i>bold-italic</i></b></string>

TextView 的 drawable

TextView 的 drawable xml 属性

TextView 有一些属性可以在 Text 的四周设置一个 drawable 对象,图片,shape 等合法的 drawable 都可以用。

drawableStart API 14才有
drawableLeft
drawableTop
drawableBottom
drawableRight
drawableEnd API 14才有
drawablePadding 用以设置drawable与text之间的空间

left/top/right/bottom 就是在文字的上下左右放置 drawable。而 drawableStart 和 drawableEnd 则有特殊的意义,虽然它们是 API 14 加上去的,但是要在 API 17 后才能真正的生效,它们的作用是当语言方向发生变化时,会换边,LTR 语言 drawableStart 在左边,而 drawableEnd 在右边;但对于 RTL 语言来说就会反过来 drawableStart 在右,drawableEnd 在左

TextView 的 drawable 的一些注意事项

  1. TextView 的 padding 作用在 drawable 之外
  2. TextView 的高度或宽度为 wrap_content 时将是文字和 drawable 中较大的那一个,再加上 padding 和 margin
  3. gravity 只对文字起作用,对 drawable 不起作用
  4. drawable 会在其所在的维度居中显示,比如 drawableLeft 是上下垂直居中的,以此类推

代码设置 setCompoundDrawables 和 setCompoundDrawablesWithIntrinsicBounds

public void setCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom)
// 设置Drawable显示在text的左、上、右、下位置。 要先设置setBounds(x,x,x,x);  The Drawables must already have had Drawable.setBounds called.

setCompoundDrawablesWithIntrinsicBounds 是画的 drawable 的宽高是按 drawable 固定的宽高,所以才有 The Drawables' bounds will be set to their intrinsic bounds。即通过 getIntrinsicWidth() 与 getIntrinsicHeight() 获得.

setCompoundDrawables 和 setCompoundDrawablesRelative

可以在上、下、左、右设置图标,如果不想在某个地方显示,则设置为 null。但是 Drawable 必须已经 setBounds(Rect)。意思是你要添加的资源必须已经设置过初始位置、宽和高等信息。

  1. 需要设置 setBounds;就是不按比例,宽高可以打破原有的大小及比例
  2. 要适配 RTL 用 setCompoundDrawablesRelative

和 setCompoundDrawablesWithIn 和 setCompoundDrawablesRelativeWithIntrinsicBounds

可以在上、下、左、右设置图标,如果不想在某个地方显示,则设置为 null。图标的宽高将会设置为固有宽高,既自动通过 getIntrinsicWidth 和 getIntrinsicHeight 获取。

  1. 不需要设置 setBounds;按照原有比例大小显示图片
  2. 要适配 RTL 用 setCompoundDrawablesRelativeWithIntrinsicBounds
val bitmap = BitmapDrawable(resources, it)
val width = resources.getDimensionPixelSize(R.dimen.qb_px_16)
bitmap.setBounds(0, 0, width, width)
setCompoundDrawablesRelative(null, null, bitmap, null)

setCompoundDrawables 和 setCompoundDrawablesWithIn

setCompoundDrawables 画的 drawable 的宽高是按 drawable.setBound() 设置的宽高,使用之前必须使用 Drawable.setBounds 设置 Drawable 的长宽。

setCompoundDrawablesWithIntrinsicBounds 是画的 drawable 的宽高是按 drawable 固定的宽高,所以才有 The Drawables' bounds will be set to their intrinsic bounds。即通过 getIntrinsicWidth() 与 getIntrinsicHeight() 获得。

setBounds

setBounds(x,y,width,height);

  1. x: 组件在容器 X 轴上的起点
  2. y: 组件在容器 Y 轴上的起点
  3. width: 组件的长度
  4. height: 组件的高度。
Drawable myImage = getResources().getDrawable(R.drawable.home);
myImage.setBounds(1, 1, 100, 100);
button.setCompoundDrawables(null, myImage, null, null);

局限性

有些时候它也有一些局限性而没有办法用它:

  1. 当 drawable 本身没有高度时(比如 shape),这个 drawable 高度就会依赖于文字,因为 padding 是加在 drawable 之外,所以只会依赖于文字的高度。有些时候这不是想要的结果。
  2. 当 Icon 需要与文字分开单独控制时,很显示这要分成二个 View。
  3. 当需要对 Icon 进行特殊的个性化时,比如添加背景,特效等。
  4. 其他一些造成无法使用的。
  5. 除上述情况外,就要考虑使用 drawable 了。

TextView 阴影效果

shadowDx/shadowDy/shadowRadius/shadowColor

XML 属性:

代码: setShadowLayer

setShadowLayer(radius, dx, dy, color);
// 它的四个参数,分别对应上面的四个属性
// 四个属性取值,要么直接写,要么使用getResource进行一步转化才行

代码使用:

public class MainActivity extends Activity {
    TextView tv;
    Button bt;
    int a;
    float t1;
    float t2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv = (TextView) findViewById(R.id.tv);
        bt = (Button) findViewById(R.id.bt);
        a = 0;
        t1 = getResources().getDimension(R.dimen.activity_horizontal_margin);
        t2 = getResources().getDimension(R.dimen.activity_vertical_margin);

        bt.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View arg0) {
                if (a == 1) {
                    // R.color没有效果
                    tv.setShadowLayer(t1, t1, t1, R.color.aaa);
                    a = 0;

                } else {
                    tv.setShadowLayer(t2, t2, t2, 0x800000ff);
                    a = 1;
                }
                bt.setText(a + "");
            }
        });

    }   
}

各个属性值影响

<TextView
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:shadowColor="#ff0000"
	android:shadowDx="3"
	android:shadowDy="3"
	android:shadowRadius="1"
	android:text="abcdefg"
	android:textColor="#0000ff"
	android:textSize="100sp" />

|200

|400

|400

android:shadowDx="30"
android:shadowDy="30"

|400

示例

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    <TextView
        android:id="@+id/deskclock_time"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerInParent="true"
        android:includeFontPadding="false"
        android:lineSpacingExtra="0dp"
        android:padding="0dp"
        android:shadowColor="#ff0000"
        android:shadowDx="10"
        android:shadowDy="20"
        android:shadowRadius="50"
        android:text="2015年7月1日"
        android:textColor="#00ff00"
        android:textSize="53sp" />
</RelativeLayout>

image.png|400

字体 TypeFace

Android 字体简介

Android 系统默认支持三种字体,分别为:sans,serif,monospace
android.graphic.typeface 字体类:
本类的常量静态定义,首先为字体类型(typeface)名称:

  1. Typeface DEFAULT
  2. Typeface DEFAULT_BOLD
  3. Typeface MONOSPACE
  4. Typeface SANS_SERIF
  5. Typeface SERIF

字体风格(style)名称:

  1. int BOLD
  2. int BOLD_ITALIC
  3. int ITALIC
  4. int NORMAL

设置 TextView 的字体可以通过 TextView 中的 setTypeface 方法来指定一个 Typeface 对象,因为 Android 的字体类比较简单,我们列出所有成员方法:

staticTypeface create(Typeface family, int style)//静态方法,参数一为字体类型这里是Typeface的静态定义,如宋体,参数二风格,如粗体,斜体
staticTypeface create(String familyName, int style)//静态方法,参数一为字体名的字符串,参数二为风格同上,这里我们推荐使用上面的方法。
staticTypeface createFromAsset(AssetManager mgr, String path)//静态方法,参数一为AssetManager对象,主要用于从APK的assets文件夹中取出字体,参数二为相对于Android工程下的assets文件夹中的外挂字体文件的路径。
staticTypeface createFromFile(File path)//静态方法,从文件系统构造一个字体,这里参数可以是sdcard中的某个字体文件
staticTypeface createFromFile(String path) //静态方法,从指定路径中构造字体
staticTypeface defaultFromStyle(int style) //静态方法,返回默认的字体风格
intgetStyle() //获取当前字体风格
finalboolean isBold() //判断当前是否为粗体
finalboolean isItalic() //判断当前风格是否为斜体

textFontWeight 字体粗细

textFontWeight 生效条件

  1. API28 及以上
  2. 设置了字体
  3. 值在 [1, 1000] 之间

XML 设置

<androidx.appcompat.widget.AppCompatTextView
    android:id="@+id/tv_title0"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:fontFamily="sans-serif"
    android:gravity="center"
    android:text="Free Shipping(带bold, fontWeight=900)"
    android:textColor="@color/sui_color_discount_dark"
    android:textFontWeight="900"
    android:textSize="@dimen/sui_text_size_12"
    android:textStyle="bold" />

Xml 设置不生效,用代码设置:

private fun TextView?.setTextFontWeight(fontWeight: Int) {  
    if (this == null) return  
    val default = Typeface.DEFAULT  
    val newTypeface = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {  
        Typeface.create(default, fontWeight, false)  
    } else {  
        if (fontWeight >= 700) {  
            Typeface.create(default, Typeface.BOLD)  
        } else {  
            Typeface.DEFAULT  
        }  
    }  
    this.typeface = newTypeface  
}

自定义字体

TextView xml 设置

image.png|400

<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <font
        android:font="@font/din_alternate_bold"
        android:fontStyle="normal"
        android:fontWeight="900"
        app:font="@font/din_alternate_bold"
        app:fontStyle="normal"
        app:fontWeight="900" />
</font-family>

使用 android:fontFamily="@font/custom_bold_font"

如果这种方式不生效,检查主题是否设置了 android:fontFamily 导致不生效

<style name="Theme.AppWidgets" parent="Theme.MaterialComponents.DayNight.DarkActionBar">  
    <!--<item name="fontFamily">@font/raleway_regular</item>-->  
    <item name="android:fontFamily">@font/raleway_regular</item>  
    <item name="android:textColorPrimary">@color/white</item>  
    <item name="android:textColorSecondary">@color/white</item>  
    <item name="colorControlNormal">@color/white</item>  
    <item name="gradientStartColor">@color/sky_blue_200</item>  
    <item name="gradientEndColor">@color/sky_blue_500</item>  
</style>
TextPaint 设置
textPaint.typeface = ResourcesCompat.getFont(context, R.font.custom_bold_font)
Typeface.createFromAsset 从 assets 中加载

|400

fun TextView.setFont(assetsPath: String) {
    val type = Typeface.createFromAsset(context.assets, assetsPath)
    setTypeface(type)
}
// 使用
binding.btnCrash.setFont("font/AVENGEANCE-HEROIC-AVENGER-2.ttf")

|400

TextView 文字渐变

方法 1:自定义 TextView 设置 LinearGradient,渐变是整体的

继承 TextView,重写 onLayout 方法后设置 Shader

public class GradientTextView extends androidx.appcompat.widget.AppCompatTextView {
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        if (changed) {
            int startColor = Color.RED;
            int endColor = Color.BLUE;
            getPaint().setShader(
                    new LinearGradient(0, 0, getWidth(), getHeight(),
                            startColor,
                            endColor,
                            Shader.TileMode.CLAMP));
        }
    }
}

创建 LinearGradient 时,传入的起始坐标为 (0,0),结束坐标为 (getWidth(), getHeight()),所以渐变效果是从左上角向右下角渐变的:
改成从上往下渐变的效果:

getPaint().setShader(new LinearGradient(0, 0, 0, getHeight(),
        startColor,
        endColor,
        Shader.TileMode.CLAMP));

方法 2:给 TextView 设置 LinearGradient

直接给 TextView 设置 Shader,无需自定义 TextView

Shader shader = new LinearGradient(0, 0, 0, textView.getLineHeight(), Color.RED, Color.BLUE, Shader.TileMode.REPEAT);
textView.getPaint().setShader(shader);
textView.setText("哈喽,benio\n哈喽,benio\n哈喽,benio");

多行渐变,效果不错。但是这种做法有一点缺陷,那就是所有文字都变成渐变色了。假设我们只需要部分字符是渐变色的话,这种方式就不太合理了。特别是在一些使用了 Span 的场景下。

方法 3:自定义 Span

参考官方 ForegroundColorSpan 的实现,在 updateDrawState() 方法中改变颜色

class LinearGradientForegroundSpan extends CharacterStyle implements UpdateAppearance {
    private int startColor;
    private int endColor;
    private int lineHeight;

    public LinearGradientForegroundSpan(int startColor, int endColor, int lineHeight) {
        this.startColor = startColor;
        this.endColor = endColor;
        this.lineHeight = lineHeight;
    }

    @Override
    public void updateDrawState(TextPaint tp) {
        tp.setShader(new LinearGradient(0, 0, 0, lineHeight,
                startColor, endColor, Shader.TileMode.REPEAT));
    }
}

文字渐变小结

  1. 法一:渐变效果与 View 的宽或高相关。适用于所有文本整体渐变的场景
  2. 法二:渐变效果与行相关,每行的渐变效果一致。适用于每行文本渐变效果一致的场景
  3. 法三:用 Span 来实现,适用于局部文本渐变,多行文本渐变的场景

ellipsize 属性

设置当文字过长时,该控件该如何显示。有如下值设置:

ellipise 示例

  1. android:ellipsize="end" 或 mTextView.setEllipsize(TextUtils.TruncateAt.END);
    image.png

  2. android:ellipsize="start" 或 mTextView.setEllipsize(TextUtils.TruncateAt.START)
    image.png

  3. android:ellipsize="middle" 或 mTextView.setEllipsize(TextUtils.TruncateAt.MIDDLE);
    image.png

ellipise 常见问题

ellipise 无效,设置了 end,没有

  1. 设置 ellipsize 属性后没有效果 加上 singleLine="true" 就有效果,但是不能写 lines="1" 和 maxLine="1" ,这样会导致崩溃。
  2. Textview.append(" "); ellipsize 也会失效,记得加上 singleLine=true

显示不出来

<TextView
  android:id="@+id/me_login_btn"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:layout_marginEnd="16dp"
  android:ellipsize="end"
  android:gravity="center_vertical"
  android:maxLines="2"
  android:onClick="@{viewModel::clickLoginBtn}"
  android:text="@{@string/string_key_10+' '+'/'+' '+@string/string_key_11+' '+'>'}"
  android:textColor="@color/sui_color_gray_dark1"
  android:textStyle="bold"
  android:visibility="@{viewModel.showLogin}"
  app:autoSizeMaxTextSize="20sp"
  app:autoSizeMinTextSize="16sp"
  app:autoSizeStepGranularity="1sp"
  app:autoSizeTextType="uniform"
  tools:visibility="visible" />

问题: 在泰语显示两行时,第二行底部的文本显示不全,被裁剪了一部分

image.png


分析: 可能原因是配置了 maxLines 和 ellipise,又在 RecyclerView 中,导致显示不全,具体原因未知。
解决: 去掉 ellipsize="end" 即可

image.png

Android TextView 关于 ellipsize=end 的一个 bug

textView的文本内容中包含\n字符或者其它html字符比如\<.*?>
解决方案也很简单,直接在 setText 之前调用:

myText.replaceAll("\\<.*?>","");

Autosizing(TextView 文本大小自动适配)

Autosizing 方式

官方推出的 TextView 的 Autosizing 方式,在宽度固定的情况下,可以设置最大文本 Size 和最小文本 Size 和每次缩放粒度。

Autosizing 使用

XML 方式:

<TextView
  android:layout_width="340dp"
  android:layout_height="50dp"
  android:background="@drawable/shape_bg_008577"
  android:gravity="center_vertical"
  android:maxLines="1"
  android:text="这是标题,该标题的名字比较长,产品要求不换行全部显示出来"
  android:textSize="18sp"
  android:autoSizeTextType="uniform"
  android:autoSizeMaxTextSize="18sp"
  android:autoSizeMinTextSize="10sp"
  android:autoSizeStepGranularity="1sp"/>

属性解释:

代码中使用:

TextView tvText = findViewById(R.id.tv_text);
TextViewCompat.setAutoSizeTextTypeWithDefaults(tvText,TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM);
TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(tvText,10,18,1, TypedValue.COMPLEX_UNIT_SP);

image.png

Autosizing 不生效?

Autosizing 在不同的宽高 wrap_content,是否多行,都生效,前提是已经摆放不下了
原因:设置了 android:singleLine="true"
解决: 去掉即可

自定义 View 的方式(固定宽度)

其核心思想和上面的 Autosizing 的方式类似,一般是测量 TextView 字体所占的宽度与 TextView 控件的宽度对比,动态改变 TextView 的字体大小。
类似:AutoSizeTextView

使用工具类自行计算(非控件固定宽度)

自定义 View 计算宽度的方法抽取出来:

  1. 基于当前 textSize 来获取宽度
  2. 如果获取的宽度小于等于可用的宽度,就设置该文字大小,否则一直循环重复 1~2 步骤
private void adjustTvTextSize(TextView tv, int maxWidth, String text) {
    int avaiWidth = maxWidth - tv.getPaddingLeft() - tv.getPaddingRight();
    if (avaiWidth <= 0) {
        return;
    }
    TextPaint textPaintClone = new TextPaint(tv.getPaint());
    float trySize = textPaintClone.getTextSize();
    while (textPaintClone.measureText(text) > avaiWidth) {
        trySize--;
        textPaintClone.setTextSize(trySize);
    }
    tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, trySize);
}

示例:

<LinearLayout        
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:layout_marginLeft="@dimen/d_15dp"
  android:layout_marginRight="@dimen/d_15dp"
  android:gravity="center"
  android:orientation="horizontal">
  <TextView
    android:id="@+id/tv_job_detail_dollar"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="$"
    android:textColor="@color/black"
    android:textSize="@dimen/job_detail_message_size"/>
  <TextView
    android:id="@+id/text_view_hourly_rate"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginLeft="@dimen/d_2dp"
    android:singleLine="true"
    android:text="-"
    android:textColor="@color/job_detail_black"
    android:textSize="30sp" />
</LinearLayout>

可以看到 2 个都是 wrap content,那么如何实现这种适应宽度 + 多布局的变长宽度效果呢。其实就是需要我们调用方法手动的计算金额 TextView 的宽度

int mFullNameTVMaxWidth = CommUtils.dip2px(60);

//    mTextViewHourlyRate.setText(totalMoney);
//     while (true) {
//         float measureTextWidth = mTextViewHourlyRate.getPaint().measureText(totalMoney);

//         if (measureTextWidth > mFullNameTVMaxWidth) {
//             int textSize = (int) mTextViewHourlyRate.getTextSize();
//             textSize = textSize - 2;
//             mTextViewHourlyRate.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
//         } else {
//             break;
//         }
//     }
adjustTvTextSize(mTextViewHourlyRate,mFullNameTVMaxWidth,totalMoney)

效果:

image.png

1 行展示不下,先缩放字体到最小大小 10sp,还展示不下,设置展示 2 行

val textPaintClone = TextPaint()

private fun adjustTvTextSize(
    tv: TextView,
    maxLineWidth: Int,
    text: String,
    defaultTextSizeSp: Float = 18F,
    minTextSizeSp: Float = 10F
) {
    val textSizeQueue: ArrayDeque<Float> by lazy {
        ArrayDeque<Float>().apply {
            add(18f)
            add(16f)
            add(14f)
            add(12f)
            add(10f)
        }
    }
    val availWidth = maxLineWidth - tv.paddingStart - tv.paddingEnd - 6F.dp()
    if (availWidth <= 0) {
        return
    }
    tv.maxLines = 1
    tv.textSize = defaultTextSizeSp

    textPaintClone.set(tv.paint)
    textPaintClone.textSize = ScreenUtils.sp2px(this, defaultTextSizeSp).toFloat()
    var tryTextSizeSp = defaultTextSizeSp
    Log.v(
        "hacket",
        "start tryTextSizeSp(sp)=${tryTextSizeSp}, defaultTextSizeSp=$defaultTextSizeSp, minTextSizeSp=$minTextSizeSp, textSizeQueue=$textSizeQueue"
    )
    var i = 1

    while (textSizeQueue.isNotEmpty() && textPaintClone.measureText(text) >= availWidth) {
        val tempTextSize = textSizeQueue.removeFirstOrNull() ?: 0F
        if (tempTextSize <= 0F) {
            continue
        }
        if (tryTextSizeSp <= minTextSizeSp) {
            continue
        }
        textPaintClone.textSize = ScreenUtils.sp2px(this, tempTextSize).toFloat()
        tv.textSize = tempTextSize
//            val isAvail = textPaintClone.measureText(text) > availWidth
//            if (!isAvail) {
//                continue
//            }
        tryTextSizeSp = tempTextSize

        Log.v(
            "hacket", "第${i}次 尝试,measureText=${
                textPaintClone.measureText(
                    text
                )
            }, availWidth=$availWidth, tryTextSizeSp=$tryTextSizeSp(${
                ScreenUtils.sp2px(
                    this, tryTextSizeSp
                ).toFloat()
            }), minTextSizeSp=$minTextSizeSp, textSizeQueue=$textSizeQueue"
        )
        i++
    }
    val avail = textPaintClone.measureText(text) > availWidth
    if (avail) {
        tv.maxLines = 2
    }
    Log.w("hacket", "final tryTextSizeSp=${tryTextSizeSp}, avail=$avail")
}

xml:

<androidx.appcompat.widget.AppCompatTextView
    android:id="@+id/tvCouponVal"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:ellipsize="end"
    android:gravity="center"
    android:maxLines="1"
    android:paddingHorizontal="27dp"
    android:text="MIỄN PHÍ VẬN CHUYỂN"
    android:textColor="@color/sui_color_discount"
    android:textFontWeight="700"
    android:textSize="@dimen/sui_text_size_18"
    android:textStyle="bold"
    app:layout_constrainedWidth="true"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="@id/ivCouponIcon"
    app:layout_constraintStart_toStartOf="@id/ivCouponIcon"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintVertical_bias="0.15" />

TextView 跑马灯(显示一行,滚动显示)

TextView 不会主动获取焦点,一个布局中只能有一个组件获取焦点
获取到焦点时跑马灯。
设置成 maxLines="1" 时跑马灯不工作,要用 singleLine="true"

系统自带的 xml 中进行属性配置

android:ellipsize="marquee"  
android:focusable="true"
android:focusableInTouchMode="true"
android:singleLine="true"  //只显示一行

自定义一个滚动的 TextView

对 button,这种,可以触摸获取焦点的,配置 android:ellipsize="marquee" android:singleLine="true"  ,就可以将看不到的文字显示出来,而 TextView,天生不能获取焦点,那只能重写里面的方法,isActivated(),让其返回 true,那么就可以被获取焦点了。

public class FocusedTextView extends AppCompatTextView {
    public FocusedTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public FocusedTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public FocusedTextView(Context context) {
        super(context);
    }

    @Override
    public boolean isFocused() {
        return true;
    }
}

控制跑马灯

btn_start_marquee.setOnClickListener {
    if (tv_marquee.getEllipsize() != null) {
        tv_marquee.setEllipsize(null);
    } else {
        tv_marquee.setEllipsize(TextUtils.TruncateAt.MARQUEE);
    }
}

跑马灯控制方向

安卓字体不随系统字体变化

1. 字体单位用 dp 而不是 sp

2. 手动设置 setTextSize 不受影响

但如果没有设置用的系统默认字体,无效,TextView 读取属性设置字体不是走的这个方法,而是走的 setRawTextSize

@Override
public void setTextSize(int unit, float size) {
    if (unit == TypedValue.COMPLEX_UNIT_SP) {
        int dpUnit = TypedValue.COMPLEX_UNIT_DIP;
        super.setTextSize(dpUnit, size);
    }
}

3. Configuration 不受影响

https://www.jianshu.com/p/059f3bad61b2

TextView 基线

image.png


基线辅助测试
https://github.com/suragch/AndroidFontMetrics

TextView 中的文字长度测量

TextView.getPaint().measureText(String text)

只是测试传入的 text 在该 TextView 的配置下的总长度,并不是计算每一行的长度。

TextView.getLayout().getLineWidth(int line)

这个方法确实计算得到的是每一行文字的实际长度,注意这里是实际长度,也就是说当设置 singleLine 属性时,用这个方法测量得到的是一整行文字的长度,包括溢出部分。

设置 android:maxLines="1" 和 android:singleLine="true" 有什么区别

  1. maxLines 还是会默认自动进行换行策略,假如一段文字自动换行后有 5 行,maxLines 设置为 1,那么就只显示第一行的内容,其他行不显示。
  2. singleLine, 那么这段可以有 5 行的文字将会被强制放在 1 行里,然后看最多能显示多少字符,剩下的不显示。

设置成 maxLines="1" 时跑马灯不工作

TextView 添加超链接 (两种实现方式)

tv.setAutoLinkMask(Linkify.ALL);
tv.setLinksClickable(true);//设置链接可点击,不设置链接不会变蓝
tv.setMovementMethod(LinkMovementMethod.getInstance());

方式二:通过 HTML 格式化你的网址

String html = "<a href='http://www.baidu.com'>百度一下</a>";//注意这里必须加上协议号,即http://。 
CharSequence charSequence = Html.fromHtml(html);
tv.setText(charSequence);
tv.setMovementMethod(LinkMovementMethod.getInstance());

小结

总结一下就是,以 html 显示超链接,必须写全 url。以 setAutoLinkMask(Linkify.ALL) 可以不用不用写全,就能自动识别出来。

这两种方法,都得设置一下 setMovementMethod,才会跳转。
另外 setAutoLinkMask 不仅 识别超链接,包括电话号码之类的。

MovementMethod

ScrollingMovementMethod

实现带滚动条的 TextView,在更新文字时自动滚动到最后一行

textView = (TextView) findViewById( R.id.tv ) ;
textView.setMovementMethod(ScrollingMovementMethod.getInstance());

TextView 中的各种 padding

padding 相关 API

  1. getWidth(), getHeight()
    对应你代码里的 layout_widthlayout_height
  2. getPaddingLeft/Right/Top/Bottom()
    对应代码里的 Padding,布局里的 android:padding
  3. getCompoundDrawablePadding()
    TextView 设置的 drawawble 和文字之间的 padding 对应布局里的 android:drawablePadding
  4. getCompoundPaddingLeft/Right/Top/Bottom()
    获取混合的 Padding, 既然是混合的,那么它的值也就是 padding + 图片的大小 + drawablePadding 的值(getCompoundPaddingLeft() = getPaddingLeft() + drawable.getIntrinsicWidth() + getCompoundDrawablePadding())。说得通俗点就是,它是获取文字区域到 TextView 边界之间的间隔。 drawable 和 text 之间的 padding,就是 drawablePadding
  5. getExtendedPaddingTop()/Bottom
    当有部分文字没有显示出来时,也就是设置了 maxLine 时,它的值就等于首行文字到 TextView 顶端的距离。同理,getExtendedPaddingBottom() 就是最后一行文字到 TextVeiw 底部距离。其他情况下,他的值等于 getCompoundPaddingTop/Bottom() 的值。
  6. getTotalPaddingLeft/Right/Top/Bottom()
    获取总的 Padding 值,看了下源码,左右的值直接就是等于 compoundPadding 的值,上下的值等于 ExtendedPadding 的值再加上 offset 的值(跟 Gravity 的垂直方向的布局有关。说得通俗点就是,不管有没有 maxLines,上下的值都分别等于首行到 TextView 顶端和末行到 TextView 底部的值。
getTotalPaddingRight() = getCompoundDrawablePadding() + getCompoundDrawables()[2].getBounds().width()

EditText 默认 padding

EditText 没有默认的 padding,如果有 padding,那么都是来自背景图 (.9 的图)。
需要在 xml 中配置,:

android:background="@null"

如果用代码写,padding 还是存在:

 CompatUtil.setBackground(this, null);

如果是 EditText 的话,如果高度太低,那么会出现 EditText 的文本可以上下滚动

动态改变 Background 后 Padding 无效的问题

在 Layout 中指定好 background 和 padding 以后,程序里面动态修改 background 之后 padding 就失效了,貌似是一个 BUG。

解决:在 setBackgroundResource 之后重新设置一下 padding。

// 方法一
int bottom = theView.getPaddingBottom();
int top = theView.getPaddingTop();
int right = theView.getPaddingRight();
int left = theView.getPaddingLeft();
theView.setBackgroundResource(R.drawable.entry_bg_with_image);
theView.setPadding(left, top, right, bottom);

// 方法二
int pad = resources.getDimensionPixelSize(R.dimen.linear_layout_padding);
theView.setBackgroundResource(R.drawable.entry_bg_with_image);
theView.setPadding(pad, pad, pad, pad);