Android进阶之路 - 一串儿多样式富文本

富文本相关的知识很多人在开发中用的并不多,但是遇到一些特殊问题时你会发现富文本还是很好用的 !

标如其名,其具体实现主要采用了富文本的相关知识!从而解决了同一个字符串内有多种样式,可以参考下方效果

部分效果

Tips:仅记录篇内已经实现的富文本效果

html 拼接

在这里插入图片描述

SpannableStringBuilder 拼接

这里写图片描述

进阶课堂

Tips:通过 Kotlin扩展函数快速实现 字体颜色不同、区分加粗效果、添加图片等多样式富文本效果

在这里插入图片描述

基础认知

如果项目开发比较着急,也可以跳过基础部分直接看进阶课堂,可以直接用 Kotlin 的一些扩展函数,实现更快一些,可以留出些时间做些别的

Andorid 中关于一些常见的富文本显示效果,通常会采用俩种方式

  • html 拼接 (适用于拼接文本中不同颜色的字体颜色、大小,掌握html相关语法更具优势)
  • SpannableStringBuilder 富文本拼接,Java 提供的原始方式,扩展方式较多

其中关于Java 提供的富文本方式,主要会用到俩个相关类,俩者主要区别在于SpannableStringBuilder 增加了类似于 StringBuilderappend 等方法,可以修改原来的字符串跟样式(个人见解)

  • SpannableString
  • SpannableStringBuilder

通过 SpannableStringBuilder 实现富文本效果主动性更强,可以在构造对象时直接声明需要设置的字符串,也可以动态在append时添加已经Span的文本

fun SpannableStringBuilder.appendColor(
    @ColorInt color: Int,
    text: CharSequence,
): SpannableStringBuilder = inSpans(ForegroundColorSpan(color)) { append(text) }

inSpans 源码

在这里插入图片描述

常用 API 解析

setSpan(Object what, int start, int end, int flags)

  • what 表示设置的格式是什么,可以是前景色、背景色也可以是可点击的文本等等
  • start 表示需要设置格式的子字符串的起始下标
  • end 同理表示终止下标
  • flags 的格式可适用于各种角标位的取舍

Flag类型(四种属性)

  • Spannable.SPAN_EXCLUSIVE_EXCLUSIVE(前后都不包括) 即在指定范围的前面和后面插入新字符都不会应用新样式
  • Spannable.SPAN_EXCLUSIVE_INCLUSIVE(前面不包括,后面包括) 即仅在范围字符的后面插入新字符时会应用新样式
  • Spannable.SPAN_INCLUSIVE_EXCLUSIVE(前面包括,后面不包括)
  • Spannable.SPAN_INCLUSIVE_INCLUSIVE(前后都包括)

常用Span格式

API含义等同含义
ForegroundColorSpan前景色setTextColor(字体颜色)
BackgroundColorSpan背景色setBackgroudColor(背景颜色)
RelativeSizeSpan字体大小setTextSize
StrikethroughSpan删除线
UnderlineSpan下划线
SuperscriptSpan前景色
ForegroundColorSpan上标
SubscriptSpan下标
StyleSpan字体风格
ImageSpan文本中添加表情
ClickableSpan设置可点击的文本
URLSpan设置超链接

基础场景

此处仅做一些常用操作的使用说明,因为写的比较早,

共用

  TextView textView = findViewById(R.id.content); //需要显示富文本样式的TextView控件
  SpannableStringBuilder spannableBuilder = new SpannableStringBuilder("开始的开始,我们只是孩子!表情"); //需要实现富文本样式的字符串
  //.....添加对应Span.....//
  textView.setText(spannableBuilder); //记得将富文本后的SpannableStringBuilder设置给对应控件
字体颜色
 //前景色
 ForegroundColorSpan forColorSpan = new ForegroundColorSpan(Color.RED);
 //设置前景色
 spannableBuilder.setSpan(forColorSpan, 0, 1, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
字体大小
 //字体大小
 RelativeSizeSpan testSizeSpan = new RelativeSizeSpan(1.0f);
 //设置字体大小
 spannableBuilder.setSpan(testSizeSpan, 0, spannableBuilder.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
添加图片

在富文本中图片的话,一定要给图片设置大小,负责显示不出来

 //图片
 Drawable drawable = getResources().getDrawable(R.mipmap.ic_launcher);
 drawable.setBounds(0, 0, 42, 42);
 ImageSpan imageSpan = new ImageSpan(drawable);
 //设置图片
 spannableBuilder.setSpan(imageSpan, 13, spannableBuilder.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
// 一般常见角标区间为的一般为 spannableBuilder.length()-1 至 spannableBuilder.length()
// spannableBuilder.setSpan(imageSpan, spannableBuilder.length()-1, spannableBuilder.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE); 
设置点击效果
 //点击
 spannableBuilder.setSpan(new ClickableSpan() {
    @Override
    public void onClick(View view) {
        Toast.makeText(MainActivity.this,"我想说,你可好?",Toast.LENGTH_SHORT).show();
       }
	},0,9,Spannable.SPAN_INCLUSIVE_INCLUSIVE);
 textView.setMovementMethod(LinkMovementMethod.getInstance()); //注意添加此设置!否则点击事件无效!

实战场景

html 拼接

在这里插入图片描述

这种方式我以前在 Android进阶之路 - 常用小功能 中有记录过,为了方便特在此重新记录一下

//主要代码
 Html.fromHtml("第一段文本信息" + "<font color=\"#757575\">" + "第二段文本信息(为灰色)" + "</font>")

//使用方式(如要设置颜色,可以如第二段一般进行颜色填充~)
 mContext.setText(Html.fromHtml("希望" + "<font color=\"#757575\">" + "我们都不会老" + "</font>"));

//展示结果
// 希望 为 黑色字体
// 我们不会老 为 灰色字体
 
//上图效果
tvRisk.setText(Html.fromHtml("您的风险偏好是" + "<font color=\"#1760EA\">" + "C1稳健型" +  "</font>"));

SpannableStringBuilder 拼接

package com.example.yongliu.spannablestringbuilder;

import android.graphics.Color;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.method.LinkMovementMethod;
import android.text.style.BackgroundColorSpan;
import android.text.style.ClickableSpan;
import android.text.style.ForegroundColorSpan;
import android.text.style.ImageSpan;
import android.text.style.RelativeSizeSpan;
import android.text.style.StrikethroughSpan;
import android.text.style.StyleSpan;
import android.text.style.SuperscriptSpan;
import android.text.style.URLSpan;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    private TextView mContent;

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

        mContent = findViewById(R.id.content);
        SpannableStringBuilder data = new SpannableStringBuilder("开始的开始,我们只是孩子!表情");
		
		/* * 
		 * 创建对应的Span样式
         */
        //前景色
        ForegroundColorSpan forColorSpan = new ForegroundColorSpan(Color.RED);
        //背景色
        BackgroundColorSpan backColorSpan = new BackgroundColorSpan(Color.BLUE);
        //字体大小
        RelativeSizeSpan textSizeSpan = new RelativeSizeSpan(1.0f);
        //设置超链接
        URLSpan urlSpan = new URLSpan("https://blog.csdn.net/qq_20451879/article/details/80094968");
        //删除线
        StrikethroughSpan strikethroughSpan = new StrikethroughSpan();
        //上标
        SuperscriptSpan superscriptSpan = new SuperscriptSpan();
        //图片
        Drawable drawable = getResources().getDrawable(R.mipmap.ic_launcher);
        drawable.setBounds(0, 0, 42, 42);
        ImageSpan imageSpan = new ImageSpan(drawable);
        //设置字体Style
        StyleSpan styleSpan  = new StyleSpan(Typeface.BOLD);
        
        /* 
         * 将上方创建好的Span样式,设置到对应的角标位
         * start 起始角标值
         * end 终点角标值
         */
        //设置前景色
        spannableBuilder.setSpan(forColorSpan, 0, 1, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
        //设置背景色
        spannableBuilder.setSpan(backColorSpan, 1, 2, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
        //设置字体大小
        spannableBuilder.setSpan(textSizeSpan, 0, data.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
        //设置删除线
        spannableBuilder.setSpan(strikethroughSpan, 10, 12, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
        //设置超链接
        spannableBuilder.setSpan(urlSpan, 0, data.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
		//设置上标
        spannableBuilder.setSpan(superscriptSpan, 3, 4, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
        //设置图片
        spannableBuilder.setSpan(imageSpan, 13, data.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
        //设置字体风格
        spannableBuilder.setSpan(styleSpan, 5, 7, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        //点击
        spannableBuilder.setSpan(new ClickableSpan() {
            @Override
            public void onClick(View view) {
                Toast.makeText(MainActivity.this,"我想说,你可好?",Toast.LENGTH_SHORT).show();
            }
        },0,9,Spannable.SPAN_INCLUSIVE_INCLUSIVE);
        mContent.setText(spannableBuilder);
        mContent.setMovementMethod(LinkMovementMethod.getInstance());
    }
}

activity_main

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.yongliu.spannablestringbuilder.MainActivity">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="45dp"
        android:gravity="center"
        android:id="@+id/content"/>
</LinearLayout>

项目实战

字体多色、加粗、尾部加图、点击事件

在这里插入图片描述

实现方式

  val drawable = resources.getDrawable(R.drawable.icon_config_scheme_arrow_blue)
  drawable.setBounds(0, 0, 12.dp, 12.dp);
  val imageSpan = ImageSpan(drawable, ImageSpan.ALIGN_BASELINE)
  val spannable = SpannableStringBuilder()
  spannable.appendMore("#666666".toColorInt(), "根据你的风险偏好及期望的投资时长,通过资产配置测算,建议你")
  spannable.mediumSmall {
      color("#333333".toColorInt()) {
          append("稳健类资产配置${"40%"},权益类资产配置${"60%"}。")
      }
  }
  spannable.appendMore("#1760EA".toColorInt(), "了解更多>") {
      //如果是在组件中的话,可以直接在这里写对应逻辑,如果是在一些容器内部就需要写接口回调了!
      //callback?.invoke("")
  }
  spannable.setSpan(imageSpan, spannable.length-1, spannable.length, Spannable.SPAN_INCLUSIVE_INCLUSIVE)
  bind.tvAssetDesc.text = spannable
  bind.tvAssetDesc.movementMethod = LinkMovementMethod.getInstance();

未读消息的小红点

TextView 尾部在特定时间加入小红点图片,此为我项目中一段代码,当做自我笔记,以防遗忘

 SpannableString spannableString = new SpannableString(content);
 //设置图片的方法
 Drawable drawable = GarageApp.getAppContext().getResources().getDrawable(R.drawable.shape_msg_red_dot);
 drawable.setBounds(0, 0, DeviceUtils.dp2px(GarageApp.getAppContext(), 8), DeviceUtils.dp2px(GarageApp.getAppContext(), 8));
 ImageSpan imageSpan = new ImageSpan(drawable, ImageSpan.ALIGN_BASELINE);
 spannableString.setSpan(imageSpan, content.length() - 1, content.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
 tvContent.setText(spannableString);

附带部分尺寸转换方法

 /**
     * dp 转化为 px
     * @param context context
     * @param dpValue dpValue
     * @return int
     */
    public static int dp2px(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    /**
     * px 转化为 dp
     * @param context context
     * @param pxValue pxValue
     */
    public static int px2dp(Context context, float pxValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (pxValue / scale + 0.5f);
    }

    public static int px2sp(Context context, float pxValue) {
        final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
        return (int) (pxValue / fontScale + 0.5f);
    }


    public static int sp2px(Context context, float spValue) {
        final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
        return (int) (spValue * fontScale + 0.5f);
    }
    

不同位置显示的字体大小有所不同

感觉当设置同等字体 Size 后,显示效果不同,主要是因为屏幕适配导致的,我们可以采用对应的 dp、px转换工具 试一试

实现效果

在这里插入图片描述

起初我使用的就是以下 RelativeSizeSpan 声明的字体大小,但是实验效果不佳

  val data = SpannableStringBuilder("您还未领取0折购基打折卡,无法享受\n0折购基的优惠,是否现在去领取?")
  val startSize = RelativeSizeSpan(13.0f)
  val endSize = RelativeSizeSpan(17.0f)
  data.setSpan(startSize, 0, 5, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
  data.setSpan(endSize, 5, data.length, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
  content.text = data

后续我改用为 AbsoluteSizeSpan 声明的字体大小,效果不错

  val data = SpannableStringBuilder("您还未领取0折购基打折卡,无法享受\n0折购基的优惠,是否现在去领取?")
  data.setSpan(AbsoluteSizeSpan(13,true),0,4, Spannable.SPAN_INCLUSIVE_INCLUSIVE)
  data.setSpan(AbsoluteSizeSpan(17,true),5,9, Spannable.SPAN_INCLUSIVE_EXCLUSIVE)
  data.setSpan(AbsoluteSizeSpan(13,true),9,data.length, Spannable.SPAN_INCLUSIVE_INCLUSIVE)
  content.text = data

常见问题

可能是常见使用场景,也可能常见的出错场景,希望可以帮到你我

设置字体Size和理想中有区别?

提示:我在项目中有一个需求是不同区间,字体显示的大小有所区别,在使用 RelativeSizeSpan 方式设置后,效果不太理想,后续改为了AbsoluteSizeSpan 方式(具体实现在该篇的项目经验

  val data = SpannableStringBuilder("少年可知,鲜衣怒马?")
  val startSize = RelativeSizeSpan(13.0f)
  val endSize = RelativeSizeSpan(17.0f)
  data.setSpan(startSize, 0, 4, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
  data.setSpan(endSize, 5, data.length, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
  content.text = data

样式未生效?

设置文本时 不能使用后builder的toString()方法 ,如果您这样做了,那么辛辛苦苦设置的样式可能就被覆盖了,并不会显示出来

  1. 首先检查是否设置对应的 Span,区间是否为有效区间?
  2. 设置 span 后,是否有调用toString()方法?

点击事件未生效?

设置了点击事件却无效的时候, 查看是否有 setMovementMethod,如没有的话,那么像下面一样进行设置

  //mContent TextView控件
  mContent.setMovementMethod(LinkMovementMethod.getInstance());

超链接要注意什么?

  1. 如果要使用超链接的设置,需要同时设置点击事件,不然无法触发!
  2. 如果超链接在点击事件内生效的话,那么会优先超链接,同时消耗此事件,其他操作点击操作将无法触发(个人Demo察觉)
  3. 超链接正常跳转之后你会发现你没有加入Intent的网络权限!神不神奇!~

进阶课堂

最近因为富本文效果,把整篇Blog重新修整了修整,关于顶部提到的效果已记录在项目经验中

场景践行

以下的实现均建立在 SpannableStringBuilder 扩展函数(加粗、渐变等) 扩展函数

变色
 val spannable = SpannableStringBuilder()
 spannable.appendMore("#666666".toColorInt(), "越来越无趣")
 spannable.appendMore("#99999".toColorInt(), "为何")
 contentView.text = spannable
加粗
 textView.text = buildSpannedString { mediumSmall { append("能幼稚一生,何尝不是一种幸福") }    
加粗、变色
 contentView.text = buildSpannedString {
     mediumSmall {
       color("#333333".toColorInt()) {
          append("为何")
        }
       color("#1760EA".toColorInt()) {
         append("越来越无趣")
       }
  }
尾部加图标
 val drawable = resources.getDrawable(R.drawable.icon_config_scheme_arrow_blue)
 drawable.setBounds(0, 0, 12.dp, 12.dp);
 val imageSpan = ImageSpan(drawable, ImageSpan.ALIGN_BASELINE)
 val spannable = SpannableStringBuilder()
 spannable.appendMore("#666666".toColorInt(), "描述文本,尾部可以加个字符用于替换图标")
 spannable.setSpan(imageSpan, spannable.length-1, spannable.length, Spannable.SPAN_INCLUSIVE_INCLUSIVE)
 contentView.text = spannable

12.dp 扩展函数

inline val Int.dp: Int
    get() = (this * AppContext.resources.displayMetrics.density + 0.5f).toInt()

inline val Float.dp: Float
    get() = (this * AppContext.resources.displayMetrics.density + 0.5f).toInt().toFloat()
渐变
 private val textColor = intArrayOf("#6B4C16".toColorInt(), "#A17612".toColorInt())
 textView.text = buildSpannedString {linearGradient(colors = textColor) { append("能幼稚一生,何尝不是一种幸福") }}

SpannableStringBuilder 扩展函数(加粗、渐变等)

关于字体渐变的需求,之前也曾记录过一篇 Android进阶之路 - TextView文本渐变 ,有兴趣的也可以去看看

SpannableStringBuilder

import android.text.SpannableStringBuilder
import android.text.style.AbsoluteSizeSpan
import android.text.style.ForegroundColorSpan
import android.text.style.RelativeSizeSpan
import android.text.style.StrikethroughSpan
import android.view.View
import androidx.annotation.ColorInt
import androidx.core.text.inSpans
import cn.com.huaan.fund.acts.core.android.text.spannable.ClickableSpan
import cn.com.huaan.fund.acts.core.android.text.spannable.LinearGradientFontSpan
import cn.com.huaan.fund.acts.core.android.text.spannable.MediumSmallSpan

inline fun SpannableStringBuilder.mediumSmall(
    builderAction: SpannableStringBuilder.() -> Unit
) = inSpans(MediumSmallSpan(), builderAction = builderAction)

inline fun SpannableStringBuilder.linearGradient(
    colors: IntArray,
    orientation: Int = LinearGradientFontSpan.HORIZONTAL,
    builderAction: SpannableStringBuilder.() -> Unit
): SpannableStringBuilder {
    return inSpans(LinearGradientFontSpan(colors, orientation), builderAction = builderAction)
}

inline fun SpannableStringBuilder.clickable(
    listener: View.OnClickListener,
    builderAction: SpannableStringBuilder.() -> Unit
): SpannableStringBuilder {
    return inSpans(ClickableSpan(listener), builderAction = builderAction)
}

//
fun SpannableStringBuilder.appendClickable(
    text: CharSequence,
    listener: View.OnClickListener
): SpannableStringBuilder = inSpans(ClickableSpan(listener)) { append(text) }

fun SpannableStringBuilder.appendColor(
    @ColorInt color: Int,
    text: CharSequence,
): SpannableStringBuilder = inSpans(ForegroundColorSpan(color)) { append(text) }

fun SpannableStringBuilder.appendStrikeThrough(
    text: CharSequence,
): SpannableStringBuilder = inSpans(StrikethroughSpan()) { append(text) }

fun SpannableStringBuilder.appendRelativeSize(
    proportion: Float,
    text: CharSequence,
): SpannableStringBuilder = inSpans(RelativeSizeSpan(proportion)) { append(text) }

fun SpannableStringBuilder.appendAbsoluteSize(
    proportion: Int,
    text: CharSequence,
): SpannableStringBuilder = inSpans(AbsoluteSizeSpan(proportion,true)) { append(text) }
//
fun SpannableStringBuilder.appendMore(
    @ColorInt color: Int? = null,
    text: CharSequence,
    listener: View.OnClickListener? = null,
): SpannableStringBuilder {
    val spans = mutableListOf<Any>()
    color?.also { spans.add(ForegroundColorSpan(color)) }
    listener?.also { spans.add(ClickableSpan(listener)) }
    inSpans(*spans.toTypedArray()) { append(text) }
    return this
}

fun SpannableStringBuilder.appendLabel(
    @ColorInt color: Int? = null,
    text: String,
    listener: View.OnClickListener? = null,
): SpannableStringBuilder {
    val splits = text.replace("</b>", "<b>").split("<b>")
    splits.forEachIndexed { index, s ->
        if (index % 2 == 1) {
            appendMore(color = color, s, listener)
        } else {
            append(s)
        }
    }
    return this
}

LinearGradientFontSpan

import android.graphics.Canvas
import android.graphics.LinearGradient
import android.graphics.Paint
import android.graphics.Shader
import android.text.style.ReplacementSpan
import android.widget.LinearLayout

class LinearGradientFontSpan(val colors: IntArray, val orientation: Int = HORIZONTAL) : ReplacementSpan() {
    companion object {
        const val HORIZONTAL = LinearLayout.HORIZONTAL   // 水平渐变方向
        const val VERTICAL = LinearLayout.VERTICAL       // 垂直渐变方向
    }

    private var mMeasureTextWidth = 0  // 测量的文本宽度

    override fun getSize(paint: Paint, text: CharSequence?, start: Int, end: Int, fontMetricsInt: Paint.FontMetricsInt?): Int {
        mMeasureTextWidth = paint.measureText(text ?: "", start, end).toInt()
        // 这段不可以去掉,字体高度没设置,会出现 draw 方法没有被调用的问题
        // 详情请见:https://stackoverflow.com/questions/20069537/replacementspans-draw-method-isnt-called
        val metrics = paint.fontMetrics
        fontMetricsInt?.top = metrics.top.toInt()
        fontMetricsInt?.ascent = metrics.ascent.toInt()
        fontMetricsInt?.descent = metrics.descent.toInt()
        fontMetricsInt?.bottom = metrics.bottom.toInt()
        return mMeasureTextWidth
    }

    override fun draw(canvas: Canvas, text: CharSequence?, start: Int, end: Int, x: Float, top: Int, y: Int, bottom: Int, paint: Paint) {
        if (text.isNullOrEmpty()) return
        val linearGradient = if (orientation == VERTICAL) {
            LinearGradient(0f, 0f, 0f, paint.descent() - paint.ascent(), colors, null, Shader.TileMode.REPEAT)
        } else {
            LinearGradient(x, 0f, x + mMeasureTextWidth, 0f, colors, null, Shader.TileMode.REPEAT)
        }
        val shader = paint.shader
        val alpha = paint.alpha
        //
        paint.shader = linearGradient
        paint.alpha = 255                    //  如果是则设置不透明
        //
        canvas.drawText(text, start, end, x, y.toFloat(), paint)
        //绘制完成之后将画笔的透明度还原回去
        paint.shader = shader
        paint.alpha = alpha
    }

}

MediumSmallSpan

import android.graphics.Paint
import android.text.TextPaint
import android.text.style.MetricAffectingSpan

class MediumSmallSpan : MetricAffectingSpan() {

    override fun updateDrawState(paint: TextPaint?) = apply(paint)

    override fun updateMeasureState(paint: TextPaint) = apply(paint)

    private fun apply(paint: Paint?) {
        if (paint == null) return
        paint.strokeWidth = 0.6f
        paint.style = Paint.Style.FILL_AND_STROKE
    }

}

ClickableSpan

import android.text.TextPaint
import android.view.View
import cn.com.huaan.fund.acts.core.android.listener.OnClickWrapListener

class ClickableSpan(val listener: View.OnClickListener) : android.text.style.ClickableSpan() {

    override fun onClick(widget: View) {
        OnClickWrapListener(listener).onClick(widget)
    }

    override fun updateDrawState(ds: TextPaint) {
        ds.isUnderlineText = false
    }
}

借鉴文章(早期)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

远方那座山

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值