Android應(yīng)用中炫酷的橫向和環(huán)形進(jìn)度條的實例分享
一、概述
最近需要用進(jìn)度條,秉著不重復(fù)造輪子的原則,上github上搜索了一番,看了幾個覺得比較好看的ProgressBar,比如:daimajia的等。簡單看了下代碼,基本都是繼承自View,徹徹底底的自定義了一個進(jìn)度條。盯著那絢麗滾動條,忽然覺得,為什么要通過View去寫一個滾動條,系統(tǒng)已經(jīng)提供了ProgressBar以及屬于它的特性,我們沒必要重新去構(gòu)建一個,但是系統(tǒng)的又比較丑,不同版本變現(xiàn)還不一定一樣。那么得出我們的目標(biāo):改變系統(tǒng)ProgressBar的樣子。
對沒錯,我們沒有必要去從0打造一個ProgressBar,人家雖然長的不好看,但是特性以及穩(wěn)定性還是剛剛的,我們只需要為其整下容就ok了。
接下來,我們貼下效果圖:
1、橫向的進(jìn)度條
2、圓形的進(jìn)度條
沒錯,這就是我們的進(jìn)度條效果,橫向的模仿了daimajia的進(jìn)度條樣子。不過我們繼承子ProgressBar,簡單的為其整個容,代碼清晰易懂 。為什么說,易懂呢?
橫向那個進(jìn)度條,大家會drawLine()和drawText()吧,那么通過getWidth()拿到控件的寬度,再通過getProgress()拿到進(jìn)度,按比例控制繪制線的長短,字的位置還不是分分鐘的事。
二、實現(xiàn)
橫向的滾動條繪制肯定需要一些屬性,比如已/未到達(dá)進(jìn)度的顏色、寬度,文本的顏色、大小等。
本來呢,我是想通過系統(tǒng)ProgressBar的progressDrawable,從里面提取一些屬性完成繪制需要的參數(shù)的。但是,最終呢,反而讓代碼變得復(fù)雜。所以最終還是改用自定義屬性。 說道自定義屬性,大家應(yīng)該已經(jīng)不陌生了。
1、HorizontalProgressBarWithNumber
(1)自定義屬性
values/attr_progress_bar.xml:
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="HorizontalProgressBarWithNumber"> <attr name="progress_unreached_color" format="color" /> <attr name="progress_reached_color" format="color" /> <attr name="progress_reached_bar_height" format="dimension" /> <attr name="progress_unreached_bar_height" format="dimension" /> <attr name="progress_text_size" format="dimension" /> <attr name="progress_text_color" format="color" /> <attr name="progress_text_offset" format="dimension" /> <attr name="progress_text_visibility" format="enum"> <enum name="visible" value="0" /> <enum name="invisible" value="1" /> </attr> </declare-styleable> <declare-styleable name="RoundProgressBarWidthNumber"> <attr name="radius" format="dimension" /> </declare-styleable> </resources>
(2)構(gòu)造中獲取
public class HorizontalProgressBarWithNumber extends ProgressBar { private static final int DEFAULT_TEXT_SIZE = 10; private static final int DEFAULT_TEXT_COLOR = 0XFFFC00D1; private static final int DEFAULT_COLOR_UNREACHED_COLOR = 0xFFd3d6da; private static final int DEFAULT_HEIGHT_REACHED_PROGRESS_BAR = 2; private static final int DEFAULT_HEIGHT_UNREACHED_PROGRESS_BAR = 2; private static final int DEFAULT_SIZE_TEXT_OFFSET = 10; /** * painter of all drawing things */ protected Paint mPaint = new Paint(); /** * color of progress number */ protected int mTextColor = DEFAULT_TEXT_COLOR; /** * size of text (sp) */ protected int mTextSize = sp2px(DEFAULT_TEXT_SIZE); /** * offset of draw progress */ protected int mTextOffset = dp2px(DEFAULT_SIZE_TEXT_OFFSET); /** * height of reached progress bar */ protected int mReachedProgressBarHeight = dp2px(DEFAULT_HEIGHT_REACHED_PROGRESS_BAR); /** * color of reached bar */ protected int mReachedBarColor = DEFAULT_TEXT_COLOR; /** * color of unreached bar */ protected int mUnReachedBarColor = DEFAULT_COLOR_UNREACHED_COLOR; /** * height of unreached progress bar */ protected int mUnReachedProgressBarHeight = dp2px(DEFAULT_HEIGHT_UNREACHED_PROGRESS_BAR); /** * view width except padding */ protected int mRealWidth; protected boolean mIfDrawText = true; protected static final int VISIBLE = 0; public HorizontalProgressBarWithNumber(Context context, AttributeSet attrs) { this(context, attrs, 0); } public HorizontalProgressBarWithNumber(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); setHorizontalScrollBarEnabled(true); obtainStyledAttributes(attrs); mPaint.setTextSize(mTextSize); mPaint.setColor(mTextColor); } /** * get the styled attributes * * @param attrs */ private void obtainStyledAttributes(AttributeSet attrs) { // init values from custom attributes final TypedArray attributes = getContext().obtainStyledAttributes( attrs, R.styleable.HorizontalProgressBarWithNumber); mTextColor = attributes .getColor( R.styleable.HorizontalProgressBarWithNumber_progress_text_color, DEFAULT_TEXT_COLOR); mTextSize = (int) attributes.getDimension( R.styleable.HorizontalProgressBarWithNumber_progress_text_size, mTextSize); mReachedBarColor = attributes .getColor( R.styleable.HorizontalProgressBarWithNumber_progress_reached_color, mTextColor); mUnReachedBarColor = attributes .getColor( R.styleable.HorizontalProgressBarWithNumber_progress_unreached_color, DEFAULT_COLOR_UNREACHED_COLOR); mReachedProgressBarHeight = (int) attributes .getDimension( R.styleable.HorizontalProgressBarWithNumber_progress_reached_bar_height, mReachedProgressBarHeight); mUnReachedProgressBarHeight = (int) attributes .getDimension( R.styleable.HorizontalProgressBarWithNumber_progress_unreached_bar_height, mUnReachedProgressBarHeight); mTextOffset = (int) attributes .getDimension( R.styleable.HorizontalProgressBarWithNumber_progress_text_offset, mTextOffset); int textVisible = attributes .getInt(R.styleable.HorizontalProgressBarWithNumber_progress_text_visibility, VISIBLE); if (textVisible != VISIBLE) { mIfDrawText = false; } attributes.recycle(); }
嗯,看起來代碼挺長,其實都是在獲取自定義屬性,沒什么技術(shù)含量。
(3)onMeasure
剛才不是出onDraw里面寫寫就行了么,為什么要改onMeasure呢,主要是因為我們所有的屬性比如進(jìn)度條寬度讓用戶自定義了,所以我們的測量也得稍微變下。
@Override protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int heightMode = MeasureSpec.getMode(heightMeasureSpec); if (heightMode != MeasureSpec.EXACTLY) { float textHeight = (mPaint.descent() + mPaint.ascent()); int exceptHeight = (int) (getPaddingTop() + getPaddingBottom() + Math .max(Math.max(mReachedProgressBarHeight, mUnReachedProgressBarHeight), Math.abs(textHeight))); heightMeasureSpec = MeasureSpec.makeMeasureSpec(exceptHeight, MeasureSpec.EXACTLY); } super.onMeasure(widthMeasureSpec, heightMeasureSpec); }
寬度我們不變,所以的自定義屬性不涉及寬度,高度呢,只考慮不是EXACTLY的情況(用戶明確指定了,我們就不管了),根據(jù)padding和進(jìn)度條寬度算出自己想要的,如果非EXACTLY下,我們進(jìn)行exceptHeight封裝,傳入給控件進(jìn)行測量高度。
測量完,就到我們的onDraw了~~~
(4)onDraw
@Override protected synchronized void onDraw(Canvas canvas) { canvas.save(); //畫筆平移到指定paddingLeft, getHeight() / 2位置,注意以后坐標(biāo)都為以此為0,0 canvas.translate(getPaddingLeft(), getHeight() / 2); boolean noNeedBg = false; //當(dāng)前進(jìn)度和總值的比例 float radio = getProgress() * 1.0f / getMax(); //已到達(dá)的寬度 float progressPosX = (int) (mRealWidth * radio); //繪制的文本 String text = getProgress() + "%"; //拿到字體的寬度和高度 float textWidth = mPaint.measureText(text); float textHeight = (mPaint.descent() + mPaint.ascent()) / 2; //如果到達(dá)最后,則未到達(dá)的進(jìn)度條不需要繪制 if (progressPosX + textWidth > mRealWidth) { progressPosX = mRealWidth - textWidth; noNeedBg = true; } // 繪制已到達(dá)的進(jìn)度 float endX = progressPosX - mTextOffset / 2; if (endX > 0) { mPaint.setColor(mReachedBarColor); mPaint.setStrokeWidth(mReachedProgressBarHeight); canvas.drawLine(0, 0, endX, 0, mPaint); } // 繪制文本 if (mIfDrawText) { mPaint.setColor(mTextColor); canvas.drawText(text, progressPosX, -textHeight, mPaint); } // 繪制未到達(dá)的進(jìn)度條 if (!noNeedBg) { float start = progressPosX + mTextOffset / 2 + textWidth; mPaint.setColor(mUnReachedBarColor); mPaint.setStrokeWidth(mUnReachedProgressBarHeight); canvas.drawLine(start, 0, mRealWidth, 0, mPaint); } canvas.restore(); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mRealWidth = w - getPaddingRight() - getPaddingLeft(); }
其實核心方法就是onDraw了,但是呢,onDraw也很簡單,繪制線、繪制文本、繪制線,結(jié)束。
還有兩個簡單的輔助方法:
/** * dp 2 px * * @param dpVal */ protected int dp2px(int dpVal) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpVal, getResources().getDisplayMetrics()); } /** * sp 2 px * * @param spVal * @return */ protected int sp2px(int spVal) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spVal, getResources().getDisplayMetrics()); }
好了,到此我們的橫向進(jìn)度就結(jié)束了,是不是很簡單~~如果你是自定義View,你還得考慮progress的更新,考慮狀態(tài)的銷毀與恢復(fù)等等復(fù)雜的東西。
接下來看我們的RoundProgressBarWidthNumber圓形的進(jìn)度條。
2、RoundProgressBarWidthNumber
圓形的進(jìn)度條和橫向的進(jìn)度條基本變量都是一致的,于是我就讓RoundProgressBarWidthNumber extends HorizontalProgressBarWithNumber 了。
然后需要改變的就是測量和onDraw了:
完整代碼:
package com.zhy.view; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint.Cap; import android.graphics.Paint.Style; import android.graphics.RectF; import android.util.AttributeSet; import com.zhy.library.view.R; public class RoundProgressBarWidthNumber extends HorizontalProgressBarWithNumber { /** * mRadius of view */ private int mRadius = dp2px(30); public RoundProgressBarWidthNumber(Context context) { this(context, null); } public RoundProgressBarWidthNumber(Context context, AttributeSet attrs) { super(context, attrs); mReachedProgressBarHeight = (int) (mUnReachedProgressBarHeight * 2.5f); TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.RoundProgressBarWidthNumber); mRadius = (int) ta.getDimension( R.styleable.RoundProgressBarWidthNumber_radius, mRadius); ta.recycle(); mTextSize = sp2px(14); mPaint.setStyle(Style.STROKE); mPaint.setAntiAlias(true); mPaint.setDither(true); mPaint.setStrokeCap(Cap.ROUND); } @Override protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int heightMode = MeasureSpec.getMode(heightMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int paintWidth = Math.max(mReachedProgressBarHeight, mUnReachedProgressBarHeight); if (heightMode != MeasureSpec.EXACTLY) { int exceptHeight = (int) (getPaddingTop() + getPaddingBottom() + mRadius * 2 + paintWidth); heightMeasureSpec = MeasureSpec.makeMeasureSpec(exceptHeight, MeasureSpec.EXACTLY); } if (widthMode != MeasureSpec.EXACTLY) { int exceptWidth = (int) (getPaddingLeft() + getPaddingRight() + mRadius * 2 + paintWidth); widthMeasureSpec = MeasureSpec.makeMeasureSpec(exceptWidth, MeasureSpec.EXACTLY); } super.onMeasure(heightMeasureSpec, heightMeasureSpec); } @Override protected synchronized void onDraw(Canvas canvas) { String text = getProgress() + "%"; // mPaint.getTextBounds(text, 0, text.length(), mTextBound); float textWidth = mPaint.measureText(text); float textHeight = (mPaint.descent() + mPaint.ascent()) / 2; canvas.save(); canvas.translate(getPaddingLeft(), getPaddingTop()); mPaint.setStyle(Style.STROKE); // draw unreaded bar mPaint.setColor(mUnReachedBarColor); mPaint.setStrokeWidth(mUnReachedProgressBarHeight); canvas.drawCircle(mRadius, mRadius, mRadius, mPaint); // draw reached bar mPaint.setColor(mReachedBarColor); mPaint.setStrokeWidth(mReachedProgressBarHeight); float sweepAngle = getProgress() * 1.0f / getMax() * 360; canvas.drawArc(new RectF(0, 0, mRadius * 2, mRadius * 2), 0, sweepAngle, false, mPaint); // draw text mPaint.setStyle(Style.FILL); canvas.drawText(text, mRadius - textWidth / 2, mRadius - textHeight, mPaint); canvas.restore(); } }
首先獲取它的專有屬性mRadius,然后根據(jù)此屬性去測量,測量完成繪制;
繪制的過程呢?
先繪制一個細(xì)一點的圓,然后繪制一個粗一點的弧度,二者疊在一起就行。文本呢,繪制在中間~~~總體,沒什么代碼量。
好了,兩個進(jìn)度條就到這了,是不是發(fā)現(xiàn)簡單很多。總體設(shè)計上,存在些問題,如果抽取一個BaseProgressBar用于獲取公共的屬性;然后不同樣子的進(jìn)度條繼承分別實現(xiàn)自己的測量和樣子,這樣結(jié)構(gòu)可能會清晰些~~~
三、使用
布局文件
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:zhy="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" > <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="25dp" > <com.zhy.view.HorizontalProgressBarWithNumber android:id="@+id/id_progressbar01" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="50dip" android:padding="5dp" /> <com.zhy.view.HorizontalProgressBarWithNumber android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="50dip" android:padding="5dp" android:progress="50" zhy:progress_text_color="#ffF53B03" zhy:progress_unreached_color="#ffF7C6B7" /> <com.zhy.view.RoundProgressBarWidthNumber android:id="@+id/id_progress02" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="50dip" android:padding="5dp" android:progress="30" /> <com.zhy.view.RoundProgressBarWidthNumber android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="50dip" android:padding="5dp" android:progress="50" zhy:progress_reached_bar_height="20dp" zhy:progress_text_color="#ffF53B03" zhy:radius="60dp" /> </LinearLayout> </ScrollView>
MainActivity
package com.zhy.sample.progressbar; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import com.zhy.annotation.Log; import com.zhy.view.HorizontalProgressBarWithNumber; public class MainActivity extends Activity { private HorizontalProgressBarWithNumber mProgressBar; private static final int MSG_PROGRESS_UPDATE = 0x110; private Handler mHandler = new Handler() { @Log public void handleMessage(android.os.Message msg) { int progress = mProgressBar.getProgress(); mProgressBar.setProgress(++progress); if (progress >= 100) { mHandler.removeMessages(MSG_PROGRESS_UPDATE); } mHandler.sendEmptyMessageDelayed(MSG_PROGRESS_UPDATE, 100); }; }; @Log @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mProgressBar = (HorizontalProgressBarWithNumber) findViewById(R.id.id_progressbar01); mHandler.sendEmptyMessage(MSG_PROGRESS_UPDATE); } }
- Android實現(xiàn)環(huán)形進(jìn)度條
- Android自定義環(huán)形LoadingView效果
- Android自定義View實現(xiàn)環(huán)形進(jìn)度條的思路與實例
- Android實現(xiàn)計步進(jìn)度的環(huán)形Progress
- Android實現(xiàn)環(huán)形進(jìn)度條的實例
- Android實現(xiàn)環(huán)形進(jìn)度條代碼
- Android中制作進(jìn)度框和環(huán)形進(jìn)度條的簡單實例分享
- Android環(huán)形進(jìn)度條(安卓默認(rèn)形式)實例代碼
- android自定義環(huán)形對比圖效果
相關(guān)文章
Android 使用Vitamio打造自己的萬能播放器(10)—— 本地播放 (縮略圖、視頻信息、視頻掃描服務(wù))
本文主要介紹Android 使用Vitamio開發(fā)播放器,這里主要講解本地播放 (縮略圖、視頻信息、視頻掃描服務(wù))等功能,有需要的小伙伴可以參考下2016-07-07Android多種方式實現(xiàn)相機圓形預(yù)覽的示例代碼
這篇文章主要介紹了Android多種方式實現(xiàn)相機圓形預(yù)覽的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08Android音樂播放器制作 點擊歌曲實現(xiàn)播放(二)
這篇文章主要為大家詳細(xì)介紹了Android音樂播放器的制作方法,點擊歌曲實現(xiàn)播放,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-02-02基于android示例程序(bitmapfun) 高效加載圖片讓人無語地方
嘗試了使用git上的一個開源項目afinal(bitmapfun的封裝版)來加載圖片,但是在測試的時候發(fā)現(xiàn)了一個問題,新的圖片加載器(bitmapfun)比之前用的ImageDownloader要慢很多,特別是在網(wǎng)絡(luò)狀況不好的時候,那簡直是太讓人無語了2013-04-04