Android 自定义 View 中使用 Spannable的实例详解

我们都知道 Android 中使用 Spannable 可以实现 TextView 富文本的显示,但是在自定义控件中如何使用 Spannable 绘制不同样式的文字呢?

例如这种效果,标题中的 分数字61 是粗体, 是常规字体,并且相对于 61 更小些。
第一反应可能是使用 SpannableString.setSpan() 设置 RelativeSizeSpan, 然后在 onDraw() 中进行绘制,事实是这样实现是没有效果的,因为 onDraw() 中只能获取到 SpannableString 中的内容,拿不到 Span.

那如何在自定义View 中使用 Spannable 呢? 答案就是系统提供的 Layout 类,

/**
 * A base class that manages text layout in visual elements on
 * the screen.
 * <p>For text that will be edited, use a {@link DynamicLayout},
 * which will be updated as the text changes.
 * For text that will not change, use a {@link StaticLayout}.
 */
public abstract class Layout {

}

可以看到 Layout 是一个抽象类,有三个子类,可以实现一些自动换行的显示效果。

  • BoringLayout
  • DynamicLayout
  • StaticLayout

实现代码

1. 定义自定义属性

<?xml version="1.0" encoding="utf-8"?>
<resources>
 <declare-styleable name="ArcProgressView">
  <attr name="arcBackgroundColor" format="color" />
  <attr name="arcProgressColor" format="color" />
  <attr name="arcSubTitleColor" format="color" />
  <attr name="arcStrokeWidth" format="dimension" />
  <attr name="arcTitleTextSize" format="dimension" />
  <attr name="arcSubTitleTextSize" format="dimension" />
  <attr name="arcProgress" format="float" />
  <attr name="arcTitleNumber" format="integer" />
 </declare-styleable>
</resources>

2. 继承 View, 在 onDraw() 中绘制

public class ArcProgressView extends View {
 private int arcBackgroundColor; // 圆弧背景颜色
 private int arcProgressColor;  // 圆弧进度颜色
 private int arcSubTitleColor;  // 副标题颜色
 private float arcStrokeWidth;  // 圆弧线的厚度
 private float arcTitleTextSize; // 标题文字大小
 private float arcSubTitleTextSize; // 副标题文字大小
 private float arcProgress; // 进度
 private int arcTitleNumber; // 值
 private Paint paint;
 private float centerX;
 private float centerY;
 private float radius; // 半径
 private RectF rectF;
 private int startAngle = 135;
 private int sweepAngle = 270;
 private String subTitle = "1月份";
 private SpannableString spannableString;
 private TextPaint textPaint;
 private RelativeSizeSpan relativeSizeSpan;
 private DynamicLayout dynamicLayout;
 private String text = "11分";
 private StyleSpan styleSpan;
 private float curProgress; // 当前进度
 private int curNumber;
 public ArcProgressView(Context context) {
  this(context, null);
 }
 public ArcProgressView(Context context, @Nullable AttributeSet attrs) {
  this(context, attrs, 0);
 }
 public ArcProgressView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
  readAttrs(context, attrs);
  init(context);
 }
 private void readAttrs(Context context, AttributeSet attributeSet) {
  TypedArray typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.ArcProgressView);
  arcBackgroundColor = typedArray.getColor(R.styleable.ArcProgressView_arcBackgroundColor, 0x1c979797);
  arcProgressColor = typedArray.getColor(R.styleable.ArcProgressView_arcProgressColor, 0xff3372FF);
  arcSubTitleColor = typedArray.getColor(R.styleable.ArcProgressView_arcSubTitleColor, 0x66000000);
  arcStrokeWidth = typedArray.getDimensionPixelSize(R.styleable.ArcProgressView_arcStrokeWidth, dp2px(5));
  arcTitleTextSize = typedArray.getDimensionPixelSize(R.styleable.ArcProgressView_arcTitleTextSize, dp2px(30));
  arcSubTitleTextSize = typedArray.getDimensionPixelSize(R.styleable.ArcProgressView_arcSubTitleTextSize, dp2px(14));
  arcProgress = typedArray.getFloat(R.styleable.ArcProgressView_arcProgress, 1.0f);
  arcTitleNumber = typedArray.getInt(R.styleable.ArcProgressView_arcTitleNumber, 100);
  typedArray.recycle();
 }
 private void init(Context context) {
  paint = new Paint(Paint.ANTI_ALIAS_FLAG);
  paint.setStrokeCap(Paint.Cap.ROUND);
  relativeSizeSpan = new RelativeSizeSpan(0.6f);
  styleSpan = new StyleSpan(android.graphics.Typeface.BOLD);
  textPaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG);
  textPaint.setColor(arcProgressColor);
//  textPaint.setTextAlign(Paint.Align.CENTER); // 设置该属性导致文字间有间隔
  textPaint.setTextSize(sp2px(22));
 }
 @Override
 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
  super.onSizeChanged(w, h, oldw, oldh);
  centerX = w / 2f;
  centerY = h / 2f;
  radius = (Math.min(w, h) - arcStrokeWidth) / 2f;
  rectF = new RectF(-radius, -radius, radius, radius);
 }
 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  int width = getMeasuredSize(widthMeasureSpec, dp2px(100));
  int height = getMeasuredSize(heightMeasureSpec, dp2px(100));
  setMeasuredDimension(width, height);
 }
 @Override
 protected void onDraw(Canvas canvas) {
  super.onDraw(canvas);
  // 绘制圆弧和进度
  drawArc(canvas);
  // 绘制文字 title
  drawTitleText(canvas);
  // 绘制文字副标题
  drawSubTitle(canvas);
 }
 @Override
 protected void onAttachedToWindow() {
  super.onAttachedToWindow();
  startAnimation();
 }
 private void startAnimation() {
  ValueAnimator progressAnimator = ValueAnimator.ofFloat(0f, arcProgress);
  ValueAnimator numberAnimator = ValueAnimator.ofInt(0, arcTitleNumber);
  progressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
   @Override
   public void onAnimationUpdate(ValueAnimator animation) {
    curProgress = (float) animation.getAnimatedValue();
    invalidate();
   }
  });
  numberAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
   @Override
   public void onAnimationUpdate(ValueAnimator animation) {
    curNumber = (int) animation.getAnimatedValue();
    text = curNumber + "分";
   }
  });
  AnimatorSet animatorSet = new AnimatorSet();
  animatorSet.playTogether(progressAnimator, numberAnimator);
  animatorSet.setDuration(700);
  animatorSet.setInterpolator(new LinearInterpolator());
  animatorSet.start();
 }
 private void drawSubTitle(Canvas canvas) {
  canvas.save();
  canvas.translate(centerX, centerY);
  paint.setTextSize(arcSubTitleTextSize);
  paint.setTextAlign(Paint.Align.CENTER);
  paint.setColor(arcSubTitleColor);
  paint.setStyle(Paint.Style.FILL);
  paint.setStrokeWidth(0);
  canvas.drawText(subTitle, 0, 60, paint);
  canvas.restore();
 }
 private void drawArc(Canvas canvas) {
  canvas.save();
  canvas.translate(centerX, centerY);
  paint.setColor(arcBackgroundColor);
  paint.setStrokeWidth(arcStrokeWidth);
  paint.setStyle(Paint.Style.STROKE);
  canvas.drawArc(rectF, startAngle, sweepAngle, false, paint);
  paint.setColor(arcProgressColor);
  canvas.drawArc(rectF, startAngle, sweepAngle * curProgress, false, paint);
  canvas.restore();
 }
 private void drawTitleText(Canvas canvas) {
  canvas.save();
  textPaint.setTextSize(arcTitleTextSize);
  float textWidth = textPaint.measureText(text); // 文字宽度
  float textHeight = -textPaint.ascent() + textPaint.descent(); // 文字高度
  // 由于 StaticLayout 绘制文字时,默认画在Canvas的(0,0)点位置,所以居中绘制居中位置,需要将画布 translate到中间位置。
  canvas.translate(centerX - textWidth * 2 / 5f, centerY - textHeight * 2 / 3f);
  spannableString = SpannableString.valueOf(text);
  spannableString.setSpan(styleSpan, 0, text.length() - 1, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
  spannableString.setSpan(relativeSizeSpan, text.length() - 1, text.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
  dynamicLayout = new DynamicLayout(spannableString, textPaint, getWidth(), Layout.Alignment.ALIGN_NORMAL, 0, 0, false);
  dynamicLayout.draw(canvas);
  canvas.restore();
 }
 /**
  * 对外提供方法,设置进度
  *
  * @param percent
  */
 public void setArcProgress(float percent) {
  this.curProgress = percent;
  invalidate();
 }
 private int getMeasuredSize(int measureSpec, int defvalue) {
  int mode = MeasureSpec.getMode(measureSpec);
  int size = MeasureSpec.getSize(measureSpec);
  if (mode == MeasureSpec.EXACTLY) {
   return size;
  }
  return Math.min(size, defvalue);
 }
 private int dp2px(int dp) {
  return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
 }
 private int sp2px(int sp) {
  return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, getResources().getDisplayMetrics());
 }
}

3. 在布局中引用

<com.xing.bottomsheetsample.ArcProgressView
  android:layout_width="match_parent"
  android:layout_height="100dp"
  android:layout_marginTop="20dp"
  app:arcProgress="0.6"
  app:arcSubTitleTextSize="14sp"
  app:arcTitleNumber="61"
  app:arcTitleTextSize="28sp" />

总结

到此这篇关于Android 自定义 View 中使用 Spannable的文章就介绍到这了,更多相关Android 使用 Spannable内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Android自定义View实现颜色选取器

    Android 自定义View 颜色选取器,可以实现水平.竖直选择颜色类似 SeekBar 的方式通过滑动选择颜色. 效果图 xml 属性 1.indicatorColor 指示点颜色 2.indicatorEnable 是否使用指示点 3.orientation 方向 horizontal 水平 vertical 竖直 使用 复制 \library\src-\ColorPickerView.java 和 \library\src\main\res\values\attrs.xml 文件到你的项

  • Android自定义view仿淘宝快递物流信息时间轴

    学了Android有一段时间了,一直没有时间写博客,趁着周末有点空,就把自己做的一些东西写下来. 一方面锻炼一下自己的写文档的能力,另一方面分享代码的同时也希望能与大家交流一下技术,共同学习,共同进步. 废话不多少说,我们先来看看我们自定义view要实现的效果: 效果图 自定义属性 <resources> <declare-styleable name="TimeLineView"> <attr name="timelineRadius"

  • Android SpannableString设置超链接、颜色、字体等属性

    Android SpannableString设置超链接.颜色.字体等属性 在Android中,TextView是我们最常用的用来显示文本的控件. 一般情况下,TextView中的文本都是一个样式.那么如何对于TextView中各个部分的文本来设置字体,大小,颜色,样式,以及超级链接等属性呢?下面我们通过SpannableString的具体实例操作来演示一下. package com.snowdream; import java.io.IOException; import org.xmlpul

  • Android TextView使用SpannableString设置复合文本的方法详解

    本文实例讲述了Android TextView使用SpannableString设置复合文本的方法.分享给大家供大家参考,具体如下: TextView通常用来显示普通文本,但是有时候需要对其中某些文本进行样式.事件方面的设置.Android系统通过SpannableString类来对指定文本进行相关处理,具体有以下功能: 1.BackgroundColorSpan 背景色 2.ClickableSpan 文本可点击,有点击事件 3.ForegroundColorSpan 文本颜色(前景色) 4.

  • Android自定义View多种效果解析

    最近项目里涉及到自定义View的东西还是挺多的,所以打算在自定义View上多花点时间,也顺便分享给大家. 先总结下自定义View的步骤: 1.自定义View的属性 2.在View的构造方法中获得我们自定义的属性 [3.重写onMeasure] 4.重写onDraw 1.首先在我们的res/values/目录下建立一个attrs.xml文件,然后在里面声明我们我们需要的自定义属性 我们定义了矩形的颜色,矩形的高度,矩形的宽度3个属性,format是指该属性的取值类型: 一共有:string,col

  • Android自定义View实现加载进度条效果

    上一篇文章总结了下自定义View的几个步骤,如果还有不清楚的同学可以先去看看Android自定义View(一),这篇文章和大家分享一下自定义加载进度条,效果如下 下面就以水平的进度条为列进行讲解: 1.首先还是在attrs.xml文件中自定义我们需要的属性: <?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="GradientP

  • Android 自定义 View 中使用 Spannable的实例详解

    我们都知道 Android 中使用 Spannable 可以实现 TextView 富文本的显示,但是在自定义控件中如何使用 Spannable 绘制不同样式的文字呢? 例如这种效果,标题中的 分数字61 是粗体,分 是常规字体,并且相对于 61 更小些. 第一反应可能是使用 SpannableString.setSpan() 设置 RelativeSizeSpan, 然后在 onDraw() 中进行绘制,事实是这样实现是没有效果的,因为 onDraw() 中只能获取到 SpannableStr

  • Android自定义View中attrs.xml的实例详解

    Android自定义View中attrs.xml的实例详解 我们在自定义View的时候通常需要先完成attrs.xml文件 在values中定义一个attrs.xml 然后添加相关属性 这一篇先详细介绍一下attrs.xml的属性. <?xml version="1.0" encoding="utf-8"?> <resources> //自定义属性名,定义公共属性 <attr name="titleText" for

  • Android自定义view实现滚动选择控件详解

    目录 前言 需求 编写代码 主要问题 前言 上篇文章通过一个有header和footer的滚动控件(Viewgroup)学了下MeasureSpec.onMeasure以及onLayout,接下来就用一个滚动选择的控件(View)来学一下onDraw的使用,并且了解下在XML自定义控件参数. 需求 这里就是一个滚动选择文字的控件,还是挺常见的,之前用别人的,现在选择手撕一个,核心思想如下: 1.有三层不同大小及透明度的选项,选中项放在中间 2.接受一个列表的数据,静态时显示三个值,滚动时显示四个

  • Android自定义View实现绘制虚线的方法详解

    前言 说实话当第一次看到这个需求的时候,第一反应就是Canvas只有drawLine方法,并没有drawDashLine方法啊!这咋整啊,难道要我自己做个遍历不断的drawLine?不到1秒,我就放弃这个想法了,因为太恶心了.方法肯定是有的,只不过我不知道而已. 绘制方法 最简单的方法是利用ShapeDrawable,比如说你想用虚线要隔开两个控件,就可以在这两个控件中加个View,然后给它个虚线背景. 嗯,理论上就是这样子的,实现上也很简单. <!-- drawable 文件 --> <

  • Android自定义View圆形图片控件代码详解

    前言 在日常开发中,圆形的图片效果还是很常见的.可以通过给Paint设置Xfermode来实现,这里简单记录如下. 实现 实现圆形效果的核心是PorterDuffXfermode,对于PorterDuffXfermode,这里不展开,可以查询相关资料. 核心代码 //绘制背景 canvas.drawCircle(mSize / 2, mSize / 2, mSize / 2, mPaint); //设置模式为:显示背景层和上层的交集,且显示上层图像 mPaint.setXfermode(new

  • Android自定义View实现纵向跑马灯效果详解

    首先看看效果图(录制的gif有点卡,真实的效果还是很流畅的) 实现思路 通过上面的gif图可以得出结论,其实它就是同时绘制两条文本信息,然后通过动画不断的改变两条文本信息距离顶部的高度,以此来实现滚动的效果. 具体实现 首先定义一些要用到的属性 <declare-styleable name="MarqueeViewStyle"> <attr name="textSize" format="dimension" /> &l

  • Android 中 Tweened animation的实例详解

    Android 中 Tweened animation的实例详解 Tweened animation有四种类型,下面主要介绍Scale类型. 运行效果如下: Android SDK提供了2种方法:直接从XML资源中读取Animation,使用Animation子类的构造函数来初始化Animation对象,第二种方法在看了Android SDK中各个类的说明就知道如何使用了,下面简要说明从XML资源中读取Animation.XML资源中的动画文件animation.xml内容为: <?xml ve

  • Android中mvp模式使用实例详解

    MVP 是从经典的模式MVC演变而来,它们的基本思想有相通的地方:Controller/Presenter负责逻辑的处理,Model提供数据,View负 责显示.作为一种新的模式,MVP与MVC有着一个重大的区别:在MVP中View并不直接使用Model,它们之间的通信是通过Presenter (MVC中的Controller)来进行的,所有的交互都发生在Presenter内部,而在MVC中View会从直接Model中读取数据而不是通过 Controller. 在MVC里,View是可以直接访问

  • Android 通过自定义view实现水波纹效果案例详解

    在实际的开发中,很多时候还会遇到相对比较复杂的需求,比如产品妹纸或UI妹纸在哪看了个让人兴奋的效果,兴致高昂的来找你,看了之后目的很明确,当然就是希望你能给她: 在这样的关键时候,身子板就一定得硬了,可千万别说不行,爷们儿怎么能说不行呢: 好了,为了让大家都能给妹纸们想要的,后面会逐渐分享一些比较比较不错的效果,目的只有一个,通过自定义view实现我们所能实现的动效: 今天主要分享水波纹效果: 标准正余弦水波纹: 非标准圆形液柱水波纹: 虽说都是水波纹,但两者在实现上差异是比较大的,一个通过正余

  • Android通过自定义view实现刮刮乐效果详解

    前言 已经有两个月没有更新博客了,其实这篇文章我早在两个月前就写好了,一直保存在草稿箱里没有发布出来.原因是有一些原理性的东西还没了解清楚,最近抽时间研究了一下混合模式,终于也理解了刮刮乐是怎么实现的,所以想继续分享一下自己的一些心得,先上效果图. 效果图: 实现原理 其实刮刮乐实现原理也不算很复杂,最关键的还是需要了解Paint的混合模式.因为刮刮乐是由两个bitmap组成的,一个是源图另一个是目标图,我们需要把目标图的颜色改成灰色,在源图上面盖上了一张灰色的目标图.当手指滑动屏幕时paint

随机推荐