我们都知道 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. *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. 定义自定义属性
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. 在布局中引用