版權(quán)聲明:本文為博主原創(chuàng)文章,未經(jīng)博主允許不得轉(zhuǎn)載五垮。
系列教程:Android開發(fā)之從零開始系列
源碼:github.com/AnliaLee/Progressbar灵汪,歡迎star大家要是看到有錯(cuò)誤的地方或者有啥好的建議胯舷,歡迎留言評論
前言:相信同行們都知道肌蜻,我們程序員有一種痛,叫做別人的代碼恕刘。讀懂別人的代碼很重要的一點(diǎn)就是要抓住作者的思路缤谎,有了思路才能將過程推導(dǎo)出來,否則腦闊會疼褐着。為己為人坷澡,本系列教程博客,我都會將自己實(shí)現(xiàn)的思路寫下來含蓉,帶大家一步步從零開始實(shí)現(xiàn)我們想要的效果频敛。因?yàn)樽罱诰W(wǎng)上看了很多前輩們實(shí)現(xiàn)的 水波浪進(jìn)度框,一時(shí)手癢馅扣,所以任性地決定這系列的第二篇博客的主角就是它了
本篇只著重于思路和實(shí)現(xiàn)步驟斟赚,里面用到的一些知識原理不會非常細(xì)地拿來講,如果有不清楚的api或方法可以在網(wǎng)上搜下相應(yīng)的資料差油,肯定有大神講得非常清楚的拗军,我這就不獻(xiàn)丑了。本著認(rèn)真負(fù)責(zé)的精神我會把相關(guān)知識的博文鏈接也貼出來(其實(shí)就是懶不想寫那么多哈哈)蓄喇,大家可以自行傳送发侵。為了照顧第一次閱讀系列博客的小伙伴,本篇會出現(xiàn)一些在之前系列博客就講過的內(nèi)容妆偏,看過的童鞋自行跳過該段即可
國際慣例刃鳄,先來效果展示
目錄
- 繪制一段波浪(二階貝塞爾曲線)
- 繪制填充物
- 測量及自適應(yīng)View的寬高
- 讓波浪隨進(jìn)度上升
- 實(shí)現(xiàn)波浪平移效果
- 繪制圓形進(jìn)度框背景
- 自定義attr屬性
- 擴(kuò)展一:實(shí)現(xiàn)隨進(jìn)度變化的文字效果
- 擴(kuò)展二:實(shí)現(xiàn)波浪高度隨進(jìn)度上升而下降的效果
- 擴(kuò)展三:實(shí)現(xiàn)雙波浪效果
繪制一段波浪(二階貝塞爾曲線)
相關(guān)博文鏈接
【Android - 自定義View】之自定義View淺析
Android 自定義View (一)
Android-貝塞爾曲線
安卓自定義View進(jìn)階:Path基本操作
既然我們實(shí)現(xiàn)的是水波浪進(jìn)度條,那我們就先從波浪效果入手吧钱骂。波浪是上下起伏的叔锐,也就意味著我們繪制的波浪應(yīng)該是一條上下波動的曲線。查閱資料發(fā)現(xiàn)二階貝塞爾曲線足以滿足我們的需求罐柳,我們可以通過控制其控制點(diǎn)的坐標(biāo)系y值實(shí)現(xiàn)曲線的上下波動掌腰。Android中提供了繪制貝塞爾曲線的API及方法,下面我們就試著繪制一條上下波動的二階貝塞爾曲線(有關(guān)貝塞爾曲線以及Path方面的知識已經(jīng)有許多大大講得非常清楚了张吉,這里貼出他們的博客鏈接,就不詳細(xì)展開了)
public class WaveProgressView extends View {
private Paint wavePaint;//繪制波浪畫筆
private Path wavePath;//繪制波浪Path
private float waveWidth;//波浪寬度
private float waveHeight;//波浪高度
public WaveProgressView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context,attrs);
}
private void init(Context context,AttributeSet attrs){
waveWidth = DpOrPxUtils.dip2px(context,15);
waveHeight = DpOrPxUtils.dip2px(context,20);
wavePath = new Path();
wavePaint = new Paint();
wavePaint.setColor(Color.GREEN);
wavePaint.setAntiAlias(true);//設(shè)置抗鋸齒
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(getWavePath(),wavePaint);
}
private Path getWavePath(){
wavePath.reset();
wavePath.moveTo(0,waveHeight);//起始點(diǎn)移動至(0,waveHeight),注意坐標(biāo)系y軸是向下的
for (int i=0;i<5;i++){
wavePath.rQuadTo(waveWidth/2, waveHeight, waveWidth, 0);
wavePath.rQuadTo(waveWidth/2, -waveHeight, waveWidth, 0);
}
return wavePath;
}
}
其中用到了dp和px相互轉(zhuǎn)換的工具類(相關(guān)知識有興趣的可以自己上網(wǎng)搜下)催植,這里也將相關(guān)代碼貼出來
public class DpOrPxUtils {
public static int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
public static int px2dip(Context context, float pxValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5f);
}
}
界面布局:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.anlia.progressbar.WaveProgressView
android:id="@+id/wave_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginLeft="20dp"/>
</LinearLayout>
</RelativeLayout>
在Activity中進(jìn)行注冊
waveProgressView = (WaveProgressView) findViewById(R.id.wave_progress);
效果如圖
繪制填充物
相關(guān)博文鏈接
根據(jù)我們的需求肮蛹,我們要模擬出進(jìn)度框中水位隨著進(jìn)度的增加而不斷上升的效果勺择。我們將水看作是一種填充物,然后將填充物劃分成最上層的波浪曲線區(qū)域以及下層的矩形區(qū)域伦忠。我們可以利用path.lineTo()和path.close()方法將波浪曲線和矩形組裝封閉起來省核,最終效果如圖
path繪制的順序如下圖所示(初始點(diǎn)為p0,p3至p0段繪制波浪曲線)
實(shí)現(xiàn)代碼如下昆码,修改我們的WaveProgressView
public class WaveProgressView extends View {
//省略部分代碼...
private int waveNum;//波浪組的數(shù)量(一次起伏為一組)
private int defaultSize;//自定義View默認(rèn)的寬高
private int maxHeight;//為了看到波浪效果气忠,給定一個(gè)比填充物稍高的高度
private void init(Context context,AttributeSet attrs){
//省略部分代碼...
waveWidth = DpOrPxUtils.dip2px(context,20);
waveHeight = DpOrPxUtils.dip2px(context,10);
defaultSize = DpOrPxUtils.dip2px(context,200);
maxHeight = DpOrPxUtils.dip2px(context,250);
waveNum =(int) Math.ceil(Double.parseDouble(String.valueOf(defaultSize / waveWidth / 2)));//波浪的數(shù)量需要進(jìn)一取整,所以使用Math.ceil函數(shù)
}
private Path getWavePath(){
wavePath.reset();
//移動到右上方赋咽,也就是p0點(diǎn)
wavePath.moveTo(defaultSize, maxHeight - defaultSize);
//移動到右下方旧噪,也就是p1點(diǎn)
wavePath.lineTo(defaultSize, defaultSize);
//移動到左下邊,也就是p2點(diǎn)
wavePath.lineTo(0, defaultSize);
//移動到左上方脓匿,也就是p3點(diǎn)
wavePath.lineTo(0, maxHeight - defaultSize);
//從p3開始向p0方向繪制波浪曲線
for (int i=0;i<waveNum;i++){
wavePath.rQuadTo(waveWidth/2, waveHeight, waveWidth, 0);
wavePath.rQuadTo(waveWidth/2, -waveHeight, waveWidth, 0);
}
//將path封閉起來
wavePath.close();
return wavePath;
}
}
測量及自適應(yīng)View的寬高
相關(guān)博文鏈接
在上面的代碼中淘钟,View的寬高是由path區(qū)域的大小決定的,直接寫死在了init()方法中陪毡,而我們的實(shí)際需求是View的寬高可以由我們在外部進(jìn)行設(shè)置米母。根據(jù)需求,進(jìn)度框是一個(gè)圓形毡琉,我們需要將View的寬高強(qiáng)制相等铁瞒,因此我們重寫View的onMeasure()方法
public class WaveProgressView extends View {
//省略部分代碼...
private int viewSize;//重新測量后View實(shí)際的寬高
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int height = measureSize(defaultSize, heightMeasureSpec);
int width = measureSize(defaultSize, widthMeasureSpec);
int min = Math.min(width, height);// 獲取View最短邊的長度
setMeasuredDimension(min, min);// 強(qiáng)制改View為以最短邊為長度的正方形
viewSize = min;
waveNum =(int) Math.ceil(Double.parseDouble(String.valueOf(viewSize / waveWidth / 2)));
}
private int measureSize(int defaultSize,int measureSpec) {
int result = defaultSize;
int specMode = View.MeasureSpec.getMode(measureSpec);
int specSize = View.MeasureSpec.getSize(measureSpec);
if (specMode == View.MeasureSpec.EXACTLY) {
result = specSize;
} else if (specMode == View.MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
return result;
}
}
讓波浪隨進(jìn)度上升
波浪隨進(jìn)度上升,實(shí)際上就是填充物的高度(p0p1桅滋,p3p2的長度)隨進(jìn)度值的增加而增加慧耍。修改我們的WaveProgressView,并添加動畫效果
public class WaveProgressView extends View {
//省略部分代碼...
private WaveProgressAnim waveProgressAnim;
private float percent;//進(jìn)度條占比
private float progressNum;//可以更新的進(jìn)度條數(shù)值
private float maxNum;//進(jìn)度條最大值
private void init(Context context,AttributeSet attrs){
//省略部分代碼...
percent = 0;
progressNum = 0;
maxNum = 100;
waveProgressAnim = new WaveProgressAnim();
}
private Path getWavePath(){
wavePath.reset();
//移動到右上方虱歪,也就是p0點(diǎn)
wavePath.moveTo(viewSize, (1-percent)*viewSize);//讓p0p1的長度隨percent的增加而增加(注意這里y軸方向默認(rèn)是向下的)
//移動到右下方蜂绎,也就是p1點(diǎn)
wavePath.lineTo(viewSize, viewSize);
//移動到左下邊,也就是p2點(diǎn)
wavePath.lineTo(0, viewSize);
//移動到左上方笋鄙,也就是p3點(diǎn)
wavePath.lineTo(0, (1-percent)*viewSize);//讓p3p2的長度隨percent的增加而增加(注意這里y軸方向默認(rèn)是向下的)
//從p3開始向p0方向繪制波浪曲線
for (int i=0;i<waveNum;i++){
wavePath.rQuadTo(waveWidth/2, waveHeight, waveWidth, 0);
wavePath.rQuadTo(waveWidth/2, -waveHeight, waveWidth, 0);
}
//將path封閉起來
wavePath.close();
return wavePath;
}
public class WaveProgressAnim extends Animation {
public WaveProgressAnim(){}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
percent = interpolatedTime * progressNum / maxNum;
postInvalidate();
}
}
/**
* 設(shè)置進(jìn)度條數(shù)值
* @param progressNum 進(jìn)度條數(shù)值
* @param time 動畫持續(xù)時(shí)間
*/
public void setProgressNum(float progressNum, int time) {
this.progressNum = progressNum;
percent = 0;
waveProgressAnim.setDuration(time);
this.startAnimation(waveProgressAnim);
}
}
在Activity中調(diào)用setProgressNum()方法
waveProgressView.setProgressNum(80,3000);
效果如圖
實(shí)現(xiàn)波浪平移效果
上一小節(jié)我們實(shí)現(xiàn)的波浪上升的動畫师枣,這一節(jié)中我們要為波浪添加一個(gè)循環(huán)向左平移的效果
讓波浪向左平移,我們將其可以理解為繪制波浪曲線的起點(diǎn)不斷向左移動萧落,而循環(huán)則是當(dāng)起點(diǎn)移動一段距離后又回到原來的位置重新向左移動践美。通過之前的分析我們知道波浪曲線的繪制起點(diǎn)是p3,因此整個(gè)波浪的平移效果我們只需要通過修改p3的位置即可實(shí)現(xiàn)
但僅僅是這樣還不夠找岖,我們之前整段波浪曲線的寬度和View(正方形目標(biāo)區(qū)域)的寬度是相等的陨倡,如果我們僅僅只是讓p3向左平移,會出現(xiàn)曲線不能鋪滿目標(biāo)區(qū)域的情況许布,曲線與p0則會以默認(rèn)的直線進(jìn)行連接兴革。有2D橫向游戲開發(fā)經(jīng)驗(yàn)的小伙伴對于這種橫向背景循環(huán)的效果會很熟悉,一般的處理手段是將至少兩個(gè)相同的背景圖片拼接起來,當(dāng)角色從第一個(gè)背景圖片最左端出發(fā)杂曲,向右移動了第一個(gè)背景圖片寬度的距離時(shí)庶艾,將角色重新放回到第一個(gè)背景圖片的最左端,這樣就能實(shí)現(xiàn)背景圖片循環(huán)的效果擎勘。參考這種手段咱揍,對于我們波浪循環(huán)平移來說,p3就相當(dāng)于角色棚饵,波浪曲線相當(dāng)于背景圖片煤裙,p3點(diǎn)平移的最大距離為原來一整段曲線的寬度(目標(biāo)區(qū)域的寬度),整段曲線的寬度也變成原來的兩倍(至少兩倍)噪漾。為了讓大家更清楚地了解整個(gè)過程硼砰,我修改了View寬度的測量邏輯給大家看下效果(波浪到達(dá)最大高度后高度不再改變,僅進(jìn)行平移循環(huán))
然后下面是我們實(shí)際要實(shí)現(xiàn)的效果
實(shí)現(xiàn)代碼如下怪与,修改我們的WaveProgressView
public class WaveProgressView extends View {
//省略部分代碼...
private float waveMovingDistance;//波浪平移的距離
private void init(Context context,AttributeSet attrs){
//省略部分代碼...
waveMovingDistance = 0;
}
private Path getWavePath(){
//省略部分代碼...
//移動到左上方夺刑,也就是p3點(diǎn)(x軸默認(rèn)方向是向右的,我們要向左平移分别,因此設(shè)為負(fù)值)
//wavePath.lineTo(0, (1-percent)*viewSize);
wavePath.lineTo(-waveMovingDistance, (1-percent)*viewSize);
//從p3開始向p0方向繪制波浪曲線(曲線寬度為原來的兩倍也就是波浪數(shù)量*2)
for (int i=0;i<waveNum*2;i++){
wavePath.rQuadTo(waveWidth/2, waveHeight, waveWidth, 0);
wavePath.rQuadTo(waveWidth/2, -waveHeight, waveWidth, 0);
}
}
public class WaveProgressAnim extends Animation {
//省略部分代碼...
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
//波浪高度到達(dá)最大值后就不需要循環(huán)了遍愿,只需讓波浪曲線平移循環(huán)即可
if(percent < progressNum / maxNum){
percent = interpolatedTime * progressNum / maxNum;
}
waveMovingDistance = interpolatedTime * waveNum * waveWidth * 2;
postInvalidate();
}
}
/**
* 設(shè)置進(jìn)度條數(shù)值
* @param progressNum 進(jìn)度條數(shù)值
* @param time 動畫持續(xù)時(shí)間
*/
public void setProgressNum(float progressNum, int time) {
//省略部分代碼...
waveAnim.setRepeatCount(Animation.INFINITE);//讓動畫無限循環(huán)
waveAnim.setInterpolator(new LinearInterpolator());//讓動畫勻速播放,不然會出現(xiàn)波浪平移停頓的現(xiàn)象
}
}
如果需要讓波浪到達(dá)最高處后平移的速度改變耘斩,給動畫設(shè)置監(jiān)聽即可
waveProgressAnim.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {}
@Override
public void onAnimationEnd(Animation animation) {}
@Override
public void onAnimationRepeat(Animation animation) {
if(percent == progressNum / maxNum){
waveProgressAnim.setDuration(8000);
}
}
});
繪制圓形進(jìn)度框背景
相關(guān)博文鏈接
android 自定義view 緩存技術(shù)
Android中Canvas繪圖之PorterDuffXfermode使用及工作原理詳解
Android 自定義View學(xué)習(xí)(五)——Paint 關(guān)于PorterDuffXfermode學(xué)習(xí)
終于要開始繪制進(jìn)度框了沼填,之所以要將進(jìn)度框放到后面來講,不僅是因?yàn)檫@部分比較簡單括授,而且按照這樣一個(gè)順序去思考設(shè)計(jì)對于初學(xué)者來說會更加友好坞笙,畢竟是從零開始的教程嘛(所以給個(gè)贊唄?乛?乛?)。好了荚虚,一番自夸之后我們進(jìn)入正題薛夜,按照需求,我們不僅要繪制圓形進(jìn)度框作為背景版述,還需要取進(jìn)度框和波浪填充物的交集部分繪制到進(jìn)度框中梯澜,這里用到了PorterDuffXfermode方面的知識(有不了解的童鞋可以通過上面的博客鏈接傳送過去看看),我們繼續(xù)修改WaveProgressView渴析,只需要加多幾行代碼就可以了
public class WaveProgressView extends View {
//省略部分代碼...
private Paint circlePaint;//圓形進(jìn)度框畫筆
private Bitmap bitmap;//緩存bitmap
private Canvas bitmapCanvas;
private void init(Context context,AttributeSet attrs){
//省略部分代碼...
wavePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));//根據(jù)繪制順序的不同選擇相應(yīng)的模式即可
circlePaint = new Paint();
circlePaint.setColor(Color.GRAY);
circlePaint.setAntiAlias(true);//設(shè)置抗鋸齒
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//這里用到了緩存技術(shù)
bitmap = Bitmap.createBitmap(viewSize, viewSize, Bitmap.Config.ARGB_8888);
bitmapCanvas = new Canvas(bitmap);
bitmapCanvas.drawCircle(viewSize/2, viewSize/2, viewSize/2, circlePaint);
bitmapCanvas.drawPath(getWavePath(),wavePaint);
canvas.drawBitmap(bitmap, 0, 0, null);
}
}
效果如圖
同樣的晚伙,如果想要用其他圖片作為背景進(jìn)度框,也可以按照這樣的思路進(jìn)行擴(kuò)展俭茧,這留給小伙伴們自己去研究咆疗,就不展開說啦(如果用不規(guī)則圖片作為背景時(shí)記得要重新測量View的大小)
自定義attr屬性
相關(guān)博文鏈接
Android自定義View(二、深入解析自定義屬性)
解析:TypedArray 為什么需要調(diào)用recycle()
我們的View中有許多屬性需要在布局文件中進(jìn)行設(shè)置母债,這需要我們自己進(jìn)行自定義午磁,實(shí)現(xiàn)過程如下
首先在res\values文件夾中添加attr.xml,為WaveProgressView自定義屬性
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--注意這里的name要和自定義View的名稱一致,不然在xml布局中無法引用-->
<declare-styleable name="WaveProgressView">
<attr name="wave_color" format="color"></attr>
<attr name="bg_color" format="color"></attr>
<attr name="wave_width" format="dimension"></attr>
<attr name="wave_height" format="dimension"></attr>
</declare-styleable>
</resources>
修改WaveProgressView漓踢,為自定義屬性賦值
public class WaveProgressView extends View {
//省略部分代碼...
private int waveColor;//波浪顏色
private int bgColor;//背景進(jìn)度框顏色
private void init(Context context,AttributeSet attrs){
//省略部分代碼...
TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.WaveProgressView);
waveWidth = typedArray.getDimension(R.styleable.WaveProgressView_wave_width,DpOrPxUtils.dip2px(context,25));
waveHeight = typedArray.getDimension(R.styleable.WaveProgressView_wave_height,DpOrPxUtils.dip2px(context,5));
waveColor = typedArray.getColor(R.styleable.WaveProgressView_wave_color,Color.GREEN);
bgColor = typedArray.getColor(R.styleable.WaveProgressView_bg_color,Color.GRAY);
typedArray.recycle();
wavePaint.setColor(waveColor);
circlePaint.setColor(bgColor);
}
}
在布局文件中設(shè)置自定義屬性試試效果
<!--省略部分代碼-->
<RelativeLayout
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.anlia.progressbar.CircleBarView
app:start_angle="135"
app:sweep_angle="270"
app:progress_color="@color/red"
app:bg_color="@color/gray_light"
app:bar_width="20dp"/>
</LinearLayout>
</RelativeLayout>
效果如圖
到這里我們的水波浪進(jìn)度框的基礎(chǔ)框架已經(jīng)搭建完畢牵署,下面是在這基礎(chǔ)上進(jìn)行擴(kuò)展
擴(kuò)展一:實(shí)現(xiàn)隨進(jìn)度變化的文字效果
根據(jù)需求漏隐,我們需要顯示可以隨進(jìn)度變化的文字喧半,網(wǎng)上許多實(shí)現(xiàn)的方法都是在自定義View中實(shí)現(xiàn)相應(yīng)的文字處理邏輯,然后使用canvas.drawText()方法去繪制文字青责。我個(gè)人覺得這樣寫比較麻煩且可擴(kuò)展性不高挺据,下面提供另外一種思路供大家參考
我的做法是將條形進(jìn)度條和文字顯示區(qū)分開來,文字顯示的組件直接在布局文件用TextView就可以了脖隶,將TextView傳入WaveProgressView扁耐,然后在WaveProgressView提供接口編寫文字處理的邏輯即可。這樣實(shí)現(xiàn)的好處在于后期我們要是想改變文字的字體产阱、樣式婉称、位置等等都不需要再在WaveProgressView中傷筋動骨地去改,實(shí)現(xiàn)了文字與進(jìn)度框控件解耦
具體實(shí)現(xiàn)如下构蹬,修改我們的WaveProgressView
public class WaveProgressView extends View {
//省略部分代碼...
private TextView textView;
private OnAnimationListener onAnimationListener;
public class WaveProgressAnim extends Animation {
//省略部分代碼...
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
if(percent < progressNum / maxNum){
if(textView !=null && onAnimationListener!=null){
textView.setText(onAnimationListener.howToChangeText(interpolatedTime, progressNum,maxNum));
}
}
}
}
/**
* 設(shè)置顯示文字的TextView
* @param textView
*/
public void setTextView(TextView textView) {
this.textView = textView;
}
public interface OnAnimationListener {
/**
* 如何處理要顯示的文字內(nèi)容
* @param interpolatedTime 從0漸變成1,到1時(shí)結(jié)束動畫
* @param updateNum 進(jìn)度條數(shù)值
* @param maxNum 進(jìn)度條最大值
* @return
*/
String howToChangeText(float interpolatedTime, float updateNum, float maxNum);
}
public void setOnAnimationListener(OnAnimationListener onAnimationListener) {
this.onAnimationListener = onAnimationListener;
}
}
然后在Activity中調(diào)用接口
textProgress = (TextView) findViewById(R.id.text_progress);
waveProgressView.setTextView(textProgress);
waveProgressView.setOnAnimationListener(new WaveProgressView.OnAnimationListener() {
@Override
public String howToChangeText(float interpolatedTime, float updateNum, float maxNum) {
DecimalFormat decimalFormat=new DecimalFormat("0.00");
String s = decimalFormat.format(interpolatedTime * updateNum / maxNum * 100)+"%";
return s;
}
});
waveProgressView.setProgressNum(80,1500);
布局文件也相應(yīng)修改
<RelativeLayout
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="10dp">
<com.anlia.progressbar.WaveProgressView
android:id="@+id/wave_progress"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:wave_height="8dp"
app:wave_width="40dp"
app:wave_color="@color/blue_light"
app:wave_bg_color="@color/gray_light"/>
<TextView
android:id="@+id/text_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"
android:textColor="@color/textColorPrimary"
android:textSize="13dp"
android:textStyle="bold"/>
</RelativeLayout>
來看下效果
擴(kuò)展二:實(shí)現(xiàn)波浪高度隨進(jìn)度上升而下降的效果
如果已經(jīng)理解之前所講的波浪繪制以及接口擴(kuò)展的原理王暗,相信實(shí)現(xiàn)起來是非常簡單的,這里就不詳細(xì)解釋了庄敛,大家看代碼即可
public class WaveProgressView extends View {
//省略部分代碼...
private Path getWavePath(){
//省略部分代碼...
float changeWaveHeight = waveHeight;
if(onAnimationListener!=null){
changeWaveHeight =
onAnimationListener.howToChangeWaveHeight(percent,waveHeight) == 0 && percent < 1
?waveHeight
:onAnimationListener.howToChangeWaveHeight(percent,waveHeight);
}
//從p3開始向p0方向繪制波浪曲線
for (int i=0;i<waveNum*2;i++){
wavePath.rQuadTo(waveWidth/2, changeWaveHeight, waveWidth, 0);
wavePath.rQuadTo(waveWidth/2, -changeWaveHeight, waveWidth, 0);
}
}
public interface OnAnimationListener {
//省略部分代碼...
/**
* 如何處理波浪高度
* @param percent 進(jìn)度占比
* @param waveHeight 波浪高度
* @return
*/
float howToChangeWaveHeight(float percent, float waveHeight);
}
}
然后在Activity中調(diào)用接口
waveProgressView.setOnAnimationListener(new WaveProgressView.OnAnimationListener() {
//省略部分代碼...
@Override
public float howToChangeWaveHeight(float percent, float waveHeight) {
return (1-percent)*waveHeight;
}
});
效果如圖
擴(kuò)展三:實(shí)現(xiàn)雙波浪效果
我們繪制第二層波浪要與第一層波浪平移的方向相反俗壹,只需要改一下path的繪制順序就可以了。即初始點(diǎn)變?yōu)?strong>p3藻烤,p0至p3段繪制波浪曲線枕屉,則繪制順序如下圖(哈哈又是這張圖释液,重復(fù)利用)所示
最后將相應(yīng)的path繪制到我們的緩存區(qū)即可(注意繪制的先后順序),實(shí)現(xiàn)代碼如下
public class WaveProgressView extends View {
//省略部分代碼...
private int secondWaveColor;//第二層波浪顏色
private boolean isDrawSecondWave;//是否繪制第二層波浪
private void init(Context context,AttributeSet attrs){
//省略部分代碼...
secondWaveColor = typedArray.getColor(R.styleable.WaveProgressView_second_wave_color,getResources().getColor(R.color.light));
secondWavePaint = new Paint();
secondWavePaint.setColor(secondWaveColor);
secondWavePaint.setAntiAlias(true);//設(shè)置抗鋸齒
//因?yàn)橐采w在第一層波浪上,且要讓半透明生效品姓,所以選SRC_ATOP模式
secondWavePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP));
isDrawSecondWave = false;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
bitmap = Bitmap.createBitmap(viewSize, viewSize, Bitmap.Config.ARGB_8888);
bitmapCanvas = new Canvas(bitmap);
bitmapCanvas.drawCircle(viewSize/2, viewSize/2, viewSize/2, circlePaint);
bitmapCanvas.drawPath(getWavePath(),wavePaint);
if(isDrawSecondWave){
bitmapCanvas.drawPath(getSecondWavePath(),secondWavePaint);
}
canvas.drawBitmap(bitmap, 0, 0, null);
}
private Path getSecondWavePath(){
float changeWaveHeight = waveHeight;
if(onAnimationListener!=null){
changeWaveHeight =
onAnimationListener.howToChangeWaveHeight(percent,waveHeight) == 0 && percent < 1
?waveHeight
:onAnimationListener.howToChangeWaveHeight(percent,waveHeight);
}
wavePath.reset();
//移動到左上方,也就是p3點(diǎn)
wavePath.moveTo(0, (1-percent)*viewSize);
//移動到左下方埂陆,也就是p2點(diǎn)
wavePath.lineTo(0, viewSize);
//移動到右下方刽沾,也就是p1點(diǎn)
wavePath.lineTo(viewSize, viewSize);
//移動到右上方,也就是p0點(diǎn)
wavePath.lineTo(viewSize + waveMovingDistance, (1-percent)*viewSize);
//從p0開始向p3方向繪制波浪曲線(注意繪制二階貝塞爾曲線控制點(diǎn)和終點(diǎn)x坐標(biāo)的正負(fù)值)
for (int i=0;i<waveNum*2;i++){
wavePath.rQuadTo(-waveWidth/2, changeWaveHeight, -waveWidth, 0);
wavePath.rQuadTo(-waveWidth/2, -changeWaveHeight, -waveWidth, 0);
}
//將path封閉起來
wavePath.close();
return wavePath;
}
/**
* 是否繪制第二層波浪
* @param isDrawSecondWave
*/
public void setDrawSecondWave(boolean isDrawSecondWave) {
this.isDrawSecondWave = isDrawSecondWave;
}
}
在Activity中設(shè)置isDrawSecondWave為true
waveProgressView.setDrawSecondWave(true);
效果如圖
至此本篇從零開始實(shí)現(xiàn)的教程就告一段落了峭跳,如果大家看了感覺還不錯(cuò)麻煩點(diǎn)個(gè)贊膘婶,你們的支持是我最大的動力~要是小伙伴們想要擴(kuò)展一些新的功能,也可以在評論區(qū)給我留言蛀醉,我有空會把新功能的實(shí)現(xiàn)教程更新上去