2016年总结

没有一点点防备,2016 年就这么过去了,写完 bugs 关掉 AS,现在已经是深夜 1 点。虽然 16 年已经过去,不过总结还是要写一下的。没做到的就不说了,反正也没做到,还说它干嘛。

做到了哪些,或者说取得了哪些小成就?

技术上:

  1. 从15年下半年接触开源社区,算一算现在也有近一年半的时间了。当然做出了些可能能被认可的小贡献基本上也是16年的产出。106 个 Followers,2100+ Star。虽说这些数据和技术水平不能画等号,不过确实通过在开源社区的锻炼,自己的技能水平得到了很大的提高。
  2. 可能看了有几百篇技术文章,知识面拓展了不少。
  3. 写了几个项目。

生活上:

  1. 和女友关系慢慢稳定,准备在2017年下旬结婚。
  2. 买了套房。

2017年的小目标

  1. 巩固基础
  2. 算法
  3. 玩转Vue
  4. 职业规划上的一些计划
  5. 把女友娶回家

总结

其实没做到的有太多太多,Python,JavaScript,都没有太深入的去看。三天打鱼,两天晒网,最后什么都没学会。2017年少些口水,踏实做事吧。

GearMachine Canvas绘制漂亮的齿轮装置

前言

无意中看到一加网站上有一个漂亮的齿轮装置动画 www.oneplus.cn/3years,第一眼惊艳,于是想尝试一下在 Android 下利用 Canvas 的相关 API 以纯代码的方式绘制出来。

本文主要记录一下开发这个控件的过程,如果不了解 Canvas , Paint , Path 等相关概念的小伙伴,请先翻看官方文档进行学习。

临摹的效果来源于一加,关于设计部分的版权归一加所有。

先看实现的效果

Gif动图录制较卡顿,更清晰流畅的效果视频请移步 Youtube



效果分析

  1. 首先确定四个关键点,即:
    左上角齿轮中心,右上角齿轮中心,和中线上的两个齿轮中心
  2. 左上角和右上角的齿轮,需要三角形的锯齿效果。
    下方的大齿轮和小齿轮需要梯形的锯齿效果。
    纵轴上的两个齿轮需要圆形的小孔
  3. 传送带
  4. 最下方齿轮中心的阴影效果

分析完毕

首先获取几个关键点 计算的方式是按照位置占全图宽高的比值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

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

int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
int paddingTop = getPaddingTop();
int paddingBottom = getPaddingBottom();

// 横向有效宽度
int validWidth = getWidth() - paddingLeft - paddingRight;
// 纵向有效高度
int validHeight = getHeight() - paddingTop - paddingBottom;

// 左上齿轮中心
mLeftTopCentre[X] = (float) (paddingLeft + validWidth * 0.237);
mLeftTopCentre[Y] = (float) (paddingTop + validHeight * 0.102);

// 右上齿轮中心
mRightTopCentre[X] = (float) (paddingLeft + validWidth * 0.763);
mRightTopCentre[Y] = (float) (paddingTop + validHeight * 0.102);

// 中上齿轮中心
mTopCentre[X] = (float) (paddingLeft + validWidth * 0.5);
mTopCentre[Y] = (float) (paddingTop + validHeight * 0.311);

// 中下齿轮中心
mBottomCentre[X] = (float) (paddingLeft + validWidth * 0.5);
mBottomCentre[Y] = (float) (paddingTop + validHeight * 0.597);
}

使用 Canvas 的相关 API 绘制左上角齿轮
最上方两个齿轮效果除了动画转动的方向不同,其他的相同,所以我们现在以左上角的齿轮为例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

// 左上齿轮
// 最底层(半径最大)的红色圆的背景
canvas.drawCircle(mLeftTopCentre[X], mLeftTopCentre[Y], mTopCircleRadius, mRedPaint);

// 半径第二大的深红色圆背景
canvas.drawCircle(mLeftTopCentre[X], mLeftTopCentre[Y], mTopCircleRadius / 4 * 3, mDarkRedPaint);
canvas.save();

// 动画需要,根据mCurProgress的值来做画布旋转
canvas.rotate(mCurProgress * 360, mLeftTopCentre[X], mLeftTopCentre[Y]);

// 绘制24个三角齿轮
for (int i = 0; i < 24; i ++) {

// 齿轮的三角 path
canvas.drawPath(mLeftTopTrianglePath, mRedPaint);

// 旋转角为15度,绘制24个三角形
canvas.rotate(15.0f, mLeftTopCentre[X], mLeftTopCentre[Y]);
}
canvas.restore();

// 绘制其他圆形
canvas.drawCircle(mLeftTopCentre[X], mLeftTopCentre[Y], mTopCircleRadius / 2, mRedPaint);
canvas.drawCircle(mLeftTopCentre[X], mLeftTopCentre[Y], mTopCircleRadius / 5 * 2, mDarkRedPaint);
canvas.drawCircle(mLeftTopCentre[X], mLeftTopCentre[Y], mTopCircleRadius / 4, mRedPaint);

注意
下方最大的齿轮相对特殊,原因是有一个 130 度的缺口,并且锯齿也需要转动的动画,所以此处我先绘制了一个大空心圆,并使用 drawArc() 绘制扇形以初始角度为 180 度,扇形圆心角 130 度再搭配使用 PorterDuffXfermode(PorterDuff.Mode.CLEAR) 来进行叠加,此时就可以绘制出 130 度缺口的大齿轮了。

梯形
至于梯形的绘制使用 Path 连线 4 个点后 并每次旋转画布 20 度,动态绘制 18 份就可以完成大齿轮的锯齿显示。

传送带
传送带的效果,利于 drawLine(float startX, float startY, float stopX, float stopY, Paint paint) 可绘制两个点的连线。

阴影
如果仔细观察,你会发现在下方大齿轮的中心,其实是存在阴影效果的。此时我选用的方案是利用 View 的 setLayerType(int layerType, Paint paint) 方法搭配使用 Paint 的 setShadowLayer(float radius, float dx, float dy, int shadowColor) 方法实现阴影效果。

有了以上的分析与实现,剩下的部分可以通过举一反三的方式很轻松的绘制出来

绘制完成,怎么能让齿轮转动起来呢?
这次实现的效果其实在动画方面可以说非常简单,只要让齿轮能转起来就好了嘛。齿轮转动无外乎从以圆心为准做 0 度到 360 度旋转就好了。咦,数值的变化,是不是脑海里第一想到的就是使用 ValueAnimator ?没错。

1
2
3
4
5
6
7
8
9
10
11
12
13

mValueAnimator = ValueAnimator.ofFloat(0f, 1f);
mValueAnimator.setRepeatCount(ValueAnimator.INFINITE);
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mCurProgress = (float) valueAnimator.getAnimatedValue();
postInvalidate();
}
});
mValueAnimator.setDuration(5000);
mValueAnimator.setInterpolator(new LinearInterpolator());
mValueAnimator.start();

动画时长 5 秒,设置重复模式为无限循环 (INFINITE) 。虽然此处的 AnimatedValue 的范围是 [0f,1f] ,不过只要再与 360 相乘即可获得画布旋转的角度。

Talk is cheap, show me the code. 完整代码已开源至 Github

####后记

关于这个漂亮的齿轮装置效果的实现就到此结束了。其实把一个复杂的效果拆分成小模块,问题就会变得非常容易解决。
笔者要继续去抢春运火车票啦。:grimacing:

修复了 MultiStateLayout的一个无child view导致的crash的bug后。
一时睡不着,想起来了11月初一件有意思的事情。
和往常一样,按时收取下邮件。2号那天看到未读邮件中一封标题中含有Facebook is hiring的字眼,顿时要炸了,心想Facebook hr也会看走眼,😀,仔细阅读了下全文,不确定后又用机器翻译全文,最终确定的确是面试邀约。后来仔细了解了一下,发现工作地点都在美帝。
小伙伴建议试试,但是想了下自己口语烂的要命,除了知道少部分的计算机术语外,还真没勇气来一次口语对答,加上工作地点的原因最终还是放弃了这次机会(其实还是觉得自己技术不过关吧 😀)。不过在此之后或许也能在知乎上回答

“收到Facebook面试邀约是种什么样的体验?”

这类的问题了。

年底前的点技能计划

已到9月。一年已过大半,也忘记了年初定下的目标(#说到底还是身体很诚实😑#)
最近的一些计划还是多开阔些视野。工作到现在一直在做移动端开发,说到底眼界还是严重不足。话说吃哪补哪,那最近先把薄弱的部分补起来吧。

Material Design

关于官方和第三方开发者提供的符合Material Design控件/动画,不敢说都用过,但是至少见过的不少。但是真正实战上却还是略显不足,最近的计划之一也是打算把这一部分知识梳理一遍。(#或许会撸个APP?#)

Python

和Python这门语言说来还是有些渊源的。除了初中时写过一点点易语言,Python也算是我的(未)入门语言。高中时,Symbian S60 v3/v5相当流行,且Python提供了支持这些系统的平台组件。那时玩手机比看书还认真的我,没事就喜欢研究下用Python写的小软件,那时感觉在手机上能写软件真是相当diao的样子。后来因为Android兴起,而我又是三分钟热度,也就扔下了,话说真是丢掉了打好Python基础的好机会。
最近也是打算把这门语言重新捡起来,Python这门语言入门真是太简单了,有其他语言基础,一个小时就能简单入门。这也是某些教育家推荐Python作为学生的学习编程的首选语言的原因?
题跑偏,最近还是要把Python好好的学一下,算是弥补下高中时代的遗憾吧。

关于问道

问道十年,初一就开始玩,大概玩了3年+。今年初问道的手游也出了,现在慢慢升80级中……也在慢慢的在做80级装备,还是坚持做不花钱玩家。😆

最近的一点感悟

见的越多,知道的越多,有些事,就越不敢迈步。

这是最近看到的一句话,感同身受。并不是因为自大而说自己已经了解到了足够多的知识,而是站在比以前更高的地方,有更宽阔的视野时,发现自己比想象中若小的真实写照。