Android 自定義 View 中使用 Spannable的實(shí)例詳解
我們都知道 Android 中使用 Spannable 可以實(shí)現(xiàn) TextView 富文本的顯示,但是在自定義控件中如何使用 Spannable 繪制不同樣式的文字呢?

例如這種效果,標(biāo)題中的 分?jǐn)?shù)字61 是粗體,分 是常規(guī)字體,并且相對(duì)于 61 更小些。
第一反應(yīng)可能是使用 SpannableString.setSpan() 設(shè)置 RelativeSizeSpan, 然后在 onDraw() 中進(jìn)行繪制,事實(shí)是這樣實(shí)現(xiàn)是沒(méi)有效果的,因?yàn)?onDraw() 中只能獲取到 SpannableString 中的內(nèi)容,拿不到 Span.
那如何在自定義View 中使用 Spannable 呢? 答案就是系統(tǒng)提供的 Layout 類(lèi),
/**
* 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 是一個(gè)抽象類(lèi),有三個(gè)子類(lèi),可以實(shí)現(xiàn)一些自動(dòng)換行的顯示效果。
- BoringLayout
- DynamicLayout
- StaticLayout
實(shí)現(xiàn)代碼
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; // 圓弧進(jìn)度顏色
private int arcSubTitleColor; // 副標(biāo)題顏色
private float arcStrokeWidth; // 圓弧線的厚度
private float arcTitleTextSize; // 標(biāo)題文字大小
private float arcSubTitleTextSize; // 副標(biāo)題文字大小
private float arcProgress; // 進(jìn)度
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; // 當(dāng)前進(jìn)度
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); // 設(shè)置該屬性導(dǎo)致文字間有間隔
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);
// 繪制圓弧和進(jìn)度
drawArc(canvas);
// 繪制文字 title
drawTitleText(canvas);
// 繪制文字副標(biāo)題
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 繪制文字時(shí),默認(rèn)畫(huà)在Canvas的(0,0)點(diǎn)位置,所以居中繪制居中位置,需要將畫(huà)布 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();
}
/**
* 對(duì)外提供方法,設(shè)置進(jìn)度
*
* @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" />
總結(jié)
到此這篇關(guān)于Android 自定義 View 中使用 Spannable的文章就介紹到這了,更多相關(guān)Android 使用 Spannable內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
VerticalBannerView仿淘寶頭條實(shí)現(xiàn)垂直輪播廣告
這篇文章主要為大家詳細(xì)介紹了VerticalBannerView仿淘寶頭條實(shí)現(xiàn)垂直輪播廣告,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-08-08
ListView異步加載圖片實(shí)現(xiàn)思路(優(yōu)化篇)
關(guān)于listview的異步加載,網(wǎng)上其實(shí)很多示例了,中心思想都差不多,不過(guò)很多版本或是有bug,或是有性能問(wèn)題有待優(yōu)化,下面就讓在下闡述其原理以探索個(gè)中奧秘2013-04-04
Android生存指南之:解Bug策略與思路問(wèn)題的詳解
本篇文章是對(duì)Android 解Bug策略與思路的問(wèn)題進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05
Android 用戶Session管理的設(shè)計(jì)方案
這篇文章主要介紹了Android 用戶Session管理的設(shè)計(jì)方案,需要的朋友可以參考下2017-12-12
Android 后臺(tái)調(diào)度任務(wù)與省電詳解
本文主要介紹 Android 后臺(tái)調(diào)度任務(wù)與省電,這里整理了詳細(xì)的知識(shí)資料供大家學(xué)習(xí)參考,希望能幫助有需要的小伙伴2016-08-08
Android WebView調(diào)用本地相冊(cè)的方法
這篇文章主要為大家詳細(xì)介紹了Android WebView調(diào)用本地相冊(cè)的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-12-12
淺談Android開(kāi)發(fā)系列網(wǎng)絡(luò)篇之Retrofit
這篇文章主要介紹了淺談Android開(kāi)發(fā)系列網(wǎng)絡(luò)篇之Retrofit,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2016-12-12
Android實(shí)現(xiàn)隨意拖動(dòng)View效果的實(shí)例代碼
這篇文章主要介紹了Android實(shí)現(xiàn)隨意拖動(dòng)View效果,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-07-07
Android App支付系列(一):微信支付接入詳細(xì)指南(附官方支付demo)
這篇文章主要介紹了Android App支付系列(一):微信支付接入詳細(xì)指南(附官方支付demo) ,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-11-11

