仿IWatch環(huán)形進(jìn)度條自定義控件


環(huán)形進(jìn)度條可以設(shè)置圓環(huán)寬度、圓環(huán)進(jìn)度顏色、圓環(huán)軌道顏色、圓環(huán)刷新速率、文案內(nèi)容等屬性,可實(shí)現(xiàn)更多展示效果。并將進(jìn)度條子集進(jìn)行匯總嵌套,通過設(shè)置圓環(huán)間隔呈現(xiàn)IWatch環(huán)形進(jìn)度條效果。

一、環(huán)形進(jìn)度條

環(huán)形進(jìn)度條.png

1、自定義屬性

  • 在XML布局中可配置控件的屬性,實(shí)現(xiàn)自定義控件時(shí),我們需要抽出可配置的公有屬性,方便塑造更多形式。

字段名 字段類型 字段說明
_roundColor color 進(jìn)度條背景色
_roundProgressColor color 進(jìn)度填充色
_roundWidth color 進(jìn)度畫筆寬
_textColor dimension 進(jìn)度文字色
_rate integer 進(jìn)度刷新速率
_max integer 進(jìn)度最大值
_progress integer 進(jìn)度值
_textIsDisplayable boolean 是否顯示進(jìn)度文字
_style enum 環(huán)形進(jìn)度條是否鏤空,STROKE,F(xiàn)ILL
<declare-styleable name="ChildProgressBar">
    <attr name="_roundColor" format="color" />
    <attr name="_roundProgressColor" format="color" />
    <attr name="_roundWidth" format="dimension" />
    <attr name="_textColor" format="color" />
    <attr name="_rate" format="integer" />
    <attr name="_max" format="integer" />
    <attr name="_progress" format="integer" />
    <attr name="_textIsDisplayable" format="boolean" />
    <attr name="_style">
        <enum name="STROKE" value="0" />
        <enum name="FILL" value="1" />
    </attr>
</declare-styleable>

2、繪制

  • 需定義每個(gè)元素的畫筆屬性和繪制位置,相互之間可以形成約束,比如圓環(huán)寬可以和文字大小和圓點(diǎn)大小形成約束,降低用戶操作難度。

  • 計(jì)算字體大小跟隨進(jìn)度寬度變化

int centerX = getWidth() / 2; // 獲取圓心的x坐標(biāo)
int centerY = getHeight() / 2;
int radius;
if (centerX > centerY) {
    radius = (int) (centerY - roundWidth); // 圓環(huán)的半徑 減10的目的是為了讓字體
} else {
    radius = (int) (centerX - roundWidth); // 圓環(huán)的半徑 減10的目的是為了讓字體
}
lessSize = 20;
textSize = roundWidth / 2 + 35;//根據(jù)畫筆寬度改變字體大小        
  • 繪制底部圓軌道

paint.setColor(roundColor); // 設(shè)置圓環(huán)的顏色
paint.setStyle(Paint.Style.STROKE); // 設(shè)置空心
paint.setStrokeWidth(roundWidth); // 設(shè)置圓環(huán)的寬度
paint.setAntiAlias(true); // 消除鋸齒
canvas.drawCircle(centerX, centerY, radius, paint); // 畫出圓環(huán)
  • 繪制圓心文字

if (textIsDisplayable && style == STROKE){
  paint.setColor(Color.WHITE);
  paint.setTextSize(textSize);
  paint.setTypeface(Typeface.DEFAULT); // 設(shè)置字體

  float p = 0.0f;
  if (max != 0) {
      p = ((float) progress / (float) max) * 100.0f;
  }
  DecimalFormat decimalFormat = new DecimalFormat("######0.0");//構(gòu)造方法的字符格式這里如果小數(shù)不足1位,會(huì)以0補(bǔ)足.
  String percent = decimalFormat.format(p) + "%";//format 返回的是字符串
  float percentWidth = paint.measureText(percent);// 測量字體寬度,我們需要根據(jù)字體的寬度設(shè)置在圓環(huán)中間
  canvas.drawText(percent, centerX - percentWidth / 2, centerY, paint); // 畫出進(jìn)度百分比

  paint.setTextSize(textSize); // 改變畫筆字體大小格式
  String s = text + "率";
  float textWidth = paint.measureText(s); // 測量字體寬度,我們需要根據(jù)字體的寬度設(shè)置在圓環(huán)中間
  canvas.drawText(s, centerX - textWidth / 2, centerY + (textSize - lessSize / 2), paint); // 畫出進(jìn)度百分比
}
  • 繪制帶圓角的圓弧進(jìn)度

paint.setStrokeWidth(roundWidth); // 設(shè)置圓環(huán)的寬度
paint.setColor(roundProgressColor); // 設(shè)置進(jìn)度的顏色
RectF oval = new RectF((centerX - radius), (centerY - radius),
                (centerX + radius), (centerY + radius)); // 用于定義的圓弧的形狀和大小的界限

switch (style) {
  case STROKE: {
      paint.setStyle(Paint.Style.STROKE); // 設(shè)置進(jìn)度是實(shí)心還是空心
      paint.setStrokeCap(Paint.Cap.ROUND); // 設(shè)置線冒樣式
      if (max != 0) {
          if (progress == 0) {
              canvas.drawArc(oval, -90, 1, false, paint);
          } else {
              canvas.drawArc(oval, -90, (count) * 360 / max, false, paint); // 根據(jù)進(jìn)度畫圓弧
          }
      } else {
          canvas.drawArc(oval, 0, 0, false, paint); // 根據(jù)進(jìn)度畫圓弧
      }
      break;
  }
  case FILL: {
      paint.setStyle(Paint.Style.FILL_AND_STROKE);
      if (max != 0) {
          if (progress == 0) {
              canvas.drawArc(oval, -90, 1, true, paint);
          } else {
              canvas.drawArc(oval, -90, (count) * 360 / max, true, paint); // 根據(jù)進(jìn)度畫圓弧
          }
      } else {
          canvas.drawArc(oval, 0, 0, true, paint); // 根據(jù)進(jìn)度畫圓弧
      }
      break;
  }
}
  • 繪制與圓環(huán)頂部齊平的進(jìn)度條文字描述和小圓點(diǎn)

// 繪制與圓環(huán)填充色一致的小圓點(diǎn)
paint.setColor(roundProgressColor);
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(roundWidth / 2);
canvas.drawCircle(centerX - 6 * distance, centerY - radius + distance / 2, distance / 3, paint);

// 繪制文字描述
paint.setColor(textColor);// 設(shè)置字體
paint.setTextSize(textSize); // 改變畫筆字體大小格式
canvas.drawText(text + progress, centerX - 5 * distance,
                centerY + (textSize - lessSize) / 2 - radius, paint); // 畫出進(jìn)度百分比

3、實(shí)現(xiàn)動(dòng)態(tài)刷新機(jī)制

  • 利用Handler的消息延時(shí)機(jī)制,按照一定速率的增加繪制進(jìn)度,實(shí)現(xiàn)動(dòng)態(tài)刷新效果

/**
 * 記錄當(dāng)前所畫的每小塊圓弧個(gè)數(shù)
 */
private int count = 0;

/**
 * 記錄還沒畫出的圓弧進(jìn)度
 */
private int reverse_pro;

/**
 * 圓環(huán)動(dòng)畫速度
 */
private int rate;

/**
 * 設(shè)置進(jìn)度,此為線程安全控件,由于考慮多線的問題,需要同步 刷新界面調(diào)用postInvalidate()能在非UI線程刷新
 *
 * @param progress 當(dāng)前需繪制的進(jìn)度
 */
public synchronized void setProgress(int progress) {
    if (progress < 0) {
        throw new IllegalArgumentException("progress not less than 0");
    }
    if (progress > max) {
        progress = max;
    }
    if (progress <= max) {
        this.progress = progress;
        count = 0;
        reverse_pro = progress;// 將傳進(jìn)來的進(jìn)程數(shù)傳給用來記錄當(dāng)前圓環(huán)的比率
        postInvalidate();
    }
}
public void refreshProgress() {
    count = 0; // 記錄已經(jīng)繪制好的圓弧進(jìn)度,在 onDraw 中運(yùn)用
    reverse_pro = progress; // 記錄還沒畫出的圓弧進(jìn)度

    Message message = handler.obtainMessage(1);
    handler.sendMessageDelayed(message, rate);
}

Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case 1:
                if (reverse_pro != 0)
                    count++;
                reverse_pro--;
                postInvalidate();// progress每增加一格就刷新一次界面,用count來記錄弧度單元格個(gè)數(shù)
                if (reverse_pro > 0) {
                    Message message = handler.obtainMessage(1);
                    handler.sendMessageDelayed(message, rate);
                }
                break;
        }
        super.handleMessage(msg);
    }
};

4、部署控件

<com.xs.lightpuzzle.demo.a_circle_progress_bar_demo.ChildProgressBar
            android:id="@+id/a_progress_bar"
            android:layout_width="180dp"
            android:layout_height="180dp"
            progress_bars:_textColor="@color/white"
            progress_bars:_roundColor="@color/progress_track_color"
            progress_bars:_roundProgressColor="@color/progress_color"
            progress_bars:_roundWidth="8dp"
            progress_bars:_style="STROKE"
            progress_bars:_rate="20"
            progress_bars:_max="100"
            progress_bars:_progress="75"
            progress_bars:_textIsDisplayable="true"/>

二、進(jìn)度條嵌套集合

環(huán)形進(jìn)度條嵌套集合.png

1、自定義屬性

字段名 字段類型 字段說明
rate integer 進(jìn)度刷新速率
progressTrackColor color 進(jìn)度條底部軌道顏色
textColor color 進(jìn)度條文字顏色
progressGap dimension 每根進(jìn)度條彼此間隔
roundWidth dimension 每根進(jìn)度條繪制寬度
onlyFirstShowCenter boolean 總體是否只顯示第一條進(jìn)度的中心文案
isShowTopTextAndPoint boolean 每條進(jìn)度是否顯示頂部齊平的文案和圓點(diǎn)
<declare-styleable name="ProgressBarsView">
    <attr name="rate" format="integer" />
    <attr name="progressTrackColor" format="color" />
    <attr name="textColor" format="color" />
    <attr name="progressGap" format="dimension"/>
    <attr name="roundWidth" format="dimension"/>
    <attr name="onlyFirstShowCenter" format="boolean" />
    <attr name="isShowTopTextAndPoint" format="boolean" />
</declare-styleable>
public ProgressBarsView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    mContext = context;

    TypedArray mTypedArray = context.obtainStyledAttributes(attrs, R.styleable.ProgressBarsView);
    // 獲取自定義屬性和默認(rèn)值
    progressTrackColor = mTypedArray.getColor(R.styleable.ProgressBarsView_progressTrackColor, Color.GRAY); // 每條進(jìn)度底部軌道顏色一致
    textColor = mTypedArray.getColor(R.styleable.ProgressBarsView_textColor, Color.WHITE); // 每條進(jìn)度文字顏色一致
    rate = mTypedArray.getInteger(R.styleable.ProgressBarsView_rate, 50); // 每條進(jìn)度刷新速率一致
    progressGap = mTypedArray.getDimension(R.styleable.ProgressBarsView_progressGap, 10); // 每條進(jìn)度彼此間隔一致
    roundWidth = mTypedArray.getDimension(R.styleable.ProgressBarsView_roundWidth, 20); // 每條進(jìn)度寬度一致
    onlyFirstShowCenter = mTypedArray.getBoolean(R.styleable.ProgressBarsView_onlyFirstShowCenter, true); // 總體是否只顯示第一條進(jìn)度的中心文案
    isShowTopTextAndPoint = mTypedArray.getBoolean(R.styleable.ProgressBarsView_isShowTopTextAndPoint, true); // 每條進(jìn)度是否顯示頂部齊平的文案和圓點(diǎn)
}

2、控件部署

<com.xs.lightpuzzle.demo.a_circle_progress_bar_demo.ProgressBarsView
    android:id="@+id/team_diary_data_progress_rate_bar"
    android:layout_width="180dp"
    android:layout_height="180dp"
    progress_bars:rate="20"
    progress_bars:roundWidth="8dp"
    progress_bars:progressGap="3dp"
    progress_bars:onlyFirstShowCenter="true"
    progress_bars:isShowTopTextAndPoint="true"
    progress_bars:textColor="@color/progress_text_color"
    progress_bars:progressTrackColor="@color/progress_track_color"/>
public void addProgressBar(String text, int progressColor, int maxProgress, int progress) {
    ViewHolder holder = new ViewHolder();
    holder.rate = rate;
    holder.roundWidth = roundWidth;
    holder.progressTrackColor = progressTrackColor;
    holder.isShowTopTextAndPoint = isShowTopTextAndPoint;
    holder.textColor = textColor;

    holder.max = maxProgress;
    holder.progress = progress;
    holder.text = text;
    holder.roundProgressColor = progressColor;
    list.add(holder);
}
public void showProgress() {
    this.removeAllViews();

    for (int i = 0; i < list.size(); i++) {
        ViewHolder holder = list.get(i);
        ChildProgressBar progressBar = new ChildProgressBar(mContext);
        progressBar.setText(holder.text);
        progressBar.setTextColor(holder.textColor);
        progressBar.setCricleColor(holder.progressTrackColor);
        progressBar.setCricleProgressColor(holder.roundProgressColor);
        progressBar.setRoundWidth(holder.roundWidth);
        progressBar.setRate(holder.rate);
        progressBar.setMax(holder.max);
        progressBar.setProgress(holder.progress);
        progressBar.setTextIsDisplayable(i == 0 && onlyFirstShowCenter); // 是否繪制圓中心文案
        progressBar.setShowTextHint(holder.isShowTopTextAndPoint); // 是否繪制與圓頂部平行的文字和圓點(diǎn)

        float totalMargin = (roundWidth + progressGap) * i;
        LayoutParams params = new LayoutParams(
                android.view.ViewGroup.LayoutParams.MATCH_PARENT,
                android.view.ViewGroup.LayoutParams.MATCH_PARENT);
        params.setMargins((int) totalMargin, (int) totalMargin,
                (int) totalMargin, (int) totalMargin);
        this.addView(progressBar, params);

        progressBar.updateBar();
    }
}

三、一鍵部署IWatch環(huán)形進(jìn)度條自定義控件

progressbar.addProgressBar("勝", 0XBBEA595C, 100, 80);
progressbar.addProgressBar("平", 0XBBD6A20E, 100, 90);
progressbar.addProgressBar("負(fù)", 0XBBD7D5DA, 100, 30);
progressbar.showProgress();
仿IWatch環(huán)形進(jìn)度條嵌套集合.png
IWatch環(huán)形進(jìn)度條控件.jpg
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容