TextView 高级教程

前言

光看题目,估计有人已经忍不住吐槽了:尼玛,TextView 这么简单的控件,还有什么高级用法吗?放在以前,我也会这么想,但是随着开发经验的积累,我愈发觉得 TextView 简直就是一座宝藏,里面有很多宝贝值得研究。

本文基于 @Chiuki 的讲座,并结合我自己的经验整理而成。

文章中的大部分图片和代码均摘自讲稿,感谢原作者的分享。

Compound Drawable

如下图1中的效果,我们可以用 LinearLayout 里面嵌套 ImageView 和 TextView 实现,也可以只用一个带 Drawable 的 TextView 做到。

图1

<TextView
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:text="@string/animation"
  android:drawablePadding="1dp"
  android:drawableLeft="@drawable/rotating_loading"
  android:drawableRight="@drawable/animated_wifi"
  android:drawableBottom="@drawable/animated_clock"/>

相比而言,后者 View 个数更少,层级更少,是优化层级的常用方法。

我们可以通过 drawablePadding 属性来调整图片资源和文字间的间距。但是,在 xml 中,没有对应的属性去调整图片的大小,也就是说,图片会按照原始尺寸进行展示,而没有 ImageView 的各种 ScaleType 可选,除非在 Java 代码中使用 setCompoundDrawables() 方法,或者直接自定义 View。使用 setCompoundDrawables() 控制图片尺寸的用法如下:

        Drawable drBottom = getResources().getDrawable(R.mipmap.hi);
        drBottom.setBounds(0, 0, 200, 200);
        textView.setCompoundDrawables(null, null, null, drBottom);

而且,这里的 Drawable 不仅仅是图片,还可以是动画等资源文件,以此达到动画效果,如图2:

图2

关键代码:

AnimatedRotateDrawable

<!-- res/drawable/rotating_loading.xml -->
<animated-rotate
  android:pivotX="50%"
  android:pivotY="50%"
  android:drawable="@drawable/ic_loading"
  android:duration="500" />

AnimationDrawable

<!-- res/drawable/animated_wifi.xml -->
<animation-list>
  <item android:drawable="@drawable/ic_wifi_0"
      android:duration="250" />
  <item android:drawable="@drawable/ic_wifi_1"
      android:duration="250" />
  <item android:drawable="@drawable/ic_wifi_2"
      android:duration="250" />
  <item android:drawable="@drawable/ic_wifi_3"
      android:duration="250" />
</animation-list>

AnimatedVectorDrawable

<!-- res/drawable/animated_clock.xml -->
<animated-vector android:drawable="@drawable/clock">
  <target android:name="hours"
    android:animation="@anim/hours_rotation" />
  <target android:name="minutes"
    android:animation="@anim/minutes_rotation" />
</animated-vector>
private void startAnimation(
    TextView textView) {
  Drawable[] drawables
      = textView.getCompoundDrawables();
  for (Drawable drawable : drawables) {
    if (drawable != null &&
        drawable instanceof Animatable) {
      ((Animatable) drawable).start();
    }
  }
}

阴影效果

效果:

图3

代码:

<TextView
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:padding="12dp"
  android:text="@string/shadow"
  android:textSize="80sp"
  android:textStyle="bold"
  android:shadowColor="#7000"
  android:shadowDx="12"
  android:shadowDy="12"
  android:shadowRadius="8"/>
shadowColor, shadowDx, shadowDy, shado

注意,shadowDx,shadowDy,shadowRadius 的值的单位是 px,而非 dp。为了让阴影完全显示,记得设置合适的 padding。

通过综合使用这些属性,我们可以做到更多效果,如图4:

图4

Blocky 和 Glow 效果对应的代码:

Blocky

<TextView
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:padding="12dp"
  android:text="@string/blocky"
  android:textColor="@color/purple"
  android:textSize="80sp"
  android:textStyle="bold"
  android:shadowColor="@color/green"
  android:shadowDx="4"
  android:shadowDy="-4"
  android:shadowRadius="1"/>

Glow

<TextView
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:padding="12dp"
  android:text="@string/glow"
  android:textSize="80sp"
  android:textStyle="bold"
  android:textColor="@android:color/white"
  android:background="@android:color/black"
  android:shadowColor="@color/yellow"
  android:shadowDx="0"
  android:shadowDy="0"
  android:shadowRadius="24"/>

自定义字体

效果:

图5

代码:

Typeface typeface = Typeface.createFromAsset(getAssets(), "Ruthie.ttf");

textView.setTypeface(typeface);

渐变色

效果:

图6

代码:

Shader shader = new LinearGradient(
    0, 0, 0, textView.getTextSize(),
    Color.RED, Color.BLUE,
    Shader.TileMode.CLAMP);

textView.getPaint().setShader(shader);

图片填充

效果:

图7

代码:

Bitmap bitmap = BitmapFactory.decodeResource(
    getResources(),
    R.drawable.cheetah_tile);

Shader shader = new BitmapShader(
    bitmap,
    Shader.TileMode.REPEAT,
    Shader.TileMode.REPEAT);
textView.getPaint().setShader(shader);

多样式

效果:

图7

如果上述效果用 HTML 实现,其代码为:

HTML

<h1>Hello World</h1>
Here is an
<img src="octopus"><i>octopus</i>.<br>
And here is a
<a href="http://d.android.com">
link</a>.

其实,使用一个 TextView 也可以实现这种效果:

<string name="from_html_text">
<![CDATA[
<h1>Hello World</h1>
Here is an
<img src="octopus"><i>octopus</i>.<br>
And here is a
<a href="http://d.android.com">
link</a>.
]]>
</string>

setMovementMethod

String html = getString(R.string.from_html_text);
textView.setMovementMethod(
    LinkMovementMethod.getInstance());
textView.setText(Html.fromHtml(
    html, new ResourceImageGetter(this), null));

ResourceImageGetter

private static class ResourceImageGetter
    implements Html.ImageGetter {
  // Constructor takes a Context
  public Drawable getDrawable(String source) {
    int path = context.getResources().getIdentifier(
        source, "drawable", context.getPackageName());
    Drawable drawable = ContextCompat.getDrawable(context, path);
    drawable.setBounds(0, 0,
       drawable.getIntrinsicWidth(),
       drawable.getIntrinsicHeight());
    return drawable;
  }
}

各种 Sapn

span 是指连续的一段范围,对该范围范围内的内容做修饰。

比如该效果:One <u>two</u> three。

该字符串,从第 4 个到第 6 个字符,用下划线修饰。对应的的代码便是:

spannableString.setSpan(new UnderlineSpan(), 4, 6, flags);

从上面这个例子,我们可以总结出 Span 的一般用法,需要三个参数:

  • XXXSpan,修饰类型;
  • 范围,即被修饰子串的起始位置;
  • 标志位;

不同类型的 Span,只需要变化第一个参数。

根据范围的大小,可以将 Span 的类型分为两种:字符和段落。

字符

链接(ClickableSpan)

效果:

图8

代码:

ClickableSpan

String text = textView.getText().toString();

String goToSettings = getString(R.string.go_to_settings);
int start = text.indexOf(goToSettings);
int end = start + goToSettings.length();

SpannableString spannableString = new SpannableString(text);
spannableString.setSpan(new GoToSettingsSpan(), start, end, 0);
textView.setText(spannableString);

textView.setMovementMethod(new LinkMovementMethod());

private static class GoToSettingsSpan extends ClickableSpan {
  public void onClick(View view) {
    view.getContext().startActivity(
      new Intent(android.provider.Settings.ACTION_SETTINGS));
  }
}
<TextView
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:text="@string/clickable_span_text"
  android:textColorLink="@color/go_to_settings"
  android:textColorHighlight="@color/light_green"/>

利用这一原理,我们可以实现朋友圈评论的链接效果:

关于该效果的使用,以及其中点击事件分发的问题,请移步我的这篇博客 《TextView ClickableSpan 事件分发的两个坑》

自定义 TagHandler

《TextView ClickableSpan 事件分发的两个坑》 告诉我们,安卓系统支持的 Html 标签类型有限,如果要支持其他标签,我们需要使用 TagHandler 来自己实现。综合使用 TagHandler 和 MetricAffectingSpan 可以实现分数效果:

代码见 FractionSpan

下划线(UnderlineSpan)

效果:

代码:

//underline a character
span = new UnderlineSpan();

删除线(StrikethroughSpan)

效果:

代码:

// strikethrough a character
span = new StrikethroughSpan();

字符下沉(SubscriptSpan)

可用于实现类似化学式右下角标的效果。

效果:

代码:

//subscript a character
span = new SubscriptSpan();

字符上浮(SuperscriptSpan)

实现右上角标效果。

效果:

代码:

//superscript a character
span = new SuperscriptSpan();

字符背景色(BackgroundColorSpan)

效果:

代码:

/*
public BackgroundColorSpan (int color)
-color: background color
*/

//set a green background
span = new BackgroundColorSpan(Color.GREEN);

文本颜色(ForegroundColorSpan)

效果:

代码:

/*
public ForegroundColorSpan (int color)
-color: foreground color
*/

//set a red foreground
span = new ForegroundColorSpan(Color.RED);

插入图片(ImageSpan)

将范围内的子串替换成图片。

效果:

代码:

//replace a character by pic1_small image
span = new ImageSpan(this, R.drawable.pic1_small);

注意,我们可以综合使用 ImageSpan(Context context, Bitmap b) 和 createScaledBitmap(Bitmap src, int dstWidth, int dstHeight, boolean filter) 来控制图片的大小,使其与文本大小一致。我们可以将其 dstHeight 设置为文本的高度,文本高度计算方法:

int ascent = (int) (-textView.getPaint().ascent());

简单样式(StyleSpan)

改变子串的加粗、斜体、正常(bold,italic,normal)等样式。

效果:

代码:

/*
public StyleSpan (int style)
-style: int describing the style (android.graphics.Typeface)
*/

//set a bold+italic style
span = new StyleSpan(Typeface.BOLD | Typeface.ITALIC);

自定义字体(TypefaceSpan)

效果:

代码:

/*
public TypefaceSpan (String family)
-family: a font family
*/

//set the serif family
span = new TypefaceSpan("serif");

字体样式(TextAppearanceSpan)

效果:

代码:

/*
public  TextAppearanceSpan(Context context, int appearance, int colorList)
-context: a valid context
-appearance: text appearance resource (ex: android.R.style.TextAppearance_Small)
-colorList: a text color resource (ex: android.R.styleable.Theme_textColorPrimary)

public TextAppearanceSpan(String family, int style, int size, ColorStateList color, ColorStateList linkColor)
-family: a font family
-style: int describing the style (android.graphics.Typeface)
-size: text size
-color: a text color
-linkColor: a link text color
*/

//set the serif family
span = new TextAppearanceSpan(this/*a context*/, R.style.SpecialTextAppearance);

以及自定义 Style:

&lt;style name="SpecialTextAppearance" parent="@android:style/TextAppearance">
    <item name="android:textColor">@color/color1</item>
    <item name="android:textColorHighlight">@color/color2</item>
    <item name="android:textColorHint">@color/color3</item>
    <item name="android:textColorLink">@color/color4</item>
    <item name="android:textSize">28sp</item>
    <item name="android:textStyle">italic</item>
</style>

绝对尺寸(AbsoluteSizeSpan)

这里的尺寸,可以是像素或者 dip,具体通过构造方法里面的布尔值设置。

效果:

代码:

/*
public AbsoluteSizeSpan(int size, boolean dip)
-size: a size
-dip: false, size is in px; true, size is in dip (optionnal, default false)
*/

//set text size to 24dp
span = new AbsoluteSizeSpan(24, true);

相对尺寸(RelativeSizeSpan)

效果:

代码:

/*
public RelativeSizeSpan(float proportion)
-proportion: a proportion of the actual text size
*/

//set text size 2 times bigger
span = new RelativeSizeSpan(2.0f);

字体横向缩放(ScaleXSpan)

横向缩放样式,将字体按比例进行横向缩放。

效果:

代码:

/*
public ScaleXSpan(float proportion)
-proportion: a proportion of actual text scale x
*/

//scale x 3 times bigger
span = new ScaleXSpan(3.0f);

字体蒙板(MaskFilterSpan)

注意:模糊效果(BlurMaskFilter)不支持硬件加速。

模糊效果:

EmbossMaskFilter 效果(蓝色前景色+加粗样式):

代码:

/*
public MaskFilterSpan(MaskFilter filter)
-filter: a filter to apply
*/

//Blur a character
span = new MaskFilterSpan(new BlurMaskFilter(density*2, BlurMaskFilter.Blur.NORMAL));
//Emboss a character
span = new MaskFilterSpan(new EmbossMaskFilter(new float[] { 1, 1, 1 }, 0.4f, 6, 3.5f));

彩虹样式(RainbowSpan)

静态效果:

动态效果:

代码见 RainbowSpan

带横线的 EditText

EditText 是继承 TextView 的。我们继承 EditText,重写 onDraw() 方法,自己去画每行文字下面的横线。

效果:

代码见 LinedEditText

自定义 Span

上面这些 Span 功能已经被固定了,有没有一种 Span 可以让我们自由发挥、自由绘制文本呢?有的,这就是 ReplacementSpan。

比如我们可以继承 ReplacementSpan 去画一个矩形框,效果如下:

代码如下:

@Override
public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
    // return text with relative to the Paint
    mWidth = (int) paint.measureText(text, start, end);
    return mWidth;
}

@Override
public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) {
    // draw the frame with custom Paint
    canvas.drawRect(x, top, x + mWidth, bottom, mPaint);
}

自定义 Emoji

效果图:

注意,在上图中,共有4种 emoji 表情,分别是:

  • 系统自带,如第2行末尾的心和天晴图案,具体效果因系统版本而已;
  • 字库所带,如第3行的滑雪图案,需要通过继承 MetricAffectingSpan 的方式引入第三方字库;
  • 静态图片,如第5行的乌贼图案,是通过 ImageSpan 导入的图片资源;
  • 动态绘制,末尾2行的限速牌图案,是通过继承 Drawable、重写 draw() 方法的方式实现的,圆圈、底色、数字都是绘制出来的。

段落

简单项目符号(BulletSapn)

效果:

代码:

/*
public BulletSpan (int gapWidth, int color)
-gapWidth: gap in px between bullet and text
-color: bullet color (optionnal, default is transparent)
*/

//create a black BulletSpan with a gap of 15px
span = new android.text.style.BulletSpan(15, Color.BLACK);

项目符号(LeadingMarginSpan)

上面一节中的 BulletSpan 的项目符号是系统默认的小圆点。

我们可以使用 LeadingMarginSpan 实现个性化的项目符号,而不仅仅限于小圆点。

效果:

关键代码:

String[] bullets = new String[]{"1.", "2.", "3.", "4."};
        String[] itemContents = new String[]{"那一天,闭目在经殿香雾中,蓦然听见,你诵经中的真言;",
                "那一月,我摇动所有的经筒,不为超度,只为触摸你的指尖;",
                "那一年,磕长头匍匐在山路,不为觐见,只为贴着你的温暖;",
                "那一世,转山转水转佛塔呀,不为修来生,只为途中与你相见。"};

        CharSequence allText = "";
        for (int i = 0; i < bullets.length; i++) {

            final String aBullet = bullets[i];
            String t = itemContents[i].trim();

            // 注意此处的换行, 如果没有换行符, 则系统当做只有一个项目处理
            SpannableString spannableString = new SpannableString(t + "\n");

            spannableString.setSpan(new LeadingMarginSpan() {
                @Override
                public int getLeadingMargin(boolean first) {

                    // 项目符号和正文的缩进距离, 单位 px
                    // 我们可以根据 first 来改变第1行和其余行的缩进距离
                    return 100;
                }

                @Override
                public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, boolean first, Layout layout) {

                    // 只对第1行文本添加项添加符号
                    if (first) {
                        Paint.Style orgStyle = p.getStyle();
                        p.setStyle(Paint.Style.FILL);

                        c.drawText(aBullet, 0, bottom - p.descent(), p);
                        p.setStyle(orgStyle);
                    }

                }
            }, 0, t.length(), 0);

            allText = TextUtils.concat(allText, spannableString);
        }

        title.setTextSize(20);
        title.setText(allText);

引用(QuoteSapn)

效果:

代码:

/*
public QuoteSpan (int color)
-color: quote vertical line color (optionnal, default is Color.BLUE)
*/

//create a red quote
span = new android.text.style.QuoteSpan(Color.RED);

对齐方式(AlignmentSpan.Standard)

共有三种对齐方式:

  • 正常,Layout.Alignment.ALIGN_NORMAL;
  • 居中对齐,Layout.Alignment.ALIGN_CENTER;
  • 反向对齐,Layout.Alignment.ALIGN_OPPOSITE;

居中效果:

代码:

/*
public Standard(Layout.Alignment align)
-align: alignment to set
*/

//align center a paragraph
span = new AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER);

反向对齐效果:

上图中对话,4个字符串是在一个 TextView 中,左边2个字符串的对齐方式是 Layout.Alignment.ALIGN_NORMAL,右边的2个是Layout.Alignment.ALIGN_OPPOSITE。

注意,Layout.Alignment.ALIGN_OPPOSITE 的对齐方式只有在换行的情况下才会起作用,如果 “Knock knock” 和 “Who’s there?” 在同一行,即使 “Who’s is there?” 是 Layout.Alignment.ALIGN_OPPOSITE,也不会产生反向对齐的效果,实际效果如下:

反向对齐关键代码如下:

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_alignment_span);

        // some code

        appendText("Knock knock", Layout.Alignment.ALIGN_NORMAL);
        appendText("Who‘s there?", Layout.Alignment.ALIGN_OPPOSITE);
    }

    private void appendText(CharSequence text, Layout.Alignment align) {
        if (text == null || text.toString().trim().length() == 0) {
            return;
        }

        AlignmentSpan span = new AlignmentSpan.Standard(align);
        SpannableString spannableString = new SpannableString(text);
        spannableString.setSpan(span, 0, text.length(), 0);

        if (textView.length() > 0) {

            // 该行很重要,如果没有换行,那么反对齐效果失效
            textView.append("\n\n");
        }
        textView.append(spannableString);
    }

参考文章

时间: 2024-11-03 23:04:50

TextView 高级教程的相关文章

计算机网络高级教程考点个人总结

计算机网络高级教程 选择题 50~60分 论述题:40~50分 个人总结仅供参考 论述题范围 1.  google公司,IBM公司.微软公司.苹果公司.华为公司.百度公司的技术发展方向. 2.5G的特点及其产业应用. 3.智慧城市的应用探索. 4.机器人技术与应用探索. Google公司市值6819.40亿美元,IBM市值1450亿美元,苹果8000亿美元,微软5400亿美元,阿里3632.63亿美元,腾讯3462亿美元,京东600亿美元,百度600亿美元,华为没有上市,没有市值. 选择题范围

下载快速上手数据挖掘之solr搜索引擎高级教程(Solr集群、KI分词、项目实战)

Solr是一个高性能,采用Java开发,基于Lucene的全文搜索服务器.同时对其进行了扩展,提供了比Lucene更为丰富的查询语言,同时实现了可配置.可扩展并对查询性能进行了优化,并且提供了一个完善的功能管理界面,是一款非常优秀的全文搜索引擎. 快速上手数据挖掘之solr搜索引擎高级教程(Solr集群.KI分词.项目实战),刚刚入手,转一注册文件,视频的确不错,可以先下载看看:http://pan.baidu.com/s/1jIdgtWM 密码:s1t3

Net并行编程高级教程--Parallel

Net并行编程高级教程--Parallel 一直觉得自己对并发了解不够深入,特别是看了<代码整洁之道>觉得自己有必要好好学学并发编程,因为性能也是衡量代码整洁的一大标准.而且在<失控>这本书中也多次提到并发,不管是计算机还是生物都并发处理着各种事物.人真是奇怪,当你关注一个事情的时候,你会发现周围的事物中就常出现那个事情.所以好奇心驱使下学习并发.便有了此文. 一.理解硬件线程和软件线程 多核处理器带有一个以上的物理内核--物理内核是真正的独立处理单元,多个物理内核使得多条指令能够

二、个人总结的菜鸟教程|sql教程的知识点(SQL高级教程)

二.SQL高级教程 1.SQL SELECT TOP SELECT TOP 子句用于规定要返回的记录的数目. SELECT TOP 子句对于拥有数千条记录的大型表来说,是非常有用的. 注释:并非所有的数据库系统都支持 SELECT TOP 子句. sql server|ms access语法 select top number|percent column_name from table_name; mysql|oracle语法(与top等价) select Column_name from t

展讯NAND Flash高级教程【转】

转自:http://wenku.baidu.com/view/d236e6727fd5360cba1adb9e.html 展讯NAND Flash高级教程

【读书笔记】.Net并行编程高级教程--Parallel

一直觉得自己对并发了解不够深入,特别是看了<代码整洁之道>觉得自己有必要好好学学并发编程,因为性能也是衡量代码整洁的一大标准.而且在<失控>这本书中也多次提到并发,不管是计算机还是生物都并发处理着各种事物.人真是奇怪,当你关注一个事情的时候,你会发现周围的事物中就常出现那个事情.所以好奇心驱使下学习并发.便有了此文. 一.理解硬件线程和软件线程 多核处理器带有一个以上的物理内核--物理内核是真正的独立处理单元,多个物理内核使得多条指令能够同时并行运行.硬件线程也称为逻辑内核,一个物

Android开发之TextView高级应用

我们平时使用TextView往往让它作为一个显示文字的容器,但TextView的功能并不局限于此.下面就和大家分享一下TextView的一些使用技巧. Android中设置文本样式的几种方法: 1.将android:autoLink属性值设为true.系统会自动识别E-mail.电话.网址等特殊文本. 2.使用Html标签,例如,<font>.<img>等.不要设置 android:autoLink 属性. 3.在Java代码中直接使用Span对象来设置文本样式.这种方法需要将文本

ios cocopods 安装使用及高级教程

CocoaPods简介 每种语言发展到一个阶段,就会出现相应的依赖管理工具,例如Java语言的Maven,nodejs的npm.随着iOS开发者的增多,业界也出现了为iOS程序提供依赖管理的工具,它的名字叫做:CocoaPods.http://cocoapods.org/ 一.安装 1.更新gem 如果终端gem版本太老可能会有问题,先更新gem:(现在大部分新机都不会有这个问题) sudo gem update --system 2.换源 gem默认的源是外国的https://rubygems

MPLS VPN 高级教程(张洋讲解演示版)

MPLS VPN 高级教程(张洋讲解演示版) 课程目标: ü        LDP的实施和特性 ü        PE-CE路由协议以的实施,以及它们在MPLS VPN中的特性: ü        如何通过MPLS网络运载IPv6,6PE和6VPE的含义和它的运作过程: ü        MPLS VPN的各种配置方案 适合人群:希望深入理解MPLS-VPN的人群. 课程位置: http://edu.51cto.com/course/course_id-5737.html 具体内容:   第一课