公司的項目已經完成了幾個了,無一例外,都有計步的功能,我的天哪,能不能有點創意,好吧,既然還要套代碼,那麼就把一些刁鑽的UI給封裝好,以後就可以直接使用提高效率了。說是刁鑽,其實也並沒有很誇張,只是原生控件實現不了而已。
這次的自定義View就是RoundProgressBar,顧名思義,圓形的ProgressBar而已,網上其實也有相關的博客,但我這裏並不是簡單的在View上面畫個圓而已,我會稍微加點效果上去,儘量讓它華麗點。
先上效果圖:
好吧,步驟依然還是那幾步:
1. 定義好attr屬性,並在構造函數裏將其初始化。
屬性定義:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="RoundProgressBar">
<attr name="max" format="integer"/>
<attr name="progress" format="integer"/>
<attr name="count" format="integer"/>
<attr name="countWidth" format="dimension"/>
<attr name="countAngle" format="float"/>
<attr name="countColor" format="color"/>
<attr name="secondCountColor" format="color"/>
<attr name="startAngle" format="float"/>
<attr name="spacingFromAngle" format="float"/>
<attr name="spacingToAngle" format="float"/>
<attr name="isSpacing" format="boolean"/>
<attr name="animation" format="boolean"/>
<attr name="animationDuration" format="integer"/>
</declare-styleable>
</resources>
屬性註釋:
//進度
private int progress;
//最大進度
private int max;
//矩形小塊的個數
private int count;
//小塊的寬度,實際應該叫高度纔對,這個width實際是畫弧線的strokeWidth而已
private int countWidth;
//小塊所佔的角度
private float countAngle;
//小塊的顏色,相當於progressColor
private int countColor;
//小塊的第二顏色,即進度顏色,相當於secondProgressColor
private int secondCountColor;
//進度的起始角度,即從progress從0開始的角度
private float startAngle;
//是否挖空部分進度
private boolean isSpacing;
//挖空的開始角度
private float spacingFromAngle;
//挖空的結束角度
private float spacingToAngle;
//是否使用動畫更新progress
private boolean animation;
//動畫的持續時間
private long animationDuration;
2. 重寫onMeasure(),處理好寬高和默認寬高。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
//主要是使view的寬高一樣,以及當wrap_content時默認爲100dp
int widthMode=MeasureSpec.getMode(widthMeasureSpec);
int width=MeasureSpec.getSize(widthMeasureSpec);
if(widthMode==MeasureSpec.AT_MOST){
width=dp2px(100);
}
int heightMode=MeasureSpec.getMode(heightMeasureSpec);
int height=MeasureSpec.getSize(heightMeasureSpec);
if(heightMode==MeasureSpec.AT_MOST){
height=dp2px(100);
}
setMeasuredDimension(Math.min(width, height), Math.min(width, height));
}
這裏有點必須注意,使用LinearLayout作爲父容器且值爲指定值時上面代碼測量高度是不會觸發AT_MOST情況的,但使用RelativeLayout作爲父容器時,即使是指定值或match_parent都會觸發AT_MOST,是不是Bug有待進一步研究,先暫時用着LinearLayout先。
@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
//每個小塊之間的間隔角度
float countSplitAngle=0;
if(isSpacing){
//360度減去所有小塊所佔角度和挖空的總角度,併除以count-1,減1是因爲間隔只有count-1個,這樣首尾小塊位置纔對稱
countSplitAngle=(360-count*countAngle-(spacingToAngle-spacingFromAngle))/(count-1);
}else{
//360度減去所有小塊所佔角度,併除以count,以整個360度作爲進度
countSplitAngle=(360-count*countAngle)/count;
}
//這是畫弧線需要的參數,即在該矩形內畫弧線
RectF oval=new RectF(0+countWidth/2, 0+countWidth/2, getWidth()-countWidth/2, getHeight()-countWidth/2);
//設置小塊的高度,即弧線的strokeWidth
paint.setStrokeWidth(countWidth);
paint.setStyle(Style.STROKE);
//先畫進度,利用循環畫弧把逐個小塊畫好,要把進度0點的起始角度加上
paint.setColor(secondCountColor);
for(int i=0; i<count*progress/max; i++){
float sweepStartAngle=startAngle+(countAngle+countSplitAngle)*i;
canvas.drawArc(oval, sweepStartAngle, countAngle, false, paint);
}
//換一種顏色把剩下的非進度小塊畫完
paint.setColor(countColor);
for(int i=count*progress/max; i<count; i++){
float sweepStartAngle=startAngle+(countAngle+countSplitAngle)*i;
canvas.drawArc(oval, sweepStartAngle, countAngle, false, paint);
}
//畫個進度指針,這裏隨便畫了一紅色指針
paint.setColor(Color.RED);
if(isSpacing){
canvas.drawArc(oval, startAngle+(360-(spacingToAngle-spacingFromAngle))*progress/max, countAngle, false, paint);
}else{
canvas.drawArc(oval, startAngle+360*progress/max, countAngle, false, paint);
}
}
這裏需要注意的是,若需要挖空處理,那麼起始角度應該設置到挖空末尾位置的角度,這裏默認是不挖空,其實角度是90度。
另一點需要注意的是,畫弧函數的參數裏涉及到度數,其度數的0度位於右邊,和平常我們學過的幾何座標一樣,但android裏順時針是正,和我們學過的幾何座標相反。
4. 在setProgress()裏添加動畫處理。
public void setProgress(int progress){
//若使用動畫,則利用ValueAnimator進行產生一個進度加載的緩衝效果
if(animation){
ValueAnimator valueAnimator=ValueAnimator.ofInt(this.progress, progress);
valueAnimator.setDuration(animationDuration);
valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// TODO Auto-generated method stub
RoundProgressBar.this.progress=Integer.valueOf(animation.getAnimatedValue().toString());
//利用每一動畫每幀改變progress/max,然後更新UI
invalidate();
}
});
valueAnimator.start();
}else{
this.progress=progress;
invalidate();
}
}
5. 佈局文件添加自定義屬性命名空間後使用該View。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res/com.example.roundprogressbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.example.roundprogressbar.RoundProgressBar
android:id="@+id/progressBar"
android:layout_width="200dp"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
android:layout_marginTop="50dp"
custom:countWidth="10dp"
custom:count="100"
custom:animation="true"
custom:isSpacing="true"
custom:startAngle="135"
custom:animationDuration="2000"/>
<com.example.roundprogressbar.RoundProgressBar
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="20dp"
custom:progress="40"
custom:countWidth="15dp"
custom:countAngle="2"
custom:count="80"
custom:countColor="#33ffffff"
custom:secondCountColor="#ffffffff"
android:background="#6495ED"/>
</LinearLayout>
貼上完整代碼(佈局文件在上面了):
RoundProgressBar.java
public class RoundProgressBar extends View {
//進度
private int progress;
//最大進度
private int max;
//矩形小塊的個數
private int count;
//小塊的寬度,實際應該叫高度纔對,這個width實際是畫弧線的strokeWidth而已
private int countWidth;
//小塊所佔的角度
private float countAngle;
//小塊的顏色,相當於progressColor
private int countColor;
//小塊的第二顏色,即進度顏色,相當於secondProgressColor
private int secondCountColor;
//進度的起始角度,即從progress從0開始的角度
private float startAngle;
//是否挖空部分進度
private boolean isSpacing;
//挖空的開始角度
private float spacingFromAngle;
//挖空的結束角度
private float spacingToAngle;
//是否使用動畫更新progress
private boolean animation;
//動畫的持續時間
private long animationDuration;
private Paint paint;
public RoundProgressBar(Context context) {
this(context, null);
// TODO Auto-generated constructor stub
}
public RoundProgressBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
// TODO Auto-generated constructor stub
}
public RoundProgressBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
TypedArray array=context.obtainStyledAttributes(attrs, R.styleable.RoundProgressBar);
progress=array.getInt(R.styleable.RoundProgressBar_progress, 0);
max=array.getInt(R.styleable.RoundProgressBar_max, 100);
count=array.getInt(R.styleable.RoundProgressBar_count, 100);
countWidth=array.getDimensionPixelSize(R.styleable.RoundProgressBar_countWidth, dp2px(10));
countAngle=array.getFloat(R.styleable.RoundProgressBar_countAngle, 1f);
countColor=array.getColor(R.styleable.RoundProgressBar_countColor, Color.GRAY);
secondCountColor=array.getColor(R.styleable.RoundProgressBar_secondCountColor, Color.GREEN);
startAngle=array.getFloat(R.styleable.RoundProgressBar_startAngle, 90f);
isSpacing=array.getBoolean(R.styleable.RoundProgressBar_isSpacing, false);
spacingFromAngle=array.getFloat(R.styleable.RoundProgressBar_spacingFromAngle, 45f);
spacingToAngle=array.getFloat(R.styleable.RoundProgressBar_spacingToAngle, 135f);
animation=array.getBoolean(R.styleable.RoundProgressBar_animation, false);
animationDuration=array.getInteger(R.styleable.RoundProgressBar_animationDuration, 2000);
array.recycle();
paint=new Paint();
paint.setAntiAlias(true);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
//主要是使view的寬高一樣,以及當wrap_content時默認爲100dp
int widthMode=MeasureSpec.getMode(widthMeasureSpec);
int width=MeasureSpec.getSize(widthMeasureSpec);Log.d("zz", "111:"+width);
if(widthMode==MeasureSpec.AT_MOST){
width=dp2px(100);Log.d("zz", "222:"+width);
}
int heightMode=MeasureSpec.getMode(heightMeasureSpec);
int height=MeasureSpec.getSize(heightMeasureSpec);Log.d("zz", "333:"+height);
if(heightMode==MeasureSpec.AT_MOST){
height=dp2px(100);Log.d("zz", "444:"+height);
}
setMeasuredDimension(Math.min(width, height), Math.min(width, height));
}
@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
//每個小塊之間的間隔角度
float countSplitAngle=0;
if(isSpacing){
//360度減去所有小塊所佔角度和挖空的總角度,併除以count-1,減1是因爲間隔只有count-1個,這樣首尾小塊位置纔對稱
countSplitAngle=(360-count*countAngle-(spacingToAngle-spacingFromAngle))/(count-1);
}else{
//360度減去所有小塊所佔角度,併除以count,以整個360度作爲進度
countSplitAngle=(360-count*countAngle)/count;
}
//這是畫弧線需要的參數,即在該矩形內畫弧線
RectF oval=new RectF(0+countWidth/2, 0+countWidth/2, getWidth()-countWidth/2, getHeight()-countWidth/2);
//設置小塊的高度,即弧線的strokeWidth
paint.setStrokeWidth(countWidth);
paint.setStyle(Style.STROKE);
//先畫進度,利用循環畫弧把逐個小塊畫好,要把進度0點的起始角度加上
paint.setColor(secondCountColor);
for(int i=0; i<count*progress/max; i++){
float sweepStartAngle=startAngle+(countAngle+countSplitAngle)*i;
canvas.drawArc(oval, sweepStartAngle, countAngle, false, paint);
}
//換一種顏色把剩下的非進度小塊畫完
paint.setColor(countColor);
for(int i=count*progress/max; i<count; i++){
float sweepStartAngle=startAngle+(countAngle+countSplitAngle)*i;
canvas.drawArc(oval, sweepStartAngle, countAngle, false, paint);
}
//畫個進度指針,這裏隨便畫了一紅色指針
paint.setColor(Color.RED);
if(isSpacing){
canvas.drawArc(oval, startAngle+(360-(spacingToAngle-spacingFromAngle))*progress/max, countAngle, false, paint);
}else{
canvas.drawArc(oval, startAngle+360*progress/max, countAngle, false, paint);
}
}
public void setProgress(int progress){
//若使用動畫,則利用ValueAnimator進行產生一個進度加載的緩衝效果
if(animation){
ValueAnimator valueAnimator=ValueAnimator.ofInt(this.progress, progress);
valueAnimator.setDuration(animationDuration);
valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// TODO Auto-generated method stub
RoundProgressBar.this.progress=Integer.valueOf(animation.getAnimatedValue().toString());
//利用每一動畫每幀改變progress/max,然後更新UI
invalidate();
}
});
valueAnimator.start();
}else{
this.progress=progress;
invalidate();
}
}
//dp轉px單位
private int dp2px(int dp){
return (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
}
}
MainActivity.java
public class MainActivity extends Activity {
private RoundProgressBar progressBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
progressBar=(RoundProgressBar)findViewById(R.id.progressBar);
progressBar.post(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
progressBar.setProgress(80);
}
});
}
}
代碼下載:github
---------------------------------程序員小記----------------------------------
昨天爬山,今天又在健身房苦練,好累好累,睡覺先,明天繼續,加油~