編輯:關於android開發
模擬器上運行有些鋸齒,真機上和預期一樣好
看效果,第一直覺肯定是Android原生態控件中沒有這樣的控件實現這種效果,自然想到應該需要自定義控件了,沒錯,這就是通過自定義控件來繪制的一個圓環進度條。仔細分析發現這個效果的進度條應該由幾個部分組成,首先是無進度時的淺色圓環,然後是一個隨進度變化的深色圓弧,而中間部分是一個深藍色的實心圓,最後就是顯示進度百分比的文字。這幾部分大部分都是圖形,所以使用圖形繪制技術應該可以繪制出分部分效果,然後加上進度控制部分應該心裡就有底了。
重寫onDraw()方法的目的是為了找到繪制圖形的時機與場所。(2)繪制淺色圓環。
需求准備Paint畫筆,並設置畫筆寬度,顏色,樣式等,然後繪制圓環。(3)繪制深色進度圓弧。
重置Paint畫筆,並設置畫筆顏色,繪制進度圓弧。(4)添加SeekBar,並通過進度變化控制進度圓弧。
測試進度圓弧是否會隨進度的變化正常工作。(5)繪制深藍色實心圓。
重置Paint畫筆,並設置畫筆顏色,繪制實心圓。(6)繪制百分比文本。
計算進度百分比,繪制文本。(7)自定義屬性,使進度條樣式可配置(擴展部分)。 自定義View,重寫構造方法,重寫onDraw()方法
package com.kedi.myprogressbar; import android.content.Context; import android.graphics.Canvas; import android.util.AttributeSet; import android.view.View; public class MyProgressBar extends View { public MyProgressBar(Context context) { this(context, null); } public MyProgressBar(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MyProgressBar(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); } }有了自定義MyProgressBar,此時就可以在XML布局中先占位了,下面是XML布局文件。
繪制淺色圓環
Paint 畫筆:
|- new Paint() 創建畫筆|-setColor() 設置畫筆顏色|-setStyle()設置畫筆樣式(Paint.Style.STROKE 虛框樣式,Paint.Style.FILL 實心樣式)|-setStrokeWidth()設置畫筆線寬|-setAntiAlias()消除鋸齒或毛邊|-setTextSize()設置畫文本時的文本大小|-setTypeface()設置畫文本時的文本字體
Canvas畫布:
有了這些技術准備,繪制工作將一路順利。為了進行接下來的繪制工作,先在初始化方法中創建一個空畫筆。|-drawCircle(cx, cy, radius, paint)繪制圓環或圓。cx,cy:圓心坐標,radius:圓半徑,paint:畫筆。|-drawText(text, x, y, paint)繪制文本。text:文本,x,y:文本坐標,paint:畫筆。|-drawArc(oval, startAngle, sweepAngle, useCenter, paint)繪制圓弧。oval:矩形區域,用來確定圓弧形狀和大小。new RectF(left, top, right, bottom)確定一個矩形區域。startAngle:圓弧始端角度。sweepAngle:圓弧末端角度。paint:畫筆。userCenter:設置是否顯示圓弧的兩邊線條,false時只畫圓弧沒有兩邊,true時帶兩邊。如下圖:
public class MyProgressBar extends View { private Paint paint;// 畫筆 public MyProgressBar(Context context) { this(context, null); } public MyProgressBar(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MyProgressBar(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } /** * 初始化方法 */ private void init() { paint = new Paint(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); } }接下來繪制淺色圓環 核心邏輯:
private int roundW;// 圓環寬 /** * 初始化方法 */ private void init() { paint = new Paint(); //初始化圓環寬,這裡考慮了適配把15dp進行了對應平台的像素轉換。 roundW = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 15, getResources().getDisplayMetrics()); } // 1.圓心(x,y)坐標值 int centerX = getWidth() / 2; int centerY = centerX; // 2.圓環半徑 int radius1 = (centerX - roundW / 2); // 3.設置圓環顏色(淺色) paint.setColor(Color.parseColor("#11339ED4")); // 4.設置畫筆的風格 paint.setStyle(Paint.Style.STROKE); // 5.設置畫圓環的寬度 paint.setStrokeWidth(roundW); // 6.消除鋸齒 paint.setAntiAlias(true); // 7.畫圓環 canvas.drawCircle(centerX, centerY, radius1, paint);
完整代碼(如何是直接拷貝這塊代碼的時候記的修改包名和導包):
package com.kedi.myprogressbar; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.util.AttributeSet; import android.util.TypedValue; import android.view.View; public class MyProgressBar extends View { private Paint paint;// 畫筆 private int roundW;// 圓環寬 public MyProgressBar(Context context) { this(context, null); } public MyProgressBar(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MyProgressBar(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } /** * 初始化方法 */ private void init() { paint = new Paint(); // 初始化圓環寬,這裡考慮了適配把15dp進行了對應平台的像素轉換。 roundW = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 15, getResources().getDisplayMetrics()); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // 繪制淺色圓環 // 1.圓心(x,y)坐標值 int centerX = getWidth() / 2; int centerY = centerX; // 2.圓環半徑 int radius1 = (centerX - roundW / 2); // 3.設置畫大圓環顏色 paint.setColor(Color.parseColor("#11339ED4")); // 4.設置畫筆的風格 paint.setStyle(Paint.Style.STROKE); // 5.設置畫圓環的寬度 paint.setStrokeWidth(roundW); // 6.消除鋸齒 paint.setAntiAlias(true); // 7.畫圓環 canvas.drawCircle(centerX, centerY, radius1, paint); } }
效果圖:
繪制深色進度圓弧
圓弧的繪制重點在開始角度和末端角度的計算和確定,因為我們的圓弧代表進度,所以開始角度和末端角度都與進度有關,會跟著進度的變化而變化,所以首先我們的確定當前進度和最大進度。然後就可以計算始末角度了。
定義進度值:private int progress = 0;// 當前進度值 private int maxProgress = 100;// 最大進度值
進度改變接口方法:
/** * 更新進度和界面的方法 * * @param progress */ public void setProgress(int progress) { if (progress < 0) { progress = 0; } else { this.progress = progress; } invalidate(); }計算始末角度:
開始角度 0
末端角度(float)360 * progress / (float)maxProgress核心邏輯:
//繪制深色進度圓弧 // 1.設置圓孤的寬度 paint.setStrokeWidth(roundW); // 2.設置圓孤進度的顏色 paint.setColor(Color.parseColor("#339ED4")); // 3.定義圓弧的形狀和大小區域 RectF oval = new RectF(centerX - radius1, centerY - radius1, centerX + radius1, centerY + radius1); // 4.設置空心樣式 paint.setStyle(Paint.Style.STROKE); // 5.根據進度畫圓弧 canvas.drawArc(oval, 0, (float)360 * progress / (float)maxProgress, false, paint);完整代碼(為了測試進度效果,我們先把progress=20,測試完再設置為progress=0):
package com.kedi.myprogressbar; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.RectF; import android.util.AttributeSet; import android.util.TypedValue; import android.view.View; public class MyProgressBar extends View { private Paint paint;// 畫筆 private int roundW;// 圓環寬 private int progress = 20;// 當前進度值 private int maxProgress = 100;// 最大進度值 /** * 更新進度和界面的方法 * * @param progress */ public void setProgress(int progress) { if (progress < 0) { progress = 0; } else { this.progress = progress; } invalidate(); } public MyProgressBar(Context context) { this(context, null); } public MyProgressBar(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MyProgressBar(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } /** * 初始化方法 */ private void init() { paint = new Paint(); roundW = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 15, getResources().getDisplayMetrics()); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // 繪制淺色圓環 // 1.圓心(x,y)坐標值 int centerX = getWidth() / 2; int centerY = centerX; // 2.圓環半徑 int radius1 = (centerX - roundW / 2); // 3.設置畫大圓環顏色 paint.setColor(Color.parseColor("#11339ED4")); // 4.設置畫筆的風格 paint.setStyle(Paint.Style.STROKE); // 5.設置畫圓環的寬度 paint.setStrokeWidth(roundW); // 6.消除鋸齒 paint.setAntiAlias(true); // 7.畫圓環 canvas.drawCircle(centerX, centerY, radius1, paint); // 繪制深色進度圓弧 // 1.設置圓孤的寬度 paint.setStrokeWidth(roundW); // 2.設置圓孤進度的顏色 paint.setColor(Color.parseColor("#339ED4")); // 3.定義圓弧的形狀和大小區域界限 RectF oval = new RectF(centerX - radius1, centerY - radius1, centerX + radius1, centerY + radius1); // 4.設置空心樣式 paint.setStyle(Paint.Style.STROKE); // 5.根據進度畫圓弧 canvas.drawArc(oval, 0, (float) 360 * progress / (float) maxProgress, false, paint); } }
效果圖:
添加SeekBar,並通過進度變化控制進度圓弧
完整代碼:
package com.kedi.myprogressbar; import android.app.Activity; import android.os.Bundle; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; public class MainActivity extends Activity { //自定義進度條控件 private MyProgressBar pg; //SeekBar控件 private SeekBar sb; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initViews(); initEvents(); } /** * 初始化View */ private void initViews(){ pg =(MyProgressBar) findViewById(R.id.pg); sb = (SeekBar) findViewById(R.id.sb); } /** * 初始化事件監聽與處理 */ private void initEvents() { sb.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { @Override public void onStopTrackingTouch(SeekBar seekBar) { } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { //改變圓弧的進度,並重新繪制圓弧,主要是通過觸發自定義控件的onDraw()方法達到目的 pg.setProgress(progress); } }); } }
效果圖:
繪制深藍色實心圓
實心圓的繪制也是通過drawCircle()方法,只是畫筆的樣式為FILL實心,所以比較簡單。
核心代碼:
// 繪制深藍色實心圓 // 1.實心圓半徑 int radius2 = centerX - roundW; // 2.實心圓顏色 paint.setColor(Color.parseColor("#336799")); // 3.設置畫筆風格為實心 paint.setStyle(Paint.Style.FILL); // 4.畫實心圓 canvas.drawCircle(centerX, centerY, radius2, paint);
效果圖:
繪制百分比文本
繪制文本需要知道文本大小,文本字體,文本顏色,文本x,y坐標位置以及文本內容。
文本大小我們定義個成員變量,然後在init()方法中初始化,當然可以對外提供接口方法,設置文本大小,
文本顏色使用白色,當然也可以像文本大小那樣定義成員變量,然後初始化,也可對外提供接口方法,設置文本顏色
文本x,y坐標需要根據圓心坐標與文本自身的寬高進行計算。
文本內容是進度百分比,需要根據進度進行計算。(float) progress / (float) maxProgress) * 100
核心邏輯:
// 繪制百分比文本 // 1.設置無邊框 paint.setStrokeWidth(0); // 2.設置字體顏色 paint.setColor(Color.WHITE); paint.setAntiAlias(true); // 3.設置字體大小,定義成員變量textSize,然後在初始化方法中賦初始值 paint.setTextSize(textSize); // 4.設置字體 paint.setTypeface(Typeface.DEFAULT_BOLD); // 5.計算進度百分比 int percent = (int) (((float) progress / (float) maxProgress) * 100); // 6.測量字體寬度 float textWidth = paint.measureText(percent + "%"); // 7.畫出進度百分比文本 canvas.drawText(percent + "%", centerX - textWidth / 2, centerY + textSize / 2, paint);
完整代碼:
package com.kedi.myprogressbar; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.RectF; import android.graphics.Typeface; import android.util.AttributeSet; import android.util.TypedValue; import android.view.View; public class MyProgressBar extends View { private Paint paint;// 畫筆 private int roundW;// 圓環寬 private int textSize;// 字體大小 private int progress = 0;// 當前進度值 private int maxProgress = 100;// 最大進度值 /** * 更新進度和界面的方法 * * @param progress */ public void setProgress(int progress) { if (progress < 0) { progress = 0; } else { this.progress = progress; } invalidate(); } /** * @param context */ public MyProgressBar(Context context) { this(context, null); } /** * @param context * @param attrs */ public MyProgressBar(Context context, AttributeSet attrs) { this(context, attrs, 0); } /** * @param context * @param attrs * @param defStyleAttr */ public MyProgressBar(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } /** * 初始化方法 */ private void init() { paint = new Paint(); roundW = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 15, getResources().getDisplayMetrics()); textSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 25, getResources().getDisplayMetrics()); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // 繪制淺色圓環 // 1.圓心(x,y)坐標值 int centerX = getWidth() / 2; int centerY = centerX; // 2.圓環半徑 int radius1 = (centerX - roundW / 2); // 3.設置畫大圓環顏色 paint.setColor(Color.parseColor("#11339ED4")); // 4.設置畫筆的風格 paint.setStyle(Paint.Style.STROKE); // 5.設置畫圓環的寬度 paint.setStrokeWidth(roundW); // 6.消除鋸齒 paint.setAntiAlias(true); // 7.畫圓環 canvas.drawCircle(centerX, centerY, radius1, paint); // 繪制深色進度圓弧 // 1.設置圓孤的寬度 paint.setStrokeWidth(roundW); // 2.設置圓孤進度的顏色 paint.setColor(Color.parseColor("#339ED4")); // 3.定義圓弧的形狀和大小區域界限 RectF oval = new RectF(centerX - radius1, centerY - radius1, centerX + radius1, centerY + radius1); // 4.設置空心樣式 paint.setStyle(Paint.Style.STROKE); // 5.根據進度畫圓弧 canvas.drawArc(oval, 0, (float) 360 * progress / (float) maxProgress, false, paint); // 繪制深藍色實心圓 // 1.實心圓半徑 int radius2 = centerX - roundW; // 2.實心圓顏色 paint.setColor(Color.parseColor("#336799")); // 3.設置畫筆風格為實心 paint.setStyle(Paint.Style.FILL); // 4.畫實心圓 canvas.drawCircle(centerX, centerY, radius2, paint); // 繪制百分比文本 // 1.設置無邊框 paint.setStrokeWidth(0); // 2.設置字體顏色 paint.setColor(Color.WHITE); // 3.設置字體大小 paint.setTextSize(textSize); // 4.設置字體 paint.setTypeface(Typeface.DEFAULT_BOLD); // 5.計算進度百分比 int percent = (int) (((float) progress / (float) maxProgress) * 100); // 6.測量字體寬度 float textWidth = paint.measureText(percent + "%"); // 7.畫出進度百分比文本 canvas.drawText(percent + "%", centerX - textWidth / 2, centerY + textSize / 2, paint); } }
效果圖:
自定義屬性,使進度條樣式可配置(擴展部分)
效果已經實現完了,但是如果我的項目中需要一個其它主題風格的進度條,那我們不得不重新Copy一份,然後修改其中的顏色值什麼的,這樣的自定義控件顯然不夠靈活和通用,如果進度條提供了對主題風格的定制接口那就靈活不少,所以接下來做為擴展部分,要做的是為進度條提供主題風格定制途徑---自定義屬性與Setter方法。
經分析,把與樣式相關的特征都定義成成員變量,有如下幾個:
//圓環相關成員變量 private int roundW;// 圓環寬 private int roundColor;//圓環顏色 //圓弧相關成員變量 private int progress;// 當前進度值 private int progressColor;//進度圓弧顏色 private int maxProgress = 100;// 最大進度值 //實心圓相關成員變量 private int circleColor;//實心圓顏色 //百分比文本相關成員變量 private int textColor;//字體顏色 private int textSize;// 字體大小
定義自定義屬性文件values/attrs.xml,並定義成上面成員變量一一對應的自定義屬性:
構造方法中獲取自定義屬性,然後初始化上面的那幾個成員變量(將原來的值做為默認值):
public MyProgressBar(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(attrs); } private void init(AttributeSet attrs) { paint = new Paint(); TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.MyProgressBarSytle); roundW = (int) typedArray.getDimension(R.styleable.MyProgressBarSytle_roundW, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 15, getResources().getDisplayMetrics())); roundColor = typedArray.getColor(R.styleable.MyProgressBarSytle_roundColor, Color.parseColor("#11339ED4")); progress = typedArray.getInt(R.styleable.MyProgressBarSytle_progress, 0); progressColor = typedArray.getColor(R.styleable.MyProgressBarSytle_progressColor, Color.parseColor("#339ED4")); circleColor = typedArray.getColor(R.styleable.MyProgressBarSytle_circleColor, Color.parseColor("#336799")); textColor = typedArray.getColor(R.styleable.MyProgressBarSytle_textColor, Color.parseColor("#ffffff")); textSize = (int) typedArray.getDimension(R.styleable.MyProgressBarSytle_textSize, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 25, getResources().getDisplayMetrics())); typedArray.recycle(); }
將繪制邏輯各分部中樣式原來的值,換成成員變量:
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //繪制淺色圓環 // 1.圓心(x,y)坐標值 int centerX = getWidth() / 2; int centerY = centerX; // 2.圓環半徑 int radius1 = (centerX - roundW / 2); // 3.設置畫大圓環顏色 paint.setColor(roundColor); // 4.設置畫筆的風格 paint.setStyle(Paint.Style.STROKE); // 5.設置畫圓環的寬度 paint.setStrokeWidth(roundW); // 6.消除鋸齒 paint.setAntiAlias(true); // 7.畫圓環 canvas.drawCircle(centerX, centerY, radius1, paint); // 繪制深色進度圓弧 // 1.設置圓孤的寬度 paint.setStrokeWidth(roundW); // 2.設置圓孤進度的顏色 paint.setColor(progressColor); // 3.定義圓弧的形狀和大小區域界限 RectF oval = new RectF(centerX - radius1, centerY - radius1, centerX + radius1, centerY + radius1); // 4.設置空心樣式 paint.setStyle(Paint.Style.STROKE); // 5.根據進度畫圓弧 canvas.drawArc(oval, 0, (float) 360 * progress / (float) maxProgress, false, paint); // 繪制深藍色實心圓 // 1.實心圓半徑 int radius2 = centerX - roundW; // 2.實心圓顏色 paint.setColor(circleColor); // 3.設置畫筆風格為實心 paint.setStyle(Paint.Style.FILL); // 4.畫實心圓 canvas.drawCircle(centerX, centerY, radius2, paint); // 繪制百分比文本 // 1.設置無邊框 paint.setStrokeWidth(0); // 2.設置字體顏色 paint.setColor(textColor); paint.setAntiAlias(true); // 3.設置字體大小 paint.setTextSize(textSize); // 4.設置字體 paint.setTypeface(Typeface.DEFAULT_BOLD); // 5.計算進度百分比 int percent = (int) (((float) progress / (float) maxProgress) * 100); // 6.測量字體寬度 float textWidth = paint.measureText(percent + "%"); // 7.畫出進度百分比文本 canvas.drawText(percent + "%", centerX - textWidth / 2, centerY + textSize / 2, paint); }如果我們希望通過代碼也可以修改這些樣式值,就對外提供Setter方法:
/** * 設置圓環寬度 * @param roundW */ public void setRoundW(int roundW) { this.roundW = roundW; } /** * 設置圓環顏色 * @param roundColor */ public void setRoundColor(int roundColor) { this.roundColor = roundColor; } /** * 設置進度圓弧顏色 * @param progressColor */ public void setProgressColor(int progressColor) { this.progressColor = progressColor; } /** * 設置最大進度值 * @param maxProgress */ public void setMaxProgress(int maxProgress) { this.maxProgress = maxProgress; } /** * 設置實心圓顏色 * @param circleColor */ public void setCircleColor(int circleColor) { this.circleColor = circleColor; } /** * 設置百分比進度文本顏色 * @param textColor */ public void setTextColor(int textColor) { this.textColor = textColor; } /** * 設置百分比進度文本字體大小 * @param textSize */ public void setTextSize(int textSize) { this.textSize = textSize; } /** * 更新進度和界面的方法 * * @param progress */ public void setProgress(int progress) { if (progress < 0) { progress = 0; } else { this.progress = progress; } invalidate(); }最後演示一下使用自定義屬性定制主題的方式:
(1)在根布局中添加命名空間
xmlns:my="http://schemas.android.com/apk/res-auto"
其中my是是命名空間名,可隨意指定,而“http://schemas.android.com/apk/res-auto”是固定的命名空間。
(2)使用自定義屬性並指定屬性值
完整布局代碼:
<relativelayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:my="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <com.kedi.myprogressbar.myprogressbar android:id="@+id/pg" android:layout_width="120dp" android:layout_height="120dp" android:layout_centerinparent="true" android:padding="5dp" my:circlecolor="#ACD900" my:maxprogress="100" my:progresscolor="#FF6400" my:roundcolor="#316900" my:roundw="8dp" my:textcolor="#EE3400" my:textsize="20sp"> </com.kedi.myprogressbar.myprogressbar> <seekbar android:id="@+id/sb" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/pg" android:layout_margin="20dp" android:max="100"> </seekbar></relativelayout>效果圖:
到此就完成了比較靈活的圓形進度條的制作。
淺談 EventBus,淺談eventbus概述: EventBus是一款針對Android優化的發布/訂閱事件總線。 主要功能是替代Intent,Handler,Bro
如何在Android的ListView中構建CheckBox和RadioButton列表(Android版支持單選和多選的投票項目),androidlistview多選引
Android React Native使用原生UI組件 Android React Native 已經將幾個常用的原生組件進行了封裝,比如 ScrollView 和
《Android源碼設計模式解析與實戰》讀書筆記(二十四) 第二十四章、橋接模式 橋接模式也稱為橋梁模式,是結構型設計模式之一。橋接模式中體現了“單一職責原
Android Studio 優秀插件(二): Parcelable C