高效的構(gòu)建一個(gè)進(jìn)度圖表視圖

基于 Android 系統(tǒng)視圖繪制原理與事件分發(fā)機(jī)制我們可以構(gòu)造出系統(tǒng)組件之外的視圖類以滿足特定產(chǎn)品需求,這是一個(gè)龐大但過程明確的體系,本文從實(shí)踐出發(fā),通過實(shí)現(xiàn)一個(gè)圓形進(jìn)度視圖介紹怎樣使用 Paint 工具在 View 的 onDraw 階段繪制出想要的自定義 View 以及這其中的思考方法與最佳實(shí)踐,最后得到打包后僅8KB但功能強(qiáng)大的 View 庫。

Github 源碼地址:https://github.com/timqi/SectorProgressView

SectorProgressView

ColorfulRingProgressView

為實(shí)現(xiàn)某個(gè)特定的視圖效果通常需要先對它進(jìn)行分解,分解的足夠細(xì)以至于和已有的工具(如SDK API)完美對接在一起則離實(shí)現(xiàn)目標(biāo)就完成一大半了。

帶著分解的思路我們首先看第一個(gè)示例 SectorProgressView。雖然很簡單,但我們?nèi)匀豢梢杂胁煌姆纸夥椒?,比?/p>

  1. 先用背景色畫一個(gè)圓
  2. 再用前景色根據(jù)進(jìn)度繪制扇形區(qū)域

或者:

  1. 使用前景色繪制出表示進(jìn)度的部分
  2. 使用背景色繪制剩余扇形部分

上面兩種分解思路都能輕松的實(shí)現(xiàn)目的,但是比較其中異同我們發(fā)現(xiàn)后一種方法相較前一種方法避免了表示進(jìn)度的前景色部分的重繪,這在某些高性能要求的情況下是要考慮的,當(dāng)然今天這種簡單的控件我們選擇第一種方案。

要實(shí)現(xiàn)這個(gè)方案我們需要拿到繪制所需的參數(shù),接下來就要分析需要哪些參數(shù)來畫背景圓,哪些參數(shù)來畫扇形進(jìn)度區(qū)域:

  • 背景色值
  • 前景色值
  • 進(jìn)度的百分比的值
  • 進(jìn)度區(qū)域開始時(shí)的角度
  • 描述圓位置的矩形區(qū)域

要拿到上面描述的參數(shù),結(jié)合 Android SDK 提供的方法我們可以在構(gòu)造函數(shù),onSizeChanged 中獲得,于是編寫代碼:

public class SectorProgressView extends View {
  private int bgColor;
  private int fgColor;
  private float percent;
  private float startAngle;
  private RectF oval;

  private ObjectAnimator animator;

  public SectorProgressView(Context context, AttributeSet attrs) {
    super(context, attrs);
    TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
        R.styleable.SectorProgressView,
        0, 0);

    try {
      bgColor = a.getColor(R.styleable.SectorProgressView_bgColor, 0xffe5e5e5);
      fgColor = a.getColor(R.styleable.SectorProgressView_fgColor, 0xffff765c);
      percent = a.getFloat(R.styleable.SectorProgressView_percent, 0);
      startAngle = a.getFloat(R.styleable.SectorProgressView_startAngle, 0) + 270;

    } finally {
      a.recycle();
    }
    
    init();
  }

  @Override
  protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);

    float xpad = (float) (getPaddingLeft() + getPaddingRight());
    float ypad = (float) (getPaddingBottom() + getPaddingTop());

    float wwd = (float) w - xpad;
    float hhd = (float) h - ypad;

    oval = new RectF(getPaddingLeft(), getPaddingTop(), getPaddingLeft() + wwd, getPaddingTop() + hhd);
  }

  private void refreshTheLayout() {
    invalidate();
    requestLayout();
  }

  ...
}

繼承 View 類編寫構(gòu)造函數(shù),監(jiān)聽 onSizeChanged 方法以獲取我們需要的參數(shù)。同時(shí)為這些參數(shù)添加 getter,setter 方法,在 setter 方法中調(diào)用 refreshTheLayout 觸發(fā)繪制以及時(shí)看到效果,最后在 onDraw 函數(shù)中繪制圖形

private void init() {
  bgPaint = new Paint();
  bgPaint.setColor(bgColor);

  fgPaint = new Paint();
  fgPaint.setColor(fgColor);
}

@Override
protected void onDraw(Canvas canvas) {
  super.onDraw(canvas);

  canvas.drawArc(oval, 0, 360, true, bgPaint);
  canvas.drawArc(oval, startAngle, percent * 3.6f, true, fgPaint);
}

onDraw 方法非常簡單,我們也應(yīng)該在所有的情況下保證 onDraw 方法足夠簡單,甚至包括精簡申請初始化變量這種操作,盡可能減少 CPU 世間與內(nèi)存占用。

通常我們認(rèn)為 Android 手機(jī)以 60fps 的幀率運(yùn)行是流暢的,也就是說手機(jī)屏幕要每秒刷新 60 次。也就是要想保證 60fps 的幀率需要重繪的所有操作在 16ms 內(nèi)完成。這些操作不僅包括了當(dāng)前 View 的 onDraw 方法,還有其他 View 的,還包括一些布局等計(jì)算,所以我們應(yīng)該盡可能保證 onDraw 方法足夠簡單。

最后為視圖添加一個(gè)無限循環(huán)的動(dòng)畫。動(dòng)畫本質(zhì)即是一系列繪制屬性關(guān)于時(shí)間的函數(shù),進(jìn)度無限循環(huán)的動(dòng)畫就是 startAngle 屬性在時(shí)間上連續(xù)不斷改變的結(jié)果。同時(shí) Android SDK 也提供了很多用于構(gòu)建動(dòng)畫的類,比如 ObjectAnimator,雖然 startAngle 是一個(gè)自定義的屬性,但是受益于 ObjectAnimator 使用反射的靈活,為 startAngle 提供 getter,setter 方法后依然可以使用 ObjectAnimator。

public void animateIndeterminate(int durationOneCircle,
                                 TimeInterpolator interpolator) {
  animator = ObjectAnimator.ofFloat(this, "startAngle", getStartAngle(), getStartAngle() + 360);
  if (interpolator != null) animator.setInterpolator(interpolator);
  animator.setDuration(durationOneCircle);
  animator.setRepeatCount(ValueAnimator.INFINITE);
  animator.setRepeatMode(ValueAnimator.RESTART);
  animator.start();
}

對于 ColorfulRingProgressView 同樣適用上面的思路

繪制分解 -> 分析所需參數(shù) -> 獲取參數(shù) -> draw

當(dāng)然,分析的步驟需要了解 Framework 已經(jīng)為我們提供了什么功能,比如 Paint,Canvas。熟悉已有的高效實(shí)現(xiàn)的 API 有助于我們快速構(gòu)建優(yōu)質(zhì)代碼。

完整源碼請看:https://github.com/timqi/SectorProgressView

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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