android 自定义 view format,Android 自定义 View 中使用 Spannable的实例详解

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

4cc7f2906afc7157f4adb0d9fcbec0da.png

例如这种效果,标题中的 分数字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 {

}

aba75e0285399ceba2a1bacf9c9f0983.png

4d470988d847895ef909bb5a31aeb893.png

可以看到 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. 在布局中引用

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内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值