高效的构建一个进度图表视图

基于 Android 系统视图绘制原理与事件分发机制我们可以构造出系统组件之外的视图类以满足特定产品需求,这是一个庞大但过程明确的体系,本文从实践出发,通过实现一个圆形进度视图介绍怎样使用 Paint 工具在 View 的 onDraw 阶段绘制出想要的自定义 View 以及这其中的思考方法与最佳实践,最后得到打包后仅8KB但功能强大的 View 库。

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

SectorProgressView

ColorfulRingProgressView

为实现某个特定的视图效果通常需要先对它进行分解,分解的足够细以至于和已有的工具(如SDK API)完美对接在一起则离实现目标就完成一大半了。

带着分解的思路我们首先看第一个示例 SectorProgressView。虽然很简单,但我们仍然可以有不同的分解方法,比如

  1. 先用背景色画一个圆
  2. 再用前景色根据进度绘制扇形区域

或者:

  1. 使用前景色绘制出表示进度的部分
  2. 使用背景色绘制剩余扇形部分

上面两种分解思路都能轻松的实现目的,但是比较其中异同我们发现后一种方法相较前一种方法避免了表示进度的前景色部分的重绘,这在某些高性能要求的情况下是要考虑的,当然今天这种简单的控件我们选择第一种方案。

要实现这个方案我们需要拿到绘制所需的参数,接下来就要分析需要哪些参数来画背景圆,哪些参数来画扇形进度区域:

  • 背景色值
  • 前景色值
  • 进度的百分比的值
  • 进度区域开始时的角度
  • 描述圆位置的矩形区域

要拿到上面描述的参数,结合 Android SDK 提供的方法我们可以在构造函数,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 类编写构造函数,监听 onSizeChanged 方法以获取我们需要的参数。同时为这些参数添加 getter,setter 方法,在 setter 方法中调用 refreshTheLayout 触发绘制以及时看到效果,最后在 onDraw 函数中绘制图形

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 方法非常简单,我们也应该在所有的情况下保证 onDraw 方法足够简单,甚至包括精简申请初始化变量这种操作,尽可能减少 CPU 世间与内存占用。

通常我们认为 Android 手机以 60fps 的帧率运行是流畅的,也就是说手机屏幕要每秒刷新 60 次。也就是要想保证 60fps 的帧率需要重绘的所有操作在 16ms 内完成。这些操作不仅包括了当前 View 的 onDraw 方法,还有其他 View 的,还包括一些布局等计算,所以我们应该尽可能保证 onDraw 方法足够简单

最后为视图添加一个无限循环的动画。动画本质即是一系列绘制属性关于时间的函数,进度无限循环的动画就是 startAngle 属性在时间上连续不断改变的结果。同时 Android SDK 也提供了很多用于构建动画的类,比如 ObjectAnimator,虽然 startAngle 是一个自定义的属性,但是受益于 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 同样适用上面的思路

绘制分解 -> 分析所需参数 -> 获取参数 -> draw

当然,分析的步骤需要了解 Framework 已经为我们提供了什么功能,比如 Paint,Canvas。熟悉已有的高效实现的 API 有助于我们快速构建优质代码。

完整源码请看:https://github.com/timqi/SectorProgressView

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,978评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,954评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,623评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,324评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,390评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,741评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,892评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,655评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,104评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,451评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,569评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,254评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,834评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,725评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,950评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,260评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,446评论 2 348

推荐阅读更多精彩内容