自訂圓形進度條 自訂倒計時進度條

來源:互聯網
上載者:User

標籤:

自訂圓形進度條 自訂倒計時進度條

著作權聲明:轉載必須註明本文轉自嚴振傑的部落格: http://blog.csdn.net/yanzhenjie1003

此控制項源碼已開源到Github:https://github.com/yanzhenjie/CircleTextProgressbar,歡迎Star。
歡迎加入我部落格左側的QQ交流群一起探討。

效果預覽

原始碼傳送門:https://github.com/yanzhenjie/CircleTextProgressbar

實現與原理

這個文字圓形的進度條我們在很多APP中看到過,比如APP歡迎頁面倒計時,下載檔案倒計時等。

分析下原理,可能有的同學一看到這個自訂View就慌了,這個是不是要繼承View啊,是不是要繪製啊之類的,答案是:是的。但是我們也不要擔心,實現這個效果實在是so easy。下面就跟我一起來看看核心分析和代碼吧。

原理分析

首先我們觀察,需要幾個部分組成:
1. 外面逐漸增加/減少的圓形進度條。
2. 圓形進度條中間的展示文字。
3. 圓形進度條外麵包裹的圓。
4. 圓形進度條中間的填充色。
5. 字型顏色/填充顏色點擊變色:ColorStateList類。

我們分析得出需要四個部分。一看有文字,那麼第一個想到的自然是TextView啦,正好可以少做一個字型顏色的記錄。中間的填充顏色(原型暫且不考慮)點擊時變色,需要ColorStateList類來記錄。剩下的進度條、輪廓圓和填充圓是需要我們繪製的。

踩坑的過程

其實好久沒有寫過自訂View了,有些東西還真忘記了,所以寫這個View的時候又把之前的坑踩了一遍,為了避免其它同學也被坑,這裡把我踩的坑也記錄下。

View繪製地區

這裡我遇到一個問題,因為我們繼承的TextView文字多了就是長的,那麼繪製出來的圓長寬是一樣的,所以在TextView上繪製出來的圓只能看到一部分或者是橢圓的。所以我們要把View的繪製地區擴大。當時我第一個想到的是layout()方法,因為當View的父布局onLayout()的時候會調用View的layout()來讓子View布局,我重寫了layout方法:

@Overridepublic void layout(int left, int top, int right, int bottom) {    int w = right - left;    int h = bottom - top;    int size = w > h ? w : h;    if (w > h) {        bottom += (size - h);    } else {        right += (size - w);    }    super.layout(left, top, right, bottom);}

這段代碼的原理就是寬和高,那個大,就把view擴大到這麼最大的這個值。

當放了一個View在Layout時,效果出來沒問題,但是我放多個View到LinearLayout中的時候發現幾個View重疊了,哦舍特。我恍然大悟啊,這尼瑪人家Layout已經把我繪製地區的寬高指定了,我強行去佔領別的View的了。so,我應該重寫onMeasure()啊,在測量寬高的時候就告訴父Layout我要多大的地盤:

@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    super.onMeasure(widthMeasureSpec, heightMeasureSpec);    int width = getMeasuredWidth();    int height = getMeasuredHeight();    int size = width > height ? width : height;    setMeasuredDimension(size, size);}

這段代碼的意思更容易理解,就是看super.onMeasure測量的時候的寬高哪個大,就把寬高都設定成最大的這個值。告訴父Layout我要多大的地盤,那麼等我繪製的時候我想怎麼玩就怎麼玩。

繪製View的實現

好了,來到了關鍵的地方,前面的都搞定了就看我們怎麼繪製我們的幾個圓圈圈了。畫圓圈圈就要重寫onDraw()方法啦。

首先需要一個畫筆:

Paint mPaint = new Paint();mPaint.setAntiAlias(true);// 消除鋸齒
拿到繪製地區

我們可以通過getDrawingRect(Rect)擷取到繪製地區,通過繪製地區計算出這個地區可以繪製圓的半徑。

Rect bounds = new Rect();@Overrideprotected void onDraw(Canvas canvas) {    getDrawingRect(bounds);//擷取view的邊界    int size = bounds.height() > bounds.width() ? bounds.width() : bounds.height();    float outerRadius = size / 2; // 計算出繪製圓的半徑}
繪製填充圓

那麼剛才提到過點擊的時候變色,所以我們要用到ColorStateList,這裡做一個初始化,並且支援在xml中定義這個屬性:

// 預設透明填充。ColorStateList inCircleColors = ColorStateList.valueOf(Color.TRANSPARENT);private void initialize(Context ctx, AttributeSet attributeSet) {    TypedArray typedArray = ctx.obtainStyledAttributes(attributeSet, R.styleable.Progressbar);    inCircleColors = typedArray.getColorStateList(R.styleable.Progressbar_circle_color);    typedArray.recycle();}

不明白如何自定View xml屬性的同學請求自行Google。

根據點擊、Check、Select狀態繪製填充圓的顏色,因為是填充,所以這裡Paint的Style是FILL:

int circleColor = inCircleColors.getColorForState(getDrawableState(), 0);mPaint.setStyle(Paint.Style.FILL);mPaint.setColor(circleColor);canvas.drawCircle(bounds.centerX(), bounds.centerY(), outerRadius - outLineWidth, mPaint);

圓心是繪製地區的圓心,半徑是繪製地區圓的半徑減去外部輪廓圓線的寬度。這樣正好填充圓和外部輪廓圓不重疊。

繪製外部邊框圓

這個就簡單了,因為是空心的線,所以Style是STROKE,然後設定線的寬度,畫筆的顏色:

mPaint.setStyle(Paint.Style.STROKE);mPaint.setStrokeWidth(outLineWidth);mPaint.setColor(outLineColor);canvas.drawCircle(bounds.centerX(), bounds.centerY(), outerRadius - outLineWidth / 2, mPaint);

圓心是繪製地區的圓心,半徑是繪製地區圓的半徑減去外部輪廓圓線的寬度的一半,這樣剛好外部輪廓線和內部填充圓緊靠著。

繪製TextView的字

為了我們的繪製和TextView自身的繪製不重疊,我們幹掉了super.onDraw(canvas);,所以這裡我們要把TextView的字也要寫上去。

首先拿到TextView的預設畫筆,設定TextView本身的字型顏色,消除鋸齒,為了美觀我們強行讓文字置中:

//畫字Paint paint = getPaint();paint.setColor(getCurrentTextColor());paint.setAntiAlias(true);paint.setTextAlign(Paint.Align.CENTER);float textY = bounds.centerY() - (paint.descent() + paint.ascent()) / 2;canvas.drawText(getText().toString(), bounds.centerX(), textY, paint);
繪製進度條

進度條可不是一個圓了喔,準確的說它是一個圓弧,
畫筆使用預設畫筆,設定顏色、Style為STROKE,設定線的寬度,最後是指定繪製地區和圓心,角度:

RectF mArcRect = new RectF();Rect bounds = new Rect();@Overrideprotected void onDraw(Canvas canvas) {    getDrawingRect(bounds);//擷取view的邊界    ...    // 繪製進度條圓弧。    mPaint.setColor(progressLineColor);    mPaint.setStyle(Paint.Style.STROKE);    mPaint.setStrokeWidth(progressLineWidth);    mPaint.setStrokeCap(Paint.Cap.ROUND);    int deleteWidth = progressLineWidth + outLineWidth;    // 指定繪製地區    mArcRect.set(bounds.left + deleteWidth / 2, bounds.top + deleteWidth / 2,    bounds.right -deleteWidth / 2, bounds.bottom - deleteWidth / 2);    canvas.drawArc(mArcRect, 0, 360 * progress / 100, false, mPaint);}

這裡痛點在指定繪製地區,因為不能把外部輪廓線覆蓋了,所以要貼近外部輪廓線的內部畫,所以要最外層繪製圓的地區,所以要減去(外部圓線的寬 + 進度條線的寬) / 2得出來的界線就是進度條的邊界。

繪製和測量的完整代碼

到這裡關鍵代碼都擼完了,你可以自己寫一個試試了,我這裡把完整的onDraw()和onMeasure()的源碼貼出來:

private int outLineColor = Color.BLACK;private int outLineWidth = 2;private ColorStateList inCircleColors = ColorStateList.valueOf(Color.TRANSPARENT);private int circleColor;private int progressLineColor = Color.BLUE;private int progressLineWidth = 8;private Paint mPaint = new Paint();private RectF mArcRect = new RectF();private int progress = 100;final Rect bounds = new Rect();@Overrideprotected void onDraw(Canvas canvas) {    //擷取view的邊界    getDrawingRect(bounds);    int size = bounds.height() > bounds.width() ? bounds.width() : bounds.height();    float outerRadius = size / 2;        //畫內部背景    int circleColor = inCircleColors.getColorForState(getDrawableState(), 0);    mPaint.setStyle(Paint.Style.FILL);    mPaint.setColor(circleColor);    canvas.drawCircle(bounds.centerX(), bounds.centerY(), outerRadius - outLineWidth, mPaint);    //畫邊框圓    mPaint.setStyle(Paint.Style.STROKE);    mPaint.setStrokeWidth(outLineWidth);    mPaint.setColor(outLineColor);    canvas.drawCircle(bounds.centerX(), bounds.centerY(), outerRadius - outLineWidth / 2, mPaint);    //畫字    Paint paint = getPaint();    paint.setColor(getCurrentTextColor());    paint.setAntiAlias(true);    paint.setTextAlign(Paint.Align.CENTER);    float textY = bounds.centerY() - (paint.descent() + paint.ascent()) / 2;    canvas.drawText(getText().toString(), bounds.centerX(), textY, paint);    //畫進度條    mPaint.setColor(progressLineColor);    mPaint.setStyle(Paint.Style.STROKE);    mPaint.setStrokeWidth(progressLineWidth);    mPaint.setStrokeCap(Paint.Cap.ROUND);    int deleteWidth = progressLineWidth + outLineWidth;    mArcRect.set(bounds.left + deleteWidth / 2, bounds.top + deleteWidth / 2,    bounds.right - deleteWidth / 2, bounds.bottom - deleteWidth / 2);    canvas.drawArc(mArcRect, 0, 360 * progress / 100, false, mPaint);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    super.onMeasure(widthMeasureSpec, heightMeasureSpec);    int lineWidth = 4 * (outLineWidth + progressLineWidth);    int width = getMeasuredWidth();    int height = getMeasuredHeight();    int size = (width > height ? width : height) + lineWidth;    setMeasuredDimension(size, size);}

上述完整源碼:https://github.com/yanzhenjie/CircleTextProgressbar,歡迎Star。

著作權聲明:轉載必須註明本文轉自嚴振傑的部落格: http://blog.csdn.net/yanzhenjie1003

自訂圓形進度條 自訂倒計時進度條

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.