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

例如這種效果,標題中的 分數(shù)字61 是粗體,分 是常規(guī)字體,并且相對于 61 更小些。
第一反應可能是使用 SpannableString.setSpan() 設(shè)置 RelativeSizeSpan, 然后在 onDraw() 中進行繪制,事實是這樣實現(xiàn)是沒有效果的,因為 onDraw() 中只能獲取到 SpannableString 中的內(nèi)容,拿不到 Span.
那如何在自定義View 中使用 Spannable 呢? 答案就是系統(tǒng)提供的 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 是一個抽象類,有三個子類,可以實現(xiàn)一些自動換行的顯示效果。
- BoringLayout
- DynamicLayout
- StaticLayout
實現(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; // 圓弧進度顏色
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); // 設(shè)置該屬性導致文字間有間隔
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();
}
/**
* 對外提供方法,設(shè)置進度
*
* @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)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
VerticalBannerView仿淘寶頭條實現(xiàn)垂直輪播廣告
這篇文章主要為大家詳細介紹了VerticalBannerView仿淘寶頭條實現(xiàn)垂直輪播廣告,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-08-08
ListView異步加載圖片實現(xiàn)思路(優(yōu)化篇)
關(guān)于listview的異步加載,網(wǎng)上其實很多示例了,中心思想都差不多,不過很多版本或是有bug,或是有性能問題有待優(yōu)化,下面就讓在下闡述其原理以探索個中奧秘2013-04-04
Android WebView調(diào)用本地相冊的方法
這篇文章主要為大家詳細介紹了Android WebView調(diào)用本地相冊的方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-12-12
淺談Android開發(fā)系列網(wǎng)絡篇之Retrofit
這篇文章主要介紹了淺談Android開發(fā)系列網(wǎng)絡篇之Retrofit,具有一定的參考價值,感興趣的小伙伴們可以參考一下。2016-12-12
Android實現(xiàn)隨意拖動View效果的實例代碼
這篇文章主要介紹了Android實現(xiàn)隨意拖動View效果,本文通過實例代碼給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下2019-07-07
Android App支付系列(一):微信支付接入詳細指南(附官方支付demo)
這篇文章主要介紹了Android App支付系列(一):微信支付接入詳細指南(附官方支付demo) ,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-11-11

