Android 自定義 View 中使用 Spannable的例項詳解
阿新 • • 發佈:2020-05-26
我們都知道 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,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,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,sweepAngle * curProgress,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,text.length() - 1,Spanned.SPAN_INCLUSIVE_EXCLUSIVE); spannableString.setSpan(relativeSizeSpan,text.length(),Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); dynamicLayout = new DynamicLayout(spannableString,textPaint,getWidth(),Layout.Alignment.ALIGN_NORMAL,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內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!